Erstellen einer Live-Streaming-App mit Node.js und React

Blog

Erstellen einer Live-Streaming-App mit Node.js und React

In diesem Tutorial zeige ich, wie Sie mit Node.js und React Ihre eigene Video-Streaming-App erstellen können

Ich habe an einer App gearbeitet, mit der Sie Ihren Desktop live streamen können. Es nimmt einen RTMP-Stream vom Sender auf und wandelt ihn in einen HLS-Stream um, den die Zuschauer in ihren Webbrowsern ansehen können. In diesem Tutorial zeige ich, wie Sie mit Nodejs Ihre eigene Video-Streaming-App erstellen können. Wenn Sie nicht weiter gehen möchten und nur den Code erkunden möchten, finden Sie ihn hier Repository . Ich werde dieses Tutorial in fünf Teile unterteilen.

  • Webserver mit Standardauthentifizierung
  • Einrichten eines RTMP-Servers in Nodejs
  • Livestreams anzeigen
  • Ausgabe von Streaming-Schlüsseln an Sender
  • Erstellen von Livestream-Thumbnails

Webserver mit Basisauthentifizierung

Lassen Sie uns einen einfachen Knotenserver mit lokaler Passstrategie-Authentifizierung einrichten. Wir werden MongoDB mit Mongoose ODM für den persistenten Speicher verwenden. Initialisieren Sie ein neues Projekt, indem Sie es ausführen

$ npm init

und installieren Sie diese 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 in Ihrem Projektverzeichnis zwei Ordner Client und Server. Wir platzieren unsere Reaktionskomponenten im Client-Verzeichnis und den Backend-Code im Server-Verzeichnis. Für diesen Teil arbeiten wir im Serververzeichnis. Wir verwenden Passport.js zur Authentifizierung. Wir haben bereits Pass- und Pass-Lokal-Module installiert. Bevor wir unsere lokale Strategie zur Authentifizierung von Benutzern definieren, erstellen wir eine app.js-Datei und fügen den erforderlichen Code zum Ausführen eines einfachen Webservers hinzu. Stellen Sie sicher, dass MongoDB auf Ihrem System installiert ist und als Dienst ausgeführt wird.

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 alle notwendigen Middlewares für unsere Anwendung gebootet, mit MongoDB verbunden und die Express-Sitzung so konfiguriert, dass sie den Dateispeicher für die Sitzungspersistenz im Falle eines Webserver-Neustarts verwendet. Jetzt werden wir unsere Passport-Strategien für die Registrierung und Authentifizierung von Benutzern definieren. Erstellen Sie einen Ordner namens auth mit einer Datei Passport.js und fügen Sie den folgenden Code hinzu.

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 strategy for handling user registration 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 strategy for authenticating users 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;

Wir müssen auch das Schema für unser Benutzermodell definieren. Erstellen Sie ein Datenbankverzeichnis mit der Datei UserSchema.js und fügen Sie den folgenden Code hinzu.

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;

Wir haben drei Methoden in unserem Benutzerschema. Die Methode 'generateHash' konvertiert ein Klartext-Passwort in einen bcrypt-Hash. Wir verwenden es in unserer Passport-Strategie, um einfache Passwort-Strings in bcrypt-Hash zu konvertieren, bevor sie in der Datenbank gespeichert werden. Die validPassword-Methode nimmt ein Klartext-Passwort auf und validiert es, indem es mit dem in unserer Datenbank gespeicherten bcrypt-Hash verglichen wird. Die Methode 'generateStreamKey' generiert eine eindeutige Zeichenfolge, die wir den Benutzern als Streaming-Schlüssel für RTMP-Clients ausgeben.

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

Nachdem wir nun unsere Passport-Strategien definiert, ein Benutzerschema hinzugefügt und daraus ein Modell erstellt haben, initialisieren wir Passport in app.js.

// Add on the top next to imports const passport = require('./auth/passport'); app.use(passport.initialize()); app.use(passport.session());

Registrieren Sie diese Routen auch in der Datei app.js.

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

Erstellen Sie im Routenverzeichnis eine login.js- und eine register.js-Datei, in der wir diese Routen definieren und die Passport-Middleware für die Registrierung und Authentifizierung verwenden.

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; 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 Vorlage login.ejs und register.ejs zum Ansichtenverzeichnis hinzu und fügen Sie den folgenden Code hinzu.

Login


Email address Password Don't have an account? Register here . Login

Register


Username Email address Password Have an account? Login here . Register

Wir sind ziemlich fertig mit der Authentifizierung. Jetzt werden wir zum nächsten Teil dieses Tutorials übergehen und unseren RTMP-Server einrichten.

Brückenmuster c++

Einrichten eines RTMP-Servers

Real-Time Messaging Protocol (RTMP) wurde für die Hochleistungsübertragung von Video, Audio und Daten zwischen Sender und Server entwickelt. Twitch, Facebook, Youtube und viele andere Websites, die Live-Streaming anbieten, akzeptieren RTMP-Streams und wandeln sie dann in HTTP-Streams (HLS-Format) um, bevor sie für hohe Verfügbarkeit an ihre CDNs verteilt werden.

Wir verwenden node-media-server, eine Node.js-Implementierung des RTMP-Medienservers. Es akzeptiert RTMP-Streams und remuxt sie mit ffmpeg in HLS/DASH. Stellen Sie sicher, dass ffmpeg auf Ihrem System installiert ist. Wenn Sie Linux ausführen und ffmpeg bereits installiert haben, können Sie Ihren Installationspfad finden, indem Sie diesen Befehl vom Terminal aus ausführen.

$ which ffmpeg # /usr/bin/ffmpeg

|_+_| empfiehlt ffmpeg 4.x-Version. Sie können Ihre Version überprüfen, indem Sie diesen Befehl ausführen.

node-media-server

Wenn Sie ffmpeg nicht installiert haben und Ubuntu ausführen, können Sie es installieren, indem Sie diese Befehle über das Terminal ausführen.

$ 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 Windows ausführen, können Sie ffmpeg windows herunterladen baut . Fügen Sie diese Konfigurationsdatei zu Ihrem Projekt hinzu.

# Add PPA. If you install without PPA, it will install # ffmpeg version 3.x. $ sudo add-apt-repository ppa:jonathonf/ffmpeg-4 $ sudo apt install ffmpeg

Ändern Sie den ffmpeg-Wert in Ihren eigenen ffmpeg-Installationspfad. Wenn Sie Windows ausführen und Windows-Builds über den obigen Link heruntergeladen haben, stellen Sie sicher, dass Sie die Erweiterung .exe am Ende Ihres Pfads hinzufügen.

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;

Installieren Sie außerdem |_+_| durch Laufen

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

Medien erstellen_|_+_| und fügen Sie diesen Code hinzu.

node-media-server

Die Verwendung von NodeMediaServer ist ziemlich einfach. Es führt einen RTMP-Server aus und lässt Sie Verbindungsereignisse abhören. Sie können eine eingehende Verbindung ablehnen, wenn ein Streaming-Schlüssel ungültig ist. Wir werden der PrePublish-Veranstaltung zuhören. Im nächsten Teil dieses Tutorials werden wir mehr Code in die Schließung des prePublish-Ereignis-Listeners einfügen, um eingehende Verbindungen mit ungültigen Streaming-Schlüsseln abzulehnen. Im Moment akzeptieren wir alle eingehenden Verbindungen auf dem Standard-RTMP-Port 1935. Jetzt müssen wir nur noch das nms-Objekt in die Datei app.js importieren und seine run-Methode aufrufen.

$ npm install node-media-server --save

Open Broadcaster-Software herunterladen ( OBS ) und installieren Sie es auf Ihrem PC. Gehen Sie zu Einstellungen > Streamen. Wählen Sie Benutzerdefinierter Dienst und geben Sie rtmp://127.0.0.1:1935/live in die Servereingabe ein. Sie können die Stream-Key-Eingabe leer lassen oder eine zufällige Zeichenfolge hinzufügen, wenn Sie die Einstellungen nicht speichern können. Klicken Sie auf Übernehmen und OK. Klicken Sie auf die Schaltfläche Streaming starten, um Ihren RTMP-Stream an Ihren lokalen Server zu senden.

Gehen Sie zu Ihrem Terminal, um die Ausgabe Ihres Knotenmedienservers anzuzeigen. Sie sehen einen eingehenden Stream mit einigen Ereignis-Listener-Protokollen.

Der Knotenmedienserver stellt eine API bereit, um alle verbundenen Clients aufzulisten. Sie können es in Ihrem Browser unter aufrufen http://127.0.0.1:8888/api/streams . Später werden wir diese API in unserer Frontend-React-App verwenden, um Live-Streaming-Benutzer anzuzeigen. Sie sehen eine Ausgabe wie diese.

server.js file

Unser Backend ist so gut wie fertig. Es ist ein funktionierender HTTP/RTMP/HLS-Streaming-Server. Wir müssen jedoch noch eingehende RTMP-Verbindungen validieren, um sicherzustellen, dass nur die Streams authentifizierter Benutzer akzeptiert werden. Fügen Sie diesen Code zu Ihrem prePublish-Ereignis-Listener-Closing hinzu.

reagieren-Teilchen-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;

Innerhalb der Schließung fragen wir die Datenbank ab, um einen Benutzer mit dem Streaming-Schlüssel zu finden. Wenn es einem Benutzer gehört, würden wir ihn einfach verbinden und seinen Stream veröffentlichen lassen. Andernfalls lehnen wir die eingehende RTMP-Verbindung ab.

Im nächsten Teil dieses Tutorials werden wir ein grundlegendes React-Frontend erstellen, mit dem Benutzer Live-Streams anzeigen, ihre Streaming-Schlüssel generieren und anzeigen können.

Live-Streams anzeigen

Für diesen Teil arbeiten wir im Client-Verzeichnis. Da es sich um eine React-App handelt, werden wir Webpack und die notwendigen Loader verwenden, um JSX in browserfähiges JavaScript zu transpilieren. Installieren Sie diese Module.

// Add this on the top of app.js file // next to all imports const node_media_server = require('./media_server'); // and call run() method at the end // file where we start our web server node_media_server.run();

Fügen Sie diese Webpack-Konfiguration zu Ihrem Projekt hinzu.

{ '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' } ] } } }

Wir fragen die Datenbank ab, um alle Benutzer mit übereinstimmenden Streaming-Schlüsseln auszuwählen, die wir von der NMS-API abgerufen haben, und geben sie als JSON-Antwort zurück. Registrieren Sie diese Route in der Datei app.js.

// Add import at the start of file 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 { // do stuff } } }); }); const getStreamKeyFromStreamPath = (path) => { let parts = path.split('/'); return parts[parts.length - 1]; };

Am Ende rendern wir Livestreams mit Benutzernamen und Thumbnails. Im letzten Teil dieses Tutorials werden wir Thumbnails für unsere Streams generieren. Diese Thumbnails sind mit den einzelnen Seiten verlinkt, auf denen HLS-Streams in einer video.js-Playerkomponente wiedergegeben werden. Erstellen Sie die VideoPlayer.js-Komponente.

$ 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

Beim Bereitstellen von Komponenten rufen wir den Streaming-Schlüssel des Benutzers ab, um einen HLS-Stream im video.js-Player zu initiieren.

wie man dateien auf github hochlädt

Ausgabe von Streaming-Schlüsseln an Sender

Erstellen Sie die Settings.js-Komponente.

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' }, .woff2($, { test: /..png'>After our component mounts, we are making a call to NMS API to retrieve all the connected clients. NMS API does not have much information about the user other than their streaming key through which they are connected to our RTMP server. We will use the streaming key to query our database to get users records. In getStreamsInfo method, we are making an XHR request to /streams/info which we have not yet defined. Create a server/routes/streams.js file and add the following code to it. We will pass on the streams returned from the NMS API to our backend to retrieve information about connected clients.

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; 

Im Rahmen der lokalen Strategie unseres Passports erstellen wir bei erfolgreicher Registrierung eines Benutzers einen neuen Benutzerdatensatz mit einem eindeutigen Streaming-Schlüssel. Wenn ein Benutzer die Route /settings besucht, kann er seinen vorhandenen Schlüssel anzeigen. Wenn Komponenten bereitgestellt werden, führen wir einen XHR-Aufruf an das Back-End aus, um den vorhandenen Streaming-Schlüssel des Benutzers abzurufen und in unserer Komponente zu rendern.

Benutzer können einen neuen Schlüssel generieren, indem sie auf die Schaltfläche Neuen Schlüssel generieren klicken, die einen XHR-Aufruf an das Backend ausführt, um einen neuen Schlüssel zu erstellen, ihn in der Benutzersammlung zu speichern und ihn auch zurückzugeben, damit er in der Komponente gerendert werden kann. Wir müssen sowohl GET- als auch POST- /settings/stream_key-Routen definieren. Erstellen Sie eine Datei server/routes/settings.js und fügen Sie den folgenden Code hinzu.

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

Wir verwenden das Shortid-Modul zum Generieren eindeutiger Zeichenfolgen. Registrieren Sie diese Routen in der Datei app.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 ( {this.state.stream ? ( this.videoNode = node} className='video-js vjs-big-play-centered'/> ) : ' Loading ... '} ) } }

Erstellen von Livestream-Miniaturansichten

In Komponenten zeigen wir Miniaturbilder für Livestreams an.

render() { let streams = this.state.live_streams.map((stream, index) => { return (LIVE  Repository . Wenn Sie auf ein Problem stoßen, melden Sie es bitte, indem Sie ein neues Problem im Repository erstellen, damit es behoben werden kann. Anweisungen zur Einrichtung und Verwendung finden Sie im Repository Liesmich Datei.

#node-js #reactjs