Commit 05a324a07a1848b020a06d671680907f8b92207d

Authored by mk1990
Committed by GitHub
2 parents a0537d74 db865cc9

Merge branch '648540858:wvp-28181-2.0' into wvp-28181-2.0

src/main/java/com/genersoft/iot/vmp/gb28181/session/SsrcConfig.java
@@ -136,4 +136,7 @@ public class SsrcConfig { @@ -136,4 +136,7 @@ public class SsrcConfig {
136 this.notUsed = notUsed; 136 this.notUsed = notUsed;
137 } 137 }
138 138
  139 + public boolean checkSsrc(String ssrcInResponse) {
  140 + return !isUsed.contains(ssrcInResponse);
  141 + }
139 } 142 }
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
@@ -93,8 +93,8 @@ public interface ISIPCommander { @@ -93,8 +93,8 @@ public interface ISIPCommander {
93 * @param device 视频设备 93 * @param device 视频设备
94 * @param channelId 预览通道 94 * @param channelId 预览通道
95 */ 95 */
96 - void playStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent);  
97 - 96 + void playStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent);
  97 +
98 /** 98 /**
99 * 请求回放视频流 99 * 请求回放视频流
100 * 100 *
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
@@ -343,7 +343,7 @@ public class SIPCommander implements ISIPCommander { @@ -343,7 +343,7 @@ public class SIPCommander implements ISIPCommander {
343 */ 343 */
344 @Override 344 @Override
345 public void playStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, 345 public void playStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
346 - ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent) { 346 + ZLMHttpHookSubscribe.Event event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) {
347 String streamId = ssrcInfo.getStream(); 347 String streamId = ssrcInfo.getStream();
348 try { 348 try {
349 if (device == null) return; 349 if (device == null) return;
@@ -436,6 +436,7 @@ public class SIPCommander implements ISIPCommander { @@ -436,6 +436,7 @@ public class SIPCommander implements ISIPCommander {
436 // 这里为例避免一个通道的点播只有一个callID这个参数使用一个固定值 436 // 这里为例避免一个通道的点播只有一个callID这个参数使用一个固定值
437 streamSession.put(device.getDeviceId(), channelId ,"play", streamId, ssrcInfo.getSsrc(), mediaServerItem.getId(), ((ResponseEvent)e.event).getClientTransaction(), VideoStreamSessionManager.SessionType.play); 437 streamSession.put(device.getDeviceId(), channelId ,"play", streamId, ssrcInfo.getSsrc(), mediaServerItem.getId(), ((ResponseEvent)e.event).getClientTransaction(), VideoStreamSessionManager.SessionType.play);
438 streamSession.put(device.getDeviceId(), channelId ,"play", e.dialog); 438 streamSession.put(device.getDeviceId(), channelId ,"play", e.dialog);
  439 + okEvent.response(e);
439 }); 440 });
440 441
441 442
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/SubscribeRequestProcessor.java
@@ -202,6 +202,7 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme @@ -202,6 +202,7 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme
202 String platformId = SipUtils.getUserIdFromFromHeader(evt.getRequest()); 202 String platformId = SipUtils.getUserIdFromFromHeader(evt.getRequest());
203 String deviceID = XmlUtil.getText(rootElement, "DeviceID"); 203 String deviceID = XmlUtil.getText(rootElement, "DeviceID");
204 ParentPlatform platform = storager.queryParentPlatByServerGBId(platformId); 204 ParentPlatform platform = storager.queryParentPlatByServerGBId(platformId);
  205 + if (platform == null)return;
205 SubscribeInfo subscribeInfo = new SubscribeInfo(evt, platformId); 206 SubscribeInfo subscribeInfo = new SubscribeInfo(evt, platformId);
206 if (evt.getServerTransaction() == null) { 207 if (evt.getServerTransaction() == null) {
207 ServerTransaction serverTransaction = platform.getTransport().equals("TCP") ? tcpSipProvider.getNewServerTransaction(evt.getRequest()) 208 ServerTransaction serverTransaction = platform.getTransport().equals("TCP") ? tcpSipProvider.getNewServerTransaction(evt.getRequest())
src/main/java/com/genersoft/iot/vmp/gb28181/utils/XmlUtil.java
@@ -222,7 +222,24 @@ public class XmlUtil { @@ -222,7 +222,24 @@ public class XmlUtil {
222 // 由于海康会错误的发送65535作为这里的取值,所以这里除非是0否则认为是1 222 // 由于海康会错误的发送65535作为这里的取值,所以这里除非是0否则认为是1
223 deviceChannel.setParental(Integer.parseInt(XmlUtil.getText(itemDevice, "Parental")) == 1?1:0); 223 deviceChannel.setParental(Integer.parseInt(XmlUtil.getText(itemDevice, "Parental")) == 1?1:0);
224 } 224 }
225 - deviceChannel.setParentId(XmlUtil.getText(itemDevice, "ParentID")); 225 + /**
  226 + * 行政区划展示设备树与业务分组展示设备树是两种不同的模式
  227 + * 行政区划展示设备树 各个目录之间主要靠deviceId做关联,摄像头通过CivilCode指定其属于那个行政区划;都是不超过十位的编号; 结构如下:
  228 + * 河北省
  229 + * --> 石家庄市
  230 + * --> 摄像头
  231 + * --> 正定县
  232 + * --> 摄像头
  233 + * --> 摄像头
  234 + *
  235 + * 业务分组展示设备树是顶级是业务分组,其下的虚拟组织靠BusinessGroupID指定其所属的业务分组;摄像头通过ParentId来指定其所属于的虚拟组织:
  236 + * 业务分组
  237 + * --> 虚拟组织
  238 + * --> 摄像头
  239 + * --> 虚拟组织
  240 + * --> 摄像头
  241 + * --> 摄像头
  242 + */
226 String parentId = XmlUtil.getText(itemDevice, "ParentID"); 243 String parentId = XmlUtil.getText(itemDevice, "ParentID");
227 if (parentId != null) { 244 if (parentId != null) {
228 if (parentId.contains("/")) { 245 if (parentId.contains("/")) {
src/main/java/com/genersoft/iot/vmp/service/IMediaServerService.java
@@ -46,7 +46,7 @@ public interface IMediaServerService { @@ -46,7 +46,7 @@ public interface IMediaServerService {
46 46
47 SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, boolean ssrcCheck); 47 SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, boolean ssrcCheck);
48 48
49 - SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, boolean ssrcCheck, boolean isPlayback); 49 + SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, String ssrc, boolean ssrcCheck, boolean isPlayback);
50 50
51 void closeRTPServer(String deviceId, String channelId, String ssrc); 51 void closeRTPServer(String deviceId, String channelId, String ssrc);
52 52
src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java
@@ -118,11 +118,11 @@ public class MediaServerServiceImpl implements IMediaServerService { @@ -118,11 +118,11 @@ public class MediaServerServiceImpl implements IMediaServerService {
118 118
119 @Override 119 @Override
120 public SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, boolean ssrcCheck) { 120 public SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, boolean ssrcCheck) {
121 - return openRTPServer(mediaServerItem, streamId, ssrcCheck,false); 121 + return openRTPServer(mediaServerItem, streamId, null, ssrcCheck,false);
122 } 122 }
123 123
124 @Override 124 @Override
125 - public SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, boolean ssrcCheck, boolean isPlayback) { 125 + public SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, String presetSsrc, boolean ssrcCheck, boolean isPlayback) {
126 if (mediaServerItem == null || mediaServerItem.getId() == null) { 126 if (mediaServerItem == null || mediaServerItem.getId() == null) {
127 return null; 127 return null;
128 } 128 }
@@ -135,10 +135,14 @@ public class MediaServerServiceImpl implements IMediaServerService { @@ -135,10 +135,14 @@ public class MediaServerServiceImpl implements IMediaServerService {
135 return null; 135 return null;
136 }else { 136 }else {
137 String ssrc = null; 137 String ssrc = null;
138 - if (isPlayback) {  
139 - ssrc = ssrcConfig.getPlayBackSsrc(); 138 + if (presetSsrc != null) {
  139 + ssrc = presetSsrc;
140 }else { 140 }else {
141 - ssrc = ssrcConfig.getPlaySsrc(); 141 + if (isPlayback) {
  142 + ssrc = ssrcConfig.getPlayBackSsrc();
  143 + }else {
  144 + ssrc = ssrcConfig.getPlaySsrc();
  145 + }
142 } 146 }
143 147
144 if (streamId == null) { 148 if (streamId == null) {
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
@@ -39,6 +39,7 @@ import org.springframework.stereotype.Service; @@ -39,6 +39,7 @@ import org.springframework.stereotype.Service;
39 import org.springframework.util.ResourceUtils; 39 import org.springframework.util.ResourceUtils;
40 import org.springframework.web.context.request.async.DeferredResult; 40 import org.springframework.web.context.request.async.DeferredResult;
41 41
  42 +import javax.sip.ResponseEvent;
42 import java.io.FileNotFoundException; 43 import java.io.FileNotFoundException;
43 import java.math.BigDecimal; 44 import java.math.BigDecimal;
44 import java.util.*; 45 import java.util.*;
@@ -256,7 +257,7 @@ public class PlayServiceImpl implements IPlayService { @@ -256,7 +257,7 @@ public class PlayServiceImpl implements IPlayService {
256 } 257 }
257 } 258 }
258 }, userSetting.getPlayTimeout()); 259 }, userSetting.getPlayTimeout());
259 - 260 + final String ssrc = ssrcInfo.getSsrc();
260 cmder.playStreamCmd(mediaServerItem, ssrcInfo, device, channelId, (MediaServerItem mediaServerItemInuse, JSONObject response) -> { 261 cmder.playStreamCmd(mediaServerItem, ssrcInfo, device, channelId, (MediaServerItem mediaServerItemInuse, JSONObject response) -> {
261 logger.info("收到订阅消息: " + response.toJSONString()); 262 logger.info("收到订阅消息: " + response.toJSONString());
262 timer.cancel(); 263 timer.cancel();
@@ -264,10 +265,38 @@ public class PlayServiceImpl implements IPlayService { @@ -264,10 +265,38 @@ public class PlayServiceImpl implements IPlayService {
264 onPublishHandlerForPlay(mediaServerItemInuse, response, device.getDeviceId(), channelId, uuid); 265 onPublishHandlerForPlay(mediaServerItemInuse, response, device.getDeviceId(), channelId, uuid);
265 hookEvent.response(mediaServerItemInuse, response); 266 hookEvent.response(mediaServerItemInuse, response);
266 }, (event) -> { 267 }, (event) -> {
  268 + ResponseEvent responseEvent = (ResponseEvent)event.event;
  269 + String contentString = new String(responseEvent.getResponse().getRawContent());
  270 + // 获取ssrc
  271 + int ssrcIndex = contentString.indexOf("y=");
  272 + // 检查是否有y字段
  273 + if (ssrcIndex >= 0) {
  274 + //ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段 TODO 后续对不规范的非10位ssrc兼容
  275 + String ssrcInResponse = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
  276 + if (!ssrc.equals(ssrcInResponse) && device.isSsrcCheck()) { // 查询到ssrc不一致且开启了ssrc校验则需要针对处理
  277 + // 查询 ssrcInResponse 是否可用
  278 + if (mediaServerItem.isRtpEnable() && !mediaServerItem.getSsrcConfig().checkSsrc(ssrcInResponse)) {
  279 + // ssrc 不可用
  280 + // 释放ssrc
  281 + mediaServerService.releaseSsrc(mediaServerItem.getId(), finalSsrcInfo.getSsrc());
  282 + streamSession.remove(device.getDeviceId(), channelId, finalSsrcInfo.getStream());
  283 + event.msg = "下级自定义了ssrc,但是此ssrc不可用";
  284 + event.statusCode = 400;
  285 + errorEvent.response(event);
  286 + return;
  287 + }
  288 + // 关闭rtp server
  289 + mediaServerService.closeRTPServer(device.getDeviceId(), channelId, finalSsrcInfo.getStream());
  290 + // 重新开启ssrc server
  291 + mediaServerService.openRTPServer(mediaServerItem, finalSsrcInfo.getStream(), ssrcInResponse, device.isSsrcCheck(), false);
  292 + }
  293 + }
  294 + }, (event) -> {
267 timer.cancel(); 295 timer.cancel();
268 mediaServerService.closeRTPServer(device.getDeviceId(), channelId, finalSsrcInfo.getStream()); 296 mediaServerService.closeRTPServer(device.getDeviceId(), channelId, finalSsrcInfo.getStream());
269 // 释放ssrc 297 // 释放ssrc
270 mediaServerService.releaseSsrc(mediaServerItem.getId(), finalSsrcInfo.getSsrc()); 298 mediaServerService.releaseSsrc(mediaServerItem.getId(), finalSsrcInfo.getSsrc());
  299 +
271 streamSession.remove(device.getDeviceId(), channelId, finalSsrcInfo.getStream()); 300 streamSession.remove(device.getDeviceId(), channelId, finalSsrcInfo.getStream());
272 errorEvent.response(event); 301 errorEvent.response(event);
273 }); 302 });
web_src/src/components/channelList.vue
@@ -93,7 +93,7 @@ @@ -93,7 +93,7 @@
93 <el-button size="mini" icon="el-icon-s-open" type="primary" v-if="scope.row.subCount > 0 || scope.row.parental === 1" 93 <el-button size="mini" icon="el-icon-s-open" type="primary" v-if="scope.row.subCount > 0 || scope.row.parental === 1"
94 @click="changeSubchannel(scope.row)">查看 94 @click="changeSubchannel(scope.row)">查看
95 </el-button> 95 </el-button>
96 - <el-button size="mini" icon="el-icon-video-camera" type="primary" @click="queryRecords(scope.row)">设备录 96 + <el-button size="mini" icon="el-icon-video-camera" type="primary" @click="queryRecords(scope.row)">设备录
97 </el-button> 97 </el-button>
98 <!-- <el-button size="mini" @click="sendDevicePush(scope.row)">录像查询</el-button> --> 98 <!-- <el-button size="mini" @click="sendDevicePush(scope.row)">录像查询</el-button> -->
99 </el-button-group> 99 </el-button-group>
web_src/src/components/dialog/catalogEdit.vue
@@ -12,25 +12,22 @@ @@ -12,25 +12,22 @@
12 > 12 >
13 <div id="shared" style="margin-top: 1rem;margin-right: 100px;"> 13 <div id="shared" style="margin-top: 1rem;margin-right: 100px;">
14 <el-form ref="form" :rules="rules" :model="form" label-width="140px" > 14 <el-form ref="form" :rules="rules" :model="form" label-width="140px" >
  15 +<!-- <el-form-item >-->
  16 +<!-- 建议的类型:-->
  17 +<!-- <br/>-->
  18 +<!-- &emsp;&emsp;行政区划(可选2位/4位/6位/8位/10位数字,例如:130432,表示河北省邯郸市广平县)-->
  19 +<!-- <br/>-->
  20 +<!-- &emsp;&emsp;业务分组(第11、12、13位215,例如:34020000002150000001)-->
  21 +<!-- <br/>-->
  22 +<!-- &emsp;&emsp;虚拟组织(第11、12、13位216,例如:34020000002160000001)-->
  23 +<!-- </el-form-item>-->
15 <el-form-item label="节点编号" prop="id" > 24 <el-form-item label="节点编号" prop="id" >
16 - <el-tooltip class="item" effect="dark" content="" placement="top-start">  
17 - <div slot="content">  
18 - 建议的类型:  
19 - <br/>  
20 - &emsp;&emsp;行政区划(可选2位/4位/6位/8位/10位数字,例如:130432,表示河北省邯郸市广平县)  
21 - <br/>  
22 - &emsp;&emsp;业务分组(第11、12、13位215,例如:34020000002150000001)  
23 - <br/>  
24 - &emsp;&emsp;虚拟组织(第11、12、13位216,例如:34020000002160000001)  
25 - </div>  
26 - <el-input v-model="form.id" :disabled="isEdit"></el-input>  
27 - </el-tooltip> 25 + <el-input v-model="form.id" :disabled="isEdit" clearable></el-input>
28 </el-form-item> 26 </el-form-item>
29 <el-form-item label="节点名称" prop="name"> 27 <el-form-item label="节点名称" prop="name">
30 <el-input v-model="form.name" clearable></el-input> 28 <el-input v-model="form.name" clearable></el-input>
31 </el-form-item> 29 </el-form-item>
32 30
33 -  
34 <el-form-item> 31 <el-form-item>
35 <div style="float: right;"> 32 <div style="float: right;">
36 <el-button type="primary" @click="onSubmit" >确认</el-button> 33 <el-button type="primary" @click="onSubmit" >确认</el-button>
@@ -65,13 +62,14 @@ export default { @@ -65,13 +62,14 @@ export default {
65 }, 62 },
66 rules: { 63 rules: {
67 name: [{ required: true, message: "请输入名称", trigger: "blur" }], 64 name: [{ required: true, message: "请输入名称", trigger: "blur" }],
68 - id: [{ required: true, message: "请输入id", trigger: "blur" }] 65 + id: [{ required: true, message: "请输入ID", trigger: "blur" }]
69 }, 66 },
70 }; 67 };
71 }, 68 },
72 methods: { 69 methods: {
73 openDialog: function (isEdit, id, name, parentId, callback) { 70 openDialog: function (isEdit, id, name, parentId, callback) {
74 console.log("parentId: " + parentId) 71 console.log("parentId: " + parentId)
  72 + console.log(this.form)
75 this.isEdit = isEdit; 73 this.isEdit = isEdit;
76 this.form.id = id; 74 this.form.id = id;
77 this.form.name = name; 75 this.form.name = name;
@@ -105,8 +103,14 @@ export default { @@ -105,8 +103,14 @@ export default {
105 }); 103 });
106 }, 104 },
107 close: function () { 105 close: function () {
  106 + this.isEdit = false;
  107 + this.form.id = null;
  108 + this.form.name = null;
  109 + this.form.platformId = null;
  110 + this.form.parentId = null;
  111 + this.callback = null;
108 this.showDialog = false; 112 this.showDialog = false;
109 - this.$refs.form.resetFields(); 113 + console.log(this.form)
110 }, 114 },
111 }, 115 },
112 }; 116 };