Node.js рдФрд░ рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рд╕реНрдЯреНрд░реАрдорд┐рдВрдЧ рдХреЗ рд▓рд┐рдП рдПрдХ рдПрдкреНрд▓рд┐рдХреЗрд╢рди рд╡рд┐рдХрд╕рд┐рдд рдХрд░рдирд╛

рд╕рд╛рдордЧреНрд░реА рдХрд╛ рд▓реЗрдЦрдХ, рдЬрд┐рд╕рдХрд╛ рдЕрдиреБрд╡рд╛рдж рдЖрдЬ рд╣рдо рдкреНрд░рдХрд╛рд╢рд┐рдд рдХрд░рддреЗ рд╣реИрдВ, рдХрд╛ рдХрд╣рдирд╛ рд╣реИ рдХрд┐ рд╡рд╣ рдПрдХ рдРрд╕реЗ рдЕрдиреБрдкреНрд░рдпреЛрдЧ рдкрд░ рдХрд╛рдо рдХрд░ рд░рд╣рд╛ рд╣реИ рдЬреЛ рдЖрдкрдХреЛ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХреЗ рдбреЗрд╕реНрдХрдЯреЙрдк рдкрд░ рд╣реЛ рд░рд╣реА рд╕реНрдЯреНрд░реАрдорд┐рдВрдЧ (рд╕реНрдЯреНрд░реАрдорд┐рдВрдЧ) рдХреЛ рд╡реНрдпрд╡рд╕реНрдерд┐рдд рдХрд░рдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рджреЗрддрд╛ рд╣реИред

рдЫрд╡рд┐

рдПрдкреНрд▓рд┐рдХреЗрд╢рди рд╕реНрдЯреНрд░реАрдорд░ рд╕реЗ RTMP рдкреНрд░рд╛рд░реВрдк рдореЗрдВ рдПрдХ рд╕реНрдЯреНрд░реАрдо рдкреНрд░рд╛рдкреНрдд рдХрд░рддрд╛ рд╣реИ рдФрд░ рдЗрд╕реЗ HLS рд╕реНрдЯреНрд░реАрдо рдореЗрдВ рдкрд░рд┐рд╡рд░реНрддрд┐рдд рдХрд░рддрд╛ рд╣реИ, рдЬрд┐рд╕реЗ рджрд░реНрд╢рдХреЛрдВ рдХреЗ рдмреНрд░рд╛рдЙрдЬрд╝рд░ рдореЗрдВ рдЪрд▓рд╛рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИред рдпрд╣ рдЖрд▓реЗрдЦ рдЗрд╕ рдмрд╛рд░реЗ рдореЗрдВ рдмрд╛рдд рдХрд░реЗрдЧрд╛ рдХрд┐ рдЖрдк Node.js рдФрд░ React рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдЕрдкрдирд╛ рд╕реНрд╡рдпрдВ рдХрд╛ рд╕реНрдЯреНрд░реАрдорд┐рдВрдЧ рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдХреИрд╕реЗ рдмрдирд╛ рд╕рдХрддреЗ рд╣реИрдВред рдпрджрд┐ рдЖрдк рдЙрд╕ рд╡рд┐рдЪрд╛рд░ рдХреЛ рджреЗрдЦрдиреЗ рдХреЗ рдЖрджреА рд╣реИрдВ, рдЬреЛ рдЖрдкрдХреЗ рд╣рд┐рдд рдореЗрдВ рд╣реИ, рддреЛ рддреБрд░рдВрдд рдЕрдкрдиреЗ рдЖрдк рдХреЛ рдХреЛрдб рдореЗрдВ рдбреБрдмреЛ рджреЗрдВ, рдЕрдм рдЖрдк рдЗрд╕ рднрдВрдбрд╛рд░ рдореЗрдВ рджреЗрдЦ рд╕рдХрддреЗ рд╣реИрдВред

рдореВрд▓ рдкреНрд░рдорд╛рдгреАрдХрд░рдг рдкреНрд░рдгрд╛рд▓реА рдХреЗ рд╕рд╛рде рд╡реЗрдм рд╕рд░реНрд╡рд░ рд╡рд┐рдХрд╛рд╕


рдЪрд▓реЛ Node.js рдкрд░ рдЖрдзрд╛рд░рд┐рдд рдПрдХ рд╕рд╛рдзрд╛рд░рдг рд╡реЗрдм рд╕рд░реНрд╡рд░ рдмрдирд╛рддреЗ рд╣реИрдВ, рдЬрд┐рд╕рдореЗрдВ, рдкрд╛рд╕рдкреЛрд░реНрдЯ.рдЬреЗрдПрд╕ рд▓рд╛рдЗрдмреНрд░реЗрд░реА рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ, рдПрдХ рд╕реНрдерд╛рдиреАрдп рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдкреНрд░рдорд╛рдгреАрдХрд░рдг рд░рдгрдиреАрддрд┐ рд▓рд╛рдЧреВ рдХреА рдЬрд╛рддреА рд╣реИред рд╣рдо MongoDB рдХрд╛ рдЙрдкрдпреЛрдЧ рд╕реВрдЪрдирд╛ рдХреЗ рд╕реНрдерд╛рдпреА рднрдВрдбрд╛рд░рдг рдХреЗ рд░реВрдк рдореЗрдВ рдХрд░реЗрдВрдЧреЗред рд╣рдо Mongoose ODM рдкреБрд╕реНрддрдХрд╛рд▓рдп рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░ рдбреЗрдЯрд╛рдмреЗрд╕ рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░реЗрдВрдЧреЗред

рдПрдХ рдирдИ рдкрд░рд┐рдпреЛрдЬрдирд╛ рд╢реБрд░реВ рдХрд░реЗрдВ:

$ npm init 

рдирд┐рд░реНрднрд░рддрд╛ рд╕реНрдерд╛рдкрд┐рдд рдХрд░реЗрдВ:

 $ 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 

рдкреНрд░реЛрдЬреЗрдХреНрдЯ рдбрд╛рдпрд░реЗрдХреНрдЯрд░реА рдореЗрдВ, рджреЛ рдлрд╝реЛрд▓реНрдбрд░ рдмрдирд╛рдПрдБ - client рдФрд░ server ред рд░рд┐рдПрдХреНрдЯ рдкрд░ рдЖрдзрд╛рд░рд┐рдд рдлреНрд░рдВрдЯреЗрдВрдб рдХреЛрдб client рдлрд╝реЛрд▓реНрдбрд░ рдореЗрдВ рд╕рдорд╛рдкреНрдд рд╣реЛ рдЬрд╛рдПрдЧрд╛, рдФрд░ рдмреИрдХрдПрдВрдб рдХреЛрдб server рдлрд╝реЛрд▓реНрдбрд░ рдореЗрдВ рд╕рдВрдЧреНрд░рд╣реАрдд рдХрд┐рдпрд╛ рдЬрд╛рдПрдЧрд╛ред рдЕрдм рд╣рдо server рдлрд╝реЛрд▓реНрдбрд░ рдореЗрдВ рдХрд╛рдо рдХрд░ рд░рд╣реЗ рд╣реИрдВред рдЕрд░реНрдерд╛рддреН, рд╣рдо рдкреНрд░рдорд╛рдгреАрдХрд░рдг рдкреНрд░рдгрд╛рд▓реА рдмрдирд╛рдиреЗ рдХреЗ рд▓рд┐рдП рдкрд╛рд╕рдкреЛрд░реНрдЯ.рдЬреЗрдПрд╕ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВрдЧреЗред рд╣рдордиреЗ рдкрд╣рд▓реЗ рд╣реА рдкрд╛рд╕рдкреЛрд░реНрдЯ рдФрд░ рдкрд╛рд╕рдкреЛрд░реНрдЯ-рд╕реНрдерд╛рдиреАрдп рдореЙрдбреНрдпреВрд▓ рд╕реНрдерд╛рдкрд┐рдд рдХрд░ рджрд┐рдП рд╣реИрдВред рдЗрд╕рд╕реЗ рдкрд╣рд▓реЗ рдХрд┐ рд╣рдо рд╕реНрдерд╛рдиреАрдп рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдкреНрд░рдорд╛рдгреАрдХрд░рдг рд░рдгрдиреАрддрд┐ рдХрд╛ рд╡рд░реНрдгрди рдХрд░реЗрдВ, рдПрдХ app.js рдлрд╝рд╛рдЗрд▓ рдмрдирд╛рдПрдВ рдФрд░ рдПрдХ рд╕рд╛рдзрд╛рд░рдг рд╕рд░реНрд╡рд░ рд╢реБрд░реВ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЖрд╡рд╢реНрдпрдХ рдХреЛрдб рдХреЛ рдЙрд╕рдореЗрдВ рдЬреЛрдбрд╝реЗрдВред рдпрджрд┐ рдЖрдк рдЗрд╕ рдХреЛрдб рдХреЛ рд╕реНрд╡рдпрдВ рдЪрд▓рд╛рдПрдВрдЧреЗ, рддреЛ рд╕реБрдирд┐рд╢реНрдЪрд┐рдд рдХрд░реЗрдВ рдХрд┐ рдЖрдкрдХреЗ рдкрд╛рд╕ MongoDB DBMS рд╕реНрдерд╛рдкрд┐рдд рд╣реИ рдФрд░ рдпрд╣ рдПрдХ рд╕реЗрд╡рд╛ рдХреЗ рд░реВрдк рдореЗрдВ рдЪрд▓рддрд╛ рд╣реИред

рдпрд╣рд╛рдБ 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}!`)); 

рд╣рдо рдЖрд╡реЗрджрди рдХреЗ рд▓рд┐рдП рдЖрд╡рд╢реНрдпрдХ рд╕рднреА рдорд┐рдбрд▓рд╡реЗрдпрд░ рдХреЛ рдбрд╛рдЙрдирд▓реЛрдб рдХрд░рддреЗ рд╣реИрдВ, рдЬреЛ MongoDB рд╕реЗ рдЬреБрдбрд╝рд╛ рд╣реИ, рдлрд╝рд╛рдЗрд▓ рднрдВрдбрд╛рд░рдг рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХреНрд╕рдкреНрд░реЗрд╕ рд╕рддреНрд░ рдХреЛ рдХреЙрдиреНрдлрд╝рд┐рдЧрд░ рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИред рдПрдХ рд╕рд░реНрд╡рд░ рд░рд┐рдмреВрдЯ рдХреЗ рдмрд╛рдж рднрдВрдбрд╛рд░рдг рд╕рддреНрд░ рдЙрдиреНрд╣реЗрдВ рдмрд╣рд╛рд▓ рдХрд░рдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рджреЗрдЧрд╛ред

рдЕрдм рд╣рдо рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдкрдВрдЬреАрдХрд░рдг рдФрд░ рдкреНрд░рдорд╛рдгреАрдХрд░рдг рдХреЗ рдЖрдпреЛрдЬрди рдХреЗ рд▓рд┐рдП рдкрд╛рд╕рдкреЛрд░реНрдЯ.рдЬреЗрдПрд╕ рд░рдгрдиреАрддрд┐рдпреЛрдВ рдХрд╛ рд╡рд░реНрдгрди рдХрд░рддреЗ рд╣реИрдВред server рдлрд╝реЛрд▓реНрдбрд░ рдореЗрдВ рдПрдХ auth рдлреЛрд▓реНрдбрд░ рдмрдирд╛рдПрдВ рдФрд░ рдЙрд╕рдореЗрдВ passport.js рдлрд╛рдЗрд▓ рд░рдЦреЗрдВред рдпрд╣рд╛рдБ рд╡рд╣ рд╣реИ рдЬреЛ 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); }); //  passport,    passport.use('localRegister', new LocalStrategy({        usernameField: 'email',        passwordField: 'password',        passReqToCallback: true    },    (req, email, password, done) => {        User.findOne({$or: [{email: email}, {username: req.body.username}]}, (err, user) => {            if (err)                return done(err);            if (user) {                if (user.email === email) {                    req.flash('email', 'Email is already taken');                               if (user.username === req.body.username) {                    req.flash('username', 'Username is already taken');                               return done(null, false);            } else {                let user = new User();                user.email = email;                user.password = user.generateHash(password);                user.username = req.body.username;                user.stream_key = shortid.generate();                user.save( (err) => {                    if (err)                        throw err;                    return done(null, user);                });                   });    })); //  passport,    passport.use('localLogin', new LocalStrategy({        usernameField: 'email',        passwordField: 'password',        passReqToCallback: true    },    (req, email, password, done) => {        User.findOne({'email': email}, (err, user) => {            if (err)                return done(err);            if (!user)                return done(null, false, req.flash('email', 'Email doesn\'t exist.'));            if (!user.validPassword(password))                return done(null, false, req.flash('password', 'Oops! Wrong password.'));            return done(null, user);        });    })); module.exports = passport; 

рдЗрд╕рдХреЗ рдЕрд▓рд╛рд╡рд╛, рд╣рдореЗрдВ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдореЙрдбрд▓ рдХреЗ рд▓рд┐рдП рд╕реНрдХреАрдорд╛ рдХрд╛ рд╡рд░реНрдгрди рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ (рдЗрд╕реЗ UserSchema рдХрд╣рд╛ UserSchema )ред server рдлрд╝реЛрд▓реНрдбрд░ рдореЗрдВ database рдлрд╝реЛрд▓реНрдбрд░ рдмрдирд╛рдПрдБ, рдФрд░ рдЗрд╕рдореЗрдВ UserSchema.js рдлрд╝рд╛рдЗрд▓ред

рдпрд╣рд╛рдБ server/database.UserSchema.js рдХреЗ рд▓рд┐рдП рдХреЛрдб рд╣реИред 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 рдХреА рддреАрди рд╡рд┐рдзрд┐рдпрд╛рдБ рд╣реИрдВред рдЬреЗрдирд░реЗрдЯрд╣реИрд╢ рдкрджреНрдзрддрд┐ рдХреЛ рдПрдХ рдкрд╛рд╕рд╡рд░реНрдб рдХреЛ рдкрд░рд┐рд╡рд░реНрддрд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдбрд┐рдЬрд╝рд╛рдЗрди рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИ, рдЬрд┐рд╕реЗ рд╕рд╛рджреЗ рдкрд╛рда рдХреЗ рд░реВрдк рдореЗрдВ, рдПрдХ bcrypt рд╣реИрд╢ рдореЗрдВ рдкреНрд░рд╕реНрддреБрдд рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИред рд╣рдо рдЗрд╕ рд╡рд┐рдзрд┐ рдХрд╛ рдЙрдкрдпреЛрдЧ рдкрд╛рд╕рдкреЛрд░реНрдЯ рд░рдгрдиреАрддрд┐ рдореЗрдВ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛рдУрдВ рджреНрд╡рд╛рд░рд╛ рджрд░реНрдЬ рдкрд╛рд╕рд╡рд░реНрдб рдХреЛ bcrypt рд╣реИрд╢ рдореЗрдВ рдмрджрд▓рдиреЗ рдХреЗ рд▓рд┐рдП рдХрд░рддреЗ рд╣реИрдВред рдкреНрд░рд╛рдкреНрдд рдкрд╛рд╕рд╡рд░реНрдб рд╣реИрд╢ рдХреЛ рддрдм рдбреЗрдЯрд╛рдмреЗрд╕ рдореЗрдВ рд╕рдВрдЧреНрд░рд╣реАрдд рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред validPassword рд╡рд┐рдзрд┐ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рджреНрд╡рд╛рд░рд╛ рджрд░реНрдЬ рдХрд┐рдП рдЧрдП рдкрд╛рд╕рд╡рд░реНрдб рдХреЛ рд╕реНрд╡реАрдХрд╛рд░ рдХрд░рддреА рд╣реИ рдФрд░ рдбреЗрдЯрд╛рдмреЗрд╕ рдореЗрдВ рд╕рдВрдЧреНрд░рд╣реАрдд рд╣реИрд╢ рдХреЗ рд╕рд╛рде рдЙрд╕рдХреЗ рд╣реИрд╢ рдХреА рддреБрд▓рдирд╛ рдХрд░рдХреЗ рдЗрд╕реЗ рд╕рддреНрдпрд╛рдкрд┐рдд рдХрд░рддреА рд╣реИред generateStreamKey рд╡рд┐рдзрд┐ рдЕрджреНрд╡рд┐рддреАрдп рд╕реНрдЯреНрд░рд┐рдВрдЧреНрд╕ рдмрдирд╛рддреА рд╣реИ рдЬреЛ рд╣рдо рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛рдУрдВ рдХреЛ RTMP рдХреНрд▓рд╛рдЗрдВрдЯ рдХреЗ рд▓рд┐рдП рдЙрдирдХреА рд╕реНрдЯреНрд░реАрдорд┐рдВрдЧ рдХреБрдВрдЬрд┐рдпреЛрдВ (рд╕реНрдЯреНрд░реАрдо рдХреБрдВрдЬрд┐рдпреЛрдВ) рдХреЗ рд░реВрдк рдореЗрдВ рд╕реНрдерд╛рдирд╛рдВрддрд░рд┐рдд рдХрд░реЗрдВрдЧреЗред

рдпрд╣рд╛рдБ server/database/Schema.js рдлрд╝рд╛рдЗрд▓ рдХреЛрдб рд╣реИ:

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

рдЕрдм рдЬрдм рд╣рдордиреЗ рдкрд╛рд╕рдкреЛрд░реНрдЯ рд░рдгрдиреАрддрд┐рдпреЛрдВ рдХреЛ рдкрд░рд┐рднрд╛рд╖рд┐рдд рдХрд░ рд▓рд┐рдпрд╛ рд╣реИ, рддреЛ UserSchema рд╕реНрдХреАрдо рдХрд╛ рд╡рд░реНрдгрди рдХрд┐рдпрд╛ рд╣реИ UserSchema рдФрд░ рдЗрд╕рдХреЗ рдЖрдзрд╛рд░ рдкрд░ рдПрдХ рдореЙрдбрд▓ рдмрдирд╛рдпрд╛ рд╣реИ, рдЖрдЗрдП рдкрд╛рд╕рдкреЛрд░реНрдЯ рдХреЛ app.js рдореЗрдВ app.js

рдпрд╣рд╛рдБ server/app.js рдлрд╝рд╛рдЗрд▓ рдХреЛ рдкреВрд░рдХ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдХреЛрдб рд╣реИ:

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

рдЗрд╕рдХреЗ рдЕрд▓рд╛рд╡рд╛, рдирдП рдорд╛рд░реНрдЧреЛрдВ рдХреЛ app.js рдореЗрдВ рдкрдВрдЬреАрдХреГрдд рд╣реЛрдирд╛ рдЪрд╛рд╣рд┐рдП рдРрд╕рд╛ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рдирд┐рдореНрди рдХреЛрдб рдХреЛ server/app.js :

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

routes рдлрд╝реЛрд▓реНрдбрд░ рдореЗрдВ server рдлрд╝реЛрд▓реНрдбрд░ рдореЗрдВ login.js рдФрд░ register.js рдлрд╝рд╛рдЗрд▓реЗрдВ рдмрдирд╛рдПрдБред рдЗрди рдлрд╝рд╛рдЗрд▓реЛрдВ рдореЗрдВ рд╣рдо рдЙрдкрд░реЛрдХреНрдд рдорд╛рд░реНрдЧреЛрдВ рдХреЗ рдПрдХ рдЬреЛрдбрд╝реЗ рдХреЛ рдкрд░рд┐рднрд╛рд╖рд┐рдд рдХрд░рддреЗ рд╣реИрдВ рдФрд░ рдкрдВрдЬреАрдХрд░рдг рдФрд░ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдкреНрд░рдорд╛рдгреАрдХрд░рдг рдХреЛ рд╡реНрдпрд╡рд╕реНрдерд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдкрд╛рд╕рдкреЛрд░реНрдЯ рдорд┐рдбрд▓рд╡реЗрдпрд░ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд╣реИрдВред

рдпрд╣рд╛рдБ 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; 

рдпрд╣рд╛рдБ 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; 

рд╣рдо ejs templating рдЗрдВрдЬрди рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд╣реИрдВред login.ejs рдФрд░ register.ejs рдЯреЗрдореНрдкрд▓реЗрдЯ рдлрд╝рд╛рдЗрд▓реЛрдВ рдХреЛ views рдлрд╝реЛрд▓реНрдбрд░ рдореЗрдВ рдЬреЛрдбрд╝реЗрдВ, рдЬреЛ server рдлрд╝реЛрд▓реНрдбрд░ рдореЗрдВ рд╕реНрдерд┐рдд рд╣реИред

рдпрд╣рд╛рдБ 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> 

рдпрд╣рд╛рдБ server/views/register.ejs рдореЗрдВ рдХреНрдпрд╛ рд╣реЛрдирд╛ рдЪрд╛рд╣рд┐рдПред 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> 

рд╣рдо рдХрд╣ рд╕рдХрддреЗ рд╣реИрдВ рдХрд┐ рд╣рдордиреЗ рдкреНрд░рдорд╛рдгреАрдХрд░рдг рдкреНрд░рдгрд╛рд▓реА рдкрд░ рдХрд╛рдо рдЦрддреНрдо рдХрд░ рджрд┐рдпрд╛ рд╣реИред рдЕрдм рд╣рдо рдкрд░рд┐рдпреЛрдЬрдирд╛ рдХреЗ рдЕрдЧрд▓реЗ рднрд╛рдЧ рдХрд╛ рдирд┐рд░реНрдорд╛рдг рд╢реБрд░реВ рдХрд░реЗрдВрдЧреЗ рдФрд░ RTMP рд╕рд░реНрд╡рд░ рдХреЛ рдХреЙрдиреНрдлрд╝рд┐рдЧрд░ рдХрд░реЗрдВрдЧреЗред

RTMP рд╕рд░реНрд╡рд░ рдХреЙрдиреНрдлрд╝рд┐рдЧрд░ рдХрд░реЗрдВ


RTMP (рд░рд┐рдпрд▓-рдЯрд╛рдЗрдо рдореИрд╕реЗрдЬрд┐рдВрдЧ рдкреНрд░реЛрдЯреЛрдХреЙрд▓) рдПрдХ рдкреНрд░реЛрдЯреЛрдХреЙрд▓ рд╣реИ рдЬрд┐рд╕реЗ рдЯреЗрдк рдбреНрд░рд╛рдЗрд╡ рдФрд░ рд╕рд░реНрд╡рд░ рдХреЗ рдмреАрдЪ рд╡реАрдбрд┐рдпреЛ, рдСрдбрд┐рдпреЛ рдФрд░ рд╡рд┐рднрд┐рдиреНрди рдбреЗрдЯрд╛ рдХреЗ рдЙрдЪреНрдЪ-рдкреНрд░рджрд░реНрд╢рди рд╕рдВрдЪрд░рдг рдХреЗ рд▓рд┐рдП рд╡рд┐рдХрд╕рд┐рдд рдХрд┐рдпрд╛ рдЧрдпрд╛ рдерд╛ред рдЪрд┐рдХреЛрдЯреА, рдлреЗрд╕рдмреБрдХ, рдпреВрдЯреНрдпреВрдм рдФрд░ рдХрдИ рдЕрдиреНрдп рд╕реНрдЯреНрд░реАрдорд┐рдВрдЧ рд╕рд╛рдЗрдЯреЗрдВ RTMP рд╕реНрдЯреНрд░реАрдо рд╕реНрд╡реАрдХрд╛рд░ рдХрд░рддреА рд╣реИрдВ рдФрд░ рдЕрдкрдиреА рдЙрдЪреНрдЪ рдЙрдкрд▓рдмреНрдзрддрд╛ рд╕реБрдирд┐рд╢реНрдЪрд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЗрди рдзрд╛рд░рд╛рдУрдВ рдХреЛ рдЕрдкрдиреЗ CDN рдореЗрдВ рдЯреНрд░рд╛рдВрд╕рдлрд░ рдХрд░рдиреЗ рд╕реЗ рдкрд╣рд▓реЗ HTTP рд╕реНрдЯреНрд░реАрдо (HLS рдлреЙрд░реНрдореЗрдЯ) рдореЗрдВ рдЯреНрд░рд╛рдВрд╕рдХреЛрдб рдХрд░рддреА рд╣реИрдВред

рд╣рдо рдиреЛрдб-рдореАрдбрд┐рдпрд╛-рд╕рд░реНрд╡рд░ рдореЙрдбреНрдпреВрд▓ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд╣реИрдВ - RTMP рдореАрдбрд┐рдпрд╛ рд╕рд░реНрд╡рд░ рдХрд╛ Node.js-рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрдиред рдпрд╣ рдореАрдбрд┐рдпрд╛ рд╕рд░реНрд╡рд░ RTMP рд╕реНрдЯреНрд░реАрдо рдХреЛ рд╕реНрд╡реАрдХрд╛рд░ рдХрд░рддрд╛ рд╣реИ рдФрд░ рдЙрдиреНрд╣реЗрдВ ffmpeg рдорд▓реНрдЯреАрдореАрдбрд┐рдпрд╛ рдлреНрд░реЗрдорд╡рд░реНрдХ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ HLS / DASH рдореЗрдВ рдкрд░рд┐рд╡рд░реНрддрд┐рдд рдХрд░рддрд╛ рд╣реИред рдкреНрд░реЛрдЬреЗрдХреНрдЯ рдХреЛ рд╕рдлрд▓рддрд╛рдкреВрд░реНрд╡рдХ рдХрд╛рдо рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рдЖрдкрдХреЗ рд╕рд┐рд╕реНрдЯрдо рдкрд░ ffmpeg рд╕реНрдерд╛рдкрд┐рдд рд╣реЛрдирд╛ рдЪрд╛рд╣рд┐рдПред рдпрджрд┐ рдЖрдк рд▓рд┐рдирдХреНрд╕ рдкрд░ рдХрд╛рдо рдХрд░ рд░рд╣реЗ рд╣реИрдВ рдФрд░ рдЖрдкрдХреЗ рдкрд╛рд╕ рдкрд╣рд▓реЗ рд╕реЗ рд╣реА ffmpeg рд╕реНрдерд╛рдкрд┐рдд рд╣реИ, рддреЛ рдЖрдк рдЯрд░реНрдорд┐рдирд▓ рдкрд░ рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рдХрдорд╛рдВрдб рдХреЛ рдЪрд▓рд╛рдХрд░ рдЗрд╕рдХрд╛ рдкрддрд╛ рд▓рдЧрд╛ рд╕рдХрддреЗ рд╣реИрдВ:

 $ which ffmpeg # /usr/bin/ffmpeg 

рдиреЛрдб-рдореАрдбрд┐рдпрд╛-рд╕рд░реНрд╡рд░ рдкреИрдХреЗрдЬ рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, ffmpeg рд╕рдВрд╕реНрдХрд░рдг 4.x рдХреА рд╕рд┐рдлрд╛рд░рд┐рд╢ рдХреА рдЬрд╛рддреА рд╣реИред рдЖрдк рдЗрд╕ рддрд░рд╣ ffmpeg рдХреЗ рд╕реНрдерд╛рдкрд┐рдд рд╕рдВрд╕реНрдХрд░рдг рдХреА рдЬрд╛рдБрдЪ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ:

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

рдпрджрд┐ рдЖрдкрдХреЗ рдкрд╛рд╕ ffmpeg рд╕реНрдерд╛рдкрд┐рдд рдирд╣реАрдВ рд╣реИ рдФрд░ рдЖрдк Ubuntu рдЪрд▓рд╛ рд░рд╣реЗ рд╣реИрдВ, рддреЛ рдЖрдк рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рдХрдорд╛рдВрдб рдХреЛ рдЪрд▓рд╛рдХрд░ рдЗрд╕ рдврд╛рдВрдЪреЗ рдХреЛ рд╕реНрдерд╛рдкрд┐рдд рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ:

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

рдпрджрд┐ рдЖрдк рд╡рд┐рдВрдбреЛрдЬ рдкрд░ рдХрд╛рдо рдХрд░рддреЗ рд╣реИрдВ, рддреЛ рдЖрдк рд╡рд┐рдВрдбреЛрдЬ рдХреЗ рд▓рд┐рдП ffmpeg рдмрд┐рд▓реНрдб рдбрд╛рдЙрдирд▓реЛрдб рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред

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; 

ffmpeg рдХреЗ рдорд╛рди рдХреЛ рдЙрд╕ рдкрде рд╕реЗ рдмрджрд▓реЗрдВ, рдЬрд╣рд╛рдБ рдЖрдкрдХреЗ рд╕рд┐рд╕реНрдЯрдо рдкрд░ ffmpeg рд╕реНрдерд╛рдкрд┐рдд рд╣реИред рдпрджрд┐ рдЖрдк рд╡рд┐рдВрдбреЛрдЬ рдкрд░ рдХрд╛рдо рдХрд░ рд░рд╣реЗ рд╣реИрдВ рдФрд░ рдКрдкрд░ рджрд┐рдП рдЧрдП рд▓рд┐рдВрдХ рд╕реЗ рд╡рд┐рдВрдбреЛрдЬ рдЕрд╕реЗрдВрдмрд▓реА ffmpeg рдбрд╛рдЙрдирд▓реЛрдб рдХрд┐рдпрд╛ рд╣реИ - рдлрд╝рд╛рдЗрд▓ рдирд╛рдо рдореЗрдВ .exe рдПрдХреНрд╕рдЯреЗрдВрд╢рди рдЬреЛрдбрд╝рдирд╛ рди рднреВрд▓реЗрдВред рдлрд┐рд░ рдЙрдкрд░реЛрдХреНрдд рдХреЛрдб рдХрд╛ рд╕рдВрдмрдВрдзрд┐рдд рдЯреБрдХрдбрд╝рд╛ рдЗрд╕ рддрд░рд╣ рджрд┐рдЦреЗрдЧрд╛:

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

рдЕрдм рдирд┐рдореНрди рдХрдорд╛рдВрдб рдЪрд▓рд╛рдХрд░ рдиреЛрдб-рдореАрдбрд┐рдпрд╛-рд╕рд░реНрд╡рд░ рд╕реНрдерд╛рдкрд┐рдд рдХрд░реЗрдВ:

 $ npm install node-media-server --save 

server рдлрд╝реЛрд▓реНрдбрд░ рдореЗрдВ рдПрдХ media_server.js рдлрд╝рд╛рдЗрд▓ рдмрдирд╛рдПрдБред

рдпрд╣рд╛рдБ 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; 

NodeMediaService рдСрдмреНрдЬреЗрдХреНрдЯ NodeMediaService рдЙрдкрдпреЛрдЧ рдХрд░рдирд╛ рдХрд╛рдлреА рд╕рд░рд▓ рд╣реИред рдпрд╣ RTMP рд╕рд░реНрд╡рд░ рдкреНрд░рджрд╛рди рдХрд░рддрд╛ рд╣реИ рдФрд░ рдЖрдкрдХреЛ рдХрдиреЗрдХреНрд╢рди рдХреЗ рд▓рд┐рдП рдкреНрд░рддреАрдХреНрд╖рд╛ рдХрд░рдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рджреЗрддрд╛ рд╣реИред рдпрджрд┐ рд╕реНрдЯреНрд░реАрдорд┐рдВрдЧ рдХреБрдВрдЬреА рдЕрдорд╛рдиреНрдп рд╣реИ, рддреЛ рдЖрдиреЗ рд╡рд╛рд▓реЗ рдХрдиреЗрдХреНрд╢рди рдХреЛ рдЕрд╕реНрд╡реАрдХрд╛рд░ рдХрд░ рджрд┐рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИред рд╣рдо рдЗрд╕ prePublish рд╡рд╕реНрддреБ рдХреА рдШрдЯрдирд╛ рдХреЛ рд╕рдВрднрд╛рд▓ prePublish ред рдЕрдЧрд▓реЗ рднрд╛рдЧ рдореЗрдВ, рд╣рдо prePublish рдИрд╡реЗрдВрдЯ рд╢реНрд░реЛрддрд╛ рдмрдВрдж рд╣реЛрдиреЗ рдХреЗ рд▓рд┐рдП рдЕрддрд┐рд░рд┐рдХреНрдд рдХреЛрдб рдЬреЛрдбрд╝рддреЗ рд╣реИрдВред рдпрд╣ рдЖрдкрдХреЛ рдЕрдорд╛рдиреНрдп рд╕реНрдЯреНрд░реАрдорд┐рдВрдЧ рдХреБрдВрдЬрд┐рдпреЛрдВ рдХреЗ рд╕рд╛рде рдЖрдиреЗ рд╡рд╛рд▓реЗ рдХрдиреЗрдХреНрд╢рди рдХреЛ рдЕрд╕реНрд╡реАрдХрд╛рд░ рдХрд░рдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рджреЗрдЧрд╛ред рдЗрд╕ рдмреАрдЪ, рд╣рдо рдЖрдиреЗ рд╡рд╛рд▓реЗ рд╕рднреА рдХрдиреЗрдХреНрд╢рдиреЛрдВ рдХреЛ рдбрд┐рдлрд╝реЙрд▓реНрдЯ RTMP рдкреЛрд░реНрдЯ (1935) рдореЗрдВ рд╕реНрд╡реАрдХрд╛рд░ рдХрд░реЗрдВрдЧреЗред рд╣рдореЗрдВ рдХреЗрд╡рд▓ node_media_server рдореЗрдВ app.js рдСрдмреНрдЬреЗрдХреНрдЯ рдЖрдпрд╛рдд рдХрд░рдирд╛ app.js рдФрд░ рдЗрд╕рдХреА run рд╡рд┐рдзрд┐ рдХреЛ рдХреЙрд▓ рдХрд░рдирд╛ рд╣реЛрдЧрд╛ред

server/app.js рдХреЗ рд▓рд┐рдП рдирд┐рдореНрди рдХреЛрдб рдЬреЛрдбрд╝реЗрдВ:

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

рдУрдмреАрдПрд╕ (рдУрдкрди рдмреНрд░реЙрдбрдХрд╛рд╕реНрдЯрд░ рд╕реЙрдлреНрдЯрд╡реЗрдпрд░) рдбрд╛рдЙрдирд▓реЛрдб рдФрд░ рдЗрдВрд╕реНрдЯреЙрд▓ рдХрд░реЗрдВред рдПрдкреНрд▓рд┐рдХреЗрд╢рди рд╕реЗрдЯрд┐рдВрдЧ рд╡рд┐рдВрдбреЛ рдЦреЛрд▓реЗрдВ рдФрд░ Stream рдЕрдиреБрднрд╛рдЧ рдкрд░ рдЬрд╛рдПрдВред Service рдХреНрд╖реЗрддреНрд░ рдореЗрдВ Custom рдХрд╛ рдЪрдпрди рдХрд░реЗрдВ рдФрд░ rtmp://127.0.0.1:1935/live рджрд░реНрдЬ рдХрд░реЗрдВ rtmp://127.0.0.1:1935/live Server рдХреНрд╖реЗрддреНрд░ рдореЗрдВ rtmp://127.0.0.1:1935/live ред Stream Key рдлрд╝реАрд▓реНрдб рдХреЛ рд░рд┐рдХреНрдд рдЫреЛрдбрд╝рд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИред рдпрджрд┐ рдкреНрд░реЛрдЧреНрд░рд╛рдо рдЖрдкрдХреЛ рдЗрд╕ рдлрд╝реАрд▓реНрдб рдореЗрдВ рднрд░рдиреЗ рдХреЗ рдмрд┐рдирд╛ рд╕реЗрдЯрд┐рдВрдЧреНрд╕ рдХреЛ рд╕рд╣реЗрдЬрдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рдирд╣реАрдВ рджреЗрддрд╛ рд╣реИ, рддреЛ рдЖрдк рдЗрд╕рдореЗрдВ рдХрд┐рд╕реА рднреА рд╡рд░реНрдг рдХреЛ рд╕реЗрдЯ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред Apply рдмрдЯрди рдкрд░ рдФрд░ OK рдмрдЯрди рдкрд░ рдХреНрд▓рд┐рдХ рдХрд░реЗрдВред рдЕрдкрдиреЗ рд╕реНрдерд╛рдиреАрдп рд╕рд░реНрд╡рд░ рдкрд░ рдЕрдкрдиреЗ RTMP рд╕реНрдЯреНрд░реАрдо рдХреЛ рд╕реНрдерд╛рдирд╛рдВрддрд░рд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП Start Streaming рдмрдЯрди рдкрд░ рдХреНрд▓рд┐рдХ рдХрд░реЗрдВред


OBS рдХреЙрдиреНрдлрд╝рд┐рдЧрд░ рдХрд░реЗрдВ

рдЯрд░реНрдорд┐рдирд▓ рдкрд░ рдЬрд╛рдПрдВ рдФрд░ рдореАрдбрд┐рдпрд╛ рд╕рд░реНрд╡рд░ рд╡рд╣рд╛рдВ рдХреНрдпрд╛ рдкреНрд░рджрд░реНрд╢рд┐рдд рдХрд░рддрд╛ рд╣реИ, рдЗрд╕реЗ рджреЗрдЦреЗрдВред рдЖрдкрдХреЛ рдЖрдиреЗ рд╡рд╛рд▓реА рд╕реНрдЯреНрд░реАрдо рдФрд░ рдХрдИ рдИрд╡реЗрдВрдЯ рд╢реНрд░реЛрддрд╛рдУрдВ рдХреЗ рд▓реЙрдЧ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдЬрд╛рдирдХрд╛рд░реА рджрд┐рдЦрд╛рдИ рджреЗрдЧреАред


Node.js рдкрд░ рдЖрдзрд╛рд░рд┐рдд рдореАрдбрд┐рдпрд╛ рд╕рд░реНрд╡рд░ рджреНрд╡рд╛рд░рд╛ рдЯрд░реНрдорд┐рдирд▓ рдореЗрдВ рдбреЗрдЯрд╛ рдЖрдЙрдЯрдкреБрдЯ

рдореАрдбрд┐рдпрд╛ рд╕рд░реНрд╡рд░ рдПрдкреАрдЖрдИ рддрдХ рдкрд╣реБрдВрдЪ рджреЗрддрд╛ рд╣реИ, рдЬрд┐рд╕рд╕реЗ рдЖрдк рдХрдиреЗрдХреНрдЯреЗрдб рдХреНрд▓рд╛рдЗрдВрдЯ рдХреА рд╕реВрдЪреА рдкреНрд░рд╛рдкреНрдд рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред рдЗрд╕ рд╕реВрдЪреА рдХреЛ рджреЗрдЦрдиреЗ рдХреЗ рд▓рд┐рдП, рдЖрдк http://127.0.0.1:8888/api/streams рдкрд░ рдмреНрд░рд╛рдЙрдЬрд╝рд░ рдкрд░ рдЬрд╛ рд╕рдХрддреЗ рд╣реИрдВред рдмрд╛рдж рдореЗрдВ, рд╣рдо рдмреНрд░реЙрдбрдХрд╛рд╕реНрдЯ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛рдУрдВ рдХреА рд╕реВрдЪреА рдкреНрд░рджрд░реНрд╢рд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЗрд╕ API рдХрд╛ рдЙрдкрдпреЛрдЧ рд░рд┐рдПрдХреНрдЯ рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдореЗрдВ рдХрд░реЗрдВрдЧреЗред рдЗрд╕ 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"       } 

рдЕрдм рдмреИрдХреЗрдВрдб рд▓рдЧрднрдЧ рддреИрдпрд╛рд░ рд╣реИред рдпрд╣ рдПрдХ рдХрд╛рд░реНрдпрд╢реАрд▓ рд╕реНрдЯреНрд░реАрдорд┐рдВрдЧ рд╕рд░реНрд╡рд░ рд╣реИ рдЬреЛ HTTP, RTMP рдФрд░ HLS рддрдХрдиреАрдХреЛрдВ рдХрд╛ рд╕рдорд░реНрдерди рдХрд░рддрд╛ рд╣реИред рд╣рд╛рд▓рд╛рдБрдХрд┐, рд╣рдордиреЗ рдЕрднреА рддрдХ рдЖрдиреЗ рд╡рд╛рд▓реЗ RTMP рдХрдиреЗрдХреНрд╢рди рдХреА рдЬрд╛рдБрдЪ рдХреЗ рд▓рд┐рдП рдПрдХ рд╕рд┐рд╕реНрдЯрдо рдирд╣реАрдВ рдмрдирд╛рдпрд╛ рд╣реИред рд╣рдореЗрдВ рдпрд╣ рд╕реБрдирд┐рд╢реНрдЪрд┐рдд рдХрд░рдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рджреЗрдиреА рдЪрд╛рд╣рд┐рдП рдХрд┐ рд╕рд░реНрд╡рд░ рдХреЗрд╡рд▓ рдкреНрд░рдорд╛рдгрд┐рдд рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛рдУрдВ рд╕реЗ рд╣реА рд╕реНрдЯреНрд░реАрдо рд╕реНрд╡реАрдХрд╛рд░ рдХрд░рддрд╛ рд╣реИред server/media_server.js рдореЗрдВ prePublish рдИрд╡реЗрдВрдЯ prePublish рдореЗрдВ рдирд┐рдореНрди рдХреЛрдб рдЬреЛрдбрд╝реЗрдВ:

 //       const User = require('./database/Schema').User; nms.on('prePublish', async (id, StreamPath, args) => {    let stream_key = getStreamKeyFromStreamPath(StreamPath);    console.log('[NodeEvent on prePublish]', `id=${id} StreamPath=${StreamPath} args=${JSON.stringify(args)}`);    User.findOne({stream_key: stream_key}, (err, user) => {        if (!err) {            if (!user) {                let session = nms.getSession(id);                session.reject();            } else {                // -                       }); }); const getStreamKeyFromStreamPath = (path) => {    let parts = path.split('/');    return parts[parts.length - 1]; }; 

рдХреНрд▓реЛрдЬрд░ рдореЗрдВ, рд╣рдо рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХреЛ рд╕реНрдЯреНрд░реАрдорд┐рдВрдЧ рдХреБрдВрдЬреА рдХреЗ рд╕рд╛рде рдЦреЛрдЬрдиреЗ рдХреЗ рд▓рд┐рдП рдбреЗрдЯрд╛рдмреЗрд╕ рдХреА рдХреНрд╡реЗрд░реА рдХрд░рддреЗ рд╣реИрдВред рдпрджрд┐ рдХреБрдВрдЬреА рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХреА рд╣реИ, рддреЛ рд╣рдо рдХреЗрд╡рд▓ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХреЛ рд╕рд░реНрд╡рд░ рд╕реЗ рдХрдиреЗрдХреНрдЯ рдХрд░рдиреЗ рдФрд░ рдЙрдирдХреЗ рдкреНрд░рд╕рд╛рд░рдг рдХреЛ рдкреНрд░рдХрд╛рд╢рд┐рдд рдХрд░рдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рджреЗрддреЗ рд╣реИрдВред рдЕрдиреНрдпрдерд╛, рд╣рдо рдЖрдиреЗ рд╡рд╛рд▓реЗ RTMP рдХрдиреЗрдХреНрд╢рди рдХреЛ рдЕрд╕реНрд╡реАрдХрд╛рд░ рдХрд░ рджреЗрддреЗ рд╣реИрдВред

рдЕрдЧрд▓реЗ рднрд╛рдЧ рдореЗрдВ, рд╣рдо React рдХреЗ рдЖрдзрд╛рд░ рдкрд░ рдПрдХ рд╕рд░рд▓ рдХреНрд▓рд╛рдЗрдВрдЯ-рд╕рд╛рдЗрдб рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдмрдирд╛рдПрдВрдЧреЗред рджрд░реНрд╢рдХреЛрдВ рдХреЛ рд╕реНрдЯреНрд░реАрдорд┐рдВрдЧ рдкреНрд░рд╕рд╛рд░рдг рджреЗрдЦрдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рджреЗрдиреЗ рдХреЗ рд▓рд┐рдП, рд╕рд╛рде рд╣реА рд╕реНрдЯреНрд░реАрдорд░реЛрдВ рдХреЛ рдЕрдкрдиреА рд╕реНрдЯреНрд░реАрдорд┐рдВрдЧ рдХреБрдВрдЬреА рдЙрддреНрдкрдиреНрди рдХрд░рдиреЗ рдФрд░ рджреЗрдЦрдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рджреЗрдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИред

рд▓рд╛рдЗрд╡ рд╕реНрдЯреНрд░реАрдорд┐рдВрдЧ


рдЕрдм clients рдлреЛрд▓реНрдбрд░ рдореЗрдВ рдЬрд╛рдПрдВред рдЪреВрдВрдХрд┐ рд╣рдо рд░рд┐рдПрдХреНрдЯ рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдмрдирд╛рдиреЗ рдЬрд╛ рд░рд╣реЗ рд╣реИрдВ, рд╣рдореЗрдВ рдПрдХ рд╡реЗрдмрдкреИрдХ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реЛрдЧреАред рд╣рдореЗрдВ рд▓реЛрдбрд░ рднреА рдЪрд╛рд╣рд┐рдП, рдЬреЛ рдЬреЗрдПрд╕рдПрдХреНрд╕ рдХреЛрдб рдХреЛ рдЬрд╛рд╡рд╛рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдХреЛрдб рдореЗрдВ рдЕрдиреБрд╡рд╛рдж рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЙрдкрдпреЛрдЧ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ рдЬреЛ рдмреНрд░рд╛рдЙрдЬрд╝рд░ рд╕рдордЭрддреЗ рд╣реИрдВред рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рдореЙрдбреНрдпреВрд▓ рд╕реНрдерд╛рдкрд┐рдд рдХрд░реЗрдВ:

 $ 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 

рдкреНрд░реЛрдЬреЗрдХреНрдЯ рдореЗрдВ рдЬреЛрдбрд╝реЗрдВ, рдЗрд╕рдХреА рдореВрд▓ рдирд┐рд░реНрджреЗрд╢рд┐рдХрд╛ рдореЗрдВ, 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    }, }; 

client/index.js рдлрд╝рд╛рдЗрд▓ рдХреЛ рдкреНрд░реЛрдЬреЗрдХреНрдЯ рдореЗрдВ рдЬреЛрдбрд╝реЗрдВ:

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

рдпрд╣рд╛рдБ 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; } 

рд░рд╛рдЙрдЯрд░ рдХреЗ рд▓рд┐рдП рд░рд┐рдПрдХреНрдЯ рд░рд╛рдЙрдЯрд░ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред рджреГрд╢реНрдпрдкрдЯрд▓ рдореЗрдВ, рд╣рдо рдмреВрдЯрд╕реНрдЯреНрд░реИрдк рдХрд╛ рднреА рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд╣реИрдВ, рдФрд░, рдкреНрд░рд╕рд╛рд░рдг, рд╡реАрдбрд┐рдпреЛ рдХреЗ рд▓рд┐рдПред jsред рдЕрдм components рдлрд╝реЛрд▓реНрдбрд░ рдХреЛ client рдлрд╝реЛрд▓реНрдбрд░ рдореЗрдВ рдЬреЛрдбрд╝реЗрдВ, рдФрд░ Root.js рдлрд╝рд╛рдЗрд▓ рдореЗрдВред рдпрд╣рд╛рдБ 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>          } 

<Root/> рдШрдЯрдХ рдПрдХ <Router/> рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛ рдШрдЯрдХ рдкреНрд░рджрд╛рди рдХрд░рддрд╛ рд╣реИ рдЬрд┐рд╕рдореЗрдВ рддреАрди <Route/> рдХрдорд░реНрд╕ рд╣реЛрддреЗ рд╣реИрдВред <LiveStreams/> рдШрдЯрдХ рдкреНрд░рд╕рд╛рд░рдг рдХреА рдПрдХ рд╕реВрдЪреА рдкреНрд░рджрд░реНрд╢рд┐рдд рдХрд░рддрд╛ рд╣реИред рд╡реАрдбрд┐рдпреЛ рдкреНрд░рджрд░реНрд╢рд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП <VideoPlayer/> рдШрдЯрдХ рдЬрд┐рдореНрдореЗрджрд╛рд░ рд╣реИред рдЬреЗрдПрд╕ рдкреНрд▓реЗрдпрд░ред рд╕реНрдЯреНрд░реАрдорд┐рдВрдЧ <Settings/> рд╕рд╛рде рдХрд╛рдо рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЗрдВрдЯрд░рдлрд╝реЗрд╕ рдмрдирд╛рдиреЗ рдХреЗ рд▓рд┐рдП <Settings/> рдШрдЯрдХ рдЬрд┐рдореНрдореЗрджрд╛рд░ рд╣реИред

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

рдпрд╣ рд╡рд╣реА рд╣реИ рдЬреЛ рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдкреГрд╖реНрда рдЬреИрд╕рд╛ рджрд┐рдЦрддрд╛ рд╣реИред


рд╕реАрдорд╛рд╡рд░реНрддреА рд╕реНрдЯреНрд░реАрдорд┐рдВрдЧ рд╕реЗрд╡рд╛

<LiveStreams/> рдШрдЯрдХ рдмрдврд╝рдиреЗ рдХреЗ рдмрд╛рдж, рдПрдирдПрдордПрд╕ рдПрдкреАрдЖрдИ рдХреЛ рд╕рд┐рд╕реНрдЯрдо рд╕реЗ рдЬреБрдбрд╝реЗ рдЧреНрд░рд╛рд╣рдХреЛрдВ рдХреА рдПрдХ рд╕реВрдЪреА рдкреНрд░рд╛рдкреНрдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдХрд╣рд╛ рдЬрд╛рддрд╛ рд╣реИред рдПрдирдПрдордПрд╕ рдПрдкреАрдЖрдИ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛рдУрдВ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдЕрдзрд┐рдХ рдЬрд╛рдирдХрд╛рд░реА рдкреНрд░рджрд╛рди рдирд╣реАрдВ рдХрд░рддрд╛ рд╣реИред рд╡рд┐рд╢реЗрд╖ рд░реВрдк рд╕реЗ, рд╣рдо рдЗрд╕рд╕реЗ рд╕реНрдЯреНрд░реАрдорд┐рдВрдЧ рдХреБрдВрдЬреА рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдЬрд╛рдирдХрд╛рд░реА рдкреНрд░рд╛рдкреНрдд рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ, рдЬрд┐рд╕рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ RTMP рд╕рд░реНрд╡рд░ рд╕реЗ рдЬреБрдбрд╝реЗ рд╣реИрдВред рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдЦрд╛рддреЛрдВ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдЬрд╛рдирдХрд╛рд░реА рдкреНрд░рд╛рдкреНрдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдбреЗрдЯрд╛рдмреЗрд╕ рдореЗрдВ рдкреНрд░рд╢реНрди рдЙрддреНрдкрдиреНрди рдХрд░рддреЗ рд╕рдордп рд╣рдо рдЗрди рдХреБрдВрдЬрд┐рдпреЛрдВ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВрдЧреЗред

getStreamsInfo рдкрджреНрдзрддрд┐ рдореЗрдВ getStreamsInfo рд╣рдо рдПрдХ XHR рдЕрдиреБрд░реЛрдз /streams/info рдЕрдорд▓ рдХрд░рддреЗ рд╣реИрдВ, рд▓реЗрдХрд┐рди рд╣рдордиреЗ рдЕрднреА рддрдХ рдРрд╕рд╛ рдХреБрдЫ рдирд╣реАрдВ рдмрдирд╛рдпрд╛ рд╣реИ рдЬреЛ рдЗрд╕ рдЕрдиреБрд░реЛрдз рдХрд╛ рдЬрд╡рд╛рдм рджреЗ рд╕рдХреЗред рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рд╕рд╛рдордЧреНрд░реА рдХреЗ рд╕рд╛рде рдПрдХ server/routes/streams.js рдмрдирд╛рдПрдБ:

 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; 

рд╣рдо рдЬреБрдбрд╝реЗ рдЧреНрд░рд╛рд╣рдХреЛрдВ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдЬрд╛рдирдХрд╛рд░реА рдкреНрд░рд╛рдкреНрдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рдмреИрдХреЗрдВрдб рдХреЛ рдПрдирдПрдордПрд╕ рдПрдкреАрдЖрдИ рджреНрд╡рд╛рд░рд╛ рд▓реМрдЯрд╛рдП рдЧрдП рдкреНрд░рд╡рд╛рд╣ рдХреА рдЬрд╛рдирдХрд╛рд░реА рджреЗрддреЗ рд╣реИрдВред

рд╣рдо рдЙрди рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛рдУрдВ рдХреА рд╕реВрдЪреА рдкреНрд░рд╛рдкреНрдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рдбреЗрдЯрд╛рдмреЗрд╕ рдХреНрд╡реЗрд░реА рдХрд░рддреЗ рд╣реИрдВ, рдЬрд┐рдирдХреА рд╕реНрдЯреНрд░реАрдорд┐рдВрдЧ рдХреБрдВрдЬрд┐рдпрд╛рдБ рд╣рдореЗрдВ NMS API рд╕реЗ рдорд┐рд▓рддреА рд╣реИрдВред рд╣рдо JSON рдкреНрд░рд╛рд░реВрдк рдореЗрдВ рд╕реВрдЪреА рд▓реМрдЯрд╛рддреЗ рд╣реИрдВред рд╣рдо server/app.js рдореЗрдВ рдорд╛рд░реНрдЧ рдХреЛ рдкрдВрдЬреАрдХреГрдд рдХрд░рддреЗ рд╣реИрдВ:

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

рдкрд░рд┐рдгрд╛рдорд╕реНрд╡рд░реВрдк, рд╣рдо рд╕рдХреНрд░рд┐рдп рдкреНрд░рд╕рд╛рд░рдгреЛрдВ рдХреА рдПрдХ рд╕реВрдЪреА рдкреНрд░рджрд░реНрд╢рд┐рдд рдХрд░рддреЗ рд╣реИрдВред рдЗрд╕ рд╕реВрдЪреА рдореЗрдВ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдирд╛рдо рдФрд░ рдердВрдмрдиреЗрд▓ рд╢рд╛рдорд┐рд▓ рд╣реИрдВред рд╣рдо рд▓реЗрдЦ рдХреЗ рдЕрдВрдд рдореЗрдВ рдкреНрд░рд╕рд╛рд░рдг рдХреЗ рд▓рд┐рдП рдердВрдмрдиреЗрд▓ рдмрдирд╛рдиреЗ рдХреЗ рддрд░реАрдХреЗ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдмрд╛рдд рдХрд░реЗрдВрдЧреЗред рдердВрдмрдиреЗрд▓ рдХреЛ рдЙрди рд╡рд┐рд╢рд┐рд╖реНрдЯ рдкреГрд╖реНрдареЛрдВ рд╕реЗ рдмрд╛рдВрдзрд╛ рдЧрдпрд╛ рд╣реИ рдЬрд╣рд╛рдВ HLS рдзрд╛рд░рд╛рдПрдБ рд╡реАрдбрд┐рдпреЛред Js рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдЦреЗрд▓реА рдЬрд╛рддреА рд╣реИрдВред

client/components/VideoPlayer.js рдмрдирд╛рдПрдБ:

 import React from 'react'; import videojs from 'video.js' import axios from 'axios'; import config from '../../server/config/default'; export default class VideoPlayer extends React.Component {    constructor(props) {        super(props);        this.state = {            stream: false,            videoJsOptions: null              componentDidMount() {        axios.get('/user', {            params: {                username: this.props.match.params.username                   }).then(res => {            this.setState({                stream: true,                videoJsOptions: {                    autoplay: false,                    controls: true,                    sources: [{                        src: 'http://127.0.0.1:' + config.rtmp_server.http.port + '/live/' + res.data.stream_key + '/index.m3u8',                        type: 'application/x-mpegURL'                    }],                    fluid: true,                           }, () => {                this.player = videojs(this.videoNode, this.state.videoJsOptions, function onPlayerReady() {                    console.log('onPlayerReady', this)                });            });        })       componentWillUnmount() {        if (this.player) {            this.player.dispose()              render() {        return (            <div className="row">                <div className="col-xs-12 col-sm-12 col-md-10 col-lg-8 mx-auto mt-5">                    {this.state.stream ? (                        <div data-vjs-player>                            <video ref={node => this.videoNode = node} className="video-js vjs-big-play-centered"/>                        </div>                    ) : ' Loading ... '}                </div>            </div>       } 

HLS- video.js.




,


client/components/Settings.js :

 import React from 'react'; import axios from 'axios'; export default class Navbar extends React.Component {    constructor(props){        super(props);        this.state = {            stream_key : ''        };        this.generateStreamKey = this.generateStreamKey.bind(this);       componentDidMount() {        this.getStreamKey();       generateStreamKey(e){        axios.post('/settings/stream_key')            .then(res => {                this.setState({                    stream_key : res.data.stream_key                });            })       getStreamKey(){        axios.get('/settings/stream_key')            .then(res => {                this.setState({                    stream_key : res.data.stream_key                });            })       render() {        return (            <React.Fragment>                <div className="container mt-5">                    <h4>Streaming Key</h4>                    <hr className="my-4"/>                    <div className="col-xs-12 col-sm-12 col-md-8 col-lg-6">                        <div className="row">                            <h5>{this.state.stream_key}</h5>                        </div>                        <div className="row">                            <button                                className="btn btn-dark mt-2"                                onClick={this.generateStreamKey}>                                Generate a new key                            </button>                        </div>                    </div>                </div>                <div className="container mt-5">                    <h4>How to Stream</h4>                    <hr className="my-4"/>                    <div className="col-12">                        <div className="row">                            <p>                                You can use <a target="_blank" href="https://obsproject.com/">OBS</a> or                                <a target="_blank" href="https://www.xsplit.com/">XSplit</a> to Live stream. If you're                                using OBS, go to Settings > Stream and select Custom from service dropdown. Enter                                <b>rtmp://127.0.0.1:1935/live</b> in server input field. Also, add your stream key.                                Click apply to save.                            </p>                        </div>                    </div>                </div>            </React.Fragment>       } 

passport.js, , , . /settings тАФ . XHR- <Settings/> .

. Generate a new key . XHR- . , . . тАФ GET POST /settings/stream_key . server/routes/settings.js :

 const express = require('express'),    router = express.Router(),    User = require('../database/Schema').User,    shortid = require('shortid'); router.get('/stream_key',    require('connect-ensure-login').ensureLoggedIn(),    (req, res) => {        User.findOne({email: req.user.email}, (err, user) => {            if (!err) {                res.json({                    stream_key: user.stream_key                })                   });    }); router.post('/stream_key',    require('connect-ensure-login').ensureLoggedIn(),    (req, res) => {        User.findOneAndUpdate({            email: req.user.email        }, {            stream_key: shortid.generate()        }, {            upsert: true,            new: true,        }, (err, user) => {            if (!err) {                res.json({                    stream_key: user.stream_key                })                   });    }); module.exports = router; 

shortid.

server/app.js :

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


,


<LiveStreams/> ( client/components/LiveStreams.js ) :

 render() {    let streams = this.state.live_streams.map((stream, index) => {        return (            <div className="stream col-xs-12 col-sm-12 col-md-3 col-lg-4" key={index}>                <span className="live-label">LIVE</span>                <Link to={'/stream/' + stream.username}>                    <div className="stream-thumbnail">                        <img align="center" src={'/thumbnails/' + stream.stream_key + '.png'}/>                    </div>                </Link>                <span className="username">                    <Link to={'/stream/' + stream.username}>                        {stream.username}                    </Link>                </span>            </div>        );    });    return (        <div className="container mt-5">            <h4>Live Streams</h4>            <hr className="my-4"/>            <div className="streams row">                {streams}            </div>        </div>   } 

. cron, , 5 , .

server/helpers/helpers.js :

 const spawn = require('child_process').spawn,    config = require('../config/default'),    cmd = config.rtmp_server.trans.ffmpeg; const generateStreamThumbnail = (stream_key) => {    const args = [        '-y',        '-i', 'http://127.0.0.1:8888/live/'+stream_key+'/index.m3u8',        '-ss', '00:00:01',        '-vframes', '1',        '-vf', 'scale=-2:300',        'server/thumbnails/'+stream_key+'.png',    ];    spawn(cmd, args, {        detached: true,        stdio: 'ignore'    }).unref(); }; module.exports = {    generateStreamThumbnail : generateStreamThumbnail }; 

generateStreamThumbnail .

ffmpeg-, HLS-. prePublish ( server/media_server.js ):

 nms.on('prePublish', async (id, StreamPath, args) => {    let stream_key = getStreamKeyFromStreamPath(StreamPath);    console.log('[NodeEvent on prePublish]', `id=${id} StreamPath=${StreamPath} args=${JSON.stringify(args)}`);    User.findOne({stream_key: stream_key}, (err, user) => {        if (!err) {            if (!user) {                let session = nms.getSession(id);                session.reject();            } else {                helpers.generateStreamThumbnail(stream_key);                      }); }); 

, cron ( server/cron/thumbnails.js ):

 const CronJob = require('cron').CronJob,    request = require('request'),    helpers = require('../helpers/helpers'),    config = require('../config/default'),    port = config.rtmp_server.http.port; const job = new CronJob('*/5 * * * * *', function () {    request        .get('http://127.0.0.1:' + port + '/api/streams', function (error, response, body) {            let streams = JSON.parse(body);            if (typeof (streams['live'] !== undefined)) {                let live_streams = streams['live'];                for (let stream in live_streams) {                    if (!live_streams.hasOwnProperty(stream)) continue;                    helpers.generateStreamThumbnail(stream);                                  }); }, null, true); module.exports = job; 

5 . API NMS . server/app.js :

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

рдкрд░рд┐рдгрд╛рдо


, . , . - тАФ . - тАФ .

.

! , , ?

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


All Articles