Fermer

avril 13, 2021

Créer une application de streaming vidéo avec Nuxt.js, Node et Express


Dans cet article, nous allons créer une application de streaming vidéo à l'aide de Nuxt.js et Node.js. Plus précisément, nous montrerons comment mettre en œuvre un service de diffusion en continu avec les composants suivants: une application Node.js côté serveur qui gérera la récupération et la diffusion de vidéos, générera des miniatures pour vos vidéos et diffusera des sous-titres et des sous-titres pour les vidéos; une application client dans Nuxt.js qui consommera les vidéos sur notre serveur.

Les vidéos fonctionnent avec des flux. Cela signifie qu'au lieu d'envoyer la vidéo entière à la fois, une vidéo est envoyée sous la forme d'un ensemble de petits morceaux qui composent la vidéo complète. Cela explique pourquoi les vidéos se mettent en mémoire tampon lors de la visualisation d'une vidéo sur un haut débit lent, car elle ne lit que les morceaux qu'elle a reçus et essaie d'en charger davantage.

Cet article est destiné aux développeurs qui souhaitent apprendre une nouvelle technologie en créant un projet réel: application de streaming vidéo avec Node.js comme backend et Nuxt.js comme client.

  • Node.js est un runtime utilisé pour créer des applications rapides et évolutives. Nous l'utiliserons pour récupérer et diffuser des vidéos, générer des miniatures pour les vidéos et servir des légendes et des sous-titres pour les vidéos.
  • Nuxt.js est un framework Vue.js qui nous aide à créer des applications Vue.js rendues par le serveur facilement. Nous utiliserons notre API pour les vidéos et cette application aura deux vues: une liste des vidéos disponibles et une vue du lecteur pour chaque vidéo.

Prérequis

Configuration de notre application

Dans cette application, nous allons construire les itinéraires pour faire des requêtes depuis le frontend:

  • videos itinéraire pour obtenir une liste de vidéos et leurs données.
  • un itinéraire pour récupérer une seule vidéo de notre liste de vidéos.
  • streaming route pour diffuser les vidéos.
  • sous-titres route pour ajouter des légendes aux vidéos que nous diffusons.

Une fois nos itinéraires créés, nous échafaudons notre Nuxt où nous allons créer la page Accueil et le lecteur dynamique . Ensuite, nous demandons notre route vidéos pour remplir la page d'accueil avec les données vidéo, une autre demande pour diffuser les vidéos sur notre page player et enfin une demande pour servir les fichiers de sous-titres. utilisé par les vidéos.

Pour configurer notre application, nous créons notre répertoire de projet,

 mkdir streaming-app 

Configuration de notre serveur

Dans notre répertoire streaming-app nous créons un dossier nommé backend .

 cd streaming-app
mkdir backend 

Dans notre dossier backend, nous initialisons un fichier package.json pour stocker des informations sur notre projet serveur.

 cd backend
npm init -y 

nous devons installer les packages suivants pour construire notre application.

  • nodemon redémarre automatiquement notre serveur lorsque nous apportons des modifications.
  • express nous donne une interface agréable pour gérer les routes.
  • cors nous permettra de faire des requêtes cross-origin puisque notre client et notre serveur fonctionneront sur des ports différents.

Dans notre répertoire backend, nous créons un dossier actifs pour stocker nos vidéos en streaming.

 mkdir assets 

Copiez un fichier .mp4 dans le dossier assets et nommez-le video1 . Vous pouvez utiliser .mp4 de courts exemples de vidéos disponibles sur Github Repo .

Créez un fichier app.js et ajoutez les packages nécessaires à notre app.

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

Le module fs permet de lire et d'écrire facilement dans des fichiers sur notre serveur, tandis que le module path permet de travailler avec les répertoires et les chemins de fichiers.

Nous créons maintenant une route ./ video . Sur demande, il renverra un fichier vidéo au client.

 // add after 'const app = express ();'

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

Cet itinéraire dessert le fichier vidéo video1.mp4 sur demande. Nous écoutons ensuite notre serveur sur le port 3000 .

 // ajouter à la fin du fichier app.js

app.listen (5000, () => {
    console.log ('Écoute sur le port 5000!')
}); 

Un script est ajouté dans le fichier package.json pour démarrer notre serveur en utilisant nodemon.


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

Puis sur votre terminal exécutez:

 npm run start 

Si vous voyez le message Listening on port 3000! dans le terminal, le serveur fonctionne correctement. Accédez à http: // localhost: 5000 / video dans votre navigateur et vous devriez voir la lecture de la vidéo.

Demandes à traiter par le frontend

Voici les demandes que nous allons faire à le backend de notre frontend que nous avons besoin du serveur pour gérer.

  • / videos
    Renvoie un tableau de données de maquette vidéo qui seront utilisées pour remplir la liste des vidéos sur la page Accueil de notre frontend.
  • / video /: id / data
    Renvoie les métadonnées pour une seule vidéo. Utilisé par la page Player de notre interface.
  • / video /: id
    Diffuse une vidéo avec un identifiant donné. Utilisé par la page Player .

Créons les itinéraires.

Renvoyer les données de maquette pour la liste des vidéos

Pour cette application de démonstration, nous allons créer un tableau d'objets qui contiendra les métadonnées et les enverra au frontend sur demande. Dans une application réelle, vous liriez probablement les données d'une base de données, qui serait ensuite utilisée pour générer un tableau comme celui-ci. Par souci de simplicité, nous ne le ferons pas dans ce didacticiel.

Dans notre dossier principal, créez un fichier mockdata.js et remplissez-le avec les métadonnées de notre liste de vidéos.

 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 

Nous pouvons voir d'en haut, chaque objet contient des informations sur la vidéo. Notez l'attribut poster qui contient le lien vers une image poster de la vidéo.

Créons un itinéraire videos puisque toutes nos demandes à faire par le frontend sont précédées de / videos .

Pour ce faire, créons un dossier routes et ajoutons un fichier Video.js pour notre route / videos . Dans ce fichier, nous aurons besoin de express et utiliserons le routeur express pour créer notre route.

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

Lorsque nous allons sur la route / videos nous voulons obtenir notre liste de vidéos, alors demandons le fichier mockData.js dans notre fichier Video.js et faites notre demande.

 const express = require ('express')
const router = express.Router ()
const videos = require ('../ mockData')
// obtenir la liste des vidéos
router.get ('/', (req, res) => {
    res.json (vidéos)
})
module.exports = router; 

La route / videos est maintenant déclarée, enregistrez le fichier et il devrait redémarrer automatiquement le serveur. Une fois qu'il a démarré, accédez à http: // localhost: 3000 / videos et notre tableau est renvoyé au format JSON.

Renvoyez les données pour une seule vidéo

Nous voulons être en mesure de faire un demande d'une vidéo particulière dans notre liste de vidéos. Nous pouvons récupérer des données vidéo particulières dans notre tableau en utilisant l ' id que nous lui avons donné. Faisons une demande, toujours dans notre fichier Video.js .


// faire une demande pour une vidéo particulière
router.get ('/: id / data', (req, res) => {
    const id = parseInt (req.params.id, 10)
    res.json (vidéos [id])
}) 

Le code ci-dessus obtient l'identifiant des paramètres de l'itinéraire et le convertit en un entier. Ensuite, nous renvoyons au client l'objet qui correspond à id du tableau videos .

Streaming The Videos

Dans notre app.js fichier, nous avons créé une route / video qui sert une vidéo au client. Nous voulons que ce point de terminaison envoie de plus petits morceaux de la vidéo, au lieu de servir un fichier vidéo entier sur demande.

Nous voulons pouvoir dynamiquement servir l'une des trois vidéos qui se trouvent dans le ] allVideos et diffusez les vidéos en morceaux, donc:

Supprimez la route / video de app.js .

Nous avons besoin de trois vidéos, donc copiez les exemples de vidéos du code source du didacticiel dans le répertoire assets / de votre projet server . Assurez-vous que les noms de fichiers des vidéos correspondent à l'id dans le tableau videos :

De retour dans notre fichier Video.js créez l'itinéraire pour vidéos en streaming.

 router.get ('/ video /: id', (req, res) => {
    const videoPath = `assets / $ {req.params.id} .mp4`;
    const videoStat = fs.statSync (chemin vidéo);
    const fileSize = videoStat.size;
    const videoRange = req.headers.range;
    if (videoRange) {
        const parts = videoRange.replace (/ bytes = /, "") .split ("-");
        const start = parseInt (parties [0]10);
        const end = parts [1]
            ? parseInt (pièces [1]10)
            : fileSize-1;
        const chunksize = (fin-début) + 1;
        fichier const = fs.createReadStream (videoPath, {start, end});
        const head = {
            'Content-Range': `octets $ {start} - $ {end} / $ {fileSize}`,
            'Accept-Ranges': 'octets',
            'Content-Length': taille de bloc,
            'Content-Type': 'vidéo / mp4',
        };
        res.writeHead (206, tête);
        file.pipe (res);
    } autre {
        const head = {
            'Content-Length': taille du fichier,
            'Content-Type': 'vidéo / mp4',
        };
        res.writeHead (200, tête);
        fs.createReadStream (videoPath) .pipe (res);
    }
}); 

Si nous naviguons vers http: // localhost: 5000 / videos / video / outside-the-wire dans notre navigateur, nous pouvons voir la vidéo en streaming.

Comment le streaming Video Route Works

Il y a pas mal de code écrit dans notre route vidéo stream, alors regardons-le ligne par ligne.

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

Premièrement, à partir de notre requête, nous obtenons le id de la route en utilisant req.params.id et l'utilisons pour générer le videoPath vers la vidéo. Nous lisons ensuite le fileSize en utilisant le système de fichiers fs que nous avons importé. Pour les vidéos, le navigateur d'un utilisateur enverra un paramètre de plage dans la demande. Cela permet au serveur de savoir quelle partie de la vidéo renvoyer au client.

Certains navigateurs envoient une plage dans la requête initiale, mais d'autres pas. Pour ceux qui n'en ont pas, ou si pour toute autre raison le navigateur n'envoie pas de plage, nous traitons cela dans le bloc else . Ce code récupère la taille du fichier et envoie les premiers morceaux de la vidéo:

 else {
    const head = {
        'Content-Length': taille du fichier,
        'Content-Type': 'vidéo / mp4',
    };
    res.writeHead (200, tête);
    fs.createReadStream (chemin) .pipe (res);
} 

Nous traiterons les requêtes suivantes incluant la plage dans un bloc if .

 if (videoRange) {
        const parts = videoRange.replace (/ bytes = /, "") .split ("-");
        const start = parseInt (parties [0]10);
        const end = parts [1]
            ? parseInt (pièces [1]10)
            : fileSize-1;
        const chunksize = (fin-début) + 1;
        fichier const = fs.createReadStream (videoPath, {start, end});
        const head = {
            'Content-Range': `octets $ {start} - $ {end} / $ {fileSize}`,
            'Accept-Ranges': 'octets',
            'Content-Length': taille de bloc,
            'Content-Type': 'vidéo / mp4',
        };
        res.writeHead (206, tête);
        file.pipe (res);
    } 

Ce code ci-dessus crée un flux de lecture en utilisant les valeurs start et end de la plage. Définissez Content-Length des en-têtes de réponse sur la taille de bloc calculée à partir des valeurs start et end . Nous utilisons également le code HTTP 206 signifiant que la réponse contient un contenu partiel. Cela signifie que le navigateur continuera à faire des requêtes jusqu'à ce qu'il ait récupéré tous les morceaux de la vidéo.

Que se passe-t-il sur les connexions instables

Si l'utilisateur est sur une connexion lente, le flux réseau le signalera en demandant que le I / O source s'arrête jusqu'à ce que le client soit prêt pour plus de données. Ceci est connu sous le nom de contre-pression . Nous pouvons pousser cet exemple un peu plus loin et voir à quel point il est facile d'étendre le flux. Nous pouvons aussi facilement ajouter de la compression!

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

Nous pouvons voir ci-dessus qu'un ReadStream est créé et sert le bloc vidéo par bloc.

 const head = {
            'Content-Range': `octets $ {start} - $ {end} / $ {fileSize}`,
            'Accept-Ranges': 'octets',
            'Content-Length': taille de bloc,
            'Content-Type': 'vidéo / mp4',
        };
res.writeHead (206, tête);
        file.pipe (res); 

L'en-tête de la requête contient le Content-Range qui est le début et la fin du changement pour obtenir le prochain morceau de vidéo à diffuser sur le frontend, le contenu -length est le morceau de vidéo envoyé. Nous spécifions également le type de contenu que nous diffusons qui est mp4 . La tête d'écriture 206 est configurée pour répondre uniquement aux flux nouvellement créés.

Création d'un fichier de sous-titres pour nos vidéos

Voici à quoi ressemble un fichier de sous-titres .vtt .

 WEBVTT

00: 00: 00.200 -> 00: 00: 01.000
Créer un tutoriel peut être très

00: 00: 01.500 -> 00: 00: 04.300
amusant à faire. 

Les fichiers de sous-titres contiennent du texte pour ce qui est dit dans une vidéo. Il contient également des codes temporels indiquant le moment où chaque ligne de texte doit être affichée. Nous voulons que nos vidéos aient des sous-titres, et nous ne créerons pas notre propre fichier de sous-titres pour ce didacticiel. Vous pouvez donc vous diriger vers le dossier des légendes dans le répertoire assets dans the repo ] et téléchargez les légendes.

Créons une nouvelle route qui gérera la demande de sous-titres:

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

Construire notre frontend

Pour commencer la partie visuelle de notre système, nous devrons construire notre échafaudage frontal.

Note : Vous avez besoin de vue-cli pour créer notre application. Si vous ne l'avez pas installé sur votre ordinateur, vous pouvez lancer npm install -g @ vue / cli pour l'installer.

Installation

À la racine de notre projet, créons notre dossier frontal:

 frontend mkdir
cd frontend 

et dans celui-ci, nous initialisons notre fichier package.json copions et collons ce qui suit:

 {
  "nom": "mon-application",
  "scripts": {
    "dev": "nuxt",
    "build": "nuxt build",
    "generate": "nuxt generate",
    "start": "nuxt start"
  }
} 

puis installez nuxt :

 npm add nuxt 

et exécutez la commande suivante pour exécuter l'application Nuxt.js:

 npm run dev 

Our Nuxt File Structure

] Maintenant que Nuxt est installé, nous pouvons commencer à mettre en page notre interface.

Tout d'abord, nous devons créer un dossier layouts à la racine de notre application. Ce dossier définit la mise en page de l'application, quelle que soit la page vers laquelle nous naviguons. Des éléments tels que notre barre de navigation et notre pied de page se trouvent ici. Dans le dossier du frontend, nous créons default.vue pour notre mise en page par défaut lorsque nous démarrons notre application frontend.

 mkdir layouts
mises en page de cd
touchez default.vue 

Puis un dossier components pour créer tous nos composants. Nous n'aurons besoin que de deux composants, NavBar et composant vidéo . Donc, dans notre dossier racine du frontend, nous:

 composants mkdir
composants cd
touchez NavBar.vue
touch Video.vue 

Enfin, un dossier de pages où toutes nos pages comme home et about peuvent être créées. Les deux pages dont nous avons besoin dans cette application sont la page d'accueil affichant toutes nos vidéos et informations vidéo et une page de lecteur dynamique qui dirige vers la vidéo sur laquelle nous cliquons.

 pages mkdir
pages cd
touche index.vue
lecteur mkdir
lecteur CD
touch _name.vue 

Notre répertoire frontend ressemble maintenant à ceci:

 | -frontend
  | -composants
    | -NavBar.vue
    | -Vidéo.vue
  | -layouts
    | -default.vue
  | -pages
    | -index.vue
    | -joueur
      | -_nom.vue
  | -package.json
  | -yarn.lock 

Notre NavBar.vue ressemble à ceci:

 

La NavBar a une balise h1 qui affiche Streaming App avec un peu de style.

Importons la NavBar dans notre mise en page default.vue .

 // default.vue
  

La mise en page default.vue contient maintenant notre composant NavBar et la balise après elle indique où toute page que nous créons sera affichée.

In notre index.vue (qui est notre page d'accueil), faisons une demande à http: // localhost: 5000 / videos pour obtenir toutes les vidéos de notre serveur. Passer les données comme accessoire à notre composant video.vue que nous créerons plus tard. Mais pour l'instant, nous l'avons déjà importé.

  

Composant vidéo

Ci-dessous, nous déclarons d'abord notre accessoire. Étant donné que les données vidéo sont désormais disponibles dans le composant, en utilisant le v-for de Vue nous itérons sur toutes les données reçues et pour chacune d’entre elles, nous affichons les informations. Nous pouvons utiliser la directive v-for pour parcourir les données et les afficher sous forme de liste. Un style de base a également été ajouté.

   

Nous remarquons également que le NuxtLink a une route dynamique, c'est-à-dire le routage vers le /player/video.id .

La fonctionnalité que nous voulons, c'est quand un utilisateur clique sur l'une des vidéos, il commence la diffusion. Pour y parvenir, nous utilisons la nature dynamique de la route _name.vue .

Dans celle-ci, nous créons un lecteur vidéo et définissons la source sur notre point de terminaison pour la diffusion en continu de la vidéo, mais nous ajoutez la vidéo à lire à notre terminal à l'aide de ceci. $ route.params.name qui capture quel paramètre le lien a reçu.

   

Lorsque nous cliquons sur l'une des vidéos, nous obtenons:

 Résultat final de l'application de streaming vidéo Nuxt
Le streaming vidéo démarre lorsque l'utilisateur clique sur la vignette. ( Grand aperçu )

Ajout de notre fichier de sous-titres

Pour ajouter notre fichier de piste, nous nous assurons que tous les fichiers .vtt dans le dossier légendes portent le même nom que notre id . Mettez à jour notre élément vidéo avec la piste, en demandant les sous-titres.

 

Nous avons ajouté crossOrigin = "anonymous" à l'élément vidéo; sinon, la demande de sous-titres échouera. Maintenant, actualisez et vous verrez que les légendes ont été ajoutées avec succès.

Ce qu'il faut garder à l'esprit lors de la création d'un streaming vidéo résilient.

Lors de la création d'applications de streaming comme Twitch, Hulu ou Netflix, un certain nombre de choses sont mises en considération:

  • Pipeline de traitement de données vidéo
    Cela peut être un défi technique car des serveurs très performants sont nécessaires pour servir des millions de vidéos aux utilisateurs. Une latence élevée ou des temps d'arrêt doivent être évités à tout prix.
  • Mise en cache
    Des mécanismes de mise en cache doivent être utilisés lors de la création de ce type d'application Exemple Cassandra, Amazon S3, AWS SimpleDB.
  • Géographie des utilisateurs
    Compte tenu de la géographie

Conclusion

Dans ce tutoriel, nous avons vu comment créer un serveur dans Node.js qui diffuse des vidéos, génère des sous-titres pour ces vidéos et fournit des métadonnées des vidéos. Nous avons également vu comment utiliser Nuxt.js sur l'interface pour consommer les points de terminaison et les données générées par le serveur.

Contrairement à d'autres frameworks, créer une application avec Nuxt.js et Express.js est assez simple et rapide. La partie intéressante de Nuxt.js est la façon dont il gère vos itinéraires et vous permet de mieux structurer vos applications.

Ressources

 Smashing Editorial "width =" 35 "height =" 46 "loading =" lazy "decoding =" asynchrone (ks, vf, yk, il)




Source link