Penulis materi, terjemahan yang kami terbitkan hari ini, mengatakan bahwa ia sedang mengerjakan aplikasi yang memungkinkan Anda untuk mengatur siaran streaming (streaming) dari apa yang terjadi di desktop pengguna.

Aplikasi menerima aliran dalam format RTMP dari streamer dan mengubahnya menjadi aliran HLS, yang dapat diputar di browser pemirsa. Artikel ini akan berbicara tentang bagaimana Anda dapat membuat aplikasi streaming Anda sendiri menggunakan Node.js dan Bereaksi. Jika Anda terbiasa melihat ide yang menarik bagi Anda, segera libatkan diri Anda dalam kode, Anda sekarang dapat melihat ke repositori
ini .
Pengembangan server web dengan sistem otentikasi dasar
Mari kita membuat server web sederhana berdasarkan Node.js, di mana, menggunakan perpustakaan passport.js, strategi otentikasi pengguna lokal diimplementasikan. Kami akan menggunakan MongoDB sebagai penyimpanan informasi permanen. Kami akan bekerja dengan database menggunakan pustaka Mongoose ODM.
Inisialisasi proyek baru:
$ npm init
Instal dependensi:
$ 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
Di direktori proyek, buat dua folder -
client
dan
server
. Kode frontend berdasarkan Bereaksi akan berakhir di folder
client
, dan kode backend akan disimpan di folder
server
. Sekarang kami bekerja di folder
server
. Yaitu, kami akan menggunakan passport.js untuk membuat sistem otentikasi. Kami telah memasang paspor dan modul paspor-lokal. Sebelum kami menjelaskan strategi otentikasi pengguna lokal, buat file
app.js
dan tambahkan kode yang diperlukan untuk memulai server sederhana. Jika Anda akan menjalankan kode ini sendiri, pastikan Anda telah menginstal DBMS MongoDB dan menjalankannya sebagai layanan.
Berikut adalah kode untuk file yang ada dalam proyek di
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}!`));
Kami mengunduh semua middleware yang diperlukan untuk aplikasi, yang terhubung ke MongoDB, mengonfigurasi sesi ekspres untuk menggunakan penyimpanan file. Menyimpan sesi akan memungkinkan mereka untuk dipulihkan setelah server reboot.
Sekarang kami menjelaskan strategi passport.js untuk mengatur pendaftaran dan otentikasi pengguna. Buat folder
auth
di folder
server
dan tempatkan file
passport.js
di dalamnya. Inilah yang seharusnya ada di file
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); });
Selain itu, kita perlu menggambarkan skema untuk model pengguna (itu akan disebut
UserSchema
). Buat folder
database
di folder
server
, dan di dalamnya file
UserSchema.js
.
Berikut adalah kode untuk file
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
memiliki tiga metode. Metode
generateHash
dirancang untuk mengubah kata sandi, disajikan dalam bentuk teks biasa, menjadi hash bcrypt. Kami menggunakan metode ini dalam strategi paspor untuk mengubah kata sandi yang dimasukkan oleh pengguna menjadi hash bcrypt. Hash kata sandi yang diterima kemudian disimpan dalam database. Metode
validPassword
menerima kata sandi yang dimasukkan oleh pengguna dan memverifikasinya dengan membandingkan hash-nya dengan hash yang disimpan dalam database. Metode
generateStreamKey
string unik yang akan kami transfer ke pengguna sebagai kunci streaming (kunci aliran) mereka untuk klien RTMP.
Berikut adalah kode file
server/database/Schema.js
:
let mongoose = require('mongoose'); exports.User = mongoose.model('User', require('./UserSchema'));
Sekarang kita telah menetapkan strategi paspor, menjelaskan skema
UserSchema
dan membuat model berdasarkan itu, mari kita inisialisasi paspor di
app.js
Berikut adalah kode untuk melengkapi file
server/app.js
:
Selain itu, rute baru harus didaftarkan di
app.js
Untuk melakukan ini, tambahkan kode berikut ke
server/app.js
:
Buat file
login.js
dan
register.js
di folder
routes
terletak di folder
server
. Dalam file-file ini kami mendefinisikan beberapa rute di atas dan menggunakan middleware paspor untuk mengatur registrasi dan otentikasi pengguna.
Berikut adalah kode untuk file
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;
Berikut adalah kode untuk file
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;
Kami menggunakan mesin templat ejs. Tambahkan file
login.ejs
dan
register.ejs
ke folder
views
, yang terletak di folder
server
.
Berikut ini isi file
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>
Inilah yang seharusnya ada di file
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>
Kami dapat mengatakan kami telah selesai bekerja pada sistem otentikasi. Sekarang kita akan mulai membuat bagian selanjutnya dari proyek dan mengkonfigurasi server RTMP.
Konfigurasikan Server RTMP
RTMP (Real-Time Messaging Protocol) adalah protokol yang dikembangkan untuk transmisi video, audio, dan berbagai data antara tape drive dan server yang berkinerja tinggi. Kedutan, Facebook, YouTube, dan banyak situs streaming lainnya menerima aliran RTMP dan mentranskodenya menjadi aliran HTTP (format HLS) sebelum mentransfer aliran ini ke CDN mereka untuk memastikan ketersediaannya yang tinggi.
Kami menggunakan modul node-media-server - Node.js-implementasi server media RTMP. Server media ini menerima stream RTMP dan mengubahnya menjadi HLS / DASH menggunakan kerangka kerja ffmpeg multimedia. Agar proyek berhasil, ffmpeg harus diinstal pada sistem Anda. Jika Anda bekerja di Linux dan Anda sudah menginstal ffmpeg, Anda bisa mengetahui path ke sana dengan menjalankan perintah berikut dari terminal:
$ which ffmpeg # /usr/bin/ffmpeg
Untuk bekerja dengan paket node-media-server, ffmpeg versi 4.x direkomendasikan. Anda dapat memeriksa versi ffmpeg yang diinstal seperti ini:
$ 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)
Jika Anda belum menginstal ffmpeg dan menjalankan Ubuntu, Anda dapat menginstal kerangka kerja ini dengan menjalankan perintah berikut:
# PPA-. PPA, # ffmpeg 3.x. $ sudo add-apt-repository ppa:jonathonf/ffmpeg-4 $ sudo apt install ffmpeg
Jika Anda bekerja pada Windows, Anda dapat mengunduh ffmpeg
build untuk Windows.
Tambahkan file konfigurasi
server/config/default.js
ke proyek:
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;
Ganti nilai properti
ffmpeg
dengan path tempat ffmpeg diinstal pada sistem Anda. Jika Anda bekerja pada Windows dan mengunduh ffmpeg rakitan Windows dari tautan di atas - jangan lupa untuk menambahkan ekstensi
.exe
ke nama file. Maka fragmen yang sesuai dari kode di atas akan terlihat seperti ini:
const config = { .... trans: { ffmpeg: 'D:/ffmpeg/bin/ffmpeg.exe', ... };
Sekarang instal node-media-server dengan menjalankan perintah berikut:
$ npm install node-media-server --save
Buat file
media_server.js
di folder
server
.
Berikut adalah kode untuk dimasukkan ke
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;
Menggunakan objek
NodeMediaService
cukup sederhana. Ini menyediakan server RTMP dan memungkinkan Anda untuk menunggu koneksi. Jika kunci streaming tidak valid, koneksi yang masuk dapat ditolak. Kami akan menangani acara objek
prePublish
ini. Di bagian berikutnya, kami menambahkan kode tambahan ke penutupan pendengar acara
prePublish
-
prePublish
. Ini akan memungkinkan Anda untuk menolak koneksi masuk dengan kunci streaming yang tidak valid. Sementara itu, kami akan menerima semua koneksi masuk yang datang ke port RTMP default (1935). Kita hanya perlu mengimpor objek
app.js
dalam
node_media_server
dan memanggil metode
run
.
Tambahkan kode berikut ke
server/app.js
:
Unduh dan instal
OBS (Open Broadcaster Software). Buka jendela pengaturan aplikasi dan buka bagian
Stream
. Pilih
Custom
di bidang
Service
dan masukkan
rtmp://127.0.0.1:1935/live
di bidang
Server
. Bidang
Stream Key
dapat dibiarkan kosong. Jika program tidak memungkinkan Anda untuk menyimpan pengaturan tanpa mengisi bidang ini, Anda dapat memasukkan serangkaian karakter yang sewenang-wenang di dalamnya. Klik pada tombol
Apply
dan pada tombol
OK
. Klik tombol
Start Streaming
untuk mulai mentransfer aliran RTMP Anda ke server lokal Anda sendiri.
Konfigurasikan OBSPergi ke terminal dan lihat apa yang ditampilkan server media di sana. Anda akan melihat informasi di sana tentang aliran masuk dan log dari beberapa pendengar acara.
Output data ke terminal oleh server media berdasarkan Node.jsServer media memberikan akses ke API, yang memungkinkan Anda untuk mendapatkan daftar klien yang terhubung. Untuk melihat daftar ini, Anda dapat membuka browser di
http://127.0.0.1:8888/api/streams
. Nantinya, kami akan menggunakan API ini dalam aplikasi Bereaksi untuk menampilkan daftar pengguna siaran. Inilah yang dapat Anda lihat dengan mengakses API ini:
{ "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" }
Sekarang backend hampir siap. Ini adalah server streaming yang berfungsi yang mendukung teknologi HTTP, RTMP dan HLS. Namun, kami belum membuat sistem untuk memeriksa koneksi RTMP yang masuk. Itu harus memungkinkan kami untuk memastikan bahwa server menerima aliran hanya dari pengguna yang diautentikasi. Tambahkan kode berikut ke
prePublish
event
prePublish
di file
server/media_server.js
:
Pada penutupan, kami meminta basis data untuk menemukan pengguna dengan kunci streaming. Jika kunci milik pengguna, kami hanya mengizinkan pengguna untuk terhubung ke server dan menerbitkan siaran mereka. Kalau tidak, kami menolak koneksi RTMP yang masuk.
Di bagian selanjutnya, kita akan membuat aplikasi sisi klien sederhana berdasarkan React. Hal ini diperlukan untuk memungkinkan pemirsa menonton siaran streaming, serta untuk memungkinkan streamer menghasilkan dan melihat kunci streaming mereka.
Streaming langsung
Sekarang buka folder
clients
. Karena kita akan membuat aplikasi Bereaksi, kita akan memerlukan paket web. Kami juga membutuhkan pemuat, yang digunakan untuk menerjemahkan kode JSX ke dalam kode JavaScript yang dimengerti oleh browser. Pasang modul-modul berikut:
$ 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
Tambahkan ke proyek, di direktori root, file konfigurasi untuk 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 }, };
Tambahkan file
client/index.js
ke proyek:
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') ); }
Berikut ini isi dari file
client/index.scss
:
@import '~bootstrap/dist/css/bootstrap.css'; @import '~video.js/dist/video-js.css'; @import url('https://fonts.googleapis.com/css?family=Dosis'); html,body{ font-family: 'Dosis', sans-serif; }
React router digunakan untuk routing. Di frontend, kami juga menggunakan bootstrap, dan, untuk siaran, video.js. Sekarang tambahkan folder
components
ke folder
client
, dan file
Root.js
ke
Root.js
. Berikut ini isi dari file
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> }
Komponen
<Root/>
merender komponen
<Router/>
React yang berisi tiga subkomponen
<Route/>
. Komponen
<LiveStreams/>
menampilkan daftar siaran. Komponen
<VideoPlayer/>
bertanggung jawab untuk menampilkan pemutar video.js. Komponen
<Settings/>
bertanggung jawab untuk membuat antarmuka untuk bekerja dengan kunci streaming.
Buat
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> }
Seperti inilah tampilan halaman aplikasi.
Layanan streaming frontendSetelah memasang komponen
<LiveStreams/>
, API NMS dipanggil untuk mendapatkan daftar klien yang terhubung ke sistem. API NMS tidak memberikan banyak informasi tentang pengguna. Secara khusus, kami dapat memperoleh informasi tentang kunci streaming darinya, di mana pengguna terhubung ke server RTMP. Kami akan menggunakan kunci-kunci ini ketika membuat kueri ke basis data untuk mendapatkan informasi tentang akun pengguna.
Dalam metode
getStreamsInfo
kami menjalankan permintaan XHR ke
/streams/info
, tetapi kami belum membuat sesuatu yang dapat menanggapi permintaan ini. Buat file
server/routes/streams.js
dengan konten berikut:
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;
Kami meneruskan informasi aliran yang dikembalikan oleh NMS API ke backend, untuk mendapatkan informasi tentang klien yang terhubung.
Kami melakukan kueri basis data untuk mendapatkan daftar pengguna yang kunci streaming-nya cocok dengan yang kami terima dari NMS API. Kami mengembalikan daftar dalam format JSON. Kami mendaftarkan rute dalam file
server/app.js
:
app.use('/streams', require('./routes/streams'));
Hasilnya, kami menampilkan daftar siaran aktif. Daftar ini berisi nama pengguna dan thumbnail. Kami akan berbicara tentang cara membuat thumbnail untuk siaran di akhir artikel. Gambar kecil diikat ke halaman tertentu tempat streaming HLS diputar menggunakan video.js.
Buat
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
:
Ringkasan
, . , . - β
. - β .
.
! , , ?