主要應用Node.js 實現的RTSP(結合ffmpeg)/RTMP/HTTP/WebSocket/HLS/DASH流媒體服務器html
簡單運行方式前端
npm i node app.js
多核模式運行vue
node cluster.js
mkdir nms cd nms npm install node-media-server
const { NodeMediaServer } = require('node-media-server'); const config = { rtmp: { port: 1935, chunk_size: 60000, gop_cache: true, ping: 60, ping_timeout: 30 }, http: { port: 8000, allow_origin: '*' } }; var nms = new NodeMediaServer(config) nms.run();
mkdir nms cd nms npm install node-media-server
const { NodeMediaCluster } = require('node-media-server'); const numCPUs = require('os').cpus().length; const config = { rtmp: { port: 1935, chunk_size: 60000, gop_cache: true, ping: 60, ping_timeout: 30 }, http: { port: 8000, allow_origin: '*' }, cluster: { num: numCPUs } }; var nmcs = new NodeMediaCluster(config) nmcs.run();
rtmp://hostname:port/appname/stream?sign=expires-HashValue
http://hostname:port/appname/stream.flv?sign=expires-HashValue
ws://hostname:port/appname/stream.flv?sign=expires-HashValuejava
1.原始推流或播放地址:node
rtmp://192.168.0.10/live/streamgit
2.配置驗證祕鑰爲: 'nodemedia2017privatekey',同時打開播放和發佈的鑑權開關github
const config = { rtmp: { port: 1935, chunk_size: 60000, gop_cache: true, ping: 60, ping_timeout: 30 }, http: { port: 8000, allow_origin: '*' }, auth: { play: true, publish: true, secret: 'nodemedia2017privatekey' } }
3.請求過時時間爲: 2017/8/23 11:25:21 ,則請求過時時間戳爲:web
1503458721docker
4.md5計算結合「完整流地址-失效時間-密鑰」的字符串:npm
HashValue = md5("/live/stream-1503458721-nodemedia2017privatekey」)
HashValue = 80c1d1ad2e0c2ab63eebb50eed64201a
5.最終請求地址爲
rtmp://192.168.0.10/live/stream?sign=1503458721-80c1d1ad2e0c2ab63eebb50eed64201a
注意:'sign' 關鍵字不能修改成其餘的
H.265並無在Adobe的官方規範裏實現,這裏使用id 12做爲標識,也是國內絕大多數雲服務商使用的id號
PC轉碼推流: ffmpeg-hw-win32
純JavaScrip 直播播放器: NodePlayer.js
...... nms.run(); nms.on('preConnect', (id, args) => { console.log('[NodeEvent on preConnect]', `id=${id} args=${JSON.stringify(args)}`); // let session = nms.getSession(id); // session.reject(); }); nms.on('postConnect', (id, args) => { console.log('[NodeEvent on postConnect]', `id=${id} args=${JSON.stringify(args)}`); }); nms.on('doneConnect', (id, args) => { console.log('[NodeEvent on doneConnect]', `id=${id} args=${JSON.stringify(args)}`); }); nms.on('prePublish', (id, StreamPath, args) => { console.log('[NodeEvent on prePublish]', `id=${id} StreamPath=${StreamPath} args=${JSON.stringify(args)}`); // let session = nms.getSession(id); // session.reject(); }); nms.on('postPublish', (id, StreamPath, args) => { console.log('[NodeEvent on postPublish]', `id=${id} StreamPath=${StreamPath} args=${JSON.stringify(args)}`); }); nms.on('donePublish', (id, StreamPath, args) => { console.log('[NodeEvent on donePublish]', `id=${id} StreamPath=${StreamPath} args=${JSON.stringify(args)}`); }); nms.on('prePlay', (id, StreamPath, args) => { console.log('[NodeEvent on prePlay]', `id=${id} StreamPath=${StreamPath} args=${JSON.stringify(args)}`); // let session = nms.getSession(id); // session.reject(); }); nms.on('postPlay', (id, StreamPath, args) => { console.log('[NodeEvent on postPlay]', `id=${id} StreamPath=${StreamPath} args=${JSON.stringify(args)}`); }); nms.on('donePlay', (id, StreamPath, args) => { console.log('[NodeEvent on donePlay]', `id=${id} StreamPath=${StreamPath} args=${JSON.stringify(args)}`); });
openssl genrsa -out privatekey.pem 1024 openssl req -new -key privatekey.pem -out certrequest.csr openssl x509 -req -in certrequest.csr -signkey privatekey.pem -out certificate.pem
const { NodeMediaServer } = require('node-media-server'); const config = { rtmp: { port: 1935, chunk_size: 60000, gop_cache: true, ping: 60, ping_timeout: 30 }, http: { port: 8000, allow_origin: '*' }, https: { port: 8443, key:'./privatekey.pem', cert:'./certificate.pem', } }; var nms = new NodeMediaServer(config) nms.run();
https://localhost:8443/live/STREAM_NAME.flv wss://localhost:8443/live/STREAM_NAME.flv
注意:Web瀏覽器播放自簽名的證書需先添加信任才能訪問
const config = { ....... auth: { api : true, api_user: 'admin', api_pass: 'nms2018', }, ...... }
注意:基於Basic auth提供驗證,請注意修改密碼,默認並未開啓。
http://localhost:8000/api/server
{ "os": { "arch": "x64", "platform": "darwin", "release": "16.7.0" }, "cpu": { "num": 8, "load": 12, "model": "Intel(R) Core(TM) i7-4790 CPU @ 3.60GHz", "speed": 3592 }, "mem": { "totle": 8589934592, "free": 754126848 }, "net": { "inbytes": 6402345, "outbytes": 6901489 }, "nodejs": { "uptime": 109, "version": "v8.9.0", "mem": { "rss": 59998208, "heapTotal": 23478272, "heapUsed": 15818096, "external": 3556366 } }, "clients": { "accepted": 207, "active": 204, "idle": 0, "rtmp": 203, "http": 1, "ws": 0 } }
http://localhost:8000/api/streams
{ "live": { "s": { "publisher": { "app": "live", "stream": "s", "clientId": "U3UYQ02P", "connectCreated": "2017-12-21T02:29:13.594Z", "bytes": 190279524, "ip": "::1", "audio": { "codec": "AAC", "profile": "LC", "samplerate": 48000, "channels": 6 }, "video": { "codec": "H264", "width": 1920, "height": 1080, "profile": "Main", "level": 4.1, "fps": 24 } }, "subscribers": [ { "app": "live", "stream": "s", "clientId": "H227P4IR", "connectCreated": "2017-12-21T02:31:35.278Z", "bytes": 18591846, "ip": "::ffff:127.0.0.1", "protocol": "http" }, { "app": "live", "stream": "s", "clientId": "ZNULPE9K", "connectCreated": "2017-12-21T02:31:45.394Z", "bytes": 8744478, "ip": "::ffff:127.0.0.1", "protocol": "ws" }, { "app": "live", "stream": "s", "clientId": "C5G8NJ30", "connectCreated": "2017-12-21T02:31:51.736Z", "bytes": 2046073, "ip": "::ffff:192.168.0.91", "protocol": "rtmp" } ] }, "stream": { "publisher": null, "subscribers": [ { "app": "live", "stream": "stream", "clientId": "KBH4PCWB", "connectCreated": "2017-12-21T02:31:30.245Z", "bytes": 0, "ip": "::ffff:127.0.0.1", "protocol": "http" } ] } } }
const { NodeMediaServer } = require('node-media-server'); const config = { rtmp: { port: 1935, chunk_size: 60000, gop_cache: true, ping: 60, ping_timeout: 30 }, http: { port: 8000, mediaroot: './media', allow_origin: '*' }, trans: { ffmpeg: '/usr/local/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]' } ] } }; var nms = new NodeMediaServer(config) nms.run();
const { NodeMediaServer } = require('node-media-server'); const config = { rtmp: { port: 1935, chunk_size: 60000, gop_cache: true, ping: 60, ping_timeout: 30 }, http: { port: 8000, mediaroot: './media', allow_origin: '*' }, trans: { ffmpeg: '/usr/local/bin/ffmpeg', tasks: [ { app: 'vod', mp4: true, mp4Flags: '[movflags=faststart]', } ] } }; var nms = new NodeMediaServer(config) nms.run();
NodeMediaServer 使用ffmpeg實現RTMP/RTSP的中繼服務。(咱們主要應用這裏將rtsp轉爲rtmp)
靜態拉流模式在服務啓動時執行,當發生錯誤時自動重連。能夠是一個直播流,也能夠是一個本地文件。理論上並不限制是RTSP或RTMP協議
relay: { ffmpeg: '/usr/local/bin/ffmpeg', tasks: [ { app: 'cctv', mode: 'static', edge: 'rtsp://184.72.239.149/vod/mp4:BigBuckBunny_175k.mov',//rtsp name: 'BigBuckBunny' rtsp_transport : 'tcp' //['udp', 'tcp', 'udp_multicast', 'http'] }, { app: 'iptv', mode: 'static', edge: 'rtmp://live.hkstv.hk.lxdns.com/live/hks',//rtmp name: 'hks' }, { app: 'mv', mode: 'static', edge: '/Volumes/ExtData/Movies/Dancing.Queen-SD.mp4',//本地文件 name: 'dq' } ] }
當本地服務器收到一個播放請求,若是這個流不存在,則從配置的邊緣服務器拉取這個流。當沒有客戶端播放這個流時,自動斷開。
relay: { ffmpeg: '/usr/local/bin/ffmpeg', tasks: [ { app: 'live', mode: 'pull', edge: 'rtmp://192.168.0.20', } ] }
當本地服務器收到一個發佈請求,自動將這個流推送到邊緣服務器。
relay: { ffmpeg: '/usr/local/bin/ffmpeg', tasks: [ { app: 'live', mode: 'push', edge: 'rtmp://192.168.0.10', } ] }
# 進入/usr/local/src目錄安裝yasm cd /usr/local/src wget http://www.tortall.net/projects/yasm/releases/yasm-1.3.0.tar.gz tar zxvf yasm-1.3.0.tar.gz cd yasm-1.3.0/ ./configure make && make install # 進入/usr/local/src目錄安裝 cd /usr/local/src wget https://ffmpeg.org/releases/ffmpeg-4.0.tar.bz2 tar jxvf ffmpeg-4.0.tar.bz2 cd ffmpeg-4.0/ ./configure make && make install # 查看是否安裝成功 cd /usr/local/bin ll (存在ffmpeg可執行文件)
# 克隆工程到本地/usr/local/src目錄 cd /usr/local/src git clone git@github.com:qq1126176532/Node-Media-Server.git # 開始安裝npm curl --silent --location https://rpm.nodesource.com/setup_10.x | bash - yum install -y nodejs npm install -g cnpm --registry=https://registry.npm.taobao.org # 查看版本確認安裝成功 npm -v #執行安裝 cd Node-Media-Server/ npm i #修改啓動腳本(這裏以簡單工做模式啓動爲例,多核心集羣模式結合中繼模式存在一些問題) vim app.js: --- const { NodeMediaServer } = require('./index'); const config = { rtmp: { port: 1935, chunk_size: 60000, gop_cache: true, ping: 60, ping_timeout: 30 }, http: { port: 8000, webroot: './public', mediaroot: './media', allow_origin: '*' }, https: { port: 8443, key: './privatekey.pem', cert: './certificate.pem', }, auth: { api: true, api_user: 'admin', api_pass: 'admin', play: false, publish: false, secret: 'nodemedia2017privatekey' }, //引入中繼模式任務 relay: { //指定ffmpeg可執行文件位置 ffmpeg: '/usr/local/bin/ffmpeg', tasks: [ { //應用名稱 app: 'iptv', //工做模式 靜態便可 mode: 'static', //中繼地址 edge: 'rtsp://184.72.239.149/vod/mp4:BigBuckBunny_175k.mov', //訪問資源名稱 name: 'rtsp', //傳輸協議 rtsp_transport : 'tcp' //['udp', 'tcp', 'udp_multicast', 'http'] } ] }, }; let nms = new NodeMediaServer(config) nms.run(); nms.on('preConnect', (id, args) => { console.log('[NodeEvent on preConnect]', `id=${id} args=${JSON.stringify(args)}`); // let session = nms.getSession(id); // session.reject(); }); nms.on('postConnect', (id, args) => { console.log('[NodeEvent on postConnect]', `id=${id} args=${JSON.stringify(args)}`); }); nms.on('doneConnect', (id, args) => { console.log('[NodeEvent on doneConnect]', `id=${id} args=${JSON.stringify(args)}`); }); nms.on('prePublish', (id, StreamPath, args) => { console.log('[NodeEvent on prePublish]', `id=${id} StreamPath=${StreamPath} args=${JSON.stringify(args)}`); // let session = nms.getSession(id); // session.reject(); }); nms.on('postPublish', (id, StreamPath, args) => { console.log('[NodeEvent on postPublish]', `id=${id} StreamPath=${StreamPath} args=${JSON.stringify(args)}`); }); nms.on('donePublish', (id, StreamPath, args) => { console.log('[NodeEvent on donePublish]', `id=${id} StreamPath=${StreamPath} args=${JSON.stringify(args)}`); }); nms.on('prePlay', (id, StreamPath, args) => { console.log('[NodeEvent on prePlay]', `id=${id} StreamPath=${StreamPath} args=${JSON.stringify(args)}`); // let session = nms.getSession(id); // session.reject(); }); nms.on('postPlay', (id, StreamPath, args) => { console.log('[NodeEvent on postPlay]', `id=${id} StreamPath=${StreamPath} args=${JSON.stringify(args)}`); }); nms.on('donePlay', (id, StreamPath, args) => { console.log('[NodeEvent on donePlay]', `id=${id} StreamPath=${StreamPath} args=${JSON.stringify(args)}`); }); --- # 保存退出 # 啓動服務 nohup node app.js & # 查看服務是否啓動成功 ps -ef | grep "app.js" [root@localhost Node-Media-Server]# ps -ef | grep "app.js" root 37745 127550 7 10:31 pts/1 00:00:00 node app.js tail -f nohup.out(查看啓動日誌) # 三個所用(1935 8000 8443)端口暴露 firewall-cmd --zone=public --add-port=1935/tcp --permanent firewall-cmd --zone=public --add-port=8000/tcp --permanent firewall-cmd --zone=public --add-port=8443/tcp --permanent #(關閉端口 若是開錯了備用還原) firewall-cmd --zone=public --remove-port=8080/tcp --permanent # 從新加載配置 firewall-cmd --reload # 重啓防火牆 systemctl restart firewalld.service # 查看全部開放端口 firewall-cmd --zone=public --list-ports
感謝以下連接提供文章資源:
前期準備和思考: