主要应用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
感谢以下连接提供文章资源:
前期准备和思考: