Manipular videos con Azure Media Services

Durante la semana pasada estuve trabajando en una prueba de concepto que demostraba cómo con Azure Media Services también puedes manipular vídeos: combinarlos, recortarlos, sobreponer audios o imágenes (logos), etcétera. Hoy quiero compartir contigo lo que prepararé para este tipo de escenarios.

Arquitectura de la PoC

Por si no lo sabías, Azure Media Services se trata de un conjunto de APIs a las que puedes invocar para conseguir el flujo deseado para tu contenido media, pero necesitas algún desarrollo por encima que haga uso de las mismas. En el ejemplo que te presento hoy, con el objetivo de mostrar el escenario de una forma visual, he utilizado la siguiente arquitectura (no olvides que esto es una prueba de concepto)🤓) :

Arquitectura de la PoC

Como ves, en ella utilizo Azure Static Web Apps para alojar el frontal web, el cual está desarrollado en Angular, Azure Functions para las APIs que harán de backend al frontal, por supuesto, Azure Media Services para los trabajos con los videos, que en este caso serán dos: combinar videos y sobreponer un audio sobre el video seleccionado, y Azure Storage, asociada al recurso de Azure Media Services, donde se almacenará el contenido.
En lineas naranjas he marcado una funcionalidad adicional y es la capacidad de suscribirte, a través de Azure Event Grid, a los eventos que genera Azure Media Services. En este ejemplo me he suscrito utilizando otra Azure Function que manda las actualizaciones a la interfaz a través de Azure SignalR.

Todo el código lo tienes disponible en GitHub en este repositorio, que cubre el frontal y sus APIs, y este otro que tiene la Azure Function, que gestiona los eventos que le llegan de Event Grid. Vamos a ver las partes más relevantes de cada punto.

Aplicación en Angular

De la parte de Angular, a parte de que quede más bonita o más fea, la clave es el componente videos que es donde se interactúa con los videos subidos a Azure Media Services. Este a su vez delega las llamadas en un servicio llamado media.service.ts:

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { environment } from 'src/environments/environment';
import { Video } from './video';
@Injectable({
  providedIn: 'root'
})
export class MediaService {
  constructor(private http: HttpClient,) { }
  private mediaUrl = environment.apiUrl
  getVideos(): Observable<Video[]> {
    return this.http.get<Video[]>(`${this.mediaUrl}/ListVideos`);
  }
  getAudios(): Observable<Video[]> {
    return this.http.get<Video[]>(`${this.mediaUrl}/GetAudios`);
  }
  combine(videos: Video[]): Observable<Video> {
    return this.http.post<Video>(`${this.mediaUrl}/Combine`, videos);
  }
  overlay(video: Video, audio: Video) {
    this.http.post(`${this.mediaUrl}/OverlayAudio`, { audio: audio.name, video: video.name }).subscribe();
  }
  deleteVideo(video: Video) {
    return this.http.post(`${this.mediaUrl}/DeleteAsset`, { name: video.name });
  }
}

Este utiliza la API desarrollada en Azure Functions que te cuento a continuación.

Azure Functions que hace de backend de la aplicación de Angular

En la carpeta api, dentro del mismo proyecto de Angular, se encuentra uno de Azure Functions con las funciones a las que hace mención el servicio anterior, del tipo HttpTrigger, y que nos permite interactuar con Azure Media Services, para recuperar los videos, los audios, combinarlos y añadir esa superposición del audio sobre video. Azure Static Web Apps te permite tener este servicio embebido dentro del mismo, aunque es posible también asociarlo con un recurso de Azure Functions independiente.

Azure Function suscrita a los eventos de Azure Media Services

Por último, tenemos un proyecto donde he desarrollado una Azure Function llamada ProcessingEvents, que nos permite recibir eventos de Azure Event Grid y mandar aquellos que son importantes a mi interfaz de usuario, como por ejemplo: saber cuándo un proceso está programado, en proceso, el porcentaje que lleva antes de ser completado, etcétera.

Actualización del progreso en la UI con Azure Event Grid y Azure SignalR

Para poder enviar estos eventos a Angular, he utilizado Azure SignalR, para generar una comunicación a través de websockets, y que solo actualice cuando realmente haya algo que actualizar, haciendo más eficiente la comunicación. Para que el frontal se conecte a este he implementado el otro servicio llamado message.service.ts donde se genera la comunicación y se configura a qué funciones del cliente puede llamar SignalR y llevar acabo así la actualización de la interfaz:

import { Injectable } from '@angular/core';
import * as signalR from '@microsoft/signalr';
import { BehaviorSubject } from 'rxjs';
import { environment } from 'src/environments/environment';
@Injectable({
  providedIn: 'root'
})
export class MessageService {
  connection?: signalR.HubConnection;
  messages: string[] = [];
  private messageSource = new BehaviorSubject('');
  currentMessage = this.messageSource.asObservable();

  public initiateSignalrConnection(): Promise<void> {
    return new Promise((resolve, reject) => {
      const apiBaseUrl = window.location.origin;
      this.connection = new signalR.HubConnectionBuilder()
        .withUrl(environment.signalRApi)
        .build();
      //set methods
      this.updateProgress();
      this.connection.start().then(() => {
        console.log(`SignalR connection success! connectionId: ${this.connection!.connectionId}`);
        resolve();
      }).catch(err => {
        console.log(`SignalR connection error: ${err}`);
        reject(err);
      });
    });
  }
  public updateProgress(): void {
    this.connection!.on('UpdateProgress', (info: any) => {
      console.log(`Try to send ${info}`);
      this.messageSource.next(info);
      console.log(`sent ${info}`);

    });
    this.connection!.on('Refresh', (info: any) => {
      console.log('refresh!');
      this.messageSource.next('refresh');
    });
  }
}

Para que esto se inicialice al principio de la aplicación he tenido que añadir algunas líneas en el archivo app.module.ts.

Aquí te dejo un video de todo el proceso para que lo veas en acción 😊

¡Saludos!

Foto de portada por Kelly L de Pexels