Creación de una aplicación de transmisión de video con Nuxt.js, Node y Express

 

 

 

  • Anuncie en la revista Smashing
  • SmashingConf Friburgo 2024

  • Índice
    1. Requisitos previos
    2. Configurando nuestra aplicación
    3. Configurando nuestro servidor
    4. Solicitudes que serán manejadas por el frontend
    5. Devolver datos de maqueta para la lista de videos
    6. Devolver datos para un solo video
    7. Transmitiendo los vídeos
    8. Cómo funciona la ruta del vídeo en streaming
    9. ¿Qué sucede con las conexiones inestables?
    10. Creando un archivo de subtítulos para nuestros videos
    11. Construyendo nuestra interfaz
    12. Instalación
    13. Nuestra estructura de archivos Nuxt
    14. Componente de barra de navegación
    15. Componente de vídeo
    16. Agregar nuestro archivo de subtítulos
    17. Qué tener en cuenta al crear una transmisión de vídeo resiliente.
    18. Conclusión
      1. Recursos

    En este artículo, crearemos una aplicación de transmisión de video utilizando Nuxt.js y Node.js. Específicamente, crearemos una aplicación Node.js del lado del servidor que se encargará de buscar y transmitir videos, generar miniaturas para sus videos y publicar subtítulos.

     

    Los vídeos funcionan con transmisiones. Esto significa que, en lugar de enviar el vídeo completo de una vez, se envía un vídeo como un conjunto de fragmentos más pequeños que conforman el vídeo completo. Esto explica por qué los videos se almacenan en el buffer cuando se mira un video en una banda ancha lenta porque solo reproduce los fragmentos que ha recibido e intenta cargar más.

    Este artículo está dirigido a desarrolladores que estén dispuestos a aprender una nueva tecnología mediante la creación de un proyecto real: una aplicación de transmisión de video con Node.js como backend y Nuxt.js como cliente.

    • Node.js es un tiempo de ejecución que se utiliza para crear aplicaciones rápidas y escalables. Lo usaremos para gestionar la búsqueda y transmisión de videos, generar miniaturas para videos y publicar subtítulos para videos.
    • Nuxt.js es un marco de Vue.js que nos ayuda a crear fácilmente aplicaciones Vue.js renderizadas en servidor. Consumiremos nuestra API para los videos y esta aplicación tendrá dos vistas: una lista de videos disponibles y una vista de reproductor para cada video.

    Requisitos previos

    • Comprensión de HTML, CSS, JavaScript, Node/Express y Vue.
    • Un editor de texto (por ejemplo, VS Code).
    • Un navegador web (por ejemplo, Chrome, Firefox).
    • FFmpeg instalado en su estación de trabajo.
    • Nodo.js.​ nvm .
    • Puedes obtener el código fuente en GitHub .

    Configurando nuestra aplicación

    En esta aplicación, construiremos las rutas para realizar solicitudes desde el frontend:

    • videosruta para obtener una lista de videos y sus datos.
    • una ruta para buscar solo un video de nuestra lista de videos.
    • streamingruta para transmitir los videos.
    • captionsruta para agregar subtítulos a los videos que estamos transmitiendo.

    Una vez creadas nuestras rutas, crearemos una estructura estructural de nuestra Nuxtinterfaz, donde crearemos la página Homedinámica player. Luego solicitamos nuestra videosruta para llenar la página de inicio con los datos del video, otra solicitud para transmitir los videos en nuestra playerpágina y, finalmente, una solicitud para entregar los archivos de subtítulos que usarán los videos.

    Para configurar nuestra aplicación, creamos nuestro directorio de proyecto,

    mkdir streaming-app

    Configurando nuestro servidor

    En nuestro streaming-appdirectorio, creamos una carpeta llamada backend.

    cd streaming-appmkdir backend

    En nuestra carpeta backend, inicializamos un package.jsonarchivo para almacenar información sobre nuestro proyecto de servidor.

     

    cd backendnpm init -y

    Necesitamos instalar los siguientes paquetes para construir nuestra aplicación.

    • nodemonreinicia automáticamente nuestro servidor cuando realizamos cambios.
    • expressnos brinda una buena interfaz para manejar rutas.
    • corsnos permitirá realizar solicitudes de origen cruzado ya que nuestro cliente y servidor se ejecutarán en diferentes puertos.

    En nuestro directorio backend, creamos una carpeta assetspara guardar nuestros videos para transmisión.

     mkdir assets

    Copie un .mp4archivo en la carpeta de activos y asígnele el nombre video1. Puede utilizar .mp4vídeos breves de muestra que se pueden encontrar en Github Repo .

    Crea un app.jsarchivo y agrega los paquetes necesarios para nuestra aplicación.

    const express = require('express');const fs = require('fs');const cors = require('cors');const path = require('path');const app = express();app.use(cors())

    El fsmódulo se utiliza para leer y escribir archivos fácilmente en nuestro servidor, mientras que el pathmódulo proporciona una forma de trabajar con directorios y rutas de archivos.

    Ahora creamos una ./videoruta. Cuando se solicite, enviará un archivo de video al cliente.

    // add after 'const app = express();'app.get('/video', (req, res) = { res.sendFile('assets/video1.mp4', { root: __dirname });});

    Esta ruta sirve el video1.mp4archivo de vídeo cuando se solicita. Luego escuchamos a nuestro servidor en el puerto 3000.

    // add to end of app.js fileapp.listen(5000, () = { console.log('Listening on port 5000!')});

    Se agrega un script en el package.jsonarchivo para iniciar nuestro servidor usando nodemon.

    "scripts": { "start": "nodemon app.js" },

    Luego en tu terminal ejecuta:

    npm run start

    Si ve el mensaje Listening on port 3000!en la terminal, entonces el servidor está funcionando correctamente. Navegue a https://localhost:5000/video en su navegador y debería ver el video reproduciéndose.

    Solicitudes que serán manejadas por el frontend

    A continuación se muestran las solicitudes que haremos al backend desde nuestro frontend y que necesitamos que el servidor maneje.

    • /videos
      Devuelve una serie de datos de maquetas de vídeo que se utilizarán para completar la lista de vídeos en la Homepágina de nuestra interfaz.
    • /video/:id/data
      Devuelve metadatos para un solo vídeo. Utilizado por la Playerpágina en nuestro frontend.
    • /video/:id
      Transmite un vídeo con una identificación determinada. Utilizado por la Playerpágina.

    Creemos las rutas.

    Devolver datos de maqueta para la lista de videos

    Para esta aplicación de demostración, crearemos una matriz de objetos que contendrán los metadatos y los enviaremos a la interfaz cuando se solicite. En una aplicación real, probablemente estarías leyendo los datos de una base de datos, que luego se usarían para generar una matriz como esta. En aras de la simplicidad, no haremos eso en este tutorial.

     

    En nuestra carpeta backend, cree un archivo mockdata.jsy rellénelo con metadatos para nuestra lista de videos.

    const allVideos = [ { id: "tom and jerry", poster: 'https://image.tmdb.org/t/p/w500/fev8UFNFFYsD5q7AcYS8LyTzqwl.jpg', duration: '3 mins', name: 'Tom Jerry' }, { id: "soul", poster: 'https://image.tmdb.org/t/p/w500/kf456ZqeC45XTvo6W9pW5clYKfQ.jpg', duration: '4 mins', name: 'Soul' }, { id: "outside the wire", poster: 'https://image.tmdb.org/t/p/w500/lOSdUkGQmbAl5JQ3QoHqBZUbZhC.jpg', duration: '2 mins', name: 'Outside the wire' },];module.exports = allVideos

    Como podemos ver desde arriba, cada objeto contiene información sobre el video. Observe el posteratributo que contiene el enlace a una imagen de póster del vídeo.

    Creemos una videosruta ya que todas nuestras solicitudes que debe realizar la interfaz van precedidas de /videos.

    Para hacer esto, creemos una routescarpeta y agreguemos un Video.jsarchivo para nuestra /videosruta. En este archivo, necesitaremos expressy usaremos el enrutador express para crear nuestra ruta.

    const express = require('express')const router = express.Router()

    Cuando vamos a la /videosruta, queremos obtener nuestra lista de videos, así que solicitemos el mockData.jsarchivo en nuestro Video.jsarchivo y hagamos nuestra solicitud.

    const express = require('express')const router = express.Router()const videos = require('../mockData')// get list of videosrouter.get('/', (req,res)={ res.json(videos)})module.exports = router;

    La /videosruta ahora está declarada, guarde el archivo y debería reiniciar automáticamente el servidor. Una vez que haya comenzado, navegue hasta https://localhost:3000/videos y nuestra matriz se devolverá en formato JSON.

    Devolver datos para un solo video

    Queremos poder realizar una solicitud para un video en particular en nuestra lista de videos. Podemos recuperar datos de video en particular en nuestra matriz usando el que idle proporcionamos. Hagamos una solicitud, todavía en nuestro Video.jsexpediente.

    // make request for a particular videorouter.get('/:id/data', (req,res)= { const id = parseInt(req.params.id, 10) res.json(videos[id])})

    El código anterior obtiene los idparámetros de ruta y los convierte a un número entero. Luego enviamos el objeto que coincide con el idde la videosmatriz al cliente.

    Transmitiendo los vídeos

    En nuestro app.jsarchivo, creamos una /videoruta que entrega un video al cliente. Queremos que este punto final envíe fragmentos más pequeños del video, en lugar de entregar un archivo de video completo a pedido.

    Queremos poder servir dinámicamente uno de los tres videos que están en la allVideosmatriz y transmitir los videos en fragmentos, por lo tanto:

    Eliminar la /videoruta de app.js.

     

    Necesitamos tres videos, así que copie los videos de ejemplo del código fuente del tutorial en el assets/directorio de su serverproyecto. Asegúrese de que los nombres de archivo de los videos correspondan a los idde la videosmatriz:

    De vuelta en nuestro Video.jsarchivo, crea la ruta para la transmisión de videos.

    router.get('/video/:id', (req, res) = { const videoPath = `assets/${req.params.id}.mp4`; const videoStat = fs.statSync(videoPath); const fileSize = videoStat.size; const videoRange = req.headers.range; if (videoRange) { const parts = videoRange.replace(/bytes=/, "").split("-"); const start = parseInt(parts[0], 10); const end = parts[1] ? parseInt(parts[1], 10) : fileSize-1; const chunksize = (end-start) + 1; const file = fs.createReadStream(videoPath, {start, end}); const head = { 'Content-Range': `bytes ${start}-${end}/${fileSize}`, 'Accept-Ranges': 'bytes', 'Content-Length': chunksize, 'Content-Type': 'video/mp4', }; res.writeHead(206, head); file.pipe(res); } else { const head = { 'Content-Length': fileSize, 'Content-Type': 'video/mp4', }; res.writeHead(200, head); fs.createReadStream(videoPath).pipe(res); }});

    Si navegamos a https://localhost:5000/videos/video/outside-the-wire en nuestro navegador, podremos ver la transmisión de video.

    Cómo funciona la ruta del vídeo en streaming

    Hay bastante código escrito en nuestra ruta de transmisión de video, así que veámoslo línea por línea. Literatura y libros

     const videoPath = `assets/${req.params.id}.mp4`; const videoStat = fs.statSync(videoPath); const fileSize = videoStat.size; const videoRange = req.headers.range;

    Primero, de nuestra solicitud, obtenemos el iduso de la ruta req.params.idy lo usamos para generar videoPathel video. Luego leemos fileSizeusando el sistema de archivos fsque importamos. Para videos, el navegador de un usuario enviará un rangeparámetro en la solicitud. Esto le permite al servidor saber qué parte del video enviar al cliente.

    Algunos navegadores envían un rango en la solicitud inicial, pero otros no. Para aquellos que no lo hacen, o si por cualquier otro motivo el navegador no envía un rango, lo manejamos en el elsebloque. Este código obtiene el tamaño del archivo y envía los primeros fragmentos del vídeo:

    else { const head = { 'Content-Length': fileSize, 'Content-Type': 'video/mp4', }; res.writeHead(200, head); fs.createReadStream(path).pipe(res);}

    Manejaremos solicitudes posteriores, incluido el rango en un ifbloque.

    if (videoRange) { const parts = videoRange.replace(/bytes=/, "").split("-"); const start = parseInt(parts[0], 10); const end = parts[1] ? parseInt(parts[1], 10) : fileSize-1; const chunksize = (end-start) + 1; const file = fs.createReadStream(videoPath, {start, end}); const head = { 'Content-Range': `bytes ${start}-${end}/${fileSize}`, 'Accept-Ranges': 'bytes', 'Content-Length': chunksize, 'Content-Type': 'video/mp4', }; res.writeHead(206, head); file.pipe(res); }

    Este código anterior crea una secuencia de lectura utilizando los valores starty enddel rango. Establezca los Content-Lengthencabezados de respuesta en el tamaño del fragmento que se calcula a partir de los valores starty end. También usamos el código HTTP 206 , lo que significa que la respuesta contiene contenido parcial. Esto significa que el navegador seguirá realizando solicitudes hasta que haya recuperado todos los fragmentos del vídeo.

     

    ¿Qué sucede con las conexiones inestables?

    Si el usuario tiene una conexión lenta, la transmisión de red lo indicará solicitando que la fuente de E/S se detenga hasta que el cliente esté listo para recibir más datos. Esto se conoce como contrapresión . Podemos llevar este ejemplo un paso más allá y ver lo fácil que es extender la transmisión. ¡También podemos agregar compresión fácilmente!

    const start = parseInt(parts[0], 10); const end = parts[1] ? parseInt(parts[1], 10) : fileSize-1; const chunksize = (end-start) + 1; const file = fs.createReadStream(videoPath, {start, end});

    Podemos ver arriba que ReadStreamse crea a y sirve el video fragmento por fragmento.

    const head = { 'Content-Range': `bytes ${start}-${end}/${fileSize}`, 'Accept-Ranges': 'bytes', 'Content-Length': chunksize, 'Content-Type': 'video/mp4', };res.writeHead(206, head); file.pipe(res);

    El encabezado de la solicitud contiene Content-Range, que es el cambio de inicio y final para que el siguiente fragmento de video se transmita al frontend, es content-lengthel fragmento de video enviado. También especificamos el tipo de contenido que estamos transmitiendo, que es mp4. El cabezal de escritura de 206 está configurado para responder solo con transmisiones recién creadas.

    Creando un archivo de subtítulos para nuestros videos

    Así es como .vttse ve un archivo de título.

    WEBVTT00:00:00.200 -- 00:00:01.000Creating a tutorial can be very00:00:01.500 -- 00:00:04.300fun to do.

    Los archivos de subtítulos contienen texto de lo que se dice en un vídeo. También contiene códigos de tiempo para saber cuándo debe mostrarse cada línea de texto. Queremos que nuestros videos tengan subtítulos y no crearemos nuestro propio archivo de subtítulos para este tutorial, por lo que puede dirigirse a la carpeta de subtítulos en el assetsdirectorio del repositorio y descargarlos.

    Creemos una nueva ruta que manejará la solicitud de título:

    router.get('/video/:id/caption', (req, res) = res.sendFile(`assets/captions/${req.params.id}.vtt`, { root: __dirname }));

    Construyendo nuestra interfaz

    Para comenzar con la parte visual de nuestro sistema, tendríamos que construir nuestra plataforma frontal.

    Nota : necesita vue-cli para crear nuestra aplicación. Si no lo tienes instalado en tu computadora, puedes ejecutarlo npm install -g @vue/clipara instalarlo.

     

    Instalación

    En la raíz de nuestro proyecto, creemos nuestra carpeta de front-end:

    mkdir frontendcd frontend

    y en él inicializamos nuestro package.jsonarchivo, copiamos y pegamos lo siguiente en él:

    { "name": "my-app", "scripts": { "dev": "nuxt", "build": "nuxt build", "generate": "nuxt generate", "start": "nuxt start" }}

    luego instale nuxt:

    npm add nuxt

    y ejecute el siguiente comando para ejecutar la aplicación Nuxt.js:

    npm run dev

    Nuestra estructura de archivos Nuxt

    Ahora que tenemos Nuxt instalado, podemos comenzar a diseñar nuestra interfaz.

    Primero, necesitamos crear una layoutscarpeta en la raíz de nuestra aplicación. Esta carpeta define el diseño de la aplicación, sin importar la página a la que naveguemos. Cosas como nuestra barra de navegación y pie de página se encuentran aquí. En la carpeta frontend, creamos default.vuenuestro diseño predeterminado cuando iniciamos nuestra aplicación frontend.

    mkdir layoutscd layoutstouch default.vue

    Luego una componentscarpeta para crear todos nuestros componentes. Necesitaremos solo dos componentes NavBary videocomponente. Entonces, en nuestra carpeta raíz de frontend nosotros:

    mkdir componentscd componentstouch NavBar.vuetouch Video.vue

    Finalmente, una carpeta de páginas donde todas nuestras páginas gustan homey aboutse pueden crear. Las dos páginas que necesitamos en esta aplicación son la homepágina que muestra todos nuestros videos e información de video y una página de reproductor dinámico que dirige al video en el que hacemos clic.

    mkdir pagescd pagestouch index.vuemkdir playercd playertouch _name.vue

    Nuestro directorio frontend ahora se ve así:

    |-frontend |-components |-NavBar.vue |-Video.vue |-layouts |-default.vue |-pages |-index.vue |-player |-_name.vue |-package.json |-yarn.lock

    Componente de barra de navegación

    Nuestro NavBar.vuese ve así:

    template div h1Streaming App/h1 /div/templatestyle scoped.navbar { display: flex; background-color: #161616; justify-content: center; align-items: center;}h1{ color:#a33327;}/style

    Tiene NavBaruna h1etiqueta que muestra Streaming App , con un pequeño estilo.

    Importemos el NavBara nuestro default.vuediseño.

    // default.vuetemplate div NavBar / nuxt / /div/templatescriptimport NavBar from "@/components/NavBar.vue"export default { components: { NavBar, }}/script

    El default.vuediseño ahora contiene nuestro NavBarcomponente y la nuxt /etiqueta que aparece después indica dónde se mostrará cualquier página que creemos.

    En nuestra index.vue(que es nuestra página de inicio), hagamos una solicitud para https://localhost:5000/videosobtener todos los videos de nuestro servidor. Pasar los datos como accesorio a nuestro video.vuecomponente que crearemos más adelante. Pero por ahora ya lo hemos importado.

     

    templatediv Video :videoList="videos"//div/templatescriptimport Video from "@/components/Video.vue"export default { components: { Video },head: { title: "Home" }, data() { return { videos: [] } }, async fetch() { this.videos = await fetch( 'https://localhost:5000/videos' ).then(res = res.json()) }}/script

    Componente de vídeo

    A continuación, primero declaramos nuestro accesorio. Dado que los datos de video ahora están disponibles en el componente, usando Vue v-foriteramos sobre todos los datos recibidos y para cada uno, mostramos la información. Podemos usar la v-fordirectiva para recorrer los datos y mostrarlos como una lista. También se han agregado algunos estilos básicos.

    templatediv div div v-for="(video, id) in videoList" :key="id" NuxtLink :to="`/player/${video.id}`" div :style="{ backgroundImage: `url(${video.poster})` }" /div div div h2{{video.name}}/h2 p{{video.duration}}/p /div /div /NuxtLink /div /div/div/templatescriptexport default { props:['videoList'],}/scriptstyle scoped.container { display: flex; justify-content: center; align-items: center; margin-top: 2rem;}.vid-con { display: flex; flex-direction: column; flex-shrink: 0; justify-content: center; width: 50%; max-width: 16rem; margin: auto 2em; }.vid { height: 15rem; width: 100%; background-position: center; background-size: cover;}.movie-info { background: black; color: white; width: 100%;}.details { padding: 16px 20px;}/style

    También notamos que NuxtLinktiene una ruta dinámica, es decir, enrutamiento al /player/video.id.

    La funcionalidad que queremos es que cuando un usuario haga clic en cualquiera de los videos, comience a transmitirse. Para conseguirlo aprovechamos el carácter dinámico del _name.vuerecorrido.

    En él, creamos un reproductor de video y configuramos la fuente en nuestro punto final para transmitir el video, pero agregamos dinámicamente qué video reproducir a nuestro punto final con la ayuda de this.$route.params.nameese parámetro que captura el enlace recibido.

    template div video controls muted autoPlay source :src="`https://localhost:5000/videos/video/${vidName}`" type="video/mp4" /video /div/templatescriptexport default { data() { return { vidName: '' } },mounted(){ this.vidName = this.$route.params.name}}/scriptstyle scoped.player { display: flex; justify-content: center; align-items: center; margin-top: 2em;}/style

    Cuando hacemos clic en cualquiera de los vídeos obtenemos:

    La transmisión de video comienza cuando el usuario hace clic en la miniatura. ( Vista previa grande )

    Agregar nuestro archivo de subtítulos

    Para agregar nuestro archivo de pista, nos aseguramos de que todos los .vttarchivos en la carpeta de subtítulos tengan el mismo nombre que nuestro id. Actualice nuestro elemento de video con la pista, solicitando los subtítulos.

    template div video controls muted autoPlay crossOrigin="anonymous" source :src="`https://localhost:5000/videos/video/${vidName}`" type="video/mp4" track label="English" kind="captions" srcLang="en" :src="`https://localhost:5000/videos/video/${vidName}/caption`" default /video /div/template

    Hemos agregado crossOrigin="anonymous"al elemento de video; de lo contrario, la solicitud de subtítulos fallará. Ahora actualice y verá que los subtítulos se agregaron correctamente.

    Qué tener en cuenta al crear una transmisión de vídeo resiliente.

    Al crear aplicaciones de streaming como Twitch, Hulu o Netflix, se tienen en cuenta una serie de cosas:

    • Canal de procesamiento de datos de vídeo
      Esto puede ser un desafío técnico, ya que se necesitan servidores de alto rendimiento para ofrecer millones de vídeos a los usuarios. Se debe evitar a toda costa una alta latencia o tiempo de inactividad.
    • Almacenamiento en caché
      Se deben utilizar mecanismos de almacenamiento en caché al crear este tipo de aplicación, por ejemplo Cassandra, Amazon S3, AWS SimpleDB.
    • Geografía de los usuarios
      Se debe considerar la geografía de sus usuarios para la distribución.

    Conclusión

    En este tutorial, hemos visto cómo crear un servidor en Node.js que transmite videos, genera subtítulos para esos videos y proporciona metadatos de los videos. También hemos visto cómo usar Nuxt.js en el frontend para consumir los puntos finales y los datos generados por el servidor.

    A diferencia de otros marcos, crear una aplicación con Nuxt.js y Express.js es bastante fácil y rápido. Lo bueno de Nuxt.js es la forma en que administra tus rutas y te permite estructurar mejor tus aplicaciones.

    • Puede obtener más información sobre Nuxt.js aquí .
    • Puedes obtener el código fuente en Github .

    Recursos

    • “ Agregar leyendas y subtítulos a videos HTML5 ”, MDN Web Docs
    • “ Comprensión de los títulos y subtítulos ”, Screenfont.ca

    (ks, vf, yk, il)Explora más en

    • vista
    • Nuxt.js
    • Nodo.js
    • Tutoriales
    • javascript





    Tal vez te puede interesar:

    1. Creación de su propia biblioteca de validación de React: las características (Parte 2)
    2. Introducción a Quasar Framework: creación de aplicaciones multiplataforma
    3. Creación de un componente web retro que se puede arrastrar con iluminación
    4. Creación y acoplamiento de una aplicación Node.js con arquitectura sin estado con la ayuda de Kinsta

    Creación de una aplicación de transmisión de video con Nuxt.js, Node y Express

    Creación de una aplicación de transmisión de video con Nuxt.js, Node y Express

    Anuncie en la revista Smashing SmashingConf Friburgo 2024 Índice Requisitos previos

    programar

    es

    https://aprendeprogramando.es/static/images/programar-creacion-de-una-aplicacion-de-transmision-de-video-con-nuxt-1091-0.jpg

    2024-05-21

     

    Creación de una aplicación de transmisión de video con Nuxt.js, Node y Express
    Creación de una aplicación de transmisión de video con Nuxt.js, Node y Express

    Si crees que alguno de los contenidos (texto, imagenes o multimedia) en esta página infringe tus derechos relativos a propiedad intelectual, marcas registradas o cualquier otro de tus derechos, por favor ponte en contacto con nosotros en el mail [email protected] y retiraremos este contenido inmediatamente

     

     

    Top 20