diff --git a/README.md b/README.md
index 6a6fee9..d93cb63 100644
--- a/README.md
+++ b/README.md
@@ -4,27 +4,10 @@
- 支持跨平台特性,已测试操作系统有CentOS,Ubunut,Raspbain(ARM嵌入式树莓派操作系统),Windows10,MacOS Big Sur等操作系统,均可正常使用。
- 提供对ZLMediaKit的集群实现,提供不同服务器、不同操作系统下的ZLMediaKit能保持同一种操作方式与输出规范。
-## 更新日志
-### 2020-11-25 跨平台重大更新
-1. 所有特用于Linux的Shell脚本用法全部取消,使用.net Core自带的Process管理第三方进程
-2. 重写性能收集类LibSystemInfo,完全适配Linux,MacOS,Windows三大平台(注意Windows平台需要额外添加WinNetworkStaCli.exe文件到StreamNode目录)
-3. 已在Linux操作系统,MacOS操作系统基本测试,一切正常。
-### 2020-10-12
-1. 更新读取ZLMediaKit配置文件时碰到以#开头的配置项时解析出错的情况,会先将此配置文件中所有以#开头的行改成以;开头,以确保以正确的ini标准的配置文件的注释。
-2. 调整配置文件位置,system.conf及logconfig.xml到项目的Config/下面。
-3. 跨平台方向上的测试与调优。
-### 2020-10-10
-1. 全面改用Log4Net来记录日志
-### 2020-10-09
-1. 【支持】StreamNode已经与最新版(2020-10-09)ZLMediaKit兼容,不再需要修改ZLMediaKit的源码了。
-### 2020-10-05
-1. 【新增】sip网关收到gb28181设备的设备列表后,自动向Camera表插入这些设备列表作为可推流的设备后选 ,设置激活状态为非激活状态,此类设备需要通过接口进行激活。
-2. 【新增】增加/MediaServer/ActivateSipCamera接口,来完成对自动写入数据库的数据进行激活。
-3. 【修正】修正一个停止推流的bug。
-4. 【修正】修正一个可能存在的,针对于公网非固定ip的gb28181设备的通讯障碍问题(效果有待验证)。
+# Wiki已开通,具体使用说明请详细参考Wiki中的内容
+- https://github.com/chatop2020/StreamNode-GB28181/wiki/StreamNode-GB28181%E4%BB%8B%E7%BB%8D
-
-## 采用StreamNode-GB28181接口管理平台实现的WEB管理平台展示(此平台不是StreamNode-GB28181的组件之一)
+# 采用StreamNode-GB28181接口管理平台实现的WEB管理平台展示(此平台不是StreamNode-GB28181的组件之一)
- 视频广场
![QQ20201116-150115@2x.png](https://i.loli.net/2020/11/16/dho7kcjfrwp6Vmq.png)
- 录制计划
@@ -42,641 +25,5 @@
- 流媒体节点(ZLMediaKit+StreamNodeServiceKeeper)管理
![QQ20201116-150453@2x.png](https://i.loli.net/2020/11/16/k9cDXlqBezt6PbZ.png)
-## 支持操作系统及要求
-- CentOS7.x 强烈推荐
-- Ubuntu
-- Windows,MACOS 已支持(2020-11-27)
-- 需要Mysql5.7或Mariadb 10.3.x以上,(当然其他FreeSql库所支持的数据库也是支持的,比如Sqlite)
-# 运行
-## 环境
-- .net core 3.1
-- mysql 5.7以上或者其他freesql支持的数据库
-- ffmpeg 4.2.2以上
-- ffmpeg 需要放在StreamNodeWebApi和StreamMediaServerKeeper的部署目录中
-## 启动
-- mysql中创建一个名为“straemnode”的数据库,要和StreamNodeWebApi/system.conf中db行指定的一致,字符集请使用utf-8
-- StreamNodeWebApi 全局只启动一份
-- 部署目录中手工创建一个log文件夹
-- 需要root权限 需要root权限 需要root权限
-~~~shell
-nohup dotnet StreamNodeWebApi.dll >/dev/null &
-~~~
-- StreamMediaServerKeeper 一个流媒体启动一份,可以与StreamNodeWebApi不在同一台服务器
-- 部署目录中手工创建一个log文件夹
-~~~shell
-nohup dotnet StreamMediaServerKeeper.dll >/dev/null &
-~~~
-# 调试
-## StreamNodeWebApi
-- http://ip:port(5800)/swagger/index.html
-## StreamMediaServerKeeper
-- http://ip:port(6880)/swagger/index.html
-## TODO List
-- 预计全面改用Log4net来记录日志,取消掉原来的Console.WriteLine等记录日志的手段(已完成)
-- 增加接口调用的鉴权机制(未开始)
-- 考虑SIP网关支持级联到上级平台(未开始)
-- 直播推流的完善支持(未开始)
-
-
-## 接口功能
-### DvrPlan 录制计划
-- /DvrPlan/DeleteDvrPlanById 删除一个录制计划ById
-- /DvrPlan/OnOrOffDvrPlanById 启用或停用一个录制计划
-- /DvrPlan/SetDvrPlanById 修改录制计划ById
-- /DvrPlan/CreateDvrPlan 创建录制计划
-- /DvrPlan/GetDvrPlan 获取录制计划
-### MediaServer 流媒体控制
-- /MediaServer/CutOrMergeVideoFile 添加一个裁剪合并任务
-- /MediaServer/GetMergeTaskStatus 获取裁剪合并任务状态
-- /MediaServer/GetBacklogTaskList 获取裁剪合并任务积压列表
-- /MediaServer/UndoSoftDelete 恢复被软删除的录像文件
-- /MediaServer/HardDeleteDvrVideoById 删除一个录像文件ById(硬删除,立即删除文件,数据库做delete标记)
-- /MediaServer/HardDeleteDvrVideoByIdList 删除一批录像文件ById(硬删除,立即删除文件,数据库做delete标记)
-- /MediaServer/SoftDeleteDvrVideoById 删除一个录像文件ById(软删除,只做标记,不删除文件,文件在24小时后删除)
-- /MediaServer/GetDvrVideoById 根据id获取视频文件信息
-- /MediaServer/GetDvrVideoList 获取录像文件列表
-- /MediaServer/GetCameraInstanceListEx 扩展查询已注册摄像头列表
-- /MediaServer/GetCameraInstanceList 获取摄像头实例列表
-- /MediaServer/ModifyCameraInstance 修改一个注册摄像头实例
-- /MediaServer/DeleteCameraInstance 删除一个摄像头实例
-- /MediaServer/AddCameraInstance 注册添加一个摄像头实例
-- /MediaServer/GetPlayerSessionList 获取在线播放器列表
-- /MediaServer/GetCameraInstanceByCameraId 根据摄像头ID查询在线摄像头对象
-- /MediaServer/GetCameraSessionList 获取在线摄像头列表
-- /MediaServer/GetConfig 获取流媒体配置信息
-- /MediaServer/AddFFmpegProxy 启动一个ffmpeg代理流
-- /MediaServer/CloseStreams 关闭一个流
-- /MediaServer/GetStreamList 获取流列表
-- /MediaServer/StartRecord 启动流的录制
-- /MediaServer/StopRecord 停止流的录制
-- /MediaServer/GetRecordStatus 获取流的录制状态
-- /MediaServer/OpenRtpPort 打开某个rtp端口
-- /MediaServer/CloseRtpPort 关闭某个rtp端口
-- /MediaServer/GetRtpPortList 获取流媒体已经开放的rtp端口列表
-- /MediaServer/CheckMediaServerRunning 检查流媒体服务是否正在运行
-- /MediaServer/RestartMediaServer 重启流媒体服务
-- /MediaServer/StopMediaServer 关闭流媒体服务
-- /MediaServer/StartMediaServer 启动流媒体服务
-- /MediaServer/ActivateSipCamera 对Sip网关自动写入的GB28181设备进行激活
-### Sip网关相关
-- /SipGate/ActiveDeviceCatalogQuery 获取Sip设备的目录列表
-- /SipGate/GetAutoPushStreamState 获取Sip网关自动推流状态
-- SipGate/SetAutoPushStreamState 设置Sip网关自动推流状态
-- /SipGate/LiveVideo 请求实时视频
-- /SipGate/ByeLiveVideo 停止实时视频预览
-- /SipGate/GetSipDeviceList 获取已注册的设备列表
-- /SipGate/PtzControl ptz控制
-### 系统相关
-- /System/GetGlobleSystemInfo 获取全局的系统信息
-- /System/GetMediaServerInstance 获取一个流媒体服务的实例
-- /System/GetMediaServerList 获取流媒体服务器列表
-### Test
-- 一些测试接口,可以无视
-### WebHook
-- 用于ZLMediaKit回调的一些接口,可以无视
-
-## 结构介绍
-- ![StreamNode结构.jpg](https://i.loli.net/2020/09/29/xwkeW8agYspHKUt.jpg)
-## Sip网关的工作流程
-
-- 摄像头配置StreamNodeWebApi中Sip网关的相关参数信息
-- 摄像头上线时,自动通过GB28181协议向Sip网关注册自己
-- Sip网关发现摄像头上线后,发启设备目录查询请求
-- 摄像头收到设备目录查询请求后,按GB28181协议要求发送自身设备列表到Sip网关
-- Sip网关收到设备列表后,完成摄像头推拉流必须的参数
-- 摄像头与Sip网关保持心跳响应
-- Sip网关在一定周期内发现摄像头心跳断连,自动跳出Sip设备列表中的摄像头
-
-## StreamNode推拉流的工作流程
-- StreamNodeWebApi支持FFmpeg及GB28181方式接入摄像头,其中FFmpeg目前只拉Rtsp视频流
-- GB28181协议兼容支持GB28181-2016
-- 在接入摄像头前需明确了解摄像头是按Rtsp方式接入还是按GB28181方式接入
-- Rtsp方式接入需要正确使用摄像头的Rtsp地址,如:rtsp://username:password@ip:port/main/ch1
-- GB28181方式接入需要在摄像头配置页面配置StreamNodeWebApi中Sip网关的相关参数
-- GB28181参数主要有 Sip网关ip地址,Sip网关ID,摄像头ID,摄像头音视频流通道ID等
-- GB28181及Rtps方式接入的摄像头音视频编码格式最好使用H.264/ACC,以保证摄像头音视频流正常被ZLMediaKit识别解析,其他支持格式请查看ZLMediakit官方说明
-- GB28181方式接入的摄像头在启动时,会尝试连接配置在摄像头中的Sip网关
-- Sip网关在收到摄像头的注册请求时会通过GB28181-2016协议与摄头通讯,详见《Sip网关的工作流程》
-- StreamNodeWebApi维护了一个定时检查的注册摄像头推拉流状态,如果发现注册摄像头在线并且还未处于推流或拉流状态(没有视频流)的情况下将发启推拉流请求
-- Rtsp拉流:当此注册摄像头注册信息中的拉流方式为Rtsp时,StreamNodeWebApi将生成ZLMediakitAddFFmpegProxy请求结构(含摄像头的Rtsp地址),并向ZLMediaKit的AddFFmpegProxy WebApi接口发起拉流请求,ZLMediaKit服务收到AddFFmpegProxy请求后,启动ffmpeg进程,将ffmpeg拉流产生的音视频流推送给流媒体服务器,拉流成功后,ZLMediaKit将会产生OnStreamChange事件,通过Webhook的方式来告知StreamNode(还有其他辅助手段,不再此介绍了),正常拉流的情况下,可以在操作系统层面看到如下进程信息”/opt/ffmpeg/ffmpeg -re -rtsp_transport tcp -i rtsp://admin:password@192.168.2.165:554/Streaming/Channels/101?transportmode=unicast&profile=Profile_1 -vcodec copy -acodec copy -f flv -y rtmp://127.0.0.1/rtsp_proxy/FDC35E14“
-- GB28181请求推流:当此注册摄像头注册信息中的拉流方式为GB28181时,StreamNodeWebApi,将向ZLMediaKit申请rtp动态端口(TCP,UDP全开),端口申请成功获取到实际端口号后,StreamNodeApi根据摄像头注册信息组织实时流请求的Sip报文,并在此时确定流的ssrc值,将生成的Sip请求实时流报文通过Sip通讯通渞发送给摄像头,摄像头收到请求后根据Sip报文内容向ZLMediakit流媒体服务器的Sip报文指定端口以指定ssrc值推送rtp流,ZLMediaKit收到rtp流后对期进行解码,生产Onpublish事件并告知SteramNodeWebApi的Webhook接口,StreamNodeWebApi实现后续的相关处理
-- 摄像头的推拉流工作受控与StreamNodeWebApi的摄像头注册信息中的LiveEnable字段,可以通过改变此字段的值(true,false)来实现摄像头音视频流的开关
-
-## StreamNode-GB28181录制计划
-- 可对每个注册摄像头进行录制计划的控制,以控制某时某刻音视频文件录制的启停
-- 系统当前仅支持mp4文件的录制
-- StreamNodeWebApi,以星期为单位控制每个星期n的00:00:01到23:59:59的录制与不录制控制(可以出现多个星期n的数据,表示每天启用与停用录制可以多段,我记得我好像是这么实现的)
-- 如果某个摄像头启用了录制功能,却没有指定星期n的录制计划要求,系统默认其为全天录制
-## StreamNode-GB28181对音视频文件的裁剪与合并操作
-- 有时需要提取某个摄像头在某个时间范围的视频,可以通过StreamNodeWebApi的相关接口进行获取
-- 此操作是一个耗时操作,因此采用异步回调的方式来获取任务结果,调用方需提供一个WebApi接口来接受任务结果
-- 可能因为某些原因造成回调时调用方的WebApi不可用,导致任务结果未收到的情况,系统提供任务状态查询接口供调用方查询,此接口同样适用于任务进度的追踪(StreamMediaServerKeeper被重启后所有之前的任务结果会被清空,因为StreamMediaServerKeeper没有数据库持久保存这些数据)
-
-
- 老版本ZLMediaKit的代码修改请看这里
-
-## 修改ZLMediaKit的部分代码(ZLMediaKit官方已经在2020-10-09日合并了我的pr,使用2020-10-09以后ZLMeidakit代码生成的可的行文件就不需要再做以下代码修改了)
-- /src/Common/config.cpp
-~~~c++
-namespace mediakit {
-/*新增开始*/
- string generalGuid() {
- srand(time(0));
- std::string random_str("");
- for (int i = 0; i < 6; ++i) {
- for (int j = 0; j < 8; j++)
- switch (rand() % 2) {
- case 1:
- random_str += ('A' + rand() % 26);
- break;
- default:
- random_str += ('0' + rand() % 10);
- break;
- }
- if (i < 5)
- random_str += "-";
- }
-
- return random_str;
- }
-/*新增结束*/
-~~~
-
-~~~c++
-//通用配置项目
-namespace General{
-#define GENERAL_FIELD "general."
-/*新增开始*/
-const string kMediaServerId = GENERAL_FIELD"mediaServerId";
-/*新增结束*/
-const string kFlowThreshold = GENERAL_FIELD"flowThreshold";
-const string kStreamNoneReaderDelayMS = GENERAL_FIELD"streamNoneReaderDelayMS";
-const string kMaxStreamWaitTimeMS = GENERAL_FIELD"maxStreamWaitMS";
-const string kEnableVhost = GENERAL_FIELD"enableVhost";
-const string kAddMuteAudio = GENERAL_FIELD"addMuteAudio";
-const string kResetWhenRePlay = GENERAL_FIELD"resetWhenRePlay";
-const string kPublishToRtxp = GENERAL_FIELD"publishToRtxp";
-const string kPublishToHls = GENERAL_FIELD"publishToHls";
-const string kPublishToMP4 = GENERAL_FIELD"publishToMP4";
-const string kMergeWriteMS = GENERAL_FIELD"mergeWriteMS";
-const string kModifyStamp = GENERAL_FIELD"modifyStamp";
-
-onceToken token([](){
- mINI::Instance()[kFlowThreshold] = 1024;
- mINI::Instance()[kStreamNoneReaderDelayMS] = 20 * 1000;
- mINI::Instance()[kMaxStreamWaitTimeMS] = 15 * 1000;
- mINI::Instance()[kEnableVhost] = 0;
- mINI::Instance()[kAddMuteAudio] = 1;
- mINI::Instance()[kResetWhenRePlay] = 1;
- mINI::Instance()[kPublishToRtxp] = 1;
- mINI::Instance()[kPublishToHls] = 1;
- mINI::Instance()[kPublishToMP4] = 0;
- mINI::Instance()[kMergeWriteMS] = 0;
- mINI::Instance()[kModifyStamp] = 0;
-/*新增开始*/
- mINI::Instance()[kMediaServerId] = generalGuid();
-/*新增结束*/
-},nullptr);
-~~~
-
-- /src/Common/config.h
-~~~c++
-////////////通用配置///////////
-namespace General{
-/*新增开始*/
-//每个流媒体服务器的ID(GUID)
-extern const string kMediaServerId;
-/*新增结束*/
-//流量汇报事件流量阈值,单位KB,默认1MB
-extern const string kFlowThreshold;
-//流无人观看并且超过若干时间后才触发kBroadcastStreamNoneReader事件
-//默认连续5秒无人观看然后触发kBroadcastStreamNoneReader事件
-extern const string kStreamNoneReaderDelayMS;
-//等待流注册超时时间,收到播放器后请求后,如果未找到相关流,服务器会等待一定时间,
-//如果在这个时间内,相关流注册上了,那么服务器会立即响应播放器播放成功,
-//否则会最多等待kMaxStreamWaitTimeMS毫秒,然后响应播放器播放失败
-extern const string kMaxStreamWaitTimeMS;
-//是否启动虚拟主机
-extern const string kEnableVhost;
-//拉流代理时是否添加静音音频
-extern const string kAddMuteAudio;
-//拉流代理时如果断流再重连成功是否删除前一次的媒体流数据,如果删除将重新开始,
-//如果不删除将会接着上一次的数据继续写(录制hls/mp4时会继续在前一个文件后面写)
-extern const string kResetWhenRePlay;
-//是否默认推流时转换成rtsp或rtmp,hook接口(on_publish)中可以覆盖该设置
-extern const string kPublishToRtxp ;
-//是否默认推流时转换成hls,hook接口(on_publish)中可以覆盖该设置
-extern const string kPublishToHls ;
-//是否默认推流时mp4录像,hook接口(on_publish)中可以覆盖该设置
-extern const string kPublishToMP4 ;
-//合并写缓存大小(单位毫秒),合并写指服务器缓存一定的数据后才会一次性写入socket,这样能提高性能,但是会提高延时
-//开启后会同时关闭TCP_NODELAY并开启MSG_MORE
-extern const string kMergeWriteMS ;
-//全局的时间戳覆盖开关,在转协议时,对frame进行时间戳覆盖
-extern const string kModifyStamp;
-}//namespace General
-
-~~~
-
--/server/FFmpegSource.cpp
-~~~c++
-/**
- * 定时检查媒体是否在线
- */
-void FFmpegSource::startTimer(int timeout_ms) {
- weak_ptr weakSelf = shared_from_this();
- _timer = std::make_shared(1, [weakSelf, timeout_ms]() {
- auto strongSelf = weakSelf.lock();
- if (!strongSelf) {
- //自身已经销毁
- return false;
- }
- if (is_local_ip(strongSelf->_media_info._host)) {
- //推流给自己的,我们通过检查是否已经注册来判断FFmpeg是否工作正常
- strongSelf->findAsync(0, [&](const MediaSource::Ptr &src) {
- //同步查找流
- if (!src) {
- //流不在线,重新拉流
-/*修改原来的10到20,以保证ffmpeg拉流断掉后的重新拉流超时时间不会因为过短而反复失败*/
- if(strongSelf->_replay_ticker.elapsedTime() > 20 * 1000){
-/*修改原来的10到20,以保证ffmpeg拉流断掉后的重新拉流超时时间不会因为过短而反复失败*/
- //上次重试时间超过10秒,那么再重试FFmpeg拉流
- strongSelf->_replay_ticker.resetTime();
- strongSelf->play(strongSelf->_src_url, strongSelf->_dst_url, timeout_ms, [](const SockException &) {});
- }
- }
- });
- } else {
- //推流给其他服务器的,我们通过判断FFmpeg进程是否在线,如果FFmpeg推流中断,那么它应该会自动退出
- if (!strongSelf->_process.wait(false)) {
- //ffmpeg不在线,重新拉流
- strongSelf->play(strongSelf->_src_url, strongSelf->_dst_url, timeout_ms, [weakSelf](const SockException &ex) {
- if(!ex){
- //没有错误
- return;
- }
- auto strongSelf = weakSelf.lock();
- if (!strongSelf) {
- //自身已经销毁
- return;
- }
- //上次重试时间超过10秒,那么再重试FFmpeg拉流
- strongSelf->startTimer(10 * 1000);
- });
- }
- }
- return true;
- }, _poller);
-}
-~~~
--server/WebHook.cpp
-~~~c++
-static ArgsType make_json(const MediaInfo &args){
- ArgsType body;
-/*新增开始*/
- GET_CONFIG(string,mediaServerId,General::kMediaServerId);
-/*新增结束*/
- body["mediaserverid"] = mediaServerId;
- body["schema"] = args._schema;
- body["vhost"] = args._vhost;
- body["app"] = args._app;
- body["stream"] = args._streamid;
- body["params"] = args._param_strs;
- return std::move(body);
-}
-~~~
-~~~c++
- //监听rtsp、rtmp源注册或注销事件
- NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastMediaChanged,[](BroadcastMediaChangedArgs){
- if(!hook_enable || hook_stream_chaned.empty()){
- return;
- }
- ArgsType body;
-/*新增开始*/
- GET_CONFIG(string,mediaServerId,General::kMediaServerId);
- body["mediaserverid"] = mediaServerId;
-/*新增结束*/
- body["regist"] = bRegist;
- body["schema"] = sender.getSchema();
- body["vhost"] = sender.getVhost();
- body["app"] = sender.getApp();
- body["stream"] = sender.getId();
- //执行hook
- do_http_hook(hook_stream_chaned,body, nullptr);
- });
-~~~
-~~~c++
-#ifdef ENABLE_MP4
- //录制mp4文件成功后广播
- NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastRecordMP4,[](BroadcastRecordMP4Args){
- if(!hook_enable || hook_record_mp4.empty()){
- return;
- }
- ArgsType body;
-/*新增开始*/
- GET_CONFIG(string,mediaServerId,General::kMediaServerId);
- body["mediaserverid"] = mediaServerId;
-/*新增结束*/
- body["start_time"] = (Json::UInt64)info.ui64StartedTime;
- body["time_len"] = (Json::UInt64)info.ui64TimeLen;
- body["file_size"] = (Json::UInt64)info.ui64FileSize;
- body["file_path"] = info.strFilePath;
- body["file_name"] = info.strFileName;
- body["folder"] = info.strFolder;
- body["url"] = info.strUrl;
- body["app"] = info.strAppName;
- body["stream"] = info.strStreamId;
- body["vhost"] = info.strVhost;
- //执行hook
- do_http_hook(hook_record_mp4,body, nullptr);
- });
-#endif //ENABLE_MP4
-~~~c++
-NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastShellLogin,[](BroadcastShellLoginArgs){
- if(!hook_enable || hook_shell_login.empty() || sender.get_peer_ip() == "127.0.0.1"){
- invoker("");
- return;
- }
- ArgsType body;
-/*新增开始*/
- GET_CONFIG(string,mediaServerId,General::kMediaServerId);
- body["mediaserverid"] = mediaServerId;
-/*新增结束*/
- body["ip"] = sender.get_peer_ip();
- body["port"] = sender.get_peer_port();
- body["id"] = sender.getIdentifier();
- body["user_name"] = user_name;
- body["passwd"] = passwd;
-
- //执行hook
- do_http_hook(hook_shell_login,body, [invoker](const Value &,const string &err){
- invoker(err);
- });
- });
-~~~c++
-NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastStreamNoneReader,[](BroadcastStreamNoneReaderArgs){
- if(!hook_enable || hook_stream_none_reader.empty()){
- return;
- }
-
- ArgsType body;
-/*新增开始*/
- GET_CONFIG(string,mediaServerId,General::kMediaServerId);
- body["mediaserverid"] = mediaServerId;
-/*新增结束*/
- body["schema"] = sender.getSchema();
- body["vhost"] = sender.getVhost();
- body["app"] = sender.getApp();
- body["stream"] = sender.getId();
- weak_ptr weakSrc = sender.shared_from_this();
- //执行hook
- do_http_hook(hook_stream_none_reader,body, [weakSrc](const Value &obj,const string &err){
- bool flag = obj["close"].asBool();
- auto strongSrc = weakSrc.lock();
- if(!flag || !err.empty() || !strongSrc){
- return;
- }
- strongSrc->close(false);
- });
-
- });
-~~~
-~~~c++
- NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastHttpAccess,[](BroadcastHttpAccessArgs){
- if(sender.get_peer_ip() == "127.0.0.1" || parser.Params() == hook_adminparams){
- //如果是本机或超级管理员访问,那么不做访问鉴权;权限有效期1个小时
- invoker("","",60 * 60);
- return;
- }
- if(!hook_enable || hook_http_access.empty()){
- //未开启http文件访问鉴权,那么允许访问,但是每次访问都要鉴权;
- //因为后续随时都可能开启鉴权(重载配置文件后可能重新开启鉴权)
- invoker("","",0);
- return;
- }
-
- ArgsType body;
-/*新增开始*/
- GET_CONFIG(string,mediaServerId,General::kMediaServerId);
- body["mediaserverid"] = mediaServerId;
-/*新增结束*/
- body["ip"] = sender.get_peer_ip();
- body["port"] = sender.get_peer_port();
- body["id"] = sender.getIdentifier();
- body["path"] = path;
- body["is_dir"] = is_dir;
- body["params"] = parser.Params();
- for(auto &pr : parser.getHeader()){
- body[string("header.") + pr.first] = pr.second;
- }
- //执行hook
- do_http_hook(hook_http_access,body, [invoker](const Value &obj,const string &err){
- if(!err.empty()){
- //如果接口访问失败,那么仅限本次没有访问http服务器的权限
- invoker(err,"",0);
- return;
- }
- //err参数代表不能访问的原因,空则代表可以访问
- //path参数是该客户端能访问或被禁止的顶端目录,如果path为空字符串,则表述为当前目录
- //second参数规定该cookie超时时间,如果second为0,本次鉴权结果不缓存
- invoker(obj["err"].asString(),obj["path"].asString(),obj["second"].asInt());
- });
- });
-
- //汇报服务器重新启动
- reportServerStarted();
-}
-
-~~~
-
--src/Record/Recorder.cpp
-~~~c++
-string Recorder::getRecordPath(Recorder::type type, const string &vhost, const string &app, const string &stream_id, const string &customized_path) {
- GET_CONFIG(bool, enableVhost, General::kEnableVhost);
- switch (type) {
- case Recorder::type_hls: {
- GET_CONFIG(string, hlsPath, Hls::kFilePath);
- string m3u8FilePath;
- if (enableVhost) {
- m3u8FilePath = vhost + "/" + app + "/" + stream_id + "/hls.m3u8";
- } else {
- m3u8FilePath = app + "/" + stream_id + "/hls.m3u8";
- }
- //Here we use the customized file path.
- if (!customized_path.empty()) {
- m3u8FilePath = customized_path + "/hls.m3u8";
- }
- return File::absolutePath(m3u8FilePath, hlsPath);
- }
- case Recorder::type_mp4: {
- GET_CONFIG(string, recordPath, Record::kFilePath);
- GET_CONFIG(string, recordAppName, Record::kAppName);
- string mp4FilePath;
- if (enableVhost) {
- mp4FilePath = vhost + "/" + recordAppName + "/" + app + "/" + stream_id + "/";
- } else {
- mp4FilePath = recordAppName + "/" + app + "/" + stream_id + "/";
- }
- //Here we use the customized file path.
- if (!customized_path.empty()) {
- /*开始删除*/
- // mp4FilePath = customized_path + "/";
- /*删除结束*/
- /*开始添加*/
- return customized_path + "/"+mp4FilePath;
- /*开始添加*/
- }
-
- return File::absolutePath(mp4FilePath, recordPath);
- }
- default:
- return "";
- }
-}
-~~~
-
-
-
-# 组成部分
-## StreamNodeWebApi
-- 全局的流媒体管理API服务,包含了所有流媒体功能的控制,如摄像头注册,录制计划,rtp推流,ptz控制等。
-- 此服务全局只存在一份,负责收集来原于StreamMediaServerKeeper的流媒体服务信息,并进行流媒体服务的管理。
-## StreamMediaServerKeeper
-- 用于流媒体的相关控制,如控制流媒体服务器的启动,停止,重启,获取某个录制文件是否存在,裁剪与合并任务的执行等。
-- 此服务针对于流媒体进行部署,每一个流媒体服务都需要部署一个StreamMediaServerKeeper,此服务与StreamNodeWebApi通过WebApi进行通讯。
-- 此服务启动时,将自己的状态以心跳的方式汇报给StreamNodeWebApi,并帮助StreamNodeWebApi服务控制ZLMediaKit流媒体服务器。
-## 其他内容
-- LibGB28181SipGate GB28181 SIP信令服务网关
-- GB28181.SIPSorcery GB28181 SIP信令协议栈
-- CommonFunctions 通用方法与通用结构
-- Logger4Net 部分组件用到的日志工具
-- sipsorcery SIP协议栈
-- StreamNodeCtrlApis 控制API集合
-- Test_* 测试的工程
-
-
-# 配置文件解释
-## StreamNodeWebApi/Config/gb28181.xml
-- 此文件用于指定GB28181 sip信令网关的参数
-~~~xml
-
-
- 10
- 上级平台
- GB-2016
-
- 34020000002000000001
-
- 192.168.2.43
-
- 5060
-
- 5060
-
- false
-
- admin
-
- 123456
-
- UDP
-
- UDP
-
- passive
-
- GB2312
-
- true
-
- 5000
-
- 3
-
- 192.168.2.43
-
- 10000
-
- 10000
-
- 10000
-
-
-
-
-
-
-~~~
-
-
-
-
-## StreamNodeWebApi/Config/system.conf
-- StreamNodeWebApi的配置文件,参数名与参数值以::分开,每行以;结束
-- 数据库方面采用CodeFirst 模式,在数据库中建立一个名为streamnode的库,数据表会自动创建
-~~~
-#webapi的端口
-httpport::5800;
-#暂时无用
-password::password123!@#;
-#鉴权,暂时无用
-allowkey::0D906284-6801-4B84-AEC9-DCE07FAE81DA * 192.168.2.* ;
-#数据库连接串
-db::Data Source=192.168.2.35;Port=3306;User ID=root;Password=password; Initial Catalog=streamnode;Charset=utf8; SslMode=none;Min pool size=1;
-#数据库类型
-dbtype::mysql;
-#ffmpeg可执行文件的位置
-ffmpegpath::./ffmpeg;
-~~~
-
-## StreamMediaServerKeeper/Config/config.conf
-- StreamMediaServerKeeper的配置文件
-~~~
-#ZLMediaKit流媒体服务器可执行文件路径
-MediaServerBinPath::/root/MediaService/MediaServer;
-#向哪个StreamNodeWebApi注册自己的服务
-StreamNodeServerUrl::http://192.168.2.43:5800/WebHook/MediaServerRegister;
-#服务的WebApi端口
-HttpPort::6880;
-#本机ip地址
-IpAddress::192.168.2.43;
-#自定义存储视频的位置
-CustomizedRecordFilePath::/home/cdtnb;
-~~~
-
-## StreamNodeWebApi/Config/logconfig.xml & StreamMediaServerKeeper/Config/logconfig.xml
-- 日志配置文件
-~~~xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-~~~
-