Commit f75b3e6cdab1c0ad98eb09906912a42b783f67bf

Authored by 648540858
1 parent 5a4859f0

使用异步接口, 更好的并发, 对hook使用订阅机制

替换前段播放器, 支持h265的播放
放弃循环获取编码信息,
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/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 6
6 /** 7 /**
7 * @Description:设备能力接口,用于定义设备的控制、查询能力 8 * @Description:设备能力接口,用于定义设备的控制、查询能力
@@ -19,7 +20,7 @@ public interface ISIPCommander { @@ -19,7 +20,7 @@ public interface ISIPCommander {
19 * @param upDown 镜头上移下移 0:停止 1:上移 2:下移 20 * @param upDown 镜头上移下移 0:停止 1:上移 2:下移
20 * @param moveSpeed 镜头移动速度 21 * @param moveSpeed 镜头移动速度
21 */ 22 */
22 - public boolean ptzdirectCmd(Device device,String channelId,int leftRight, int upDown); 23 + boolean ptzdirectCmd(Device device,String channelId,int leftRight, int upDown);
23 24
24 /** 25 /**
25 * 云台方向放控制 26 * 云台方向放控制
@@ -30,7 +31,7 @@ public interface ISIPCommander { @@ -30,7 +31,7 @@ public interface ISIPCommander {
30 * @param upDown 镜头上移下移 0:停止 1:上移 2:下移 31 * @param upDown 镜头上移下移 0:停止 1:上移 2:下移
31 * @param moveSpeed 镜头移动速度 32 * @param moveSpeed 镜头移动速度
32 */ 33 */
33 - public boolean ptzdirectCmd(Device device,String channelId,int leftRight, int upDown, int moveSpeed); 34 + boolean ptzdirectCmd(Device device,String channelId,int leftRight, int upDown, int moveSpeed);
34 35
35 /** 36 /**
36 * 云台缩放控制,使用配置文件中的默认镜头缩放速度 37 * 云台缩放控制,使用配置文件中的默认镜头缩放速度
@@ -39,7 +40,7 @@ public interface ISIPCommander { @@ -39,7 +40,7 @@ public interface ISIPCommander {
39 * @param channelId 预览通道 40 * @param channelId 预览通道
40 * @param inOut 镜头放大缩小 0:停止 1:缩小 2:放大 41 * @param inOut 镜头放大缩小 0:停止 1:缩小 2:放大
41 */ 42 */
42 - public boolean ptzZoomCmd(Device device,String channelId,int inOut); 43 + boolean ptzZoomCmd(Device device,String channelId,int inOut);
43 44
44 /** 45 /**
45 * 云台缩放控制 46 * 云台缩放控制
@@ -49,7 +50,7 @@ public interface ISIPCommander { @@ -49,7 +50,7 @@ public interface ISIPCommander {
49 * @param inOut 镜头放大缩小 0:停止 1:缩小 2:放大 50 * @param inOut 镜头放大缩小 0:停止 1:缩小 2:放大
50 * @param zoomSpeed 镜头缩放速度 51 * @param zoomSpeed 镜头缩放速度
51 */ 52 */
52 - public boolean ptzZoomCmd(Device device,String channelId,int inOut, int moveSpeed); 53 + boolean ptzZoomCmd(Device device,String channelId,int inOut, int moveSpeed);
53 54
54 /** 55 /**
55 * 云台控制,支持方向与缩放控制 56 * 云台控制,支持方向与缩放控制
@@ -62,7 +63,7 @@ public interface ISIPCommander { @@ -62,7 +63,7 @@ public interface ISIPCommander {
62 * @param moveSpeed 镜头移动速度 63 * @param moveSpeed 镜头移动速度
63 * @param zoomSpeed 镜头缩放速度 64 * @param zoomSpeed 镜头缩放速度
64 */ 65 */
65 - public boolean ptzCmd(Device device,String channelId,int leftRight, int upDown, int inOut, int moveSpeed, int zoomSpeed); 66 + boolean ptzCmd(Device device,String channelId,int leftRight, int upDown, int inOut, int moveSpeed, int zoomSpeed);
66 67
67 /** 68 /**
68 * 前端控制,包括PTZ指令、FI指令、预置位指令、巡航指令、扫描指令和辅助开关指令 69 * 前端控制,包括PTZ指令、FI指令、预置位指令、巡航指令、扫描指令和辅助开关指令
@@ -74,7 +75,7 @@ public interface ISIPCommander { @@ -74,7 +75,7 @@ public interface ISIPCommander {
74 * @param parameter2 数据2 75 * @param parameter2 数据2
75 * @param combineCode2 组合码2 76 * @param combineCode2 组合码2
76 */ 77 */
77 - public boolean frontEndCmd(Device device, String channelId, int cmdCode, int parameter1, int parameter2, int combineCode2); 78 + boolean frontEndCmd(Device device, String channelId, int cmdCode, int parameter1, int parameter2, int combineCode2);
78 79
79 /** 80 /**
80 * 请求预览视频流 81 * 请求预览视频流
@@ -82,7 +83,7 @@ public interface ISIPCommander { @@ -82,7 +83,7 @@ public interface ISIPCommander {
82 * @param device 视频设备 83 * @param device 视频设备
83 * @param channelId 预览通道 84 * @param channelId 预览通道
84 */ 85 */
85 - public StreamInfo playStreamCmd(Device device, String channelId); 86 + void playStreamCmd(Device device, String channelId, ZLMHttpHookSubscribe.Event event);
86 87
87 /** 88 /**
88 * 请求回放视频流 89 * 请求回放视频流
@@ -92,14 +93,14 @@ public interface ISIPCommander { @@ -92,14 +93,14 @@ public interface ISIPCommander {
92 * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss 93 * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
93 * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss 94 * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
94 */ 95 */
95 - public StreamInfo playbackStreamCmd(Device device,String channelId, String startTime, String endTime); 96 + void playbackStreamCmd(Device device, String channelId, String startTime, String endTime, ZLMHttpHookSubscribe.Event event);
96 97
97 /** 98 /**
98 * 视频流停止 99 * 视频流停止
99 * 100 *
100 * @param ssrc ssrc 101 * @param ssrc ssrc
101 */ 102 */
102 - public void streamByeCmd(String ssrc); 103 + void streamByeCmd(String ssrc);
103 104
104 /** 105 /**
105 * 语音广播 106 * 语音广播
@@ -107,7 +108,7 @@ public interface ISIPCommander { @@ -107,7 +108,7 @@ public interface ISIPCommander {
107 * @param device 视频设备 108 * @param device 视频设备
108 * @param channelId 预览通道 109 * @param channelId 预览通道
109 */ 110 */
110 - public boolean audioBroadcastCmd(Device device,String channelId); 111 + boolean audioBroadcastCmd(Device device,String channelId);
111 112
112 /** 113 /**
113 * 音视频录像控制 114 * 音视频录像控制
@@ -115,21 +116,21 @@ public interface ISIPCommander { @@ -115,21 +116,21 @@ public interface ISIPCommander {
115 * @param device 视频设备 116 * @param device 视频设备
116 * @param channelId 预览通道 117 * @param channelId 预览通道
117 */ 118 */
118 - public boolean recordCmd(Device device,String channelId); 119 + boolean recordCmd(Device device,String channelId);
119 120
120 /** 121 /**
121 * 报警布防/撤防命令 122 * 报警布防/撤防命令
122 * 123 *
123 * @param device 视频设备 124 * @param device 视频设备
124 */ 125 */
125 - public boolean guardCmd(Device device); 126 + boolean guardCmd(Device device);
126 127
127 /** 128 /**
128 * 报警复位命令 129 * 报警复位命令
129 * 130 *
130 * @param device 视频设备 131 * @param device 视频设备
131 */ 132 */
132 - public boolean alarmCmd(Device device); 133 + boolean alarmCmd(Device device);
133 134
134 /** 135 /**
135 * 强制关键帧命令,设备收到此命令应立刻发送一个IDR帧 136 * 强制关键帧命令,设备收到此命令应立刻发送一个IDR帧
@@ -137,21 +138,21 @@ public interface ISIPCommander { @@ -137,21 +138,21 @@ public interface ISIPCommander {
137 * @param device 视频设备 138 * @param device 视频设备
138 * @param channelId 预览通道 139 * @param channelId 预览通道
139 */ 140 */
140 - public boolean iFameCmd(Device device,String channelId); 141 + boolean iFameCmd(Device device,String channelId);
141 142
142 /** 143 /**
143 * 看守位控制命令 144 * 看守位控制命令
144 * 145 *
145 * @param device 视频设备 146 * @param device 视频设备
146 */ 147 */
147 - public boolean homePositionCmd(Device device); 148 + boolean homePositionCmd(Device device);
148 149
149 /** 150 /**
150 * 设备配置命令 151 * 设备配置命令
151 * 152 *
152 * @param device 视频设备 153 * @param device 视频设备
153 */ 154 */
154 - public boolean deviceConfigCmd(Device device); 155 + boolean deviceConfigCmd(Device device);
155 156
156 157
157 /** 158 /**
@@ -159,7 +160,7 @@ public interface ISIPCommander { @@ -159,7 +160,7 @@ public interface ISIPCommander {
159 * 160 *
160 * @param device 视频设备 161 * @param device 视频设备
161 */ 162 */
162 - public boolean deviceStatusQuery(Device device); 163 + boolean deviceStatusQuery(Device device);
163 164
164 /** 165 /**
165 * 查询设备信息 166 * 查询设备信息
@@ -167,14 +168,14 @@ public interface ISIPCommander { @@ -167,14 +168,14 @@ public interface ISIPCommander {
167 * @param device 视频设备 168 * @param device 视频设备
168 * @return 169 * @return
169 */ 170 */
170 - public boolean deviceInfoQuery(Device device); 171 + boolean deviceInfoQuery(Device device);
171 172
172 /** 173 /**
173 * 查询目录列表 174 * 查询目录列表
174 * 175 *
175 * @param device 视频设备 176 * @param device 视频设备
176 */ 177 */
177 - public boolean catalogQuery(Device device); 178 + boolean catalogQuery(Device device);
178 179
179 /** 180 /**
180 * 查询录像信息 181 * 查询录像信息
@@ -183,33 +184,33 @@ public interface ISIPCommander { @@ -183,33 +184,33 @@ public interface ISIPCommander {
183 * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss 184 * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
184 * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss 185 * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
185 */ 186 */
186 - public boolean recordInfoQuery(Device device, String channelId, String startTime, String endTime); 187 + boolean recordInfoQuery(Device device, String channelId, String startTime, String endTime);
187 188
188 /** 189 /**
189 * 查询报警信息 190 * 查询报警信息
190 * 191 *
191 * @param device 视频设备 192 * @param device 视频设备
192 */ 193 */
193 - public boolean alarmInfoQuery(Device device); 194 + boolean alarmInfoQuery(Device device);
194 195
195 /** 196 /**
196 * 查询设备配置 197 * 查询设备配置
197 * 198 *
198 * @param device 视频设备 199 * @param device 视频设备
199 */ 200 */
200 - public boolean configQuery(Device device); 201 + boolean configQuery(Device device);
201 202
202 /** 203 /**
203 * 查询设备预置位置 204 * 查询设备预置位置
204 * 205 *
205 * @param device 视频设备 206 * @param device 视频设备
206 */ 207 */
207 - public boolean presetQuery(Device device); 208 + boolean presetQuery(Device device);
208 209
209 /** 210 /**
210 * 查询移动设备位置数据 211 * 查询移动设备位置数据
211 * 212 *
212 * @param device 视频设备 213 * @param device 视频设备
213 */ 214 */
214 - public boolean mobilePostitionQuery(Device device); 215 + boolean mobilePostitionQuery(Device device);
215 } 216 }
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,57 @@ public class SIPCommander implements ISIPCommander { @@ -282,53 +286,57 @@ 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 96 98 97\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 96 98 97\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 96 98 97\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=rtpmap:96 PS/90000\r\n");
  313 + content.append("a=rtpmap:98 H264/90000\r\n");
  314 + content.append("a=rtpmap:97 MPEG4/90000\r\n");
  315 + if("TCP-PASSIVE".equals(streamMode)){ // tcp被动模式
  316 + content.append("a=setup:passive\r\n");
305 content.append("a=connection:new\r\n"); 317 content.append("a=connection:new\r\n");
306 - }else if ("TCP-ACTIVE".equals(streamMode)) { // tcp主动模式 318 + }else if ("TCP-ACTIVE".equals(streamMode)) { // tcp主动模式
307 content.append("a=setup:active\r\n"); 319 content.append("a=setup:active\r\n");
308 content.append("a=connection:new\r\n"); 320 content.append("a=connection:new\r\n");
309 } 321 }
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); 322 + content.append("y="+ssrc+"\r\n");//ssrc
  323 +
  324 + Request request = headerProvider.createInviteRequest(device, channelId, content.toString(), null, "live", null, ssrc);
  325 +
  326 + ClientTransaction transaction = transmitRequest(device, request);
  327 + streamSession.put(ssrc, transaction);
316 DeviceChannel deviceChannel = storager.queryChannel(device.getDeviceId(), channelId); 328 DeviceChannel deviceChannel = storager.queryChannel(device.getDeviceId(), channelId);
317 if (deviceChannel != null) { 329 if (deviceChannel != null) {
318 deviceChannel.setSsrc(ssrc); 330 deviceChannel.setSsrc(ssrc);
319 storager.updateChannel(device.getDeviceId(), deviceChannel); 331 storager.updateChannel(device.getDeviceId(), deviceChannel);
320 } 332 }
321 333
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; 334 + // TODO 订阅SIP response,处理对方的错误返回
  335 +
  336 +
328 } catch ( SipException | ParseException | InvalidArgumentException e) { 337 } catch ( SipException | ParseException | InvalidArgumentException e) {
329 e.printStackTrace(); 338 e.printStackTrace();
330 - return null;  
331 - } 339 + }
332 } 340 }
333 341
334 /** 342 /**
@@ -340,10 +348,18 @@ public class SIPCommander implements ISIPCommander { @@ -340,10 +348,18 @@ public class SIPCommander implements ISIPCommander {
340 * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss 348 * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
341 */ 349 */
342 @Override 350 @Override
343 - public StreamInfo playbackStreamCmd(Device device, String channelId, String startTime, String endTime) { 351 + public void playbackStreamCmd(Device device, String channelId, String startTime, String endTime, ZLMHttpHookSubscribe.Event event) {
344 try { 352 try {
345 MediaServerConfig mediaInfo = storager.getMediaInfo(); 353 MediaServerConfig mediaInfo = storager.getMediaInfo();
346 String ssrc = streamSession.createPlayBackSsrc(); 354 String ssrc = streamSession.createPlayBackSsrc();
  355 + String streamId = String.format("%08x", Integer.parseInt(ssrc)).toUpperCase();
  356 + // 添加订阅
  357 + JSONObject subscribeKey = new JSONObject();
  358 + subscribeKey.put("app", "rtp");
  359 + subscribeKey.put("id", streamId);
  360 +
  361 + subscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_publish, subscribeKey, event);
  362 +
347 // 363 //
348 StringBuffer content = new StringBuffer(200); 364 StringBuffer content = new StringBuffer(200);
349 content.append("v=0\r\n"); 365 content.append("v=0\r\n");
@@ -386,16 +402,8 @@ public class SIPCommander implements ISIPCommander { @@ -386,16 +402,8 @@ public class SIPCommander implements ISIPCommander {
386 ClientTransaction transaction = transmitRequest(device, request); 402 ClientTransaction transaction = transmitRequest(device, request);
387 streamSession.put(ssrc, transaction); 403 streamSession.put(ssrc, transaction);
388 404
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) { 405 } catch ( SipException | ParseException | InvalidArgumentException e) {
397 e.printStackTrace(); 406 e.printStackTrace();
398 - return null;  
399 } 407 }
400 } 408 }
401 409
@@ -433,6 +441,7 @@ public class SIPCommander implements ISIPCommander { @@ -433,6 +441,7 @@ public class SIPCommander implements ISIPCommander {
433 clientTransaction = udpSipProvider.getNewClientTransaction(byeRequest); 441 clientTransaction = udpSipProvider.getNewClientTransaction(byeRequest);
434 } 442 }
435 dialog.sendRequest(clientTransaction); 443 dialog.sendRequest(clientTransaction);
  444 + streamSession.remove(ssrc);
436 } catch (TransactionDoesNotExistException e) { 445 } catch (TransactionDoesNotExistException e) {
437 e.printStackTrace(); 446 e.printStackTrace();
438 } catch (SipException e) { 447 } catch (SipException e) {
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/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.web.bind.annotation.*;
  17 +
  18 +import javax.servlet.http.HttpServletRequest;
  19 +import java.math.BigInteger;
  20 +import java.text.DecimalFormat;
  21 +import java.util.HashMap;
  22 +import java.util.Map;
  23 +
  24 +/**
  25 + * @Description:针对 ZLMediaServer的hook事件订阅
  26 + * @author: pan
  27 + * @date: 2020年12月2日 21:17:32
  28 + */
  29 +@Component
  30 +public class ZLMHttpHookSubscribe {
  31 +
  32 + private final static Logger logger = LoggerFactory.getLogger(ZLMHttpHookSubscribe.class);
  33 +
  34 + public enum HookType{
  35 + on_flow_report,
  36 + on_http_access,
  37 + on_play,
  38 + on_publish,
  39 + on_record_mp4,
  40 + on_rtsp_auth,
  41 + on_rtsp_realm,
  42 + on_shell_login,
  43 + on_stream_changed,
  44 + on_stream_none_reader,
  45 + on_stream_not_found,
  46 + on_server_started
  47 + }
  48 +
  49 + public interface Event{
  50 + void response(JSONObject response);
  51 + }
  52 +
  53 + private Map<HookType, Map<JSONObject, ZLMHttpHookSubscribe.Event>> allSubscribes = new HashMap<>();
  54 +
  55 + public void addSubscribe(HookType type, JSONObject hookResponse, ZLMHttpHookSubscribe.Event event) {
  56 + Map<JSONObject, Event> eventMap = allSubscribes.get(type);
  57 + if (eventMap == null) {
  58 + eventMap = new HashMap<JSONObject, Event>();
  59 + allSubscribes.put(type,eventMap);
  60 + }
  61 + eventMap.put(hookResponse, event);
  62 + }
  63 +
  64 + public ZLMHttpHookSubscribe.Event getSubscribe(HookType type, JSONObject hookResponse) {
  65 + ZLMHttpHookSubscribe.Event event= null;
  66 + Map<JSONObject, Event> eventMap = allSubscribes.get(type);
  67 + if (eventMap == null) {
  68 + return null;
  69 + }
  70 + for (JSONObject key : eventMap.keySet()) {
  71 + Boolean result = null;
  72 + for (String s : key.keySet()) {
  73 + String string = hookResponse.getString(s);
  74 + String string1 = key.getString(s);
  75 + if (result == null) {
  76 + result = key.getString(s).equals(hookResponse.getString(s));
  77 + }else {
  78 + result = result && key.getString(s).equals(hookResponse.getString(s));
  79 + }
  80 +
  81 + }
  82 + if (result) {
  83 + event = eventMap.get(key);
  84 + }
  85 + }
  86 + return event;
  87 + }
  88 +}
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,95 +46,56 @@ public class PlayController { @@ -39,95 +46,56 @@ 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 + RequestMessage msg = new RequestMessage();
  68 + msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
  69 + msg.setData("Timeout");
  70 + resultHolder.invokeResult(msg);
  71 + });
  72 + // 录像查询以channelId作为deviceId查询
  73 + resultHolder.put(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid, result);
  74 +
54 if (streamInfo == null) { 75 if (streamInfo == null) {
55 - streamInfo = cmder.playStreamCmd(device, channelId); 76 + // TODO playStreamCmd 超时处理
  77 + cmder.playStreamCmd(device, channelId, (JSONObject response) -> {
  78 + logger.info("收到订阅消息: " + response.toJSONString());
  79 + playService.onPublishHandlerForPlay(response, deviceId, channelId, uuid.toString());
  80 + });
56 } else { 81 } else {
57 String streamId = String.format("%08x", Integer.parseInt(streamInfo.getSsrc())).toUpperCase(); 82 String streamId = String.format("%08x", Integer.parseInt(streamInfo.getSsrc())).toUpperCase();
58 JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(streamId); 83 JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(streamId);
59 if (rtpInfo.getBoolean("exist")) { 84 if (rtpInfo.getBoolean("exist")) {
60 - return new ResponseEntity<String>(JSON.toJSONString(streamInfo), HttpStatus.OK); 85 + RequestMessage msg = new RequestMessage();
  86 + msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
  87 + msg.setData(JSON.toJSONString(streamInfo));
  88 + resultHolder.invokeResult(msg);
61 } else { 89 } else {
62 storager.stopPlay(streamInfo); 90 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 - logger.info(tracks.toJSONString());  
97 - streamInfo.setTracks(tracks);  
98 - storager.startPlay(streamInfo);  
99 - } else {  
100 - logger.info("流编码信息未获取,2秒后重试...");  
101 - }  
102 - } else {  
103 - Thread.sleep(2000);  
104 - continue;  
105 - }  
106 - }  
107 - } catch (InterruptedException e) {  
108 - e.printStackTrace();  
109 - } 91 + // TODO playStreamCmd 超时处理
  92 + cmder.playStreamCmd(device, channelId, (JSONObject response) -> {
  93 + logger.info("收到订阅消息: " + response.toJSONString());
  94 + playService.onPublishHandlerForPlay(response, deviceId, channelId, uuid.toString());
  95 + });
110 } 96 }
111 - } else {  
112 - String flv = storager.getMediaInfo().getWanIp() + ":" + storager.getMediaInfo().getHttpPort() + "/rtp/"  
113 - + streamId + ".flv";  
114 - streamInfo.setFlv("http://" + flv);  
115 - streamInfo.setWs_flv("ws://" + flv);  
116 - storager.startPlay(streamInfo);  
117 - }  
118 -  
119 - if (logger.isDebugEnabled()) {  
120 - logger.debug(String.format("设备预览 API调用,deviceId:%s ,channelId:%s", deviceId, channelId));  
121 - logger.debug("设备预览 API调用,ssrc:" + streamInfo.getSsrc() + ",ZLMedia streamId:"  
122 - + Integer.toHexString(Integer.parseInt(streamInfo.getSsrc())));  
123 - }  
124 -  
125 - if (streamInfo != null) {  
126 - return new ResponseEntity<String>(JSON.toJSONString(streamInfo), HttpStatus.OK);  
127 - } else {  
128 - logger.warn("设备预览API调用失败!");  
129 - return new ResponseEntity<String>(HttpStatus.INTERNAL_SERVER_ERROR);  
130 } 97 }
  98 + return result;
131 } 99 }
132 100
133 @PostMapping("/play/{ssrc}/stop") 101 @PostMapping("/play/{ssrc}/stop")
@@ -180,10 +148,20 @@ public class PlayController { @@ -180,10 +148,20 @@ public class PlayController {
180 result.put("code", 0); 148 result.put("code", 0);
181 JSONObject data = jsonObject.getJSONObject("data"); 149 JSONObject data = jsonObject.getJSONObject("data");
182 if (data != null) { 150 if (data != null) {
183 - result.put("key", data.getString("key"));  
184 - result.put("rtmp", dstUrl);  
185 - result.put("flv", String.format("http://%s:%s/convert/%s.flv", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));  
186 - result.put("ws_flv", String.format("ws://%s:%s/convert/%s.flv", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId)); 151 + result.put("key", data.getString("key"));
  152 + StreamInfo streamInfoResult = new StreamInfo();
  153 + streamInfoResult.setRtmp(dstUrl);
  154 + streamInfoResult.setRtsp(String.format("rtsp://%s:%s/convert/%s", mediaInfo.getWanIp(), mediaInfo.getRtspPort(), streamId));
  155 + streamInfoResult.setStreamId(streamId);
  156 + streamInfoResult.setFlv(String.format("http://%s:%s/convert/%s.flv", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
  157 + streamInfoResult.setWs_flv(String.format("ws://%s:%s/convert/%s.flv", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
  158 + streamInfoResult.setHls(String.format("http://%s:%s/convert/%s/hls.m3u8", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
  159 + streamInfoResult.setWs_hls(String.format("ws://%s:%s/convert/%s/hls.m3u8", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
  160 + streamInfoResult.setFmp4(String.format("http://%s:%s/convert/%s.live.mp4", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
  161 + streamInfoResult.setWs_fmp4(String.format("ws://%s:%s/convert/%s.live.mp4", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
  162 + streamInfoResult.setTs(String.format("http://%s:%s/convert/%s.live.ts", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
  163 + streamInfoResult.setWs_ts(String.format("ws://%s:%s/convert/%s.live.ts", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
  164 + result.put("data", streamInfoResult);
187 } 165 }
188 }else { 166 }else {
189 result.put("code", 1); 167 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,41 @@ public class PlaybackController { @@ -39,105 +45,41 @@ 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 + RequestMessage msg = new RequestMessage();
  66 + msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
  67 + msg.setData("Timeout");
  68 + resultHolder.invokeResult(msg);
  69 + });
59 Device device = storager.queryVideoDevice(deviceId); 70 Device device = storager.queryVideoDevice(deviceId);
60 StreamInfo streamInfo = storager.queryPlaybackByDevice(deviceId, channelId); 71 StreamInfo streamInfo = storager.queryPlaybackByDevice(deviceId, channelId);
61 -  
62 if (streamInfo != null) { 72 if (streamInfo != null) {
  73 + // 停止之前的回放
63 cmder.streamByeCmd(streamInfo.getSsrc()); 74 cmder.streamByeCmd(streamInfo.getSsrc());
64 } 75 }
  76 + resultHolder.put(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid, result);
  77 + cmder.playbackStreamCmd(device, channelId, startTime, endTime, (JSONObject response) -> {
  78 + logger.info("收到订阅消息: " + response.toJSONString());
  79 + playService.onPublishHandlerForPlayBack(response, deviceId, channelId, uuid.toString());
  80 + });
65 81
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 - } 82 + return result;
141 } 83 }
142 84
143 @RequestMapping("/playback/{ssrc}/stop") 85 @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
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,75 +234,75 @@ export default { @@ -180,75 +234,75 @@ 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('关闭视频');
@@ -256,23 +310,18 @@ export default { @@ -256,23 +310,18 @@ export default {
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() {