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,12 +5,18 @@ import com.alibaba.fastjson.JSONArray;
5 public class StreamInfo { 5 public class StreamInfo {
6 6
7 private String ssrc; 7 private String ssrc;
  8 + private String streamId;
8 private String deviceID; 9 private String deviceID;
9 private String cahnnelId; 10 private String cahnnelId;
10 private String flv; 11 private String flv;
11 private String ws_flv; 12 private String ws_flv;
12 - private String rtmp; 13 + private String fmp4;
  14 + private String ws_fmp4;
13 private String hls; 15 private String hls;
  16 + private String ws_hls;
  17 + private String ts;
  18 + private String ws_ts;
  19 + private String rtmp;
14 private String rtsp; 20 private String rtsp;
15 private JSONArray tracks; 21 private JSONArray tracks;
16 22
@@ -85,4 +91,52 @@ public class StreamInfo { @@ -85,4 +91,52 @@ public class StreamInfo {
85 public void setTracks(JSONArray tracks) { 91 public void setTracks(JSONArray tracks) {
86 this.tracks = tracks; 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,6 +113,7 @@ public class SipLayer implements SipListener {
113 */ 113 */
114 @Override 114 @Override
115 public void processRequest(RequestEvent evt) { 115 public void processRequest(RequestEvent evt) {
  116 + logger.debug(evt.getRequest().toString());
116 // 由于jainsip是单线程程序,为提高性能并发处理 117 // 由于jainsip是单线程程序,为提高性能并发处理
117 processThreadPool.execute(() -> { 118 processThreadPool.execute(() -> {
118 processorFactory.createRequestProcessor(evt).process(); 119 processorFactory.createRequestProcessor(evt).process();
@@ -122,6 +123,7 @@ public class SipLayer implements SipListener { @@ -122,6 +123,7 @@ public class SipLayer implements SipListener {
122 @Override 123 @Override
123 public void processResponse(ResponseEvent evt) { 124 public void processResponse(ResponseEvent evt) {
124 Response response = evt.getResponse(); 125 Response response = evt.getResponse();
  126 + logger.debug(evt.getResponse().toString());
125 int status = response.getStatusCode(); 127 int status = response.getStatusCode();
126 if (((status >= 200) && (status < 300)) || status == 401) { // Success! 128 if (((status >= 200) && (status < 300)) || status == 401) { // Success!
127 ISIPResponseProcessor processor = processorFactory.createResponseProcessor(evt); 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,7 +32,7 @@ public class OnlineEventListener implements ApplicationListener&lt;OnlineEvent&gt; {
32 public void onApplicationEvent(OnlineEvent event) { 32 public void onApplicationEvent(OnlineEvent event) {
33 33
34 if (logger.isDebugEnabled()) { 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 String key = VideoManagerConstants.KEEPLIVEKEY_PREFIX + event.getDeviceId(); 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,13 +17,11 @@ public class VideoStreamSessionManager {
17 private ConcurrentHashMap<String, ClientTransaction> sessionMap = new ConcurrentHashMap<>(); 17 private ConcurrentHashMap<String, ClientTransaction> sessionMap = new ConcurrentHashMap<>();
18 18
19 public String createPlaySsrc(){ 19 public String createPlaySsrc(){
20 - String ssrc = SsrcUtil.getPlaySsrc();  
21 - return ssrc; 20 + return SsrcUtil.getPlaySsrc();
22 } 21 }
23 22
24 public String createPlayBackSsrc(){ 23 public String createPlayBackSsrc(){
25 - String ssrc = SsrcUtil.getPlayBackSsrc();  
26 - return ssrc; 24 + return SsrcUtil.getPlayBackSsrc();
27 } 25 }
28 26
29 public void put(String ssrc,ClientTransaction transaction){ 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,6 +22,8 @@ public class DeferredResultHolder {
22 22
23 public static final String CALLBACK_CMD_RECORDINFO = "CALLBACK_RECORDINFO"; 23 public static final String CALLBACK_CMD_RECORDINFO = "CALLBACK_RECORDINFO";
24 24
  25 + public static final String CALLBACK_CMD_PlAY = "CALLBACK_PLAY";
  26 +
25 private Map<String, DeferredResult> map = new HashMap<String, DeferredResult>(); 27 private Map<String, DeferredResult> map = new HashMap<String, DeferredResult>();
26 28
27 public void put(String key, DeferredResult result) { 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,6 +2,7 @@ package com.genersoft.iot.vmp.gb28181.transmit.cmd;
2 2
3 import com.genersoft.iot.vmp.common.StreamInfo; 3 import com.genersoft.iot.vmp.common.StreamInfo;
4 import com.genersoft.iot.vmp.gb28181.bean.Device; 4 import com.genersoft.iot.vmp.gb28181.bean.Device;
  5 +import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe;
5 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; 6 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
6 7
7 /** 8 /**
@@ -20,7 +21,7 @@ public interface ISIPCommander { @@ -20,7 +21,7 @@ public interface ISIPCommander {
20 * @param upDown 镜头上移下移 0:停止 1:上移 2:下移 21 * @param upDown 镜头上移下移 0:停止 1:上移 2:下移
21 * @param moveSpeed 镜头移动速度 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,7 +32,7 @@ public interface ISIPCommander {
31 * @param upDown 镜头上移下移 0:停止 1:上移 2:下移 32 * @param upDown 镜头上移下移 0:停止 1:上移 2:下移
32 * @param moveSpeed 镜头移动速度 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,7 +41,7 @@ public interface ISIPCommander {
40 * @param channelId 预览通道 41 * @param channelId 预览通道
41 * @param inOut 镜头放大缩小 0:停止 1:缩小 2:放大 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,7 +51,7 @@ public interface ISIPCommander {
50 * @param inOut 镜头放大缩小 0:停止 1:缩小 2:放大 51 * @param inOut 镜头放大缩小 0:停止 1:缩小 2:放大
51 * @param zoomSpeed 镜头缩放速度 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,7 +64,7 @@ public interface ISIPCommander {
63 * @param moveSpeed 镜头移动速度 64 * @param moveSpeed 镜头移动速度
64 * @param zoomSpeed 镜头缩放速度 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 * 前端控制,包括PTZ指令、FI指令、预置位指令、巡航指令、扫描指令和辅助开关指令 70 * 前端控制,包括PTZ指令、FI指令、预置位指令、巡航指令、扫描指令和辅助开关指令
@@ -75,7 +76,7 @@ public interface ISIPCommander { @@ -75,7 +76,7 @@ public interface ISIPCommander {
75 * @param parameter2 数据2 76 * @param parameter2 数据2
76 * @param combineCode2 组合码2 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,7 +84,7 @@ public interface ISIPCommander {
83 * @param device 视频设备 84 * @param device 视频设备
84 * @param channelId 预览通道 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,14 +94,14 @@ public interface ISIPCommander {
93 * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss 94 * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
94 * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss 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 * @param ssrc ssrc 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,7 +109,7 @@ public interface ISIPCommander {
108 * @param device 视频设备 109 * @param device 视频设备
109 * @param channelId 预览通道 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,21 +117,21 @@ public interface ISIPCommander {
116 * @param device 视频设备 117 * @param device 视频设备
117 * @param channelId 预览通道 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 * @param device 视频设备 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 * @param device 视频设备 132 * @param device 视频设备
132 */ 133 */
133 - public boolean alarmCmd(Device device); 134 + boolean alarmCmd(Device device);
134 135
135 /** 136 /**
136 * 强制关键帧命令,设备收到此命令应立刻发送一个IDR帧 137 * 强制关键帧命令,设备收到此命令应立刻发送一个IDR帧
@@ -138,21 +139,21 @@ public interface ISIPCommander { @@ -138,21 +139,21 @@ public interface ISIPCommander {
138 * @param device 视频设备 139 * @param device 视频设备
139 * @param channelId 预览通道 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 * @param device 视频设备 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 * @param device 视频设备 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,7 +161,7 @@ public interface ISIPCommander {
160 * 161 *
161 * @param device 视频设备 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,14 +169,14 @@ public interface ISIPCommander {
168 * @param device 视频设备 169 * @param device 视频设备
169 * @return 170 * @return
170 */ 171 */
171 - public boolean deviceInfoQuery(Device device); 172 + boolean deviceInfoQuery(Device device);
172 173
173 /** 174 /**
174 * 查询目录列表 175 * 查询目录列表
175 * 176 *
176 * @param device 视频设备 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,35 +185,33 @@ public interface ISIPCommander {
184 * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss 185 * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
185 * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss 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 * @param device 视频设备 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 * @param device 视频设备 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 * @param device 视频设备 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 * @param device 视频设备 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,6 +19,7 @@ import com.alibaba.fastjson.JSONObject;
19 import com.genersoft.iot.vmp.common.StreamInfo; 19 import com.genersoft.iot.vmp.common.StreamInfo;
20 import com.genersoft.iot.vmp.conf.MediaServerConfig; 20 import com.genersoft.iot.vmp.conf.MediaServerConfig;
21 import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; 21 import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
  22 +import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe;
22 import com.genersoft.iot.vmp.media.zlm.ZLMUtils; 23 import com.genersoft.iot.vmp.media.zlm.ZLMUtils;
23 import com.genersoft.iot.vmp.storager.IVideoManagerStorager; 24 import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
24 import org.springframework.beans.factory.annotation.Autowired; 25 import org.springframework.beans.factory.annotation.Autowired;
@@ -67,6 +68,9 @@ public class SIPCommander implements ISIPCommander { @@ -67,6 +68,9 @@ public class SIPCommander implements ISIPCommander {
67 @Value("${media.rtp.enable}") 68 @Value("${media.rtp.enable}")
68 private boolean rtpEnable; 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,12 +268,12 @@ public class SIPCommander implements ISIPCommander {
264 } 268 }
265 /** 269 /**
266 * 请求预览视频流 270 * 请求预览视频流
267 - * 271 + *
268 * @param device 视频设备 272 * @param device 视频设备
269 * @param channelId 预览通道 273 * @param channelId 预览通道
270 - */ 274 + */
271 @Override 275 @Override
272 - public StreamInfo playStreamCmd(Device device, String channelId) { 276 + public void playStreamCmd(Device device, String channelId, ZLMHttpHookSubscribe.Event event) {
273 try { 277 try {
274 278
275 String ssrc = streamSession.createPlaySsrc(); 279 String ssrc = streamSession.createPlaySsrc();
@@ -282,53 +286,63 @@ public class SIPCommander implements ISIPCommander { @@ -282,53 +286,63 @@ public class SIPCommander implements ISIPCommander {
282 }else { 286 }else {
283 mediaPort = mediaInfo.getRtpProxyPort(); 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 StringBuffer content = new StringBuffer(200); 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 }else if ("TCP-ACTIVE".equals(streamMode)) { 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 }else if("UDP".equals(streamMode)) { 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 content.append("a=connection:new\r\n"); 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 content.append("a=setup:active\r\n"); 325 content.append("a=setup:active\r\n");
308 content.append("a=connection:new\r\n"); 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 DeviceChannel deviceChannel = storager.queryChannel(device.getDeviceId(), channelId); 334 DeviceChannel deviceChannel = storager.queryChannel(device.getDeviceId(), channelId);
317 if (deviceChannel != null) { 335 if (deviceChannel != null) {
318 deviceChannel.setSsrc(ssrc); 336 deviceChannel.setSsrc(ssrc);
319 storager.updateChannel(device.getDeviceId(), deviceChannel); 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 } catch ( SipException | ParseException | InvalidArgumentException e) { 343 } catch ( SipException | ParseException | InvalidArgumentException e) {
329 e.printStackTrace(); 344 e.printStackTrace();
330 - return null;  
331 - } 345 + }
332 } 346 }
333 347
334 /** 348 /**
@@ -340,10 +354,18 @@ public class SIPCommander implements ISIPCommander { @@ -340,10 +354,18 @@ public class SIPCommander implements ISIPCommander {
340 * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss 354 * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
341 */ 355 */
342 @Override 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 try { 358 try {
345 MediaServerConfig mediaInfo = storager.getMediaInfo(); 359 MediaServerConfig mediaInfo = storager.getMediaInfo();
346 String ssrc = streamSession.createPlayBackSsrc(); 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 StringBuffer content = new StringBuffer(200); 370 StringBuffer content = new StringBuffer(200);
349 content.append("v=0\r\n"); 371 content.append("v=0\r\n");
@@ -362,16 +384,22 @@ public class SIPCommander implements ISIPCommander { @@ -362,16 +384,22 @@ public class SIPCommander implements ISIPCommander {
362 } 384 }
363 String streamMode = device.getStreamMode().toUpperCase(); 385 String streamMode = device.getStreamMode().toUpperCase();
364 if("TCP-PASSIVE".equals(streamMode)) { 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 }else if ("TCP-ACTIVE".equals(streamMode)) { 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 }else if("UDP".equals(streamMode)) { 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 if("TCP-PASSIVE".equals(streamMode)){ // tcp被动模式 403 if("TCP-PASSIVE".equals(streamMode)){ // tcp被动模式
376 content.append("a=setup:passive\r\n"); 404 content.append("a=setup:passive\r\n");
377 content.append("a=connection:new\r\n"); 405 content.append("a=connection:new\r\n");
@@ -386,16 +414,8 @@ public class SIPCommander implements ISIPCommander { @@ -386,16 +414,8 @@ public class SIPCommander implements ISIPCommander {
386 ClientTransaction transaction = transmitRequest(device, request); 414 ClientTransaction transaction = transmitRequest(device, request);
387 streamSession.put(ssrc, transaction); 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 } catch ( SipException | ParseException | InvalidArgumentException e) { 417 } catch ( SipException | ParseException | InvalidArgumentException e) {
397 e.printStackTrace(); 418 e.printStackTrace();
398 - return null;  
399 } 419 }
400 } 420 }
401 421
@@ -433,6 +453,7 @@ public class SIPCommander implements ISIPCommander { @@ -433,6 +453,7 @@ public class SIPCommander implements ISIPCommander {
433 clientTransaction = udpSipProvider.getNewClientTransaction(byeRequest); 453 clientTransaction = udpSipProvider.getNewClientTransaction(byeRequest);
434 } 454 }
435 dialog.sendRequest(clientTransaction); 455 dialog.sendRequest(clientTransaction);
  456 + streamSession.remove(ssrc);
436 } catch (TransactionDoesNotExistException e) { 457 } catch (TransactionDoesNotExistException e) {
437 e.printStackTrace(); 458 e.printStackTrace();
438 } catch (SipException e) { 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,14 +21,12 @@ public class AckRequestProcessor extends SIPRequestAbstractProcessor {
21 * 处理 ACK请求 21 * 处理 ACK请求
22 * 22 *
23 * @param evt 23 * @param evt
24 - * @param layer  
25 - * @param transaction  
26 - * @param config  
27 - */ 24 + */
28 @Override 25 @Override
29 public void process(RequestEvent evt) { 26 public void process(RequestEvent evt) {
30 Request request = evt.getRequest(); 27 Request request = evt.getRequest();
31 Dialog dialog = evt.getDialog(); 28 Dialog dialog = evt.getDialog();
  29 + if (dialog == null) return;
32 try { 30 try {
33 Request ackRequest = null; 31 Request ackRequest = null;
34 CSeq csReq = (CSeq) request.getHeader(CSeq.NAME); 32 CSeq csReq = (CSeq) request.getHeader(CSeq.NAME);
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/ByeRequestProcessor.java
1 package com.genersoft.iot.vmp.gb28181.transmit.request.impl; 1 package com.genersoft.iot.vmp.gb28181.transmit.request.impl;
2 2
  3 +import javax.sip.InvalidArgumentException;
3 import javax.sip.RequestEvent; 4 import javax.sip.RequestEvent;
  5 +import javax.sip.SipException;
  6 +import javax.sip.message.Response;
4 7
5 import com.genersoft.iot.vmp.gb28181.transmit.request.SIPRequestAbstractProcessor; 8 import com.genersoft.iot.vmp.gb28181.transmit.request.SIPRequestAbstractProcessor;
6 9
  10 +import java.text.ParseException;
  11 +
7 /** 12 /**
8 * @Description: BYE请求处理器 13 * @Description: BYE请求处理器
9 * @author: swwheihei 14 * @author: swwheihei
@@ -11,18 +16,35 @@ import com.genersoft.iot.vmp.gb28181.transmit.request.SIPRequestAbstractProcesso @@ -11,18 +16,35 @@ import com.genersoft.iot.vmp.gb28181.transmit.request.SIPRequestAbstractProcesso
11 */ 16 */
12 public class ByeRequestProcessor extends SIPRequestAbstractProcessor { 17 public class ByeRequestProcessor extends SIPRequestAbstractProcessor {
13 18
14 - /** 19 + /**
15 * 处理BYE请求 20 * 处理BYE请求
16 - *  
17 * @param evt 21 * @param evt
18 - * @param layer  
19 - * @param transaction  
20 - * @param config  
21 - */ 22 + */
22 @Override 23 @Override
23 public void process(RequestEvent evt) { 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 // TODO 优先级99 Bye Request消息实现,此消息一般为级联消息,上级给下级发送视频停止指令 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,10 +184,11 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
184 DeviceChannel deviceChannel = new DeviceChannel(); 184 DeviceChannel deviceChannel = new DeviceChannel();
185 deviceChannel.setName(channelName); 185 deviceChannel.setName(channelName);
186 deviceChannel.setChannelId(channelDeviceId); 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 deviceChannel.setStatus(1); 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 deviceChannel.setStatus(0); 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,6 +49,9 @@ public class ZLMHttpHookListener {
49 @Autowired 49 @Autowired
50 private ZLMRESTfulUtils zlmresTfulUtils; 50 private ZLMRESTfulUtils zlmresTfulUtils;
51 51
  52 + @Autowired
  53 + private ZLMHttpHookSubscribe subscribe;
  54 +
52 @Value("${media.ip}") 55 @Value("${media.ip}")
53 private String mediaIp; 56 private String mediaIp;
54 57
@@ -128,30 +131,38 @@ public class ZLMHttpHookListener { @@ -128,30 +131,38 @@ public class ZLMHttpHookListener {
128 } 131 }
129 String app = json.getString("app"); 132 String app = json.getString("app");
130 String streamId = json.getString("id"); 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 // TODO Auto-generated method stub 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,6 +36,9 @@ public class ZLMRunner implements CommandLineRunner {
36 @Value("${media.wanIp}") 36 @Value("${media.wanIp}")
37 private String mediaWanIp; 37 private String mediaWanIp;
38 38
  39 + @Value("${media.hookIp}")
  40 + private String mediaHookIp;
  41 +
39 @Value("${media.port}") 42 @Value("${media.port}")
40 private int mediaPort; 43 private int mediaPort;
41 44
@@ -51,6 +54,9 @@ public class ZLMRunner implements CommandLineRunner { @@ -51,6 +54,9 @@ public class ZLMRunner implements CommandLineRunner {
51 @Value("${server.port}") 54 @Value("${server.port}")
52 private String serverPort; 55 private String serverPort;
53 56
  57 + @Value("${media.autoConfig}")
  58 + private boolean autoConfig;
  59 +
54 @Autowired 60 @Autowired
55 private ZLMRESTfulUtils zlmresTfulUtils; 61 private ZLMRESTfulUtils zlmresTfulUtils;
56 62
@@ -61,8 +67,7 @@ public class ZLMRunner implements CommandLineRunner { @@ -61,8 +67,7 @@ public class ZLMRunner implements CommandLineRunner {
61 MediaServerConfig mediaServerConfig = getMediaServerConfig(); 67 MediaServerConfig mediaServerConfig = getMediaServerConfig();
62 if (mediaServerConfig != null) { 68 if (mediaServerConfig != null) {
63 logger.info("zlm接入成功..."); 69 logger.info("zlm接入成功...");
64 - logger.info("设置zlm...");  
65 - saveZLMConfig(); 70 + if (autoConfig) saveZLMConfig();
66 mediaServerConfig = getMediaServerConfig(); 71 mediaServerConfig = getMediaServerConfig();
67 storager.updateMediaInfo(mediaServerConfig); 72 storager.updateMediaInfo(mediaServerConfig);
68 } 73 }
@@ -91,12 +96,12 @@ public class ZLMRunner implements CommandLineRunner { @@ -91,12 +96,12 @@ public class ZLMRunner implements CommandLineRunner {
91 } 96 }
92 97
93 private void saveZLMConfig() { 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 Map<String, Object> param = new HashMap<>(); 105 Map<String, Object> param = new HashMap<>();
101 param.put("api.secret",mediaSecret); // -profile:v Baseline 106 param.put("api.secret",mediaSecret); // -profile:v Baseline
102 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"); 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,6 +555,10 @@ public class VideoManagerRedisStoragerImpl implements IVideoManagerStorager {
555 List<Object> playLeys = redis.scan(String.format("%S_*_%s_%s", VideoManagerConstants.PLAY_BLACK_PREFIX, 555 List<Object> playLeys = redis.scan(String.format("%S_*_%s_%s", VideoManagerConstants.PLAY_BLACK_PREFIX,
556 deviceId, 556 deviceId,
557 code)); 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 if (playLeys == null || playLeys.size() == 0) return null; 562 if (playLeys == null || playLeys.size() == 0) return null;
559 return (StreamInfo)redis.get(playLeys.get(0).toString()); 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,7 +4,10 @@ import com.alibaba.fastjson.JSON;
4 import com.alibaba.fastjson.JSONArray; 4 import com.alibaba.fastjson.JSONArray;
5 import com.genersoft.iot.vmp.common.StreamInfo; 5 import com.genersoft.iot.vmp.common.StreamInfo;
6 import com.genersoft.iot.vmp.conf.MediaServerConfig; 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 import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils; 9 import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
  10 +import com.genersoft.iot.vmp.vmanager.service.IPlayService;
8 import org.slf4j.Logger; 11 import org.slf4j.Logger;
9 import org.slf4j.LoggerFactory; 12 import org.slf4j.LoggerFactory;
10 import org.springframework.beans.factory.annotation.Autowired; 13 import org.springframework.beans.factory.annotation.Autowired;
@@ -22,6 +25,10 @@ import com.alibaba.fastjson.JSONObject; @@ -22,6 +25,10 @@ import com.alibaba.fastjson.JSONObject;
22 import com.genersoft.iot.vmp.gb28181.bean.Device; 25 import com.genersoft.iot.vmp.gb28181.bean.Device;
23 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; 26 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
24 import com.genersoft.iot.vmp.storager.IVideoManagerStorager; 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 @CrossOrigin 33 @CrossOrigin
27 @RestController 34 @RestController
@@ -39,94 +46,57 @@ public class PlayController { @@ -39,94 +46,57 @@ public class PlayController {
39 @Autowired 46 @Autowired
40 private ZLMRESTfulUtils zlmresTfulUtils; 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 @GetMapping("/play/{deviceId}/{channelId}") 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 Device device = storager.queryVideoDevice(deviceId); 60 Device device = storager.queryVideoDevice(deviceId);
52 StreamInfo streamInfo = storager.queryPlayByDevice(deviceId, channelId); 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 if (streamInfo == null) { 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 } else { 82 } else {
57 String streamId = String.format("%08x", Integer.parseInt(streamInfo.getSsrc())).toUpperCase(); 83 String streamId = String.format("%08x", Integer.parseInt(streamInfo.getSsrc())).toUpperCase();
58 JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(streamId); 84 JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(streamId);
59 if (rtpInfo.getBoolean("exist")) { 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 } else { 90 } else {
62 storager.stopPlay(streamInfo); 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 @PostMapping("/play/{ssrc}/stop") 102 @PostMapping("/play/{ssrc}/stop")
@@ -172,17 +142,28 @@ public class PlayController { @@ -172,17 +142,28 @@ public class PlayController {
172 MediaServerConfig mediaInfo = storager.getMediaInfo(); 142 MediaServerConfig mediaInfo = storager.getMediaInfo();
173 String dstUrl = String.format("rtmp://%s:%s/convert/%s", "127.0.0.1", mediaInfo.getRtmpPort(), 143 String dstUrl = String.format("rtmp://%s:%s/convert/%s", "127.0.0.1", mediaInfo.getRtmpPort(),
174 streamId ); 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 System.out.println(jsonObject); 147 System.out.println(jsonObject);
177 JSONObject result = new JSONObject(); 148 JSONObject result = new JSONObject();
178 if (jsonObject != null && jsonObject.getInteger("code") == 0) { 149 if (jsonObject != null && jsonObject.getInteger("code") == 0) {
179 result.put("code", 0); 150 result.put("code", 0);
180 JSONObject data = jsonObject.getJSONObject("data"); 151 JSONObject data = jsonObject.getJSONObject("data");
181 if (data != null) { 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 }else { 168 }else {
188 result.put("code", 1); 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,7 +3,10 @@ package com.genersoft.iot.vmp.vmanager.playback;
3 import com.alibaba.fastjson.JSON; 3 import com.alibaba.fastjson.JSON;
4 import com.alibaba.fastjson.JSONArray; 4 import com.alibaba.fastjson.JSONArray;
5 import com.genersoft.iot.vmp.common.StreamInfo; 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 import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils; 8 import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
  9 +import com.genersoft.iot.vmp.vmanager.service.IPlayService;
7 import org.slf4j.Logger; 10 import org.slf4j.Logger;
8 import org.slf4j.LoggerFactory; 11 import org.slf4j.LoggerFactory;
9 import org.springframework.beans.factory.annotation.Autowired; 12 import org.springframework.beans.factory.annotation.Autowired;
@@ -22,6 +25,9 @@ import com.alibaba.fastjson.JSONObject; @@ -22,6 +25,9 @@ import com.alibaba.fastjson.JSONObject;
22 import com.genersoft.iot.vmp.gb28181.bean.Device; 25 import com.genersoft.iot.vmp.gb28181.bean.Device;
23 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; 26 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
24 import com.genersoft.iot.vmp.storager.IVideoManagerStorager; 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 @CrossOrigin 32 @CrossOrigin
27 @RestController 33 @RestController
@@ -39,105 +45,42 @@ public class PlaybackController { @@ -39,105 +45,42 @@ public class PlaybackController {
39 @Autowired 45 @Autowired
40 private ZLMRESTfulUtils zlmresTfulUtils; 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 @GetMapping("/playback/{deviceId}/{channelId}") 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 if (logger.isDebugEnabled()) { 58 if (logger.isDebugEnabled()) {
50 logger.debug(String.format("设备回放 API调用,deviceId:%s ,channelId:%s", deviceId, channelId)); 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 Device device = storager.queryVideoDevice(deviceId); 71 Device device = storager.queryVideoDevice(deviceId);
60 StreamInfo streamInfo = storager.queryPlaybackByDevice(deviceId, channelId); 72 StreamInfo streamInfo = storager.queryPlaybackByDevice(deviceId, channelId);
61 -  
62 if (streamInfo != null) { 73 if (streamInfo != null) {
  74 + // 停止之前的回放
63 cmder.streamByeCmd(streamInfo.getSsrc()); 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 @RequestMapping("/playback/{ssrc}/stop") 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,8 +34,7 @@ public class ApiStreamController {
34 @Autowired 34 @Autowired
35 private IVideoManagerStorager storager; 35 private IVideoManagerStorager storager;
36 36
37 - @Value("${media.closeWaitRTPInfo}")  
38 - private boolean closeWaitRTPInfo; 37 + private boolean closeWaitRTPInfo = false;
39 38
40 39
41 @Autowired 40 @Autowired
@@ -94,7 +93,7 @@ public class ApiStreamController { @@ -94,7 +93,7 @@ public class ApiStreamController {
94 StreamInfo streamInfo = storager.queryPlayByDevice(device.getDeviceId(), code); 93 StreamInfo streamInfo = storager.queryPlayByDevice(device.getDeviceId(), code);
95 if (streamInfo == null) { 94 if (streamInfo == null) {
96 logger.debug("streamInfo 等于null, 重新点播"); 95 logger.debug("streamInfo 等于null, 重新点播");
97 - streamInfo = cmder.playStreamCmd(device, code); 96 +// streamInfo = cmder.playStreamCmd(device, code);
98 }else { 97 }else {
99 logger.debug("streamInfo 不等于null, 向流媒体查询是否正在推流"); 98 logger.debug("streamInfo 不等于null, 向流媒体查询是否正在推流");
100 String streamId = String.format("%08x", Integer.parseInt(streamInfo.getSsrc())).toUpperCase(); 99 String streamId = String.format("%08x", Integer.parseInt(streamInfo.getSsrc())).toUpperCase();
@@ -136,7 +135,7 @@ public class ApiStreamController { @@ -136,7 +135,7 @@ public class ApiStreamController {
136 } else { 135 } else {
137 logger.debug("向流媒体查询没有推流, 重新点播"); 136 logger.debug("向流媒体查询没有推流, 重新点播");
138 storager.stopPlay(streamInfo); 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 \ No newline at end of file 95 \ No newline at end of file
src/main/resources/application.yml
1 spring: 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 \ No newline at end of file 2 \ No newline at end of file
  3 + profiles:
  4 + active: dev
66 \ No newline at end of file 5 \ No newline at end of file
web_src/build/webpack.dev.conf.js
@@ -64,9 +64,8 @@ const devWebpackConfig = merge(baseWebpackConfig, { @@ -64,9 +64,8 @@ const devWebpackConfig = merge(baseWebpackConfig, {
64 to: config.dev.assetsSubDirectory, 64 to: config.dev.assetsSubDirectory,
65 ignore: ['.*'] 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,9 +115,8 @@ const webpackConfig = merge(baseWebpackConfig, {
115 to: config.build.assetsSubDirectory, 115 to: config.build.assetsSubDirectory,
116 ignore: ['.*'] 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,7 +6,7 @@
6 <title>国标28181</title> 6 <title>国标28181</title>
7 </head> 7 </head>
8 <body> 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 <div id="app"></div> 10 <div id="app"></div>
11 <!-- built files will be auto injected --> 11 <!-- built files will be auto injected -->
12 </body> 12 </body>
web_src/package-lock.json
@@ -4,10 +4,10 @@ @@ -4,10 +4,10 @@
4 "lockfileVersion": 1, 4 "lockfileVersion": 1,
5 "requires": true, 5 "requires": true,
6 "dependencies": { 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 "@types/q": { 12 "@types/q": {
13 "version": "1.5.4", 13 "version": "1.5.4",
web_src/package.json
@@ -10,7 +10,7 @@ @@ -10,7 +10,7 @@
10 "build": "node build/build.js" 10 "build": "node build/build.js"
11 }, 11 },
12 "dependencies": { 12 "dependencies": {
13 - "@liveqing/liveplayer": "^1.9.6", 13 + "@easydarwin/easywasmplayer": "^4.0.7",
14 "axios": "^0.19.2", 14 "axios": "^0.19.2",
15 "core-js": "^2.6.5", 15 "core-js": "^2.6.5",
16 "echarts": "^4.7.0", 16 "echarts": "^4.7.0",
web_src/src/components/gb28181/devicePlayer.vue
1 <template> 1 <template>
2 <div id="devicePlayer" v-loading="isLoging"> 2 <div id="devicePlayer" v-loading="isLoging">
  3 +
3 <el-dialog title="视频播放" top="0" :close-on-click-modal="false" :visible.sync="showVideoDialog" :destroy-on-close="true" @close="close()"> 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 <div id="shared" style="text-align: right; margin-top: 1rem;"> 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 <el-tab-pane label="实时视频" name="media"> 9 <el-tab-pane label="实时视频" name="media">
8 <div style="margin-bottom: 0.5rem;"> 10 <div style="margin-bottom: 0.5rem;">
9 <!-- <el-button type="primary" size="small" @click="playRecord(true, '')">播放</el-button>--> 11 <!-- <el-button type="primary" size="small" @click="playRecord(true, '')">播放</el-button>-->
@@ -97,6 +99,32 @@ @@ -97,6 +99,32 @@
97 </div> 99 </div>
98 </div> 100 </div>
99 </el-tab-pane> 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 </el-tabs> 128 </el-tabs>
101 </div> 129 </div>
102 </el-dialog> 130 </el-dialog>
@@ -104,12 +132,12 @@ @@ -104,12 +132,12 @@
104 </template> 132 </template>
105 133
106 <script> 134 <script>
107 -import LivePlayer from '@liveqing/liveplayer' 135 +import player from './player.vue'
108 export default { 136 export default {
109 name: 'devicePlayer', 137 name: 'devicePlayer',
110 props: {}, 138 props: {},
111 components: { 139 components: {
112 - LivePlayer 140 + player,
113 }, 141 },
114 computed: { 142 computed: {
115 getPlayerShared: function () { 143 getPlayerShared: function () {
@@ -131,6 +159,7 @@ export default { @@ -131,6 +159,7 @@ export default {
131 }, 159 },
132 showVideoDialog: false, 160 showVideoDialog: false,
133 ssrc: '', 161 ssrc: '',
  162 + streamId: '',
134 convertKey: '', 163 convertKey: '',
135 deviceId: '', 164 deviceId: '',
136 channelId: '', 165 channelId: '',
@@ -148,20 +177,45 @@ export default { @@ -148,20 +177,45 @@ export default {
148 cruisingGroup: 0, 177 cruisingGroup: 0,
149 scanSpeed: 100, 178 scanSpeed: 100,
150 scanGroup: 0, 179 scanGroup: 0,
  180 + tracks: [],
  181 + coverPlaying:false,
  182 + tracksLoading: false
151 }; 183 };
152 }, 184 },
153 methods: { 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 openDialog: function (tab, deviceId, channelId, param) { 209 openDialog: function (tab, deviceId, channelId, param) {
155 this.tabActiveName = tab; 210 this.tabActiveName = tab;
156 this.channelId = channelId; 211 this.channelId = channelId;
157 this.deviceId = deviceId; 212 this.deviceId = deviceId;
158 this.ssrc = ""; 213 this.ssrc = "";
  214 + this.streamId = "";
159 this.videoUrl = "" 215 this.videoUrl = ""
160 if (!!this.$refs.videoPlayer) { 216 if (!!this.$refs.videoPlayer) {
161 this.$refs.videoPlayer.pause(); 217 this.$refs.videoPlayer.pause();
162 } 218 }
163 -  
164 -  
165 switch (tab) { 219 switch (tab) {
166 case "media": 220 case "media":
167 this.play(param.streamInfo, param.hasAudio) 221 this.play(param.streamInfo, param.hasAudio)
@@ -180,99 +234,94 @@ export default { @@ -180,99 +234,94 @@ export default {
180 console.log(val) 234 console.log(val)
181 }, 235 },
182 play: function (streamInfo, hasAudio) { 236 play: function (streamInfo, hasAudio) {
  237 +
183 this.hasaudio = hasAudio; 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 var that = this; 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 method: 'post', 287 method: 'post',
218 - url: '/api/play/' + streamInfo.ssrc + '/convert' 288 + url: '/api/play/convert/stop/' + this.convertKey
219 }).then(function (res) { 289 }).then(function (res) {
220 if (res.data.code == 0) { 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 playFromStreamInfo: function (realHasAudio, streamInfo) { 302 playFromStreamInfo: function (realHasAudio, streamInfo) {
247 - this.videoUrl = streamInfo.ws_flv;  
248 this.showVideoDialog = true; 303 this.showVideoDialog = true;
249 this.hasaudio = realHasAudio && this.hasaudio; 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 close: function () { 307 close: function () {
254 console.log('关闭视频'); 308 console.log('关闭视频');
255 - if (!this.$refs.videoPlayer){ 309 + if (!!this.$refs.videoPlayer){
256 this.$refs.videoPlayer.pause(); 310 this.$refs.videoPlayer.pause();
257 } 311 }
258 this.videoUrl = ''; 312 this.videoUrl = '';
  313 + this.coverPlaying = false;
259 this.showVideoDialog = false; 314 this.showVideoDialog = false;
260 if (this.convertKey != '') { 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 copySharedInfo: function (data) { 321 copySharedInfo: function (data) {
275 console.log('复制内容:' + data); 322 console.log('复制内容:' + data);
  323 + this.coverPlaying = false;
  324 + this.tracks = []
276 let _this = this; 325 let _this = this;
277 this.$copyText(data).then( 326 this.$copyText(data).then(
278 function (e) { 327 function (e) {
@@ -602,4 +651,15 @@ export default { @@ -602,4 +651,15 @@ export default {
602 .control-bottom .fa { 651 .control-bottom .fa {
603 transform: rotate(-45deg) translateY(7px); 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 </style> 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 \ No newline at end of file 58 \ No newline at end of file
web_src/src/components/videoList.vue
@@ -73,12 +73,10 @@ @@ -73,12 +73,10 @@
73 </template> 73 </template>
74 74
75 <script> 75 <script>
76 - import devicePlayer from './gb28181/devicePlayer.vue'  
77 import uiHeader from './UiHeader.vue' 76 import uiHeader from './UiHeader.vue'
78 export default { 77 export default {
79 name: 'app', 78 name: 'app',
80 components: { 79 components: {
81 - devicePlayer,  
82 uiHeader 80 uiHeader
83 }, 81 },
84 data() { 82 data() {
web_src/src/router/index.js
@@ -35,7 +35,7 @@ export default new VueRouter({ @@ -35,7 +35,7 @@ export default new VueRouter({
35 path: '/channelList/:deviceId/:parentChannelId/:count/:page', 35 path: '/channelList/:deviceId/:parentChannelId/:count/:page',
36 name: 'channelList', 36 name: 'channelList',
37 component: channelList, 37 component: channelList,
38 - },, 38 + },
39 { 39 {
40 path: '/parentPlatformList/:count/:page', 40 path: '/parentPlatformList/:count/:page',
41 name: 'parentPlatformList', 41 name: 'parentPlatformList',