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

Créer une application de base de Video-App Video avec ASP.NET et Angular

Publié le November 5, 2020

Temps de lecture : 12 minutes

Angular est de loin le framework d'application à page unique (SPA) le plus populaire utilisé par les développeurs .NET. Jusqu'à ce projet, je n'avais pas utilisé Angular depuis avant que avant qu'il n'abandonne le JS de son nom. Par conséquent, j'étais enthousiaste à l'idée de m'y essayer à nouveau, cette fois en l'utilisant pour présenter l'API Video de Vonage. Vous pouvez suivre ce tutoriel, où nous allons créer une application de chat vidéo de base à l'aide de l'API Video de Vonage. Angular (même s'il y aura toujours le JS dans mon ❤).

Conditions préalables

  • Visual Studio (j'utilise 2019, mais les versions antérieures devraient fonctionner)

  • .NET Core 3.1 kit du développeur

  • A Compte Video API de Vonage

  • Un projet Video API de Vonage, qui peut être créé à partir de la page de votre Account.

Droit au code

Si vous souhaitez simplement récupérer le code de cette présentation, visitez le site GitHub pour cet article de blog, suivez les instructions, et vous serez prêt.

Les choses sérieuses d'abord

Commençons par ouvrir Visual Studio. Cliquez sur Créer un nouveau projet -> ASP.NET Core Web Application -> donnez-lui un nom (j'appelle le mien BasicVideoChatAngular) -> Créer -> Angular.

En procédant de la sorte, vous allez créer une application ASP.NET shell avec tout votre code côté client dans le dossier ClientApp dans le dossier

Importer des paquets Nuget

Importez les paquets NuGet suivants pour ce projet :

  • OpenTok

  • Microsoft.EntityFrameworkCore.SqlServer (j'utilise la version 3.1.3)

Créer le modèle d'entité

Nous allons utiliser un Entity Framework très basique. Ajouter un fichier Model.cs au projet. Supprimez la déclaration Class et ajoutez-y le code suivant :

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

Vous devrez également ajouter ce qui suit à votre section "using" :

using Microsoft.EntityFrameworkCore;

Créer la base de données

Ceci étant ajouté, créons la base de données. Naviguez dans le dossier de votre projet et exécutez ce qui suit :

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

Cela créera un fichier de base de données qui contiendra vos salles et vos identifiants de session.

Créer un contrôleur de session

Cliquez avec le bouton droit de la souris sur le dossier Controllers dossier -> Ajouter -> Contrôleur - Contrôleur MVC - vide -> le nommer SessionController.

Dans la dépendance SessionControllerla dépendance injecte un objet IConfiguration, et crée une classe de formulaire élémentaire pour contenir le nom de notre salle, appelée RoomForm:

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

Ensuite, ajoutez une requête HttpPost appelée GetSession qui prend un RoomForm comme argument :

[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"] });
}

Cette méthode vérifie dans la base de données si le roomName possède déjà un sessionId. Si c'est le cas, elle génère un jeton pour cet identifiant de session. Dans le cas contraire, elle crée une nouvelle session et un nouveau jeton. Ensuite, il crée une nouvelle ligne dans la base de données pour cette salle. Dans les deux cas, il renvoie un sessionId, un token et une ApiKey sous forme de JSON.

Construire le client

L'arrière-plan étant terminé, passons à la construction du client. Nous allons disposer de deux vues principales : la nôtre et la nôtre. Rejoindre où l'utilisateur saisira le nom de la salle qu'il souhaite rejoindre :

Chat App Join ViewChat App Join View

Et un Video qui contiendra l'appel vidéo :

Chat App Video ViewChat App Video View

Installer les dépendances npm

Tout d'abord, naviguez dans le répertoire ClientApp dans votre terminal et exécutez :

npm install --save @opentok/client

Nettoyer les vues de démonstration

Lorsque vous créez un projet Angular dans Visual Studio, un certain nombre de composants de démonstration sont automatiquement ajoutés sous la rubrique ClientApp\src\app, y compris counter, fetch-data, home, et nav-menu. Nous n'aurons besoin d'aucun de ces éléments, alors supprimons-les dès maintenant.

Ajouter les fichiers nécessaires

Créez les dossiers/fichiers suivants :

  • Sous ClientApp\src ajouter config.ts

  • Sous ClientApp\src\app ajouter stateService.ts

  • Sous ClientApp\src\app créer des répertoires : join, subscriber, video

  • Sous ClientApp\src\join créer join.component.css, join.component.html, join.component.ts

  • Sous ClientApp\src\subscriber créer subscriber.component.html, subscriber.component.ts, subscriber.component.css

  • Sous ClientApp\src\video créer video.component.css, video.component.html, video.component.ts

Construire la configuration

En ClientApp\src\config.ts, nous allons mettre en place notre configuration, qui contient un champ, SAMPLE_SERVER_BASE_URL. Définissez-le à l'URL de base que vous utilisez pour IIS - le fichier devrait ressembler à ceci :

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

Si vous utilisez IIS Express pour déboguer, trouvez le fichier base_url en cliquant avec le bouton droit de la souris sur votre fichier de projet -> Propriétés -> Débogageet en bas vous verrez les URLs IIS.

Développer les services de l'État

Nous allons avoir une transition entre les composants après avoir cliqué sur le bouton Join. Nous devrons transporter le jeton, l'identifiant de session et la clé ApiKey entre le composant Join et le composant Video afin que ce dernier puisse participer à l'appel. Pour résoudre ce problème, nous allons partager cet état à l'aide d'un service d'état - nous injecterons le service d'état dans le composant suivant lorsque nous passerons d'un composant à l'autre. Nous avons juste besoin d'un Injectable pour accomplir cela avec quelques champs observables :

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

Note : A ce stade, il se peut que vous obteniez une erreur IntelliSense : À ce stade, il se peut que vous obteniez une erreur IntelliSense "Experimental support for decorators is a feature that is subject to change in a future release. Définissez l'option 'experimentalDecorators' pour supprimer cet avertissement" Pour résoudre ce problème, vous devez définir l'action de construction du fichier ClientApp\tsconfig.json à Content, et il se peut que vous deviez redémarrer Visual Studio.

Construire le composant abonné

Le composant Subscriber sera le composant responsable de la réception du flux vidéo. Pour le construire, supprimez tous les éléments HTML préajoutés de subscriber.component.html et ajoutez cette ligne :

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

Il ne contiendra qu'une div, qui servira de cible au flux entrant.

Maintenant, dans subscriber.component.cssajoute quelques styles :

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

Ce CSS fera en sorte que le composant occupe tout l'écran et le poussera au bas de l'indice z, ce qui l'empêchera de supplanter la vidéo de l'éditeur, qui apparaîtra comme un PIP au bas de l'écran.

Dans le subscriber.component.ts filenous allons créer un composant avec une session et un flux d'entrée. Il a également besoin d'une référence à l'élément SubscriberDiv du modèle HTML, ainsi que d'une session et d'un flux que nous obtiendrons du composant Video. Enfin, il a besoin d'une méthode subscribe pour s'abonner à un flux de session lorsque l'événement onStreamCreate se déclenche. Ajoutez le code suivant au fichier :

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

Construire le composant Video

Commençons par le fichier video.component.html . Tout d'abord, supprimez toutes les pages html générées automatiquement et ajoutées à ce fichier. Ensuite, ajoutez le modèle :

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

Le publishingDiv sera l'ancre dans le DOM que nous allons utiliser pour le flux vidéo de notre éditeur. Le modèle subscriberHost sera l'endroit où notre abonné sera ajouté lorsqu'il se joindra à un appel. Dans le fichier CSS, supprimons tout CSS généré automatiquement. Ajoutez des styles qui placeront le publishingDiv dans le coin inférieur gauche de l'écran, dans une position fixe, occupant 25 % de la hauteur et de la largeur de la fenêtre, et avec un indice z de 1 (juste au-dessus de l'endroit où nous avons placé notre subscriberDiv). Ajoutez ce qui suit au fichier video.component.css le texte suivant :

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

Enfin, nous devons configurer le composant lui-même. Vous vous souvenez du StateService de tout à l'heure ? Nous allons l'injecter ; à partir de lui, nous obtiendrons le sessionId, le token et l'ApiKey du SessionController que nous avons créé plus tôt.

Importations et habillage du composant

Tout d'abord, importez tous les éléments dont nous aurons besoin et créez la classe 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 {
}

Champs et constructeurs de composants

Ensuite, définissez quelques champs pour la classe et le constructeur VideoComponent et le constructeur. Dans le constructeur, nous injectons un ComponentFactoryResolverque nous utiliserons plus tard pour récupérer la référence native du subscriberHost, et le champ StateServicequi est l'endroit où nous allons récupérer notre apiKey, notre token et notre sessionId. Le Router nous aidera à passer d'un composant à l'autre dans notre projet ; en particulier, vous en aurez besoin pour retourner au contrôleur de jointure si le service d'état ne contient pas d'apiKey, de token ou de sessionId.

A l'intérieur de la classe VideoComponent ajoutez ce qui suit :

@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
) { }

Logique d'initialisation

Ensuite, nous allons mettre en place la fonction ngOnInit . La fonction StateService est injectée immédiatement lors de l'init, c'est donc là que nous allons récupérer l'apiKey, le token et le sessionId. Cette fonction va stocker ces éléments. Si l'un d'entre eux n'existe pas, nous allons rediriger vers la page 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$;
}

Publier le flux de l'utilisateur

Ensuite, nous allons mettre en place la méthode de publication. Nous allons l'appeler une fois que la vue aura fini de s'initialiser. Cette fonction appellera la méthode de publication de la session, en passant l'élément publisher. Elle fera passer le champ de publication à true lorsque le callback sera résolu. Ajoutez ce qui suit après ngOnInit:

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

Gérer la création d'un flux

Une fois le flux créé, nous devrons nous y abonner. Pour ce faire, nous allons récupérer la référence au modèle d'abonné que nous avons créé dans le code HTML, initialiser un composant Subscriber, lui attribuer le flux et l'identifiant de session, et lui demander de s'abonner. Ajoutez ce qui suit après la méthode de publication :

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

Configurer l'éditeur

Après l'initialisation de la vue, ngAfterViewInit se déclenche. À ce stade du cycle de vie du contrôleur, nous disposons de tout ce dont nous avons besoin pour lancer l'appel vidéo. Nous allons initialiser l'éditeur, initialiser la session, nous connecter à la session, et dans le callback après que nous nous soyons connectés à la session, nous allons dire à notre flux de publier. Nous allons également nous abonner à l'événement streamCreated qui va appeler la fonction onStreamCreated que nous avons créée plus tôt. Ajoutez ce qui suit ngAfterViewInit fonction :

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

Construire le composant Join

Une fois le composant Video construit, il ne nous reste plus qu'à configurer le composant Join et le module App.

Configurer le Html

Dans le fichier join.component.html nous allons créer un fichier joinFormqui n'aura qu'une seule entrée, un roomNameque nous allons utiliser pour récupérer/générer les sessionId's et les tokens. Le modèle du composant va ressembler à ceci :

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

Ajouter des styles

Nous n'allons rien faire de trop sophistiqué avec les styles ici - nous allons juste nous assurer que le bouton et l'entrée sont centrés et ont la même taille. Ajoutez ce qui suit à 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;
}

Construire le composant

Le composant join va avoir une fonction de soumission pour le formulaire join, qui va récupérer les données de session de notre back-end et les acheminer vers le composant video via le service d'état. Pour ce faire, il utilisera la fonction HttpClient, FormBuilder, StateService, et Router grâce à l'injection de dépendances, puis construire le formulaire de salle. Ensuite, il attendra un onSubmit de joinRoomFormà la suite de quoi il enverra la réponse roomName au contrôleur de session et utilisera cette réponse pour construire le composant 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'])
      }
    )
  }
}

Configurer l'application

Avant qu'Angular ne fonctionne, nous allons devoir configurer l'ensemble du module de l'application. Nous allons commencer par mettre en place le HTML de base. Dans ClientApp\src\app\app.component.htmlj'ai ajouté un titre au-dessus du router-outletce qui garantit que le titre s'affichera sur nos pages enfants. Veillez également à supprimer l'élément <app-nav-menu></app-nav-menu>car il préexiste au modèle construit à l'origine :

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

Ensuite, dans ClientApp\src\app\app.module.tsnous devons définir notre module, ce qui signifie ajouter les nouveaux composants que nous avons créés, supprimer les composants que nous avons supprimés au tout début, et établir les routes que nous allons vouloir utiliser. Ajoutez les composants en tant qu'importations, puis dans le champ des déclarations, assurez-vous d'avoir les éléments suivants HttpClientModule, FormsModule, ReactiveFormsModule, et RouterModule dans votre section d'importation. SubscriberComponent sera un composant d'entrée. Les routes se présenteront comme suit : '' -> JoinComponent, video -> VideoComponent, subscriber -> SubscriberComponent.

Configurez votre application.

Vous devez établir la configuration à deux endroits, config.ts et appsettings.json. Vous auriez dû configurer config.ts plus tôt, donc je ne reviendrai pas là-dessus. Pour appsettings.tsil suffit d'ajouter apiKey et apiSecret comme champs et de les remplir avec l'ApiKey et l'ApiSecret de votre compte API Video de Vonage. Le fichier ressemblera à ceci :

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

Avec cela, vous devriez être prêt ! J'ai rencontré quelques problèmes avec les versions d'Angular lorsque j'ai construit l'application de démonstration la première fois - n'hésitez pas à emprunter mon fichier package.json sur GitHub.

Essais

Tout ce que vous avez à faire pour le tester est de le lancer dans IIS Express - appuyez sur le bouton Debug ou sur f5 dans Visual Studio. Cette opération permet d'ouvrir la page Join (Rejoindre). Saisissez le nom d'une salle et vous rejoindrez une nouvelle session associée à cette salle. Vous pouvez demander à un autre point d'extrémité de naviguer vers ce même point d'extrémité et de rejoindre la même salle, et il se joindra à vous dans la salle.

Conclusion

Maintenant que vous avez une coquille de base d'une application de chat vidéo dans Angular, vous pouvez explorer faire beaucoup plus avec les API Video de Vonage. Vous pouvez enregistrer des sessions, partager vos médias, diffuser vos appels vidéo, et bien plus encore !

Ressources

  • Consultez notre documentation sur l'API Video de Vonage ici

  • Le code de cet article de blog se trouve sur GitHub

Partager:

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

Ancien développeur .NET Advocate @Vonage, ingénieur logiciel polyglotte full-stack, AI/ML