El autor del material, cuya traducción publicamos hoy, dice que está trabajando en una aplicación que le permite organizar la transmisión (transmisión) de lo que está sucediendo en el escritorio del usuario.

La aplicación recibe una transmisión en formato RTMP del transmisor y la convierte en una transmisión HLS, que se puede reproducir en los navegadores de los espectadores. Este artículo hablará sobre cómo puede crear su propia aplicación de transmisión usando Node.js y React. Si está acostumbrado a ver la idea que le interesa, sumérjase inmediatamente en el código, ahora puede buscar en
este repositorio.
Desarrollo de servidor web con sistema básico de autenticación.
Creemos un servidor web simple basado en Node.js, en el que, utilizando la biblioteca passport.js, se implemente una estrategia de autenticación de usuario local. Utilizaremos MongoDB como almacenamiento permanente de información. Trabajaremos con la base de datos utilizando la biblioteca Mongoose ODM.
Inicializar un nuevo proyecto:
$ npm init
Instalar las dependencias:
$ 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
En el directorio del proyecto, cree dos carpetas:
client
y
server
. El código de interfaz basado en React terminará en la carpeta del
client
, y el código de back-end se almacenará en la carpeta del
server
. Ahora estamos trabajando en la carpeta del
server
. A saber, usaremos passport.js para crear un sistema de autenticación. Ya hemos instalado el pasaporte y los módulos de pasaporte local. Antes de describir la estrategia de autenticación de usuario local, cree un archivo
app.js
y agregue el código necesario para iniciar un servidor simple. Si va a ejecutar este código por su cuenta, asegúrese de tener instalado el DBMS MongoDB y de que se ejecute como un servicio.
Aquí está el código para el archivo que está en el proyecto en
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}!`));
Descargamos todo el middleware necesario para la aplicación, conectado a MongoDB, configuramos la sesión rápida para usar el almacenamiento de archivos. Almacenar sesiones les permitirá restaurarlas después de reiniciar el servidor.
Ahora describimos las estrategias de passport.js para organizar el registro y la autenticación de usuarios. Cree una carpeta de
auth
en la carpeta del
server
y coloque el archivo
passport.js
en ella. Esto es lo que debe estar en el archivo
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); });
Además, necesitamos describir el esquema para el modelo de usuario (se llamará
UserSchema
). Cree la carpeta de la
database
en la carpeta del
server
, y en ella el archivo
UserSchema.js
.
Aquí está el código para el archivo
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
tiene tres métodos. El método
generateHash
está diseñado para convertir una contraseña, presentada en forma de texto sin formato, en un hash bcrypt. Utilizamos este método en la estrategia de pasaporte para convertir las contraseñas ingresadas por los usuarios en hash de bcrypt. Los hashes de contraseña recibidos se almacenan en la base de datos. El método
validPassword
acepta la contraseña ingresada por el usuario y la verifica comparando su hash con el hash almacenado en la base de datos. El método
generateStreamKey
cadenas únicas que transferiremos a los usuarios como sus claves de transmisión (claves de transmisión) para clientes RTMP.
Aquí está el código del archivo
server/database/Schema.js
:
let mongoose = require('mongoose'); exports.User = mongoose.model('User', require('./UserSchema'));
Ahora que hemos definido estrategias de pasaporte, descrito el esquema
UserSchema
y creado un modelo basado en él, inicialicemos el pasaporte en
app.js
Aquí está el código para complementar el archivo
server/app.js
:
Además, las nuevas rutas deben registrarse en
app.js
Para hacer esto, agregue el siguiente código a
server/app.js
:
Cree los archivos
login.js
y
register.js
en la carpeta de
routes
ubicada en la carpeta del
server
. En estos archivos definimos un par de las rutas anteriores y usamos el middleware del pasaporte para organizar el registro y la autenticación del usuario.
Aquí está el código para el
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;
Aquí está el código para el archivo
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;
Usamos el motor de plantillas ejs. Agregue los archivos de plantilla
login.ejs
y
register.ejs
a la carpeta de
views
, que se encuentra en la carpeta del
server
.
Aquí está el contenido del
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>
Esto es lo que debe estar en el archivo
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>
Podemos decir que hemos terminado el trabajo en el sistema de autenticación. Ahora comencemos la creación de la siguiente parte del proyecto y configuremos el servidor RTMP.
Configurar el servidor RTMP
RTMP (Protocolo de mensajería en tiempo real) es un protocolo que se desarrolló para la transmisión de video, audio y varios datos de alto rendimiento entre la unidad de cinta y el servidor. Twitch, Facebook, YouTube y muchos otros sitios de transmisión aceptan transmisiones RTMP y las transcodifican en transmisiones HTTP (formato HLS) antes de transferir estas transmisiones a sus CDN para garantizar su alta disponibilidad.
Usamos el módulo de servidor de medios de nodo - Node.js-implementación del servidor de medios RTMP. Este servidor de medios acepta transmisiones RTMP y las convierte a HLS / DASH utilizando el marco multimedia ffmpeg. Para que el proyecto funcione con éxito, ffmpeg debe estar instalado en su sistema. Si está trabajando en Linux y ya tiene instalado ffmpeg, puede encontrar la ruta a él ejecutando el siguiente comando desde el terminal:
$ which ffmpeg # /usr/bin/ffmpeg
Para trabajar con el paquete node-media-server, se recomienda ffmpeg versión 4.x. Puede verificar la versión instalada de ffmpeg de esta manera:
$ 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 no tiene instalado ffmpeg y está ejecutando Ubuntu, puede instalar este marco ejecutando el siguiente comando:
# PPA-. PPA, # ffmpeg 3.x. $ sudo add-apt-repository ppa:jonathonf/ffmpeg-4 $ sudo apt install ffmpeg
Si trabaja en Windows, puede descargar
compilaciones de ffmpeg para Windows.
Agregue el archivo de configuración
server/config/default.js
al proyecto:
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;
Reemplace el valor de la propiedad
ffmpeg
con la ruta donde está instalado ffmpeg en su sistema. Si está trabajando en Windows y descargó el ensamblado de Windows ffmpeg desde el enlace de arriba, no olvide agregar la extensión
.exe
al nombre del archivo. Entonces el fragmento correspondiente del código anterior se verá así:
const config = { .... trans: { ffmpeg: 'D:/ffmpeg/bin/ffmpeg.exe', ... };
Ahora instale node-media-server ejecutando el siguiente comando:
$ npm install node-media-server --save
Cree un archivo
media_server.js
en la carpeta del
server
.
Aquí está el código para poner en
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;
Usar el objeto
NodeMediaService
bastante simple. Proporciona el servidor RTMP y le permite esperar las conexiones. Si la clave de transmisión no es válida, la conexión entrante se puede rechazar.
prePublish
el evento de este objeto
prePublish
. En la siguiente sección, agregamos código adicional al cierre del detector de eventos
prePublish
. Le permitirá rechazar conexiones entrantes con claves de transmisión no válidas. Mientras tanto, aceptaremos todas las conexiones entrantes que lleguen al puerto RTMP predeterminado (1935). Solo necesitamos importar el objeto
app.js
en el
node_media_server
y llamar a su método de
run
.
Agregue el siguiente código a
server/app.js
:
Descargue e instale
OBS (Open Broadcaster Software). Abra la ventana de configuración de la aplicación y vaya a la sección
Stream
. Seleccione
Custom
en el campo
Service
e ingrese
rtmp://127.0.0.1:1935/live
en el campo
Server
. El campo
Stream Key
transmisión se puede dejar en blanco. Si el programa no le permite guardar la configuración sin completar este campo, puede ingresar cualquier conjunto de caracteres en él. Haga clic en el botón
Apply
y en el botón
OK
. Haga clic en el botón
Start Streaming
para comenzar a transferir su transmisión RTMP a su propio servidor local.
Configurar OBSVaya a la terminal y mire lo que muestra el servidor de medios allí. Verá allí información sobre la transmisión entrante y los registros de varios oyentes de eventos.
Salida de datos al terminal por un servidor de medios basado en Node.jsEl servidor de medios le da acceso a la API, que le permite obtener una lista de clientes conectados. Para ver esta lista, puede ir al navegador en
http://127.0.0.1:8888/api/streams
. Más tarde, utilizaremos esta API en una aplicación React para mostrar una lista de usuarios de difusión. Esto es lo que puede ver accediendo a esta 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" }
Ahora el backend está casi listo. Es un servidor de transmisión en funcionamiento que admite las tecnologías HTTP, RTMP y HLS. Sin embargo, todavía no hemos creado un sistema para verificar las conexiones RTMP entrantes. Debe permitirnos asegurarnos de que el servidor acepte transmisiones solo de usuarios autenticados. Agregue el siguiente código al
prePublish
eventos
prePublish
en el
server/media_server.js
:
En el cierre, consultamos la base de datos para encontrar al usuario con la clave de transmisión. Si la clave pertenece al usuario, simplemente permitimos que el usuario se conecte al servidor y publique su transmisión. De lo contrario, rechazamos la conexión RTMP entrante.
En la siguiente sección, crearemos una aplicación simple del lado del cliente basada en React. Es necesario para permitir a los espectadores ver transmisiones de transmisión, así como para permitir que los transmisores generen y vean sus claves de transmisión.
Transmisión en vivo
Ahora ve a la carpeta de
clients
. Como vamos a crear una aplicación React, necesitaremos un paquete web. También necesitamos cargadores, que se utilizan para traducir el código JSX en código JavaScript que los navegadores entienden. Instale los siguientes módulos:
$ 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
Agregue al proyecto, en su directorio raíz, el archivo de configuración para 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 }, };
Agregue el archivo
client/index.js
al proyecto:
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') ); }
Aquí está el contenido del
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; }
React router se utiliza para enrutar. En la interfaz, también usamos bootstrap y, para transmisiones, video.js. Ahora agregue la carpeta de
components
a la carpeta del
client
y el archivo
Root.js
Aquí está el contenido del
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> }
El componente
<Root/>
representa un componente React
<Router/>
que contiene tres subcomponentes
<Route/>
. El componente
<LiveStreams/>
muestra una lista de transmisiones. El
<VideoPlayer/>
es responsable de mostrar el reproductor video.js. El componente
<Settings/>
es responsable de crear una interfaz para trabajar con claves de transmisión.
Cree el
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> }
Así es como se ve la página de la aplicación.
Servicio de transmisión frontendDespués de montar el componente
<LiveStreams/>
, se llama a la API de NMS para obtener una lista de clientes conectados al sistema. La API de NMS no proporciona mucha información sobre los usuarios. En particular, podemos obtener información sobre la transmisión de claves a través de ella, a través de la cual los usuarios se conectan a un servidor RTMP. Utilizaremos estas claves cuando generemos consultas a la base de datos para obtener información sobre las cuentas de los usuarios.
En el método
getStreamsInfo
ejecutamos una solicitud XHR a
/streams/info
, pero aún no hemos creado algo que pueda responder a esta solicitud. Cree un
server/routes/streams.js
con los siguientes contenidos:
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;
Pasamos la información de flujo devuelta por la API de NMS al back-end para obtener información sobre los clientes conectados.
Realizamos una consulta de base de datos para obtener una lista de usuarios cuyas claves de transmisión coinciden con las que recibimos de la API de NMS. Devolvemos la lista en formato JSON. Registramos la ruta en el
server/app.js
:
app.use('/streams', require('./routes/streams'));
Como resultado, mostramos una lista de transmisiones activas. Esta lista contiene el nombre de usuario y la miniatura. Hablaremos sobre cómo crear miniaturas para las transmisiones al final del artículo. Las miniaturas están vinculadas a páginas específicas donde se reproducen transmisiones HLS usando video.js.
Cree el
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
:
Resumen
, . , . - —
. - — .
.
! , , ?