L'auteur du matériel, dont nous publions la traduction aujourd'hui, dit qu'il travaille sur une application qui vous permet d'organiser la diffusion en continu (streaming) de ce qui se passe sur le bureau de l'utilisateur.

L'application reçoit un flux au format RTMP du streamer et le convertit en un flux HLS, qui peut ĂȘtre lu dans les navigateurs des tĂ©lĂ©spectateurs. Cet article explique comment crĂ©er votre propre application de streaming Ă l'aide de Node.js et React. Si vous avez l'habitude de voir l'idĂ©e qui vous intĂ©resse, plongez-vous immĂ©diatement dans le code, vous pouvez maintenant regarder dans
ce référentiel.
Développement de serveur Web avec systÚme d'authentification de base
CrĂ©ons un serveur Web simple basĂ© sur Node.js, dans lequel, Ă l'aide de la bibliothĂšque passport.js, une stratĂ©gie d'authentification des utilisateurs locaux est mise en Ćuvre. Nous utiliserons MongoDB comme stockage permanent d'informations. Nous travaillerons avec la base de donnĂ©es en utilisant la bibliothĂšque Mongoose ODM.
Initialisez un nouveau projet:
$ npm init
Installez les dépendances:
$ npm install axios bcrypt-nodejs body-parser bootstrap config connect-ensure-login connect-flash cookie-parser ejs express express-session mongoose passport passport-local request session-file-store --save-dev
Dans le répertoire du projet, créez deux dossiers -
client
et
server
. Le code frontal basé sur React se retrouvera dans le dossier
client
et le code principal sera stocké dans le dossier
server
. Maintenant, nous travaillons dans le dossier du
server
. à savoir, nous utiliserons passport.js pour créer un systÚme d'authentification. Nous avons déjà installé les modules passeport et passeport local. Avant de décrire la stratégie d'authentification des utilisateurs locaux, créez un fichier
app.js
et ajoutez-y le code nĂ©cessaire pour dĂ©marrer un serveur simple. Si vous exĂ©cutez ce code par vous-mĂȘme, assurez-vous que le SGBD MongoDB est installĂ© et qu'il s'exĂ©cute en tant que service.
Voici le code du fichier qui se trouve dans le projet sur
server/app.js
:
const express = require('express'), Session = require('express-session'), bodyParse = require('body-parser'), mongoose = require('mongoose'), middleware = require('connect-ensure-login'), FileStore = require('session-file-store')(Session), config = require('./config/default'), flash = require('connect-flash'), port = 3333, app = express(); mongoose.connect('mongodb://127.0.0.1/nodeStream' , { useNewUrlParser: true }); app.set('view engine', 'ejs'); app.set('views', path.join(__dirname, './views')); app.use(express.static('public')); app.use(flash()); app.use(require('cookie-parser')()); app.use(bodyParse.urlencoded({extended: true})); app.use(bodyParse.json({extended: true})); app.use(Session({ store: new FileStore({ path : './server/sessions' }), secret: config.server.secret, maxAge : Date().now + (60 * 1000 * 30) })); app.get('*', middleware.ensureLoggedIn(), (req, res) => { res.render('index'); }); app.listen(port, () => console.log(`App listening on ${port}!`));
Nous avons tĂ©lĂ©chargĂ© tout le middleware nĂ©cessaire Ă l'application, connectĂ© Ă MongoDB, configurĂ© la session express pour utiliser le stockage de fichiers. Les sessions de stockage leur permettront d'ĂȘtre restaurĂ©es aprĂšs un redĂ©marrage du serveur.
Nous décrivons maintenant les stratégies passport.js pour organiser l'enregistrement et l'authentification des utilisateurs. Créez un dossier d'
auth
dans le dossier du
server
et placez-y le fichier
passport.js
. Voici ce qui devrait se trouver dans le fichier
server/auth/passport.js
:
const passport = require('passport'), LocalStrategy = require('passport-local').Strategy, User = require('../database/Schema').User, shortid = require('shortid'); passport.serializeUser( (user, cb) => { cb(null, user); }); passport.deserializeUser( (obj, cb) => { cb(null, obj); });
De plus, nous devons décrire le schéma du modÚle utilisateur (il sera appelé
UserSchema
). Créez le dossier de
database
dans le dossier du
server
et dans celui-ci le fichier
UserSchema.js
.
Voici le code du fichier
server/database.UserSchema.js
:
let mongoose = require('mongoose'), bcrypt = require('bcrypt-nodejs'), shortid = require('shortid'), Schema = mongoose.Schema; let UserSchema = new Schema({ username: String, email : String, password: String, stream_key : String, }); UserSchema.methods.generateHash = (password) => { return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null); }; UserSchema.methods.validPassword = function(password){ return bcrypt.compareSync(password, this.password); }; UserSchema.methods.generateStreamKey = () => { return shortid.generate(); }; module.exports = UserSchema;
UserSchema
a trois méthodes. La méthode
generateHash
est conçue pour convertir un mot de passe, présenté sous forme de texte brut, en un hachage bcrypt. Nous utilisons cette méthode dans la stratégie de passeport pour convertir les mots de passe entrés par les utilisateurs en hachages bcrypt. Les hachages de mot de passe reçus sont ensuite stockés dans la base de données. La méthode
validPassword
accepte le mot de passe entré par l'utilisateur et le vérifie en comparant son hachage avec le hachage stocké dans la base de données. La méthode
generateStreamKey
des chaßnes uniques que nous transférerons aux utilisateurs comme clés de streaming (clés de flux) pour les clients RTMP.
Voici le code du fichier
server/database/Schema.js
:
let mongoose = require('mongoose'); exports.User = mongoose.model('User', require('./UserSchema'));
Maintenant que nous avons défini des stratégies de passeport, décrit le schéma
UserSchema
et créé un modÚle basé sur celui-ci, initialisons le passeport dans
app.js
Voici le code pour compléter le fichier
server/app.js
:
De plus, les nouveaux itinĂ©raires doivent ĂȘtre enregistrĂ©s dans
app.js
Pour ce faire, ajoutez le code suivant Ă
server/app.js
:
Créez les fichiers
login.js
et
register.js
dans le dossier
routes
situé dans le dossier du
server
. Dans ces fichiers, nous définissons quelques-unes des routes ci-dessus et utilisons le middleware de passeport pour organiser l'enregistrement et l'authentification des utilisateurs.
Voici le code du
server/routes/login.js
:
const express = require('express'), router = express.Router(), passport = require('passport'); router.get('/', require('connect-ensure-login').ensureLoggedOut(), (req, res) => { res.render('login', { user : null, errors : { email : req.flash('email'), password : req.flash('password') }); }); router.post('/', passport.authenticate('localLogin', { successRedirect : '/', failureRedirect : '/login', failureFlash : true })); module.exports = router;
Voici le code du fichier
server/routes/register.js
:
const express = require('express'), router = express.Router(), passport = require('passport'); router.get('/', require('connect-ensure-login').ensureLoggedOut(), (req, res) => { res.render('register', { user : null, errors : { username : req.flash('username'), email : req.flash('email') }); }); router.post('/', require('connect-ensure-login').ensureLoggedOut(), passport.authenticate('localRegister', { successRedirect : '/', failureRedirect : '/register', failureFlash : true }) ); module.exports = router;
Nous utilisons le moteur de modélisation ejs. Ajoutez les fichiers de modÚle
login.ejs
et
register.ejs
au dossier
views
, qui se trouve dans le dossier du
server
.
Voici le contenu du
server/views/login.ejs
:
<!doctype html> <html lang="en"> <% include header.ejs %> <body> <% include navbar.ejs %> <div class="container app mt-5"> <h4>Login</h4> <hr class="my-4"> <div class="row"> <form action="/login" method="post" class="col-xs-12 col-sm-12 col-md-8 col-lg-6"> <div class="form-group"> <label>Email address</label> <input type="email" name="email" class="form-control" placeholder="Enter email" required> <% if (errors.email.length) { %> <small class="form-text text-danger"><%= errors.email %></small> <% } %> </div> <div class="form-group"> <label>Password</label> <input type="password" name="password" class="form-control" placeholder="Password" required> <% if (errors.password.length) { %> <small class="form-text text-danger"><%= errors.password %></small> <% } %> </div> <div class="form-group"> <div class="leader"> Don't have an account? Register <a href="/register">here</a>. </div> </div> <button type="submit" class="btn btn-dark btn-block">Login</button> </form> </div> </div> <% include footer.ejs %> </body> </html>
Voici ce qui devrait se trouver dans le fichier
server/views/register.ejs
:
<!doctype html> <html lang="en"> <% include header.ejs %> <body> <% include navbar.ejs %> <div class="container app mt-5"> <h4>Register</h4> <hr class="my-4"> <div class="row"> <form action="/register" method="post" class="col-xs-12 col-sm-12 col-md-8 col-lg-6"> <div class="form-group"> <label>Username</label> <input type="text" name="username" class="form-control" placeholder="Enter username" required> <% if (errors.username.length) { %> <small class="form-text text-danger"><%= errors.username %></small> <% } %> </div> <div class="form-group"> <label>Email address</label> <input type="email" name="email" class="form-control" placeholder="Enter email" required> <% if (errors.email.length) { %> <small class="form-text text-danger"><%= errors.email %></small> <% } %> </div> <div class="form-group"> <label>Password</label> <input type="password" name="password" class="form-control" placeholder="Password" required> </div> <div class="form-group"> <div class="leader"> Have an account? Login <a href="/login">here</a>. </div> </div> <button type="submit" class="btn btn-dark btn-block">Register</button> </form> </div> </div> <% include footer.ejs %> </body> </html>
Nous pouvons dire que nous avons terminé le travail sur le systÚme d'authentification. Nous allons maintenant commencer la création de la prochaine partie du projet et configurer le serveur RTMP.
Configurer le serveur RTMP
RTMP (Real-Time Messaging Protocol) est un protocole qui a été développé pour la transmission haute performance de données vidéo, audio et diverses entre le lecteur de bande et le serveur. Twitch, Facebook, YouTube et de nombreux autres sites de streaming acceptent les flux RTMP et les transcodent en flux HTTP (format HLS) avant de transférer ces flux vers leurs CDN pour garantir leur haute disponibilité.
Nous utilisons le module node-media-server - Node.js-implementation du serveur multimĂ©dia RTMP. Ce serveur multimĂ©dia accepte les flux RTMP et les convertit en HLS / DASH Ă l'aide de l'infrastructure multimĂ©dia ffmpeg. Pour que le projet fonctionne correctement, ffmpeg doit ĂȘtre installĂ© sur votre systĂšme. Si vous travaillez sous Linux et que vous avez dĂ©jĂ installĂ© ffmpeg, vous pouvez trouver le chemin d'accĂšs en exĂ©cutant la commande suivante Ă partir du terminal:
$ which ffmpeg # /usr/bin/ffmpeg
Pour travailler avec le package node-media-server, ffmpeg version 4.x est recommandé. Vous pouvez vérifier la version installée de ffmpeg comme ceci:
$ ffmpeg --version # ffmpeg version 4.1.3-0york1~18.04 Copyright (c) 2000-2019 the # FFmpeg developers built with gcc 7 (Ubuntu 7.3.0-27ubuntu1~18.04)
Si vous n'avez pas installé ffmpeg et que vous exécutez Ubuntu, vous pouvez installer ce cadre en exécutant la commande suivante:
# PPA-. PPA, # ffmpeg 3.x. $ sudo add-apt-repository ppa:jonathonf/ffmpeg-4 $ sudo apt install ffmpeg
Si vous travaillez sous Windows, vous pouvez télécharger les
versions ffmpeg pour Windows.
Ajoutez le fichier de configuration
server/config/default.js
au projet:
const config = { server: { secret: 'kjVkuti2xAyF3JGCzSZTk0YWM5JhI9mgQW4rytXc' }, rtmp_server: { rtmp: { port: 1935, chunk_size: 60000, gop_cache: true, ping: 60, ping_timeout: 30 }, http: { port: 8888, mediaroot: './server/media', allow_origin: '*' }, trans: { ffmpeg: '/usr/bin/ffmpeg', tasks: [ app: 'live', hls: true, hlsFlags: '[hls_time=2:hls_list_size=3:hls_flags=delete_segments]', dash: true, dashFlags: '[f=dash:window_size=3:extra_window_size=5]' }; module.exports = config;
Remplacez la valeur de la propriété
ffmpeg
par le chemin oĂč ffmpeg est installĂ© sur votre systĂšme. Si vous travaillez sur Windows et avez tĂ©lĂ©chargĂ© l'assembly ffmpeg de Windows Ă partir du lien ci-dessus - n'oubliez pas d'ajouter l'extension
.exe
au nom de fichier. Ensuite, le fragment correspondant du code ci-dessus ressemblera Ă ceci:
const config = { .... trans: { ffmpeg: 'D:/ffmpeg/bin/ffmpeg.exe', ... };
Installez maintenant node-media-server en exécutant la commande suivante:
$ npm install node-media-server --save
Créez un fichier
media_server.js
dans le dossier du
server
.
Voici le code Ă mettre dans
server/media_server.js
:
const NodeMediaServer = require('node-media-server'), config = require('./config/default').rtmp_server; nms = new NodeMediaServer(config); nms.on('prePublish', async (id, StreamPath, args) => { let stream_key = getStreamKeyFromStreamPath(StreamPath); console.log('[NodeEvent on prePublish]', `id=${id} StreamPath=${StreamPath} args=${JSON.stringify(args)}`); }); const getStreamKeyFromStreamPath = (path) => { let parts = path.split('/'); return parts[parts.length - 1]; }; module.exports = nms;
L'utilisation de l'objet
NodeMediaService
assez simple. Il fournit le serveur RTMP et vous permet d'attendre les connexions. Si la clĂ© de streaming n'est pas valide, la connexion entrante peut ĂȘtre rejetĂ©e. Nous traiterons l'Ă©vĂ©nement de cet objet de
prePublish
. Dans la section suivante, nous ajoutons du code supplémentaire à la fermeture de l'écouteur d'événement de
prePublish
. Il vous permettra de rejeter les connexions entrantes avec des clés de streaming non valides. En attendant, nous accepterons toutes les connexions entrantes arrivant sur le port RTMP par défaut (1935). Il suffit d'importer l'objet
app.js
dans le
node_media_server
et d'appeler sa méthode d'
run
.
Ajoutez le code suivant Ă
server/app.js
:
Téléchargez et installez
OBS (Open Broadcaster Software). Ouvrez la fenĂȘtre des paramĂštres de l'application et accĂ©dez Ă la section
Stream
. Sélectionnez
Custom
dans le champ
Service
et entrez
rtmp://127.0.0.1:1935/live
dans le champ
Server
. Le champ
Stream Key
peut ĂȘtre laissĂ© vide. Si le programme ne vous permet pas d'enregistrer les paramĂštres sans remplir ce champ, vous pouvez entrer n'importe quel jeu de caractĂšres. Cliquez sur le bouton
Apply
et sur le bouton
OK
. Cliquez sur le bouton
Start Streaming
pour commencer à transférer votre flux RTMP vers votre propre serveur local.
Configurer OBSAccédez au terminal et regardez ce que le serveur multimédia y affiche. Vous y verrez des informations sur le flux entrant et les journaux de plusieurs écouteurs d'événements.
Sortie de donnĂ©es vers le terminal par un serveur multimĂ©dia basĂ© sur Node.jsLe serveur multimĂ©dia donne accĂšs Ă l'API, qui vous permet d'obtenir une liste des clients connectĂ©s. Pour voir cette liste, vous pouvez aller sur le navigateur Ă
http://127.0.0.1:8888/api/streams
. Plus tard, nous utiliserons cette API dans une application React pour afficher une liste d'utilisateurs de diffusion. Voici ce que vous pouvez voir en accédant à cette API:
{ "live": { "0wBic-qV4": { "publisher": { "app": "live", "stream": "0wBic-qV4", "clientId": "WMZTQAEY", "connectCreated": "2019-05-12T16:13:05.759Z", "bytes": 33941836, "ip": "::ffff:127.0.0.1", "audio": { "codec": "AAC", "profile": "LC", "samplerate": 44100, "channels": 2 }, "video": { "codec": "H264", "width": 1920, "height": 1080, "profile": "High", "level": 4.2, "fps": 60 }, "subscribers": [ "app": "live", "stream": "0wBic-qV4", "clientId": "GNJ9JYJC", "connectCreated": "2019-05-12T16:13:05.985Z", "bytes": 33979083, "ip": "::ffff:127.0.0.1", "protocol": "rtmp" }
Maintenant, le backend est presque prĂȘt. Il s'agit d'un serveur de streaming opĂ©rationnel qui prend en charge les technologies HTTP, RTMP et HLS. Cependant, nous n'avons pas encore créé de systĂšme pour vĂ©rifier les connexions RTMP entrantes. Cela devrait nous permettre de nous assurer que le serveur accepte uniquement les flux provenant d'utilisateurs authentifiĂ©s. Ajoutez le code suivant au
prePublish
événements
prePublish
dans le
server/media_server.js
:
Dans la fermeture, nous interrogeons la base de données pour trouver l'utilisateur avec la clé de streaming. Si la clé appartient à l'utilisateur, nous autorisons simplement l'utilisateur à se connecter au serveur et à publier sa diffusion. Sinon, nous rejetons la connexion RTMP entrante.
Dans la section suivante, nous allons créer une application cÎté client simple basée sur React. Il est nécessaire pour permettre aux téléspectateurs de regarder des émissions en streaming, ainsi que pour permettre aux streamers de générer et d'afficher leurs clés de streaming.
Streaming en direct
Allez maintenant dans le dossier
clients
. Puisque nous allons créer une application React, nous aurons besoin d'un webpack. Nous avons également besoin de chargeurs, qui sont utilisés pour traduire le code JSX en code JavaScript que les navigateurs comprennent. Installez les modules suivants:
$ npm install @babel/core @babel/preset-env @babel/preset-react babel-loader css-loader file-loader mini-css-extract-plugin node-sass sass-loader style-loader url-loader webpack webpack-cli react react-dom react-router-dom video.js jquery bootstrap history popper.js
Ajoutez au projet, dans son répertoire racine, le fichier de configuration de webpack (
webpack.config.js
):
const path = require('path'); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const devMode = process.env.NODE_ENV !== 'production'; const webpack = require('webpack'); module.exports = { entry : './client/index.js', output : { filename : 'bundle.js', path : path.resolve(__dirname, 'public') }, module : { rules : [ test: /\.s?[ac]ss$/, use: [ MiniCssExtractPlugin.loader, { loader: 'css-loader', options: { url: false, sourceMap: true } }, { loader: 'sass-loader', options: { sourceMap: true } } ], }, test: /\.js$/, exclude: /node_modules/, use: "babel-loader" }, test: /\.woff($|\?)|\.woff2($|\?)|\.ttf($|\?)|\.eot($|\?)|\.svg($|\?)/, loader: 'url-loader' }, test: /\.(png|jpg|gif)$/, use: [{ loader: 'file-loader', options: { outputPath: '/', }, }], }, }, devtool: 'source-map', plugins: [ new MiniCssExtractPlugin({ filename: "style.css" }), new webpack.ProvidePlugin({ $: 'jquery', jQuery: 'jquery' }) ], mode : devMode ? 'development' : 'production', watch : devMode, performance: { hints: process.env.NODE_ENV === 'production' ? "warning" : false }, };
Ajoutez le fichier
client/index.js
au projet:
import React from "react"; import ReactDOM from 'react-dom'; import {BrowserRouter} from 'react-router-dom'; import 'bootstrap'; require('./index.scss'); import Root from './components/Root.js'; if(document.getElementById('root')){ ReactDOM.render( <BrowserRouter> <Root/> </BrowserRouter>, document.getElementById('root') ); }
Voici le contenu du
client/index.scss
:
@import '~bootstrap/dist/css/bootstrap.css'; @import '~video.js/dist/video-js.css'; @import url('https://fonts.googleapis.com/css?family=Dosis'); html,body{ font-family: 'Dosis', sans-serif; }
Le routeur React est utilisé pour le routage. Dans le frontend, nous utilisons également le bootstrap et, pour les diffusions, video.js. Ajoutez maintenant le dossier des
components
dossier
client
et le fichier
Root.js
Ă
Root.js
. Voici le contenu du
client/components/Root.js
:
import React from "react"; import {Router, Route} from 'react-router-dom'; import Navbar from './Navbar'; import LiveStreams from './LiveStreams'; import Settings from './Settings'; import VideoPlayer from './VideoPlayer'; const customHistory = require("history").createBrowserHistory(); export default class Root extends React.Component { constructor(props){ super(props); render(){ return ( <Router history={customHistory} > <div> <Navbar/> <Route exact path="/" render={props => ( <LiveStreams {...props} /> )}/> <Route exact path="/stream/:username" render={(props) => ( <VideoPlayer {...props}/> )}/> <Route exact path="/settings" render={props => ( <Settings {...props} /> )}/> </div> </Router> }
Le composant
<Root/>
affiche un composant
<Router/>
React qui contient trois sous-composants
<Route/>
. Le composant
<LiveStreams/>
affiche une liste des diffusions. Le
<VideoPlayer/>
est responsable de l'affichage du lecteur video.js. Le composant
<Settings/>
est chargé de créer une interface pour travailler avec les clés de streaming.
Créez le
client/components/LiveStreams.js
:
import React from 'react'; import axios from 'axios'; import {Link} from 'react-router-dom'; import './LiveStreams.scss'; import config from '../../server/config/default'; export default class Navbar extends React.Component { constructor(props) { super(props); this.state = { live_streams: [] componentDidMount() { this.getLiveStreams(); getLiveStreams() { axios.get('http://127.0.0.1:' + config.rtmp_server.http.port + '/api/streams') .then(res => { let streams = res.data; if (typeof (streams['live'] !== 'undefined')) { this.getStreamsInfo(streams['live']); }); getStreamsInfo(live_streams) { axios.get('/streams/info', { params: { streams: live_streams }).then(res => { this.setState({ live_streams: res.data }, () => { console.log(this.state); }); }); render() { let streams = this.state.live_streams.map((stream, index) => { return ( <div className="stream col-xs-12 col-sm-12 col-md-3 col-lg-4" key={index}> <span className="live-label">LIVE</span> <Link to={'/stream/' + stream.username}> <div className="stream-thumbnail"> <img align="center" src={'/thumbnails/' + stream.stream_key + '.png'}/> </div> </Link> <span className="username"> <Link to={'/stream/' + stream.username}> {stream.username} </Link> </span> </div> ); }); return ( <div className="container mt-5"> <h4>Live Streams</h4> <hr className="my-4"/> <div className="streams row"> {streams} </div> </div> }
Voici Ă quoi ressemble la page d'application.
Service de streaming frontalAprÚs avoir monté le composant
<LiveStreams/>
, l'API NMS est appelĂ©e pour obtenir une liste des clients connectĂ©s au systĂšme. L'API NMS ne fournit pas beaucoup d'informations sur les utilisateurs. En particulier, nous pouvons en obtenir des informations sur les clĂ©s de streaming, via lesquelles les utilisateurs sont connectĂ©s Ă un serveur RTMP. Nous utiliserons ces clĂ©s lors de la gĂ©nĂ©ration de requĂȘtes dans la base de donnĂ©es pour obtenir des informations sur les comptes d'utilisateurs.
Dans la méthode
getStreamsInfo
nous exécutons une demande XHR vers
/streams/info
, mais nous n'avons pas encore créé quelque chose qui puisse répondre à cette demande. Créez un
server/routes/streams.js
avec le contenu suivant:
const express = require('express'), router = express.Router(), User = require('../database/Schema').User; router.get('/info', require('connect-ensure-login').ensureLoggedIn(), (req, res) => { if(req.query.streams){ let streams = JSON.parse(req.query.streams); let query = {$or: []}; for (let stream in streams) { if (!streams.hasOwnProperty(stream)) continue; query.$or.push({stream_key : stream}); User.find(query,(err, users) => { if (err) return; if (users) { res.json(users); }); }); module.exports = router;
Nous transmettons les informations de flux retournées par l'API NMS au backend, afin d'obtenir des informations sur les clients connectés.
Nous effectuons une requĂȘte de base de donnĂ©es pour obtenir une liste d'utilisateurs dont les clĂ©s de streaming correspondent Ă celles que nous avons reçues de l'API NMS. Nous renvoyons la liste au format JSON. Nous enregistrons l'itinĂ©raire dans le
server/app.js
:
app.use('/streams', require('./routes/streams'));
En consĂ©quence, nous affichons une liste des Ă©missions actives. Cette liste contient le nom d'utilisateur et la miniature. Nous parlerons de la façon de crĂ©er des vignettes pour les Ă©missions Ă la fin de l'article. Les miniatures sont liĂ©es Ă des pages spĂ©cifiques oĂč les flux HLS sont lus Ă l'aide de video.js.
Créez le
client/components/VideoPlayer.js
:
import React from 'react'; import videojs from 'video.js' import axios from 'axios'; import config from '../../server/config/default'; export default class VideoPlayer extends React.Component { constructor(props) { super(props); this.state = { stream: false, videoJsOptions: null componentDidMount() { axios.get('/user', { params: { username: this.props.match.params.username }).then(res => { this.setState({ stream: true, videoJsOptions: { autoplay: false, controls: true, sources: [{ src: 'http://127.0.0.1:' + config.rtmp_server.http.port + '/live/' + res.data.stream_key + '/index.m3u8', type: 'application/x-mpegURL' }], fluid: true, }, () => { this.player = videojs(this.videoNode, this.state.videoJsOptions, function onPlayerReady() { console.log('onPlayerReady', this) }); }); }) componentWillUnmount() { if (this.player) { this.player.dispose() render() { return ( <div className="row"> <div className="col-xs-12 col-sm-12 col-md-10 col-lg-8 mx-auto mt-5"> {this.state.stream ? ( <div data-vjs-player> <video ref={node => this.videoNode = node} className="video-js vjs-big-play-centered"/> </div> ) : ' Loading ... '} </div> </div> }
HLS- video.js.
,
client/components/Settings.js
:
import React from 'react'; import axios from 'axios'; export default class Navbar extends React.Component { constructor(props){ super(props); this.state = { stream_key : '' }; this.generateStreamKey = this.generateStreamKey.bind(this); componentDidMount() { this.getStreamKey(); generateStreamKey(e){ axios.post('/settings/stream_key') .then(res => { this.setState({ stream_key : res.data.stream_key }); }) getStreamKey(){ axios.get('/settings/stream_key') .then(res => { this.setState({ stream_key : res.data.stream_key }); }) render() { return ( <React.Fragment> <div className="container mt-5"> <h4>Streaming Key</h4> <hr className="my-4"/> <div className="col-xs-12 col-sm-12 col-md-8 col-lg-6"> <div className="row"> <h5>{this.state.stream_key}</h5> </div> <div className="row"> <button className="btn btn-dark mt-2" onClick={this.generateStreamKey}> Generate a new key </button> </div> </div> </div> <div className="container mt-5"> <h4>How to Stream</h4> <hr className="my-4"/> <div className="col-12"> <div className="row"> <p> You can use <a target="_blank" href="https://obsproject.com/">OBS</a> or <a target="_blank" href="https://www.xsplit.com/">XSplit</a> to Live stream. If you're using OBS, go to Settings > Stream and select Custom from service dropdown. Enter <b>rtmp://127.0.0.1:1935/live</b> in server input field. Also, add your stream key. Click apply to save. </p> </div> </div> </div> </React.Fragment> }
passport.js, , , .
/settings
â . XHR-
<Settings/>
.
.
Generate a new key
. XHR- . , . . â
GET
POST
/settings/stream_key
.
server/routes/settings.js
:
const express = require('express'), router = express.Router(), User = require('../database/Schema').User, shortid = require('shortid'); router.get('/stream_key', require('connect-ensure-login').ensureLoggedIn(), (req, res) => { User.findOne({email: req.user.email}, (err, user) => { if (!err) { res.json({ stream_key: user.stream_key }) }); }); router.post('/stream_key', require('connect-ensure-login').ensureLoggedIn(), (req, res) => { User.findOneAndUpdate({ email: req.user.email }, { stream_key: shortid.generate() }, { upsert: true, new: true, }, (err, user) => { if (!err) { res.json({ stream_key: user.stream_key }) }); }); module.exports = router;
shortid.
server/app.js
:
app.use('/settings', require('./routes/settings'));
,<LiveStreams/>
(
client/components/LiveStreams.js
) :
render() { let streams = this.state.live_streams.map((stream, index) => { return ( <div className="stream col-xs-12 col-sm-12 col-md-3 col-lg-4" key={index}> <span className="live-label">LIVE</span> <Link to={'/stream/' + stream.username}> <div className="stream-thumbnail"> <img align="center" src={'/thumbnails/' + stream.stream_key + '.png'}/> </div> </Link> <span className="username"> <Link to={'/stream/' + stream.username}> {stream.username} </Link> </span> </div> ); }); return ( <div className="container mt-5"> <h4>Live Streams</h4> <hr className="my-4"/> <div className="streams row"> {streams} </div> </div> }
. cron, , 5 , .
server/helpers/helpers.js
:
const spawn = require('child_process').spawn, config = require('../config/default'), cmd = config.rtmp_server.trans.ffmpeg; const generateStreamThumbnail = (stream_key) => { const args = [ '-y', '-i', 'http://127.0.0.1:8888/live/'+stream_key+'/index.m3u8', '-ss', '00:00:01', '-vframes', '1', '-vf', 'scale=-2:300', 'server/thumbnails/'+stream_key+'.png', ]; spawn(cmd, args, { detached: true, stdio: 'ignore' }).unref(); }; module.exports = { generateStreamThumbnail : generateStreamThumbnail };
generateStreamThumbnail
.
ffmpeg-, HLS-.
prePublish
(
server/media_server.js
):
nms.on('prePublish', async (id, StreamPath, args) => { let stream_key = getStreamKeyFromStreamPath(StreamPath); console.log('[NodeEvent on prePublish]', `id=${id} StreamPath=${StreamPath} args=${JSON.stringify(args)}`); User.findOne({stream_key: stream_key}, (err, user) => { if (!err) { if (!user) { let session = nms.getSession(id); session.reject(); } else { helpers.generateStreamThumbnail(stream_key); }); });
, cron (
server/cron/thumbnails.js
):
const CronJob = require('cron').CronJob, request = require('request'), helpers = require('../helpers/helpers'), config = require('../config/default'), port = config.rtmp_server.http.port; const job = new CronJob('*/5 * * * * *', function () { request .get('http://127.0.0.1:' + port + '/api/streams', function (error, response, body) { let streams = JSON.parse(body); if (typeof (streams['live'] !== undefined)) { let live_streams = streams['live']; for (let stream in live_streams) { if (!live_streams.hasOwnProperty(stream)) continue; helpers.generateStreamThumbnail(stream); }); }, null, true); module.exports = job;
5 . API NMS .
server/app.js
:
Résumé
, . , . - â
. - â .
.
! , , ?