O autor do material, cuja tradução publicamos hoje, diz que está trabalhando em um aplicativo que permite organizar a transmissão em fluxo contínuo do que está acontecendo na área de trabalho do usuário.

O aplicativo recebe um fluxo no formato RTMP da serpentina e o converte em um fluxo HLS, que pode ser reproduzido nos navegadores dos espectadores. Este artigo abordará como você pode criar seu próprio aplicativo de streaming usando o Node.js. e o React. Se você está acostumado a ver a ideia que lhe interessa, mergulhe imediatamente no código, agora poderá procurar
neste repositório.
Desenvolvimento de servidor Web com sistema básico de autenticação
Vamos criar um servidor Web simples baseado no Node.js, no qual, usando a biblioteca passport.js, uma estratégia de autenticação de usuário local é implementada. Usaremos o MongoDB como um armazenamento permanente de informações. Trabalharemos com o banco de dados usando a biblioteca Mongoose ODM.
Inicialize um novo projeto:
$ npm init
Instale as dependências:
$ 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
No diretório do projeto, crie duas pastas -
client
e
server
. O código de front-end baseado em React será finalizado na pasta do
client
e o código de back-end será armazenado na pasta do
server
. Agora estamos trabalhando na pasta do
server
. Ou seja, usaremos o passport.js para criar um sistema de autenticação. Já instalamos os módulos passaporte e passaporte-local. Antes de descrever a estratégia de autenticação do usuário local, crie um arquivo
app.js
e adicione o código necessário para iniciar um servidor simples. Se você executar esse código por conta própria, verifique se possui o DBMS do MongoDB instalado e se ele é executado como um serviço.
Aqui está o código para o arquivo que está no projeto em
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}!`));
Fizemos o download de todo o middleware necessário para o aplicativo, conectado ao MongoDB, configuramos a sessão expressa para usar o armazenamento de arquivos. As sessões de armazenamento permitirão que elas sejam restauradas após a reinicialização do servidor.
Agora, descrevemos as estratégias passport.js para organizar o registro e a autenticação do usuário. Crie uma pasta de
auth
na pasta do
server
e coloque o arquivo
passport.js
nela. Aqui está o que deve estar no arquivo
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); });
Além disso, precisamos descrever o esquema para o modelo do usuário (será chamado de
UserSchema
). Crie a pasta do
database
na pasta do
server
e nela o arquivo
UserSchema.js
.
Aqui está o código para o arquivo
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
tem três métodos. O método
generateHash
foi projetado para converter uma senha, apresentada na forma de texto sem formatação, em um hash bcrypt. Usamos esse método na estratégia de passaporte para converter senhas inseridas pelos usuários em hashes bcrypt. Os hashes de senha recebidos são armazenados no banco de dados. O método
validPassword
aceita a senha inserida pelo usuário e a verifica comparando seu hash com o hash armazenado no banco de dados. O método
generateStreamKey
sequências únicas que serão transferidas para os usuários como suas chaves de fluxo contínuo (chaves de fluxo) para clientes RTMP.
Aqui está o código do arquivo
server/database/Schema.js
:
let mongoose = require('mongoose'); exports.User = mongoose.model('User', require('./UserSchema'));
Agora que definimos estratégias de passaporte, descrevemos o esquema
UserSchema
e criamos um modelo baseado nele, vamos inicializar o passaporte em
app.js
Aqui está o código para complementar o arquivo
server/app.js
:
Além disso, novas rotas devem ser registradas em
app.js
Para fazer isso, adicione o seguinte código ao
server/app.js
:
Crie os arquivos
login.js
e
register.js
na pasta
routes
localizada na pasta do
server
. Nesses arquivos, definimos algumas das rotas acima e usamos o middleware do passaporte para organizar o registro e a autenticação do usuário.
Aqui está o código para o
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;
Aqui está o código para o arquivo
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 o mecanismo de modelo ejs. Inclua os arquivos de modelo
login.ejs
e
register.ejs
na pasta
views
, localizada na pasta do
server
.
Aqui está o conteúdo do
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>
Aqui está o que deve estar no arquivo
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 dizer que terminamos o trabalho no sistema de autenticação. Agora iniciaremos a criação da próxima parte do projeto e configuraremos o servidor RTMP.
Configurar servidor RTMP
O RTMP (Real-Time Messaging Protocol) é um protocolo desenvolvido para transmissão de alto desempenho de vídeo, áudio e vários dados entre a unidade de fita e o servidor. O Twitch, o Facebook, o YouTube e muitos outros sites de streaming aceitam fluxos RTMP e os codificam para fluxos HTTP (formato HLS) antes de transferir esses fluxos para suas CDNs para garantir sua alta disponibilidade.
Usamos o módulo node-media-server - Node.js-deployment do servidor de mídia RTMP. Esse servidor de mídia aceita fluxos RTMP e os converte em HLS / DASH usando a estrutura multimídia ffmpeg. Para que o projeto funcione com sucesso, o ffmpeg deve estar instalado no seu sistema. Se você estiver trabalhando no Linux e já tiver o ffmpeg instalado, poderá descobrir o caminho para ele executando o seguinte comando no terminal:
$ which ffmpeg # /usr/bin/ffmpeg
Para trabalhar com o pacote node-media-server, é recomendável o ffmpeg versão 4.x. Você pode verificar a versão instalada do ffmpeg assim:
$ 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)
Se você não possui o ffmpeg instalado e está executando o Ubuntu, pode instalar esta estrutura executando o seguinte comando:
# PPA-. PPA, # ffmpeg 3.x. $ sudo add-apt-repository ppa:jonathonf/ffmpeg-4 $ sudo apt install ffmpeg
Se você trabalha no Windows, pode fazer o download do ffmpeg
builds para Windows.
Inclua o arquivo de configuração
server/config/default.js
no projeto:
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;
Substitua o valor da propriedade
ffmpeg
pelo caminho em que o ffmpeg está instalado no seu sistema. Se você estiver trabalhando no Windows e baixou o assembly ffmpeg do Windows no link acima - não esqueça de adicionar a extensão
.exe
ao nome do arquivo. Em seguida, o fragmento correspondente do código acima ficará assim:
const config = { .... trans: { ffmpeg: 'D:/ffmpeg/bin/ffmpeg.exe', ... };
Agora instale o node-media-server executando o seguinte comando:
$ npm install node-media-server --save
Crie um arquivo
media_server.js
na pasta do
server
.
Aqui está o código para colocar em
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 o objeto
NodeMediaService
bastante simples. Ele fornece o servidor RTMP e permite aguardar conexões. Se a chave de streaming for inválida, a conexão recebida poderá ser rejeitada. Manipularemos o evento desse objeto
prePublish
-
prePublish
. Na próxima seção, adicionamos código adicional ao fechamento do ouvinte de
prePublish
-
prePublish
. Isso permitirá que você rejeite as conexões recebidas com chaves de streaming inválidas. Enquanto isso, aceitaremos todas as conexões de entrada que chegarem à porta RTMP padrão (1935). Precisamos apenas importar o objeto
app.js
no
node_media_server
e chamar seu método
run
.
Adicione o seguinte código ao
server/app.js
:
Baixe e instale o
OBS (Open Broadcaster Software). Abra a janela de configurações do aplicativo e vá para a seção
Stream
. Selecione
Custom
no campo
Service
e digite
rtmp://127.0.0.1:1935/live
no campo
Server
. O campo
Stream Key
pode ser deixado em branco. Se o programa não permitir salvar as configurações sem preencher esse campo, você poderá inserir qualquer conjunto de caracteres. Clique no botão
Apply
e no botão
OK
. Clique no botão
Start Streaming
para começar a transferir o fluxo RTMP para o seu próprio servidor local.
Configurar OBSVá para o terminal e veja o que o servidor de mídia exibe lá. Você verá informações sobre o fluxo recebido e os logs de vários ouvintes de eventos.
Saída de dados para o terminal por um servidor de mídia baseado em Node.jsO servidor de mídia fornece acesso à API, que permite obter uma lista de clientes conectados. Para visualizar esta lista, você pode acessar o navegador em
http://127.0.0.1:8888/api/streams
. Posteriormente, usaremos essa API em um aplicativo React para exibir uma lista de usuários de transmissão. Aqui está o que você pode ver acessando 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" }
Agora, o back-end está quase pronto. É um servidor de fluxo de trabalho compatível com as tecnologias HTTP, RTMP e HLS. No entanto, ainda não criamos um sistema para verificar as conexões RTMP recebidas. Isso deve permitir garantir que o servidor aceite fluxos somente de usuários autenticados. Adicione o seguinte código ao
prePublish
eventos
prePublish
no
server/media_server.js
:
No fechamento, consultamos o banco de dados para encontrar o usuário com a chave de streaming. Se a chave pertencer ao usuário, simplesmente permitiremos que ele se conecte ao servidor e publique sua transmissão. Caso contrário, rejeitamos a conexão RTMP recebida.
Na próxima seção, criaremos um aplicativo simples do lado do cliente baseado no React. É necessário para permitir que os espectadores assistam a transmissões de streaming, bem como para permitir que os streamers gerem e visualizem suas chaves de streaming.
Transmissão ao vivo
Agora vá para a pasta
clients
. Como vamos criar um aplicativo React, precisaremos de um webpack. Também precisamos de carregadores, que são usados para converter o código JSX em código JavaScript que os navegadores entendem. Instale os seguintes 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
Adicione ao projeto, em seu diretório raiz, o arquivo de configuração do 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 }, };
Adicione o arquivo
client/index.js
ao projeto:
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') ); }
Aqui está o conteúdo do
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; }
O roteador de reação é usado para roteamento. No frontend, também usamos o bootstrap e, para transmissões, video.js. Agora adicione a pasta de
components
à pasta do
client
e o arquivo
Root.js
Aqui está o conteúdo do
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> }
O componente
<Root/>
renderiza um componente
<Router/>
React que contém três subcomponentes
<Route/>
. O componente
<LiveStreams/>
exibe uma lista de transmissões. O componente
<VideoPlayer/>
é responsável por exibir o player video.js. O componente
<Settings/>
é responsável por criar uma interface para trabalhar com chaves de streaming.
Crie o
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> }
É assim que a página do aplicativo se parece.
Serviço de streaming de front-endDepois de montar o componente
<LiveStreams/>
, a API do NMS é chamada para obter uma lista de clientes conectados ao sistema. A API do NMS não fornece muitas informações sobre os usuários. Em particular, podemos obter informações sobre o streaming de chaves, através do qual os usuários estão conectados a um servidor RTMP. Usaremos essas chaves ao gerar consultas no banco de dados para obter informações sobre contas de usuário.
No método
getStreamsInfo
executamos uma solicitação XHR para
/streams/info
, mas ainda não criamos algo que possa responder a essa solicitação. Crie um
server/routes/streams.js
com o seguinte conteúdo:
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;
Passamos as informações de fluxo retornadas pela API do NMS para o back-end, a fim de obter informações sobre clientes conectados.
Realizamos uma consulta ao banco de dados para obter uma lista de usuários cujas chaves de streaming correspondem aos que recebemos da API do NMS. Retornamos a lista no formato JSON. Registramos a rota no
server/app.js
:
app.use('/streams', require('./routes/streams'));
Como resultado, exibimos uma lista de transmissões ativas. Esta lista contém o nome de usuário e a miniatura. Falaremos sobre como criar miniaturas para transmissões no final do artigo. As miniaturas são vinculadas a páginas específicas em que os fluxos HLS são reproduzidos usando o video.js.
Crie o
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
:
Sumário
, . , . - —
. - — .
.
! , , ?