Node-Media-Server (相对稳定可用性高)

主要应用Node.js 实现的RTSP(结合ffmpeg)/RTMP/HTTP/WebSocket/HLS/DASH流媒体服务器

特性

  • 跨平台支持 Windows/Linux/Unix
  • 支持的音视频编码 H.264/H.265/AAC/SPEEX/NELLYMOSER
  • 支持缓存最近一个关键帧间隔数据,实现RTMP协议秒开
  • 支持事件回调
  • 支持https/wss加密传输
  • 支持服务器和流媒体信息统计
  • 支持RTMP直播流转HLS,DASH直播流
  • 支持RTMP直播流录制为MP4文件并开启faststart
  • 支持RTMP/RTSP中继(关键)
  • 支持多核集群模式
  • 支持录制为MP4回放
  • 支持实时转码(关键)
  • 支持低延迟HLS/DASH
  • 支持on_connect/on_publish/on_play/on_done 事件回调

用法

git 版本

简单运行方式

npm i
node app.js

多核模式运行

node cluster.js

npm 版本(推荐)

单核模式

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();

鉴权验证(更安全)

加密后的 URL 形式:

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

1.原始推流或播放地址:

rtmp://192.168.0.10/live/stream

2.配置验证秘钥为: 'nodemedia2017privatekey',同时打开播放和发布的鉴权开关

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 ,则请求过期时间戳为:

1503458721

4.md5计算结合“完整流地址-失效时间-密钥”的字符串:

HashValue = md5("/live/stream-1503458721-nodemedia2017privatekey”)

HashValue = 80c1d1ad2e0c2ab63eebb50eed64201a

5.最终请求地址为

rtmp://192.168.0.10/live/stream?sign=1503458721-80c1d1ad2e0c2ab63eebb50eed64201a

注意:'sign' 关键字不能修改为其他的

RTMP协议传输H.265视频

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

Https/Wss 视频加密传输

生成证书

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

配置 https支持

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浏览器播放自签名的证书需先添加信任才能访问

API

保护API

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

转 HLS/DASH 直播流

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();

直播录制为MP4文件

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();

Rtsp/Rtmp 中继

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',
}
]
}

安装应用流程

安装ffmpeg(版本要求4.0.0以上)

# 进入/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可执行文件)

安装Node-Media-Server

# 克隆工程到本地/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

客户端

引用资料及思考:

感谢如下链接提供文章资源

前期准备和思考

  • 1.之前没做高这方面的内容,起始渡鸟狗哥都找不到帮助太多的有价值的资料。有时候java搞起来不方便也可以倾向于考虑一下其他脚本语言或者方式的实现。
  • 2.ws-socket:此方案最终摒弃原因如下:需要对接外网收费服务,免费服务不稳定,需要定期更新密钥,连接数有限。docker服务地址官网
  • 3.Node-Media-Server方法切合实际需求,可离线内网使用,画面问题需后期持续优化。
  • 4.应用Node.js基于Chrome V8 引擎的 JavaScript 运行环境,使用事件驱动、非阻塞式 I/O 的模型轻量高效。
  • 5.服务稳定,支持多协议方便后期扩展,服务单一避免冗余和服务带来的代码臃肿,rtsp推流方式多样,提供加密传输机制更加安全。

Node-Media-Server的更多相关文章

  1. 资料整理:基于node push server实现push notification

    chat example based on ionic/ socket.io/ redis https://github.com/jbavari/ionic-socket.io-redis-chat ...

  2. Adobe Flash Media Server安装

    Flash Media Server(FMS)是一个流媒体服务器 使用 实时消息传送协议(RTMP),RTMP是一种未加密的TCP/IP协议,专门设计用来高速传送音频.视频和数据信息. 3.5版32位 ...

  3. Windows Server 2003从入门到精通之Windows Media Server流媒体服务器架建[转]

    今天我们来做一个windows media server流媒体格式文件的流媒体服务器. 现在市面上能够买到的一些电影文件有 rm格式和wmv格式.还有一些是DivX技术的avi格式,要想让你的服务器对 ...

  4. android media server 解析1-media player service 结构部分

    下面为media server注册的四个服务之一:MediaPlayerService的结构图 1.图中没有MediaPlayerService的代理对象BpMediaPlayerService部分, ...

  5. mac media server

    近日在mac osx基于开源组件nginx-rtmp-module架设了一台默认的media server,以下是过程笔记 下载https://github.com/arut/nginx-rtmp-m ...

  6. Flash Media Server 5.0 (FMS)注册码

    flash media server 4.5 及最新 flash media server 5.0 注册码 防止图片打不开时: Name:tam/CORE Serial:1652-5580-8001- ...

  7. Adobe/Flash Media Server 5.0 linux 64位系统下的安装

    一.下载 Adobe/Flash MS5.0下载地址: http://fs1.d-h.st/download/00036/VOt/adobemediaserver_5_ls1_linux64.tar. ...

  8. 使用Flash Media Server(FMS)录制mp4格式的视频

    最近在做一个有关视频直播和点播的项目,客户的一个需求就是可以控制对直播流的录制,直播的实现采用的是Adobe的Flash Media Server,具体方式就是:视频采集端采集视频并编码->rt ...

  9. windows media server 组件安装后流媒体服务器启动失败

    做好的web应用,去客户现场部署的时候发现流媒体服务器不能启动.(现场服务器系统为windows server2008 R2) 自己测试的时候搭建环境没什么问题.从来没有遇到安装windows med ...

  10. (转)ubuntu 12.04搭建Adobe Flash Media Server服务

    破解版传送门:http://fms45.cuplayer.com/fms4download.html 福利:1462-5247-1705-7678-8379-5590 下载解压 cd进目录,./ins ...

随机推荐

  1. js如何打印对象

    js调试中经常会碰到输出的内容是对象而无法打印的时候,光靠alert只能打印出object标示,却不能打印出来里面的内容,甚是不方便,于是各方面整理总结了如下一个函数,能够将数组或者对象这类的结果一一 ...

  2. Code signing is required for product type 'Application' in SDK 'iOS 11.2'

    在打包的时候出现这样一个错误,Code signing is required for product type 'Application' in SDK 'iOS 11.2'  ,就是说代码签名证书 ...

  3. 通过交换a,b 中的元素,使[序列a 元素的和]与[序列b 元素的和]之间的差最小

    题目描述: 有两个序列a,b,大小都为n,序列元素的值任意整数,无序:要求:通过交换a,b 中的元素,使[序列a 元素的和]与[序列b 元素的和]之间的差最小.例如:var a=[100,99,98, ...

  4. ASP.NET MVC4在View中调用当前Controller中的方法

    调用当前Controller中的方法 @{ ((HomeController)ViewContext.Controller).Method1(); } 调用静态方法 @{ SomeClass.Meth ...

  5. centos7命令1

    ls  查看当前路径下的文件或文件夹 pwd 查看当前路径,例如/home/python   表示根目录下的home文件夹下的python文件夹 clear清空屏幕 /斜杠 \反斜杠 |竖杠 _下划线 ...

  6. pycharm激活(转)

    1.注册码激活Activation code http://idea.lanyus.com/ 2.服务器激活License server(推荐) 或者http://182.254.242.193:10 ...

  7. java jdom 解析CDATA内容

    package com; import java.io.IOException; import java.io.StringReader; import java.util.List; import ...

  8. 无线路由和无线AP的区别

    一.答疑解惑 1.什么是无线AP? AP:Access Point 接入点 无线AP:无线接入点是一个无线网络的接入点,俗称“热点”.主要有路由交换接入一体设备和纯接入点设备,一体设备执行接入和路由工 ...

  9. 关于Socket和ServerSocket类详解

    Socket类 套接字是网络连接的一个端点.套接字使得一个应用可以从网络中读取和写入数据.放在两个不同计算机上的两个应用可以通过连接发送和接受字节流.为了从你的应用发送一条信息到另一个应用,你需要知道 ...

  10. spring-boot单元测试

    一.为什么要写单元测试 很多程序员有两件事情不愿意做: 写注释. 写单元测试. 但是在看代码时又会希望有清晰明了的注释,重构代码时能有一套随时可以跑起来的单元测试. 最近在迁移一个项目,从sqlser ...