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

рдПрдкреНрд▓рд┐рдХреЗрд╢рди рд╕реНрдЯреНрд░реАрдорд░ рд╕реЗ 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); });
рдЗрд╕рдХреЗ рдЕрд▓рд╛рд╡рд╛, рд╣рдореЗрдВ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдореЙрдбрд▓ рдХреЗ рд▓рд┐рдП рд╕реНрдХреАрдорд╛ рдХрд╛ рд╡рд░реНрдгрди рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ (рдЗрд╕реЗ
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
рдлрд╝рд╛рдЗрд▓ рдХреЛ рдкреВрд░рдХ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдХреЛрдб рд╣реИ:
рдЗрд╕рдХреЗ рдЕрд▓рд╛рд╡рд╛, рдирдП рдорд╛рд░реНрдЧреЛрдВ рдХреЛ
app.js
рдореЗрдВ рдкрдВрдЬреАрдХреГрдд рд╣реЛрдирд╛ рдЪрд╛рд╣рд┐рдП рдРрд╕рд╛ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рдирд┐рдореНрди рдХреЛрдб рдХреЛ
server/app.js
:
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
рдХреЗ рд▓рд┐рдП рдирд┐рдореНрди рдХреЛрдб рдЬреЛрдбрд╝реЗрдВ:
рдУрдмреАрдПрд╕ (рдУрдкрди рдмреНрд░реЙрдбрдХрд╛рд╕реНрдЯрд░ рд╕реЙрдлреНрдЯрд╡реЗрдпрд░) рдбрд╛рдЙрдирд▓реЛрдб рдФрд░ рдЗрдВрд╕реНрдЯреЙрд▓ рдХрд░реЗрдВред рдПрдкреНрд▓рд┐рдХреЗрд╢рди рд╕реЗрдЯрд┐рдВрдЧ рд╡рд┐рдВрдбреЛ рдЦреЛрд▓реЗрдВ рдФрд░
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
рдореЗрдВ рдирд┐рдореНрди рдХреЛрдб рдЬреЛрдбрд╝реЗрдВ:
рдХреНрд▓реЛрдЬрд░ рдореЗрдВ, рд╣рдо рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХреЛ рд╕реНрдЯреНрд░реАрдорд┐рдВрдЧ рдХреБрдВрдЬреА рдХреЗ рд╕рд╛рде рдЦреЛрдЬрдиреЗ рдХреЗ рд▓рд┐рдП рдбреЗрдЯрд╛рдмреЗрд╕ рдХреА рдХреНрд╡реЗрд░реА рдХрд░рддреЗ рд╣реИрдВред рдпрджрд┐ рдХреБрдВрдЬреА рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХреА рд╣реИ, рддреЛ рд╣рдо рдХреЗрд╡рд▓ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХреЛ рд╕рд░реНрд╡рд░ рд╕реЗ рдХрдиреЗрдХреНрдЯ рдХрд░рдиреЗ рдФрд░ рдЙрдирдХреЗ рдкреНрд░рд╕рд╛рд░рдг рдХреЛ рдкреНрд░рдХрд╛рд╢рд┐рдд рдХрд░рдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рджреЗрддреЗ рд╣реИрдВред рдЕрдиреНрдпрдерд╛, рд╣рдо рдЖрдиреЗ рд╡рд╛рд▓реЗ 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
:
рдкрд░рд┐рдгрд╛рдо
, . , . - тАФ
. - тАФ .
.
! , , ?