Entwicklung einer Anwendung für das Streaming mit Node.js und React

Der Autor des Materials, dessen Übersetzung wir heute veröffentlichen, sagt, dass er an einer Anwendung arbeitet, mit der Sie Streaming-Broadcasts (Streaming) der Vorgänge auf dem Desktop des Benutzers organisieren können.

Bild

Die Anwendung empfängt vom Streamer einen Stream im RTMP-Format und konvertiert ihn in einen HLS-Stream, der in den Browsern der Zuschauer abgespielt werden kann. In diesem Artikel wird erläutert, wie Sie mit Node.js und React Ihre eigene Streaming-Anwendung erstellen können. Wenn Sie es gewohnt sind, die Idee zu sehen, die Sie interessiert, tauchen Sie sofort in den Code ein. Sie können jetzt in dieses Repository schauen.

Webserverentwicklung mit Basisauthentifizierungssystem


Erstellen wir einen einfachen Webserver auf Basis von Node.js, auf dem mithilfe der Bibliothek passport.js eine lokale Benutzerauthentifizierungsstrategie implementiert wird. Wir werden MongoDB als permanente Speicherung von Informationen verwenden. Wir werden mit der Datenbank unter Verwendung der Mongoose ODM-Bibliothek arbeiten.

Initialisieren Sie ein neues Projekt:

$ npm init 

Installieren Sie die Abhängigkeiten:

 $ 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 

Erstellen Sie im Projektverzeichnis zwei Ordner - client und server . Der auf React basierende Frontend-Code wird im client Ordner gespeichert, und der Backend-Code wird im server Ordner gespeichert. Jetzt arbeiten wir im Serverordner. Wir werden nämlich passport.js verwenden, um ein Authentifizierungssystem zu erstellen. Wir haben bereits die Module passport und passport-local installiert. Bevor wir die lokale Benutzerauthentifizierungsstrategie beschreiben, erstellen Sie eine app.js Datei und fügen Sie den Code hinzu, der zum Starten eines einfachen Servers erforderlich ist. Wenn Sie diesen Code selbst ausführen, stellen Sie sicher, dass das MongoDB-DBMS installiert ist und als Dienst ausgeführt wird.

Hier ist der Code für die Datei, die sich im Projekt unter 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}!`)); 

Wir haben die gesamte für die Anwendung erforderliche Middleware heruntergeladen, die mit MongoDB verbunden ist, und die Express-Sitzung für die Verwendung des Dateispeichers konfiguriert. Durch das Speichern von Sitzungen können diese nach einem Neustart des Servers wiederhergestellt werden.

Jetzt beschreiben wir die Strategien von passport.js zum Organisieren der Benutzerregistrierung und -authentifizierung. Erstellen Sie einen auth im Serverordner und legen Sie die Datei passport.js darin ab. Folgendes sollte in der Datei server/auth/passport.js enthalten sein:

 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); }); //  passport,    passport.use('localRegister', new LocalStrategy({        usernameField: 'email',        passwordField: 'password',        passReqToCallback: true    },    (req, email, password, done) => {        User.findOne({$or: [{email: email}, {username: req.body.username}]}, (err, user) => {            if (err)                return done(err);            if (user) {                if (user.email === email) {                    req.flash('email', 'Email is already taken');                               if (user.username === req.body.username) {                    req.flash('username', 'Username is already taken');                               return done(null, false);            } else {                let user = new User();                user.email = email;                user.password = user.generateHash(password);                user.username = req.body.username;                user.stream_key = shortid.generate();                user.save( (err) => {                    if (err)                        throw err;                    return done(null, user);                });                   });    })); //  passport,    passport.use('localLogin', new LocalStrategy({        usernameField: 'email',        passwordField: 'password',        passReqToCallback: true    },    (req, email, password, done) => {        User.findOne({'email': email}, (err, user) => {            if (err)                return done(err);            if (!user)                return done(null, false, req.flash('email', 'Email doesn\'t exist.'));            if (!user.validPassword(password))                return done(null, false, req.flash('password', 'Oops! Wrong password.'));            return done(null, user);        });    })); module.exports = passport; 

Außerdem müssen wir das Schema für das Benutzermodell beschreiben (es heißt UserSchema ). Erstellen Sie den database im UserSchema.js und darin die Datei UserSchema.js .

Hier ist der Code für die Datei 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 verfügt über drei Methoden. Die generateHash Methode dient zum Konvertieren eines Kennworts in Form von Klartext in einen bcrypt-Hash. Wir verwenden diese Methode in der Passstrategie, um von Benutzern eingegebene Passwörter in bcrypt-Hashes umzuwandeln. Die empfangenen Passwort-Hashes werden dann in der Datenbank gespeichert. Die validPassword Methode akzeptiert das vom Benutzer eingegebene Kennwort und überprüft es, indem es seinen Hash mit dem in der Datenbank gespeicherten Hash vergleicht. Die generateStreamKey Methode generateStreamKey eindeutige Zeichenfolgen, die wir als Streaming-Schlüssel (Stream-Schlüssel) für RTMP-Clients an Benutzer übertragen.

Hier ist der server/database/Schema.js :

 let mongoose = require('mongoose'); exports.User = mongoose.model('User', require('./UserSchema')); 

UserSchema wir die Passstrategien definiert, das UserSchema Schema beschrieben und ein darauf basierendes Modell erstellt haben, initialisieren wir den Pass in app.js

Hier ist der Code, um die Datei server/app.js zu ergänzen:

 //       ,     const passport = require('./auth/passport'); app.use(passport.initialize()); app.use(passport.session()); 

Außerdem müssen neue Routen in app.js registriert app.js server/app.js Sie dazu den folgenden Code zu server/app.js :

 //    app.use('/login', require('./routes/login')); app.use('/register', require('./routes/register')); 

Erstellen Sie die Dateien login.js und register.js im Ordner login.js im login.js . In diesen Dateien definieren wir einige der oben genannten Routen und verwenden die Pass-Middleware, um die Registrierung und Benutzerauthentifizierung zu organisieren.

Hier ist der Code für die 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; 

Hier ist der Code für die Datei 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; 

Wir verwenden die ejs Templating Engine. Fügen Sie die Vorlagendateien login.ejs und register.ejs zum Ordner views hinzu, der sich im login.ejs befindet.

Hier ist der Inhalt der 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> 

Folgendes sollte in der Datei server/views/register.ejs enthalten sein:

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

Wir können sagen, dass wir die Arbeit am Authentifizierungssystem abgeschlossen haben. Jetzt beginnen wir mit der Erstellung des nächsten Teils des Projekts und konfigurieren den RTMP-Server.

Konfigurieren Sie den RTMP-Server


RTMP (Real-Time Messaging Protocol) ist ein Protokoll, das für die Hochleistungsübertragung von Video, Audio und verschiedenen Daten zwischen dem Bandlaufwerk und dem Server entwickelt wurde. Twitch, Facebook, YouTube und viele andere Streaming-Sites akzeptieren RTMP-Streams und codieren sie in HTTP-Streams (HLS-Format), bevor diese Streams auf ihre CDNs übertragen werden, um ihre hohe Verfügbarkeit sicherzustellen.

Wir verwenden das Node-Media-Server-Modul - Node.js-Implementierung des RTMP-Medienservers. Dieser Medienserver akzeptiert RTMP-Streams und konvertiert sie mithilfe des Multimedia-Frameworks ffmpeg in HLS / DASH. Damit das Projekt erfolgreich funktioniert, muss ffmpeg auf Ihrem System installiert sein. Wenn Sie unter Linux arbeiten und ffmpeg bereits installiert haben, können Sie den Pfad dazu ermitteln, indem Sie den folgenden Befehl vom Terminal aus ausführen:

 $ which ffmpeg # /usr/bin/ffmpeg 

Für die Arbeit mit dem Node-Media-Server-Paket wird ffmpeg Version 4.x empfohlen. Sie können die installierte Version von ffmpeg folgendermaßen überprüfen:

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

Wenn Sie ffmpeg nicht installiert haben und Ubuntu ausführen, können Sie dieses Framework installieren, indem Sie den folgenden Befehl ausführen:

 #    PPA-.     PPA,    # ffmpeg  3.x. $ sudo add-apt-repository ppa:jonathonf/ffmpeg-4 $ sudo apt install ffmpeg 

Wenn Sie unter Windows arbeiten, können Sie ffmpeg- Builds für Windows herunterladen.

Fügen Sie dem Projekt die Konfigurationsdatei server/config/default.js :

 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; 

Ersetzen Sie den Wert der Eigenschaft ffmpeg durch den Pfad, in dem ffmpeg auf Ihrem System installiert ist. Wenn Sie unter Windows arbeiten und die Windows-Assembly ffmpeg über den obigen Link heruntergeladen haben, vergessen Sie nicht, dem Dateinamen die Erweiterung .exe hinzuzufügen. Dann sieht das entsprechende Fragment des obigen Codes folgendermaßen aus:

 const config = {        ....        trans: {            ffmpeg: 'D:/ffmpeg/bin/ffmpeg.exe',            ...          }; 

Installieren Sie nun Node-Media-Server, indem Sie den folgenden Befehl ausführen:

 $ npm install node-media-server --save 

Erstellen Sie eine Datei media_server.js im media_server.js .

Hier ist der Code, der in 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; 

Die Verwendung des NodeMediaService Objekts NodeMediaService recht einfach. Es stellt den RTMP-Server bereit und ermöglicht es Ihnen, auf Verbindungen zu warten. Wenn der Streaming-Schlüssel ungültig ist, kann die eingehende Verbindung abgelehnt werden. Wir werden das Ereignis dieses prePublish Objekts behandeln. Im nächsten Abschnitt fügen wir dem prePublish des prePublish Ereignis-Listeners zusätzlichen Code prePublish . Sie können eingehende Verbindungen mit ungültigen Streaming-Schlüsseln ablehnen. In der Zwischenzeit akzeptieren wir alle eingehenden Verbindungen zum Standard-RTMP-Port (1935). Wir müssen nur das Objekt app.js in die node_media_server und seine run aufrufen.

Fügen Sie server/app.js den folgenden Code server/app.js :

 //      app.js, //  ,      const node_media_server = require('./media_server'); //   run()   , // ,    - node_media_server.run(); 

Laden Sie OBS (Open Broadcaster Software) herunter und installieren Sie es. Öffnen Sie das Anwendungseinstellungsfenster und gehen Sie zum Abschnitt Stream . Wählen Sie im Feld Service die rtmp://127.0.0.1:1935/live und geben rtmp://127.0.0.1:1935/live im Feld Server rtmp://127.0.0.1:1935/live . Das Feld Stream Key kann leer gelassen werden. Wenn Sie mit dem Programm die Einstellungen nicht speichern können, ohne dieses Feld auszufüllen, können Sie einen beliebigen Zeichensatz eingeben. Klicken Sie auf die Schaltfläche Apply und dann auf die Schaltfläche OK . Klicken Sie auf die Schaltfläche Start Streaming starten, um die Übertragung Ihres RTMP-Streams auf Ihren eigenen lokalen Server zu starten.


Konfigurieren Sie OBS

Gehen Sie zum Terminal und sehen Sie sich an, was der Medienserver dort anzeigt. Dort sehen Sie Informationen über den eingehenden Stream und die Protokolle mehrerer Ereignis-Listener.


Daten, die von einem Medienserver basierend auf Node.js an das Terminal ausgegeben werden

Der Medienserver bietet Zugriff auf die API, mit der Sie eine Liste der verbundenen Clients abrufen können. Um diese Liste anzuzeigen, können Sie den Browser unter http://127.0.0.1:8888/api/streams . Später werden wir diese API in einer React-Anwendung verwenden, um eine Liste der Broadcast-Benutzer anzuzeigen. Folgendes können Sie sehen, wenn Sie auf diese API zugreifen:

 {  "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"       } 

Jetzt ist das Backend fast fertig. Es ist ein funktionierender Streaming-Server, der HTTP-, RTMP- und HLS-Technologien unterstützt. Wir haben jedoch noch kein System zur Überprüfung eingehender RTMP-Verbindungen erstellt. Damit sollten wir sicherstellen können, dass der Server nur Streams von authentifizierten Benutzern akzeptiert. Fügen Sie dem prePublish Ereignishandler in der server/media_server.js den folgenden Code server/media_server.js :

 //       const User = require('./database/Schema').User; 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 {                // -                       }); }); const getStreamKeyFromStreamPath = (path) => {    let parts = path.split('/');    return parts[parts.length - 1]; }; 

Zum Abschluss fragen wir die Datenbank ab, um den Benutzer mit dem Streaming-Schlüssel zu finden. Wenn der Schlüssel dem Benutzer gehört, erlauben wir dem Benutzer einfach, eine Verbindung zum Server herzustellen und seine Sendung zu veröffentlichen. Andernfalls lehnen wir die eingehende RTMP-Verbindung ab.

Im nächsten Abschnitt erstellen wir eine einfache clientseitige Anwendung, die auf React basiert. Dies ist erforderlich, damit Zuschauer Streaming-Sendungen ansehen und Streamer ihre Streaming-Schlüssel generieren und anzeigen können.

Live-Streaming


Gehen Sie nun zum clients Ordner. Da wir eine React-Anwendung erstellen, benötigen wir ein Webpack. Wir benötigen auch Loader, mit denen JSX-Code in JavaScript-Code übersetzt wird, den Browser verstehen. Installieren Sie die folgenden Module:

 $ 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 

Fügen Sie dem Projekt in seinem Stammverzeichnis die Konfigurationsdatei für das Webpack ( webpack.config.js ) hinzu:

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

Fügen Sie die Datei client/index.js zum Projekt hinzu:

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

Hier ist der Inhalt der 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; } 

Der React Router wird für das Routing verwendet. Im Frontend verwenden wir auch Bootstrap und für Broadcasts video.js. Root.js nun den components zum Root.js und die Datei Root.js . Hier ist der Inhalt der 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>          } 

Die <Root/> -Komponente rendert eine <Router/> React-Komponente, die drei <Route/> -Unterkomponenten enthält. Die Komponente <LiveStreams/> zeigt eine Liste der Sendungen an. Die Komponente <VideoPlayer/> ist für die Anzeige des Players video.js verantwortlich. Die Komponente <Settings/> ist für die Erstellung einer Schnittstelle für die Arbeit mit Streaming-Schlüsseln verantwortlich.

Erstellen Sie die 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>       } 

So sieht die Anwendungsseite aus.


Frontend-Streaming-Service

Nach dem Mounten der <LiveStreams/> -Komponente wird die NMS-API aufgerufen, um eine Liste der mit dem System verbundenen Clients abzurufen. Die NMS-API bietet nicht viele Informationen zu Benutzern. Insbesondere können wir Informationen über Streaming-Schlüssel erhalten, über die Benutzer mit einem RTMP-Server verbunden sind. Wir werden diese Schlüssel verwenden, wenn wir Abfragen an die Datenbank generieren, um Informationen über Benutzerkonten zu erhalten.

In der Methode getStreamsInfo wir eine XHR-Anforderung an /streams/info getStreamsInfo /streams/info , haben jedoch noch nichts erstellt, das auf diese Anforderung antworten kann. Erstellen Sie eine server/routes/streams.js mit den folgenden Inhalten:

 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; 

Wir übergeben die von der NMS-API zurückgegebenen Flussinformationen an das Backend, um Informationen über verbundene Clients zu erhalten.

Wir führen eine Datenbankabfrage durch, um eine Liste der Benutzer zu erhalten, deren Streaming-Schlüssel mit denen übereinstimmen, die wir von der NMS-API erhalten haben. Wir geben die Liste im JSON-Format zurück. Wir registrieren die Route in der server/app.js :

 app.use('/streams', require('./routes/streams')); 

Als Ergebnis zeigen wir eine Liste der aktiven Sendungen an. Diese Liste enthält den Benutzernamen und die Miniaturansicht. Wir werden am Ende des Artikels darüber sprechen, wie Miniaturansichten für Sendungen erstellt werden. Miniaturansichten sind an bestimmte Seiten gebunden, auf denen HLS-Streams mit video.js abgespielt werden.

Erstellen Sie die 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 :

 //      app.js, const thumbnail_generator = require('./cron/thumbnails'); //   start()    thumbnail_generator.start(); 

Zusammenfassung


, . , . - — . - — .

.

! , , ?

Source: https://habr.com/ru/post/de457860/


All Articles