https://a.storyblok.com/f/270183/46459/1e863a7494/blog_asp-net_angular_1200x600.png

Crie um aplicativo básico de bate-papo por vídeo com ASP.NET e Angular

Publicado em November 5, 2020

Tempo de leitura: 11 minutos

O Angular é, de longe, o framework de aplicativos de página única (SPA) mais popular entre os desenvolvedores .NET. Até este projeto, eu não usava o Angular desde antes de ele ter retirado o “JS” do nome. Por isso, fiquei animado para tentar usá-lo novamente, desta vez para demonstrar a Video API da Vonage. Você pode acompanhar este tutorial, no qual criaremos um aplicativo básico de chat por vídeo usando Angular (embora o JS sempre tenha um lugar especial no meu ❤).

Pré-requisitos

Direto ao código

Se você quiser apenas baixar o código deste passo a passo, acesse o página do GitHub desta postagem do blog, siga as instruções e pronto.

O mais importante primeiro

Vamos começar abrindo o Visual Studio. Clique em Criar um novo projeto -> Aplicativo Web ASP.NET Core -> dê um nome a ele (vou chamar o meu de BasicVideoChatAngular) -> Criar -> Angular.

Ao fazer isso, você criará um aplicativo ASP.NET “shell” com todo o seu código do lado do cliente na pasta ClientApp pasta.

Importar pacotes do NuGet

Importe os seguintes pacotes NuGet para este projeto:

  • OpenTok

  • Microsoft.EntityFrameworkCore.SqlServer (estou usando a versão 3.1.3)

Criar o modelo de entidade

Vamos usar algumas noções bem básicas do Entity Framework aqui. Adicione um Model.cs arquivo ao projeto. Exclua a declaração da classe e adicione o seguinte código a ela:

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; }
}

Você também precisará adicionar o seguinte à sua seção “using”:

using Microsoft.EntityFrameworkCore;

Criar o banco de dados

Dito isso, vamos criar o banco de dados. Acesse a pasta do seu projeto e execute o seguinte:

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

Isso criará um arquivo de banco de dados que armazenará suas salas e seus sessionIds.

Criar um controlador de sessão

Clique com o botão direito do mouse na Controllers pasta -> Adicionar -> Controlador - Controlador MVC - Vazio -> nomeie-o SessionController.

No SessionController, injetamos um objeto IConfiguration e criamos uma classe de formulário elementar para conter o nome da nossa sala, chamada RoomForm:

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

Em seguida, adicione uma solicitação HttpPost chamada GetSession que receba um 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 verifica o banco de dados para ver se o `roomName` já possui um `sessionId`. Se tiver, ele gera um token para esse `sessionId`. Caso contrário, ele cria uma nova sessão e um novo token. Em seguida, ele cria uma nova linha no banco de dados para essa sala. Em ambos os casos, ele retorna um `sessionId`, um token e uma `ApiKey` no formato JSON.

Compilar o cliente

Agora que já resolvemos a parte do back-end, vamos começar a desenvolver o cliente. Teremos duas visualizações principais — nossa visualização “Join” , onde o usuário inserirá o nome da sala na qual gostaria de entrar:

Chat App Join ViewChat App Join View

E um Video que conterá a videochamada:

Chat App Video ViewChat App Video View

Instalar dependências do npm

Antes de mais nada, acesse o ClientApp diretório no seu terminal e execute:

npm install --save @opentok/client

Limpar visualizações de demonstração

Ao criar um projeto Angular no Visual Studio, vários componentes de demonstração são inseridos automaticamente na pasta ClientApp\src\app, incluindo counter, fetch-data, home, e nav-menu. Não vamos precisar de nenhum deles, então vamos simplesmente excluir todos logo de cara.

Adicionar os arquivos necessários

Crie as seguintes pastas/arquivos:

  • Em ClientApp\src adicionar config.ts

  • Em ClientApp\src\app adicionar stateService.ts

  • Em ClientApp\src\app crie os diretórios: join, subscriber, video

  • Em ClientApp\src\join criar join.component.css, join.component.html, join.component.ts

  • Em ClientApp\src\subscriber criar subscriber.component.html, subscriber.component.ts, subscriber.component.css

  • Em ClientApp\src\video criar video.component.css, video.component.html, video.component.ts

Configurar a configuração

Neste ClientApp\src\config.ts, vamos definir nossa configuração, que contém um campo, SAMPLE_SERVER_BASE_URL. Defina-o como a URL base que você acabará usando para o IIS — o arquivo deve ficar assim:

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

Se você estiver usando o IIS Express para depuração, localize o base_url clicando com o botão direito do mouse no arquivo do seu projeto -> Propriedades -> Depuração; na parte inferior, você verá as URLs do IIS.

Implementar o StateService

Teremos uma transição entre os componentes depois de clicarmos no botão “Join”. Precisaremos passar o token, o sessionId e a ApiKey do componente “Join” para o componente “Video”, para que este possa participar da chamada. Para resolver esse problema, vamos compartilhar esse estado usando um serviço de estado — injetaremos o serviço de estado no próximo componente à medida que fizermos a transição entre eles. Precisamos apenas de um `Injectable` para fazer isso, com alguns campos observáveis:

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

Observação: Nesta etapa, você pode receber um erro do IntelliSense: “O suporte experimental a decoradores é um recurso sujeito a alterações em uma versão futura. Defina a opção ‘experimentalDecorators’ para remover este aviso.” Para resolver isso, você precisa definir a ação de compilação do ClientApp\tsconfig.json ficheiro para “Conteúdo”, e talvez seja necessário reiniciar o Visual Studio.

Criar o componente de assinante

O componente Subscriber será o responsável por receber o fluxo de vídeo recebido. Para criá-lo, remova todo o código HTML pré-adicionado de subscriber.component.html e adicione esta linha:

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

Ele conterá apenas um elemento `div`, que servirá como destino para o fluxo de dados recebido.

Agora, em subscriber.component.css, adicione alguns 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%;
}

Esse CSS fará com que o componente ocupe toda a tela e o coloque na parte inferior do z-index, o que impedirá que ele se sobreponha ao Video do editor, que aparecerá como PIP na parte inferior.

Neste subscriber.component.ts file, vamos criar um componente com uma sessão e uma entrada de stream. Ele também precisa de uma referência ao elemento SubscriberDiv do modelo HTML, bem como uma sessão e um stream que receberemos do componente de Video. Por fim, ele precisa de um método `subscribe` para se inscrever no stream da sessão quando o onStreamCreate evento for disparado. Adicione o código a seguir ao arquivo:

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);
      }
    });
  }
}

Criar o componente de Video

Vamos começar pelo video.component.html arquivo. Primeiro, exclua qualquer código HTML gerado automaticamente que tenha sido adicionado a este arquivo. Em seguida, adicione o modelo:

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

O publishingDiv será a âncora no DOM que vamos usar para o feed de vídeo do nosso editor. O subscriberHost modelo será onde nosso assinante será adicionado quando ele entrar em uma chamada. No arquivo CSS, vamos remover todo o CSS gerado automaticamente. Adicione estilos que fixem o publishingDiv no canto inferior esquerdo da tela em uma posição fixa, ocupem 25% da altura e da largura da janela e fiquem com um z-index de 1 (imediatamente acima de onde colocamos nosso subscriberDiv). Adicione o seguinte ao video.component.css arquivo:

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

Por fim, precisamos configurar o próprio componente. Lembra-se do StateService de antes? Vamos injetá-lo; a partir dele, obteremos o sessionId, o token e a ApiKey do SessionController que criamos anteriormente.

Importação e configuração do componente

Primeiro, vamos importar tudo o que vamos precisar e criar a VideoComponent classe.

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 e construtores de componentes

Em seguida, defina alguns campos para a VideoComponent classe e do construtor. No construtor, injetamos um ComponentFactoryResolver, que usaremos mais tarde para obter a referência nativa do subscriberHost, e o StateService, que é de onde vamos obter nossa `apiKey`, `token` e `sessionId`. O Router nos ajudará a fazer o roteamento entre os componentes do nosso projeto; especificamente, você precisará dele para voltar ao controlador `join` caso o serviço de estado não contenha uma `apiKey`, um `token` ou um `sessionId`.

Dentro da VideoComponent classe, adicione o seguinte:

@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 de inicialização

A seguir, vamos configurar a ngOnInit função. A StateService é injetada imediatamente após a inicialização, então é lá que vamos obter a apiKey, o token e o sessionId. Essa função vai armazenar esses itens. Se algum deles não existir, vamos redirecionar para a página “Cadastrar-se”.

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 o stream do usuário

A seguir, vamos configurar o método `publish`. Vamos chamá-lo depois que a visualização terminar de ser inicializada. Essa função chamará o método `publish` da sessão, passando o elemento `publisher`. Ela fará a transição do campo de publicação para true quando o callback for resolvido. Adicione o seguinte após ngOnInit:

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

Lidar com a criação de um fluxo

Após a criação do stream, precisaremos nos inscrever nele. Para fazer isso, vamos pegar a referência ao modelo de assinante que criamos no HTML, inicializar um componente Subscriber para ele, atribuir o stream e o ID da sessão a ele e instruí-lo a se inscrever. Adicione o seguinte após o 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 o Publisher

Depois que a visualização for inicializada, ngAfterViewInit é disparado. Nesse ponto do ciclo de vida do controlador, temos tudo o que precisamos para iniciar a videochamada. Vamos inicializar o publisher, inicializar a sessão, conectar-nos à sessão e, no callback após a conexão com a sessão, vamos instruir nosso stream a publicar. Também vamos nos inscrever no streamCreated evento, que chamará a onStreamCreated função que criamos anteriormente. Adicione a seguinte ngAfterViewInit função:

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);
      });
    }
  })
}

Criar o componente “Join”

Com o componente de vídeo já criado, só precisamos configurar o componente de junção e o módulo do aplicativo.

Configurar o HTML

No join.component.html arquivo, vamos criar um joinForm, que terá apenas um campo de entrada, um roomName, que vamos usar para obter/gerar os sessionId’s e tokens. O modelo do componente ficará assim:

<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>

Adicione alguns estilos

Não vamos fazer nada muito sofisticado com os estilos aqui — vamos apenas garantir que o botão e o campo de entrada fiquem centralizados e tenham o mesmo tamanho. Adicione o seguinte ao 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;
}

Compilar o componente

O componente de cadastro terá uma função de envio para o formulário de cadastro, que irá obter os dados da sessão do nosso back-end e encaminhá-los para o componente de Video por meio do serviço de estado. Para isso, ele utilizará o HttpClient, FormBuilder, StateServicee Router serviços por meio da injeção de dependências e, em seguida, criará o formulário da sala. Em seguida, ele aguardará um onSubmit de joinRoomForm, momento em que enviará a roomName para o controlador de sessão e usará essa resposta para construir o 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 o aplicativo

Antes que qualquer parte do nosso Angular funcione, precisaremos configurar todo o módulo do aplicativo. Vamos começar definindo o HTML básico. Em ClientApp\src\app\app.component.html, adicionei um título acima da tag router-outlet, o que garante que o título seja exibido em nossas páginas filhas. Certifique-se também de remover o <app-nav-menu></app-nav-menu>, já que ele já existe no modelo criado originalmente:

<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>

Em seguida, em ClientApp\src\app\app.module.ts, precisamos definir nosso módulo, o que significa adicionar os novos componentes que criamos, remover os componentes que removemos logo no início e definir as rotas que vamos querer usar. Adicione os componentes como importações e, em seguida, no campo de declarações, certifique-se de ter o HttpClientModule, FormsModule, ReactiveFormsModule, e RouterModule na seção de importações. SubscriberComponent será um componente de entrada. As rotas ficarão assim: '' -> JoinComponent, video -> VideoComponent, subscriber -> SubscriberComponent.

Configure seu aplicativo.

É preciso definir a configuração em dois lugares, config.ts e appsettings.json. Você já deve ter feito a configuração config.ts antes, então não vou repetir isso. Para appsettings.ts, basta adicionar apiKey e apiSecret como campos e preencha com a ApiKey e a ApiSecret da sua conta da Video API da Vonage. O arquivo ficará mais ou menos assim:

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

Com isso, você já deve estar pronto! Encontrei alguns problemas com o controle de versões do Angular quando criei o aplicativo de demonstração pela primeira vez — fique à vontade para usar meu package.json arquivo no GitHub.

Testes

Para testar, basta executá-lo no IIS Express — clique no botão “Debug” ou pressione F5 no Visual Studio. Isso abrirá a página “Join”. Digite o nome de uma sala e você entrará em uma nova sessão associada a essa sala. Você pode fazer com que outro terminal acesse esse mesmo endpoint e entre na mesma sala, e ele se juntará a você na sala.

Conclusão

Agora que você já tem uma estrutura básica de um aplicativo de videochamada em Angular, pode explorar muito mais possibilidades com as Video APIs da Vonage. Você pode gravar sessões, compartilhar seus arquivos de mídia, transmitir suas videochamadas e muito mais!

Recursos

  • Confira nossa documentação sobre a Video API da Vonage aqui

  • O código desta postagem do blog está no GitHub

Compartilhar:

https://a.storyblok.com/f/270183/384x384/73d57fd8eb/stevelorello.png
Steve LorelloEx-funcionários da Vonage

Steve é um ex-membro da equipe da Vonage. Ele atuou como Developer Advocate .NET na Vonage, engenheiro de software full-stack poliglota, especializado em IA/ML