Commit 44d216100b45c3337c593ee82ee68e7e0f35d24b

Authored by Lawrence
1 parent ecaf8750

与master分支同步

Showing 30 changed files with 824 additions and 445 deletions
src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java
... ... @@ -5,12 +5,18 @@ import com.alibaba.fastjson.JSONArray;
5 5 public class StreamInfo {
6 6  
7 7 private String ssrc;
  8 + private String streamId;
8 9 private String deviceID;
9 10 private String cahnnelId;
10 11 private String flv;
11 12 private String ws_flv;
12   - private String rtmp;
  13 + private String fmp4;
  14 + private String ws_fmp4;
13 15 private String hls;
  16 + private String ws_hls;
  17 + private String ts;
  18 + private String ws_ts;
  19 + private String rtmp;
14 20 private String rtsp;
15 21 private JSONArray tracks;
16 22  
... ... @@ -85,4 +91,52 @@ public class StreamInfo {
85 91 public void setTracks(JSONArray tracks) {
86 92 this.tracks = tracks;
87 93 }
  94 +
  95 + public String getFmp4() {
  96 + return fmp4;
  97 + }
  98 +
  99 + public void setFmp4(String fmp4) {
  100 + this.fmp4 = fmp4;
  101 + }
  102 +
  103 + public String getWs_fmp4() {
  104 + return ws_fmp4;
  105 + }
  106 +
  107 + public void setWs_fmp4(String ws_fmp4) {
  108 + this.ws_fmp4 = ws_fmp4;
  109 + }
  110 +
  111 + public String getWs_hls() {
  112 + return ws_hls;
  113 + }
  114 +
  115 + public void setWs_hls(String ws_hls) {
  116 + this.ws_hls = ws_hls;
  117 + }
  118 +
  119 + public String getTs() {
  120 + return ts;
  121 + }
  122 +
  123 + public void setTs(String ts) {
  124 + this.ts = ts;
  125 + }
  126 +
  127 + public String getWs_ts() {
  128 + return ws_ts;
  129 + }
  130 +
  131 + public void setWs_ts(String ws_ts) {
  132 + this.ws_ts = ws_ts;
  133 + }
  134 +
  135 + public String getStreamId() {
  136 + return streamId;
  137 + }
  138 +
  139 + public void setStreamId(String streamId) {
  140 + this.streamId = streamId;
  141 + }
88 142 }
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java
... ... @@ -113,6 +113,7 @@ public class SipLayer implements SipListener {
113 113 */
114 114 @Override
115 115 public void processRequest(RequestEvent evt) {
  116 + logger.debug(evt.getRequest().toString());
116 117 // 由于jainsip是单线程程序,为提高性能并发处理
117 118 processThreadPool.execute(() -> {
118 119 processorFactory.createRequestProcessor(evt).process();
... ... @@ -122,6 +123,7 @@ public class SipLayer implements SipListener {
122 123 @Override
123 124 public void processResponse(ResponseEvent evt) {
124 125 Response response = evt.getResponse();
  126 + logger.debug(evt.getResponse().toString());
125 127 int status = response.getStatusCode();
126 128 if (((status >= 200) && (status < 300)) || status == 401) { // Success!
127 129 ISIPResponseProcessor processor = processorFactory.createResponseProcessor(evt);
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/event/online/OnlineEventListener.java
... ... @@ -32,7 +32,7 @@ public class OnlineEventListener implements ApplicationListener&lt;OnlineEvent&gt; {
32 32 public void onApplicationEvent(OnlineEvent event) {
33 33  
34 34 if (logger.isDebugEnabled()) {
35   - logger.debug("设备线事件触发,deviceId:" + event.getDeviceId() + ",from:" + event.getFrom());
  35 + logger.debug("设备线事件触发,deviceId:" + event.getDeviceId() + ",from:" + event.getFrom());
36 36 }
37 37  
38 38 String key = VideoManagerConstants.KEEPLIVEKEY_PREFIX + event.getDeviceId();
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java
... ... @@ -17,13 +17,11 @@ public class VideoStreamSessionManager {
17 17 private ConcurrentHashMap<String, ClientTransaction> sessionMap = new ConcurrentHashMap<>();
18 18  
19 19 public String createPlaySsrc(){
20   - String ssrc = SsrcUtil.getPlaySsrc();
21   - return ssrc;
  20 + return SsrcUtil.getPlaySsrc();
22 21 }
23 22  
24 23 public String createPlayBackSsrc(){
25   - String ssrc = SsrcUtil.getPlayBackSsrc();
26   - return ssrc;
  24 + return SsrcUtil.getPlayBackSsrc();
27 25 }
28 26  
29 27 public void put(String ssrc,ClientTransaction transaction){
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/DeferredResultHolder.java
... ... @@ -22,6 +22,8 @@ public class DeferredResultHolder {
22 22  
23 23 public static final String CALLBACK_CMD_RECORDINFO = "CALLBACK_RECORDINFO";
24 24  
  25 + public static final String CALLBACK_CMD_PlAY = "CALLBACK_PLAY";
  26 +
25 27 private Map<String, DeferredResult> map = new HashMap<String, DeferredResult>();
26 28  
27 29 public void put(String key, DeferredResult result) {
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
... ... @@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.gb28181.transmit.cmd;
2 2  
3 3 import com.genersoft.iot.vmp.common.StreamInfo;
4 4 import com.genersoft.iot.vmp.gb28181.bean.Device;
  5 +import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe;
5 6 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
6 7  
7 8 /**
... ... @@ -20,7 +21,7 @@ public interface ISIPCommander {
20 21 * @param upDown 镜头上移下移 0:停止 1:上移 2:下移
21 22 * @param moveSpeed 镜头移动速度
22 23 */
23   - public boolean ptzdirectCmd(Device device,String channelId,int leftRight, int upDown);
  24 + boolean ptzdirectCmd(Device device,String channelId,int leftRight, int upDown);
24 25  
25 26 /**
26 27 * 云台方向放控制
... ... @@ -31,7 +32,7 @@ public interface ISIPCommander {
31 32 * @param upDown 镜头上移下移 0:停止 1:上移 2:下移
32 33 * @param moveSpeed 镜头移动速度
33 34 */
34   - public boolean ptzdirectCmd(Device device,String channelId,int leftRight, int upDown, int moveSpeed);
  35 + boolean ptzdirectCmd(Device device,String channelId,int leftRight, int upDown, int moveSpeed);
35 36  
36 37 /**
37 38 * 云台缩放控制,使用配置文件中的默认镜头缩放速度
... ... @@ -40,7 +41,7 @@ public interface ISIPCommander {
40 41 * @param channelId 预览通道
41 42 * @param inOut 镜头放大缩小 0:停止 1:缩小 2:放大
42 43 */
43   - public boolean ptzZoomCmd(Device device,String channelId,int inOut);
  44 + boolean ptzZoomCmd(Device device,String channelId,int inOut);
44 45  
45 46 /**
46 47 * 云台缩放控制
... ... @@ -50,7 +51,7 @@ public interface ISIPCommander {
50 51 * @param inOut 镜头放大缩小 0:停止 1:缩小 2:放大
51 52 * @param zoomSpeed 镜头缩放速度
52 53 */
53   - public boolean ptzZoomCmd(Device device,String channelId,int inOut, int moveSpeed);
  54 + boolean ptzZoomCmd(Device device,String channelId,int inOut, int moveSpeed);
54 55  
55 56 /**
56 57 * 云台控制,支持方向与缩放控制
... ... @@ -63,7 +64,7 @@ public interface ISIPCommander {
63 64 * @param moveSpeed 镜头移动速度
64 65 * @param zoomSpeed 镜头缩放速度
65 66 */
66   - public boolean ptzCmd(Device device,String channelId,int leftRight, int upDown, int inOut, int moveSpeed, int zoomSpeed);
  67 + boolean ptzCmd(Device device,String channelId,int leftRight, int upDown, int inOut, int moveSpeed, int zoomSpeed);
67 68  
68 69 /**
69 70 * 前端控制,包括PTZ指令、FI指令、预置位指令、巡航指令、扫描指令和辅助开关指令
... ... @@ -75,7 +76,7 @@ public interface ISIPCommander {
75 76 * @param parameter2 数据2
76 77 * @param combineCode2 组合码2
77 78 */
78   - public boolean frontEndCmd(Device device, String channelId, int cmdCode, int parameter1, int parameter2, int combineCode2);
  79 + boolean frontEndCmd(Device device, String channelId, int cmdCode, int parameter1, int parameter2, int combineCode2);
79 80  
80 81 /**
81 82 * 请求预览视频流
... ... @@ -83,7 +84,7 @@ public interface ISIPCommander {
83 84 * @param device 视频设备
84 85 * @param channelId 预览通道
85 86 */
86   - public StreamInfo playStreamCmd(Device device, String channelId);
  87 + void playStreamCmd(Device device, String channelId, ZLMHttpHookSubscribe.Event event);
87 88  
88 89 /**
89 90 * 请求回放视频流
... ... @@ -93,14 +94,14 @@ public interface ISIPCommander {
93 94 * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
94 95 * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
95 96 */
96   - public StreamInfo playbackStreamCmd(Device device,String channelId, String startTime, String endTime);
  97 + void playbackStreamCmd(Device device, String channelId, String startTime, String endTime, ZLMHttpHookSubscribe.Event event);
97 98  
98 99 /**
99 100 * 视频流停止
100 101 *
101 102 * @param ssrc ssrc
102 103 */
103   - public void streamByeCmd(String ssrc);
  104 + void streamByeCmd(String ssrc);
104 105  
105 106 /**
106 107 * 语音广播
... ... @@ -108,7 +109,7 @@ public interface ISIPCommander {
108 109 * @param device 视频设备
109 110 * @param channelId 预览通道
110 111 */
111   - public boolean audioBroadcastCmd(Device device,String channelId);
  112 + boolean audioBroadcastCmd(Device device,String channelId);
112 113  
113 114 /**
114 115 * 音视频录像控制
... ... @@ -116,21 +117,21 @@ public interface ISIPCommander {
116 117 * @param device 视频设备
117 118 * @param channelId 预览通道
118 119 */
119   - public boolean recordCmd(Device device,String channelId);
  120 + boolean recordCmd(Device device,String channelId);
120 121  
121 122 /**
122 123 * 报警布防/撤防命令
123 124 *
124 125 * @param device 视频设备
125 126 */
126   - public boolean guardCmd(Device device);
  127 + boolean guardCmd(Device device);
127 128  
128 129 /**
129 130 * 报警复位命令
130 131 *
131 132 * @param device 视频设备
132 133 */
133   - public boolean alarmCmd(Device device);
  134 + boolean alarmCmd(Device device);
134 135  
135 136 /**
136 137 * 强制关键帧命令,设备收到此命令应立刻发送一个IDR帧
... ... @@ -138,21 +139,21 @@ public interface ISIPCommander {
138 139 * @param device 视频设备
139 140 * @param channelId 预览通道
140 141 */
141   - public boolean iFameCmd(Device device,String channelId);
  142 + boolean iFameCmd(Device device,String channelId);
142 143  
143 144 /**
144 145 * 看守位控制命令
145 146 *
146 147 * @param device 视频设备
147 148 */
148   - public boolean homePositionCmd(Device device);
  149 + boolean homePositionCmd(Device device);
149 150  
150 151 /**
151 152 * 设备配置命令
152 153 *
153 154 * @param device 视频设备
154 155 */
155   - public boolean deviceConfigCmd(Device device);
  156 + boolean deviceConfigCmd(Device device);
156 157  
157 158  
158 159 /**
... ... @@ -160,7 +161,7 @@ public interface ISIPCommander {
160 161 *
161 162 * @param device 视频设备
162 163 */
163   - public boolean deviceStatusQuery(Device device);
  164 + boolean deviceStatusQuery(Device device);
164 165  
165 166 /**
166 167 * 查询设备信息
... ... @@ -168,14 +169,14 @@ public interface ISIPCommander {
168 169 * @param device 视频设备
169 170 * @return
170 171 */
171   - public boolean deviceInfoQuery(Device device);
  172 + boolean deviceInfoQuery(Device device);
172 173  
173 174 /**
174 175 * 查询目录列表
175 176 *
176 177 * @param device 视频设备
177 178 */
178   - public boolean catalogQuery(Device device);
  179 + boolean catalogQuery(Device device);
179 180  
180 181 /**
181 182 * 查询录像信息
... ... @@ -184,35 +185,33 @@ public interface ISIPCommander {
184 185 * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
185 186 * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
186 187 */
187   - public boolean recordInfoQuery(Device device, String channelId, String startTime, String endTime);
  188 + boolean recordInfoQuery(Device device, String channelId, String startTime, String endTime);
188 189  
189 190 /**
190 191 * 查询报警信息
191 192 *
192 193 * @param device 视频设备
193 194 */
194   - public boolean alarmInfoQuery(Device device);
  195 + boolean alarmInfoQuery(Device device);
195 196  
196 197 /**
197 198 * 查询设备配置
198 199 *
199 200 * @param device 视频设备
200 201 */
201   - public boolean configQuery(Device device);
  202 + boolean configQuery(Device device);
202 203  
203 204 /**
204 205 * 查询设备预置位置
205 206 *
206 207 * @param device 视频设备
207 208 */
208   - public boolean presetQuery(Device device);
  209 + boolean presetQuery(Device device);
209 210  
210 211 /**
211 212 * 查询移动设备位置数据
212 213 *
213 214 * @param device 视频设备
214 215 */
215   - public boolean mobilePostitionQuery(Device device);
216   -
217   -
  216 + boolean mobilePostitionQuery(Device device);
218 217 }
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
... ... @@ -19,6 +19,7 @@ import com.alibaba.fastjson.JSONObject;
19 19 import com.genersoft.iot.vmp.common.StreamInfo;
20 20 import com.genersoft.iot.vmp.conf.MediaServerConfig;
21 21 import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
  22 +import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe;
22 23 import com.genersoft.iot.vmp.media.zlm.ZLMUtils;
23 24 import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
24 25 import org.springframework.beans.factory.annotation.Autowired;
... ... @@ -67,6 +68,9 @@ public class SIPCommander implements ISIPCommander {
67 68 @Value("${media.rtp.enable}")
68 69 private boolean rtpEnable;
69 70  
  71 + @Autowired
  72 + private ZLMHttpHookSubscribe subscribe;
  73 +
70 74  
71 75  
72 76 /**
... ... @@ -264,12 +268,12 @@ public class SIPCommander implements ISIPCommander {
264 268 }
265 269 /**
266 270 * 请求预览视频流
267   - *
  271 + *
268 272 * @param device 视频设备
269 273 * @param channelId 预览通道
270   - */
  274 + */
271 275 @Override
272   - public StreamInfo playStreamCmd(Device device, String channelId) {
  276 + public void playStreamCmd(Device device, String channelId, ZLMHttpHookSubscribe.Event event) {
273 277 try {
274 278  
275 279 String ssrc = streamSession.createPlaySsrc();
... ... @@ -282,53 +286,63 @@ public class SIPCommander implements ISIPCommander {
282 286 }else {
283 287 mediaPort = mediaInfo.getRtpProxyPort();
284 288 }
  289 +
  290 + String streamId = String.format("%08x", Integer.parseInt(ssrc)).toUpperCase();
  291 + // 添加订阅
  292 + JSONObject subscribeKey = new JSONObject();
  293 + subscribeKey.put("app", "rtp");
  294 + subscribeKey.put("id", streamId);
  295 +
  296 + subscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_publish, subscribeKey, event);
285 297 //
286 298 StringBuffer content = new StringBuffer(200);
287   - content.append("v=0\r\n");
288   - content.append("o="+channelId+" 0 0 IN IP4 "+mediaInfo.getWanIp()+"\r\n");
289   - content.append("s=Play\r\n");
290   - content.append("c=IN IP4 "+mediaInfo.getWanIp()+"\r\n");
291   - content.append("t=0 0\r\n");
292   - if("TCP-PASSIVE".equals(streamMode)) {
293   - content.append("m=video "+ mediaPort +" TCP/RTP/AVP 96 98 97\r\n");
  299 + content.append("v=0\r\n");
  300 + content.append("o="+channelId+" 0 0 IN IP4 "+mediaInfo.getWanIp()+"\r\n");
  301 + content.append("s=Play\r\n");
  302 + content.append("c=IN IP4 "+mediaInfo.getWanIp()+"\r\n");
  303 + content.append("t=0 0\r\n");
  304 + if("TCP-PASSIVE".equals(streamMode)) {
  305 + content.append("m=video "+ mediaPort +" TCP/RTP/AVP 126 125 99 34 98 97 96\r\n");
294 306 }else if ("TCP-ACTIVE".equals(streamMode)) {
295   - content.append("m=video "+ mediaPort +" TCP/RTP/AVP 96 98 97\r\n");
  307 + content.append("m=video "+ mediaPort +" TCP/RTP/AVP 126 125 99 34 98 97 96\r\n");
296 308 }else if("UDP".equals(streamMode)) {
297   - content.append("m=video "+ mediaPort +" RTP/AVP 96 98 97\r\n");
  309 + content.append("m=video "+ mediaPort +" RTP/AVP 126 125 99 34 98 97 96\r\n");
298 310 }
299   - content.append("a=recvonly\r\n");
300   - content.append("a=rtpmap:96 PS/90000\r\n");
301   - content.append("a=rtpmap:98 H264/90000\r\n");
302   - content.append("a=rtpmap:97 MPEG4/90000\r\n");
303   - if("TCP-PASSIVE".equals(streamMode)){ // tcp被动模式
304   - content.append("a=setup:passive\r\n");
  311 + content.append("a=recvonly\r\n");
  312 + content.append("a=fmtp:126 profile-level-id=42e01e\r\n");
  313 + content.append("a=rtpmap:126 H264/90000\r\n");
  314 + content.append("a=rtpmap:125 H264S/90000\r\n");
  315 + content.append("a=fmtp:125 profile-level-id=42e01e\r\n");
  316 + content.append("a=rtpmap:99 MP4V-ES/90000\r\n");
  317 + content.append("a=fmtp:99 profile-level-id=3\r\n");
  318 + content.append("a=rtpmap:98 H264/90000\r\n");
  319 + content.append("a=rtpmap:97 MPEG4/90000\r\n");
  320 + content.append("a=rtpmap:96 PS/90000\r\n");
  321 + if("TCP-PASSIVE".equals(streamMode)){ // tcp被动模式
  322 + content.append("a=setup:passive\r\n");
305 323 content.append("a=connection:new\r\n");
306   - }else if ("TCP-ACTIVE".equals(streamMode)) { // tcp主动模式
  324 + }else if ("TCP-ACTIVE".equals(streamMode)) { // tcp主动模式
307 325 content.append("a=setup:active\r\n");
308 326 content.append("a=connection:new\r\n");
309 327 }
310   - content.append("y="+ssrc+"\r\n");//ssrc
311   -
312   - Request request = headerProvider.createInviteRequest(device, channelId, content.toString(), null, "live", null, ssrc);
313   -
314   - ClientTransaction transaction = transmitRequest(device, request);
315   - streamSession.put(ssrc, transaction);
  328 + content.append("y="+ssrc+"\r\n");//ssrc
  329 +
  330 + Request request = headerProvider.createInviteRequest(device, channelId, content.toString(), null, "live", null, ssrc);
  331 +
  332 + ClientTransaction transaction = transmitRequest(device, request);
  333 + streamSession.put(ssrc, transaction);
316 334 DeviceChannel deviceChannel = storager.queryChannel(device.getDeviceId(), channelId);
317 335 if (deviceChannel != null) {
318 336 deviceChannel.setSsrc(ssrc);
319 337 storager.updateChannel(device.getDeviceId(), deviceChannel);
320 338 }
321 339  
322   - StreamInfo streamInfo = new StreamInfo();
323   - streamInfo.setSsrc(ssrc);
324   - streamInfo.setCahnnelId(channelId);
325   - streamInfo.setDeviceID(device.getDeviceId());
326   - storager.startPlay(streamInfo);
327   - return streamInfo;
  340 + // TODO 订阅SIP response,处理对方的错误返回
  341 +
  342 +
328 343 } catch ( SipException | ParseException | InvalidArgumentException e) {
329 344 e.printStackTrace();
330   - return null;
331   - }
  345 + }
332 346 }
333 347  
334 348 /**
... ... @@ -340,10 +354,18 @@ public class SIPCommander implements ISIPCommander {
340 354 * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
341 355 */
342 356 @Override
343   - public StreamInfo playbackStreamCmd(Device device, String channelId, String startTime, String endTime) {
  357 + public void playbackStreamCmd(Device device, String channelId, String startTime, String endTime, ZLMHttpHookSubscribe.Event event) {
344 358 try {
345 359 MediaServerConfig mediaInfo = storager.getMediaInfo();
346 360 String ssrc = streamSession.createPlayBackSsrc();
  361 + String streamId = String.format("%08x", Integer.parseInt(ssrc)).toUpperCase();
  362 + // 添加订阅
  363 + JSONObject subscribeKey = new JSONObject();
  364 + subscribeKey.put("app", "rtp");
  365 + subscribeKey.put("id", streamId);
  366 +
  367 + subscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_publish, subscribeKey, event);
  368 +
347 369 //
348 370 StringBuffer content = new StringBuffer(200);
349 371 content.append("v=0\r\n");
... ... @@ -362,16 +384,22 @@ public class SIPCommander implements ISIPCommander {
362 384 }
363 385 String streamMode = device.getStreamMode().toUpperCase();
364 386 if("TCP-PASSIVE".equals(streamMode)) {
365   - content.append("m=video "+ mediaPort +" TCP/RTP/AVP 96 98 97\r\n");
  387 + content.append("m=video "+ mediaPort +" TCP/RTP/AVP 126 125 99 34 98 97 96\r\n");
366 388 }else if ("TCP-ACTIVE".equals(streamMode)) {
367   - content.append("m=video "+ mediaPort +" TCP/RTP/AVP 96 98 97\r\n");
  389 + content.append("m=video "+ mediaPort +" TCP/RTP/AVP 126 125 99 34 98 97 96\r\n");
368 390 }else if("UDP".equals(streamMode)) {
369   - content.append("m=video "+ mediaPort +" RTP/AVP 96 98 97\r\n");
  391 + content.append("m=video "+ mediaPort +" RTP/AVP 126 125 99 34 98 97 96\r\n");
370 392 }
371   - content.append("a=recvonly\r\n");
372   - content.append("a=rtpmap:96 PS/90000\r\n");
373   - content.append("a=rtpmap:98 H264/90000\r\n");
374   - content.append("a=rtpmap:97 MPEG4/90000\r\n");
  393 + content.append("a=recvonly\r\n");
  394 + content.append("a=fmtp:126 profile-level-id=42e01e\r\n");
  395 + content.append("a=rtpmap:126 H264/90000\r\n");
  396 + content.append("a=rtpmap:125 H264S/90000\r\n");
  397 + content.append("a=fmtp:125 profile-level-id=42e01e\r\n");
  398 + content.append("a=rtpmap:99 MP4V-ES/90000\r\n");
  399 + content.append("a=fmtp:99 profile-level-id=3\r\n");
  400 + content.append("a=rtpmap:98 H264/90000\r\n");
  401 + content.append("a=rtpmap:97 MPEG4/90000\r\n");
  402 + content.append("a=rtpmap:96 PS/90000\r\n");
375 403 if("TCP-PASSIVE".equals(streamMode)){ // tcp被动模式
376 404 content.append("a=setup:passive\r\n");
377 405 content.append("a=connection:new\r\n");
... ... @@ -386,16 +414,8 @@ public class SIPCommander implements ISIPCommander {
386 414 ClientTransaction transaction = transmitRequest(device, request);
387 415 streamSession.put(ssrc, transaction);
388 416  
389   - StreamInfo streamInfo = new StreamInfo();
390   - streamInfo.setSsrc(ssrc);
391   - streamInfo.setCahnnelId(channelId);
392   - streamInfo.setDeviceID(device.getDeviceId());
393   - boolean b = storager.startPlayback(streamInfo);
394   - return streamInfo;
395   -
396 417 } catch ( SipException | ParseException | InvalidArgumentException e) {
397 418 e.printStackTrace();
398   - return null;
399 419 }
400 420 }
401 421  
... ... @@ -433,6 +453,7 @@ public class SIPCommander implements ISIPCommander {
433 453 clientTransaction = udpSipProvider.getNewClientTransaction(byeRequest);
434 454 }
435 455 dialog.sendRequest(clientTransaction);
  456 + streamSession.remove(ssrc);
436 457 } catch (TransactionDoesNotExistException e) {
437 458 e.printStackTrace();
438 459 } catch (SipException e) {
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/AckRequestProcessor.java
... ... @@ -21,14 +21,12 @@ public class AckRequestProcessor extends SIPRequestAbstractProcessor {
21 21 * 处理 ACK请求
22 22 *
23 23 * @param evt
24   - * @param layer
25   - * @param transaction
26   - * @param config
27   - */
  24 + */
28 25 @Override
29 26 public void process(RequestEvent evt) {
30 27 Request request = evt.getRequest();
31 28 Dialog dialog = evt.getDialog();
  29 + if (dialog == null) return;
32 30 try {
33 31 Request ackRequest = null;
34 32 CSeq csReq = (CSeq) request.getHeader(CSeq.NAME);
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/ByeRequestProcessor.java
1 1 package com.genersoft.iot.vmp.gb28181.transmit.request.impl;
2 2  
  3 +import javax.sip.InvalidArgumentException;
3 4 import javax.sip.RequestEvent;
  5 +import javax.sip.SipException;
  6 +import javax.sip.message.Response;
4 7  
5 8 import com.genersoft.iot.vmp.gb28181.transmit.request.SIPRequestAbstractProcessor;
6 9  
  10 +import java.text.ParseException;
  11 +
7 12 /**
8 13 * @Description: BYE请求处理器
9 14 * @author: swwheihei
... ... @@ -11,18 +16,35 @@ import com.genersoft.iot.vmp.gb28181.transmit.request.SIPRequestAbstractProcesso
11 16 */
12 17 public class ByeRequestProcessor extends SIPRequestAbstractProcessor {
13 18  
14   - /**
  19 + /**
15 20 * 处理BYE请求
16   - *
17 21 * @param evt
18   - * @param layer
19   - * @param transaction
20   - * @param config
21   - */
  22 + */
22 23 @Override
23 24 public void process(RequestEvent evt) {
  25 + try {
  26 + responseAck(evt);
  27 + } catch (SipException e) {
  28 + e.printStackTrace();
  29 + } catch (InvalidArgumentException e) {
  30 + e.printStackTrace();
  31 + } catch (ParseException e) {
  32 + e.printStackTrace();
  33 + }
24 34 // TODO 优先级99 Bye Request消息实现,此消息一般为级联消息,上级给下级发送视频停止指令
25 35  
26 36 }
27 37  
  38 + /***
  39 + * 回复200 OK
  40 + * @param evt
  41 + * @throws SipException
  42 + * @throws InvalidArgumentException
  43 + * @throws ParseException
  44 + */
  45 + private void responseAck(RequestEvent evt) throws SipException, InvalidArgumentException, ParseException {
  46 + Response response = getMessageFactory().createResponse(Response.OK, evt.getRequest());
  47 + getServerTransaction(evt).sendResponse(response);
  48 + }
  49 +
28 50 }
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/MessageRequestProcessor.java
... ... @@ -184,10 +184,11 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
184 184 DeviceChannel deviceChannel = new DeviceChannel();
185 185 deviceChannel.setName(channelName);
186 186 deviceChannel.setChannelId(channelDeviceId);
187   - if (status.equals("ON") || status.equals("On")) {
  187 + // ONLINE OFFLINE HIKVISION DS-7716N-E4 NVR的兼容性处理
  188 + if (status.equals("ON") || status.equals("On") || status.equals("ONLINE")) {
188 189 deviceChannel.setStatus(1);
189 190 }
190   - if (status.equals("OFF") || status.equals("Off")) {
  191 + if (status.equals("OFF") || status.equals("Off") || status.equals("OFFLINE")) {
191 192 deviceChannel.setStatus(0);
192 193 }
193 194  
... ...
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
... ... @@ -49,6 +49,9 @@ public class ZLMHttpHookListener {
49 49 @Autowired
50 50 private ZLMRESTfulUtils zlmresTfulUtils;
51 51  
  52 + @Autowired
  53 + private ZLMHttpHookSubscribe subscribe;
  54 +
52 55 @Value("${media.ip}")
53 56 private String mediaIp;
54 57  
... ... @@ -128,30 +131,38 @@ public class ZLMHttpHookListener {
128 131 }
129 132 String app = json.getString("app");
130 133 String streamId = json.getString("id");
131   - if ("rtp".equals(app)) {
132   - String ssrc = new DecimalFormat("0000000000").format(Integer.parseInt(streamId, 16));
133   - StreamInfo streamInfoForPlay = storager.queryPlayBySSRC(ssrc);
134   - if ("rtp".equals(app) && streamInfoForPlay != null ) {
135   - MediaServerConfig mediaInfo = storager.getMediaInfo();
136   - streamInfoForPlay.setFlv(String.format("http://%s:%s/rtp/%s.flv", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
137   - streamInfoForPlay.setWs_flv(String.format("ws://%s:%s/rtp/%s.flv", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
138   - streamInfoForPlay.setRtmp(String.format("rtmp://%s:%s/rtp/%s", mediaInfo.getWanIp(), mediaInfo.getRtmpPort(), streamId));
139   - streamInfoForPlay.setHls(String.format("http://%s:%s/rtp/%s/hls.m3u8", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
140   - streamInfoForPlay.setRtsp(String.format("rtsp://%s:%s/rtp/%s", mediaInfo.getWanIp(), mediaInfo.getRtspPort(), streamId));
141   - storager.startPlay(streamInfoForPlay);
142   - }
143 134  
144   - StreamInfo streamInfoForPlayBack = storager.queryPlaybackBySSRC(ssrc);
145   - if ("rtp".equals(app) && streamInfoForPlayBack != null ) {
146   - MediaServerConfig mediaInfo = storager.getMediaInfo();
147   - streamInfoForPlayBack.setFlv(String.format("http://%s:%s/rtp/%s.flv", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
148   - streamInfoForPlayBack.setWs_flv(String.format("ws://%s:%s/rtp/%s.flv", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
149   - streamInfoForPlayBack.setRtmp(String.format("rtmp://%s:%s/rtp/%s", mediaInfo.getWanIp(), mediaInfo.getRtmpPort(), streamId));
150   - streamInfoForPlayBack.setHls(String.format("http://%s:%s/rtp/%s/hls.m3u8", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
151   - streamInfoForPlayBack.setRtsp(String.format("rtsp://%s:%s/rtp/%s", mediaInfo.getWanIp(), mediaInfo.getRtspPort(), streamId));
152   - storager.startPlayback(streamInfoForPlayBack);
153   - }
154   - }
  135 + ZLMHttpHookSubscribe.Event subscribe = this.subscribe.getSubscribe(ZLMHttpHookSubscribe.HookType.on_publish, json);
  136 + if (subscribe != null) subscribe.response(json);
  137 +
  138 +// if ("rtp".equals(app)) {
  139 +// String ssrc = new DecimalFormat("0000000000").format(Integer.parseInt(streamId, 16));
  140 +// StreamInfo streamInfoForPlay = storager.queryPlayBySSRC(ssrc);
  141 +// if ("rtp".equals(app) && streamInfoForPlay != null ) {
  142 +// MediaServerConfig mediaInfo = storager.getMediaInfo();
  143 +// streamInfoForPlay.setFlv(String.format("http://%s:%s/rtp/%s.flv", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
  144 +// streamInfoForPlay.setWs_flv(String.format("ws://%s:%s/rtp/%s.flv", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
  145 +// streamInfoForPlay.setFmp4(String.format("http://%s:%s/rtp/%s.live.mp4", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
  146 +// streamInfoForPlay.setWs_fmp4(String.format("ws://%s:%s/rtp/%s.live.mp4", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
  147 +// streamInfoForPlay.setRtmp(String.format("rtmp://%s:%s/rtp/%s", mediaInfo.getWanIp(), mediaInfo.getRtmpPort(), streamId));
  148 +// streamInfoForPlay.setHls(String.format("http://%s:%s/rtp/%s/hls.m3u8", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
  149 +// streamInfoForPlay.setRtsp(String.format("rtsp://%s:%s/rtp/%s", mediaInfo.getWanIp(), mediaInfo.getRtspPort(), streamId));
  150 +// storager.startPlay(streamInfoForPlay);
  151 +// }
  152 +//
  153 +// StreamInfo streamInfoForPlayBack = storager.queryPlaybackBySSRC(ssrc);
  154 +// if ("rtp".equals(app) && streamInfoForPlayBack != null ) {
  155 +// MediaServerConfig mediaInfo = storager.getMediaInfo();
  156 +// streamInfoForPlayBack.setFlv(String.format("http://%s:%s/rtp/%s.flv", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
  157 +// streamInfoForPlayBack.setWs_flv(String.format("ws://%s:%s/rtp/%s.flv", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
  158 +// streamInfoForPlayBack.setFmp4(String.format("http://%s:%s/rtp/%s.live.mp4", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
  159 +// streamInfoForPlayBack.setWs_fmp4(String.format("ws://%s:%s/rtp/%s.live.mp4", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
  160 +// streamInfoForPlayBack.setRtmp(String.format("rtmp://%s:%s/rtp/%s", mediaInfo.getWanIp(), mediaInfo.getRtmpPort(), streamId));
  161 +// streamInfoForPlayBack.setHls(String.format("http://%s:%s/rtp/%s/hls.m3u8", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
  162 +// streamInfoForPlayBack.setRtsp(String.format("rtsp://%s:%s/rtp/%s", mediaInfo.getWanIp(), mediaInfo.getRtspPort(), streamId));
  163 +// storager.startPlayback(streamInfoForPlayBack);
  164 +// }
  165 +// }
155 166  
156 167 // TODO Auto-generated method stub
157 168  
... ...
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookSubscribe.java 0 → 100644
  1 +package com.genersoft.iot.vmp.media.zlm;
  2 +
  3 +import com.alibaba.fastjson.JSON;
  4 +import com.alibaba.fastjson.JSONObject;
  5 +import com.genersoft.iot.vmp.common.StreamInfo;
  6 +import com.genersoft.iot.vmp.conf.MediaServerConfig;
  7 +import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
  8 +import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
  9 +import org.slf4j.Logger;
  10 +import org.slf4j.LoggerFactory;
  11 +import org.springframework.beans.factory.annotation.Autowired;
  12 +import org.springframework.beans.factory.annotation.Value;
  13 +import org.springframework.http.HttpStatus;
  14 +import org.springframework.http.ResponseEntity;
  15 +import org.springframework.stereotype.Component;
  16 +import org.springframework.util.ConcurrentReferenceHashMap;
  17 +import org.springframework.web.bind.annotation.*;
  18 +
  19 +import javax.servlet.http.HttpServletRequest;
  20 +import java.math.BigInteger;
  21 +import java.text.DecimalFormat;
  22 +import java.util.HashMap;
  23 +import java.util.Map;
  24 +import java.util.concurrent.ConcurrentHashMap;
  25 +
  26 +/**
  27 + * @Description:针对 ZLMediaServer的hook事件订阅
  28 + * @author: pan
  29 + * @date: 2020年12月2日 21:17:32
  30 + */
  31 +@Component
  32 +public class ZLMHttpHookSubscribe {
  33 +
  34 + private final static Logger logger = LoggerFactory.getLogger(ZLMHttpHookSubscribe.class);
  35 +
  36 + public enum HookType{
  37 + on_flow_report,
  38 + on_http_access,
  39 + on_play,
  40 + on_publish,
  41 + on_record_mp4,
  42 + on_rtsp_auth,
  43 + on_rtsp_realm,
  44 + on_shell_login,
  45 + on_stream_changed,
  46 + on_stream_none_reader,
  47 + on_stream_not_found,
  48 + on_server_started
  49 + }
  50 +
  51 + public interface Event{
  52 + void response(JSONObject response);
  53 + }
  54 +
  55 + private Map<HookType, Map<JSONObject, ZLMHttpHookSubscribe.Event>> allSubscribes = new ConcurrentHashMap<>();
  56 +
  57 + public void addSubscribe(HookType type, JSONObject hookResponse, ZLMHttpHookSubscribe.Event event) {
  58 + Map<JSONObject, Event> eventMap = allSubscribes.get(type);
  59 + if (eventMap == null) {
  60 + eventMap = new HashMap<JSONObject, Event>();
  61 + allSubscribes.put(type,eventMap);
  62 + }
  63 + eventMap.put(hookResponse, event);
  64 + }
  65 +
  66 + public ZLMHttpHookSubscribe.Event getSubscribe(HookType type, JSONObject hookResponse) {
  67 + ZLMHttpHookSubscribe.Event event= null;
  68 + Map<JSONObject, Event> eventMap = allSubscribes.get(type);
  69 + if (eventMap == null) {
  70 + return null;
  71 + }
  72 + for (JSONObject key : eventMap.keySet()) {
  73 + Boolean result = null;
  74 + for (String s : key.keySet()) {
  75 + String string = hookResponse.getString(s);
  76 + String string1 = key.getString(s);
  77 + if (result == null) {
  78 + result = key.getString(s).equals(hookResponse.getString(s));
  79 + }else {
  80 + result = result && key.getString(s).equals(hookResponse.getString(s));
  81 + }
  82 +
  83 + }
  84 + if (result) {
  85 + event = eventMap.get(key);
  86 + }
  87 + }
  88 + return event;
  89 + }
  90 +}
... ...
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java
... ... @@ -36,6 +36,9 @@ public class ZLMRunner implements CommandLineRunner {
36 36 @Value("${media.wanIp}")
37 37 private String mediaWanIp;
38 38  
  39 + @Value("${media.hookIp}")
  40 + private String mediaHookIp;
  41 +
39 42 @Value("${media.port}")
40 43 private int mediaPort;
41 44  
... ... @@ -51,6 +54,9 @@ public class ZLMRunner implements CommandLineRunner {
51 54 @Value("${server.port}")
52 55 private String serverPort;
53 56  
  57 + @Value("${media.autoConfig}")
  58 + private boolean autoConfig;
  59 +
54 60 @Autowired
55 61 private ZLMRESTfulUtils zlmresTfulUtils;
56 62  
... ... @@ -61,8 +67,7 @@ public class ZLMRunner implements CommandLineRunner {
61 67 MediaServerConfig mediaServerConfig = getMediaServerConfig();
62 68 if (mediaServerConfig != null) {
63 69 logger.info("zlm接入成功...");
64   - logger.info("设置zlm...");
65   - saveZLMConfig();
  70 + if (autoConfig) saveZLMConfig();
66 71 mediaServerConfig = getMediaServerConfig();
67 72 storager.updateMediaInfo(mediaServerConfig);
68 73 }
... ... @@ -91,12 +96,12 @@ public class ZLMRunner implements CommandLineRunner {
91 96 }
92 97  
93 98 private void saveZLMConfig() {
94   - String hookIP = sipIP;
95   - if (mediaIp.equals(sipIP)) {
96   - hookIP = "127.0.0.1";
  99 + logger.info("设置zlm...");
  100 + if (StringUtils.isEmpty(mediaHookIp)) {
  101 + mediaHookIp = sipIP;
97 102 }
98 103  
99   - String hookPrex = String.format("http://%s:%s/index/hook", hookIP, serverPort);
  104 + String hookPrex = String.format("http://%s:%s/index/hook", mediaHookIp, serverPort);
100 105 Map<String, Object> param = new HashMap<>();
101 106 param.put("api.secret",mediaSecret); // -profile:v Baseline
102 107 param.put("ffmpeg.cmd","%s -fflags nobuffer -rtsp_transport tcp -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s");
... ...
src/main/java/com/genersoft/iot/vmp/storager/redis/VideoManagerRedisStoragerImpl.java
... ... @@ -555,6 +555,10 @@ public class VideoManagerRedisStoragerImpl implements IVideoManagerStorager {
555 555 List<Object> playLeys = redis.scan(String.format("%S_*_%s_%s", VideoManagerConstants.PLAY_BLACK_PREFIX,
556 556 deviceId,
557 557 code));
  558 + if (playLeys == null || playLeys.size() == 0) {
  559 + playLeys = redis.scan(String.format("%S_*_*_%s", VideoManagerConstants.PLAY_BLACK_PREFIX,
  560 + deviceId));
  561 + }
558 562 if (playLeys == null || playLeys.size() == 0) return null;
559 563 return (StreamInfo)redis.get(playLeys.get(0).toString());
560 564 }
... ...
src/main/java/com/genersoft/iot/vmp/vmanager/play/PlayController.java
... ... @@ -4,7 +4,10 @@ import com.alibaba.fastjson.JSON;
4 4 import com.alibaba.fastjson.JSONArray;
5 5 import com.genersoft.iot.vmp.common.StreamInfo;
6 6 import com.genersoft.iot.vmp.conf.MediaServerConfig;
  7 +import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
  8 +import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
7 9 import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
  10 +import com.genersoft.iot.vmp.vmanager.service.IPlayService;
8 11 import org.slf4j.Logger;
9 12 import org.slf4j.LoggerFactory;
10 13 import org.springframework.beans.factory.annotation.Autowired;
... ... @@ -22,6 +25,10 @@ import com.alibaba.fastjson.JSONObject;
22 25 import com.genersoft.iot.vmp.gb28181.bean.Device;
23 26 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
24 27 import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
  28 +import org.springframework.web.context.request.async.DeferredResult;
  29 +
  30 +import java.text.DecimalFormat;
  31 +import java.util.UUID;
25 32  
26 33 @CrossOrigin
27 34 @RestController
... ... @@ -39,94 +46,57 @@ public class PlayController {
39 46 @Autowired
40 47 private ZLMRESTfulUtils zlmresTfulUtils;
41 48  
42   - @Value("${media.closeWaitRTPInfo}")
43   - private boolean closeWaitRTPInfo;
  49 + @Autowired
  50 + private DeferredResultHolder resultHolder;
  51 +
  52 + @Autowired
  53 + private IPlayService playService;
44 54  
45 55 @GetMapping("/play/{deviceId}/{channelId}")
46   - public ResponseEntity<String> play(@PathVariable String deviceId, @PathVariable String channelId,
47   - Integer getEncoding) {
  56 + public DeferredResult<ResponseEntity<String>> play(@PathVariable String deviceId,
  57 + @PathVariable String channelId) {
  58 +
48 59  
49   - if (getEncoding == null) getEncoding = 0;
50   - getEncoding = closeWaitRTPInfo ? 0 : getEncoding;
51 60 Device device = storager.queryVideoDevice(deviceId);
52 61 StreamInfo streamInfo = storager.queryPlayByDevice(deviceId, channelId);
53 62  
  63 + UUID uuid = UUID.randomUUID();
  64 + DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>();
  65 + // 超时处理
  66 + result.onTimeout(()->{
  67 + logger.warn(String.format("设备点播超时,deviceId:%s ,channelId:%s", deviceId, channelId));
  68 + RequestMessage msg = new RequestMessage();
  69 + msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
  70 + msg.setData("Timeout");
  71 + resultHolder.invokeResult(msg);
  72 + });
  73 + // 录像查询以channelId作为deviceId查询
  74 + resultHolder.put(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid, result);
  75 +
54 76 if (streamInfo == null) {
55   - streamInfo = cmder.playStreamCmd(device, channelId);
  77 + // 发送点播消息
  78 + cmder.playStreamCmd(device, channelId, (JSONObject response) -> {
  79 + logger.info("收到订阅消息: " + response.toJSONString());
  80 + playService.onPublishHandlerForPlay(response, deviceId, channelId, uuid.toString());
  81 + });
56 82 } else {
57 83 String streamId = String.format("%08x", Integer.parseInt(streamInfo.getSsrc())).toUpperCase();
58 84 JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(streamId);
59 85 if (rtpInfo.getBoolean("exist")) {
60   - return new ResponseEntity<String>(JSON.toJSONString(streamInfo), HttpStatus.OK);
  86 + RequestMessage msg = new RequestMessage();
  87 + msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
  88 + msg.setData(JSON.toJSONString(streamInfo));
  89 + resultHolder.invokeResult(msg);
61 90 } else {
62 91 storager.stopPlay(streamInfo);
63   - streamInfo = cmder.playStreamCmd(device, channelId);
64   - }
65   - }
66   - String streamId = String.format("%08x", Integer.parseInt(streamInfo.getSsrc())).toUpperCase();
67   - // 等待推流, TODO 默认超时30s
68   - boolean lockFlag = true;
69   - boolean rtpPushed = false;
70   - long startTime = System.currentTimeMillis();
71   - JSONObject rtpInfo = null;
72   -
73   - if (getEncoding == 1) {
74   - while (lockFlag) {
75   - try {
76   - if (System.currentTimeMillis() - startTime > 60 * 1000) {
77   - storager.stopPlay(streamInfo);
78   - logger.info("播放等待超时");
79   - return new ResponseEntity<String>("timeout", HttpStatus.OK);
80   - } else {
81   - streamInfo = storager.queryPlayByDevice(deviceId, channelId);
82   - if (!rtpPushed) {
83   - logger.info("查询RTP推流信息...");
84   - rtpInfo = zlmresTfulUtils.getRtpInfo(streamId);
85   - }
86   - if (rtpInfo != null && rtpInfo.getBoolean("exist") && streamInfo != null
87   - && streamInfo.getFlv() != null) {
88   - logger.info("查询流编码信息:" + streamInfo.getFlv());
89   - rtpPushed = true;
90   - Thread.sleep(2000);
91   - JSONObject mediaInfo = zlmresTfulUtils.getMediaInfo("rtp", "rtmp", streamId);
92   - if (mediaInfo.getInteger("code") == 0 && mediaInfo.getBoolean("online")) {
93   - lockFlag = false;
94   - logger.info("流编码信息已获取");
95   - JSONArray tracks = mediaInfo.getJSONArray("tracks");
96   - streamInfo.setTracks(tracks);
97   - storager.startPlay(streamInfo);
98   - } else {
99   - logger.info("流编码信息未获取,2秒后重试...");
100   - }
101   - } else {
102   - Thread.sleep(2000);
103   - continue;
104   - }
105   - }
106   - } catch (InterruptedException e) {
107   - e.printStackTrace();
108   - }
  92 + // TODO playStreamCmd 超时处理
  93 + cmder.playStreamCmd(device, channelId, (JSONObject response) -> {
  94 + logger.info("收到订阅消息: " + response.toJSONString());
  95 + playService.onPublishHandlerForPlay(response, deviceId, channelId, uuid.toString());
  96 + });
109 97 }
110   - } else {
111   - String flv = storager.getMediaInfo().getWanIp() + ":" + storager.getMediaInfo().getHttpPort() + "/rtp/"
112   - + streamId + ".flv";
113   - streamInfo.setFlv("http://" + flv);
114   - streamInfo.setWs_flv("ws://" + flv);
115   - storager.startPlay(streamInfo);
116   - }
117   -
118   - if (logger.isDebugEnabled()) {
119   - logger.debug(String.format("设备预览 API调用,deviceId:%s ,channelId:%s", deviceId, channelId));
120   - logger.debug("设备预览 API调用,ssrc:" + streamInfo.getSsrc() + ",ZLMedia streamId:"
121   - + Integer.toHexString(Integer.parseInt(streamInfo.getSsrc())));
122   - }
123   -
124   - if (streamInfo != null) {
125   - return new ResponseEntity<String>(JSON.toJSONString(streamInfo), HttpStatus.OK);
126   - } else {
127   - logger.warn("设备预览API调用失败!");
128   - return new ResponseEntity<String>(HttpStatus.INTERNAL_SERVER_ERROR);
129 98 }
  99 + return result;
130 100 }
131 101  
132 102 @PostMapping("/play/{ssrc}/stop")
... ... @@ -172,17 +142,28 @@ public class PlayController {
172 142 MediaServerConfig mediaInfo = storager.getMediaInfo();
173 143 String dstUrl = String.format("rtmp://%s:%s/convert/%s", "127.0.0.1", mediaInfo.getRtmpPort(),
174 144 streamId );
175   - JSONObject jsonObject = zlmresTfulUtils.addFFmpegSource(streamInfo.getRtsp(), dstUrl, "1000000");
  145 + String srcUrl = String.format("rtsp://%s:%s/rtp/%s", "127.0.0.1", mediaInfo.getRtspPort(), streamId);
  146 + JSONObject jsonObject = zlmresTfulUtils.addFFmpegSource(srcUrl, dstUrl, "1000000");
176 147 System.out.println(jsonObject);
177 148 JSONObject result = new JSONObject();
178 149 if (jsonObject != null && jsonObject.getInteger("code") == 0) {
179 150 result.put("code", 0);
180 151 JSONObject data = jsonObject.getJSONObject("data");
181 152 if (data != null) {
182   - result.put("key", data.getString("key"));
183   - result.put("rtmp", dstUrl);
184   - result.put("flv", String.format("http://%s:%s/convert/%s.flv", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
185   - result.put("ws_flv", String.format("ws://%s:%s/convert/%s.flv", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
  153 + result.put("key", data.getString("key"));
  154 + StreamInfo streamInfoResult = new StreamInfo();
  155 + streamInfoResult.setRtmp(dstUrl);
  156 + streamInfoResult.setRtsp(String.format("rtsp://%s:%s/convert/%s", mediaInfo.getWanIp(), mediaInfo.getRtspPort(), streamId));
  157 + streamInfoResult.setStreamId(streamId);
  158 + streamInfoResult.setFlv(String.format("http://%s:%s/convert/%s.flv", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
  159 + streamInfoResult.setWs_flv(String.format("ws://%s:%s/convert/%s.flv", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
  160 + streamInfoResult.setHls(String.format("http://%s:%s/convert/%s/hls.m3u8", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
  161 + streamInfoResult.setWs_hls(String.format("ws://%s:%s/convert/%s/hls.m3u8", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
  162 + streamInfoResult.setFmp4(String.format("http://%s:%s/convert/%s.live.mp4", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
  163 + streamInfoResult.setWs_fmp4(String.format("ws://%s:%s/convert/%s.live.mp4", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
  164 + streamInfoResult.setTs(String.format("http://%s:%s/convert/%s.live.ts", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
  165 + streamInfoResult.setWs_ts(String.format("ws://%s:%s/convert/%s.live.ts", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
  166 + result.put("data", streamInfoResult);
186 167 }
187 168 }else {
188 169 result.put("code", 1);
... ...
src/main/java/com/genersoft/iot/vmp/vmanager/playback/PlaybackController.java
... ... @@ -3,7 +3,10 @@ package com.genersoft.iot.vmp.vmanager.playback;
3 3 import com.alibaba.fastjson.JSON;
4 4 import com.alibaba.fastjson.JSONArray;
5 5 import com.genersoft.iot.vmp.common.StreamInfo;
  6 +import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
  7 +import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
6 8 import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
  9 +import com.genersoft.iot.vmp.vmanager.service.IPlayService;
7 10 import org.slf4j.Logger;
8 11 import org.slf4j.LoggerFactory;
9 12 import org.springframework.beans.factory.annotation.Autowired;
... ... @@ -22,6 +25,9 @@ import com.alibaba.fastjson.JSONObject;
22 25 import com.genersoft.iot.vmp.gb28181.bean.Device;
23 26 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
24 27 import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
  28 +import org.springframework.web.context.request.async.DeferredResult;
  29 +
  30 +import java.util.UUID;
25 31  
26 32 @CrossOrigin
27 33 @RestController
... ... @@ -39,105 +45,42 @@ public class PlaybackController {
39 45 @Autowired
40 46 private ZLMRESTfulUtils zlmresTfulUtils;
41 47  
42   - @Value("${media.closeWaitRTPInfo}")
43   - private boolean closeWaitRTPInfo;
  48 + @Autowired
  49 + private IPlayService playService;
  50 +
  51 + @Autowired
  52 + private DeferredResultHolder resultHolder;
44 53  
45 54 @GetMapping("/playback/{deviceId}/{channelId}")
46   - public ResponseEntity<String> play(@PathVariable String deviceId, @PathVariable String channelId, String startTime,
47   - String endTime) {
  55 + public DeferredResult<ResponseEntity<String>> play(@PathVariable String deviceId, @PathVariable String channelId, String startTime,
  56 + String endTime) {
48 57  
49 58 if (logger.isDebugEnabled()) {
50 59 logger.debug(String.format("设备回放 API调用,deviceId:%s ,channelId:%s", deviceId, channelId));
51 60 }
52   -
53   - if (StringUtils.isEmpty(deviceId) || StringUtils.isEmpty(channelId)) {
54   - String log = String.format("设备回放 API调用失败,deviceId:%s ,channelId:%s", deviceId, channelId);
55   - logger.warn(log);
56   - return new ResponseEntity<String>(log, HttpStatus.BAD_REQUEST);
57   - }
58   -
  61 + UUID uuid = UUID.randomUUID();
  62 + DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>();
  63 + // 超时处理
  64 + result.onTimeout(()->{
  65 + logger.warn(String.format("设备回放超时,deviceId:%s ,channelId:%s", deviceId, channelId));
  66 + RequestMessage msg = new RequestMessage();
  67 + msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
  68 + msg.setData("Timeout");
  69 + resultHolder.invokeResult(msg);
  70 + });
59 71 Device device = storager.queryVideoDevice(deviceId);
60 72 StreamInfo streamInfo = storager.queryPlaybackByDevice(deviceId, channelId);
61   -
62 73 if (streamInfo != null) {
  74 + // 停止之前的回放
63 75 cmder.streamByeCmd(streamInfo.getSsrc());
64 76 }
  77 + resultHolder.put(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid, result);
  78 + cmder.playbackStreamCmd(device, channelId, startTime, endTime, (JSONObject response) -> {
  79 + logger.info("收到订阅消息: " + response.toJSONString());
  80 + playService.onPublishHandlerForPlayBack(response, deviceId, channelId, uuid.toString());
  81 + });
65 82  
66   - // }else {
67   - // String streamId = String.format("%08x",
68   - // Integer.parseInt(streamInfo.getSsrc())).toUpperCase();
69   - // JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(streamId);
70   - // if (rtpInfo.getBoolean("exist")) {
71   - // return new
72   - // ResponseEntity<String>(JSON.toJSONString(streamInfo),HttpStatus.OK);
73   - // }else {
74   - // storager.stopPlayback(streamInfo);
75   - // streamInfo = cmder.playbackStreamCmd(device, channelId, startTime, endTime);
76   - // }
77   - // }
78   - streamInfo = cmder.playbackStreamCmd(device, channelId, startTime, endTime);
79   -
80   - String streamId = String.format("%08x", Integer.parseInt(streamInfo.getSsrc())).toUpperCase();
81   -
82   - if (logger.isDebugEnabled()) {
83   - logger.debug("设备回放 API调用,ssrc:" + streamInfo.getSsrc() + ",ZLMedia streamId:" + streamId);
84   - }
85   - // 等待推流, TODO 默认超时15s
86   - boolean lockFlag = true;
87   - boolean rtpPushed = false;
88   - long lockStartTime = System.currentTimeMillis();
89   - JSONObject rtpInfo = null;
90   -
91   - if (closeWaitRTPInfo) {
92   - String flv = storager.getMediaInfo().getWanIp() + ":" + storager.getMediaInfo().getHttpPort() + "/rtp/"
93   - + streamId + ".flv";
94   - streamInfo.setFlv("http://" + flv);
95   - streamInfo.setWs_flv("ws://" + flv);
96   - storager.startPlayback(streamInfo);
97   - } else {
98   - while (lockFlag) {
99   - try {
100   - if (System.currentTimeMillis() - lockStartTime > 75 * 1000) {
101   - storager.stopPlayback(streamInfo);
102   - logger.info("播放等待超时");
103   - return new ResponseEntity<String>("timeout", HttpStatus.OK);
104   - } else {
105   - streamInfo = storager.queryPlaybackByDevice(deviceId, channelId);
106   - if (!rtpPushed) {
107   - logger.info("查询RTP推流信息...");
108   - rtpInfo = zlmresTfulUtils.getRtpInfo(streamId);
109   - }
110   - if (rtpInfo != null && rtpInfo.getBoolean("exist") && streamInfo != null
111   - && streamInfo.getFlv() != null) {
112   - logger.info("查询流编码信息:" + streamInfo.getFlv());
113   - rtpPushed = true;
114   - Thread.sleep(2000);
115   - JSONObject mediaInfo = zlmresTfulUtils.getMediaInfo("rtp", "rtmp", streamId);
116   - if (mediaInfo.getInteger("code") == 0 && mediaInfo.getBoolean("online")) {
117   - lockFlag = false;
118   - logger.info("流编码信息已获取");
119   - JSONArray tracks = mediaInfo.getJSONArray("tracks");
120   - streamInfo.setTracks(tracks);
121   - storager.startPlayback(streamInfo);
122   - } else {
123   - logger.info("流编码信息未获取,2秒后重试...");
124   - }
125   - } else {
126   - Thread.sleep(2000);
127   - continue;
128   - }
129   - }
130   - } catch (InterruptedException e) {
131   - e.printStackTrace();
132   - }
133   - }
134   - }
135   - if (streamInfo != null) {
136   - return new ResponseEntity<String>(JSON.toJSONString(streamInfo), HttpStatus.OK);
137   - } else {
138   - logger.warn("设备回放API调用失败!");
139   - return new ResponseEntity<String>(HttpStatus.INTERNAL_SERVER_ERROR);
140   - }
  83 + return result;
141 84 }
142 85  
143 86 @RequestMapping("/playback/{ssrc}/stop")
... ...
src/main/java/com/genersoft/iot/vmp/vmanager/service/IPlayService.java 0 → 100644
  1 +package com.genersoft.iot.vmp.vmanager.service;
  2 +
  3 +import com.alibaba.fastjson.JSONObject;
  4 +import com.genersoft.iot.vmp.common.StreamInfo;
  5 +
  6 +/**
  7 + * 点播处理
  8 + */
  9 +public interface IPlayService {
  10 +
  11 + void onPublishHandlerForPlayBack(JSONObject resonse, String deviceId, String channelId, String uuid);
  12 + void onPublishHandlerForPlay(JSONObject resonse, String deviceId, String channelId, String uuid);
  13 +}
... ...
src/main/java/com/genersoft/iot/vmp/vmanager/service/impl/PlayServiceImpl.java 0 → 100644
  1 +package com.genersoft.iot.vmp.vmanager.service.impl;
  2 +
  3 +import com.alibaba.fastjson.JSON;
  4 +import com.alibaba.fastjson.JSONObject;
  5 +import com.genersoft.iot.vmp.common.StreamInfo;
  6 +import com.genersoft.iot.vmp.conf.MediaServerConfig;
  7 +import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
  8 +import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
  9 +import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
  10 +import com.genersoft.iot.vmp.vmanager.play.PlayController;
  11 +import com.genersoft.iot.vmp.vmanager.service.IPlayService;
  12 +import org.slf4j.Logger;
  13 +import org.slf4j.LoggerFactory;
  14 +import org.springframework.beans.factory.annotation.Autowired;
  15 +import org.springframework.stereotype.Service;
  16 +
  17 +import java.text.DecimalFormat;
  18 +
  19 +@Service
  20 +public class PlayServiceImpl implements IPlayService {
  21 +
  22 + private final static Logger logger = LoggerFactory.getLogger(PlayServiceImpl.class);
  23 +
  24 + @Autowired
  25 + private IVideoManagerStorager storager;
  26 +
  27 + @Autowired
  28 + private DeferredResultHolder resultHolder;
  29 +
  30 + @Override
  31 + public void onPublishHandlerForPlay(JSONObject resonse, String deviceId, String channelId, String uuid) {
  32 + RequestMessage msg = new RequestMessage();
  33 + msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
  34 + StreamInfo streamInfo = onPublishHandler(resonse, deviceId, channelId, uuid);
  35 + if (streamInfo != null) {
  36 + storager.startPlay(streamInfo);
  37 + msg.setData(JSON.toJSONString(streamInfo));
  38 + resultHolder.invokeResult(msg);
  39 + } else {
  40 + logger.warn("设备预览API调用失败!");
  41 + msg.setData("设备预览API调用失败!");
  42 + resultHolder.invokeResult(msg);
  43 + }
  44 + }
  45 +
  46 + @Override
  47 + public void onPublishHandlerForPlayBack(JSONObject resonse, String deviceId, String channelId, String uuid) {
  48 + RequestMessage msg = new RequestMessage();
  49 + msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
  50 + StreamInfo streamInfo = onPublishHandler(resonse, deviceId, channelId, uuid);
  51 + if (streamInfo != null) {
  52 + storager.startPlayback(streamInfo);
  53 + msg.setData(JSON.toJSONString(streamInfo));
  54 + resultHolder.invokeResult(msg);
  55 + } else {
  56 + logger.warn("设备预览API调用失败!");
  57 + msg.setData("设备预览API调用失败!");
  58 + resultHolder.invokeResult(msg);
  59 + }
  60 + }
  61 +
  62 + public StreamInfo onPublishHandler(JSONObject resonse, String deviceId, String channelId, String uuid) {
  63 + String streamId = resonse.getString("id");
  64 + String ssrc = new DecimalFormat("0000000000").format(Integer.parseInt(streamId, 16));
  65 + StreamInfo streamInfo = new StreamInfo();
  66 + streamInfo.setSsrc(ssrc);
  67 + streamInfo.setStreamId(streamId);
  68 + streamInfo.setDeviceID(deviceId);
  69 + streamInfo.setCahnnelId(channelId);
  70 + MediaServerConfig mediaServerConfig = storager.getMediaInfo();
  71 +
  72 + streamInfo.setFlv(String.format("http://%s:%s/rtp/%s.flv", mediaServerConfig.getWanIp(), mediaServerConfig.getHttpPort(), streamId));
  73 + streamInfo.setWs_flv(String.format("ws://%s:%s/rtp/%s.flv", mediaServerConfig.getWanIp(), mediaServerConfig.getHttpPort(), streamId));
  74 +
  75 + streamInfo.setFmp4(String.format("http://%s:%s/rtp/%s.live.mp4", mediaServerConfig.getWanIp(), mediaServerConfig.getHttpPort(), streamId));
  76 + streamInfo.setWs_fmp4(String.format("ws://%s:%s/rtp/%s.live.mp4", mediaServerConfig.getWanIp(), mediaServerConfig.getHttpPort(), streamId));
  77 +
  78 + streamInfo.setHls(String.format("http://%s:%s/rtp/%s/hls.m3u8", mediaServerConfig.getWanIp(), mediaServerConfig.getHttpPort(), streamId));
  79 + streamInfo.setWs_hls(String.format("ws://%s:%s/rtp/%s/hls.m3u8", mediaServerConfig.getWanIp(), mediaServerConfig.getHttpPort(), streamId));
  80 +
  81 + streamInfo.setTs(String.format("http://%s:%s/rtp/%s.live.ts", mediaServerConfig.getWanIp(), mediaServerConfig.getHttpPort(), streamId));
  82 + streamInfo.setWs_ts(String.format("ws://%s:%s/rtp/%s.live.ts", mediaServerConfig.getWanIp(), mediaServerConfig.getHttpPort(), streamId));
  83 +
  84 + streamInfo.setRtmp(String.format("rtmp://%s:%s/rtp/%s", mediaServerConfig.getWanIp(), mediaServerConfig.getRtmpPort(), streamId));
  85 + streamInfo.setRtsp(String.format("rtsp://%s:%s/rtp/%s", mediaServerConfig.getWanIp(), mediaServerConfig.getRtspPort(), streamId));
  86 +
  87 + return streamInfo;
  88 + }
  89 +
  90 +}
... ...
src/main/java/com/genersoft/iot/vmp/web/ApiStreamController.java
... ... @@ -34,8 +34,7 @@ public class ApiStreamController {
34 34 @Autowired
35 35 private IVideoManagerStorager storager;
36 36  
37   - @Value("${media.closeWaitRTPInfo}")
38   - private boolean closeWaitRTPInfo;
  37 + private boolean closeWaitRTPInfo = false;
39 38  
40 39  
41 40 @Autowired
... ... @@ -94,7 +93,7 @@ public class ApiStreamController {
94 93 StreamInfo streamInfo = storager.queryPlayByDevice(device.getDeviceId(), code);
95 94 if (streamInfo == null) {
96 95 logger.debug("streamInfo 等于null, 重新点播");
97   - streamInfo = cmder.playStreamCmd(device, code);
  96 +// streamInfo = cmder.playStreamCmd(device, code);
98 97 }else {
99 98 logger.debug("streamInfo 不等于null, 向流媒体查询是否正在推流");
100 99 String streamId = String.format("%08x", Integer.parseInt(streamInfo.getSsrc())).toUpperCase();
... ... @@ -136,7 +135,7 @@ public class ApiStreamController {
136 135 } else {
137 136 logger.debug("向流媒体查询没有推流, 重新点播");
138 137 storager.stopPlay(streamInfo);
139   - streamInfo = cmder.playStreamCmd(device, code);
  138 +// streamInfo = cmder.playStreamCmd(device, code);
140 139 }
141 140 }
142 141  
... ...
src/main/resources/application-dev.yml 0 → 100644
  1 +spring:
  2 + # [不需要改]
  3 + application:
  4 + name: iot-vmp-vmanager
  5 + # [不需要改] 影子数据存储方式,支持redis、jdbc,暂不支持mysql,
  6 + database: redis
  7 + # [不需要改] 通信方式,支持kafka、http
  8 + communicate: http
  9 + # REDIS数据库配置
  10 + redis:
  11 + # [必须修改] Redis服务器IP, REDIS安装在本机的,使用127.0.0.1
  12 + host: 127.0.0.1
  13 + # [必须修改] 端口号
  14 + port: 6379
  15 + # [可选] 数据库 DB
  16 + database: 6
  17 + # [可选] 访问密码,若你的redis服务器没有设置密码,就不需要用密码去连接
  18 + password:
  19 + # [可选] 超时时间
  20 + timeout: 10000
  21 + # [不可用] jdbc数据库配置, 暂不支持
  22 + datasource:
  23 + name: eiot
  24 + url: jdbc:mysql://127.0.0.1:3306/eiot?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true
  25 + username:
  26 + password:
  27 + type: com.alibaba.druid.pool.DruidDataSource
  28 + driver-class-name: com.mysql.jdbc.Driver
  29 +
  30 +# [可选] WVP监听的HTTP端口, 网页和接口调用都是这个端口
  31 +server:
  32 + port: 18080
  33 +
  34 +# 作为28181服务器的配置
  35 +sip:
  36 + # [必须修改] 本机的IP, 必须是网卡上的IP
  37 + ip: 192.168.0.100
  38 + # [可选] 28181服务监听的端口
  39 + port: 5060
  40 + # 根据国标6.1.2中规定,domain宜采用ID统一编码的前十位编码。国标附录D中定义前8位为中心编码(由省级、市级、区级、基层编号组成,参照GB/T 2260-2007)
  41 + # 后两位为行业编码,定义参照附录D.3
  42 + # 3701020049标识山东济南历下区 信息行业接入
  43 + # [可选]
  44 + domain: 4401020049
  45 + # [可选]
  46 + id: 44010200492000000001
  47 + # [可选] 默认设备认证密码,后续扩展使用设备单独密码
  48 + password: admin123
  49 +
  50 +# 登陆的用户名密码
  51 +auth:
  52 + # [可选] 用户名
  53 + username: admin
  54 + # [可选] 密码, 默认为admin
  55 + password: 21232f297a57a5a743894a0e4a801fc3
  56 +
  57 +#zlm服务器配置
  58 +media:
  59 + # [必须修改] zlm服务器的内网IP
  60 + ip: 192.168.0.100
  61 + # [可选] zlm服务器的公网IP, 内网部署置空即可
  62 + wanIp:
  63 + # [可选] zlm服务器的hook所使用的IP, 默认使用sip.ip
  64 + hookIp:
  65 + # [必须修改] zlm服务器的http.port
  66 + port: 80
  67 + # [可选] 是否自动配置ZLM, 如果希望手动配置ZLM, 可以设为false, 不建议新接触的用户修改
  68 + autoConfig: true
  69 + # [可选] zlm服务器的hook.admin_params=secret
  70 + secret: 035c73f7-bb6b-4889-a715-d9eb2d1925cc
  71 + # [可选] zlm服务器的general.streamNoneReaderDelayMS
  72 + streamNoneReaderDelayMS: 18000 # 无人观看多久自动关闭流
  73 + # [可选] 关闭等待收到流编码信息后在返回,
  74 + # 设为false可以获得更好的兼容性,保证返回后流就可以播放,
  75 + # 设为true可以快速打开播放窗口,可以获得更好的体验
  76 + closeWaitRTPInfo: false
  77 + # 启用udp多端口模式
  78 + rtp:
  79 + # [可选] 是否启用udp多端口模式, 开启后会在udpPortRange范围内选择端口用于媒体流传输
  80 + enable: true
  81 + # [可选] 在此范围内选择端口用于媒体流传输, 不只是udp, 使用TCP被动传输模式时,也是从这个范围内选择端口
  82 + udpPortRange: 30000,30500 # 端口范围
  83 +
  84 +# [可选] 日志配置, 一般不需要改
  85 +logging:
  86 + file:
  87 + name: logs/wvp.log
  88 + max-history: 30
  89 + max-size: 10MB
  90 + total-size-cap: 300MB
  91 + level:
  92 + com:
  93 + genersoft:
  94 + iot: debug
0 95 \ No newline at end of file
... ...
src/main/resources/application.yml
1 1 spring:
2   - application:
3   - name: iot-vmp-vmanager
4   - # 影子数据存储方式,支持redis、jdbc,暂不支持mysql
5   - database: redis
6   - # 通信方式,支持kafka、http
7   - communicate: http
8   - redis:
9   - # Redis服务器IP
10   - host: 192.168.1.141
11   - #端口号
12   - port: 6379
13   - database: 6
14   - #访问密码,若你的redis服务器没有设置密码,就不需要用密码去连接
15   - password: 4767cb971b40a1300fa09b7f87b09d1c
16   - #超时时间
17   - timeout: 10000
18   - datasource:
19   - name: eiot
20   - url: jdbc:mysql://127.0.0.1:3306/eiot?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true
21   - username:
22   - password:
23   - type: com.alibaba.druid.pool.DruidDataSource
24   - driver-class-name: com.mysql.jdbc.Driver
25   -server:
26   - port: 18080
27   -sip:
28   - ip: 192.168.1.20
29   - port: 5060
30   - # 根据国标6.1.2中规定,domain宜采用ID统一编码的前十位编码。国标附录D中定义前8位为中心编码(由省级、市级、区级、基层编号组成,参照GB/T 2260-2007)
31   - # 后两位为行业编码,定义参照附录D.3
32   - # 3701020049标识山东济南历下区 信息行业接入
33   - domain: 3402000000
34   - id: 34020000002000000001
35   - # 默认设备认证密码,后续扩展使用设备单独密码
36   - password: 12345678
37   -
38   -auth: #32位小写md5加密(默认密码为admin)
39   - username: admin
40   - password: 21232f297a57a5a743894a0e4a801fc3
41   -
42   -media: #zlm服务器的ip与http端口, 重点: 这是http端口
43   - ip: 127.0.0.1
44   - wanIp: 192.168.1.20
45   - port: 6080
46   - secret: 035c73f7-bb6b-4889-a715-d9eb2d1925cc
47   - streamNoneReaderDelayMS: 600000 # 无人观看多久自动关闭流
48   - # 关闭等待收到流编码信息后在返回,
49   - # 设为false可以获得更好的兼容性,保证返回后流就可以播放,
50   - # 设为true可以快速打开播放窗口,可以获得更好的体验
51   - closeWaitRTPInfo: true
52   - rtp: # 启用udp多端口模式
53   - enable: true
54   - udpPortRange: 30000,30500 # 端口范围
55   -logging:
56   - file:
57   - name: logs/wvp.log
58   - max-history: 30
59   - max-size: 10MB
60   - total-size-cap: 300MB
61   - level:
62   - com:
63   - genersoft:
64   - iot: debug
65 2 \ No newline at end of file
  3 + profiles:
  4 + active: dev
66 5 \ No newline at end of file
... ...
web_src/build/webpack.dev.conf.js
... ... @@ -64,9 +64,8 @@ const devWebpackConfig = merge(baseWebpackConfig, {
64 64 to: config.dev.assetsSubDirectory,
65 65 ignore: ['.*']
66 66 },
67   - { from: 'node_modules/@liveqing/liveplayer/dist/component/crossdomain.xml'},
68   - { from: 'node_modules/@liveqing/liveplayer/dist/component/liveplayer.swf'},
69   - { from: 'node_modules/@liveqing/liveplayer/dist/component/liveplayer-lib.min.js', to: 'js/'}
  67 + { from: 'node_modules/@easydarwin/easywasmplayer/libDecoder.wasm'},
  68 + { from: 'node_modules/@easydarwin/easywasmplayer/EasyWasmPlayer.js', to: 'js/'}
70 69 ])
71 70 ]
72 71 })
... ...
web_src/build/webpack.prod.conf.js
... ... @@ -115,9 +115,8 @@ const webpackConfig = merge(baseWebpackConfig, {
115 115 to: config.build.assetsSubDirectory,
116 116 ignore: ['.*']
117 117 },
118   - { from: 'node_modules/@liveqing/liveplayer/dist/component/crossdomain.xml'},
119   - { from: 'node_modules/@liveqing/liveplayer/dist/component/liveplayer.swf'},
120   - { from: 'node_modules/@liveqing/liveplayer/dist/component/liveplayer-lib.min.js', to: 'js/'}
  118 + { from: 'node_modules/@easydarwin/easywasmplayer/libDecoder.wasm'},
  119 + { from: 'node_modules/@easydarwin/easywasmplayer/EasyWasmPlayer.js', to: 'js/'}
121 120 ])
122 121 ]
123 122 })
... ...
web_src/index.html
... ... @@ -6,7 +6,7 @@
6 6 <title>国标28181</title>
7 7 </head>
8 8 <body>
9   - <script type="text/javascript" src="./js/liveplayer-lib.min.js"></script>
  9 + <script type="text/javascript" src="./js/EasyWasmPlayer.js"></script>
10 10 <div id="app"></div>
11 11 <!-- built files will be auto injected -->
12 12 </body>
... ...
web_src/package-lock.json
... ... @@ -4,10 +4,10 @@
4 4 "lockfileVersion": 1,
5 5 "requires": true,
6 6 "dependencies": {
7   - "@liveqing/liveplayer": {
8   - "version": "1.9.9",
9   - "resolved": "https://registry.npm.taobao.org/@liveqing/liveplayer/download/@liveqing/liveplayer-1.9.9.tgz",
10   - "integrity": "sha1-K7wiab+BiY5qe1/nTpKVyeGdIGo="
  7 + "@easydarwin/easywasmplayer": {
  8 + "version": "4.0.7",
  9 + "resolved": "https://registry.npm.taobao.org/@easydarwin/easywasmplayer/download/@easydarwin/easywasmplayer-4.0.7.tgz",
  10 + "integrity": "sha1-FNtIUXbdwIWdalvIMEaH0+zUGx4="
11 11 },
12 12 "@types/q": {
13 13 "version": "1.5.4",
... ...
web_src/package.json
... ... @@ -10,7 +10,7 @@
10 10 "build": "node build/build.js"
11 11 },
12 12 "dependencies": {
13   - "@liveqing/liveplayer": "^1.9.6",
  13 + "@easydarwin/easywasmplayer": "^4.0.7",
14 14 "axios": "^0.19.2",
15 15 "core-js": "^2.6.5",
16 16 "echarts": "^4.7.0",
... ...
web_src/src/components/gb28181/devicePlayer.vue
1 1 <template>
2 2 <div id="devicePlayer" v-loading="isLoging">
  3 +
3 4 <el-dialog title="视频播放" top="0" :close-on-click-modal="false" :visible.sync="showVideoDialog" :destroy-on-close="true" @close="close()">
4   - <LivePlayer v-if="showVideoDialog" ref="videoPlayer" :videoUrl="videoUrl" :error="videoError" :message="videoError" :hasaudio="hasaudio" fluent autoplay live></LivePlayer>
  5 + <!-- <LivePlayer v-if="showVideoDialog" ref="videoPlayer" :videoUrl="videoUrl" :error="videoError" :message="videoError" :hasaudio="hasaudio" fluent autoplay live></LivePlayer> -->
  6 + <player ref="videoPlayer" :visible.sync="showVideoDialog" :videoUrl="videoUrl" :error="videoError" :message="videoError" :hasaudio="hasaudio" fluent autoplay live></player>
5 7 <div id="shared" style="text-align: right; margin-top: 1rem;">
6   - <el-tabs v-model="tabActiveName">
  8 + <el-tabs v-model="tabActiveName" @tab-click="tabHandleClick">
7 9 <el-tab-pane label="实时视频" name="media">
8 10 <div style="margin-bottom: 0.5rem;">
9 11 <!-- <el-button type="primary" size="small" @click="playRecord(true, '')">播放</el-button>-->
... ... @@ -97,6 +99,32 @@
97 99 </div>
98 100 </div>
99 101 </el-tab-pane>
  102 + <el-tab-pane label="编码信息" name="codec" v-loading="tracksLoading">
  103 + <p>
  104 + 无法播放或者没有声音?&nbsp&nbsp&nbsp试一试
  105 + <el-button size="mini" type="primary" v-if="!coverPlaying" @click="coverPlay">转码播放</el-button>
  106 + <el-button size="mini" type="danger" v-if="coverPlaying" @click="convertStopClick">停止转码</el-button>
  107 + </p>
  108 + <div class="trank" >
  109 + <div v-for="(item, index) in tracks">
  110 + <span >流 {{index}}</span>
  111 + <div class="trankInfo" v-if="item.codec_type == 0">
  112 + <p>格式: {{item.codec_id_name}}</p>
  113 + <p>类型: 视频</p>
  114 + <p>分辨率: {{item.width}} x {{item.height}}</p>
  115 + <p>帧率: {{item.fps}}</p>
  116 + </div>
  117 + <div class="trankInfo" v-if="item.codec_type == 1">
  118 + <p>格式: {{item.codec_id_name}}</p>
  119 + <p>类型: 音频</p>
  120 + <p>采样位数: {{item.sample_bit}}</p>
  121 + <p>采样率: {{item.sample_rate}}</p>
  122 + </div>
  123 + </div>
  124 +
  125 + </div>
  126 +
  127 + </el-tab-pane>
100 128 </el-tabs>
101 129 </div>
102 130 </el-dialog>
... ... @@ -104,12 +132,12 @@
104 132 </template>
105 133  
106 134 <script>
107   -import LivePlayer from '@liveqing/liveplayer'
  135 +import player from './player.vue'
108 136 export default {
109 137 name: 'devicePlayer',
110 138 props: {},
111 139 components: {
112   - LivePlayer
  140 + player,
113 141 },
114 142 computed: {
115 143 getPlayerShared: function () {
... ... @@ -131,6 +159,7 @@ export default {
131 159 },
132 160 showVideoDialog: false,
133 161 ssrc: '',
  162 + streamId: '',
134 163 convertKey: '',
135 164 deviceId: '',
136 165 channelId: '',
... ... @@ -148,20 +177,45 @@ export default {
148 177 cruisingGroup: 0,
149 178 scanSpeed: 100,
150 179 scanGroup: 0,
  180 + tracks: [],
  181 + coverPlaying:false,
  182 + tracksLoading: false
151 183 };
152 184 },
153 185 methods: {
  186 + tabHandleClick: function(tab, event) {
  187 + console.log(tab)
  188 + var that = this;
  189 + that.tracks = [];
  190 + that.tracksLoading = true;
  191 + if (tab.name == "codec") {
  192 + this.$axios({
  193 + method: 'get',
  194 + url: '/zlm/index/api/getMediaInfo?vhost=__defaultVhost__&schema=rtmp&app=rtp&stream='+ this.streamId
  195 + }).then(function (res) {
  196 + that.tracksLoading = false;
  197 + if (res.data.code == 0 && res.data.online) {
  198 + that.tracks = res.data.tracks;
  199 + }else{
  200 + that.$message({
  201 + showClose: true,
  202 + message: '获取编码信息失败,',
  203 + type: 'warning'
  204 + });
  205 + }
  206 + }).catch(function (e) {});
  207 + }
  208 + },
154 209 openDialog: function (tab, deviceId, channelId, param) {
155 210 this.tabActiveName = tab;
156 211 this.channelId = channelId;
157 212 this.deviceId = deviceId;
158 213 this.ssrc = "";
  214 + this.streamId = "";
159 215 this.videoUrl = ""
160 216 if (!!this.$refs.videoPlayer) {
161 217 this.$refs.videoPlayer.pause();
162 218 }
163   -
164   -
165 219 switch (tab) {
166 220 case "media":
167 221 this.play(param.streamInfo, param.hasAudio)
... ... @@ -180,99 +234,94 @@ export default {
180 234 console.log(val)
181 235 },
182 236 play: function (streamInfo, hasAudio) {
  237 +
183 238 this.hasaudio = hasAudio;
  239 + this.isLoging = false;
  240 + this.videoUrl = streamInfo.ws_flv;
  241 + this.ssrc = streamInfo.ssrc;
  242 + this.streamId = streamInfo.streamId;
  243 + this.playFromStreamInfo(false, streamInfo)
  244 + },
  245 + coverPlay: function () {
184 246 var that = this;
185   - that.isLoging = false;
186   - if (!!streamInfo.tracks && streamInfo.tracks.length > 0 ) {
187   - for (let i = 0; i < streamInfo.tracks.length; i++) {
188   - if (streamInfo.tracks[i].codec_type == 0 && streamInfo.tracks[i].codec_id_name != "CodecH264") { // 判断为H265视频
189   - that.coverPlay(streamInfo, streamInfo.tracks[i].codec_id_name, ()=>{
190   - that.close();
191   - return;
192   - })
193   - }else if (streamInfo.tracks[i].codec_type == 1 && streamInfo.tracks[i].codec_id_name != "CodecAAC") {
194   - that.coverPlay(streamInfo, streamInfo.tracks[i].codec_id_name, ()=>{
195   - that.playFromStreamInfo(false. streamInfo)
196   - })
197   - }else if (streamInfo.tracks[i].codec_type == 1 && streamInfo.tracks[i].codec_id_name == "CodecAAC") {
198   - that.playFromStreamInfo(true, streamInfo)
199   - }else {
200   - that.playFromStreamInfo(false, streamInfo)
201   - }
202   - }
203   - }else {
204   - that.playFromStreamInfo(false, streamInfo)
205   - }
  247 + this.coverPlaying = true;
  248 + this.$refs.videoPlayer.pause()
  249 + that.$axios({
  250 + method: 'post',
  251 + url: '/api/play/' + that.ssrc + '/convert'
  252 + }).then(function (res) {
  253 + if (res.data.code == 0) {
  254 + that.convertKey = res.data.key;
  255 + setTimeout(()=>{
  256 + that.isLoging = false;
  257 + that.playFromStreamInfo(false, res.data.data);
  258 + }, 2000)
  259 + } else {
  260 + that.isLoging = false;
  261 + that.coverPlaying = false;
  262 + that.$message({
  263 + showClose: true,
  264 + message: '转码失败',
  265 + type: 'error'
  266 + });
  267 + }
  268 + }).catch(function (e) {
  269 + console.log(e)
  270 + that.coverPlaying = false;
  271 + that.$message({
  272 + showClose: true,
  273 + message: '播放错误',
  274 + type: 'error'
  275 + });
  276 + });
206 277 },
207   - coverPlay: function (streamInfo, codec_id_name, catchcallback) {
208   - var that = this;
209   -
210   - that.$confirm(codec_id_name + ' 编码格式不支持播放, 是否转码播放?', '提示', {
211   - confirmButtonText: '确定',
212   - cancelButtonText: '取消',
213   - type: 'warning'
214   - }).then(() => {
215   - that.isLoging = true;
216   - that.$axios({
  278 + convertStopClick: function() {
  279 + this.convertStop(()=>{
  280 + this.$refs.videoPlayer.play(this.videoUrl)
  281 + });
  282 + },
  283 + convertStop: function(callback) {
  284 + var that = this;
  285 + that.$refs.videoPlayer.pause()
  286 + this.$axios({
217 287 method: 'post',
218   - url: '/api/play/' + streamInfo.ssrc + '/convert'
  288 + url: '/api/play/convert/stop/' + this.convertKey
219 289 }).then(function (res) {
220 290 if (res.data.code == 0) {
221   - streamInfo.ws_flv = res.data.ws_flv;
222   - that.convertKey = res.data.key;
223   - setTimeout(()=>{
224   - that.isLoging = false;
225   - that.playFromStreamInfo(false, streamInfo);
226   - }, 2000)
227   - } else {
228   - that.isLoging = false;
229   - that.$message({
230   - showClose: true,
231   - message: '转码失败',
232   - type: 'error'
233   - });
  291 + console.log(res.data.msg)
  292 + }else {
  293 + console.error(res.data.msg)
234 294 }
235   - }).catch(function (e) {
236   - that.$message({
237   - showClose: true,
238   - message: '播放错误',
239   - type: 'error'
240   - });
241   - });
242   - }).catch(function (e) {
243   - if (catchcallback)catchcallback()
244   - });
  295 + if (callback )callback();
  296 + }).catch(function (e) {});
  297 + that.coverPlaying = false;
  298 + that.convertKey = "";
  299 + // if (callback )callback();
245 300 },
  301 +
246 302 playFromStreamInfo: function (realHasAudio, streamInfo) {
247   - this.videoUrl = streamInfo.ws_flv;
248 303 this.showVideoDialog = true;
249 304 this.hasaudio = realHasAudio && this.hasaudio;
250   - this.ssrc = streamInfo.ssrc;
251   - console.log(this.ssrc);
  305 + this.$refs.videoPlayer.play(streamInfo.ws_flv)
252 306 },
253 307 close: function () {
254 308 console.log('关闭视频');
255   - if (!this.$refs.videoPlayer){
  309 + if (!!this.$refs.videoPlayer){
256 310 this.$refs.videoPlayer.pause();
257 311 }
258 312 this.videoUrl = '';
  313 + this.coverPlaying = false;
259 314 this.showVideoDialog = false;
260 315 if (this.convertKey != '') {
261   - this.$axios({
262   - method: 'post',
263   - url: '/api/play/convert/stop/' + this.convertKey
264   - }).then(function (res) {
265   - if (res.data.code == 0) {
266   - console.log(res.data.msg)
267   - }else {
268   - console.error(res.data.msg)
269   - }
270   - }).catch(function (e) {});
  316 + this.convertStop();
271 317 }
272   - this.convertKey = ''
  318 + this.convertKey = ''
273 319 },
  320 +
274 321 copySharedInfo: function (data) {
275 322 console.log('复制内容:' + data);
  323 + this.coverPlaying = false;
  324 + this.tracks = []
276 325 let _this = this;
277 326 this.$copyText(data).then(
278 327 function (e) {
... ... @@ -602,4 +651,15 @@ export default {
602 651 .control-bottom .fa {
603 652 transform: rotate(-45deg) translateY(7px);
604 653 }
  654 +.trank {
  655 + width: 80%;
  656 + height: 180px;
  657 + text-align: left;
  658 + padding: 0 10%;
  659 + overflow: auto;
  660 +}
  661 +.trankInfo {
  662 + width: 80%;
  663 + padding: 0 10%;
  664 +}
605 665 </style>
... ...
web_src/src/components/gb28181/player.vue 0 → 100644
  1 +<template>
  2 + <div id="player">
  3 + <div id="easyplayer"></div>
  4 + </div>
  5 +</template>
  6 +
  7 +<script>
  8 +export default {
  9 + name: 'player',
  10 + data() {
  11 + return {
  12 + easyPlayer: null
  13 + };
  14 + },
  15 + props: ['videoUrl', 'error', 'hasaudio'],
  16 + mounted () {
  17 + this.$nextTick(() =>{
  18 + console.log("初始化时的地址为: " + this.videoUrl)
  19 + this.easyPlayer = new WasmPlayer(null, 'easyplayer', this.eventcallbacK)
  20 + this.easyPlayer.play(this.videoUrl, 1)
  21 + })
  22 + },
  23 + watch:{
  24 + videoUrl(newData, oldData){
  25 + this.easyPlayer.destroy()
  26 + this.easyPlayer = new WasmPlayer(null, 'easyplayer', this.eventcallbacK)
  27 + this.easyPlayer.play(newData, 1)
  28 + },
  29 + immediate:true
  30 + },
  31 + methods: {
  32 + play: function (url) {
  33 + this.easyPlayer = new WasmPlayer(null, 'easyplayer', this.eventcallbacK)
  34 + this.easyPlayer.play(url, 1)
  35 + },
  36 + pause: function () {
  37 + this.easyPlayer.destroy();
  38 + },
  39 + eventcallbacK: function(type, message) {
  40 + console.log("player 事件回调")
  41 + console.log(type)
  42 + console.log(message)
  43 + }
  44 + },
  45 +}
  46 +</script>
  47 +
  48 +<style>
  49 + .LodingTitle {
  50 + min-width: 70px;
  51 + }
  52 + /* 隐藏logo */
  53 + /* .iconqingxiLOGO {
  54 + display: none !important;
  55 + } */
  56 +
  57 +</style>
0 58 \ No newline at end of file
... ...
web_src/src/components/videoList.vue
... ... @@ -73,12 +73,10 @@
73 73 </template>
74 74  
75 75 <script>
76   - import devicePlayer from './gb28181/devicePlayer.vue'
77 76 import uiHeader from './UiHeader.vue'
78 77 export default {
79 78 name: 'app',
80 79 components: {
81   - devicePlayer,
82 80 uiHeader
83 81 },
84 82 data() {
... ...
web_src/src/router/index.js
... ... @@ -35,7 +35,7 @@ export default new VueRouter({
35 35 path: '/channelList/:deviceId/:parentChannelId/:count/:page',
36 36 name: 'channelList',
37 37 component: channelList,
38   - },,
  38 + },
39 39 {
40 40 path: '/parentPlatformList/:count/:page',
41 41 name: 'parentPlatformList',
... ...