https://d226lax1qjow5r.cloudfront.net/blog/blogposts/create-a-basic-video-chat-app-with-asp-net-and-angular-dr/Blog_ASP-NET_Angular_1200x600.png

Crear una aplicación básica de Video Chat con ASP.NET y Angular

Publicado el November 5, 2020

Tiempo de lectura: 11 minutos

Angular es, con diferencia, el framework de aplicaciones de una sola página (SPA) más popular utilizado por los desarrolladores .NET. Hasta este proyecto, no había usado Angular desde antes de JS de su nombre. En consecuencia, estaba emocionado de probarlo de nuevo, esta vez usándolo para mostrar la Video API de Vonage. Puedes seguir este tutorial, en el que crearemos una aplicación básica de videochat usando Angular (aunque siempre tendrá el JS en mi ❤).

Requisitos previos

Directo al código

Si sólo quieres bajar el código de este tutorial, visita la página de GitHub de esta entrada de blog, sigue las instrucciones y estarás listo.

Lo primero es lo primero

Empecemos abriendo Visual Studio. Haga clic en Crear un nuevo proyecto -> Aplicación Web ASP.NET Core -> dale un nombre (yo voy a llamar al mío BasicVideoChatAngular) -> Crear -> Angular.

Haciendo esto se va a construir una aplicación ASP.NET shell con todo el código del lado del cliente en la carpeta ClientApp carpeta.

Importar paquetes Nuget

Importe los siguientes paquetes NuGet para este proyecto:

  • OpenTok

  • Microsoft.EntityFrameworkCore.SqlServer (Estoy usando 3.1.3)

Crear el modelo de entidad

Vamos a utilizar algunos muy básico Entity Framework aquí. Añadir un Model.cs al proyecto. Eliminar la declaración de clase y añadir el siguiente código a la misma:

public class OpentokContext : DbContext
{
    public DbSet<Room> Rooms { get; set; }
    protected override void OnConfiguring(DbContextOptionsBuilder options)
        => options.UseSqlite("Data Source=VonageVideo.db");
}
public class Room
{
    public int RoomId { get; set; }
    public string SessionId { get; set; }
    public string RoomName { get; set; }
    public string Token { get; set; }
}

También tendrás que añadir lo siguiente a tu sección de uso:

using Microsoft.EntityFrameworkCore;

Crear la base de datos

Con esto añadido, vamos a crear la base de datos. Navega a la carpeta de tu proyecto y ejecuta lo siguiente:

dotnet tool install --global dotnet-ef dotnet add package Microsoft.EntityFrameworkCore.Design dotnet ef migrations add InitialCreate dotnet ef database update

Esto creará un archivo de base de datos que contendrá sus salas y sessionIds.

Crear un controlador de sesión

Haga clic con el botón derecho en la Controllers carpeta -> Añadir -> Controlador - Controlador MVC - Vacío -> nombrarlo SessionController.

En la dependencia SessionControllerinyectamos un objeto IConfiguration, y creamos una clase elemental para guardar el nombre de nuestra habitación llamada RoomForm:

private IConfiguration _Configuration;
public SessionController(IConfiguration config)
{
    _Configuration = config;
}
public class RoomForm
{
    public string RoomName { get; set; }
}

Después de esto, añade una petición HttpPost llamada GetSession que toma un RoomForm como argumento:

[HttpPost]
public IActionResult GetSession([FromBody]RoomForm roomForm)
{
    var apiKey = int.Parse(_Configuration["ApiKey"]);
    var apiSecret = _Configuration["ApiSecret"];
    var opentok = new OpenTok(apiKey, apiSecret);
    var roomName = roomForm.RoomName;
    string sessionId;
    string token;
    using (var db = new OpentokContext())
    {
        var room = db.Rooms.Where(r => r.RoomName == roomName).FirstOrDefault();
        if (room != null)
        {
            sessionId = room.SessionId;
            token = opentok.GenerateToken(sessionId);
            room.Token = token;
            db.SaveChanges();
        }
        else
        {
            var session = opentok.CreateSession();
            sessionId = session.Id;
            token = opentok.GenerateToken(sessionId);
            var roomInsert = new Room
            {
                SessionId = sessionId,
                Token = token,
                RoomName = roomName
            };
            db.Add(roomInsert);
            db.SaveChanges();
        }
    }
    return Json(new { sessionId = sessionId, token = token, apiKey = _Configuration["ApiKey"] });
}

Este método comprueba en la base de datos si el roomName ya tiene un sessionId. Si lo tiene, genera un token para ese sessionId. En caso contrario, crea una nueva sesión y un nuevo identificador. A continuación, crea una nueva fila en la base de datos para esa habitación. En ambos casos, devuelve un sessionId, un token y una ApiKey como JSON.

Construir el cliente

Con el back-end fuera del camino, vamos a pasar a la construcción del cliente. Vamos a tener dos puntos de vista primarios-nuestro Unirse donde el usuario introducirá el nombre de la sala a la que desea unirse:

Chat App Join ViewChat App Join View

Y un Video que contendrá la videollamada:

Chat App Video ViewChat App Video View

Instalar dependencias npm

En primer lugar, vaya al directorio ClientApp en tu terminal y ejecútalo:

npm install --save @opentok/client

Limpiar vistas de demostración

Cuando se crea un proyecto Angular en Visual Studio, un montón de componentes de demostración son auto-poblada bajo ClientApp\src\app, incluyendo counter, fetch-data, homey nav-menu. No vamos a necesitar ninguno de ellos, así que vamos a eliminarlos todos de un plumazo.

Añadir archivos necesarios

Crea las siguientes carpetas/archivos:

  • En ClientApp\src añadir config.ts

  • En ClientApp\src\app añadir stateService.ts

  • En ClientApp\src\app crear directorios: join, subscriber, video

  • En ClientApp\src\join crear join.component.css, join.component.html, join.component.ts

  • En ClientApp\src\subscriber crear subscriber.component.html, subscriber.component.ts, subscriber.component.css

  • En ClientApp\src\video crear video.component.css, video.component.html, video.component.ts

Cree la configuración

En ClientApp\src\config.ts, vamos a establecer nuestra configuración, que contiene un campo , SAMPLE_SERVER_BASE_URL. Establecer esto a la URL de base que terminan utilizando para IIS-el archivo debe tener este aspecto:

export default {
    SAMPLE_SERVER_BASE_URL: 'https://localhost:44340'
}

Si está utilizando IIS Express para depurar, encuentre el archivo base_url haciendo clic con el botón derecho en el archivo del proyecto -> Propiedades -> Depurary en la parte inferior verás las URLs de IIS.

Construir el EstadoServicio

Vamos a tener una transición entre componentes después de hacer clic en el botón Join. Tendremos que llevar el token, sessionId y ApiKey entre el componente Join y el componente Video para que el componente Video pueda unirse a la llamada. Para resolver este problema, vamos a compartir este estado utilizando un servicio de estado: inyectaremos el servicio de estado en el siguiente componente cuando realicemos la transición entre ellos. Sólo necesitamos un Inyectable para lograr esto con algunos campos observables:

import { Injectable } from "@angular/core";
@Injectable({providedIn:'root'})
export class StateService {
    public token$: string;
    public sessionId$: string;
    public apiKey$: string;
    constructor() {}
}

Nota: En esta etapa, es posible que aparezca un error de IntelliSense "El soporte experimental para decoradores es una característica que está sujeta a cambios en una versión futura. Establezca la opción 'experimentalDecorators' para eliminar esta advertencia" Para resolverlo, debe establecer la acción de compilación de ClientApp\tsconfig.json a Content, y es posible que tenga que reiniciar Visual Studio.

Construir el componente de abonado

El componente Suscriptor será el componente responsable de mantener el flujo de vídeo entrante. Para construirlo, elimine todo el HTML preañadido de subscriber.component.html y añada esta línea:

<div class="subscriber-div" #subscriberDiv></div>

Sólo contendrá un div, que servirá de destino para el flujo entrante.

Ahora en subscriber.component.css, añade un par de estilos:

.subscriber-div {
  height: 100%;
  width: 100%;
  position: fixed;
  top:50px;
  bottom: 0;
  left: 0;
  z-index: 0;
}
.container {
  
  background: black;
  color: white;
  height: 100%;
}

Este CSS hará que el componente ocupe toda la pantalla y lo empujará a la parte inferior del z-index, lo que evitará que sobrepase al vídeo del editor, que aparecerá como un PIP en la parte inferior.

En el subscriber.component.ts filevamos a crear un componente con una sesión y una entrada de flujo. También necesita una referencia al elemento SubscriberDiv de la plantilla HTML, así como una sesión y un flujo que obtendremos del componente Video. Por último, necesita un método subscribe para suscribirse a un flujo de sesión cuando se produzca el evento onStreamCreate se produzca el evento. Añade el siguiente código al archivo:

import { Component, ElementRef, ViewChild, Input } from '@angular/core';
import *  as OT from '@opentok/client';
@Component({
  selector: 'app-subscriber',
  templateUrl: './subscriber.component.html',
  styleUrls: ['./subscriber.component.css']
})
export class SubscriberComponent {
  @ViewChild('subscriberDiv', { static: true }) subscriberDiv: ElementRef;
  @Input() session: OT.Session;
  @Input() stream: OT.Stream;
  constructor() { }
  subscribe(): void {
    const subscriber = this.session.subscribe(this.stream, this.subscriberDiv.nativeElement, {
      insertMode: "append",
      width: "100%",
      height: "100%"
    }, (err) => {
      if (err) {
        alert(err.message);
      }
    });
  }
}

Construir el componente Video

Empecemos con el archivo video.component.html archivo. Primero borre cualquier html auto-generado añadido a este archivo. A continuación, agregue la plantilla:

<div class="publishingDiv" [ngClass]="{'publishing': publishing}" #publisherDiv></div>
<div>  
  <ng-template #subscriberHost></ng-template>
</div>

El publishingDiv será el ancla en el DOM que vamos a utilizar para la fuente de vídeo de nuestro editor. La plantilla subscriberHost será donde se añada nuestro suscriptor cuando se una a una llamada. En el archivo CSS, vamos a eliminar cualquier CSS auto-generado. Añadir estilos que se establecerá el publishingDiv en la esquina inferior izquierda de la pantalla en una posición fija, que ocupe el 25% de la altura y la anchura de la ventana, y se sitúe en un z-index de 1 (inmediatamente por encima de donde ponemos nuestro subscriberDiv). Añade lo siguiente al archivo video.component.css archivo:

.publishingDiv {
  height: 25%;
  width: 25%;
  left: 0;
  bottom: 0;
  position: fixed;
  z-index: 1;
}

Por último, tenemos que configurar el propio componente. ¿Recuerdas el StateService de antes? Vamos a inyectarlo; desde él, obtendremos el sessionId, el token y el ApiKey del SessionController que creamos antes.

Importación y aderezo del componente

Primero, importa todo lo que vamos a necesitar y construye la clase VideoComponent clase.

import { ViewContainerRef, Component, ElementRef, AfterViewInit, ViewChild, ComponentFactoryResolver, OnInit } from '@angular/core';
import * as OT from '@opentok/client';
import { SubscriberComponent } from '../subscriber/subscriber.component';
import { StateService } from '../stateService';
import { Router } from '@angular/router';
@Component({
  selector: 'app-video',
  templateUrl: './video.component.html',
  styleUrls: ['./video.component.css']
})
export class VideoComponent implements AfterViewInit, OnInit {
}

Campos y constructores de componentes

A continuación, configure algunos campos para la clase VideoComponent y el constructor. En el constructor, inyectamos un campo ComponentFactoryResolverque utilizaremos más adelante para obtener la referencia nativa del subscriberHost, y el campo StateServiceque es de donde sacaremos nuestra apiKey, token y sessionId. El Router nos ayudará a enrutar entre componentes en nuestro proyecto; específicamente, lo necesitarás para navegar de vuelta al controlador join si el servicio de estado no contiene un apiKey, token, o sessionId.

Dentro de la clase VideoComponent añada lo siguiente:

@ViewChild('publisherDiv', { static: false }) publisherDiv: ElementRef;
@ViewChild('subscriberHost', { read: ViewContainerRef, static: true }) subscriberHost: ViewContainerRef;
session: OT.Session;
publisher: OT.Publisher;
publishing;
apiKey: string;
token: string;
sessionId: string;
constructor(
  private componentFactoryResolver: ComponentFactoryResolver,
  private stateService: StateService,
  private router: Router
) { }

Lógica On Init

A continuación, configuraremos la función ngOnInit la función El StateService se inyecta inmediatamente después de init, así que ahí es donde vamos a agarrar el apiKey, token, y sessionId. Esta función va a almacenar esos elementos. Si alguno de ellos no existe, vamos a redirigir a la página Join.

ngOnInit(): void {
  if (!this.stateService.apiKey$ || !this.stateService.token$ || !this.stateService.sessionId$) {
    this.router.navigate(['/']);
  }
  this.apiKey = this.stateService.apiKey$;
  this.token = this.stateService.token$;
  this.sessionId = this.stateService.sessionId$;
}

Publicar el flujo de usuarios

A continuación, vamos a configurar el método de publicación. Vamos a llamarlo después de que la vista termine de inicializarse. Esta función llamará al método publish de la sesión, pasando el elemento publisher. Cambiará el campo de publicación a true cuando se resuelva la llamada de retorno. Añade lo siguiente después de ngOnInit:

publish() {
    this.session.publish(this.publisher, (err) => {
      if (err) {
        console.log(err)
      }
      else {
        this.publishing = true;
      }
    });
  }

Gestionar la creación de un flujo

Una vez creado el flujo, tendremos que suscribirnos a él. La forma en que lo haremos es tomando la referencia a la plantilla de suscriptor que creamos en el HTML, inicializando un componente Suscriptor para él, asignándole el flujo y el Id de sesión, y diciéndole que se suscriba. Añade lo siguiente después del método publish:

onStreamCreated(stream) {
  const componentFactory = this.componentFactoryResolver.resolveComponentFactory(SubscriberComponent);
  const viewContainerRef = this.subscriberHost;
  const componentRef = viewContainerRef.createComponent(componentFactory);
  (<SubscriberComponent>componentRef.instance).stream = stream;
  (<SubscriberComponent>componentRef.instance).session = this.session;
  (<SubscriberComponent>componentRef.instance).subscribe();
}

Configurar el editor

Después de inicializar la vista, ngAfterViewInit se dispara. En este punto del ciclo de vida del controlador, tenemos todo lo que necesitamos para entrar en la llamada de video. Vamos a inicializar el editor, inicializar la sesión, conectarse a la sesión, y en la devolución de llamada después de conectarse a la sesión vamos a decirle a nuestro flujo de publicar. También vamos a suscribirnos al evento streamCreated que llamará a la función onStreamCreated que hicimos antes. Añade lo siguiente ngAfterViewInit función:

ngAfterViewInit(): void {
  this.publisher = OT.initPublisher
    (
      this.publisherDiv.nativeElement, {
      height: "100%",
      width: "100%",
      insertMode: 'append'
    });
  this.session = OT.initSession(this.apiKey, this.sessionId);
  this.session.connect(this.token, (err) => {
    if (err) {
      console.log(err);
    }
    else {
      console.log("connected");
      this.publish()
      let that = this;
      this.session.on("streamCreated", function (event) {
        that.onStreamCreated(event.stream);
      });
    }
  })
}

Construir el componente de unión

Una vez creado el componente de vídeo, sólo tenemos que configurar el componente de unión y el módulo de la aplicación.

Configurar el Html

En el archivo join.component.html vamos a crear un archivo joinFormque sólo tendrá una entrada, a roomNameque vamos a utilizar para capturar/generar los sessionId's y los tokens. La plantilla para el componente va a tener este aspecto:

<form class="joinForm" [formGroup]="joinRoomForm" (ngSubmit)="onSubmit(joinRoomForm.value)">
  <div>
    <input placeholder="room name" id="roomName" type="text" formControlName="roomName" align="center">
  </div>
  <button align="center" class="button" type="submit">Join</button>
</form>

Añadir algunos estilos

No vamos a hacer nada demasiado sofisticado con los estilos aquí, sólo vamos a asegurarnos de que el botón y la entrada están centrados y tienen el mismo tamaño. Añade lo siguiente a join.component.css:

form {
  display: normal;
  text-align: center;
  margin: auto;
}
input {
  display: inline-block;
  font-size: inherit;
  padding: .5em;
  margin-bottom: .2em;
  width: 300px;
}
button {
  display: inline-block;
  font-size: inherit;
  padding: .5em;
  width: 300px;
}

Construir el componente

El componente de unión va a tener una función de envío para el formulario de unión, que va a tomar los datos de la sesión de nuestro back-end y enrutar los datos de la sesión al componente de vídeo a través del servicio de estado. Para ello, traerá el HttpClient, FormBuilder, StateServicey Router mediante inyección de dependencias y, a continuación, creará el formulario de sala. A continuación, esperará un onSubmit de joinRoomFormen cuyo momento enviará la respuesta roomName al controlador de sesión y utilizará esa respuesta para crear el componente Video.

import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { FormBuilder } from '@angular/forms';
import { HttpClient } from '@angular/common/http';
import config from '../../config';
import { StateService } from '../stateService';
@Component({
  selector: 'app-join',
  templateUrl: '/join.component.html',
  styleUrls: ['/join.component.css']
})
export class JoinComponent {
  joinRoomForm;
  constructor(
    private http: HttpClient,
    private formBuilder: FormBuilder,
    private stateService: StateService,
    private router: Router) {
    this.joinRoomForm = this.formBuilder.group({
      roomName: ''
    });
  }
  onSubmit(roomData) {
    let get_session_url = config.SAMPLE_SERVER_BASE_URL + '/session/getSession'
    this.http.post(get_session_url, roomData).subscribe(
      (res) => {
        this.stateService.token$ = res['token'];
        this.stateService.sessionId$ = res['sessionId'];
        this.stateService.apiKey$ = res['apiKey'];
        this.router.navigate(['/video'])
      }
    )
  }
}

Configurar la aplicación

Antes de que nada de nuestro Angular funcione, vamos a tener que configurar todo el módulo de la aplicación. Empezaremos por configurar el HTML base. En ClientApp\src\app\app.component.htmlhe añadido un título encima del router-outletlo que garantiza que el título se mostrará en nuestras páginas hijas. Asegúrate también de eliminar el <app-nav-menu></app-nav-menu>ya que existe desde la plantilla original:

<body>
  <div class="container">
    <b><p style="font-size: 34px; text-align:center">Basic Angular Video Chat</p></b>
    <router-outlet></router-outlet>
  </div>
</body>

A continuación, en ClientApp\src\app\app.module.tstenemos que definir nuestro módulo, lo que significa añadir los nuevos componentes que hemos creado, eliminar los componentes que hemos quitado al principio, y establecer las rutas que vamos a querer utilizar. Añade los componentes como imports, y luego en el campo de declaraciones asegúrate de que tienes las etiquetas HttpClientModule, FormsModule, ReactiveFormsModule, y RouterModule en la sección de importación. SubscriberComponent será un componente de entrada. Las rutas tendrán el siguiente aspecto '' -> JoinComponent, video -> VideoComponent, subscriber -> SubscriberComponent.

Configure su aplicación.

Tienes que establecer la configuración en dos lugares, config.ts y appsettings.json. Deberías haber configurado config.ts antes, así que no volveré sobre ello. Para appsettings.tssólo tienes que añadir apiKey y apiSecret como campos y rellénalos con la ApiKey y el ApiSecret de tu cuenta de Video API de Vonage. El archivo tendrá este aspecto:

{
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ApiKey": "",
  "ApiSecret": ""
}

Con eso, ¡ya deberías estar listo! Me encontré con algunos problemas con el versionado de Angular cuando construí la aplicación de demostración por primera vez. package.json de GitHub.

Pruebas

Todo lo que tiene que hacer para probarlo es iniciarlo en IIS Express, pulsando el botón Debug o f5 en Visual Studio. Al hacerlo, se abrirá la página Unirse. Introduzca un nombre de sala y se unirá a una nueva sesión asociada a esa sala. Puede hacer que otro punto final navegue hasta ese mismo punto final y se una a la misma sala, y se unirá a usted en la sala.

Para terminar

Ahora que tienes una estructura básica de una aplicación de videochat en Angular, puedes explorar mucho más con las Video API de Vonage. Puedes grabar sesiones, compartir tus medios, transmitir tus videollamadas y mucho más.

Recursos

  • Consulta nuestra documentación para la Video API de Vonage aquí

  • El código de esta entrada del blog está en GitHub

Compartir:

https://a.storyblok.com/f/270183/384x384/73d57fd8eb/stevelorello.png
Steve LorelloAntiguos alumnos de Vonage

Antiguo desarrollador .NET Advocate @Vonage, ingeniero de software poliglota full-stack, AI/ML