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.

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); });
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:
Außerdem müssen neue Routen in
app.js
registriert
app.js
server/app.js
Sie dazu den folgenden Code zu
server/app.js
:
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
:
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 OBSGehen 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 werdenDer 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
:
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-ServiceNach 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
:
Zusammenfassung
, . , . - —
. - — .
.
! , , ?