Commit 26739237e2d93460eb869067a6004bfa63a1bdb8
Merge remote-tracking branch 'origin/wvp-28181-2.0' into commercial
# Conflicts: # sql/update.sql # src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java # src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java # src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java # src/main/java/com/genersoft/iot/vmp/service/IPlayService.java # src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java # web_src/src/components/dialog/deviceEdit.vue # web_src/src/components/dialog/devicePlayer.vue
Showing
106 changed files
with
3655 additions
and
1463 deletions
DOCKERFILE
README.md
| ... | ... | @@ -107,6 +107,8 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git |
| 107 | 107 | - [X] 鈭垢敶閬蝵脣蝙嚗 |
| 108 | 108 | - [X] 憭嚗韐蝸雿雿輻 |
| 109 | 109 | - [X] WEB蝡舀H264銝265嚗憸.711A/G.711U/AAC,閬虜蝻撘 |
| 110 | +- [X] 摮 | |
| 111 | +- [X] WGS84CJ02銝斤頂 | |
| 110 | 112 | |
| 111 | 113 | [//]: # (# docker敹恍) |
| 112 | 114 | |
| ... | ... | @@ -143,7 +145,7 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git |
| 143 | 145 | |
| 144 | 146 | # |
| 145 | 147 | 蝘摰之摰嗅之銝瓷移憸隞乩晶圾蝑隞交R |
| 146 | 148 | -嚗笆隞遣霈桀隞交SSUE嚗隞亙黎銝韏瑁賑甈Z頞憿寧銝剜犖 |
| 149 | +嚗笆隞遣霈桀隞交SSUE嚗隞亙黎銝韏瑁賑甈Z頞憿寧銝剜犖 | |
| 147 | 150 | |
| 148 | 151 | |
| 149 | 152 | |
| ... | ... | @@ -163,6 +165,7 @@ QQ蝘縑銝銝, 蝎曉.甈Z之摰嗅蝢日悄霈.閫★撖嫣 |
| 163 | 165 | [hotcoffie](https://github.com/hotcoffie) [xiaomu](https://github.com/nikmu) [TristingChen](https://github.com/TristingChen) |
| 164 | 166 | [chenparty](https://github.com/chenparty) [Hotleave](https://github.com/hotleave) [ydwxb](https://github.com/ydwxb) |
| 165 | 167 | [ydpd](https://github.com/ydpd) [szy833](https://github.com/szy833) [ydwxb](https://github.com/ydwxb) [Albertzhu666](https://github.com/Albertzhu666) |
| 168 | +[mk1990](https://github.com/mk1990) | |
| 166 | 169 | |
| 167 | 170 | ps: 葵摰鈭之雿穿洽餈之雿祈頂溶 |
| 168 | 171 | ... | ... |
pom.xml
| ... | ... | @@ -11,7 +11,7 @@ |
| 11 | 11 | |
| 12 | 12 | <groupId>com.genersoft</groupId> |
| 13 | 13 | <artifactId>wvp-pro</artifactId> |
| 14 | - <version>2.2.1</version> | |
| 14 | + <version>2.3.1</version> | |
| 15 | 15 | <name>web video platform</name> |
| 16 | 16 | <description>国标28181视频平台</description> |
| 17 | 17 | |
| ... | ... | @@ -159,9 +159,10 @@ |
| 159 | 159 | <dependency> |
| 160 | 160 | <groupId>com.alibaba</groupId> |
| 161 | 161 | <artifactId>fastjson</artifactId> |
| 162 | - <version>1.2.73</version> | |
| 162 | + <version>1.2.83</version> | |
| 163 | 163 | </dependency> |
| 164 | 164 | |
| 165 | + | |
| 165 | 166 | <!-- okhttp --> |
| 166 | 167 | <dependency> |
| 167 | 168 | <groupId>com.squareup.okhttp3</groupId> |
| ... | ... | @@ -180,9 +181,9 @@ |
| 180 | 181 | |
| 181 | 182 | <!-- okhttp-digest --> |
| 182 | 183 | <dependency> |
| 183 | - <groupId>com.burgstaller</groupId> | |
| 184 | + <groupId>io.github.rburgst</groupId> | |
| 184 | 185 | <artifactId>okhttp-digest</artifactId> |
| 185 | - <version>2.1</version> | |
| 186 | + <version>2.5</version> | |
| 186 | 187 | </dependency> |
| 187 | 188 | |
| 188 | 189 | <!-- https://mvnrepository.com/artifact/net.sf.kxml/kxml2 --> | ... | ... |
sql/mysql.sql
| ... | ... | @@ -471,6 +471,7 @@ CREATE TABLE `stream_push` ( |
| 471 | 471 | `createStamp` bigint(20) DEFAULT NULL, |
| 472 | 472 | `aliveSecond` int(11) DEFAULT NULL, |
| 473 | 473 | `mediaServerId` varchar(50) DEFAULT NULL, |
| 474 | + `serverId` varchar(50) not NULL, | |
| 474 | 475 | PRIMARY KEY (`id`), |
| 475 | 476 | UNIQUE KEY `stream_push_pk` (`app`,`stream`) |
| 476 | 477 | ) ENGINE=InnoDB AUTO_INCREMENT=300838 DEFAULT CHARSET=utf8mb4; | ... | ... |
sql/update.sql
| 1 | +alter table parent_platform | |
| 2 | + add startOfflinePush int default 0 null; | |
| 3 | + | |
| 4 | +alter table parent_platform | |
| 5 | + add administrativeDivision varchar(50) not null; | |
| 6 | + | |
| 7 | +alter table parent_platform | |
| 8 | + add catalogGroup int default 1 null; | |
| 9 | + | |
| 1 | 10 | alter table device |
| 2 | 11 | add audioChannelForReceive VARCHAR(50) null; |
| 3 | 12 | |
| 4 | 13 | alter table device |
| 5 | - add audioChannelForSend VARCHAR(50) null; | |
| 6 | 14 | \ No newline at end of file |
| 15 | + add audioChannelForSend VARCHAR(50) null; | |
| 16 | + | |
| 17 | +alter table stream_push | |
| 18 | + add serverId varchar(50) not null; | |
| 19 | +alter table device | |
| 20 | + add geoCoordSys varchar(50) not null; | |
| 21 | +update device set device.geoCoordSys='WGS84'; | |
| 22 | +alter table device_channel | |
| 23 | + add longitudeGcj02 double default null; | |
| 24 | +alter table device_channel | |
| 25 | + add latitudeGcj02 double default null; | |
| 26 | +alter table device_channel | |
| 27 | + add longitudeWgs84 double default null; | |
| 28 | +alter table device_channel | |
| 29 | + add latitudeWgs84 double default null; | |
| 30 | + | ... | ... |
src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java
| ... | ... | @@ -97,4 +97,5 @@ public class VideoManagerConstants { |
| 97 | 97 | //************************** 第三方 **************************************** |
| 98 | 98 | public static final String WVP_STREAM_GB_ID_PREFIX = "memberNo_"; |
| 99 | 99 | public static final String WVP_STREAM_GPS_MSG_PREFIX = "WVP_STREAM_GPS_MSG_"; |
| 100 | + | |
| 100 | 101 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/conf/DynamicTask.java
| ... | ... | @@ -103,12 +103,12 @@ public class DynamicTask { |
| 103 | 103 | |
| 104 | 104 | public void stop(String key) { |
| 105 | 105 | if (futureMap.get(key) != null && !futureMap.get(key).isCancelled()) { |
| 106 | - futureMap.get(key).cancel(true); | |
| 107 | - Runnable runnable = runnableMap.get(key); | |
| 108 | - if (runnable instanceof ISubscribeTask) { | |
| 109 | - ISubscribeTask subscribeTask = (ISubscribeTask) runnable; | |
| 110 | - subscribeTask.stop(); | |
| 111 | - } | |
| 106 | +// Runnable runnable = runnableMap.get(key); | |
| 107 | +// if (runnable instanceof ISubscribeTask) { | |
| 108 | +// ISubscribeTask subscribeTask = (ISubscribeTask) runnable; | |
| 109 | +// subscribeTask.stop(); | |
| 110 | +// } | |
| 111 | + futureMap.get(key).cancel(false); | |
| 112 | 112 | } |
| 113 | 113 | } |
| 114 | 114 | ... | ... |
src/main/java/com/genersoft/iot/vmp/conf/MediaConfig.java
| ... | ... | @@ -6,6 +6,10 @@ import org.springframework.beans.factory.annotation.Value; |
| 6 | 6 | import org.springframework.context.annotation.Configuration; |
| 7 | 7 | import org.springframework.util.StringUtils; |
| 8 | 8 | |
| 9 | +import java.net.InetAddress; | |
| 10 | +import java.net.UnknownHostException; | |
| 11 | +import java.util.regex.Pattern; | |
| 12 | + | |
| 9 | 13 | |
| 10 | 14 | @Configuration("mediaConfig") |
| 11 | 15 | public class MediaConfig{ |
| ... | ... | @@ -161,7 +165,18 @@ public class MediaConfig{ |
| 161 | 165 | if (StringUtils.isEmpty(sdpIp)){ |
| 162 | 166 | return ip; |
| 163 | 167 | }else { |
| 164 | - return sdpIp; | |
| 168 | + if (isValidIPAddress(sdpIp)) { | |
| 169 | + return sdpIp; | |
| 170 | + }else { | |
| 171 | + // 按照域名解析 | |
| 172 | + String hostAddress = null; | |
| 173 | + try { | |
| 174 | + hostAddress = InetAddress.getByName(sdpIp).getHostAddress(); | |
| 175 | + } catch (UnknownHostException e) { | |
| 176 | + throw new RuntimeException(e); | |
| 177 | + } | |
| 178 | + return hostAddress; | |
| 179 | + } | |
| 165 | 180 | } |
| 166 | 181 | } |
| 167 | 182 | |
| ... | ... | @@ -211,4 +226,11 @@ public class MediaConfig{ |
| 211 | 226 | return mediaServerItem; |
| 212 | 227 | } |
| 213 | 228 | |
| 229 | + private boolean isValidIPAddress(String ipAddress) { | |
| 230 | + if ((ipAddress != null) && (!ipAddress.isEmpty())) { | |
| 231 | + return Pattern.matches("^([1-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])){3}$", ipAddress); | |
| 232 | + } | |
| 233 | + return false; | |
| 234 | + } | |
| 235 | + | |
| 214 | 236 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/conf/RedisConfig.java
| ... | ... | @@ -2,7 +2,9 @@ package com.genersoft.iot.vmp.conf; |
| 2 | 2 | |
| 3 | 3 | import com.genersoft.iot.vmp.common.VideoManagerConstants; |
| 4 | 4 | import com.genersoft.iot.vmp.service.impl.RedisAlarmMsgListener; |
| 5 | -import com.genersoft.iot.vmp.service.impl.RedisGPSMsgListener; | |
| 5 | +import com.genersoft.iot.vmp.service.impl.RedisGpsMsgListener; | |
| 6 | +import com.genersoft.iot.vmp.service.impl.RedisGbPlayMsgListener; | |
| 7 | +import com.genersoft.iot.vmp.service.impl.RedisStreamMsgListener; | |
| 6 | 8 | import org.apache.commons.lang3.StringUtils; |
| 7 | 9 | import org.springframework.beans.factory.annotation.Autowired; |
| 8 | 10 | import org.springframework.beans.factory.annotation.Value; |
| ... | ... | @@ -47,11 +49,17 @@ public class RedisConfig extends CachingConfigurerSupport { |
| 47 | 49 | private int poolMaxWait; |
| 48 | 50 | |
| 49 | 51 | @Autowired |
| 50 | - private RedisGPSMsgListener redisGPSMsgListener; | |
| 52 | + private RedisGpsMsgListener redisGPSMsgListener; | |
| 51 | 53 | |
| 52 | 54 | @Autowired |
| 53 | 55 | private RedisAlarmMsgListener redisAlarmMsgListener; |
| 54 | 56 | |
| 57 | + @Autowired | |
| 58 | + private RedisStreamMsgListener redisStreamMsgListener; | |
| 59 | + | |
| 60 | + @Autowired | |
| 61 | + private RedisGbPlayMsgListener redisGbPlayMsgListener; | |
| 62 | + | |
| 55 | 63 | @Bean |
| 56 | 64 | public JedisPool jedisPool() { |
| 57 | 65 | if (StringUtils.isBlank(password)) { |
| ... | ... | @@ -98,6 +106,8 @@ public class RedisConfig extends CachingConfigurerSupport { |
| 98 | 106 | container.setConnectionFactory(connectionFactory); |
| 99 | 107 | container.addMessageListener(redisGPSMsgListener, new PatternTopic(VideoManagerConstants.VM_MSG_GPS)); |
| 100 | 108 | container.addMessageListener(redisAlarmMsgListener, new PatternTopic(VideoManagerConstants.VM_MSG_SUBSCRIBE_ALARM_RECEIVE)); |
| 109 | + container.addMessageListener(redisStreamMsgListener, new PatternTopic(VideoManagerConstants.WVP_MSG_STREAM_CHANGE_PREFIX + "PUSH")); | |
| 110 | + container.addMessageListener(redisGbPlayMsgListener, new PatternTopic(RedisGbPlayMsgListener.WVP_PUSH_STREAM_KEY)); | |
| 101 | 111 | return container; |
| 102 | 112 | } |
| 103 | 113 | ... | ... |
src/main/java/com/genersoft/iot/vmp/conf/security/LoginSuccessHandler.java
| ... | ... | @@ -11,6 +11,9 @@ import javax.servlet.http.HttpServletRequest; |
| 11 | 11 | import javax.servlet.http.HttpServletResponse; |
| 12 | 12 | import java.io.IOException; |
| 13 | 13 | |
| 14 | +/** | |
| 15 | + * @author lin | |
| 16 | + */ | |
| 14 | 17 | @Component |
| 15 | 18 | public class LoginSuccessHandler implements AuthenticationSuccessHandler { |
| 16 | 19 | ... | ... |
src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java
| ... | ... | @@ -20,6 +20,7 @@ import java.util.List; |
| 20 | 20 | |
| 21 | 21 | /** |
| 22 | 22 | * 配置Spring Security |
| 23 | + * @author lin | |
| 23 | 24 | */ |
| 24 | 25 | @Configuration |
| 25 | 26 | @EnableWebSecurity |
| ... | ... | @@ -132,15 +133,19 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { |
| 132 | 133 | .anyRequest().authenticated() |
| 133 | 134 | // 异常处理(权限拒绝、登录失效等) |
| 134 | 135 | .and().exceptionHandling() |
| 135 | - .authenticationEntryPoint(anonymousAuthenticationEntryPoint)//匿名用户访问无权限资源时的异常处理 | |
| 136 | + //匿名用户访问无权限资源时的异常处理 | |
| 137 | + .authenticationEntryPoint(anonymousAuthenticationEntryPoint) | |
| 136 | 138 | // .accessDeniedHandler(accessDeniedHandler)//登录用户没有权限访问资源 |
| 137 | - // 登入 | |
| 138 | - .and().formLogin().permitAll()//允许所有用户 | |
| 139 | - .successHandler(loginSuccessHandler)//登录成功处理逻辑 | |
| 140 | - .failureHandler(loginFailureHandler)//登录失败处理逻辑 | |
| 139 | + // 登入 允许所有用户 | |
| 140 | + .and().formLogin().permitAll() | |
| 141 | + //登录成功处理逻辑 | |
| 142 | + .successHandler(loginSuccessHandler) | |
| 143 | + //登录失败处理逻辑 | |
| 144 | + .failureHandler(loginFailureHandler) | |
| 141 | 145 | // 登出 |
| 142 | - .and().logout().logoutUrl("/api/user/logout").permitAll()//允许所有用户 | |
| 143 | - .logoutSuccessHandler(logoutHandler)//登出成功处理逻辑 | |
| 146 | + .and().logout().logoutUrl("/api/user/logout").permitAll() | |
| 147 | + //登出成功处理逻辑 | |
| 148 | + .logoutSuccessHandler(logoutHandler) | |
| 144 | 149 | .deleteCookies("JSESSIONID") |
| 145 | 150 | // 会话管理 |
| 146 | 151 | // .and().sessionManagement().invalidSessionStrategy(invalidSessionHandler) // 超时处理 | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java
| 1 | 1 | package com.genersoft.iot.vmp.gb28181.bean; |
| 2 | 2 | |
| 3 | 3 | |
| 4 | +/** | |
| 5 | + * 国标设备/平台 | |
| 6 | + * @author lin | |
| 7 | + */ | |
| 4 | 8 | public class Device { |
| 5 | 9 | |
| 6 | 10 | /** |
| ... | ... | @@ -127,7 +131,12 @@ public class Device { |
| 127 | 131 | /** |
| 128 | 132 | * 是否开启ssrc校验,默认关闭,开启可以防止串流 |
| 129 | 133 | */ |
| 130 | - private boolean ssrcCheck; | |
| 134 | + private boolean ssrcCheck = true; | |
| 135 | + | |
| 136 | + /** | |
| 137 | + * 地理坐标系, 目前支持 WGS84,GCJ02 TODO CGCS2000 | |
| 138 | + */ | |
| 139 | + private String geoCoordSys; | |
| 131 | 140 | |
| 132 | 141 | |
| 133 | 142 | public String getDeviceId() { |
| ... | ... | @@ -322,4 +331,12 @@ public class Device { |
| 322 | 331 | this.ssrcCheck = ssrcCheck; |
| 323 | 332 | } |
| 324 | 333 | |
| 334 | + public String getGeoCoordSys() { | |
| 335 | + return geoCoordSys; | |
| 336 | + } | |
| 337 | + | |
| 338 | + public void setGeoCoordSys(String geoCoordSys) { | |
| 339 | + this.geoCoordSys = geoCoordSys; | |
| 340 | + } | |
| 341 | + | |
| 325 | 342 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceChannel.java
| ... | ... | @@ -155,6 +155,26 @@ public class DeviceChannel { |
| 155 | 155 | private double latitude; |
| 156 | 156 | |
| 157 | 157 | /** |
| 158 | + * 经度 GCJ02 | |
| 159 | + */ | |
| 160 | + private double longitudeGcj02; | |
| 161 | + | |
| 162 | + /** | |
| 163 | + * 纬度 GCJ02 | |
| 164 | + */ | |
| 165 | + private double latitudeGcj02; | |
| 166 | + | |
| 167 | + /** | |
| 168 | + * 经度 WGS84 | |
| 169 | + */ | |
| 170 | + private double longitudeWgs84; | |
| 171 | + | |
| 172 | + /** | |
| 173 | + * 纬度 WGS84 | |
| 174 | + */ | |
| 175 | + private double latitudeWgs84; | |
| 176 | + | |
| 177 | + /** | |
| 158 | 178 | * 子设备数 |
| 159 | 179 | */ |
| 160 | 180 | private int subCount; |
| ... | ... | @@ -407,6 +427,38 @@ public class DeviceChannel { |
| 407 | 427 | this.latitude = latitude; |
| 408 | 428 | } |
| 409 | 429 | |
| 430 | + public double getLongitudeGcj02() { | |
| 431 | + return longitudeGcj02; | |
| 432 | + } | |
| 433 | + | |
| 434 | + public void setLongitudeGcj02(double longitudeGcj02) { | |
| 435 | + this.longitudeGcj02 = longitudeGcj02; | |
| 436 | + } | |
| 437 | + | |
| 438 | + public double getLatitudeGcj02() { | |
| 439 | + return latitudeGcj02; | |
| 440 | + } | |
| 441 | + | |
| 442 | + public void setLatitudeGcj02(double latitudeGcj02) { | |
| 443 | + this.latitudeGcj02 = latitudeGcj02; | |
| 444 | + } | |
| 445 | + | |
| 446 | + public double getLongitudeWgs84() { | |
| 447 | + return longitudeWgs84; | |
| 448 | + } | |
| 449 | + | |
| 450 | + public void setLongitudeWgs84(double longitudeWgs84) { | |
| 451 | + this.longitudeWgs84 = longitudeWgs84; | |
| 452 | + } | |
| 453 | + | |
| 454 | + public double getLatitudeWgs84() { | |
| 455 | + return latitudeWgs84; | |
| 456 | + } | |
| 457 | + | |
| 458 | + public void setLatitudeWgs84(double latitudeWgs84) { | |
| 459 | + this.latitudeWgs84 = latitudeWgs84; | |
| 460 | + } | |
| 461 | + | |
| 410 | 462 | public int getSubCount() { |
| 411 | 463 | return subCount; |
| 412 | 464 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/bean/HandlerCatchData.java
0 → 100644
| 1 | +package com.genersoft.iot.vmp.gb28181.bean; | |
| 2 | + | |
| 3 | +import org.dom4j.Element; | |
| 4 | + | |
| 5 | +import javax.sip.RequestEvent; | |
| 6 | + | |
| 7 | +/** | |
| 8 | + * @author lin | |
| 9 | + */ | |
| 10 | +public class HandlerCatchData { | |
| 11 | + private RequestEvent evt; | |
| 12 | + private Device device; | |
| 13 | + private Element rootElement; | |
| 14 | + | |
| 15 | + public HandlerCatchData(RequestEvent evt, Device device, Element rootElement) { | |
| 16 | + this.evt = evt; | |
| 17 | + this.device = device; | |
| 18 | + this.rootElement = rootElement; | |
| 19 | + } | |
| 20 | + | |
| 21 | + public RequestEvent getEvt() { | |
| 22 | + return evt; | |
| 23 | + } | |
| 24 | + | |
| 25 | + public void setEvt(RequestEvent evt) { | |
| 26 | + this.evt = evt; | |
| 27 | + } | |
| 28 | + | |
| 29 | + public Device getDevice() { | |
| 30 | + return device; | |
| 31 | + } | |
| 32 | + | |
| 33 | + public void setDevice(Device device) { | |
| 34 | + this.device = device; | |
| 35 | + } | |
| 36 | + | |
| 37 | + public Element getRootElement() { | |
| 38 | + return rootElement; | |
| 39 | + } | |
| 40 | + | |
| 41 | + public void setRootElement(Element rootElement) { | |
| 42 | + this.rootElement = rootElement; | |
| 43 | + } | |
| 44 | +} | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/bean/SendRtpItem.java
| ... | ... | @@ -72,6 +72,11 @@ public class SendRtpItem { |
| 72 | 72 | private String mediaServerId; |
| 73 | 73 | |
| 74 | 74 | /** |
| 75 | + * 使用的服务的ID | |
| 76 | + */ | |
| 77 | + private String serverId; | |
| 78 | + | |
| 79 | + /** | |
| 75 | 80 | * invite的callId |
| 76 | 81 | */ |
| 77 | 82 | private String CallId; |
| ... | ... | @@ -259,4 +264,12 @@ public class SendRtpItem { |
| 259 | 264 | public void setOnlyAudio(boolean onlyAudio) { |
| 260 | 265 | this.onlyAudio = onlyAudio; |
| 261 | 266 | } |
| 267 | + | |
| 268 | + public String getServerId() { | |
| 269 | + return serverId; | |
| 270 | + } | |
| 271 | + | |
| 272 | + public void setServerId(String serverId) { | |
| 273 | + this.serverId = serverId; | |
| 274 | + } | |
| 262 | 275 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/bean/SubscribeHolder.java
| ... | ... | @@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.gb28181.bean; |
| 2 | 2 | |
| 3 | 3 | import com.genersoft.iot.vmp.common.VideoManagerConstants; |
| 4 | 4 | import com.genersoft.iot.vmp.conf.DynamicTask; |
| 5 | +import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask; | |
| 5 | 6 | import com.genersoft.iot.vmp.gb28181.task.impl.MobilePositionSubscribeHandlerTask; |
| 6 | 7 | import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; |
| 7 | 8 | import com.genersoft.iot.vmp.storager.IRedisCatchStorage; |
| ... | ... | @@ -38,7 +39,6 @@ public class SubscribeHolder { |
| 38 | 39 | catalogMap.put(platformId, subscribeInfo); |
| 39 | 40 | // 添加订阅到期 |
| 40 | 41 | String taskOverdueKey = taskOverduePrefix + "catalog_" + platformId; |
| 41 | - dynamicTask.stop(taskOverdueKey); | |
| 42 | 42 | // 添加任务处理订阅过期 |
| 43 | 43 | dynamicTask.startDelay(taskOverdueKey, () -> removeCatalogSubscribe(subscribeInfo.getId()), |
| 44 | 44 | subscribeInfo.getExpires() * 1000); |
| ... | ... | @@ -49,10 +49,17 @@ public class SubscribeHolder { |
| 49 | 49 | } |
| 50 | 50 | |
| 51 | 51 | public void removeCatalogSubscribe(String platformId) { |
| 52 | + | |
| 52 | 53 | catalogMap.remove(platformId); |
| 53 | 54 | String taskOverdueKey = taskOverduePrefix + "catalog_" + platformId; |
| 55 | + Runnable runnable = dynamicTask.get(taskOverdueKey); | |
| 56 | + if (runnable instanceof ISubscribeTask) { | |
| 57 | + ISubscribeTask subscribeTask = (ISubscribeTask) runnable; | |
| 58 | + subscribeTask.stop(); | |
| 59 | + } | |
| 54 | 60 | // 添加任务处理订阅过期 |
| 55 | 61 | dynamicTask.stop(taskOverdueKey); |
| 62 | + | |
| 56 | 63 | } |
| 57 | 64 | |
| 58 | 65 | public void putMobilePositionSubscribe(String platformId, SubscribeInfo subscribeInfo) { |
| ... | ... | @@ -63,7 +70,6 @@ public class SubscribeHolder { |
| 63 | 70 | storager, platformId, subscribeInfo.getSn(), key, this, dynamicTask), |
| 64 | 71 | subscribeInfo.getGpsInterval() * 1000); |
| 65 | 72 | String taskOverdueKey = taskOverduePrefix + "MobilePosition_" + platformId; |
| 66 | - dynamicTask.stop(taskOverdueKey); | |
| 67 | 73 | // 添加任务处理订阅过期 |
| 68 | 74 | dynamicTask.startDelay(taskOverdueKey, () -> { |
| 69 | 75 | removeMobilePositionSubscribe(subscribeInfo.getId()); |
| ... | ... | @@ -81,6 +87,11 @@ public class SubscribeHolder { |
| 81 | 87 | // 结束任务处理GPS定时推送 |
| 82 | 88 | dynamicTask.stop(key); |
| 83 | 89 | String taskOverdueKey = taskOverduePrefix + "MobilePosition_" + platformId; |
| 90 | + Runnable runnable = dynamicTask.get(taskOverdueKey); | |
| 91 | + if (runnable instanceof ISubscribeTask) { | |
| 92 | + ISubscribeTask subscribeTask = (ISubscribeTask) runnable; | |
| 93 | + subscribeTask.stop(); | |
| 94 | + } | |
| 84 | 95 | // 添加任务处理订阅过期 |
| 85 | 96 | dynamicTask.stop(taskOverdueKey); |
| 86 | 97 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/event/SipSubscribe.java
| ... | ... | @@ -88,8 +88,8 @@ public class SipSubscribe { |
| 88 | 88 | this.type = "timeout"; |
| 89 | 89 | this.msg = "消息超时未回复"; |
| 90 | 90 | this.statusCode = -1024; |
| 91 | - this.callId = timeoutEvent.getClientTransaction().getDialog().getCallId().getCallId(); | |
| 92 | 91 | this.dialog = timeoutEvent.getClientTransaction().getDialog(); |
| 92 | + this.callId = this.dialog != null?timeoutEvent.getClientTransaction().getDialog().getCallId().getCallId(): null; | |
| 93 | 93 | }else if (event instanceof TransactionTerminatedEvent) { |
| 94 | 94 | TransactionTerminatedEvent transactionTerminatedEvent = (TransactionTerminatedEvent)event; |
| 95 | 95 | this.type = "transactionTerminated"; |
| ... | ... | @@ -109,8 +109,8 @@ public class SipSubscribe { |
| 109 | 109 | this.type = "deviceNotFoundEvent"; |
| 110 | 110 | this.msg = "设备未找到"; |
| 111 | 111 | this.statusCode = -1024; |
| 112 | - this.callId = deviceNotFoundEvent.getDialog().getCallId().getCallId(); | |
| 113 | 112 | this.dialog = deviceNotFoundEvent.getDialog(); |
| 113 | + this.callId = this.dialog != null ?deviceNotFoundEvent.getDialog().getCallId().getCallId() : null; | |
| 114 | 114 | } |
| 115 | 115 | } |
| 116 | 116 | } |
| ... | ... | @@ -130,6 +130,9 @@ public class SipSubscribe { |
| 130 | 130 | } |
| 131 | 131 | |
| 132 | 132 | public void removeErrorSubscribe(String key) { |
| 133 | + if(key == null){ | |
| 134 | + return; | |
| 135 | + } | |
| 133 | 136 | errorSubscribes.remove(key); |
| 134 | 137 | errorTimeSubscribes.remove(key); |
| 135 | 138 | } |
| ... | ... | @@ -139,6 +142,9 @@ public class SipSubscribe { |
| 139 | 142 | } |
| 140 | 143 | |
| 141 | 144 | public void removeOkSubscribe(String key) { |
| 145 | + if(key == null){ | |
| 146 | + return; | |
| 147 | + } | |
| 142 | 148 | okSubscribes.remove(key); |
| 143 | 149 | okTimeSubscribes.remove(key); |
| 144 | 150 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/event/subscribe/catalog/CatalogEventLister.java
| ... | ... | @@ -66,7 +66,7 @@ public class CatalogEventLister implements ApplicationListener<CatalogEvent> { |
| 66 | 66 | subscribe = subscribeHolder.getCatalogSubscribe(event.getPlatformId()); |
| 67 | 67 | |
| 68 | 68 | if (subscribe == null) { |
| 69 | - logger.info("发送订阅消息时发现订阅信息已经不存在"); | |
| 69 | + logger.info("发送订阅消息时发现订阅信息已经不存在: {}", event.getPlatformId()); | |
| 70 | 70 | return; |
| 71 | 71 | } |
| 72 | 72 | }else { | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java
| ... | ... | @@ -99,8 +99,8 @@ public class VideoStreamSessionManager { |
| 99 | 99 | return dialog; |
| 100 | 100 | } |
| 101 | 101 | |
| 102 | - public SIPDialog getDialogByCallId(String deviceId, String channelId, String callID){ | |
| 103 | - SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId, callID, null); | |
| 102 | + public SIPDialog getDialogByCallId(String deviceId, String channelId, String callId){ | |
| 103 | + SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId, callId, null); | |
| 104 | 104 | if (ssrcTransaction == null) { |
| 105 | 105 | return null; |
| 106 | 106 | } |
| ... | ... | @@ -108,11 +108,17 @@ public class VideoStreamSessionManager { |
| 108 | 108 | if (dialogByteArray == null) { |
| 109 | 109 | return null; |
| 110 | 110 | } |
| 111 | - SIPDialog dialog = (SIPDialog)SerializeUtils.deSerialize(dialogByteArray); | |
| 112 | - return dialog; | |
| 111 | + return (SIPDialog)SerializeUtils.deSerialize(dialogByteArray); | |
| 113 | 112 | } |
| 114 | 113 | |
| 115 | 114 | public SsrcTransaction getSsrcTransaction(String deviceId, String channelId, String callId, String stream){ |
| 115 | + | |
| 116 | + if (StringUtils.isEmpty(deviceId)) { | |
| 117 | + deviceId ="*"; | |
| 118 | + } | |
| 119 | + if (StringUtils.isEmpty(channelId)) { | |
| 120 | + channelId ="*"; | |
| 121 | + } | |
| 116 | 122 | if (StringUtils.isEmpty(callId)) { |
| 117 | 123 | callId ="*"; |
| 118 | 124 | } |
| ... | ... | @@ -179,7 +185,7 @@ public class VideoStreamSessionManager { |
| 179 | 185 | |
| 180 | 186 | |
| 181 | 187 | public List<SsrcTransaction> getAllSsrc() { |
| 182 | - List<Object> ssrcTransactionKeys = redisUtil.scan(String.format("%s_*_*_*_*", VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX+ userSetting.getServerId() + "_" )); | |
| 188 | + List<Object> ssrcTransactionKeys = redisUtil.scan(String.format("%s_*_*_*_*", VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX+ userSetting.getServerId())); | |
| 183 | 189 | List<SsrcTransaction> result= new ArrayList<>(); |
| 184 | 190 | for (int i = 0; i < ssrcTransactionKeys.size(); i++) { |
| 185 | 191 | String key = (String)ssrcTransactionKeys.get(i); | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/task/impl/MobilePositionSubscribeHandlerTask.java
| ... | ... | @@ -71,7 +71,9 @@ public class MobilePositionSubscribeHandlerTask implements ISubscribeTask { |
| 71 | 71 | String gbId = gbStream.getGbId(); |
| 72 | 72 | GPSMsgInfo gpsMsgInfo = redisCatchStorage.getGpsMsgInfo(gbId); |
| 73 | 73 | if (gpsMsgInfo != null) { // 无最新位置不发送 |
| 74 | - logger.info("无最新位置不发送"); | |
| 74 | + if (logger.isDebugEnabled()) { | |
| 75 | + logger.debug("无最新位置不发送"); | |
| 76 | + } | |
| 75 | 77 | // 经纬度都为0不发送 |
| 76 | 78 | if (gpsMsgInfo.getLng() == 0 && gpsMsgInfo.getLat() == 0) { |
| 77 | 79 | continue; | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPProcessorObserver.java
| ... | ... | @@ -150,30 +150,24 @@ public class SIPProcessorObserver implements ISIPProcessorObserver { |
| 150 | 150 | public void processTimeout(TimeoutEvent timeoutEvent) { |
| 151 | 151 | logger.info("[消息发送超时]"); |
| 152 | 152 | ClientTransaction clientTransaction = timeoutEvent.getClientTransaction(); |
| 153 | - eventPublisher.requestTimeOut(timeoutEvent); | |
| 153 | + | |
| 154 | 154 | if (clientTransaction != null) { |
| 155 | + logger.info("[发送错误订阅] clientTransaction != null"); | |
| 155 | 156 | Request request = clientTransaction.getRequest(); |
| 156 | 157 | if (request != null) { |
| 158 | + logger.info("[发送错误订阅] request != null"); | |
| 157 | 159 | CallIdHeader callIdHeader = (CallIdHeader) request.getHeader(CallIdHeader.NAME); |
| 158 | 160 | if (callIdHeader != null) { |
| 161 | + logger.info("[发送错误订阅]"); | |
| 159 | 162 | SipSubscribe.Event subscribe = sipSubscribe.getErrorSubscribe(callIdHeader.getCallId()); |
| 160 | 163 | SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(timeoutEvent); |
| 161 | 164 | subscribe.response(eventResult); |
| 165 | + sipSubscribe.removeOkSubscribe(callIdHeader.getCallId()); | |
| 162 | 166 | sipSubscribe.removeErrorSubscribe(callIdHeader.getCallId()); |
| 163 | 167 | } |
| 164 | 168 | } |
| 165 | 169 | } |
| 166 | - | |
| 167 | -// Timeout timeout = timeoutEvent.getTimeout(); | |
| 168 | -// ServerTransaction serverTransaction = timeoutEvent.getServerTransaction(); | |
| 169 | -// if (serverTransaction != null) { | |
| 170 | -// Request request = serverTransaction.getRequest(); | |
| 171 | -// URI requestURI = request.getRequestURI(); | |
| 172 | -// Header header = request.getHeader(FromHeader.NAME); | |
| 173 | -// } | |
| 174 | -// if(timeoutProcessor != null) { | |
| 175 | -// timeoutProcessor.process(timeoutEvent); | |
| 176 | -// } | |
| 170 | + eventPublisher.requestTimeOut(timeoutEvent); | |
| 177 | 171 | } |
| 178 | 172 | |
| 179 | 173 | @Override | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
| ... | ... | @@ -148,6 +148,14 @@ public interface ISIPCommander { |
| 148 | 148 | * 回放倍速播放 |
| 149 | 149 | */ |
| 150 | 150 | void playSpeedCmd(Device device, StreamInfo streamInfo, Double speed); |
| 151 | + | |
| 152 | + /** | |
| 153 | + * 回放控制 | |
| 154 | + * @param device | |
| 155 | + * @param streamInfo | |
| 156 | + * @param content | |
| 157 | + */ | |
| 158 | + void playbackControlCmd(Device device, StreamInfo streamInfo, String content,SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent); | |
| 151 | 159 | |
| 152 | 160 | |
| 153 | 161 | /** | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommanderForPlatform.java
| ... | ... | @@ -104,6 +104,14 @@ public interface ISIPCommanderForPlatform { |
| 104 | 104 | boolean recordInfo(DeviceChannel deviceChannel, ParentPlatform parentPlatform, String fromTag, RecordInfo recordInfo); |
| 105 | 105 | |
| 106 | 106 | /** |
| 107 | + * 录像播放推送完成时发送MediaStatus消息 | |
| 108 | + * @param platform | |
| 109 | + * @param sendRtpItem | |
| 110 | + * @return | |
| 111 | + */ | |
| 112 | + boolean sendMediaStatusNotify(ParentPlatform platform, SendRtpItem sendRtpItem); | |
| 113 | + | |
| 114 | + /** | |
| 107 | 115 | * 向发起点播的上级回复bye |
| 108 | 116 | * @param platform 平台信息 |
| 109 | 117 | * @param callId callId | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
| ... | ... | @@ -32,7 +32,9 @@ import org.springframework.stereotype.Component; |
| 32 | 32 | import org.springframework.util.StringUtils; |
| 33 | 33 | |
| 34 | 34 | import javax.sip.*; |
| 35 | +import javax.sip.address.Address; | |
| 35 | 36 | import javax.sip.address.SipURI; |
| 37 | +import javax.sip.address.URI; | |
| 36 | 38 | import javax.sip.header.*; |
| 37 | 39 | import javax.sip.message.Request; |
| 38 | 40 | import java.lang.reflect.Field; |
| ... | ... | @@ -708,22 +710,19 @@ public class SIPCommander implements ISIPCommander { |
| 708 | 710 | } |
| 709 | 711 | SIPDialog dialog; |
| 710 | 712 | if (callId != null) { |
| 711 | - dialog = streamSession.getDialogByCallId(deviceId, channelId, callId); | |
| 713 | + dialog = streamSession.getDialogByCallId(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), callId); | |
| 712 | 714 | }else { |
| 713 | - if (stream == null) { | |
| 715 | + if (stream == null && ssrcTransaction == null && ssrcTransaction.getStream() == null) { | |
| 714 | 716 | return; |
| 715 | 717 | } |
| 716 | - dialog = streamSession.getDialogByStream(deviceId, channelId, stream); | |
| 717 | - } | |
| 718 | - if (ssrcTransaction != null) { | |
| 719 | - MediaServerItem mediaServerItem = mediaServerService.getOne(ssrcTransaction.getMediaServerId()); | |
| 720 | - mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcTransaction.getSsrc()); | |
| 721 | - mediaServerService.closeRTPServer(deviceId, channelId, ssrcTransaction.getStream()); | |
| 722 | - streamSession.remove(deviceId, channelId, ssrcTransaction.getStream()); | |
| 718 | + dialog = streamSession.getDialogByStream(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getStream()); | |
| 723 | 719 | } |
| 720 | + mediaServerService.releaseSsrc(ssrcTransaction.getMediaServerId(), ssrcTransaction.getSsrc()); | |
| 721 | + mediaServerService.closeRTPServer(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getStream()); | |
| 722 | + streamSession.remove(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getStream()); | |
| 724 | 723 | |
| 725 | 724 | if (dialog == null) { |
| 726 | - logger.warn("[ {} -> {}]停止视频流的时候发现对话已丢失", deviceId, channelId); | |
| 725 | + logger.warn("[ {} -> {}]停止视频流的时候发现对话已丢失", ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId()); | |
| 727 | 726 | return; |
| 728 | 727 | } |
| 729 | 728 | SipStack sipStack = udpSipProvider.getSipStack(); |
| ... | ... | @@ -1456,12 +1455,20 @@ public class SIPCommander implements ISIPCommander { |
| 1456 | 1455 | |
| 1457 | 1456 | Request request; |
| 1458 | 1457 | if (dialog != null) { |
| 1459 | - logger.info("发送移动位置订阅消息时 dialog的状态为: {}", dialog.getState()); | |
| 1458 | + SipURI requestURI = sipFactory.createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress()); | |
| 1460 | 1459 | request = dialog.createRequest(Request.SUBSCRIBE); |
| 1460 | + ExpiresHeader expiresHeader = sipFactory.createHeaderFactory().createExpiresHeader(device.getSubscribeCycleForCatalog()); | |
| 1461 | + request.setExpires(expiresHeader); | |
| 1462 | + | |
| 1463 | + request.setRequestURI(requestURI); | |
| 1464 | + | |
| 1461 | 1465 | ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml"); |
| 1462 | 1466 | request.setContent(subscribePostitionXml.toString(), contentTypeHeader); |
| 1463 | - ExpiresHeader expireHeader = sipFactory.createHeaderFactory().createExpiresHeader(device.getSubscribeCycleForMobilePosition()); | |
| 1464 | - request.addHeader(expireHeader); | |
| 1467 | + | |
| 1468 | + CSeqHeader cSeqHeader = (CSeqHeader)request.getHeader(CSeqHeader.NAME); | |
| 1469 | + cSeqHeader.setSeqNumber(redisCatchStorage.getCSEQ(Request.SUBSCRIBE)); | |
| 1470 | + request.removeHeader(CSeqHeader.NAME); | |
| 1471 | + request.addHeader(cSeqHeader); | |
| 1465 | 1472 | }else { |
| 1466 | 1473 | String tm = Long.toString(System.currentTimeMillis()); |
| 1467 | 1474 | CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId() |
| ... | ... | @@ -1552,12 +1559,21 @@ public class SIPCommander implements ISIPCommander { |
| 1552 | 1559 | |
| 1553 | 1560 | Request request; |
| 1554 | 1561 | if (dialog != null) { |
| 1555 | - logger.info("发送目录订阅消息时 dialog的状态为: {}", dialog.getState()); | |
| 1562 | + SipURI requestURI = sipFactory.createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress()); | |
| 1556 | 1563 | request = dialog.createRequest(Request.SUBSCRIBE); |
| 1564 | + ExpiresHeader expiresHeader = sipFactory.createHeaderFactory().createExpiresHeader(device.getSubscribeCycleForCatalog()); | |
| 1565 | + request.setExpires(expiresHeader); | |
| 1566 | + | |
| 1567 | + request.setRequestURI(requestURI); | |
| 1568 | + | |
| 1557 | 1569 | ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml"); |
| 1558 | 1570 | request.setContent(cmdXml.toString(), contentTypeHeader); |
| 1559 | - ExpiresHeader expireHeader = sipFactory.createHeaderFactory().createExpiresHeader(device.getSubscribeCycleForMobilePosition()); | |
| 1560 | - request.addHeader(expireHeader); | |
| 1571 | + | |
| 1572 | + CSeqHeader cSeqHeader = (CSeqHeader)request.getHeader(CSeqHeader.NAME); | |
| 1573 | + cSeqHeader.setSeqNumber(redisCatchStorage.getCSEQ(Request.SUBSCRIBE)); | |
| 1574 | + request.removeHeader(CSeqHeader.NAME); | |
| 1575 | + request.addHeader(cSeqHeader); | |
| 1576 | + | |
| 1561 | 1577 | }else { |
| 1562 | 1578 | String tm = Long.toString(System.currentTimeMillis()); |
| 1563 | 1579 | |
| ... | ... | @@ -1779,6 +1795,43 @@ public class SIPCommander implements ISIPCommander { |
| 1779 | 1795 | e.printStackTrace(); |
| 1780 | 1796 | } |
| 1781 | 1797 | } |
| 1798 | + | |
| 1799 | + @Override | |
| 1800 | + public void playbackControlCmd(Device device, StreamInfo streamInfo, String content,SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) { | |
| 1801 | + try { | |
| 1802 | + Request request = headerProvider.createInfoRequest(device, streamInfo, content); | |
| 1803 | + if (request == null) { | |
| 1804 | + return; | |
| 1805 | + } | |
| 1806 | + logger.info(request.toString()); | |
| 1807 | + ClientTransaction clientTransaction = null; | |
| 1808 | + if ("TCP".equals(device.getTransport())) { | |
| 1809 | + clientTransaction = tcpSipProvider.getNewClientTransaction(request); | |
| 1810 | + } else if ("UDP".equals(device.getTransport())) { | |
| 1811 | + clientTransaction = udpSipProvider.getNewClientTransaction(request); | |
| 1812 | + } | |
| 1813 | + CallIdHeader callIdHeader = (CallIdHeader)request.getHeader(CallIdHeader.NAME); | |
| 1814 | + if(errorEvent != null) { | |
| 1815 | + sipSubscribe.addErrorSubscribe(callIdHeader.getCallId(), (eventResult -> { | |
| 1816 | + errorEvent.response(eventResult); | |
| 1817 | + sipSubscribe.removeErrorSubscribe(eventResult.callId); | |
| 1818 | + sipSubscribe.removeOkSubscribe(eventResult.callId); | |
| 1819 | + })); | |
| 1820 | + } | |
| 1821 | + | |
| 1822 | + if(okEvent != null) { | |
| 1823 | + sipSubscribe.addOkSubscribe(callIdHeader.getCallId(), eventResult -> { | |
| 1824 | + okEvent.response(eventResult); | |
| 1825 | + sipSubscribe.removeOkSubscribe(eventResult.callId); | |
| 1826 | + sipSubscribe.removeErrorSubscribe(eventResult.callId); | |
| 1827 | + }); | |
| 1828 | + } | |
| 1829 | + clientTransaction.sendRequest(); | |
| 1830 | + | |
| 1831 | + } catch (SipException | ParseException | InvalidArgumentException e) { | |
| 1832 | + e.printStackTrace(); | |
| 1833 | + } | |
| 1834 | + } | |
| 1782 | 1835 | |
| 1783 | 1836 | @Override |
| 1784 | 1837 | public boolean sendAlarmMessage(Device device, DeviceAlarm deviceAlarm) { | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java
| ... | ... | @@ -31,6 +31,7 @@ import javax.sip.address.SipURI; |
| 31 | 31 | import javax.sip.header.*; |
| 32 | 32 | import javax.sip.message.Request; |
| 33 | 33 | import java.lang.reflect.Field; |
| 34 | +import java.net.InetAddress; | |
| 34 | 35 | import java.text.ParseException; |
| 35 | 36 | import java.util.ArrayList; |
| 36 | 37 | import java.util.HashSet; |
| ... | ... | @@ -276,8 +277,8 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { |
| 276 | 277 | catalogXml.append("<Owner>" + channel.getOwner() + "</Owner>\r\n"); |
| 277 | 278 | catalogXml.append("<CivilCode>" + channel.getCivilCode() + "</CivilCode>\r\n"); |
| 278 | 279 | catalogXml.append("<Address>" + channel.getAddress() + "</Address>\r\n"); |
| 279 | - catalogXml.append("<Longitude>" + channel.getLongitude() + "</Longitude>\r\n"); | |
| 280 | - catalogXml.append("<Latitude>" + channel.getLatitude() + "</Latitude>\r\n"); | |
| 280 | + catalogXml.append("<Longitude>" + channel.getLongitudeWgs84() + "</Longitude>\r\n"); | |
| 281 | + catalogXml.append("<Latitude>" + channel.getLatitudeWgs84() + "</Latitude>\r\n"); | |
| 281 | 282 | catalogXml.append("<IPAddress>" + channel.getIpAddress() + "</IPAddress>\r\n"); |
| 282 | 283 | catalogXml.append("<Port>" + channel.getPort() + "</Port>\r\n"); |
| 283 | 284 | catalogXml.append("<Info>\r\n"); |
| ... | ... | @@ -546,14 +547,8 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { |
| 546 | 547 | } |
| 547 | 548 | notifyRequest.addHeader(event); |
| 548 | 549 | SipURI sipURI = (SipURI) notifyRequest.getRequestURI(); |
| 549 | - if (subscribeInfo.getTransaction() != null) { | |
| 550 | - SIPRequest request = (SIPRequest) subscribeInfo.getTransaction().getRequest(); | |
| 551 | - sipURI.setHost(request.getRemoteAddress().getHostAddress()); | |
| 552 | - sipURI.setPort(request.getRemotePort()); | |
| 553 | - }else { | |
| 554 | - sipURI.setHost(parentPlatform.getServerIP()); | |
| 555 | - sipURI.setPort(parentPlatform.getServerPort()); | |
| 556 | - } | |
| 550 | + sipURI.setHost(parentPlatform.getServerIP()); | |
| 551 | + sipURI.setPort(parentPlatform.getServerPort()); | |
| 557 | 552 | |
| 558 | 553 | ClientTransaction transaction = null; |
| 559 | 554 | if ("TCP".equals(parentPlatform.getTransport())) { |
| ... | ... | @@ -751,6 +746,82 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { |
| 751 | 746 | } |
| 752 | 747 | |
| 753 | 748 | @Override |
| 749 | + public boolean sendMediaStatusNotify(ParentPlatform platform, SendRtpItem sendRtpItem) { | |
| 750 | + if (sendRtpItem == null) { | |
| 751 | + return false; | |
| 752 | + } | |
| 753 | + if (platform == null) { | |
| 754 | + return false; | |
| 755 | + } | |
| 756 | + | |
| 757 | + byte[] dialogByteArray = sendRtpItem.getDialog(); | |
| 758 | + if (dialogByteArray == null) { | |
| 759 | + return false; | |
| 760 | + } | |
| 761 | + try{ | |
| 762 | + SIPDialog dialog = (SIPDialog) SerializeUtils.deSerialize(dialogByteArray); | |
| 763 | + SipStack sipStack; | |
| 764 | + if ("TCP".equals(platform.getTransport())) { | |
| 765 | + sipStack = tcpSipProvider.getSipStack(); | |
| 766 | + } else { | |
| 767 | + sipStack = udpSipProvider.getSipStack(); | |
| 768 | + } | |
| 769 | + SIPDialog sipDialog = ((SipStackImpl) sipStack).putDialog(dialog); | |
| 770 | + if (dialog != sipDialog) { | |
| 771 | + dialog = sipDialog; | |
| 772 | + } | |
| 773 | + if ("TCP".equals(platform.getTransport())) { | |
| 774 | + dialog.setSipProvider(tcpSipProvider); | |
| 775 | + } else { | |
| 776 | + dialog.setSipProvider(udpSipProvider); | |
| 777 | + } | |
| 778 | + | |
| 779 | + Field sipStackField = SIPDialog.class.getDeclaredField("sipStack"); | |
| 780 | + sipStackField.setAccessible(true); | |
| 781 | + sipStackField.set(dialog, sipStack); | |
| 782 | + Field eventListenersField = SIPDialog.class.getDeclaredField("eventListeners"); | |
| 783 | + eventListenersField.setAccessible(true); | |
| 784 | + eventListenersField.set(dialog, new HashSet<>()); | |
| 785 | + | |
| 786 | + SIPRequest messageRequest = (SIPRequest)dialog.createRequest(Request.MESSAGE); | |
| 787 | + String characterSet = platform.getCharacterSet(); | |
| 788 | + StringBuffer mediaStatusXml = new StringBuffer(200); | |
| 789 | + mediaStatusXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n"); | |
| 790 | + mediaStatusXml.append("<Notify>\r\n"); | |
| 791 | + mediaStatusXml.append("<CmdType>MediaStatus</CmdType>\r\n"); | |
| 792 | + mediaStatusXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n"); | |
| 793 | + mediaStatusXml.append("<DeviceID>" + sendRtpItem.getChannelId() + "</DeviceID>\r\n"); | |
| 794 | + mediaStatusXml.append("<NotifyType>121</NotifyType>\r\n"); | |
| 795 | + mediaStatusXml.append("</Notify>\r\n"); | |
| 796 | + ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml"); | |
| 797 | + messageRequest.setContent(mediaStatusXml.toString(), contentTypeHeader); | |
| 798 | + SipURI sipURI = (SipURI) messageRequest.getRequestURI(); | |
| 799 | + sipURI.setHost(platform.getServerIP()); | |
| 800 | + sipURI.setPort(platform.getServerPort()); | |
| 801 | + ClientTransaction clientTransaction; | |
| 802 | + if ("TCP".equals(platform.getTransport())) { | |
| 803 | + clientTransaction = tcpSipProvider.getNewClientTransaction(messageRequest); | |
| 804 | + }else { | |
| 805 | + clientTransaction = udpSipProvider.getNewClientTransaction(messageRequest); | |
| 806 | + } | |
| 807 | + dialog.sendRequest(clientTransaction); | |
| 808 | + } catch (SipException e) { | |
| 809 | + e.printStackTrace(); | |
| 810 | + return false; | |
| 811 | + } catch (ParseException e) { | |
| 812 | + e.printStackTrace(); | |
| 813 | + return false; | |
| 814 | + } catch (NoSuchFieldException e) { | |
| 815 | + throw new RuntimeException(e); | |
| 816 | + } catch (IllegalAccessException e) { | |
| 817 | + throw new RuntimeException(e); | |
| 818 | + } | |
| 819 | + return true; | |
| 820 | + | |
| 821 | + | |
| 822 | + } | |
| 823 | + | |
| 824 | + @Override | |
| 754 | 825 | public void streamByeCmd(ParentPlatform platform, String callId) { |
| 755 | 826 | if (platform == null) { |
| 756 | 827 | return; |
| ... | ... | @@ -766,45 +837,51 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { |
| 766 | 837 | byte[] dialogByteArray = sendRtpItem.getDialog(); |
| 767 | 838 | if (dialogByteArray != null) { |
| 768 | 839 | SIPDialog dialog = (SIPDialog) SerializeUtils.deSerialize(dialogByteArray); |
| 769 | - SipStack sipStack = udpSipProvider.getSipStack(); | |
| 840 | + SipStack sipStack; | |
| 841 | + if ("TCP".equals(platform.getTransport())) { | |
| 842 | + sipStack = tcpSipProvider.getSipStack(); | |
| 843 | + } else { | |
| 844 | + sipStack = udpSipProvider.getSipStack(); | |
| 845 | + } | |
| 770 | 846 | SIPDialog sipDialog = ((SipStackImpl) sipStack).putDialog(dialog); |
| 771 | 847 | if (dialog != sipDialog) { |
| 772 | 848 | dialog = sipDialog; |
| 773 | - } else { | |
| 774 | - try { | |
| 849 | + } | |
| 850 | + try { | |
| 851 | + if ("TCP".equals(platform.getTransport())) { | |
| 852 | + dialog.setSipProvider(tcpSipProvider); | |
| 853 | + } else { | |
| 775 | 854 | dialog.setSipProvider(udpSipProvider); |
| 776 | - Field sipStackField = SIPDialog.class.getDeclaredField("sipStack"); | |
| 777 | - sipStackField.setAccessible(true); | |
| 778 | - sipStackField.set(dialog, sipStack); | |
| 779 | - Field eventListenersField = SIPDialog.class.getDeclaredField("eventListeners"); | |
| 780 | - eventListenersField.setAccessible(true); | |
| 781 | - eventListenersField.set(dialog, new HashSet<>()); | |
| 782 | - | |
| 783 | - byte[] transactionByteArray = sendRtpItem.getTransaction(); | |
| 784 | - ClientTransaction clientTransaction = (ClientTransaction) SerializeUtils.deSerialize(transactionByteArray); | |
| 785 | - Request byeRequest = dialog.createRequest(Request.BYE); | |
| 786 | - | |
| 787 | - SipURI byeURI = (SipURI) byeRequest.getRequestURI(); | |
| 788 | - SIPRequest request = (SIPRequest) clientTransaction.getRequest(); | |
| 789 | - byeURI.setHost(request.getRemoteAddress().getHostAddress()); | |
| 790 | - byeURI.setPort(request.getRemotePort()); | |
| 791 | - if ("TCP".equals(platform.getTransport())) { | |
| 792 | - clientTransaction = tcpSipProvider.getNewClientTransaction(byeRequest); | |
| 793 | - } else if ("UDP".equals(platform.getTransport())) { | |
| 794 | - clientTransaction = udpSipProvider.getNewClientTransaction(byeRequest); | |
| 795 | - } | |
| 796 | - dialog.sendRequest(clientTransaction); | |
| 797 | - } catch (SipException e) { | |
| 798 | - e.printStackTrace(); | |
| 799 | - } catch (ParseException e) { | |
| 800 | - e.printStackTrace(); | |
| 801 | - } catch (NoSuchFieldException e) { | |
| 802 | - e.printStackTrace(); | |
| 803 | - } catch (IllegalAccessException e) { | |
| 804 | - e.printStackTrace(); | |
| 805 | 855 | } |
| 806 | - | |
| 856 | + Field sipStackField = SIPDialog.class.getDeclaredField("sipStack"); | |
| 857 | + sipStackField.setAccessible(true); | |
| 858 | + sipStackField.set(dialog, sipStack); | |
| 859 | + Field eventListenersField = SIPDialog.class.getDeclaredField("eventListeners"); | |
| 860 | + eventListenersField.setAccessible(true); | |
| 861 | + eventListenersField.set(dialog, new HashSet<>()); | |
| 862 | + | |
| 863 | + Request byeRequest = dialog.createRequest(Request.BYE); | |
| 864 | + | |
| 865 | + SipURI byeURI = (SipURI) byeRequest.getRequestURI(); | |
| 866 | + byeURI.setHost(platform.getServerIP()); | |
| 867 | + byeURI.setPort(platform.getServerPort()); | |
| 868 | + ClientTransaction clientTransaction; | |
| 869 | + if ("TCP".equals(platform.getTransport())) { | |
| 870 | + clientTransaction = tcpSipProvider.getNewClientTransaction(byeRequest); | |
| 871 | + } else { | |
| 872 | + clientTransaction = udpSipProvider.getNewClientTransaction(byeRequest); | |
| 873 | + } | |
| 874 | + dialog.sendRequest(clientTransaction); | |
| 875 | + } catch (SipException e) { | |
| 876 | + e.printStackTrace(); | |
| 877 | + } catch (ParseException e) { | |
| 878 | + e.printStackTrace(); | |
| 879 | + } catch (NoSuchFieldException e) { | |
| 880 | + e.printStackTrace(); | |
| 881 | + } catch (IllegalAccessException e) { | |
| 882 | + e.printStackTrace(); | |
| 807 | 883 | } |
| 884 | + | |
| 808 | 885 | } |
| 809 | 886 | } |
| 810 | 887 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/SIPRequestProcessorAbstract.java deleted
100644 → 0
| 1 | -package com.genersoft.iot.vmp.gb28181.transmit.event.request; | |
| 2 | - | |
| 3 | -import gov.nist.javax.sip.SipProviderImpl; | |
| 4 | -import org.springframework.beans.factory.annotation.Autowired; | |
| 5 | -import org.springframework.beans.factory.annotation.Qualifier; | |
| 6 | - | |
| 7 | -/** | |
| 8 | - * @description:处理接收IPCamera发来的SIP协议请求消息 | |
| 9 | - * @author: songww | |
| 10 | - * @date: 2020年5月3日 下午4:42:22 | |
| 11 | - */ | |
| 12 | -public abstract class SIPRequestProcessorAbstract { | |
| 13 | - | |
| 14 | - | |
| 15 | - @Autowired | |
| 16 | - @Qualifier(value="tcpSipProvider") | |
| 17 | - private SipProviderImpl tcpSipProvider; | |
| 18 | - | |
| 19 | - @Autowired | |
| 20 | - @Qualifier(value="udpSipProvider") | |
| 21 | - private SipProviderImpl udpSipProvider; | |
| 22 | - | |
| 23 | -} |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java
| ... | ... | @@ -15,10 +15,13 @@ import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe; |
| 15 | 15 | import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory; |
| 16 | 16 | import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; |
| 17 | 17 | import com.genersoft.iot.vmp.service.IMediaServerService; |
| 18 | +import com.genersoft.iot.vmp.service.bean.RequestPushStreamMsg; | |
| 19 | +import com.genersoft.iot.vmp.service.impl.RedisGbPlayMsgListener; | |
| 18 | 20 | import com.genersoft.iot.vmp.storager.IRedisCatchStorage; |
| 19 | 21 | import com.genersoft.iot.vmp.storager.IVideoManagerStorage; |
| 20 | 22 | import gov.nist.javax.sip.message.SIPRequest; |
| 21 | 23 | import gov.nist.javax.sip.stack.SIPDialog; |
| 24 | +import com.genersoft.iot.vmp.utils.SerializeUtils; | |
| 22 | 25 | import org.ehcache.shadow.org.terracotta.offheapstore.storage.IntegerStorageEngine; |
| 23 | 26 | import org.slf4j.Logger; |
| 24 | 27 | import org.slf4j.LoggerFactory; |
| ... | ... | @@ -46,7 +49,7 @@ import java.util.*; |
| 46 | 49 | public class AckRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor { |
| 47 | 50 | |
| 48 | 51 | private Logger logger = LoggerFactory.getLogger(AckRequestProcessor.class); |
| 49 | - private String method = "ACK"; | |
| 52 | + private final String method = "ACK"; | |
| 50 | 53 | |
| 51 | 54 | @Autowired |
| 52 | 55 | private SIPProcessorObserver sipProcessorObserver; |
| ... | ... | @@ -84,6 +87,9 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In |
| 84 | 87 | @Autowired |
| 85 | 88 | private AudioBroadcastManager audioBroadcastManager; |
| 86 | 89 | |
| 90 | + @Autowired | |
| 91 | + private RedisGbPlayMsgListener redisGbPlayMsgListener; | |
| 92 | + | |
| 87 | 93 | |
| 88 | 94 | /** |
| 89 | 95 | * 处理 ACK请求 |
| ... | ... | @@ -159,60 +165,41 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In |
| 159 | 165 | // 向上级平台 |
| 160 | 166 | commanderForPlatform.streamByeCmd(parentPlatform, callIdHeader.getCallId()); |
| 161 | 167 | } |
| 168 | + if (mediaInfo == null) { | |
| 169 | + RequestPushStreamMsg requestPushStreamMsg = RequestPushStreamMsg.getInstance( | |
| 170 | + sendRtpItem.getMediaServerId(), sendRtpItem.getApp(), sendRtpItem.getStreamId(), | |
| 171 | + sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc(), sendRtpItem.isTcp(), | |
| 172 | + sendRtpItem.getLocalPort(), sendRtpItem.getPt(), sendRtpItem.isUsePs(), sendRtpItem.isOnlyAudio()); | |
| 173 | + redisGbPlayMsgListener.sendMsgForStartSendRtpStream(sendRtpItem.getServerId(), requestPushStreamMsg, jsonObject->{ | |
| 174 | + startSendRtpStreamHand(evt, sendRtpItem, parentPlatform, jsonObject, param, callIdHeader); | |
| 175 | + }); | |
| 176 | + }else { | |
| 177 | + JSONObject jsonObject = zlmrtpServerFactory.startSendRtpStream(mediaInfo, param); | |
| 178 | + startSendRtpStreamHand(evt, sendRtpItem, parentPlatform, jsonObject, param, callIdHeader); | |
| 162 | 179 | } |
| 163 | 180 | |
| 164 | 181 | |
| 165 | -// if (streamInfo == null) { // 流还没上来,对方就回复ack | |
| 166 | -// logger.info("监听流以等待流上线1 rtp/{}", sendRtpItem.getStreamId()); | |
| 167 | -// // 监听流上线 | |
| 168 | -// // 添加订阅 | |
| 169 | -// JSONObject subscribeKey = new JSONObject(); | |
| 170 | -// subscribeKey.put("app", "rtp"); | |
| 171 | -// subscribeKey.put("stream", sendRtpItem.getStreamId()); | |
| 172 | -// subscribeKey.put("regist", true); | |
| 173 | -// subscribeKey.put("schema", "rtmp"); | |
| 174 | -// subscribeKey.put("mediaServerId", sendRtpItem.getMediaServerId()); | |
| 175 | -// subscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey, | |
| 176 | -// (MediaServerItem mediaServerItemInUse, JSONObject json)->{ | |
| 177 | -// Map<String, Object> param = new HashMap<>(); | |
| 178 | -// param.put("vhost","__defaultVhost__"); | |
| 179 | -// param.put("app",json.getString("app")); | |
| 180 | -// param.put("stream",json.getString("stream")); | |
| 181 | -// param.put("ssrc", sendRtpItem.getSsrc()); | |
| 182 | -// param.put("dst_url",sendRtpItem.getIp()); | |
| 183 | -// param.put("dst_port", sendRtpItem.getPort()); | |
| 184 | -// param.put("is_udp", is_Udp); | |
| 185 | -// param.put("src_port", sendRtpItem.getLocalPort()); | |
| 186 | -// zlmrtpServerFactory.startSendRtpStream(mediaInfo, param); | |
| 187 | -// }); | |
| 188 | -// }else { | |
| 189 | -// Map<String, Object> param = new HashMap<>(); | |
| 190 | -// param.put("vhost","__defaultVhost__"); | |
| 191 | -// param.put("app",streamInfo.getApp()); | |
| 192 | -// param.put("stream",streamInfo.getStream()); | |
| 193 | -// param.put("ssrc", sendRtpItem.getSsrc()); | |
| 194 | -// param.put("dst_url",sendRtpItem.getIp()); | |
| 195 | -// param.put("dst_port", sendRtpItem.getPort()); | |
| 196 | -// param.put("is_udp", is_Udp); | |
| 197 | -// param.put("src_port", sendRtpItem.getLocalPort()); | |
| 198 | -// | |
| 199 | -// JSONObject jsonObject = zlmrtpServerFactory.startSendRtpStream(mediaInfo, param); | |
| 200 | -// if (jsonObject.getInteger("code") != 0) { | |
| 201 | -// logger.info("监听流以等待流上线2 {}/{}", streamInfo.getApp(), streamInfo.getStream()); | |
| 202 | -// // 监听流上线 | |
| 203 | -// // 添加订阅 | |
| 204 | -// JSONObject subscribeKey = new JSONObject(); | |
| 205 | -// subscribeKey.put("app", "rtp"); | |
| 206 | -// subscribeKey.put("stream", streamInfo.getStream()); | |
| 207 | -// subscribeKey.put("regist", true); | |
| 208 | -// subscribeKey.put("schema", "rtmp"); | |
| 209 | -// subscribeKey.put("mediaServerId", sendRtpItem.getMediaServerId()); | |
| 210 | -// subscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey, | |
| 211 | -// (MediaServerItem mediaServerItemInUse, JSONObject json)->{ | |
| 212 | -// zlmrtpServerFactory.startSendRtpStream(mediaInfo, param); | |
| 213 | -// }); | |
| 214 | -// } | |
| 215 | -// } | |
| 182 | + } | |
| 183 | + } | |
| 184 | + private void startSendRtpStreamHand(RequestEvent evt, SendRtpItem sendRtpItem, ParentPlatform parentPlatform, | |
| 185 | + JSONObject jsonObject, Map<String, Object> param, CallIdHeader callIdHeader) { | |
| 186 | + if (jsonObject == null) { | |
| 187 | + logger.error("RTP推流失败: 请检查ZLM服务"); | |
| 188 | + } else if (jsonObject.getInteger("code") == 0) { | |
| 189 | + logger.info("RTP推流成功[ {}/{} ],{}->{}:{}, " ,param.get("app"), param.get("stream"), jsonObject.getString("local_port"), param.get("dst_url"), param.get("dst_port")); | |
| 190 | + byte[] dialogByteArray = SerializeUtils.serialize(evt.getDialog()); | |
| 191 | + sendRtpItem.setDialog(dialogByteArray); | |
| 192 | + byte[] transactionByteArray = SerializeUtils.serialize(evt.getServerTransaction()); | |
| 193 | + sendRtpItem.setTransaction(transactionByteArray); | |
| 194 | + redisCatchStorage.updateSendRTPSever(sendRtpItem); | |
| 195 | + } else { | |
| 196 | + logger.error("RTP推流失败: {}, 参数:{}",jsonObject.getString("msg"),JSONObject.toJSON(param)); | |
| 197 | + if (sendRtpItem.isOnlyAudio()) { | |
| 198 | + // TODO 可能是语音对讲 | |
| 199 | + }else { | |
| 200 | + // 向上级平台 | |
| 201 | + commanderForPlatform.streamByeCmd(parentPlatform, callIdHeader.getCallId()); | |
| 202 | + } | |
| 216 | 203 | } |
| 217 | 204 | } |
| 218 | 205 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/ByeRequestProcessor.java
| ... | ... | @@ -114,13 +114,9 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In |
| 114 | 114 | playService.stopAudioBroadcast(sendRtpItem.getDeviceId(), sendRtpItem.getChannelId()); |
| 115 | 115 | } |
| 116 | 116 | if (sendRtpItem.getPlayType().equals(InviteStreamType.PUSH)) { |
| 117 | - MessageForPushChannel messageForPushChannel = new MessageForPushChannel(); | |
| 118 | - messageForPushChannel.setType(0); | |
| 119 | - messageForPushChannel.setGbId(sendRtpItem.getChannelId()); | |
| 120 | - messageForPushChannel.setApp(sendRtpItem.getApp()); | |
| 121 | - messageForPushChannel.setStream(sendRtpItem.getStreamId()); | |
| 122 | - messageForPushChannel.setMediaServerId(sendRtpItem.getMediaServerId()); | |
| 123 | - messageForPushChannel.setPlatFormId(sendRtpItem.getPlatformId()); | |
| 117 | + MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0, | |
| 118 | + sendRtpItem.getApp(), sendRtpItem.getStreamId(), sendRtpItem.getChannelId(), | |
| 119 | + sendRtpItem.getPlatformId(), null, null, sendRtpItem.getMediaServerId()); | |
| 124 | 120 | redisCatchStorage.sendStreamPushRequestedMsg(messageForPushChannel); |
| 125 | 121 | } |
| 126 | 122 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/CancelRequestProcessor.java
| ... | ... | @@ -15,7 +15,7 @@ import javax.sip.RequestEvent; |
| 15 | 15 | @Component |
| 16 | 16 | public class CancelRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor { |
| 17 | 17 | |
| 18 | - private String method = "CANCEL"; | |
| 18 | + private final String method = "CANCEL"; | |
| 19 | 19 | |
| 20 | 20 | @Autowired |
| 21 | 21 | private SIPProcessorObserver sipProcessorObserver; | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
| ... | ... | @@ -26,11 +26,14 @@ import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils; |
| 26 | 26 | import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory; |
| 27 | 27 | import com.genersoft.iot.vmp.media.zlm.dto.MediaItem; |
| 28 | 28 | import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; |
| 29 | +import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem; | |
| 29 | 30 | import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItemLite; |
| 30 | 31 | import com.genersoft.iot.vmp.service.IMediaServerService; |
| 31 | 32 | import com.genersoft.iot.vmp.service.IPlayService; |
| 33 | +import com.genersoft.iot.vmp.service.IStreamPushService; | |
| 32 | 34 | import com.genersoft.iot.vmp.service.bean.MessageForPushChannel; |
| 33 | 35 | import com.genersoft.iot.vmp.service.bean.SSRCInfo; |
| 36 | +import com.genersoft.iot.vmp.service.impl.RedisGbPlayMsgListener; | |
| 34 | 37 | import com.genersoft.iot.vmp.storager.IRedisCatchStorage; |
| 35 | 38 | import com.genersoft.iot.vmp.storager.IVideoManagerStorage; |
| 36 | 39 | import com.genersoft.iot.vmp.utils.DateUtil; |
| ... | ... | @@ -66,33 +69,42 @@ import java.util.Vector; |
| 66 | 69 | @Component |
| 67 | 70 | public class InviteRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor { |
| 68 | 71 | |
| 69 | - private final static Logger logger = LoggerFactory.getLogger(InviteRequestProcessor.class); | |
| 72 | + private final static Logger logger = LoggerFactory.getLogger(InviteRequestProcessor.class); | |
| 70 | 73 | |
| 71 | - private String method = "INVITE"; | |
| 74 | + private final String method = "INVITE"; | |
| 72 | 75 | |
| 73 | - @Autowired | |
| 74 | - private SIPCommanderFroPlatform cmderFroPlatform; | |
| 76 | + @Autowired | |
| 77 | + private SIPCommanderFroPlatform cmderFroPlatform; | |
| 75 | 78 | |
| 76 | - @Autowired | |
| 77 | - private IVideoManagerStorage storager; | |
| 79 | + @Autowired | |
| 80 | + private IVideoManagerStorage storager; | |
| 78 | 81 | |
| 79 | - @Autowired | |
| 80 | - private IRedisCatchStorage redisCatchStorage; | |
| 82 | + @Autowired | |
| 83 | + private IStreamPushService streamPushService; | |
| 81 | 84 | |
| 82 | - @Autowired | |
| 83 | - private DynamicTask dynamicTask; | |
| 85 | + @Autowired | |
| 86 | + private IRedisCatchStorage redisCatchStorage; | |
| 84 | 87 | |
| 85 | - @Autowired | |
| 86 | - private SIPCommander cmder; | |
| 88 | + @Autowired | |
| 89 | + private DynamicTask dynamicTask; | |
| 87 | 90 | |
| 88 | - @Autowired | |
| 89 | - private IPlayService playService; | |
| 91 | + @Autowired | |
| 92 | + private SIPCommander cmder; | |
| 90 | 93 | |
| 94 | + @Autowired | |
| 95 | + private IPlayService playService; | |
| 96 | + | |
| 97 | + @Autowired | |
| 98 | + private ISIPCommander commander; | |
| 99 | + | |
| 91 | 100 | @Autowired |
| 92 | 101 | private AudioBroadcastManager audioBroadcastManager; |
| 93 | 102 | |
| 94 | - @Autowired | |
| 95 | - private ZLMRTPServerFactory zlmrtpServerFactory; | |
| 103 | + @Autowired | |
| 104 | + private ZLMRTPServerFactory zlmrtpServerFactory; | |
| 105 | + | |
| 106 | + @Autowired | |
| 107 | + private IMediaServerService mediaServerService; | |
| 96 | 108 | |
| 97 | 109 | @Autowired |
| 98 | 110 | private ZLMRESTfulUtils zlmresTfulUtils; |
| ... | ... | @@ -100,17 +112,17 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements |
| 100 | 112 | @Autowired |
| 101 | 113 | private IMediaServerService mediaServerService; |
| 102 | 114 | |
| 103 | - @Autowired | |
| 104 | - private SIPProcessorObserver sipProcessorObserver; | |
| 115 | + @Autowired | |
| 116 | + private SIPProcessorObserver sipProcessorObserver; | |
| 105 | 117 | |
| 106 | - @Autowired | |
| 107 | - private VideoStreamSessionManager sessionManager; | |
| 118 | + @Autowired | |
| 119 | + private VideoStreamSessionManager sessionManager; | |
| 108 | 120 | |
| 109 | - @Autowired | |
| 110 | - private UserSetting userSetting; | |
| 121 | + @Autowired | |
| 122 | + private UserSetting userSetting; | |
| 111 | 123 | |
| 112 | - @Autowired | |
| 113 | - private ZLMMediaListManager mediaListManager; | |
| 124 | + @Autowired | |
| 125 | + private ZLMMediaListManager mediaListManager; | |
| 114 | 126 | |
| 115 | 127 | @Autowired |
| 116 | 128 | private DeferredResultHolder resultHolder; |
| ... | ... | @@ -123,542 +135,664 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements |
| 123 | 135 | |
| 124 | 136 | |
| 125 | 137 | |
| 126 | - @Override | |
| 127 | - public void afterPropertiesSet() throws Exception { | |
| 128 | - // 添加消息处理的订阅 | |
| 129 | - sipProcessorObserver.addRequestProcessor(method, this); | |
| 130 | - } | |
| 131 | - | |
| 132 | - /** | |
| 133 | - * 处理invite请求 | |
| 134 | - * | |
| 135 | - * @param evt | |
| 136 | - * 请求消息 | |
| 137 | - */ | |
| 138 | - @Override | |
| 139 | - public void process(RequestEvent evt) { | |
| 140 | - // Invite Request消息实现,此消息一般为级联消息,上级给下级发送请求视频指令 | |
| 141 | - try { | |
| 142 | - Request request = evt.getRequest(); | |
| 143 | - SipURI sipURI = (SipURI) request.getRequestURI(); | |
| 144 | - //从subject读取channelId,不再从request-line读取。 有些平台request-line是平台国标编码,不是设备国标编码。 | |
| 145 | - //String channelId = sipURI.getUser(); | |
| 146 | - String channelId = SipUtils.getChannelIdFromHeader(request); | |
| 147 | - String requesterId = SipUtils.getUserIdFromFromHeader(request); | |
| 148 | - CallIdHeader callIdHeader = (CallIdHeader)request.getHeader(CallIdHeader.NAME); | |
| 149 | - if (requesterId == null || channelId == null) { | |
| 150 | - logger.info("无法从FromHeader的Address中获取到平台id,返回400"); | |
| 151 | - responseAck(evt, Response.BAD_REQUEST); // 参数不全, 发400,请求错误 | |
| 152 | - return; | |
| 153 | - } | |
| 154 | - | |
| 155 | - // 查询请求是否来自上级平台\设备 | |
| 156 | - ParentPlatform platform = storager.queryParentPlatByServerGBId(requesterId); | |
| 157 | - if (platform == null) { | |
| 158 | - inviteFromDeviceHandle(evt, requesterId, channelId); | |
| 159 | - }else { | |
| 160 | - // 查询平台下是否有该通道 | |
| 161 | - DeviceChannel channel = storager.queryChannelInParentPlatform(requesterId, channelId); | |
| 162 | - GbStream gbStream = storager.queryStreamInParentPlatform(requesterId, channelId); | |
| 163 | - PlatformCatalog catalog = storager.getCatalog(channelId); | |
| 164 | - MediaServerItem mediaServerItem = null; | |
| 165 | - // 不是通道可能是直播流 | |
| 166 | - if (channel != null && gbStream == null ) { | |
| 167 | - if (channel.getStatus() == 0) { | |
| 168 | - logger.info("通道离线,返回400"); | |
| 169 | - responseAck(evt, Response.BAD_REQUEST, "channel [" + channel.getChannelId() + "] offline"); | |
| 170 | - return; | |
| 171 | - } | |
| 172 | - responseAck(evt, Response.CALL_IS_BEING_FORWARDED); // 通道存在,发181,呼叫转接中 | |
| 173 | - }else if(channel == null && gbStream != null){ | |
| 174 | - String mediaServerId = gbStream.getMediaServerId(); | |
| 175 | - mediaServerItem = mediaServerService.getOne(mediaServerId); | |
| 176 | - if (mediaServerItem == null) { | |
| 177 | - logger.info("[ app={}, stream={} ]找不到zlm {},返回410",gbStream.getApp(), gbStream.getStream(), mediaServerId); | |
| 178 | - responseAck(evt, Response.GONE); | |
| 179 | - return; | |
| 180 | - } | |
| 181 | - responseAck(evt, Response.CALL_IS_BEING_FORWARDED); // 通道存在,发181,呼叫转接中 | |
| 182 | - }else if (catalog != null) { | |
| 183 | - responseAck(evt, Response.BAD_REQUEST, "catalog channel can not play"); // 目录不支持点播 | |
| 184 | - return; | |
| 185 | - } else { | |
| 186 | - logger.info("通道不存在,返回404"); | |
| 187 | - responseAck(evt, Response.NOT_FOUND); // 通道不存在,发404,资源不存在 | |
| 188 | - return; | |
| 189 | - } | |
| 190 | - // 解析sdp消息, 使用jainsip 自带的sdp解析方式 | |
| 191 | - String contentString = new String(request.getRawContent()); | |
| 192 | - | |
| 193 | - // jainSip不支持y=字段, 移除以解析。 | |
| 194 | - int ssrcIndex = contentString.indexOf("y="); | |
| 195 | - // 检查是否有y字段 | |
| 196 | - String ssrcDefault = "0000000000"; | |
| 197 | - String ssrc; | |
| 198 | - SessionDescription sdp; | |
| 199 | - if (ssrcIndex >= 0) { | |
| 200 | - //ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段 | |
| 201 | - ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12); | |
| 202 | - String substring = contentString.substring(0, contentString.indexOf("y=")); | |
| 203 | - sdp = SdpFactory.getInstance().createSessionDescription(substring); | |
| 204 | - }else { | |
| 205 | - ssrc = ssrcDefault; | |
| 206 | - sdp = SdpFactory.getInstance().createSessionDescription(contentString); | |
| 207 | - } | |
| 208 | - String sessionName = sdp.getSessionName().getValue(); | |
| 209 | - | |
| 210 | - Long startTime = null; | |
| 211 | - Long stopTime = null; | |
| 212 | - Instant start = null; | |
| 213 | - Instant end = null; | |
| 214 | - if (sdp.getTimeDescriptions(false) != null && sdp.getTimeDescriptions(false).size() > 0) { | |
| 215 | - TimeDescriptionImpl timeDescription = (TimeDescriptionImpl)(sdp.getTimeDescriptions(false).get(0)); | |
| 216 | - TimeField startTimeFiled = (TimeField)timeDescription.getTime(); | |
| 217 | - startTime = startTimeFiled.getStartTime(); | |
| 218 | - stopTime = startTimeFiled.getStopTime(); | |
| 219 | - | |
| 220 | - start = Instant.ofEpochMilli(startTime*1000); | |
| 221 | - end = Instant.ofEpochMilli(stopTime*1000); | |
| 222 | - } | |
| 223 | - // 获取支持的格式 | |
| 224 | - Vector mediaDescriptions = sdp.getMediaDescriptions(true); | |
| 225 | - // 查看是否支持PS 负载96 | |
| 226 | - //String ip = null; | |
| 227 | - int port = -1; | |
| 228 | - boolean mediaTransmissionTCP = false; | |
| 229 | - Boolean tcpActive = null; | |
| 230 | - for (Object description : mediaDescriptions) { | |
| 231 | - MediaDescription mediaDescription = (MediaDescription) description; | |
| 232 | - Media media = mediaDescription.getMedia(); | |
| 233 | - | |
| 234 | - Vector mediaFormats = media.getMediaFormats(false); | |
| 235 | - if (mediaFormats.contains("96")) { | |
| 236 | - port = media.getMediaPort(); | |
| 237 | - //String mediaType = media.getMediaType(); | |
| 238 | - String protocol = media.getProtocol(); | |
| 239 | - | |
| 240 | - // 区分TCP发流还是udp, 当前默认udp | |
| 241 | - if ("TCP/RTP/AVP".equals(protocol)) { | |
| 242 | - String setup = mediaDescription.getAttribute("setup"); | |
| 243 | - if (setup != null) { | |
| 244 | - mediaTransmissionTCP = true; | |
| 245 | - if ("active".equals(setup)) { | |
| 246 | - tcpActive = true; | |
| 247 | - // 不支持tcp主动 | |
| 248 | - responseAck(evt, Response.NOT_IMPLEMENTED, "tcp active not support"); // 目录不支持点播 | |
| 249 | - return; | |
| 250 | - } else if ("passive".equals(setup)) { | |
| 251 | - tcpActive = false; | |
| 252 | - } | |
| 253 | - } | |
| 254 | - } | |
| 255 | - break; | |
| 256 | - } | |
| 257 | - } | |
| 258 | - if (port == -1) { | |
| 259 | - logger.info("不支持的媒体格式,返回415"); | |
| 260 | - // 回复不支持的格式 | |
| 261 | - responseAck(evt, Response.UNSUPPORTED_MEDIA_TYPE); // 不支持的格式,发415 | |
| 262 | - return; | |
| 263 | - } | |
| 264 | - String username = sdp.getOrigin().getUsername(); | |
| 265 | - String addressStr = sdp.getOrigin().getAddress(); | |
| 266 | - | |
| 267 | - logger.info("[上级点播]用户:{}, 地址:{}:{}, ssrc:{}", username, addressStr, port, ssrc); | |
| 268 | - Device device = null; | |
| 269 | - // 通过 channel 和 gbStream 是否为null 值判断来源是直播流合适国标 | |
| 270 | - if (channel != null) { | |
| 271 | - device = storager.queryVideoDeviceByPlatformIdAndChannelId(requesterId, channelId); | |
| 272 | - if (device == null) { | |
| 273 | - logger.warn("点播平台{}的通道{}时未找到设备信息", requesterId, channel); | |
| 274 | - responseAck(evt, Response.SERVER_INTERNAL_ERROR); | |
| 275 | - return; | |
| 276 | - } | |
| 277 | - mediaServerItem = playService.getNewMediaServerItem(device); | |
| 278 | - if (mediaServerItem == null) { | |
| 279 | - logger.warn("未找到可用的zlm"); | |
| 280 | - responseAck(evt, Response.BUSY_HERE); | |
| 281 | - return; | |
| 282 | - } | |
| 283 | - SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId, | |
| 284 | - device.getDeviceId(), channelId, | |
| 285 | - mediaTransmissionTCP); | |
| 286 | - if (tcpActive != null) { | |
| 287 | - sendRtpItem.setTcpActive(tcpActive); | |
| 288 | - } | |
| 289 | - if (sendRtpItem == null) { | |
| 290 | - logger.warn("服务器端口资源不足"); | |
| 291 | - responseAck(evt, Response.BUSY_HERE); | |
| 292 | - return; | |
| 293 | - } | |
| 294 | - sendRtpItem.setCallId(callIdHeader.getCallId()); | |
| 295 | - sendRtpItem.setPlayType("Play".equals(sessionName)?InviteStreamType.PLAY:InviteStreamType.PLAYBACK); | |
| 296 | - byte[] dialogByteArray = SerializeUtils.serialize(evt.getDialog()); | |
| 297 | - sendRtpItem.setDialog(dialogByteArray); | |
| 298 | - byte[] transactionByteArray = SerializeUtils.serialize(evt.getServerTransaction()); | |
| 299 | - sendRtpItem.setTransaction(transactionByteArray); | |
| 300 | - Long finalStartTime = startTime; | |
| 301 | - Long finalStopTime = stopTime; | |
| 302 | - ZLMHttpHookSubscribe.Event hookEvent = (mediaServerItemInUSe, responseJSON)->{ | |
| 303 | - String app = responseJSON.getString("app"); | |
| 304 | - String stream = responseJSON.getString("stream"); | |
| 305 | - logger.info("[上级点播]下级已经开始推流。 回复200OK(SDP), {}/{}", app, stream); | |
| 306 | - // * 0 等待设备推流上来 | |
| 307 | - // * 1 下级已经推流,等待上级平台回复ack | |
| 308 | - // * 2 推流中 | |
| 309 | - sendRtpItem.setStatus(1); | |
| 310 | - redisCatchStorage.updateSendRTPSever(sendRtpItem); | |
| 311 | - | |
| 312 | - StringBuffer content = new StringBuffer(200); | |
| 313 | - content.append("v=0\r\n"); | |
| 314 | - content.append("o="+ channelId +" 0 0 IN IP4 "+mediaServerItemInUSe.getSdpIp()+"\r\n"); | |
| 315 | - content.append("s=" + sessionName+"\r\n"); | |
| 316 | - content.append("c=IN IP4 "+mediaServerItemInUSe.getSdpIp()+"\r\n"); | |
| 317 | - if ("Playback".equals(sessionName)) { | |
| 318 | - content.append("t=" + finalStartTime + " " + finalStopTime + "\r\n"); | |
| 319 | - }else { | |
| 320 | - content.append("t=0 0\r\n"); | |
| 321 | - } | |
| 322 | - content.append("m=video "+ sendRtpItem.getLocalPort()+" RTP/AVP 96\r\n"); | |
| 323 | - content.append("a=sendonly\r\n"); | |
| 324 | - content.append("a=rtpmap:96 PS/90000\r\n"); | |
| 325 | - content.append("y="+ ssrc + "\r\n"); | |
| 326 | - content.append("f=\r\n"); | |
| 327 | - | |
| 328 | - try { | |
| 329 | - // 超时未收到Ack应该回复bye,当前等待时间为10秒 | |
| 330 | - dynamicTask.startDelay(callIdHeader.getCallId(), ()->{ | |
| 331 | - logger.info("Ack 等待超时"); | |
| 332 | - mediaServerService.releaseSsrc(mediaServerItemInUSe.getId(), ssrc); | |
| 333 | - // 回复bye | |
| 334 | - cmderFroPlatform.streamByeCmd(platform, callIdHeader.getCallId()); | |
| 335 | - }, 60*1000); | |
| 336 | - responseSdpAck(evt, content.toString(), platform); | |
| 337 | - | |
| 338 | - } catch (SipException e) { | |
| 339 | - e.printStackTrace(); | |
| 340 | - } catch (InvalidArgumentException e) { | |
| 341 | - e.printStackTrace(); | |
| 342 | - } catch (ParseException e) { | |
| 343 | - e.printStackTrace(); | |
| 344 | - } | |
| 345 | - }; | |
| 346 | - SipSubscribe.Event errorEvent = ((event) -> { | |
| 347 | - // 未知错误。直接转发设备点播的错误 | |
| 348 | - Response response = null; | |
| 349 | - try { | |
| 350 | - response = getMessageFactory().createResponse(event.statusCode, evt.getRequest()); | |
| 351 | - ServerTransaction serverTransaction = getServerTransaction(evt); | |
| 352 | - serverTransaction.sendResponse(response); | |
| 353 | - if (serverTransaction.getDialog() != null) { | |
| 354 | - serverTransaction.getDialog().delete(); | |
| 355 | - } | |
| 356 | - } catch (ParseException | SipException | InvalidArgumentException e) { | |
| 357 | - e.printStackTrace(); | |
| 358 | - } | |
| 359 | - }); | |
| 360 | - sendRtpItem.setApp("rtp"); | |
| 361 | - if ("Playback".equals(sessionName)) { | |
| 362 | - sendRtpItem.setPlayType(InviteStreamType.PLAYBACK); | |
| 363 | - SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, null, true, true); | |
| 364 | - sendRtpItem.setStreamId(ssrcInfo.getStream()); | |
| 365 | - // 写入redis, 超时时回复 | |
| 366 | - redisCatchStorage.updateSendRTPSever(sendRtpItem); | |
| 367 | - playService.playBack(mediaServerItem, ssrcInfo, device.getDeviceId(), channelId, DateUtil.formatter.format(start), | |
| 368 | - DateUtil.formatter.format(end), null, result -> { | |
| 369 | - if (result.getCode() != 0){ | |
| 370 | - logger.warn("录像回放失败"); | |
| 371 | - if (result.getEvent() != null) { | |
| 372 | - errorEvent.response(result.getEvent()); | |
| 373 | - } | |
| 374 | - redisCatchStorage.deleteSendRTPServer(platform.getServerGBId(), channelId, callIdHeader.getCallId(), null); | |
| 375 | - try { | |
| 376 | - responseAck(evt, Response.REQUEST_TIMEOUT); | |
| 377 | - } catch (SipException e) { | |
| 378 | - e.printStackTrace(); | |
| 379 | - } catch (InvalidArgumentException e) { | |
| 380 | - e.printStackTrace(); | |
| 381 | - } catch (ParseException e) { | |
| 382 | - e.printStackTrace(); | |
| 383 | - } | |
| 384 | - }else { | |
| 385 | - if (result.getMediaServerItem() != null) { | |
| 386 | - hookEvent.response(result.getMediaServerItem(), result.getResponse()); | |
| 387 | - } | |
| 388 | - } | |
| 389 | - }); | |
| 390 | - }else { | |
| 391 | - sendRtpItem.setPlayType(InviteStreamType.PLAY); | |
| 392 | - SsrcTransaction playTransaction = sessionManager.getSsrcTransaction(device.getDeviceId(), channelId, "play", null); | |
| 393 | - if (playTransaction != null) { | |
| 394 | - Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, "rtp", playTransaction.getStream()); | |
| 395 | - if (!streamReady) { | |
| 396 | - playTransaction = null; | |
| 397 | - } | |
| 398 | - } | |
| 399 | - if (playTransaction == null) { | |
| 400 | - String streamId = null; | |
| 401 | - if (mediaServerItem.isRtpEnable()) { | |
| 402 | - streamId = String.format("%s_%s", device.getDeviceId(), channelId); | |
| 403 | - } | |
| 404 | - SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, true, false); | |
| 405 | - sendRtpItem.setStreamId(ssrcInfo.getStream()); | |
| 406 | - // 写入redis, 超时时回复 | |
| 407 | - redisCatchStorage.updateSendRTPSever(sendRtpItem); | |
| 408 | - playService.play(mediaServerItem, ssrcInfo, device, channelId, hookEvent, errorEvent, (code, msg)->{ | |
| 409 | - redisCatchStorage.deleteSendRTPServer(platform.getServerGBId(), channelId, callIdHeader.getCallId(), null); | |
| 410 | - }, null); | |
| 411 | - }else { | |
| 412 | - sendRtpItem.setStreamId(playTransaction.getStream()); | |
| 413 | - // 写入redis, 超时时回复 | |
| 414 | - redisCatchStorage.updateSendRTPSever(sendRtpItem); | |
| 415 | - JSONObject jsonObject = new JSONObject(); | |
| 416 | - jsonObject.put("app", sendRtpItem.getApp()); | |
| 417 | - jsonObject.put("stream", sendRtpItem.getStreamId()); | |
| 418 | - hookEvent.response(mediaServerItem, jsonObject); | |
| 419 | - } | |
| 420 | - } | |
| 421 | - }else if (gbStream != null) { | |
| 422 | - | |
| 423 | - Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, gbStream.getApp(), gbStream.getStream()); | |
| 424 | - if (!streamReady ) { | |
| 425 | - if ("proxy".equals(gbStream.getStreamType())) { | |
| 426 | - // TODO 控制启用以使设备上线 | |
| 427 | - logger.info("[ app={}, stream={} ]通道离线,启用流后开始推流",gbStream.getApp(), gbStream.getStream()); | |
| 428 | - responseAck(evt, Response.BAD_REQUEST, "channel [" + gbStream.getGbId() + "] offline"); | |
| 429 | - }else if ("push".equals(gbStream.getStreamType())) { | |
| 430 | - if (!platform.isStartOfflinePush()) { | |
| 431 | - responseAck(evt, Response.TEMPORARILY_UNAVAILABLE, "channel unavailable"); | |
| 432 | - return; | |
| 433 | - } | |
| 434 | - // 发送redis消息以使设备上线 | |
| 435 | - logger.info("[ app={}, stream={} ]通道离线,发送redis信息控制设备开始推流",gbStream.getApp(), gbStream.getStream()); | |
| 436 | - MessageForPushChannel messageForPushChannel = new MessageForPushChannel(); | |
| 437 | - messageForPushChannel.setType(1); | |
| 438 | - messageForPushChannel.setGbId(gbStream.getGbId()); | |
| 439 | - messageForPushChannel.setApp(gbStream.getApp()); | |
| 440 | - messageForPushChannel.setStream(gbStream.getStream()); | |
| 441 | - // TODO 获取低负载的节点 | |
| 442 | - messageForPushChannel.setMediaServerId(gbStream.getMediaServerId()); | |
| 443 | - messageForPushChannel.setPlatFormId(platform.getServerGBId()); | |
| 444 | - messageForPushChannel.setPlatFormName(platform.getName()); | |
| 445 | - redisCatchStorage.sendStreamPushRequestedMsg(messageForPushChannel); | |
| 446 | - // 设置超时 | |
| 447 | - dynamicTask.startDelay(callIdHeader.getCallId(), ()->{ | |
| 448 | - logger.info("[ app={}, stream={} ] 等待设备开始推流超时", gbStream.getApp(), gbStream.getStream()); | |
| 449 | - try { | |
| 450 | - mediaListManager.removedChannelOnlineEventLister(gbStream.getGbId()); | |
| 451 | - responseAck(evt, Response.REQUEST_TIMEOUT); // 超时 | |
| 452 | - } catch (SipException e) { | |
| 453 | - e.printStackTrace(); | |
| 454 | - } catch (InvalidArgumentException e) { | |
| 455 | - e.printStackTrace(); | |
| 456 | - } catch (ParseException e) { | |
| 457 | - e.printStackTrace(); | |
| 458 | - } | |
| 459 | - }, userSetting.getPlatformPlayTimeout()); | |
| 460 | - // 添加监听 | |
| 461 | - MediaServerItem finalMediaServerItem = mediaServerItem; | |
| 462 | - int finalPort = port; | |
| 463 | - boolean finalMediaTransmissionTCP = mediaTransmissionTCP; | |
| 464 | - Boolean finalTcpActive = tcpActive; | |
| 465 | - mediaListManager.addChannelOnlineEventLister(gbStream.getGbId(), (app, stream)->{ | |
| 466 | - SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(finalMediaServerItem, addressStr, finalPort, ssrc, requesterId, | |
| 467 | - app, stream, channelId, finalMediaTransmissionTCP); | |
| 468 | - | |
| 469 | - if (sendRtpItem == null) { | |
| 470 | - logger.warn("服务器端口资源不足"); | |
| 471 | - try { | |
| 472 | - responseAck(evt, Response.BUSY_HERE); | |
| 473 | - } catch (SipException e) { | |
| 474 | - e.printStackTrace(); | |
| 475 | - } catch (InvalidArgumentException e) { | |
| 476 | - e.printStackTrace(); | |
| 477 | - } catch (ParseException e) { | |
| 478 | - e.printStackTrace(); | |
| 479 | - } | |
| 480 | - return; | |
| 481 | - } | |
| 482 | - if (finalTcpActive != null) { | |
| 483 | - sendRtpItem.setTcpActive(finalTcpActive); | |
| 484 | - } | |
| 485 | - sendRtpItem.setPlayType(InviteStreamType.PUSH); | |
| 486 | - // 写入redis, 超时时回复 | |
| 487 | - sendRtpItem.setStatus(1); | |
| 488 | - sendRtpItem.setCallId(callIdHeader.getCallId()); | |
| 489 | - byte[] dialogByteArray = SerializeUtils.serialize(evt.getDialog()); | |
| 490 | - sendRtpItem.setDialog(dialogByteArray); | |
| 491 | - byte[] transactionByteArray = SerializeUtils.serialize(evt.getServerTransaction()); | |
| 492 | - sendRtpItem.setTransaction(transactionByteArray); | |
| 493 | - redisCatchStorage.updateSendRTPSever(sendRtpItem); | |
| 494 | - sendStreamAck(finalMediaServerItem, sendRtpItem, platform, evt); | |
| 495 | - | |
| 496 | - }); | |
| 497 | - } | |
| 498 | - }else { | |
| 499 | - SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId, | |
| 500 | - gbStream.getApp(), gbStream.getStream(), channelId, | |
| 501 | - mediaTransmissionTCP); | |
| 502 | - | |
| 503 | - if (sendRtpItem == null) { | |
| 504 | - logger.warn("服务器端口资源不足"); | |
| 505 | - responseAck(evt, Response.BUSY_HERE); | |
| 506 | - return; | |
| 507 | - } | |
| 508 | - if (tcpActive != null) { | |
| 509 | - sendRtpItem.setTcpActive(tcpActive); | |
| 510 | - } | |
| 511 | - sendRtpItem.setPlayType(InviteStreamType.PUSH); | |
| 512 | - // 写入redis, 超时时回复 | |
| 513 | - sendRtpItem.setStatus(1); | |
| 514 | - sendRtpItem.setCallId(callIdHeader.getCallId()); | |
| 515 | - byte[] dialogByteArray = SerializeUtils.serialize(evt.getDialog()); | |
| 516 | - sendRtpItem.setDialog(dialogByteArray); | |
| 517 | - byte[] transactionByteArray = SerializeUtils.serialize(evt.getServerTransaction()); | |
| 518 | - sendRtpItem.setTransaction(transactionByteArray); | |
| 519 | - redisCatchStorage.updateSendRTPSever(sendRtpItem); | |
| 520 | - sendStreamAck(mediaServerItem, sendRtpItem, platform, evt); | |
| 521 | - } | |
| 522 | - | |
| 523 | - | |
| 524 | - } | |
| 525 | - | |
| 526 | - } | |
| 527 | - | |
| 528 | - } catch (SipException | InvalidArgumentException | ParseException e) { | |
| 529 | - e.printStackTrace(); | |
| 530 | - logger.warn("sdp解析错误"); | |
| 531 | - e.printStackTrace(); | |
| 532 | - } catch (SdpParseException e) { | |
| 533 | - e.printStackTrace(); | |
| 534 | - } catch (SdpException e) { | |
| 535 | - e.printStackTrace(); | |
| 536 | - } | |
| 537 | - } | |
| 538 | - | |
| 539 | - public void sendStreamAck(MediaServerItem mediaServerItem, SendRtpItem sendRtpItem, ParentPlatform platform, RequestEvent evt){ | |
| 540 | - | |
| 541 | - StringBuffer content = new StringBuffer(200); | |
| 542 | - content.append("v=0\r\n"); | |
| 543 | - content.append("o="+ sendRtpItem.getChannelId() +" 0 0 IN IP4 "+ mediaServerItem.getSdpIp()+"\r\n"); | |
| 544 | - content.append("s=Play\r\n"); | |
| 545 | - content.append("c=IN IP4 "+mediaServerItem.getSdpIp()+"\r\n"); | |
| 546 | - content.append("t=0 0\r\n"); | |
| 547 | - content.append("m=video "+ sendRtpItem.getLocalPort()+" RTP/AVP 96\r\n"); | |
| 548 | - content.append("a=sendonly\r\n"); | |
| 549 | - content.append("a=rtpmap:96 PS/90000\r\n"); | |
| 550 | - if (sendRtpItem.isTcp()) { | |
| 551 | - content.append("a=connection:new\r\n"); | |
| 552 | - if (!sendRtpItem.isTcpActive()) { | |
| 553 | - content.append("a=setup:active\r\n"); | |
| 554 | - }else { | |
| 555 | - content.append("a=setup:passive\r\n"); | |
| 556 | - } | |
| 557 | - } | |
| 558 | - content.append("y="+ sendRtpItem.getSsrc() + "\r\n"); | |
| 559 | - content.append("f=\r\n"); | |
| 560 | - | |
| 561 | - try { | |
| 562 | - responseSdpAck(evt, content.toString(), platform); | |
| 563 | - } catch (SipException e) { | |
| 564 | - e.printStackTrace(); | |
| 565 | - } catch (InvalidArgumentException e) { | |
| 566 | - e.printStackTrace(); | |
| 567 | - } catch (ParseException e) { | |
| 568 | - e.printStackTrace(); | |
| 569 | - } | |
| 570 | - } | |
| 571 | - | |
| 572 | - public void inviteFromDeviceHandle(RequestEvent evt, String requesterId, String channelId1) throws InvalidArgumentException, ParseException, SipException, SdpException { | |
| 573 | - | |
| 574 | - // 非上级平台请求,查询是否设备请求(通常为接收语音广播的设备) | |
| 575 | - Device device = redisCatchStorage.getDevice(requesterId); | |
| 576 | - AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(requesterId, channelId1); | |
| 577 | - if (audioBroadcastCatch == null) { | |
| 578 | - logger.warn("来自设备的Invite请求非语音广播,已忽略"); | |
| 579 | - responseAck(evt, Response.FORBIDDEN); | |
| 580 | - return; | |
| 581 | - } | |
| 582 | - Request request = evt.getRequest(); | |
| 583 | - if (device != null) { | |
| 584 | - logger.info("收到设备" + requesterId + "的语音广播Invite请求"); | |
| 585 | - responseAck(evt, Response.TRYING); | |
| 586 | - | |
| 587 | - String contentString = new String(request.getRawContent()); | |
| 588 | - // jainSip不支持y=字段, 移除移除以解析。 | |
| 589 | - String substring = contentString; | |
| 590 | - String ssrc = "0000000404"; | |
| 591 | - int ssrcIndex = contentString.indexOf("y="); | |
| 592 | - if (ssrcIndex > 0) { | |
| 593 | - substring = contentString.substring(0, ssrcIndex); | |
| 594 | - ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12).trim(); | |
| 595 | - } | |
| 596 | - ssrcIndex = substring.indexOf("f="); | |
| 597 | - if (ssrcIndex > 0) { | |
| 598 | - substring = contentString.substring(0, ssrcIndex); | |
| 599 | - } | |
| 600 | - SessionDescription sdp = SdpFactory.getInstance().createSessionDescription(substring); | |
| 601 | - | |
| 602 | - // 获取支持的格式 | |
| 603 | - Vector mediaDescriptions = sdp.getMediaDescriptions(true); | |
| 604 | - | |
| 605 | - // 查看是否支持PS 负载96 | |
| 606 | - int port = -1; | |
| 607 | - boolean mediaTransmissionTCP = false; | |
| 608 | - Boolean tcpActive = null; | |
| 609 | - for (int i = 0; i < mediaDescriptions.size(); i++) { | |
| 610 | - MediaDescription mediaDescription = (MediaDescription)mediaDescriptions.get(i); | |
| 611 | - Media media = mediaDescription.getMedia(); | |
| 612 | - | |
| 613 | - Vector mediaFormats = media.getMediaFormats(false); | |
| 614 | - if (mediaFormats.contains("8")) { | |
| 615 | - port = media.getMediaPort(); | |
| 616 | - String protocol = media.getProtocol(); | |
| 617 | - // 区分TCP发流还是udp, 当前默认udp | |
| 618 | - if ("TCP/RTP/AVP".equals(protocol)) { | |
| 619 | - String setup = mediaDescription.getAttribute("setup"); | |
| 620 | - if (setup != null) { | |
| 621 | - mediaTransmissionTCP = true; | |
| 622 | - if ("active".equals(setup)) { | |
| 623 | - tcpActive = true; | |
| 624 | - } else if ("passive".equals(setup)) { | |
| 625 | - tcpActive = false; | |
| 626 | - } | |
| 627 | - } | |
| 628 | - } | |
| 629 | - break; | |
| 630 | - } | |
| 631 | - } | |
| 632 | - if (port == -1) { | |
| 633 | - logger.info("不支持的媒体格式,返回415"); | |
| 634 | - // 回复不支持的格式 | |
| 635 | - responseAck(evt, Response.UNSUPPORTED_MEDIA_TYPE); // 不支持的格式,发415 | |
| 636 | - return; | |
| 637 | - } | |
| 638 | - String addressStr = sdp.getOrigin().getAddress(); | |
| 639 | - logger.info("设备{}请求语音流,地址:{}:{},ssrc:{}", requesterId, addressStr, port, ssrc); | |
| 640 | - | |
| 641 | - MediaServerItem mediaServerItem = playService.getNewMediaServerItem(device); | |
| 642 | - if (mediaServerItem == null) { | |
| 643 | - logger.warn("未找到可用的zlm"); | |
| 644 | - responseAck(evt, Response.BUSY_HERE); | |
| 645 | - return; | |
| 646 | - } | |
| 647 | - SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId, | |
| 648 | - device.getDeviceId(), audioBroadcastCatch.getChannelId(), | |
| 649 | - mediaTransmissionTCP); | |
| 650 | - if (sendRtpItem == null) { | |
| 651 | - logger.warn("服务器端口资源不足"); | |
| 652 | - responseAck(evt, Response.BUSY_HERE); | |
| 653 | - return; | |
| 654 | - } | |
| 655 | - sendRtpItem.setTcp(mediaTransmissionTCP); | |
| 656 | - if (tcpActive != null) { | |
| 657 | - sendRtpItem.setTcpActive(tcpActive); | |
| 658 | - } | |
| 659 | - String app = "broadcast"; | |
| 660 | - String stream = device.getDeviceId() + "_" + audioBroadcastCatch.getChannelId(); | |
| 661 | - | |
| 138 | + @Autowired | |
| 139 | + private RedisGbPlayMsgListener redisGbPlayMsgListener; | |
| 140 | + | |
| 141 | + | |
| 142 | + @Override | |
| 143 | + public void afterPropertiesSet() throws Exception { | |
| 144 | + // 添加消息处理的订阅 | |
| 145 | + sipProcessorObserver.addRequestProcessor(method, this); | |
| 146 | + } | |
| 147 | + | |
| 148 | + /** | |
| 149 | + * 处理invite请求 | |
| 150 | + * | |
| 151 | + * @param evt 请求消息 | |
| 152 | + */ | |
| 153 | + @Override | |
| 154 | + public void process(RequestEvent evt) { | |
| 155 | + // Invite Request消息实现,此消息一般为级联消息,上级给下级发送请求视频指令 | |
| 156 | + try { | |
| 157 | + Request request = evt.getRequest(); | |
| 158 | + SipURI sipUri = (SipURI) request.getRequestURI(); | |
| 159 | + //从subject读取channelId,不再从request-line读取。 有些平台request-line是平台国标编码,不是设备国标编码。 | |
| 160 | + //String channelId = sipURI.getUser(); | |
| 161 | + String channelId = SipUtils.getChannelIdFromHeader(request); | |
| 162 | + String requesterId = SipUtils.getUserIdFromFromHeader(request); | |
| 163 | + CallIdHeader callIdHeader = (CallIdHeader) request.getHeader(CallIdHeader.NAME); | |
| 164 | + if (requesterId == null || channelId == null) { | |
| 165 | + logger.info("无法从FromHeader的Address中获取到平台id,返回400"); | |
| 166 | + // 参数不全, 发400,请求错误 | |
| 167 | + responseAck(evt, Response.BAD_REQUEST); | |
| 168 | + return; | |
| 169 | + } | |
| 170 | + | |
| 171 | + // 查询请求是否来自上级平台\设备 | |
| 172 | + ParentPlatform platform = storager.queryParentPlatByServerGBId(requesterId); | |
| 173 | + if (platform == null) { | |
| 174 | + inviteFromDeviceHandle(evt, requesterId); | |
| 175 | + } else { | |
| 176 | + // 查询平台下是否有该通道 | |
| 177 | + DeviceChannel channel = storager.queryChannelInParentPlatform(requesterId, channelId); | |
| 178 | + GbStream gbStream = storager.queryStreamInParentPlatform(requesterId, channelId); | |
| 179 | + PlatformCatalog catalog = storager.getCatalog(channelId); | |
| 180 | + | |
| 181 | + MediaServerItem mediaServerItem = null; | |
| 182 | + StreamPushItem streamPushItem = null; | |
| 183 | + // 不是通道可能是直播流 | |
| 184 | + if (channel != null && gbStream == null) { | |
| 185 | + if (channel.getStatus() == 0) { | |
| 186 | + logger.info("通道离线,返回400"); | |
| 187 | + responseAck(evt, Response.BAD_REQUEST, "channel [" + channel.getChannelId() + "] offline"); | |
| 188 | + return; | |
| 189 | + } | |
| 190 | + responseAck(evt, Response.CALL_IS_BEING_FORWARDED); // 通道存在,发181,呼叫转接中 | |
| 191 | + } else if (channel == null && gbStream != null) { | |
| 192 | + | |
| 193 | + String mediaServerId = gbStream.getMediaServerId(); | |
| 194 | + mediaServerItem = mediaServerService.getOne(mediaServerId); | |
| 195 | + if (mediaServerItem == null) { | |
| 196 | + if ("proxy".equals(gbStream.getStreamType())) { | |
| 197 | + logger.info("[ app={}, stream={} ]找不到zlm {},返回410", gbStream.getApp(), gbStream.getStream(), mediaServerId); | |
| 198 | + responseAck(evt, Response.GONE); | |
| 199 | + return; | |
| 200 | + } else { | |
| 201 | + streamPushItem = streamPushService.getPush(gbStream.getApp(), gbStream.getStream()); | |
| 202 | + if (streamPushItem == null || streamPushItem.getServerId().equals(userSetting.getServerId())) { | |
| 203 | + logger.info("[ app={}, stream={} ]找不到zlm {},返回410", gbStream.getApp(), gbStream.getStream(), mediaServerId); | |
| 204 | + responseAck(evt, Response.GONE); | |
| 205 | + return; | |
| 206 | + } | |
| 207 | + } | |
| 208 | + } else { | |
| 209 | + if ("push".equals(gbStream.getStreamType())) { | |
| 210 | + streamPushItem = streamPushService.getPush(gbStream.getApp(), gbStream.getStream()); | |
| 211 | + if (streamPushItem == null) { | |
| 212 | + logger.info("[ app={}, stream={} ]找不到zlm {},返回410", gbStream.getApp(), gbStream.getStream(), mediaServerId); | |
| 213 | + responseAck(evt, Response.GONE); | |
| 214 | + return; | |
| 215 | + } | |
| 216 | + } | |
| 217 | + } | |
| 218 | + responseAck(evt, Response.CALL_IS_BEING_FORWARDED); // 通道存在,发181,呼叫转接中 | |
| 219 | + } else if (catalog != null) { | |
| 220 | + responseAck(evt, Response.BAD_REQUEST, "catalog channel can not play"); // 目录不支持点播 | |
| 221 | + return; | |
| 222 | + } else { | |
| 223 | + logger.info("通道不存在,返回404"); | |
| 224 | + responseAck(evt, Response.NOT_FOUND); // 通道不存在,发404,资源不存在 | |
| 225 | + return; | |
| 226 | + } | |
| 227 | + // 解析sdp消息, 使用jainsip 自带的sdp解析方式 | |
| 228 | + String contentString = new String(request.getRawContent()); | |
| 229 | + | |
| 230 | + // jainSip不支持y=字段, 移除以解析。 | |
| 231 | + int ssrcIndex = contentString.indexOf("y="); | |
| 232 | + // 检查是否有y字段 | |
| 233 | + String ssrcDefault = "0000000000"; | |
| 234 | + String ssrc; | |
| 235 | + SessionDescription sdp; | |
| 236 | + if (ssrcIndex >= 0) { | |
| 237 | + //ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段 | |
| 238 | + ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12); | |
| 239 | + String substring = contentString.substring(0, contentString.indexOf("y=")); | |
| 240 | + sdp = SdpFactory.getInstance().createSessionDescription(substring); | |
| 241 | + } else { | |
| 242 | + ssrc = ssrcDefault; | |
| 243 | + sdp = SdpFactory.getInstance().createSessionDescription(contentString); | |
| 244 | + } | |
| 245 | + String sessionName = sdp.getSessionName().getValue(); | |
| 246 | + | |
| 247 | + Long startTime = null; | |
| 248 | + Long stopTime = null; | |
| 249 | + Instant start = null; | |
| 250 | + Instant end = null; | |
| 251 | + if (sdp.getTimeDescriptions(false) != null && sdp.getTimeDescriptions(false).size() > 0) { | |
| 252 | + TimeDescriptionImpl timeDescription = (TimeDescriptionImpl) (sdp.getTimeDescriptions(false).get(0)); | |
| 253 | + TimeField startTimeFiled = (TimeField) timeDescription.getTime(); | |
| 254 | + startTime = startTimeFiled.getStartTime(); | |
| 255 | + stopTime = startTimeFiled.getStopTime(); | |
| 256 | + | |
| 257 | + start = Instant.ofEpochSecond(startTime); | |
| 258 | + end = Instant.ofEpochSecond(stopTime); | |
| 259 | + } | |
| 260 | + // 获取支持的格式 | |
| 261 | + Vector mediaDescriptions = sdp.getMediaDescriptions(true); | |
| 262 | + // 查看是否支持PS 负载96 | |
| 263 | + //String ip = null; | |
| 264 | + int port = -1; | |
| 265 | + boolean mediaTransmissionTCP = false; | |
| 266 | + Boolean tcpActive = null; | |
| 267 | + for (Object description : mediaDescriptions) { | |
| 268 | + MediaDescription mediaDescription = (MediaDescription) description; | |
| 269 | + Media media = mediaDescription.getMedia(); | |
| 270 | + | |
| 271 | + Vector mediaFormats = media.getMediaFormats(false); | |
| 272 | + if (mediaFormats.contains("96")) { | |
| 273 | + port = media.getMediaPort(); | |
| 274 | + //String mediaType = media.getMediaType(); | |
| 275 | + String protocol = media.getProtocol(); | |
| 276 | + | |
| 277 | + // 区分TCP发流还是udp, 当前默认udp | |
| 278 | + if ("TCP/RTP/AVP".equals(protocol)) { | |
| 279 | + String setup = mediaDescription.getAttribute("setup"); | |
| 280 | + if (setup != null) { | |
| 281 | + mediaTransmissionTCP = true; | |
| 282 | + if ("active".equals(setup)) { | |
| 283 | + tcpActive = true; | |
| 284 | + // 不支持tcp主动 | |
| 285 | + responseAck(evt, Response.NOT_IMPLEMENTED, "tcp active not support"); // 目录不支持点播 | |
| 286 | + return; | |
| 287 | + } else if ("passive".equals(setup)) { | |
| 288 | + tcpActive = false; | |
| 289 | + } | |
| 290 | + } | |
| 291 | + } | |
| 292 | + break; | |
| 293 | + } | |
| 294 | + } | |
| 295 | + if (port == -1) { | |
| 296 | + logger.info("不支持的媒体格式,返回415"); | |
| 297 | + // 回复不支持的格式 | |
| 298 | + responseAck(evt, Response.UNSUPPORTED_MEDIA_TYPE); // 不支持的格式,发415 | |
| 299 | + return; | |
| 300 | + } | |
| 301 | + String username = sdp.getOrigin().getUsername(); | |
| 302 | + String addressStr = sdp.getOrigin().getAddress(); | |
| 303 | + | |
| 304 | + logger.info("[上级点播]用户:{}, 通道:{}, 地址:{}:{}, ssrc:{}", username, channelId, addressStr, port, ssrc); | |
| 305 | + Device device = null; | |
| 306 | + // 通过 channel 和 gbStream 是否为null 值判断来源是直播流合适国标 | |
| 307 | + if (channel != null) { | |
| 308 | + device = storager.queryVideoDeviceByPlatformIdAndChannelId(requesterId, channelId); | |
| 309 | + if (device == null) { | |
| 310 | + logger.warn("点播平台{}的通道{}时未找到设备信息", requesterId, channel); | |
| 311 | + responseAck(evt, Response.SERVER_INTERNAL_ERROR); | |
| 312 | + return; | |
| 313 | + } | |
| 314 | + mediaServerItem = playService.getNewMediaServerItem(device); | |
| 315 | + if (mediaServerItem == null) { | |
| 316 | + logger.warn("未找到可用的zlm"); | |
| 317 | + responseAck(evt, Response.BUSY_HERE); | |
| 318 | + return; | |
| 319 | + } | |
| 320 | + SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId, | |
| 321 | + device.getDeviceId(), channelId, | |
| 322 | + mediaTransmissionTCP); | |
| 323 | + if (tcpActive != null) { | |
| 324 | + sendRtpItem.setTcpActive(tcpActive); | |
| 325 | + } | |
| 326 | + if (sendRtpItem == null) { | |
| 327 | + logger.warn("服务器端口资源不足"); | |
| 328 | + responseAck(evt, Response.BUSY_HERE); | |
| 329 | + return; | |
| 330 | + } | |
| 331 | + sendRtpItem.setCallId(callIdHeader.getCallId()); | |
| 332 | + sendRtpItem.setPlayType("Play".equals(sessionName) ? InviteStreamType.PLAY : InviteStreamType.PLAYBACK); | |
| 333 | + | |
| 334 | + Long finalStartTime = startTime; | |
| 335 | + Long finalStopTime = stopTime; | |
| 336 | + ZLMHttpHookSubscribe.Event hookEvent = (mediaServerItemInUSe, responseJSON) -> { | |
| 337 | + String app = responseJSON.getString("app"); | |
| 338 | + String stream = responseJSON.getString("stream"); | |
| 339 | + logger.info("[上级点播]下级已经开始推流。 回复200OK(SDP), {}/{}", app, stream); | |
| 340 | + // * 0 等待设备推流上来 | |
| 341 | + // * 1 下级已经推流,等待上级平台回复ack | |
| 342 | + // * 2 推流中 | |
| 343 | + sendRtpItem.setStatus(1); | |
| 344 | + redisCatchStorage.updateSendRTPSever(sendRtpItem); | |
| 345 | + | |
| 346 | + StringBuffer content = new StringBuffer(200); | |
| 347 | + content.append("v=0\r\n"); | |
| 348 | + content.append("o=" + channelId + " 0 0 IN IP4 " + mediaServerItemInUSe.getSdpIp() + "\r\n"); | |
| 349 | + content.append("s=" + sessionName + "\r\n"); | |
| 350 | + content.append("c=IN IP4 " + mediaServerItemInUSe.getSdpIp() + "\r\n"); | |
| 351 | + if ("Playback".equals(sessionName)) { | |
| 352 | + content.append("t=" + finalStartTime + " " + finalStopTime + "\r\n"); | |
| 353 | + } else { | |
| 354 | + content.append("t=0 0\r\n"); | |
| 355 | + } | |
| 356 | + content.append("m=video " + sendRtpItem.getLocalPort() + " RTP/AVP 96\r\n"); | |
| 357 | + content.append("a=sendonly\r\n"); | |
| 358 | + content.append("a=rtpmap:96 PS/90000\r\n"); | |
| 359 | + content.append("y=" + ssrc + "\r\n"); | |
| 360 | + content.append("f=\r\n"); | |
| 361 | + | |
| 362 | + try { | |
| 363 | + // 超时未收到Ack应该回复bye,当前等待时间为10秒 | |
| 364 | + dynamicTask.startDelay(callIdHeader.getCallId(), () -> { | |
| 365 | + logger.info("Ack 等待超时"); | |
| 366 | + mediaServerService.releaseSsrc(mediaServerItemInUSe.getId(), ssrc); | |
| 367 | + // 回复bye | |
| 368 | + cmderFroPlatform.streamByeCmd(platform, callIdHeader.getCallId()); | |
| 369 | + }, 60 * 1000); | |
| 370 | + responseSdpAck(evt, content.toString(), platform); | |
| 371 | + | |
| 372 | + } catch (SipException e) { | |
| 373 | + e.printStackTrace(); | |
| 374 | + } catch (InvalidArgumentException e) { | |
| 375 | + e.printStackTrace(); | |
| 376 | + } catch (ParseException e) { | |
| 377 | + e.printStackTrace(); | |
| 378 | + } | |
| 379 | + }; | |
| 380 | + SipSubscribe.Event errorEvent = ((event) -> { | |
| 381 | + // 未知错误。直接转发设备点播的错误 | |
| 382 | + Response response = null; | |
| 383 | + try { | |
| 384 | + response = getMessageFactory().createResponse(event.statusCode, evt.getRequest()); | |
| 385 | + ServerTransaction serverTransaction = getServerTransaction(evt); | |
| 386 | + serverTransaction.sendResponse(response); | |
| 387 | + if (serverTransaction.getDialog() != null) { | |
| 388 | + serverTransaction.getDialog().delete(); | |
| 389 | + } | |
| 390 | + } catch (ParseException | SipException | InvalidArgumentException e) { | |
| 391 | + e.printStackTrace(); | |
| 392 | + } | |
| 393 | + }); | |
| 394 | + sendRtpItem.setApp("rtp"); | |
| 395 | + if ("Playback".equals(sessionName)) { | |
| 396 | + sendRtpItem.setPlayType(InviteStreamType.PLAYBACK); | |
| 397 | + SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, null, true, true); | |
| 398 | + sendRtpItem.setStreamId(ssrcInfo.getStream()); | |
| 399 | + // 写入redis, 超时时回复 | |
| 400 | + redisCatchStorage.updateSendRTPSever(sendRtpItem); | |
| 401 | + playService.playBack(mediaServerItem, ssrcInfo, device.getDeviceId(), channelId, DateUtil.formatter.format(start), | |
| 402 | + DateUtil.formatter.format(end), null, result -> { | |
| 403 | + if (result.getCode() != 0) { | |
| 404 | + logger.warn("录像回放失败"); | |
| 405 | + if (result.getEvent() != null) { | |
| 406 | + errorEvent.response(result.getEvent()); | |
| 407 | + } | |
| 408 | + redisCatchStorage.deleteSendRTPServer(platform.getServerGBId(), channelId, callIdHeader.getCallId(), null); | |
| 409 | + try { | |
| 410 | + responseAck(evt, Response.REQUEST_TIMEOUT); | |
| 411 | + } catch (SipException e) { | |
| 412 | + e.printStackTrace(); | |
| 413 | + } catch (InvalidArgumentException e) { | |
| 414 | + e.printStackTrace(); | |
| 415 | + } catch (ParseException e) { | |
| 416 | + e.printStackTrace(); | |
| 417 | + } | |
| 418 | + } else { | |
| 419 | + if (result.getMediaServerItem() != null) { | |
| 420 | + hookEvent.response(result.getMediaServerItem(), result.getResponse()); | |
| 421 | + } | |
| 422 | + } | |
| 423 | + }); | |
| 424 | + } else { | |
| 425 | + sendRtpItem.setPlayType(InviteStreamType.PLAY); | |
| 426 | + SsrcTransaction playTransaction = sessionManager.getSsrcTransaction(device.getDeviceId(), channelId, "play", null); | |
| 427 | + if (playTransaction != null) { | |
| 428 | + Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, "rtp", playTransaction.getStream()); | |
| 429 | + if (!streamReady) { | |
| 430 | + playTransaction = null; | |
| 431 | + } | |
| 432 | + } | |
| 433 | + if (playTransaction == null) { | |
| 434 | + String streamId = null; | |
| 435 | + if (mediaServerItem.isRtpEnable()) { | |
| 436 | + streamId = String.format("%s_%s", device.getDeviceId(), channelId); | |
| 437 | + } | |
| 438 | + SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, null, device.isSsrcCheck(), false); | |
| 439 | + sendRtpItem.setStreamId(ssrcInfo.getStream()); | |
| 440 | + // 写入redis, 超时时回复 | |
| 441 | + redisCatchStorage.updateSendRTPSever(sendRtpItem); | |
| 442 | + playService.play(mediaServerItem, ssrcInfo, device, channelId, hookEvent, errorEvent, (code, msg) -> { | |
| 443 | + logger.info("[上级点播]超时, 用户:{}, 通道:{}", username, channelId); | |
| 444 | + redisCatchStorage.deleteSendRTPServer(platform.getServerGBId(), channelId, callIdHeader.getCallId(), null); | |
| 445 | + }, null); | |
| 446 | + } else { | |
| 447 | + sendRtpItem.setStreamId(playTransaction.getStream()); | |
| 448 | + // 写入redis, 超时时回复 | |
| 449 | + redisCatchStorage.updateSendRTPSever(sendRtpItem); | |
| 450 | + JSONObject jsonObject = new JSONObject(); | |
| 451 | + jsonObject.put("app", sendRtpItem.getApp()); | |
| 452 | + jsonObject.put("stream", sendRtpItem.getStreamId()); | |
| 453 | + hookEvent.response(mediaServerItem, jsonObject); | |
| 454 | + } | |
| 455 | + } | |
| 456 | + } else if (gbStream != null) { | |
| 457 | + if (streamPushItem.isStatus()) { | |
| 458 | + // 在线状态 | |
| 459 | + pushStream(evt, gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive, | |
| 460 | + mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId); | |
| 461 | + } else { | |
| 462 | + // 不在线 拉起 | |
| 463 | + notifyStreamOnline(evt, gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive, | |
| 464 | + mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId); | |
| 465 | + } | |
| 466 | + | |
| 467 | + } | |
| 468 | + | |
| 469 | + } | |
| 470 | + | |
| 471 | + } catch (SipException | InvalidArgumentException | ParseException e) { | |
| 472 | + e.printStackTrace(); | |
| 473 | + logger.warn("sdp解析错误"); | |
| 474 | + e.printStackTrace(); | |
| 475 | + } catch (SdpParseException e) { | |
| 476 | + e.printStackTrace(); | |
| 477 | + } catch (SdpException e) { | |
| 478 | + e.printStackTrace(); | |
| 479 | + } | |
| 480 | + } | |
| 481 | + | |
| 482 | + /** | |
| 483 | + * 安排推流 | |
| 484 | + */ | |
| 485 | + | |
| 486 | + private void pushStream(RequestEvent evt, GbStream gbStream, StreamPushItem streamPushItem, ParentPlatform platform, | |
| 487 | + CallIdHeader callIdHeader, MediaServerItem mediaServerItem, | |
| 488 | + int port, Boolean tcpActive, boolean mediaTransmissionTCP, | |
| 489 | + String channelId, String addressStr, String ssrc, String requesterId) throws InvalidArgumentException, ParseException, SipException { | |
| 490 | + // 推流 | |
| 491 | + if (streamPushItem.getServerId().equals(userSetting.getServerId())) { | |
| 492 | + Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, gbStream.getApp(), gbStream.getStream()); | |
| 493 | + if (streamReady) { | |
| 494 | + // 自平台内容 | |
| 495 | + SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId, | |
| 496 | + gbStream.getApp(), gbStream.getStream(), channelId, | |
| 497 | + mediaTransmissionTCP); | |
| 498 | + | |
| 499 | + if (sendRtpItem == null) { | |
| 500 | + logger.warn("服务器端口资源不足"); | |
| 501 | + responseAck(evt, Response.BUSY_HERE); | |
| 502 | + return; | |
| 503 | + } | |
| 504 | + if (tcpActive != null) { | |
| 505 | + sendRtpItem.setTcpActive(tcpActive); | |
| 506 | + } | |
| 507 | + sendRtpItem.setPlayType(InviteStreamType.PUSH); | |
| 508 | + // 写入redis, 超时时回复 | |
| 509 | + sendRtpItem.setStatus(1); | |
| 510 | + sendRtpItem.setCallId(callIdHeader.getCallId()); | |
| 511 | + byte[] dialogByteArray = SerializeUtils.serialize(evt.getDialog()); | |
| 512 | + sendRtpItem.setDialog(dialogByteArray); | |
| 513 | + byte[] transactionByteArray = SerializeUtils.serialize(evt.getServerTransaction()); | |
| 514 | + sendRtpItem.setTransaction(transactionByteArray); | |
| 515 | + redisCatchStorage.updateSendRTPSever(sendRtpItem); | |
| 516 | + sendStreamAck(mediaServerItem, sendRtpItem, platform, evt); | |
| 517 | + } else { | |
| 518 | + // 不在线 拉起 | |
| 519 | + notifyStreamOnline(evt, gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive, | |
| 520 | + mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId); | |
| 521 | + } | |
| 522 | + | |
| 523 | + } else { | |
| 524 | + // 其他平台内容 | |
| 525 | + otherWvpPushStream(evt, gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive, | |
| 526 | + mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId); | |
| 527 | + } | |
| 528 | + | |
| 529 | + } | |
| 530 | + | |
| 531 | + /** | |
| 532 | + * 通知流上线 | |
| 533 | + */ | |
| 534 | + private void notifyStreamOnline(RequestEvent evt, GbStream gbStream, StreamPushItem streamPushItem, ParentPlatform platform, | |
| 535 | + CallIdHeader callIdHeader, MediaServerItem mediaServerItem, | |
| 536 | + int port, Boolean tcpActive, boolean mediaTransmissionTCP, | |
| 537 | + String channelId, String addressStr, String ssrc, String requesterId) throws InvalidArgumentException, ParseException, SipException { | |
| 538 | + if ("proxy".equals(gbStream.getStreamType())) { | |
| 539 | + // TODO 控制启用以使设备上线 | |
| 540 | + logger.info("[ app={}, stream={} ]通道离线,启用流后开始推流", gbStream.getApp(), gbStream.getStream()); | |
| 541 | + responseAck(evt, Response.BAD_REQUEST, "channel [" + gbStream.getGbId() + "] offline"); | |
| 542 | + } else if ("push".equals(gbStream.getStreamType())) { | |
| 543 | + if (!platform.isStartOfflinePush()) { | |
| 544 | + responseAck(evt, Response.TEMPORARILY_UNAVAILABLE, "channel unavailable"); | |
| 545 | + return; | |
| 546 | + } | |
| 547 | + // 发送redis消息以使设备上线 | |
| 548 | + logger.info("[ app={}, stream={} ]通道离线,发送redis信息控制设备开始推流", gbStream.getApp(), gbStream.getStream()); | |
| 549 | + | |
| 550 | + MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(1, | |
| 551 | + gbStream.getApp(), gbStream.getStream(), gbStream.getGbId(), gbStream.getPlatformId(), | |
| 552 | + platform.getName(), null, gbStream.getMediaServerId()); | |
| 553 | + redisCatchStorage.sendStreamPushRequestedMsg(messageForPushChannel); | |
| 554 | + // 设置超时 | |
| 555 | + dynamicTask.startDelay(callIdHeader.getCallId(), () -> { | |
| 556 | + logger.info("[ app={}, stream={} ] 等待设备开始推流超时", gbStream.getApp(), gbStream.getStream()); | |
| 557 | + try { | |
| 558 | + mediaListManager.removedChannelOnlineEventLister(gbStream.getGbId()); | |
| 559 | + responseAck(evt, Response.REQUEST_TIMEOUT); // 超时 | |
| 560 | + } catch (SipException e) { | |
| 561 | + e.printStackTrace(); | |
| 562 | + } catch (InvalidArgumentException e) { | |
| 563 | + e.printStackTrace(); | |
| 564 | + } catch (ParseException e) { | |
| 565 | + e.printStackTrace(); | |
| 566 | + } | |
| 567 | + }, userSetting.getPlatformPlayTimeout()); | |
| 568 | + // 添加监听 | |
| 569 | + int finalPort = port; | |
| 570 | + Boolean finalTcpActive = tcpActive; | |
| 571 | + | |
| 572 | + // 添加在本机上线的通知 | |
| 573 | + mediaListManager.addChannelOnlineEventLister(gbStream.getGbId(), (app, stream, serverId) -> { | |
| 574 | + dynamicTask.stop(callIdHeader.getCallId()); | |
| 575 | + if (serverId.equals(userSetting.getServerId())) { | |
| 576 | + SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, finalPort, ssrc, requesterId, | |
| 577 | + app, stream, channelId, mediaTransmissionTCP); | |
| 578 | + | |
| 579 | + if (sendRtpItem == null) { | |
| 580 | + logger.warn("服务器端口资源不足"); | |
| 581 | + try { | |
| 582 | + responseAck(evt, Response.BUSY_HERE); | |
| 583 | + } catch (SipException e) { | |
| 584 | + e.printStackTrace(); | |
| 585 | + } catch (InvalidArgumentException e) { | |
| 586 | + e.printStackTrace(); | |
| 587 | + } catch (ParseException e) { | |
| 588 | + e.printStackTrace(); | |
| 589 | + } | |
| 590 | + return; | |
| 591 | + } | |
| 592 | + if (finalTcpActive != null) { | |
| 593 | + sendRtpItem.setTcpActive(finalTcpActive); | |
| 594 | + } | |
| 595 | + sendRtpItem.setPlayType(InviteStreamType.PUSH); | |
| 596 | + // 写入redis, 超时时回复 | |
| 597 | + sendRtpItem.setStatus(1); | |
| 598 | + sendRtpItem.setCallId(callIdHeader.getCallId()); | |
| 599 | + byte[] dialogByteArray = SerializeUtils.serialize(evt.getDialog()); | |
| 600 | + sendRtpItem.setDialog(dialogByteArray); | |
| 601 | + byte[] transactionByteArray = SerializeUtils.serialize(evt.getServerTransaction()); | |
| 602 | + sendRtpItem.setTransaction(transactionByteArray); | |
| 603 | + redisCatchStorage.updateSendRTPSever(sendRtpItem); | |
| 604 | + sendStreamAck(mediaServerItem, sendRtpItem, platform, evt); | |
| 605 | + } else { | |
| 606 | + // 其他平台内容 | |
| 607 | + otherWvpPushStream(evt, gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive, | |
| 608 | + mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId); | |
| 609 | + } | |
| 610 | + }); | |
| 611 | + } | |
| 612 | + } | |
| 613 | + | |
| 614 | + /** | |
| 615 | + * 来自其他wvp的推流 | |
| 616 | + */ | |
| 617 | + private void otherWvpPushStream(RequestEvent evt, GbStream gbStream, StreamPushItem streamPushItem, ParentPlatform platform, | |
| 618 | + CallIdHeader callIdHeader, MediaServerItem mediaServerItem, | |
| 619 | + int port, Boolean tcpActive, boolean mediaTransmissionTCP, | |
| 620 | + String channelId, String addressStr, String ssrc, String requesterId) { | |
| 621 | + logger.info("[级联点播]直播流来自其他平台,发送redis消息"); | |
| 622 | + // 发送redis消息 | |
| 623 | + redisGbPlayMsgListener.sendMsg(streamPushItem.getServerId(), streamPushItem.getMediaServerId(), | |
| 624 | + streamPushItem.getApp(), streamPushItem.getStream(), addressStr, port, ssrc, requesterId, | |
| 625 | + channelId, mediaTransmissionTCP, null, responseSendItemMsg -> { | |
| 626 | + SendRtpItem sendRtpItem = responseSendItemMsg.getSendRtpItem(); | |
| 627 | + if (sendRtpItem == null || responseSendItemMsg.getMediaServerItem() == null) { | |
| 628 | + logger.warn("服务器端口资源不足"); | |
| 629 | + try { | |
| 630 | + responseAck(evt, Response.BUSY_HERE); | |
| 631 | + } catch (SipException e) { | |
| 632 | + e.printStackTrace(); | |
| 633 | + } catch (InvalidArgumentException e) { | |
| 634 | + e.printStackTrace(); | |
| 635 | + } catch (ParseException e) { | |
| 636 | + e.printStackTrace(); | |
| 637 | + } | |
| 638 | + return; | |
| 639 | + } | |
| 640 | + // 收到sendItem | |
| 641 | + if (tcpActive != null) { | |
| 642 | + sendRtpItem.setTcpActive(tcpActive); | |
| 643 | + } | |
| 644 | + sendRtpItem.setPlayType(InviteStreamType.PUSH); | |
| 645 | + // 写入redis, 超时时回复 | |
| 646 | + sendRtpItem.setStatus(1); | |
| 647 | + sendRtpItem.setCallId(callIdHeader.getCallId()); | |
| 648 | + byte[] dialogByteArray = SerializeUtils.serialize(evt.getDialog()); | |
| 649 | + sendRtpItem.setDialog(dialogByteArray); | |
| 650 | + byte[] transactionByteArray = SerializeUtils.serialize(evt.getServerTransaction()); | |
| 651 | + sendRtpItem.setTransaction(transactionByteArray); | |
| 652 | + redisCatchStorage.updateSendRTPSever(sendRtpItem); | |
| 653 | + sendStreamAck(responseSendItemMsg.getMediaServerItem(), sendRtpItem, platform, evt); | |
| 654 | + }, (wvpResult) -> { | |
| 655 | + try { | |
| 656 | + // 错误 | |
| 657 | + if (wvpResult.getCode() == RedisGbPlayMsgListener.ERROR_CODE_OFFLINE) { | |
| 658 | + // 离线 | |
| 659 | + // 查询是否在本机上线了 | |
| 660 | + StreamPushItem currentStreamPushItem = streamPushService.getPush(streamPushItem.getApp(), streamPushItem.getStream()); | |
| 661 | + if (currentStreamPushItem.isStatus()) { | |
| 662 | + // 在线状态 | |
| 663 | + pushStream(evt, gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive, | |
| 664 | + mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId); | |
| 665 | + | |
| 666 | + } else { | |
| 667 | + // 不在线 拉起 | |
| 668 | + notifyStreamOnline(evt, gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive, | |
| 669 | + mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId); | |
| 670 | + } | |
| 671 | + } | |
| 672 | + } catch (InvalidArgumentException e) { | |
| 673 | + throw new RuntimeException(e); | |
| 674 | + } catch (ParseException e) { | |
| 675 | + throw new RuntimeException(e); | |
| 676 | + } catch (SipException e) { | |
| 677 | + throw new RuntimeException(e); | |
| 678 | + } | |
| 679 | + | |
| 680 | + | |
| 681 | + try { | |
| 682 | + responseAck(evt, Response.BUSY_HERE); | |
| 683 | + } catch (SipException e) { | |
| 684 | + e.printStackTrace(); | |
| 685 | + } catch (InvalidArgumentException e) { | |
| 686 | + e.printStackTrace(); | |
| 687 | + } catch (ParseException e) { | |
| 688 | + e.printStackTrace(); | |
| 689 | + } | |
| 690 | + return; | |
| 691 | + }); | |
| 692 | + } | |
| 693 | + | |
| 694 | + public void sendStreamAck(MediaServerItem mediaServerItem, SendRtpItem sendRtpItem, ParentPlatform platform, RequestEvent evt) { | |
| 695 | + | |
| 696 | + StringBuffer content = new StringBuffer(200); | |
| 697 | + content.append("v=0\r\n"); | |
| 698 | + content.append("o=" + sendRtpItem.getChannelId() + " 0 0 IN IP4 " + mediaServerItem.getSdpIp() + "\r\n"); | |
| 699 | + content.append("s=Play\r\n"); | |
| 700 | + content.append("c=IN IP4 " + mediaServerItem.getSdpIp() + "\r\n"); | |
| 701 | + content.append("t=0 0\r\n"); | |
| 702 | + content.append("m=video " + sendRtpItem.getLocalPort() + " RTP/AVP 96\r\n"); | |
| 703 | + content.append("a=sendonly\r\n"); | |
| 704 | + content.append("a=rtpmap:96 PS/90000\r\n"); | |
| 705 | + if (sendRtpItem.isTcp()) { | |
| 706 | + content.append("a=connection:new\r\n"); | |
| 707 | + if (!sendRtpItem.isTcpActive()) { | |
| 708 | + content.append("a=setup:active\r\n"); | |
| 709 | + } else { | |
| 710 | + content.append("a=setup:passive\r\n"); | |
| 711 | + } | |
| 712 | + } | |
| 713 | + content.append("y=" + sendRtpItem.getSsrc() + "\r\n"); | |
| 714 | + content.append("f=\r\n"); | |
| 715 | + | |
| 716 | + try { | |
| 717 | + responseSdpAck(evt, content.toString(), platform); | |
| 718 | + } catch (SipException e) { | |
| 719 | + e.printStackTrace(); | |
| 720 | + } catch (InvalidArgumentException e) { | |
| 721 | + e.printStackTrace(); | |
| 722 | + } catch (ParseException e) { | |
| 723 | + e.printStackTrace(); | |
| 724 | + } | |
| 725 | + } | |
| 726 | + | |
| 727 | + public void inviteFromDeviceHandle(RequestEvent evt, String requesterId) throws InvalidArgumentException, ParseException, SipException, SdpException { | |
| 728 | + | |
| 729 | + // 非上级平台请求,查询是否设备请求(通常为接收语音广播的设备) | |
| 730 | + Device device = redisCatchStorage.getDevice(requesterId); | |
| 731 | + Request request = evt.getRequest(); | |
| 732 | + if (device != null) { | |
| 733 | + logger.info("收到设备" + requesterId + "的语音广播Invite请求"); | |
| 734 | + responseAck(evt, Response.TRYING); | |
| 735 | + | |
| 736 | + String contentString = new String(request.getRawContent()); | |
| 737 | + // jainSip不支持y=字段, 移除移除以解析。 | |
| 738 | + String substring = contentString; | |
| 739 | + String ssrc = "0000000404"; | |
| 740 | + int ssrcIndex = contentString.indexOf("y="); | |
| 741 | + if (ssrcIndex > 0) { | |
| 742 | + substring = contentString.substring(0, ssrcIndex); | |
| 743 | + ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12); | |
| 744 | + } | |
| 745 | + ssrcIndex = substring.indexOf("f="); | |
| 746 | + if (ssrcIndex > 0) { | |
| 747 | + substring = contentString.substring(0, ssrcIndex); | |
| 748 | + } | |
| 749 | + SessionDescription sdp = SdpFactory.getInstance().createSessionDescription(substring); | |
| 750 | + | |
| 751 | + // 获取支持的格式 | |
| 752 | + Vector mediaDescriptions = sdp.getMediaDescriptions(true); | |
| 753 | + // 查看是否支持PS 负载96 | |
| 754 | + int port = -1; | |
| 755 | + //boolean recvonly = false; | |
| 756 | + boolean mediaTransmissionTCP = false; | |
| 757 | + Boolean tcpActive = null; | |
| 758 | + for (int i = 0; i < mediaDescriptions.size(); i++) { | |
| 759 | + MediaDescription mediaDescription = (MediaDescription) mediaDescriptions.get(i); | |
| 760 | + Media media = mediaDescription.getMedia(); | |
| 761 | + | |
| 762 | + Vector mediaFormats = media.getMediaFormats(false); | |
| 763 | + if (mediaFormats.contains("8")) { | |
| 764 | + port = media.getMediaPort(); | |
| 765 | + String protocol = media.getProtocol(); | |
| 766 | + // 区分TCP发流还是udp, 当前默认udp | |
| 767 | + if ("TCP/RTP/AVP".equals(protocol)) { | |
| 768 | + String setup = mediaDescription.getAttribute("setup"); | |
| 769 | + if (setup != null) { | |
| 770 | + mediaTransmissionTCP = true; | |
| 771 | + if ("active".equals(setup)) { | |
| 772 | + tcpActive = true; | |
| 773 | + } else if ("passive".equals(setup)) { | |
| 774 | + tcpActive = false; | |
| 775 | + } | |
| 776 | + } | |
| 777 | + } | |
| 778 | + break; | |
| 779 | + } | |
| 780 | + } | |
| 781 | + if (port == -1) { | |
| 782 | + logger.info("不支持的媒体格式,返回415"); | |
| 783 | + // 回复不支持的格式 | |
| 784 | + responseAck(evt, Response.UNSUPPORTED_MEDIA_TYPE); // 不支持的格式,发415 | |
| 785 | + return; | |
| 786 | + } | |
| 787 | + String username = sdp.getOrigin().getUsername(); | |
| 788 | + String addressStr = sdp.getOrigin().getAddress(); | |
| 789 | + logger.info("设备{}请求语音流,地址:{}:{},ssrc:{}", username, addressStr, port, ssrc); | |
| 790 | + | |
| 791 | + } else { | |
| 792 | + logger.warn("来自无效设备/平台的请求"); | |
| 793 | + responseAck(evt, Response.BAD_REQUEST); | |
| 794 | + } | |
| 795 | + } | |
| 662 | 796 | CallIdHeader callIdHeader = (CallIdHeader) request.getHeader(CallIdHeader.NAME); |
| 663 | 797 | sendRtpItem.setPlayType(InviteStreamType.PLAY); |
| 664 | 798 | sendRtpItem.setCallId(callIdHeader.getCallId()); | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/NotifyRequestProcessor.java
| 1 | 1 | package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl; |
| 2 | 2 | |
| 3 | 3 | import com.alibaba.fastjson.JSONObject; |
| 4 | -import com.genersoft.iot.vmp.common.VideoManagerConstants; | |
| 5 | 4 | import com.genersoft.iot.vmp.conf.SipConfig; |
| 6 | 5 | import com.genersoft.iot.vmp.conf.UserSetting; |
| 7 | 6 | import com.genersoft.iot.vmp.gb28181.bean.*; |
| ... | ... | @@ -18,6 +17,7 @@ import com.genersoft.iot.vmp.gb28181.utils.SipUtils; |
| 18 | 17 | import com.genersoft.iot.vmp.gb28181.utils.XmlUtil; |
| 19 | 18 | import com.genersoft.iot.vmp.storager.IRedisCatchStorage; |
| 20 | 19 | import com.genersoft.iot.vmp.storager.IVideoManagerStorage; |
| 20 | +import com.genersoft.iot.vmp.utils.DateUtil; | |
| 21 | 21 | import com.genersoft.iot.vmp.utils.redis.RedisUtil; |
| 22 | 22 | import org.dom4j.DocumentException; |
| 23 | 23 | import org.dom4j.Element; |
| ... | ... | @@ -25,6 +25,8 @@ import org.slf4j.Logger; |
| 25 | 25 | import org.slf4j.LoggerFactory; |
| 26 | 26 | import org.springframework.beans.factory.InitializingBean; |
| 27 | 27 | import org.springframework.beans.factory.annotation.Autowired; |
| 28 | +import org.springframework.beans.factory.annotation.Qualifier; | |
| 29 | +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; | |
| 28 | 30 | import org.springframework.stereotype.Component; |
| 29 | 31 | import org.springframework.util.StringUtils; |
| 30 | 32 | |
| ... | ... | @@ -35,6 +37,7 @@ import javax.sip.header.FromHeader; |
| 35 | 37 | import javax.sip.message.Response; |
| 36 | 38 | import java.text.ParseException; |
| 37 | 39 | import java.util.Iterator; |
| 40 | +import java.util.concurrent.ConcurrentLinkedQueue; | |
| 38 | 41 | |
| 39 | 42 | /** |
| 40 | 43 | * SIP命令类型: NOTIFY请求 |
| ... | ... | @@ -63,11 +66,19 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements |
| 63 | 66 | @Autowired |
| 64 | 67 | private EventPublisher publisher; |
| 65 | 68 | |
| 66 | - private String method = "NOTIFY"; | |
| 69 | + private final String method = "NOTIFY"; | |
| 67 | 70 | |
| 68 | 71 | @Autowired |
| 69 | 72 | private SIPProcessorObserver sipProcessorObserver; |
| 70 | 73 | |
| 74 | + private boolean taskQueueHandlerRun = false; | |
| 75 | + | |
| 76 | + private final ConcurrentLinkedQueue<HandlerCatchData> taskQueue = new ConcurrentLinkedQueue<>(); | |
| 77 | + | |
| 78 | + @Qualifier("taskExecutor") | |
| 79 | + @Autowired | |
| 80 | + private ThreadPoolTaskExecutor taskExecutor; | |
| 81 | + | |
| 71 | 82 | @Override |
| 72 | 83 | public void afterPropertiesSet() throws Exception { |
| 73 | 84 | // 添加消息处理的订阅 |
| ... | ... | @@ -77,23 +88,40 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements |
| 77 | 88 | @Override |
| 78 | 89 | public void process(RequestEvent evt) { |
| 79 | 90 | try { |
| 80 | - Element rootElement = getRootElement(evt); | |
| 81 | - String cmd = XmlUtil.getText(rootElement, "CmdType"); | |
| 82 | - | |
| 83 | - if (CmdType.CATALOG.equals(cmd)) { | |
| 84 | - logger.info("接收到Catalog通知"); | |
| 85 | - processNotifyCatalogList(evt); | |
| 86 | - } else if (CmdType.ALARM.equals(cmd)) { | |
| 87 | - logger.info("接收到Alarm通知"); | |
| 88 | - processNotifyAlarm(evt); | |
| 89 | - } else if (CmdType.MOBILE_POSITION.equals(cmd)) { | |
| 90 | - logger.info("接收到MobilePosition通知"); | |
| 91 | - processNotifyMobilePosition(evt); | |
| 92 | - } else { | |
| 93 | - logger.info("接收到消息:" + cmd); | |
| 94 | - responseAck(evt, Response.OK); | |
| 91 | + | |
| 92 | + taskQueue.offer(new HandlerCatchData(evt, null, null)); | |
| 93 | + responseAck(evt, Response.OK); | |
| 94 | + if (!taskQueueHandlerRun) { | |
| 95 | + taskQueueHandlerRun = true; | |
| 96 | + taskExecutor.execute(()-> { | |
| 97 | + while (!taskQueue.isEmpty()) { | |
| 98 | + try { | |
| 99 | + HandlerCatchData take = taskQueue.poll(); | |
| 100 | + Element rootElement = getRootElement(take.getEvt()); | |
| 101 | + String cmd = XmlUtil.getText(rootElement, "CmdType"); | |
| 102 | + | |
| 103 | + if (CmdType.CATALOG.equals(cmd)) { | |
| 104 | + logger.info("接收到Catalog通知"); | |
| 105 | + processNotifyCatalogList(take.getEvt()); | |
| 106 | + } else if (CmdType.ALARM.equals(cmd)) { | |
| 107 | + logger.info("接收到Alarm通知"); | |
| 108 | + processNotifyAlarm(take.getEvt()); | |
| 109 | + } else if (CmdType.MOBILE_POSITION.equals(cmd)) { | |
| 110 | + logger.info("接收到MobilePosition通知"); | |
| 111 | + processNotifyMobilePosition(take.getEvt()); | |
| 112 | + } else { | |
| 113 | + logger.info("接收到消息:" + cmd); | |
| 114 | + } | |
| 115 | + } catch (DocumentException e) { | |
| 116 | + throw new RuntimeException(e); | |
| 117 | + } | |
| 118 | + } | |
| 119 | + taskQueueHandlerRun = false; | |
| 120 | + }); | |
| 95 | 121 | } |
| 96 | - } catch (DocumentException | SipException | InvalidArgumentException | ParseException e) { | |
| 122 | + | |
| 123 | + | |
| 124 | + } catch (SipException | InvalidArgumentException | ParseException e) { | |
| 97 | 125 | e.printStackTrace(); |
| 98 | 126 | } |
| 99 | 127 | } |
| ... | ... | @@ -166,8 +194,7 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements |
| 166 | 194 | jsonObject.put("direction", mobilePosition.getDirection()); |
| 167 | 195 | jsonObject.put("speed", mobilePosition.getSpeed()); |
| 168 | 196 | redisCatchStorage.sendMobilePositionMsg(jsonObject); |
| 169 | - responseAck(evt, Response.OK); | |
| 170 | - } catch (DocumentException | SipException | InvalidArgumentException | ParseException e) { | |
| 197 | + } catch (DocumentException e) { | |
| 171 | 198 | e.printStackTrace(); |
| 172 | 199 | } |
| 173 | 200 | } |
| ... | ... | @@ -188,6 +215,7 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements |
| 188 | 215 | |
| 189 | 216 | Device device = redisCatchStorage.getDevice(deviceId); |
| 190 | 217 | if (device == null) { |
| 218 | + logger.warn("[ NotifyAlarm ] 未找到设备:{}", deviceId); | |
| 191 | 219 | return; |
| 192 | 220 | } |
| 193 | 221 | rootElement = getRootElement(evt, device.getCharset()); |
| ... | ... | @@ -195,7 +223,12 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements |
| 195 | 223 | deviceAlarm.setDeviceId(deviceId); |
| 196 | 224 | deviceAlarm.setAlarmPriority(XmlUtil.getText(rootElement, "AlarmPriority")); |
| 197 | 225 | deviceAlarm.setAlarmMethod(XmlUtil.getText(rootElement, "AlarmMethod")); |
| 198 | - deviceAlarm.setAlarmTime(XmlUtil.getText(rootElement, "AlarmTime")); | |
| 226 | + String alarmTime = XmlUtil.getText(rootElement, "AlarmTime"); | |
| 227 | + if (alarmTime == null) { | |
| 228 | + logger.warn("[ NotifyAlarm ] AlarmTime cannot be null"); | |
| 229 | + return; | |
| 230 | + } | |
| 231 | + deviceAlarm.setAlarmTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(alarmTime)); | |
| 199 | 232 | if (XmlUtil.getText(rootElement, "AlarmDescription") == null) { |
| 200 | 233 | deviceAlarm.setAlarmDescription(""); |
| 201 | 234 | } else { |
| ... | ... | @@ -212,7 +245,7 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements |
| 212 | 245 | deviceAlarm.setLatitude(0.00); |
| 213 | 246 | } |
| 214 | 247 | logger.info("[收到Notify-Alarm]:{}/{}", device.getDeviceId(), deviceAlarm.getChannelId()); |
| 215 | - if (deviceAlarm.getAlarmMethod().equals("4")) { | |
| 248 | + if ("4".equals(deviceAlarm.getAlarmMethod())) { | |
| 216 | 249 | MobilePosition mobilePosition = new MobilePosition(); |
| 217 | 250 | mobilePosition.setDeviceId(deviceAlarm.getDeviceId()); |
| 218 | 251 | mobilePosition.setTime(deviceAlarm.getAlarmTime()); |
| ... | ... | @@ -233,11 +266,10 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements |
| 233 | 266 | // TODO: 需要实现存储报警信息、报警分类 |
| 234 | 267 | |
| 235 | 268 | // 回复200 OK |
| 236 | - responseAck(evt, Response.OK); | |
| 237 | 269 | if (redisCatchStorage.deviceIsOnline(deviceId)) { |
| 238 | 270 | publisher.deviceAlarmEventPublish(deviceAlarm); |
| 239 | 271 | } |
| 240 | - } catch (DocumentException | SipException | InvalidArgumentException | ParseException e) { | |
| 272 | + } catch (DocumentException e) { | |
| 241 | 273 | e.printStackTrace(); |
| 242 | 274 | } |
| 243 | 275 | } |
| ... | ... | @@ -273,64 +305,60 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements |
| 273 | 305 | continue; |
| 274 | 306 | } |
| 275 | 307 | Element eventElement = itemDevice.element("Event"); |
| 276 | - DeviceChannel channel = XmlUtil.channelContentHander(itemDevice); | |
| 308 | + String event; | |
| 309 | + if (eventElement == null) { | |
| 310 | + logger.warn("[收到 目录订阅]:{}, 但是Event为空, 设为默认值 ADD", (device != null ? device.getDeviceId():"" )); | |
| 311 | + event = CatalogEvent.ADD; | |
| 312 | + }else { | |
| 313 | + event = eventElement.getText().toUpperCase(); | |
| 314 | + } | |
| 315 | + DeviceChannel channel = XmlUtil.channelContentHander(itemDevice, device); | |
| 277 | 316 | channel.setDeviceId(device.getDeviceId()); |
| 278 | 317 | logger.info("[收到 目录订阅]:{}/{}", device.getDeviceId(), channel.getChannelId()); |
| 279 | - switch (eventElement.getText().toUpperCase()) { | |
| 318 | + switch (event) { | |
| 280 | 319 | case CatalogEvent.ON: |
| 281 | 320 | // 上线 |
| 282 | 321 | logger.info("收到来自设备【{}】的通道【{}】上线通知", device.getDeviceId(), channel.getChannelId()); |
| 283 | 322 | storager.deviceChannelOnline(deviceId, channel.getChannelId()); |
| 284 | - // 回复200 OK | |
| 285 | - responseAck(evt, Response.OK); | |
| 286 | 323 | break; |
| 287 | 324 | case CatalogEvent.OFF : |
| 288 | 325 | // 离线 |
| 289 | 326 | logger.info("收到来自设备【{}】的通道【{}】离线通知", device.getDeviceId(), channel.getChannelId()); |
| 290 | 327 | storager.deviceChannelOffline(deviceId, channel.getChannelId()); |
| 291 | - // 回复200 OK | |
| 292 | - responseAck(evt, Response.OK); | |
| 293 | 328 | break; |
| 294 | 329 | case CatalogEvent.VLOST: |
| 295 | 330 | // 视频丢失 |
| 296 | 331 | logger.info("收到来自设备【{}】的通道【{}】视频丢失通知", device.getDeviceId(), channel.getChannelId()); |
| 297 | 332 | storager.deviceChannelOffline(deviceId, channel.getChannelId()); |
| 298 | - // 回复200 OK | |
| 299 | - responseAck(evt, Response.OK); | |
| 300 | 333 | break; |
| 301 | 334 | case CatalogEvent.DEFECT: |
| 302 | 335 | // 故障 |
| 303 | - // 回复200 OK | |
| 304 | - responseAck(evt, Response.OK); | |
| 305 | 336 | break; |
| 306 | 337 | case CatalogEvent.ADD: |
| 307 | 338 | // 增加 |
| 308 | 339 | logger.info("收到来自设备【{}】的增加通道【{}】通知", device.getDeviceId(), channel.getChannelId()); |
| 309 | 340 | storager.updateChannel(deviceId, channel); |
| 310 | - responseAck(evt, Response.OK); | |
| 311 | 341 | break; |
| 312 | 342 | case CatalogEvent.DEL: |
| 313 | 343 | // 删除 |
| 314 | 344 | logger.info("收到来自设备【{}】的删除通道【{}】通知", device.getDeviceId(), channel.getChannelId()); |
| 315 | 345 | storager.delChannel(deviceId, channel.getChannelId()); |
| 316 | - responseAck(evt, Response.OK); | |
| 317 | 346 | break; |
| 318 | 347 | case CatalogEvent.UPDATE: |
| 319 | 348 | // 更新 |
| 320 | 349 | logger.info("收到来自设备【{}】的更新通道【{}】通知", device.getDeviceId(), channel.getChannelId()); |
| 321 | 350 | storager.updateChannel(deviceId, channel); |
| 322 | - responseAck(evt, Response.OK); | |
| 323 | 351 | break; |
| 324 | 352 | default: |
| 325 | - responseAck(evt, Response.BAD_REQUEST, "event not found"); | |
| 353 | + logger.warn("[ NotifyCatalog ] event not found : {}", event ); | |
| 326 | 354 | |
| 327 | 355 | } |
| 328 | 356 | // 转发变化信息 |
| 329 | - eventPublisher.catalogEventPublish(null, channel, eventElement.getText().toUpperCase()); | |
| 357 | + eventPublisher.catalogEventPublish(null, channel, event); | |
| 330 | 358 | |
| 331 | 359 | } |
| 332 | 360 | } |
| 333 | - } catch (DocumentException | SipException | InvalidArgumentException | ParseException e) { | |
| 361 | + } catch (DocumentException e) { | |
| 334 | 362 | e.printStackTrace(); |
| 335 | 363 | } |
| 336 | 364 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/RegisterRequestProcessor.java
| 1 | 1 | package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl; |
| 2 | 2 | |
| 3 | -import com.genersoft.iot.vmp.common.VideoManagerConstants; | |
| 4 | 3 | import com.genersoft.iot.vmp.conf.SipConfig; |
| 5 | -import com.genersoft.iot.vmp.gb28181.auth.DigestServerAuthenticationHelper; | |
| 6 | 4 | import com.genersoft.iot.vmp.gb28181.bean.Device; |
| 7 | 5 | import com.genersoft.iot.vmp.gb28181.bean.WvpSipDate; |
| 8 | -import com.genersoft.iot.vmp.gb28181.event.EventPublisher; | |
| 9 | 6 | import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver; |
| 10 | 7 | import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor; |
| 11 | 8 | import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; |
| 9 | +import com.genersoft.iot.vmp.gb28181.auth.DigestServerAuthenticationHelper; | |
| 12 | 10 | import com.genersoft.iot.vmp.service.IDeviceService; |
| 13 | -import com.genersoft.iot.vmp.storager.IRedisCatchStorage; | |
| 14 | -import com.genersoft.iot.vmp.storager.IVideoManagerStorage; | |
| 15 | 11 | import com.genersoft.iot.vmp.utils.DateUtil; |
| 16 | 12 | import gov.nist.javax.sip.RequestEventExt; |
| 17 | 13 | import gov.nist.javax.sip.address.AddressImpl; |
| ... | ... | @@ -45,21 +41,12 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen |
| 45 | 41 | |
| 46 | 42 | private final Logger logger = LoggerFactory.getLogger(RegisterRequestProcessor.class); |
| 47 | 43 | |
| 48 | - public String method = "REGISTER"; | |
| 44 | + public final String method = "REGISTER"; | |
| 49 | 45 | |
| 50 | 46 | @Autowired |
| 51 | 47 | private SipConfig sipConfig; |
| 52 | 48 | |
| 53 | 49 | @Autowired |
| 54 | - private IRedisCatchStorage redisCatchStorage; | |
| 55 | - | |
| 56 | - @Autowired | |
| 57 | - private IVideoManagerStorage storager; | |
| 58 | - | |
| 59 | - @Autowired | |
| 60 | - private EventPublisher publisher; | |
| 61 | - | |
| 62 | - @Autowired | |
| 63 | 50 | private SIPProcessorObserver sipProcessorObserver; |
| 64 | 51 | |
| 65 | 52 | @Autowired |
| ... | ... | @@ -86,7 +73,7 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen |
| 86 | 73 | ExpiresHeader expiresHeader = (ExpiresHeader) request.getHeader(Expires.NAME); |
| 87 | 74 | Response response = null; |
| 88 | 75 | boolean passwordCorrect = false; |
| 89 | - // 注册标志 0:未携带授权头或者密码错误 1:注册成功 2:注销成功 | |
| 76 | + // 注册标志 | |
| 90 | 77 | boolean registerFlag = false; |
| 91 | 78 | FromHeader fromHeader = (FromHeader) request.getHeader(FromHeader.NAME); |
| 92 | 79 | AddressImpl address = (AddressImpl) fromHeader.getAddress(); |
| ... | ... | @@ -105,7 +92,6 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen |
| 105 | 92 | // 校验密码是否正确 |
| 106 | 93 | passwordCorrect = StringUtils.isEmpty(sipConfig.getPassword()) || |
| 107 | 94 | new DigestServerAuthenticationHelper().doAuthenticatePlainTextPassword(request, sipConfig.getPassword()); |
| 108 | - // 未携带授权头或者密码错误 回复401 | |
| 109 | 95 | |
| 110 | 96 | if (!passwordCorrect) { |
| 111 | 97 | // 注册失败 |
| ... | ... | @@ -154,6 +140,7 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen |
| 154 | 140 | device = new Device(); |
| 155 | 141 | device.setStreamMode("UDP"); |
| 156 | 142 | device.setCharset("GB2312"); |
| 143 | + device.setGeoCoordSys("WGS84"); | |
| 157 | 144 | device.setDeviceId(deviceId); |
| 158 | 145 | } |
| 159 | 146 | device.setIp(received); | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/SubscribeRequestProcessor.java
| ... | ... | @@ -82,7 +82,6 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme |
| 82 | 82 | @Override |
| 83 | 83 | public void process(RequestEvent evt) { |
| 84 | 84 | Request request = evt.getRequest(); |
| 85 | - | |
| 86 | 85 | try { |
| 87 | 86 | Element rootElement = getRootElement(evt); |
| 88 | 87 | String cmd = XmlUtil.getText(rootElement, "CmdType"); |
| ... | ... | @@ -176,6 +175,8 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme |
| 176 | 175 | } |
| 177 | 176 | |
| 178 | 177 | private void processNotifyCatalogList(RequestEvent evt, Element rootElement) throws SipException { |
| 178 | + | |
| 179 | + System.out.println(evt.getRequest().toString()); | |
| 179 | 180 | String platformId = SipUtils.getUserIdFromFromHeader(evt.getRequest()); |
| 180 | 181 | String deviceId = XmlUtil.getText(rootElement, "DeviceID"); |
| 181 | 182 | ParentPlatform platform = storager.queryParentPlatByServerGBId(platformId); | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/info/InfoRequestProcessor.java
0 → 100644
| 1 | +package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.info; | |
| 2 | + | |
| 3 | +import com.genersoft.iot.vmp.common.StreamInfo; | |
| 4 | +import com.genersoft.iot.vmp.gb28181.bean.*; | |
| 5 | +import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; | |
| 6 | +import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; | |
| 7 | +import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver; | |
| 8 | +import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; | |
| 9 | +import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor; | |
| 10 | +import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; | |
| 11 | +import com.genersoft.iot.vmp.gb28181.utils.SipUtils; | |
| 12 | +import com.genersoft.iot.vmp.storager.IRedisCatchStorage; | |
| 13 | +import com.genersoft.iot.vmp.storager.IVideoManagerStorage; | |
| 14 | +import gov.nist.javax.sip.message.SIPRequest; | |
| 15 | +import org.slf4j.Logger; | |
| 16 | +import org.slf4j.LoggerFactory; | |
| 17 | +import org.springframework.beans.factory.InitializingBean; | |
| 18 | +import org.springframework.beans.factory.annotation.Autowired; | |
| 19 | +import org.springframework.stereotype.Component; | |
| 20 | +import javax.sip.InvalidArgumentException; | |
| 21 | +import javax.sip.RequestEvent; | |
| 22 | +import javax.sip.SipException; | |
| 23 | +import javax.sip.header.*; | |
| 24 | +import javax.sip.message.Response; | |
| 25 | +import java.text.ParseException; | |
| 26 | + | |
| 27 | +@Component | |
| 28 | +public class InfoRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor { | |
| 29 | + | |
| 30 | + private final static Logger logger = LoggerFactory.getLogger(InfoRequestProcessor.class); | |
| 31 | + | |
| 32 | + private final String method = "INFO"; | |
| 33 | + | |
| 34 | + @Autowired | |
| 35 | + private SIPProcessorObserver sipProcessorObserver; | |
| 36 | + | |
| 37 | + @Autowired | |
| 38 | + private IVideoManagerStorage storage; | |
| 39 | + | |
| 40 | + @Autowired | |
| 41 | + private SipSubscribe sipSubscribe; | |
| 42 | + | |
| 43 | + @Autowired | |
| 44 | + private IRedisCatchStorage redisCatchStorage; | |
| 45 | + | |
| 46 | + @Autowired | |
| 47 | + private IVideoManagerStorage storager; | |
| 48 | + | |
| 49 | + @Autowired | |
| 50 | + private SIPCommander cmder; | |
| 51 | + | |
| 52 | + @Autowired | |
| 53 | + private VideoStreamSessionManager sessionManager; | |
| 54 | + | |
| 55 | + @Override | |
| 56 | + public void afterPropertiesSet() throws Exception { | |
| 57 | + // 添加消息处理的订阅 | |
| 58 | + sipProcessorObserver.addRequestProcessor(method, this); | |
| 59 | + } | |
| 60 | + | |
| 61 | + @Override | |
| 62 | + public void process(RequestEvent evt) { | |
| 63 | + logger.debug("接收到消息:" + evt.getRequest()); | |
| 64 | + String deviceId = SipUtils.getUserIdFromFromHeader(evt.getRequest()); | |
| 65 | + CallIdHeader callIdHeader = (CallIdHeader)evt.getRequest().getHeader(CallIdHeader.NAME); | |
| 66 | + // 先从会话内查找 | |
| 67 | + SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransaction(null, null, callIdHeader.getCallId(), null); | |
| 68 | + if (ssrcTransaction != null) { // 兼容海康 媒体通知 消息from字段不是设备ID的问题 | |
| 69 | + deviceId = ssrcTransaction.getDeviceId(); | |
| 70 | + } | |
| 71 | + // 查询设备是否存在 | |
| 72 | + Device device = redisCatchStorage.getDevice(deviceId); | |
| 73 | + // 查询上级平台是否存在 | |
| 74 | + ParentPlatform parentPlatform = storage.queryParentPlatByServerGBId(deviceId); | |
| 75 | + try { | |
| 76 | + if (device != null && parentPlatform != null) { | |
| 77 | + logger.warn("[重复]平台与设备编号重复:{}", deviceId); | |
| 78 | + SIPRequest request = (SIPRequest) evt.getRequest(); | |
| 79 | + String hostAddress = request.getRemoteAddress().getHostAddress(); | |
| 80 | + int remotePort = request.getRemotePort(); | |
| 81 | + if (device.getHostAddress().equals(hostAddress + ":" + remotePort)) { | |
| 82 | + parentPlatform = null; | |
| 83 | + }else { | |
| 84 | + device = null; | |
| 85 | + } | |
| 86 | + } | |
| 87 | + if (device == null && parentPlatform == null) { | |
| 88 | + // 不存在则回复404 | |
| 89 | + responseAck(evt, Response.NOT_FOUND, "device "+ deviceId +" not found"); | |
| 90 | + logger.warn("[设备未找到 ]: {}", deviceId); | |
| 91 | + if (sipSubscribe.getErrorSubscribe(callIdHeader.getCallId()) != null){ | |
| 92 | + SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(new DeviceNotFoundEvent(evt.getDialog())); | |
| 93 | + sipSubscribe.getErrorSubscribe(callIdHeader.getCallId()).response(eventResult); | |
| 94 | + }; | |
| 95 | + }else { | |
| 96 | + ContentTypeHeader header = (ContentTypeHeader)evt.getRequest().getHeader(ContentTypeHeader.NAME); | |
| 97 | + String contentType = header.getContentType(); | |
| 98 | + String contentSubType = header.getContentSubType(); | |
| 99 | + if ("Application".equals(contentType) && "MANSRTSP".equals(contentSubType)) { | |
| 100 | + SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(null, null, null, callIdHeader.getCallId()); | |
| 101 | + String streamId = sendRtpItem.getStreamId(); | |
| 102 | + StreamInfo streamInfo = redisCatchStorage.queryPlayback(null, null, streamId, null); | |
| 103 | + if (null == streamInfo) { | |
| 104 | + responseAck(evt, Response.NOT_FOUND, "stream " + streamId + " not found"); | |
| 105 | + return; | |
| 106 | + } | |
| 107 | + Device device1 = storager.queryVideoDevice(streamInfo.getDeviceID()); | |
| 108 | + cmder.playbackControlCmd(device1,streamInfo,new String(evt.getRequest().getRawContent()),eventResult -> { | |
| 109 | + // 失败的回复 | |
| 110 | + try { | |
| 111 | + responseAck(evt, eventResult.statusCode, eventResult.msg); | |
| 112 | + } catch (SipException e) { | |
| 113 | + e.printStackTrace(); | |
| 114 | + } catch (InvalidArgumentException e) { | |
| 115 | + e.printStackTrace(); | |
| 116 | + } catch (ParseException e) { | |
| 117 | + e.printStackTrace(); | |
| 118 | + } | |
| 119 | + }, eventResult -> { | |
| 120 | + // 成功的回复 | |
| 121 | + try { | |
| 122 | + responseAck(evt, eventResult.statusCode); | |
| 123 | + } catch (SipException e) { | |
| 124 | + e.printStackTrace(); | |
| 125 | + } catch (InvalidArgumentException e) { | |
| 126 | + e.printStackTrace(); | |
| 127 | + } catch (ParseException e) { | |
| 128 | + e.printStackTrace(); | |
| 129 | + } | |
| 130 | + }); | |
| 131 | + } | |
| 132 | + } | |
| 133 | + } catch (SipException e) { | |
| 134 | + logger.warn("SIP 回复错误", e); | |
| 135 | + } catch (InvalidArgumentException e) { | |
| 136 | + logger.warn("参数无效", e); | |
| 137 | + } catch (ParseException e) { | |
| 138 | + logger.warn("SIP回复时解析异常", e); | |
| 139 | + } | |
| 140 | + } | |
| 141 | + | |
| 142 | + | |
| 143 | +} | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/MessageRequestProcessor.java
| ... | ... | @@ -72,7 +72,7 @@ public class MessageRequestProcessor extends SIPRequestProcessorParent implement |
| 72 | 72 | String deviceId = SipUtils.getUserIdFromFromHeader(evt.getRequest()); |
| 73 | 73 | CallIdHeader callIdHeader = (CallIdHeader)evt.getRequest().getHeader(CallIdHeader.NAME); |
| 74 | 74 | // 先从会话内查找 |
| 75 | - SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransaction(null, null, null, callIdHeader.getCallId()); | |
| 75 | + SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransaction(null, null, callIdHeader.getCallId(), null); | |
| 76 | 76 | if (ssrcTransaction != null) { // 兼容海康 媒体通知 消息from字段不是设备ID的问题 |
| 77 | 77 | deviceId = ssrcTransaction.getDeviceId(); |
| 78 | 78 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/AlarmNotifyMessageHandler.java
| ... | ... | @@ -9,9 +9,11 @@ import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessag |
| 9 | 9 | import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.NotifyMessageHandler; |
| 10 | 10 | import com.genersoft.iot.vmp.gb28181.utils.Coordtransform; |
| 11 | 11 | import com.genersoft.iot.vmp.gb28181.utils.NumericUtil; |
| 12 | +import com.genersoft.iot.vmp.gb28181.utils.XmlUtil; | |
| 12 | 13 | import com.genersoft.iot.vmp.service.IDeviceAlarmService; |
| 13 | 14 | import com.genersoft.iot.vmp.storager.IRedisCatchStorage; |
| 14 | 15 | import com.genersoft.iot.vmp.storager.IVideoManagerStorage; |
| 16 | +import com.genersoft.iot.vmp.utils.DateUtil; | |
| 15 | 17 | import org.dom4j.Element; |
| 16 | 18 | import org.slf4j.Logger; |
| 17 | 19 | import org.slf4j.LoggerFactory; |
| ... | ... | @@ -84,7 +86,11 @@ public class AlarmNotifyMessageHandler extends SIPRequestProcessorParent impleme |
| 84 | 86 | deviceAlarm.setChannelId(channelId); |
| 85 | 87 | deviceAlarm.setAlarmPriority(getText(rootElement, "AlarmPriority")); |
| 86 | 88 | deviceAlarm.setAlarmMethod(getText(rootElement, "AlarmMethod")); |
| 87 | - deviceAlarm.setAlarmTime(getText(rootElement, "AlarmTime")); | |
| 89 | + String alarmTime = XmlUtil.getText(rootElement, "AlarmTime"); | |
| 90 | + if (alarmTime == null) { | |
| 91 | + return; | |
| 92 | + } | |
| 93 | + deviceAlarm.setAlarmTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(alarmTime)); | |
| 88 | 94 | String alarmDescription = getText(rootElement, "AlarmDescription"); |
| 89 | 95 | if (alarmDescription == null) { |
| 90 | 96 | deviceAlarm.setAlarmDescription(""); |
| ... | ... | @@ -175,7 +181,11 @@ public class AlarmNotifyMessageHandler extends SIPRequestProcessorParent impleme |
| 175 | 181 | deviceAlarm.setChannelId(channelId); |
| 176 | 182 | deviceAlarm.setAlarmPriority(getText(rootElement, "AlarmPriority")); |
| 177 | 183 | deviceAlarm.setAlarmMethod(getText(rootElement, "AlarmMethod")); |
| 178 | - deviceAlarm.setAlarmTime(getText(rootElement, "AlarmTime")); | |
| 184 | + String alarmTime = XmlUtil.getText(rootElement, "AlarmTime"); | |
| 185 | + if (alarmTime == null) { | |
| 186 | + return; | |
| 187 | + } | |
| 188 | + deviceAlarm.setAlarmTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(alarmTime)); | |
| 179 | 189 | String alarmDescription = getText(rootElement, "AlarmDescription"); |
| 180 | 190 | if (alarmDescription == null) { |
| 181 | 191 | deviceAlarm.setAlarmDescription(""); | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/KeepaliveNotifyMessageHandler.java
| ... | ... | @@ -64,16 +64,14 @@ public class KeepaliveNotifyMessageHandler extends SIPRequestProcessorParent imp |
| 64 | 64 | device.setHostAddress(received.concat(":").concat(String.valueOf(rPort))); |
| 65 | 65 | } |
| 66 | 66 | device.setKeepaliveTime(DateUtil.getNow()); |
| 67 | + // 回复200 OK | |
| 68 | + responseAck(evt, Response.OK); | |
| 67 | 69 | if (device.getOnline() == 1) { |
| 68 | - // 回复200 OK | |
| 69 | - responseAck(evt, Response.OK); | |
| 70 | 70 | deviceService.updateDevice(device); |
| 71 | 71 | }else { |
| 72 | 72 | // 对于已经离线的设备判断他的注册是否已经过期 |
| 73 | 73 | if (!deviceService.expire(device)){ |
| 74 | 74 | deviceService.online(device); |
| 75 | - // 回复200 OK | |
| 76 | - responseAck(evt, Response.OK); | |
| 77 | 75 | } |
| 78 | 76 | } |
| 79 | 77 | } catch (SipException e) { | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/MediaStatusNotifyMessageHandler.java
| ... | ... | @@ -3,11 +3,16 @@ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify |
| 3 | 3 | import com.genersoft.iot.vmp.common.StreamInfo; |
| 4 | 4 | import com.genersoft.iot.vmp.gb28181.bean.Device; |
| 5 | 5 | import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; |
| 6 | +import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem; | |
| 7 | +import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction; | |
| 8 | +import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; | |
| 6 | 9 | import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; |
| 10 | +import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform; | |
| 7 | 11 | import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; |
| 8 | 12 | import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler; |
| 9 | 13 | import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.NotifyMessageHandler; |
| 10 | 14 | import com.genersoft.iot.vmp.storager.IRedisCatchStorage; |
| 15 | +import com.genersoft.iot.vmp.storager.IVideoManagerStorage; | |
| 11 | 16 | import org.dom4j.Element; |
| 12 | 17 | import org.slf4j.Logger; |
| 13 | 18 | import org.slf4j.LoggerFactory; |
| ... | ... | @@ -37,8 +42,17 @@ public class MediaStatusNotifyMessageHandler extends SIPRequestProcessorParent i |
| 37 | 42 | private SIPCommander cmder; |
| 38 | 43 | |
| 39 | 44 | @Autowired |
| 45 | + private SIPCommanderFroPlatform sipCommanderFroPlatform; | |
| 46 | + | |
| 47 | + @Autowired | |
| 40 | 48 | private IRedisCatchStorage redisCatchStorage; |
| 41 | 49 | |
| 50 | + @Autowired | |
| 51 | + private IVideoManagerStorage storage; | |
| 52 | + | |
| 53 | + @Autowired | |
| 54 | + private VideoStreamSessionManager sessionManager; | |
| 55 | + | |
| 42 | 56 | @Override |
| 43 | 57 | public void afterPropertiesSet() throws Exception { |
| 44 | 58 | notifyMessageHandler.addHandler(cmdType, this); |
| ... | ... | @@ -59,17 +73,32 @@ public class MediaStatusNotifyMessageHandler extends SIPRequestProcessorParent i |
| 59 | 73 | } |
| 60 | 74 | CallIdHeader callIdHeader = (CallIdHeader)evt.getRequest().getHeader(CallIdHeader.NAME); |
| 61 | 75 | String NotifyType =getText(rootElement, "NotifyType"); |
| 62 | - if (NotifyType.equals("121")){ | |
| 76 | + if ("121".equals(NotifyType)){ | |
| 63 | 77 | logger.info("[录像流]推送完毕,收到关流通知"); |
| 64 | - String channelId =getText(rootElement, "DeviceID"); | |
| 65 | 78 | // 查询是设备 |
| 66 | - StreamInfo streamInfo = redisCatchStorage.queryDownload(device.getDeviceId(), channelId, null, callIdHeader.getCallId()); | |
| 67 | - // 设置进度100% | |
| 68 | - streamInfo.setProgress(1); | |
| 69 | - redisCatchStorage.startDownload(streamInfo, callIdHeader.getCallId()); | |
| 70 | - cmder.streamByeCmd(device.getDeviceId(), channelId, null, callIdHeader.getCallId()); | |
| 71 | - // TODO 如果级联播放,需要给上级发送此通知 | |
| 79 | + StreamInfo streamInfo = redisCatchStorage.queryDownload(null, null, null, callIdHeader.getCallId()); | |
| 80 | + if (streamInfo != null) { | |
| 81 | + // 设置进度100% | |
| 82 | + streamInfo.setProgress(1); | |
| 83 | + redisCatchStorage.startDownload(streamInfo, callIdHeader.getCallId()); | |
| 84 | + } | |
| 85 | + | |
| 86 | + // 先从会话内查找 | |
| 87 | + SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransaction(null, null, callIdHeader.getCallId(), null); | |
| 88 | + if (ssrcTransaction != null) { // 兼容海康 媒体通知 消息from字段不是设备ID的问题 | |
| 89 | + cmder.streamByeCmd(device.getDeviceId(), ssrcTransaction.getChannelId(), null, callIdHeader.getCallId()); | |
| 72 | 90 | |
| 91 | + // 如果级联播放,需要给上级发送此通知 TODO 多个上级同时观看一个下级 可能存在停错的问题,需要将点播CallId进行上下级绑定 | |
| 92 | + SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(null, ssrcTransaction.getChannelId(), null, null); | |
| 93 | + if (sendRtpItem != null) { | |
| 94 | + ParentPlatform parentPlatform = storage.queryParentPlatByServerGBId(sendRtpItem.getPlatformId()); | |
| 95 | + if (parentPlatform == null) { | |
| 96 | + logger.warn("[级联消息发送]:发送MediaStatus发现上级平台{}不存在", sendRtpItem.getPlatformId()); | |
| 97 | + return; | |
| 98 | + } | |
| 99 | + sipCommanderFroPlatform.sendMediaStatusNotify(parentPlatform, sendRtpItem); | |
| 100 | + } | |
| 101 | + } | |
| 73 | 102 | } |
| 74 | 103 | } |
| 75 | 104 | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/CatalogResponseMessageHandler.java
| ... | ... | @@ -20,6 +20,8 @@ import org.slf4j.Logger; |
| 20 | 20 | import org.slf4j.LoggerFactory; |
| 21 | 21 | import org.springframework.beans.factory.InitializingBean; |
| 22 | 22 | import org.springframework.beans.factory.annotation.Autowired; |
| 23 | +import org.springframework.beans.factory.annotation.Qualifier; | |
| 24 | +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; | |
| 23 | 25 | import org.springframework.stereotype.Component; |
| 24 | 26 | import org.springframework.util.StringUtils; |
| 25 | 27 | |
| ... | ... | @@ -31,6 +33,7 @@ import java.text.ParseException; |
| 31 | 33 | import java.util.ArrayList; |
| 32 | 34 | import java.util.Iterator; |
| 33 | 35 | import java.util.List; |
| 36 | +import java.util.concurrent.ConcurrentLinkedQueue; | |
| 34 | 37 | |
| 35 | 38 | @Component |
| 36 | 39 | public class CatalogResponseMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler { |
| ... | ... | @@ -38,9 +41,13 @@ public class CatalogResponseMessageHandler extends SIPRequestProcessorParent imp |
| 38 | 41 | private Logger logger = LoggerFactory.getLogger(CatalogResponseMessageHandler.class); |
| 39 | 42 | private final String cmdType = "Catalog"; |
| 40 | 43 | |
| 44 | + private boolean taskQueueHandlerRun = false; | |
| 45 | + | |
| 41 | 46 | @Autowired |
| 42 | 47 | private ResponseMessageHandler responseMessageHandler; |
| 43 | 48 | |
| 49 | + private ConcurrentLinkedQueue<HandlerCatchData> taskQueue = new ConcurrentLinkedQueue<>(); | |
| 50 | + | |
| 44 | 51 | @Autowired |
| 45 | 52 | private IVideoManagerStorage storager; |
| 46 | 53 | |
| ... | ... | @@ -63,6 +70,10 @@ public class CatalogResponseMessageHandler extends SIPRequestProcessorParent imp |
| 63 | 70 | @Autowired |
| 64 | 71 | private IRedisCatchStorage redisCatchStorage; |
| 65 | 72 | |
| 73 | + @Qualifier("taskExecutor") | |
| 74 | + @Autowired | |
| 75 | + private ThreadPoolTaskExecutor taskExecutor; | |
| 76 | + | |
| 66 | 77 | @Override |
| 67 | 78 | public void afterPropertiesSet() throws Exception { |
| 68 | 79 | responseMessageHandler.addHandler(cmdType, this); |
| ... | ... | @@ -70,68 +81,88 @@ public class CatalogResponseMessageHandler extends SIPRequestProcessorParent imp |
| 70 | 81 | |
| 71 | 82 | @Override |
| 72 | 83 | public void handForDevice(RequestEvent evt, Device device, Element element) { |
| 73 | - String key = DeferredResultHolder.CALLBACK_CMD_CATALOG + device.getDeviceId(); | |
| 74 | - Element rootElement = null; | |
| 84 | + taskQueue.offer(new HandlerCatchData(evt, device, element)); | |
| 85 | + // 回复200 OK | |
| 75 | 86 | try { |
| 76 | - rootElement = getRootElement(evt, device.getCharset()); | |
| 77 | - Element deviceListElement = rootElement.element("DeviceList"); | |
| 78 | - Element sumNumElement = rootElement.element("SumNum"); | |
| 79 | - Element snElement = rootElement.element("SN"); | |
| 80 | - if (snElement == null || sumNumElement == null || deviceListElement == null) { | |
| 81 | - responseAck(evt, Response.BAD_REQUEST, "xml error"); | |
| 82 | - return; | |
| 83 | - } | |
| 84 | - int sumNum = Integer.parseInt(sumNumElement.getText()); | |
| 85 | - | |
| 86 | - if (sumNum == 0) { | |
| 87 | - // 数据已经完整接收 | |
| 88 | - storager.cleanChannelsForDevice(device.getDeviceId()); | |
| 89 | - catalogDataCatch.setChannelSyncEnd(device.getDeviceId(), null); | |
| 90 | - }else { | |
| 91 | - Iterator<Element> deviceListIterator = deviceListElement.elementIterator(); | |
| 92 | - if (deviceListIterator != null) { | |
| 93 | - List<DeviceChannel> channelList = new ArrayList<>(); | |
| 94 | - // 遍历DeviceList | |
| 95 | - while (deviceListIterator.hasNext()) { | |
| 96 | - Element itemDevice = deviceListIterator.next(); | |
| 97 | - Element channelDeviceElement = itemDevice.element("DeviceID"); | |
| 98 | - if (channelDeviceElement == null) { | |
| 99 | - continue; | |
| 87 | + responseAck(evt, Response.OK); | |
| 88 | + } catch (SipException e) { | |
| 89 | + throw new RuntimeException(e); | |
| 90 | + } catch (InvalidArgumentException e) { | |
| 91 | + throw new RuntimeException(e); | |
| 92 | + } catch (ParseException e) { | |
| 93 | + throw new RuntimeException(e); | |
| 94 | + } | |
| 95 | + if (!taskQueueHandlerRun) { | |
| 96 | + taskQueueHandlerRun = true; | |
| 97 | + taskExecutor.execute(()-> { | |
| 98 | + while (!taskQueue.isEmpty()) { | |
| 99 | + HandlerCatchData take = taskQueue.poll(); | |
| 100 | + String key = DeferredResultHolder.CALLBACK_CMD_CATALOG + take.getDevice().getDeviceId(); | |
| 101 | + Element rootElement = null; | |
| 102 | + try { | |
| 103 | + rootElement = getRootElement(take.getEvt(), take.getDevice().getCharset()); | |
| 104 | + Element deviceListElement = rootElement.element("DeviceList"); | |
| 105 | + Element sumNumElement = rootElement.element("SumNum"); | |
| 106 | + Element snElement = rootElement.element("SN"); | |
| 107 | + if (snElement == null || sumNumElement == null || deviceListElement == null) { | |
| 108 | + responseAck(take.getEvt(), Response.BAD_REQUEST, "xml error"); | |
| 109 | + return; | |
| 100 | 110 | } |
| 101 | - //by brewswang | |
| 102 | -// if (NumericUtil.isDouble(XmlUtil.getText(itemDevice, "Longitude"))) {//如果包含位置信息,就更新一下位置 | |
| 103 | -// processNotifyMobilePosition(evt, itemDevice); | |
| 104 | -// } | |
| 105 | - DeviceChannel deviceChannel = XmlUtil.channelContentHander(itemDevice); | |
| 106 | - deviceChannel.setDeviceId(device.getDeviceId()); | |
| 107 | - | |
| 108 | - channelList.add(deviceChannel); | |
| 109 | - } | |
| 110 | - int sn = Integer.parseInt(snElement.getText()); | |
| 111 | - catalogDataCatch.put(device.getDeviceId(), sn, sumNum, device, channelList); | |
| 112 | - logger.info("收到来自设备【{}】的通道: {}个,{}/{}", device.getDeviceId(), channelList.size(), catalogDataCatch.get(device.getDeviceId()) == null ? 0 :catalogDataCatch.get(device.getDeviceId()).size(), sumNum); | |
| 113 | - if (catalogDataCatch.get(device.getDeviceId()).size() == sumNum) { | |
| 114 | - // 数据已经完整接收 | |
| 115 | - boolean resetChannelsResult = storager.resetChannels(device.getDeviceId(), catalogDataCatch.get(device.getDeviceId())); | |
| 116 | - if (!resetChannelsResult) { | |
| 117 | - String errorMsg = "接收成功,写入失败,共" + sumNum + "条,已接收" + catalogDataCatch.get(device.getDeviceId()).size() + "条"; | |
| 118 | - catalogDataCatch.setChannelSyncEnd(device.getDeviceId(), errorMsg); | |
| 111 | + int sumNum = Integer.parseInt(sumNumElement.getText()); | |
| 112 | + | |
| 113 | + if (sumNum == 0) { | |
| 114 | + // 数据已经完整接收 | |
| 115 | + storager.cleanChannelsForDevice(take.getDevice().getDeviceId()); | |
| 116 | + catalogDataCatch.setChannelSyncEnd(take.getDevice().getDeviceId(), null); | |
| 119 | 117 | }else { |
| 120 | - catalogDataCatch.setChannelSyncEnd(device.getDeviceId(), null); | |
| 118 | + Iterator<Element> deviceListIterator = deviceListElement.elementIterator(); | |
| 119 | + if (deviceListIterator != null) { | |
| 120 | + List<DeviceChannel> channelList = new ArrayList<>(); | |
| 121 | + // 遍历DeviceList | |
| 122 | + while (deviceListIterator.hasNext()) { | |
| 123 | + Element itemDevice = deviceListIterator.next(); | |
| 124 | + Element channelDeviceElement = itemDevice.element("DeviceID"); | |
| 125 | + if (channelDeviceElement == null) { | |
| 126 | + continue; | |
| 127 | + } | |
| 128 | + //by brewswang | |
| 129 | + // if (NumericUtil.isDouble(XmlUtil.getText(itemDevice, "Longitude"))) {//如果包含位置信息,就更新一下位置 | |
| 130 | + // processNotifyMobilePosition(evt, itemDevice); | |
| 131 | + // } | |
| 132 | + DeviceChannel deviceChannel = XmlUtil.channelContentHander(itemDevice, device); | |
| 133 | + deviceChannel.setDeviceId(take.getDevice().getDeviceId()); | |
| 134 | + | |
| 135 | + channelList.add(deviceChannel); | |
| 136 | + } | |
| 137 | + int sn = Integer.parseInt(snElement.getText()); | |
| 138 | + catalogDataCatch.put(take.getDevice().getDeviceId(), sn, sumNum, take.getDevice(), channelList); | |
| 139 | + logger.info("收到来自设备【{}】的通道: {}个,{}/{}", take.getDevice().getDeviceId(), channelList.size(), catalogDataCatch.get(take.getDevice().getDeviceId()) == null ? 0 :catalogDataCatch.get(take.getDevice().getDeviceId()).size(), sumNum); | |
| 140 | + if (catalogDataCatch.get(take.getDevice().getDeviceId()).size() == sumNum) { | |
| 141 | + // 数据已经完整接收 | |
| 142 | + boolean resetChannelsResult = storager.resetChannels(take.getDevice().getDeviceId(), catalogDataCatch.get(take.getDevice().getDeviceId())); | |
| 143 | + if (!resetChannelsResult) { | |
| 144 | + String errorMsg = "接收成功,写入失败,共" + sumNum + "条,已接收" + catalogDataCatch.get(take.getDevice().getDeviceId()).size() + "条"; | |
| 145 | + catalogDataCatch.setChannelSyncEnd(take.getDevice().getDeviceId(), errorMsg); | |
| 146 | + }else { | |
| 147 | + catalogDataCatch.setChannelSyncEnd(take.getDevice().getDeviceId(), null); | |
| 148 | + } | |
| 149 | + } | |
| 150 | + } | |
| 151 | + | |
| 121 | 152 | } |
| 153 | + } catch (DocumentException e) { | |
| 154 | + e.printStackTrace(); | |
| 155 | + } catch (InvalidArgumentException e) { | |
| 156 | + e.printStackTrace(); | |
| 157 | + } catch (ParseException e) { | |
| 158 | + e.printStackTrace(); | |
| 159 | + } catch (SipException e) { | |
| 160 | + e.printStackTrace(); | |
| 122 | 161 | } |
| 123 | 162 | } |
| 124 | - // 回复200 OK | |
| 125 | - responseAck(evt, Response.OK); | |
| 126 | - } | |
| 127 | - } catch (DocumentException e) { | |
| 128 | - e.printStackTrace(); | |
| 129 | - } catch (InvalidArgumentException e) { | |
| 130 | - e.printStackTrace(); | |
| 131 | - } catch (ParseException e) { | |
| 132 | - e.printStackTrace(); | |
| 133 | - } catch (SipException e) { | |
| 134 | - e.printStackTrace(); | |
| 163 | + taskQueueHandlerRun = false; | |
| 164 | + }); | |
| 165 | + | |
| 135 | 166 | } |
| 136 | 167 | } |
| 137 | 168 | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/DeviceStatusResponseMessageHandler.java
| ... | ... | @@ -82,7 +82,7 @@ public class DeviceStatusResponseMessageHandler extends SIPRequestProcessorParen |
| 82 | 82 | deviceService.offline(device.getDeviceId()); |
| 83 | 83 | } |
| 84 | 84 | RequestMessage msg = new RequestMessage(); |
| 85 | - msg.setKey(DeferredResultHolder.CALLBACK_CMD_DEVICESTATUS + device.getDeviceId() + channelId); | |
| 85 | + msg.setKey(DeferredResultHolder.CALLBACK_CMD_DEVICESTATUS + device.getDeviceId()); | |
| 86 | 86 | msg.setData(json); |
| 87 | 87 | deferredResultHolder.invokeAllResult(msg); |
| 88 | 88 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/RecordInfoResponseMessageHandler.java
| 1 | 1 | package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd; |
| 2 | 2 | |
| 3 | -import com.genersoft.iot.vmp.gb28181.bean.Device; | |
| 4 | -import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; | |
| 5 | -import com.genersoft.iot.vmp.gb28181.bean.RecordInfo; | |
| 6 | -import com.genersoft.iot.vmp.gb28181.bean.RecordItem; | |
| 3 | +import com.genersoft.iot.vmp.gb28181.bean.*; | |
| 7 | 4 | import com.genersoft.iot.vmp.gb28181.event.EventPublisher; |
| 8 | 5 | import com.genersoft.iot.vmp.gb28181.session.RecordDataCatch; |
| 9 | 6 | import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; |
| ... | ... | @@ -19,6 +16,8 @@ import org.slf4j.Logger; |
| 19 | 16 | import org.slf4j.LoggerFactory; |
| 20 | 17 | import org.springframework.beans.factory.InitializingBean; |
| 21 | 18 | import org.springframework.beans.factory.annotation.Autowired; |
| 19 | +import org.springframework.beans.factory.annotation.Qualifier; | |
| 20 | +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; | |
| 22 | 21 | import org.springframework.stereotype.Component; |
| 23 | 22 | import org.springframework.util.StringUtils; |
| 24 | 23 | |
| ... | ... | @@ -28,6 +27,9 @@ import javax.sip.SipException; |
| 28 | 27 | import javax.sip.message.Response; |
| 29 | 28 | import java.text.ParseException; |
| 30 | 29 | import java.util.*; |
| 30 | +import java.util.concurrent.BlockingQueue; | |
| 31 | +import java.util.concurrent.ConcurrentLinkedQueue; | |
| 32 | +import java.util.concurrent.LinkedBlockingQueue; | |
| 31 | 33 | |
| 32 | 34 | import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText; |
| 33 | 35 | |
| ... | ... | @@ -38,10 +40,11 @@ import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText; |
| 38 | 40 | public class RecordInfoResponseMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler { |
| 39 | 41 | |
| 40 | 42 | private Logger logger = LoggerFactory.getLogger(RecordInfoResponseMessageHandler.class); |
| 41 | - public static volatile List<String> threadNameList = new ArrayList(); | |
| 42 | 43 | private final String cmdType = "RecordInfo"; |
| 43 | - private final static String CACHE_RECORDINFO_KEY = "CACHE_RECORDINFO_"; | |
| 44 | 44 | |
| 45 | + private ConcurrentLinkedQueue<HandlerCatchData> taskQueue = new ConcurrentLinkedQueue<>(); | |
| 46 | + | |
| 47 | + private boolean taskQueueHandlerRun = false; | |
| 45 | 48 | @Autowired |
| 46 | 49 | private ResponseMessageHandler responseMessageHandler; |
| 47 | 50 | |
| ... | ... | @@ -51,11 +54,13 @@ public class RecordInfoResponseMessageHandler extends SIPRequestProcessorParent |
| 51 | 54 | @Autowired |
| 52 | 55 | private DeferredResultHolder deferredResultHolder; |
| 53 | 56 | |
| 54 | - | |
| 55 | - | |
| 56 | 57 | @Autowired |
| 57 | 58 | private EventPublisher eventPublisher; |
| 58 | 59 | |
| 60 | + @Qualifier("taskExecutor") | |
| 61 | + @Autowired | |
| 62 | + private ThreadPoolTaskExecutor taskExecutor; | |
| 63 | + | |
| 59 | 64 | @Override |
| 60 | 65 | public void afterPropertiesSet() throws Exception { |
| 61 | 66 | responseMessageHandler.addHandler(cmdType, this); |
| ... | ... | @@ -67,67 +72,89 @@ public class RecordInfoResponseMessageHandler extends SIPRequestProcessorParent |
| 67 | 72 | // 回复200 OK |
| 68 | 73 | try { |
| 69 | 74 | responseAck(evt, Response.OK); |
| 70 | - | |
| 71 | - rootElement = getRootElement(evt, device.getCharset()); | |
| 72 | - String sn = getText(rootElement, "SN"); | |
| 73 | - | |
| 74 | - String sumNumStr = getText(rootElement, "SumNum"); | |
| 75 | - int sumNum = 0; | |
| 76 | - if (!StringUtils.isEmpty(sumNumStr)) { | |
| 77 | - sumNum = Integer.parseInt(sumNumStr); | |
| 78 | - } | |
| 79 | - Element recordListElement = rootElement.element("RecordList"); | |
| 80 | - if (recordListElement == null || sumNum == 0) { | |
| 81 | - logger.info("无录像数据"); | |
| 82 | - recordDataCatch.put(device.getDeviceId(), sn, sumNum, new ArrayList<>()); | |
| 83 | - releaseRequest(device.getDeviceId(), sn); | |
| 84 | - } else { | |
| 85 | - Iterator<Element> recordListIterator = recordListElement.elementIterator(); | |
| 86 | - if (recordListIterator != null) { | |
| 87 | - List<RecordItem> recordList = new ArrayList<>(); | |
| 88 | - // 遍历DeviceList | |
| 89 | - while (recordListIterator.hasNext()) { | |
| 90 | - Element itemRecord = recordListIterator.next(); | |
| 91 | - Element recordElement = itemRecord.element("DeviceID"); | |
| 92 | - if (recordElement == null) { | |
| 93 | - logger.info("记录为空,下一个..."); | |
| 94 | - continue; | |
| 75 | + taskQueue.offer(new HandlerCatchData(evt, device, rootElement)); | |
| 76 | + if (!taskQueueHandlerRun) { | |
| 77 | + taskQueueHandlerRun = true; | |
| 78 | + taskExecutor.execute(()->{ | |
| 79 | + try { | |
| 80 | + while (!taskQueue.isEmpty()) { | |
| 81 | + HandlerCatchData take = taskQueue.poll(); | |
| 82 | + Element rootElementForCharset = getRootElement(take.getEvt(), take.getDevice().getCharset()); | |
| 83 | + String sn = getText(rootElementForCharset, "SN"); | |
| 84 | + String channelId = getText(rootElementForCharset, "DeviceID"); | |
| 85 | + RecordInfo recordInfo = new RecordInfo(); | |
| 86 | + recordInfo.setChannelId(channelId); | |
| 87 | + recordInfo.setDeviceId(take.getDevice().getDeviceId()); | |
| 88 | + recordInfo.setSn(sn); | |
| 89 | + recordInfo.setName(getText(rootElementForCharset, "Name")); | |
| 90 | + String sumNumStr = getText(rootElementForCharset, "SumNum"); | |
| 91 | + int sumNum = 0; | |
| 92 | + if (!StringUtils.isEmpty(sumNumStr)) { | |
| 93 | + sumNum = Integer.parseInt(sumNumStr); | |
| 94 | + } | |
| 95 | + recordInfo.setSumNum(sumNum); | |
| 96 | + Element recordListElement = rootElementForCharset.element("RecordList"); | |
| 97 | + if (recordListElement == null || sumNum == 0) { | |
| 98 | + logger.info("无录像数据"); | |
| 99 | + eventPublisher.recordEndEventPush(recordInfo); | |
| 100 | + recordDataCatch.put(take.getDevice().getDeviceId(), sn, sumNum, new ArrayList<>()); | |
| 101 | + releaseRequest(take.getDevice().getDeviceId(), sn); | |
| 102 | + } else { | |
| 103 | + Iterator<Element> recordListIterator = recordListElement.elementIterator(); | |
| 104 | + if (recordListIterator != null) { | |
| 105 | + List<RecordItem> recordList = new ArrayList<>(); | |
| 106 | + // 遍历DeviceList | |
| 107 | + while (recordListIterator.hasNext()) { | |
| 108 | + Element itemRecord = recordListIterator.next(); | |
| 109 | + Element recordElement = itemRecord.element("DeviceID"); | |
| 110 | + if (recordElement == null) { | |
| 111 | + logger.info("记录为空,下一个..."); | |
| 112 | + continue; | |
| 113 | + } | |
| 114 | + RecordItem record = new RecordItem(); | |
| 115 | + record.setDeviceId(getText(itemRecord, "DeviceID")); | |
| 116 | + record.setName(getText(itemRecord, "Name")); | |
| 117 | + record.setFilePath(getText(itemRecord, "FilePath")); | |
| 118 | + record.setFileSize(getText(itemRecord, "FileSize")); | |
| 119 | + record.setAddress(getText(itemRecord, "Address")); | |
| 120 | + | |
| 121 | + String startTimeStr = getText(itemRecord, "StartTime"); | |
| 122 | + record.setStartTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(startTimeStr)); | |
| 123 | + | |
| 124 | + String endTimeStr = getText(itemRecord, "EndTime"); | |
| 125 | + record.setEndTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(endTimeStr)); | |
| 126 | + | |
| 127 | + record.setSecrecy(itemRecord.element("Secrecy") == null ? 0 | |
| 128 | + : Integer.parseInt(getText(itemRecord, "Secrecy"))); | |
| 129 | + record.setType(getText(itemRecord, "Type")); | |
| 130 | + record.setRecorderId(getText(itemRecord, "RecorderID")); | |
| 131 | + recordList.add(record); | |
| 132 | + } | |
| 133 | + recordInfo.setRecordList(recordList); | |
| 134 | + // 发送消息,如果是上级查询此录像,则会通过这里通知给上级 | |
| 135 | + eventPublisher.recordEndEventPush(recordInfo); | |
| 136 | + int count = recordDataCatch.put(take.getDevice().getDeviceId(), sn, sumNum, recordList); | |
| 137 | + logger.info("[国标录像], {}->{}: {}/{}", take.getDevice().getDeviceId(), sn, count, sumNum); | |
| 138 | + } | |
| 139 | + | |
| 140 | + if (recordDataCatch.isComplete(take.getDevice().getDeviceId(), sn)){ | |
| 141 | + releaseRequest(take.getDevice().getDeviceId(), sn); | |
| 142 | + } | |
| 143 | + } | |
| 95 | 144 | } |
| 96 | - RecordItem record = new RecordItem(); | |
| 97 | - record.setDeviceId(getText(itemRecord, "DeviceID")); | |
| 98 | - record.setName(getText(itemRecord, "Name")); | |
| 99 | - record.setFilePath(getText(itemRecord, "FilePath")); | |
| 100 | - record.setFileSize(getText(itemRecord, "FileSize")); | |
| 101 | - record.setAddress(getText(itemRecord, "Address")); | |
| 102 | - | |
| 103 | - String startTimeStr = getText(itemRecord, "StartTime"); | |
| 104 | - record.setStartTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(startTimeStr)); | |
| 105 | - | |
| 106 | - String endTimeStr = getText(itemRecord, "EndTime"); | |
| 107 | - record.setEndTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(endTimeStr)); | |
| 108 | - | |
| 109 | - record.setSecrecy(itemRecord.element("Secrecy") == null ? 0 | |
| 110 | - : Integer.parseInt(getText(itemRecord, "Secrecy"))); | |
| 111 | - record.setType(getText(itemRecord, "Type")); | |
| 112 | - record.setRecorderId(getText(itemRecord, "RecorderID")); | |
| 113 | - recordList.add(record); | |
| 145 | + taskQueueHandlerRun = false; | |
| 146 | + }catch (DocumentException e) { | |
| 147 | + throw new RuntimeException(e); | |
| 114 | 148 | } |
| 115 | - int count = recordDataCatch.put(device.getDeviceId(), sn, sumNum, recordList); | |
| 116 | - logger.info("[国标录像], {}->{}: {}/{}", device.getDeviceId(), sn, count, sumNum); | |
| 117 | - } | |
| 118 | - | |
| 119 | - if (recordDataCatch.isComplete(device.getDeviceId(), sn)){ | |
| 120 | - releaseRequest(device.getDeviceId(), sn); | |
| 121 | - } | |
| 149 | + }); | |
| 122 | 150 | } |
| 151 | + | |
| 123 | 152 | } catch (SipException e) { |
| 124 | 153 | e.printStackTrace(); |
| 125 | 154 | } catch (InvalidArgumentException e) { |
| 126 | 155 | e.printStackTrace(); |
| 127 | 156 | } catch (ParseException e) { |
| 128 | 157 | e.printStackTrace(); |
| 129 | - } catch (DocumentException e) { | |
| 130 | - e.printStackTrace(); | |
| 131 | 158 | } |
| 132 | 159 | } |
| 133 | 160 | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/ByeResponseProcessor.java
| ... | ... | @@ -17,7 +17,7 @@ import javax.sip.ResponseEvent; |
| 17 | 17 | @Component |
| 18 | 18 | public class ByeResponseProcessor extends SIPResponseProcessorAbstract { |
| 19 | 19 | |
| 20 | - private String method = "BYE"; | |
| 20 | + private final String method = "BYE"; | |
| 21 | 21 | |
| 22 | 22 | @Autowired |
| 23 | 23 | private SipLayer sipLayer; | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/CancelResponseProcessor.java
| ... | ... | @@ -17,7 +17,7 @@ import javax.sip.ResponseEvent; |
| 17 | 17 | @Component |
| 18 | 18 | public class CancelResponseProcessor extends SIPResponseProcessorAbstract { |
| 19 | 19 | |
| 20 | - private String method = "CANCEL"; | |
| 20 | + private final String method = "CANCEL"; | |
| 21 | 21 | |
| 22 | 22 | @Autowired |
| 23 | 23 | private SipLayer sipLayer; | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/InviteResponseProcessor.java
| ... | ... | @@ -31,7 +31,7 @@ import java.text.ParseException; |
| 31 | 31 | public class InviteResponseProcessor extends SIPResponseProcessorAbstract { |
| 32 | 32 | |
| 33 | 33 | private final static Logger logger = LoggerFactory.getLogger(InviteResponseProcessor.class); |
| 34 | - private String method = "INVITE"; | |
| 34 | + private final String method = "INVITE"; | |
| 35 | 35 | |
| 36 | 36 | @Autowired |
| 37 | 37 | private SipLayer sipLayer; | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/RegisterResponseProcessor.java
| ... | ... | @@ -27,7 +27,7 @@ import javax.sip.message.Response; |
| 27 | 27 | public class RegisterResponseProcessor extends SIPResponseProcessorAbstract { |
| 28 | 28 | |
| 29 | 29 | private Logger logger = LoggerFactory.getLogger(RegisterResponseProcessor.class); |
| 30 | - private String method = "REGISTER"; | |
| 30 | + private final String method = "REGISTER"; | |
| 31 | 31 | |
| 32 | 32 | @Autowired |
| 33 | 33 | private ISIPCommanderForPlatform sipCommanderForPlatform; | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/utils/XmlUtil.java
| ... | ... | @@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.gb28181.utils; |
| 2 | 2 | |
| 3 | 3 | import com.alibaba.fastjson.JSONArray; |
| 4 | 4 | import com.alibaba.fastjson.JSONObject; |
| 5 | +import com.genersoft.iot.vmp.gb28181.bean.Device; | |
| 5 | 6 | import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; |
| 6 | 7 | import org.dom4j.Attribute; |
| 7 | 8 | import org.dom4j.Document; |
| ... | ... | @@ -180,7 +181,7 @@ public class XmlUtil { |
| 180 | 181 | return xml.getRootElement(); |
| 181 | 182 | } |
| 182 | 183 | |
| 183 | - public static DeviceChannel channelContentHander(Element itemDevice){ | |
| 184 | + public static DeviceChannel channelContentHander(Element itemDevice, Device device){ | |
| 184 | 185 | Element channdelNameElement = itemDevice.element("Name"); |
| 185 | 186 | String channelName = channdelNameElement != null ? channdelNameElement.getTextTrim().toString() : ""; |
| 186 | 187 | Element statusElement = itemDevice.element("Status"); |
| ... | ... | @@ -254,6 +255,8 @@ public class XmlUtil { |
| 254 | 255 | }else if (deviceChannel.getChannelId().length() == 20) { |
| 255 | 256 | if (Integer.parseInt(deviceChannel.getChannelId().substring(10, 13)) == 216) { // 虚拟组织 |
| 256 | 257 | deviceChannel.setParentId(businessGroupID); |
| 258 | + }else if (Integer.parseInt(device.getDeviceId().substring(10, 13) )== 118) {//NVR 如果上级设备编号是NVR则直接将NVR的编号设置给通道的上级编号 | |
| 259 | + deviceChannel.setParentId(device.getDeviceId()); | |
| 257 | 260 | }else if (deviceChannel.getCivilCode() != null) { |
| 258 | 261 | // 设备, 无parentId的20位是使用CivilCode表示上级的设备, |
| 259 | 262 | // 注:215 业务分组是需要有parentId的 |
| ... | ... | @@ -308,6 +311,31 @@ public class XmlUtil { |
| 308 | 311 | } else { |
| 309 | 312 | deviceChannel.setLatitude(0.00); |
| 310 | 313 | } |
| 314 | + if (deviceChannel.getLongitude()*deviceChannel.getLatitude() > 0) { | |
| 315 | + if ("WGS84".equals(device.getGeoCoordSys())) { | |
| 316 | + deviceChannel.setLongitudeWgs84(deviceChannel.getLongitude()); | |
| 317 | + deviceChannel.setLatitudeWgs84(deviceChannel.getLatitude()); | |
| 318 | + Double[] position = Coordtransform.WGS84ToGCJ02(deviceChannel.getLongitude(), deviceChannel.getLatitude()); | |
| 319 | + deviceChannel.setLongitudeGcj02(position[0]); | |
| 320 | + deviceChannel.setLatitudeGcj02(position[1]); | |
| 321 | + }else if ("GCJ02".equals(device.getGeoCoordSys())) { | |
| 322 | + deviceChannel.setLongitudeGcj02(deviceChannel.getLongitude()); | |
| 323 | + deviceChannel.setLatitudeGcj02(deviceChannel.getLatitude()); | |
| 324 | + Double[] position = Coordtransform.GCJ02ToWGS84(deviceChannel.getLongitude(), deviceChannel.getLatitude()); | |
| 325 | + deviceChannel.setLongitudeWgs84(position[0]); | |
| 326 | + deviceChannel.setLatitudeWgs84(position[1]); | |
| 327 | + }else { | |
| 328 | + deviceChannel.setLongitudeGcj02(0.00); | |
| 329 | + deviceChannel.setLatitudeGcj02(0.00); | |
| 330 | + deviceChannel.setLongitudeWgs84(0.00); | |
| 331 | + deviceChannel.setLatitudeWgs84(0.00); | |
| 332 | + } | |
| 333 | + }else { | |
| 334 | + deviceChannel.setLongitudeGcj02(deviceChannel.getLongitude()); | |
| 335 | + deviceChannel.setLatitudeGcj02(deviceChannel.getLatitude()); | |
| 336 | + deviceChannel.setLongitudeWgs84(deviceChannel.getLongitude()); | |
| 337 | + deviceChannel.setLatitudeWgs84(deviceChannel.getLatitude()); | |
| 338 | + } | |
| 311 | 339 | if (XmlUtil.getText(itemDevice, "PTZType") == null || "".equals(XmlUtil.getText(itemDevice, "PTZType"))) { |
| 312 | 340 | //兼容INFO中的信息 |
| 313 | 341 | Element info = itemDevice.element("Info"); | ... | ... |
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
| ... | ... | @@ -11,7 +11,6 @@ import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; |
| 11 | 11 | import com.genersoft.iot.vmp.gb28181.bean.GbStream; |
| 12 | 12 | import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction; |
| 13 | 13 | import com.genersoft.iot.vmp.gb28181.event.EventPublisher; |
| 14 | -import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent; | |
| 15 | 14 | import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; |
| 16 | 15 | import com.genersoft.iot.vmp.media.zlm.dto.*; |
| 17 | 16 | import com.genersoft.iot.vmp.service.*; |
| ... | ... | @@ -92,10 +91,9 @@ public class ZLMHttpHookListener { |
| 92 | 91 | public ResponseEntity<String> onServerKeepalive(@RequestBody JSONObject json){ |
| 93 | 92 | |
| 94 | 93 | if (logger.isDebugEnabled()) { |
| 95 | - logger.debug("[ ZLM HOOK ]on_server_keepalive API调用,参数:" + json.toString()); | |
| 94 | + logger.debug("[ ZLM HOOK ] on_server_keepalive API调用,参数:" + json.toString()); | |
| 96 | 95 | } |
| 97 | 96 | String mediaServerId = json.getString("mediaServerId"); |
| 98 | - | |
| 99 | 97 | List<ZLMHttpHookSubscribe.Event> subscribes = this.subscribe.getSubscribes(ZLMHttpHookSubscribe.HookType.on_server_keepalive); |
| 100 | 98 | if (subscribes != null && subscribes.size() > 0) { |
| 101 | 99 | for (ZLMHttpHookSubscribe.Event subscribe : subscribes) { |
| ... | ... | @@ -165,7 +163,6 @@ public class ZLMHttpHookListener { |
| 165 | 163 | if (mediaInfo != null) { |
| 166 | 164 | subscribe.response(mediaInfo, json); |
| 167 | 165 | } |
| 168 | - | |
| 169 | 166 | } |
| 170 | 167 | JSONObject ret = new JSONObject(); |
| 171 | 168 | ret.put("code", 0); |
| ... | ... | @@ -248,6 +245,23 @@ public class ZLMHttpHookListener { |
| 248 | 245 | ret.put("msg", "success"); |
| 249 | 246 | return new ResponseEntity<String>(ret.toString(),HttpStatus.OK); |
| 250 | 247 | } |
| 248 | + /** | |
| 249 | + * 录制hls完成后通知事件;此事件对回复不敏感。 | |
| 250 | + * | |
| 251 | + */ | |
| 252 | + @ResponseBody | |
| 253 | + @PostMapping(value = "/on_record_ts", produces = "application/json;charset=UTF-8") | |
| 254 | + public ResponseEntity<String> onRecordTs(@RequestBody JSONObject json){ | |
| 255 | + | |
| 256 | + if (logger.isDebugEnabled()) { | |
| 257 | + logger.debug("[ ZLM HOOK ]on_record_ts API调用,参数:" + json.toString()); | |
| 258 | + } | |
| 259 | + String mediaServerId = json.getString("mediaServerId"); | |
| 260 | + JSONObject ret = new JSONObject(); | |
| 261 | + ret.put("code", 0); | |
| 262 | + ret.put("msg", "success"); | |
| 263 | + return new ResponseEntity<String>(ret.toString(),HttpStatus.OK); | |
| 264 | + } | |
| 251 | 265 | |
| 252 | 266 | /** |
| 253 | 267 | * rtsp专用的鉴权事件,先触发on_rtsp_realm事件然后才会触发on_rtsp_auth事件。 |
| ... | ... | @@ -383,21 +397,22 @@ public class ZLMHttpHookListener { |
| 383 | 397 | if (item.getOriginType() == OriginType.RTSP_PUSH.ordinal() |
| 384 | 398 | || item.getOriginType() == OriginType.RTMP_PUSH.ordinal() |
| 385 | 399 | || item.getOriginType() == OriginType.RTC_PUSH.ordinal() ) { |
| 386 | - streamPushItem = zlmMediaListManager.addPush(item); | |
| 400 | + item.setSeverId(userSetting.getServerId()); | |
| 401 | + zlmMediaListManager.addPush(item); | |
| 387 | 402 | } |
| 388 | 403 | |
| 389 | - List<GbStream> gbStreams = new ArrayList<>(); | |
| 390 | - if (streamPushItem == null || streamPushItem.getGbId() == null) { | |
| 391 | - GbStream gbStream = storager.getGbStream(app, streamId); | |
| 392 | - gbStreams.add(gbStream); | |
| 393 | - }else { | |
| 394 | - if (streamPushItem.getGbId() != null) { | |
| 395 | - gbStreams.add(streamPushItem); | |
| 396 | - } | |
| 397 | - } | |
| 398 | - if (gbStreams.size() > 0) { | |
| 404 | +// List<GbStream> gbStreams = new ArrayList<>(); | |
| 405 | +// if (streamPushItem == null || streamPushItem.getGbId() == null) { | |
| 406 | +// GbStream gbStream = storager.getGbStream(app, streamId); | |
| 407 | +// gbStreams.add(gbStream); | |
| 408 | +// }else { | |
| 409 | +// if (streamPushItem.getGbId() != null) { | |
| 410 | +// gbStreams.add(streamPushItem); | |
| 411 | +// } | |
| 412 | +// } | |
| 413 | +// if (gbStreams.size() > 0) { | |
| 399 | 414 | // eventPublisher.catalogEventPublishForStream(null, gbStreams, CatalogEvent.ON); |
| 400 | - } | |
| 415 | +// } | |
| 401 | 416 | |
| 402 | 417 | }else { |
| 403 | 418 | // 兼容流注销时类型从redis记录获取 | ... | ... |
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaListManager.java
| ... | ... | @@ -24,6 +24,9 @@ import java.util.concurrent.ConcurrentHashMap; |
| 24 | 24 | import java.util.regex.Matcher; |
| 25 | 25 | import java.util.regex.Pattern; |
| 26 | 26 | |
| 27 | +/** | |
| 28 | + * @author lin | |
| 29 | + */ | |
| 27 | 30 | @Component |
| 28 | 31 | public class ZLMMediaListManager { |
| 29 | 32 | |
| ... | ... | @@ -147,7 +150,6 @@ public class ZLMMediaListManager { |
| 147 | 150 | } |
| 148 | 151 | } |
| 149 | 152 | } |
| 150 | - // StreamProxyItem streamProxyItem = gbStreamMapper.selectOne(transform.getApp(), transform.getStream()); | |
| 151 | 153 | List<GbStream> gbStreamList = gbStreamMapper.selectByGBId(transform.getGbId()); |
| 152 | 154 | if (gbStreamList != null && gbStreamList.size() == 1) { |
| 153 | 155 | transform.setGbStreamId(gbStreamList.get(0).getGbStreamId()); |
| ... | ... | @@ -162,13 +164,12 @@ public class ZLMMediaListManager { |
| 162 | 164 | } |
| 163 | 165 | if (transform != null) { |
| 164 | 166 | if (channelOnlineEvents.get(transform.getGbId()) != null) { |
| 165 | - channelOnlineEvents.get(transform.getGbId()).run(transform.getApp(), transform.getStream()); | |
| 167 | + channelOnlineEvents.get(transform.getGbId()).run(transform.getApp(), transform.getStream(), transform.getServerId()); | |
| 166 | 168 | channelOnlineEvents.remove(transform.getGbId()); |
| 167 | 169 | } |
| 168 | 170 | } |
| 169 | 171 | } |
| 170 | 172 | |
| 171 | - | |
| 172 | 173 | storager.updateMedia(transform); |
| 173 | 174 | return transform; |
| 174 | 175 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java
| ... | ... | @@ -12,6 +12,7 @@ import org.springframework.stereotype.Component; |
| 12 | 12 | |
| 13 | 13 | import java.io.*; |
| 14 | 14 | import java.net.ConnectException; |
| 15 | +import java.net.SocketTimeoutException; | |
| 15 | 16 | import java.util.HashMap; |
| 16 | 17 | import java.util.Map; |
| 17 | 18 | import java.util.Objects; |
| ... | ... | @@ -28,6 +29,9 @@ public class ZLMRESTfulUtils { |
| 28 | 29 | |
| 29 | 30 | private OkHttpClient getClient(){ |
| 30 | 31 | OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder(); |
| 32 | + //todo 暂时写死超时时间 均为5s | |
| 33 | + httpClientBuilder.connectTimeout(5,TimeUnit.SECONDS); //设置连接超时时间 | |
| 34 | + httpClientBuilder.readTimeout(5,TimeUnit.SECONDS); //设置读取超时时间 | |
| 31 | 35 | if (logger.isDebugEnabled()) { |
| 32 | 36 | HttpLoggingInterceptor logging = new HttpLoggingInterceptor(message -> { |
| 33 | 37 | logger.debug("http请求参数:" + message); |
| ... | ... | @@ -47,7 +51,10 @@ public class ZLMRESTfulUtils { |
| 47 | 51 | return null; |
| 48 | 52 | } |
| 49 | 53 | String url = String.format("http://%s:%s/index/api/%s", mediaServerItem.getIp(), mediaServerItem.getHttpPort(), api); |
| 50 | - JSONObject responseJSON = null; | |
| 54 | + JSONObject responseJSON = new JSONObject(); | |
| 55 | + //-2自定义流媒体 调用错误码 | |
| 56 | + responseJSON.put("code",-2); | |
| 57 | + responseJSON.put("msg","流媒体调用失败"); | |
| 51 | 58 | |
| 52 | 59 | FormBody.Builder builder = new FormBody.Builder(); |
| 53 | 60 | builder.add("secret",mediaServerItem.getSecret()); |
| ... | ... | @@ -78,11 +85,20 @@ public class ZLMRESTfulUtils { |
| 78 | 85 | response.close(); |
| 79 | 86 | Objects.requireNonNull(response.body()).close(); |
| 80 | 87 | } |
| 81 | - } catch (ConnectException e) { | |
| 82 | - logger.error(String.format("连接ZLM失败: %s, %s", e.getCause().getMessage(), e.getMessage())); | |
| 83 | - logger.info("请检查media配置并确认ZLM已启动..."); | |
| 84 | 88 | }catch (IOException e) { |
| 85 | 89 | logger.error(String.format("[ %s ]请求失败: %s", url, e.getMessage())); |
| 90 | + | |
| 91 | + if(e instanceof SocketTimeoutException){ | |
| 92 | + //读取超时超时异常 | |
| 93 | + logger.error(String.format("读取ZLM数据失败: %s, %s", url, e.getMessage())); | |
| 94 | + } | |
| 95 | + if(e instanceof ConnectException){ | |
| 96 | + //判断连接异常,我这里是报Failed to connect to 10.7.5.144 | |
| 97 | + logger.error(String.format("连接ZLM失败: %s, %s", url, e.getMessage())); | |
| 98 | + } | |
| 99 | + | |
| 100 | + }catch (Exception e){ | |
| 101 | + logger.error(String.format("访问ZLM失败: %s, %s", url, e.getMessage())); | |
| 86 | 102 | } |
| 87 | 103 | }else { |
| 88 | 104 | client.newCall(request).enqueue(new Callback(){ |
| ... | ... | @@ -105,8 +121,16 @@ public class ZLMRESTfulUtils { |
| 105 | 121 | |
| 106 | 122 | @Override |
| 107 | 123 | public void onFailure(@NotNull Call call, @NotNull IOException e) { |
| 108 | - logger.error(String.format("连接ZLM失败: %s, %s", e.getCause().getMessage(), e.getMessage())); | |
| 109 | - logger.info("请检查media配置并确认ZLM已启动..."); | |
| 124 | + logger.error(String.format("连接ZLM失败: %s, %s", call.request().toString(), e.getMessage())); | |
| 125 | + | |
| 126 | + if(e instanceof SocketTimeoutException){ | |
| 127 | + //读取超时超时异常 | |
| 128 | + logger.error(String.format("读取ZLM数据失败: %s, %s", call.request().toString(), e.getMessage())); | |
| 129 | + } | |
| 130 | + if(e instanceof ConnectException){ | |
| 131 | + //判断连接异常,我这里是报Failed to connect to 10.7.5.144 | |
| 132 | + logger.error(String.format("连接ZLM失败: %s, %s", call.request().toString(), e.getMessage())); | |
| 133 | + } | |
| 110 | 134 | } |
| 111 | 135 | }); |
| 112 | 136 | } |
| ... | ... | @@ -151,7 +175,7 @@ public class ZLMRESTfulUtils { |
| 151 | 175 | } |
| 152 | 176 | |
| 153 | 177 | } |
| 154 | - File snapFile = new File(targetPath + "/" + fileName); | |
| 178 | + File snapFile = new File(targetPath + File.separator + fileName); | |
| 155 | 179 | FileOutputStream outStream = new FileOutputStream(snapFile); |
| 156 | 180 | |
| 157 | 181 | outStream.write(Objects.requireNonNull(response.body()).bytes()); | ... | ... |
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java
| ... | ... | @@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.media.zlm; |
| 2 | 2 | |
| 3 | 3 | import com.alibaba.fastjson.JSONArray; |
| 4 | 4 | import com.alibaba.fastjson.JSONObject; |
| 5 | +import com.genersoft.iot.vmp.conf.UserSetting; | |
| 5 | 6 | import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem; |
| 6 | 7 | import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; |
| 7 | 8 | import org.slf4j.Logger; |
| ... | ... | @@ -20,6 +21,9 @@ public class ZLMRTPServerFactory { |
| 20 | 21 | @Autowired |
| 21 | 22 | private ZLMRESTfulUtils zlmresTfulUtils; |
| 22 | 23 | |
| 24 | + @Autowired | |
| 25 | + private UserSetting userSetting; | |
| 26 | + | |
| 23 | 27 | private int[] portRangeArray = new int[2]; |
| 24 | 28 | |
| 25 | 29 | public int getFreePort(MediaServerItem mediaServerItem, int startPort, int endPort, List<Integer> usedFreelist) { |
| ... | ... | @@ -87,10 +91,15 @@ public class ZLMRTPServerFactory { |
| 87 | 91 | int result = -1; |
| 88 | 92 | // 查询此rtp server 是否已经存在 |
| 89 | 93 | JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaServerItem, streamId); |
| 90 | - if (rtpInfo != null && rtpInfo.getInteger("code") == 0 && rtpInfo.getBoolean("exist")) { | |
| 91 | - result = rtpInfo.getInteger("local_port"); | |
| 94 | + if(rtpInfo.getInteger("code") == 0){ | |
| 95 | + if (rtpInfo.getBoolean("exist")) { | |
| 96 | + result = rtpInfo.getInteger("local_port"); | |
| 97 | + return result; | |
| 98 | + } | |
| 99 | + }else if(rtpInfo.getInteger("code") == -2){ | |
| 92 | 100 | return result; |
| 93 | 101 | } |
| 102 | + | |
| 94 | 103 | Map<String, Object> param = new HashMap<>(); |
| 95 | 104 | // 推流端口设置0则使用随机端口 |
| 96 | 105 | param.put("enable_tcp", 1); |
| ... | ... | @@ -197,6 +206,7 @@ public class ZLMRTPServerFactory { |
| 197 | 206 | sendRtpItem.setTcp(tcp); |
| 198 | 207 | sendRtpItem.setApp("rtp"); |
| 199 | 208 | sendRtpItem.setLocalPort(localPort); |
| 209 | + sendRtpItem.setServerId(userSetting.getServerId()); | |
| 200 | 210 | sendRtpItem.setMediaServerId(serverItem.getId()); |
| 201 | 211 | return sendRtpItem; |
| 202 | 212 | } |
| ... | ... | @@ -238,6 +248,7 @@ public class ZLMRTPServerFactory { |
| 238 | 248 | sendRtpItem.setChannelId(channelId); |
| 239 | 249 | sendRtpItem.setTcp(tcp); |
| 240 | 250 | sendRtpItem.setLocalPort(localPort); |
| 251 | + sendRtpItem.setServerId(userSetting.getServerId()); | |
| 241 | 252 | sendRtpItem.setMediaServerId(serverItem.getId()); |
| 242 | 253 | return sendRtpItem; |
| 243 | 254 | } |
| ... | ... | @@ -279,10 +290,10 @@ public class ZLMRTPServerFactory { |
| 279 | 290 | */ |
| 280 | 291 | public int totalReaderCount(MediaServerItem mediaServerItem, String app, String streamId) { |
| 281 | 292 | JSONObject mediaInfo = zlmresTfulUtils.getMediaInfo(mediaServerItem, app, "rtmp", streamId); |
| 282 | - Integer code = mediaInfo.getInteger("code"); | |
| 283 | 293 | if (mediaInfo == null) { |
| 284 | 294 | return 0; |
| 285 | 295 | } |
| 296 | + Integer code = mediaInfo.getInteger("code"); | |
| 286 | 297 | if ( code < 0) { |
| 287 | 298 | logger.warn("查询流({}/{})是否有其它观看者时得到: {}", app, streamId, mediaInfo.getString("msg")); |
| 288 | 299 | return -1; | ... | ... |
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/ChannelOnlineEvent.java
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/MediaItem.java
| ... | ... | @@ -61,11 +61,16 @@ public class MediaItem { |
| 61 | 61 | private String originUrl; |
| 62 | 62 | |
| 63 | 63 | /** |
| 64 | - * 服务器id | |
| 64 | + * 流媒体服务器id | |
| 65 | 65 | */ |
| 66 | 66 | private String mediaServerId; |
| 67 | 67 | |
| 68 | 68 | /** |
| 69 | + * 服务器id | |
| 70 | + */ | |
| 71 | + private String severId; | |
| 72 | + | |
| 73 | + /** | |
| 69 | 74 | * GMT unix系统时间戳,单位秒 |
| 70 | 75 | */ |
| 71 | 76 | private Long createStamp; |
| ... | ... | @@ -414,4 +419,12 @@ public class MediaItem { |
| 414 | 419 | public void setStreamInfo(StreamInfo streamInfo) { |
| 415 | 420 | this.streamInfo = streamInfo; |
| 416 | 421 | } |
| 422 | + | |
| 423 | + public String getSeverId() { | |
| 424 | + return severId; | |
| 425 | + } | |
| 426 | + | |
| 427 | + public void setSeverId(String severId) { | |
| 428 | + this.severId = severId; | |
| 429 | + } | |
| 417 | 430 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/StreamPushItem.java
| ... | ... | @@ -81,6 +81,11 @@ public class StreamPushItem extends GbStream implements Comparable<StreamPushIte |
| 81 | 81 | */ |
| 82 | 82 | private String mediaServerId; |
| 83 | 83 | |
| 84 | + /** | |
| 85 | + * 使用的服务ID | |
| 86 | + */ | |
| 87 | + private String serverId; | |
| 88 | + | |
| 84 | 89 | public String getVhost() { |
| 85 | 90 | return vhost; |
| 86 | 91 | } |
| ... | ... | @@ -219,5 +224,13 @@ public class StreamPushItem extends GbStream implements Comparable<StreamPushIte |
| 219 | 224 | public void setMediaServerId(String mediaServerId) { |
| 220 | 225 | this.mediaServerId = mediaServerId; |
| 221 | 226 | } |
| 227 | + | |
| 228 | + public String getServerId() { | |
| 229 | + return serverId; | |
| 230 | + } | |
| 231 | + | |
| 232 | + public void setServerId(String serverId) { | |
| 233 | + this.serverId = serverId; | |
| 234 | + } | |
| 222 | 235 | } |
| 223 | 236 | ... | ... |
src/main/java/com/genersoft/iot/vmp/media/zlm/event/ZLMKeepliveTimeoutListener.java
| ... | ... | @@ -61,13 +61,12 @@ public class ZLMKeepliveTimeoutListener extends RedisKeyExpirationEventMessageLi |
| 61 | 61 | // 发起http请求验证zlm是否确实无法连接,如果确实无法连接则发送离线事件,否则不作处理 |
| 62 | 62 | MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId); |
| 63 | 63 | JSONObject mediaServerConfig = zlmresTfulUtils.getMediaServerConfig(mediaServerItem); |
| 64 | - if (mediaServerConfig == null) { | |
| 65 | - publisher.zlmOfflineEventPublish(mediaServerId); | |
| 66 | - }else { | |
| 64 | + if (mediaServerConfig != null && mediaServerConfig.getInteger("code") == 0) { | |
| 67 | 65 | logger.info("[zlm心跳到期]:{}验证后zlm仍在线,恢复心跳信息", mediaServerId); |
| 68 | 66 | // 添加zlm信息 |
| 69 | 67 | mediaServerService.updateMediaServerKeepalive(mediaServerId, mediaServerConfig); |
| 68 | + }else { | |
| 69 | + publisher.zlmOfflineEventPublish(mediaServerId); | |
| 70 | 70 | } |
| 71 | - | |
| 72 | 71 | } |
| 73 | 72 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/media/zlm/event/ZLMStatusEventListener.java
| ... | ... | @@ -42,7 +42,7 @@ public class ZLMStatusEventListener { |
| 42 | 42 | logger.info("[ZLM] 上线 ID:" + event.getMediaServerId()); |
| 43 | 43 | streamPushService.zlmServerOnline(event.getMediaServerId()); |
| 44 | 44 | streamProxyService.zlmServerOnline(event.getMediaServerId()); |
| 45 | - | |
| 45 | + playService.zlmServerOnline(event.getMediaServerId()); | |
| 46 | 46 | } |
| 47 | 47 | |
| 48 | 48 | @Async | ... | ... |
src/main/java/com/genersoft/iot/vmp/service/IPlayService.java
| ... | ... | @@ -42,6 +42,8 @@ public interface IPlayService { |
| 42 | 42 | |
| 43 | 43 | StreamInfo getDownLoadInfo(String deviceId, String channelId, String stream); |
| 44 | 44 | |
| 45 | + void zlmServerOnline(String mediaServerId); | |
| 46 | + | |
| 45 | 47 | void audioBroadcast(Device device, String channelId, int timeout, AudioBroadcastEvent event); |
| 46 | 48 | void stopAudioBroadcast(String deviceId, String channelId); |
| 47 | 49 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/service/StreamGPSSubscribeTask.java
src/main/java/com/genersoft/iot/vmp/service/bean/MessageForPushChannel.java
| 1 | 1 | package com.genersoft.iot.vmp.service.bean; |
| 2 | 2 | |
| 3 | +import java.util.stream.Stream; | |
| 4 | + | |
| 3 | 5 | /** |
| 4 | 6 | * 当上级平台 |
| 7 | + * @author lin | |
| 5 | 8 | */ |
| 6 | 9 | public class MessageForPushChannel { |
| 7 | 10 | /** |
| ... | ... | @@ -45,6 +48,20 @@ public class MessageForPushChannel { |
| 45 | 48 | */ |
| 46 | 49 | private String mediaServerId; |
| 47 | 50 | |
| 51 | + public static MessageForPushChannel getInstance(int type, String app, String stream, String gbId, | |
| 52 | + String platFormId, String platFormName, String serverId, | |
| 53 | + String mediaServerId){ | |
| 54 | + MessageForPushChannel messageForPushChannel = new MessageForPushChannel(); | |
| 55 | + messageForPushChannel.setType(type); | |
| 56 | + messageForPushChannel.setGbId(gbId); | |
| 57 | + messageForPushChannel.setApp(app); | |
| 58 | + messageForPushChannel.setStream(stream); | |
| 59 | + messageForPushChannel.setMediaServerId(mediaServerId); | |
| 60 | + messageForPushChannel.setPlatFormId(platFormId); | |
| 61 | + messageForPushChannel.setPlatFormName(platFormName); | |
| 62 | + return messageForPushChannel; | |
| 63 | + } | |
| 64 | + | |
| 48 | 65 | |
| 49 | 66 | public int getType() { |
| 50 | 67 | return type; | ... | ... |
src/main/java/com/genersoft/iot/vmp/service/bean/RequestPushStreamMsg.java
0 → 100644
| 1 | +package com.genersoft.iot.vmp.service.bean; | |
| 2 | + | |
| 3 | +/** | |
| 4 | + * redis消息:请求下级推送流信息 | |
| 5 | + * @author lin | |
| 6 | + */ | |
| 7 | +public class RequestPushStreamMsg { | |
| 8 | + | |
| 9 | + | |
| 10 | + /** | |
| 11 | + * 下级服务ID | |
| 12 | + */ | |
| 13 | + private String mediaServerId; | |
| 14 | + | |
| 15 | + /** | |
| 16 | + * 流ID | |
| 17 | + */ | |
| 18 | + private String app; | |
| 19 | + | |
| 20 | + /** | |
| 21 | + * 应用名 | |
| 22 | + */ | |
| 23 | + private String stream; | |
| 24 | + | |
| 25 | + /** | |
| 26 | + * 目标IP | |
| 27 | + */ | |
| 28 | + private String ip; | |
| 29 | + | |
| 30 | + /** | |
| 31 | + * 目标端口 | |
| 32 | + */ | |
| 33 | + private int port; | |
| 34 | + | |
| 35 | + /** | |
| 36 | + * ssrc | |
| 37 | + */ | |
| 38 | + private String ssrc; | |
| 39 | + | |
| 40 | + /** | |
| 41 | + * 是否使用TCP方式 | |
| 42 | + */ | |
| 43 | + private boolean tcp; | |
| 44 | + | |
| 45 | + /** | |
| 46 | + * 本地使用的端口 | |
| 47 | + */ | |
| 48 | + private int srcPort; | |
| 49 | + | |
| 50 | + /** | |
| 51 | + * 发送时,rtp的pt(uint8_t),不传时默认为96 | |
| 52 | + */ | |
| 53 | + private int pt; | |
| 54 | + | |
| 55 | + /** | |
| 56 | + * 发送时,rtp的负载类型。为true时,负载为ps;为false时,为es; | |
| 57 | + */ | |
| 58 | + private boolean ps; | |
| 59 | + | |
| 60 | + /** | |
| 61 | + * 是否只有音频 | |
| 62 | + */ | |
| 63 | + private boolean onlyAudio; | |
| 64 | + | |
| 65 | + | |
| 66 | + public static RequestPushStreamMsg getInstance(String mediaServerId, String app, String stream, String ip, int port, String ssrc, | |
| 67 | + boolean tcp, int srcPort, int pt, boolean ps, boolean onlyAudio) { | |
| 68 | + RequestPushStreamMsg requestPushStreamMsg = new RequestPushStreamMsg(); | |
| 69 | + requestPushStreamMsg.setMediaServerId(mediaServerId); | |
| 70 | + requestPushStreamMsg.setApp(app); | |
| 71 | + requestPushStreamMsg.setStream(stream); | |
| 72 | + requestPushStreamMsg.setIp(ip); | |
| 73 | + requestPushStreamMsg.setPort(port); | |
| 74 | + requestPushStreamMsg.setSsrc(ssrc); | |
| 75 | + requestPushStreamMsg.setTcp(tcp); | |
| 76 | + requestPushStreamMsg.setSrcPort(srcPort); | |
| 77 | + requestPushStreamMsg.setPt(pt); | |
| 78 | + requestPushStreamMsg.setPs(ps); | |
| 79 | + requestPushStreamMsg.setOnlyAudio(onlyAudio); | |
| 80 | + return requestPushStreamMsg; | |
| 81 | + } | |
| 82 | + | |
| 83 | + public String getMediaServerId() { | |
| 84 | + return mediaServerId; | |
| 85 | + } | |
| 86 | + | |
| 87 | + public void setMediaServerId(String mediaServerId) { | |
| 88 | + this.mediaServerId = mediaServerId; | |
| 89 | + } | |
| 90 | + | |
| 91 | + public String getApp() { | |
| 92 | + return app; | |
| 93 | + } | |
| 94 | + | |
| 95 | + public void setApp(String app) { | |
| 96 | + this.app = app; | |
| 97 | + } | |
| 98 | + | |
| 99 | + public String getStream() { | |
| 100 | + return stream; | |
| 101 | + } | |
| 102 | + | |
| 103 | + public void setStream(String stream) { | |
| 104 | + this.stream = stream; | |
| 105 | + } | |
| 106 | + | |
| 107 | + public String getIp() { | |
| 108 | + return ip; | |
| 109 | + } | |
| 110 | + | |
| 111 | + public void setIp(String ip) { | |
| 112 | + this.ip = ip; | |
| 113 | + } | |
| 114 | + | |
| 115 | + public int getPort() { | |
| 116 | + return port; | |
| 117 | + } | |
| 118 | + | |
| 119 | + public void setPort(int port) { | |
| 120 | + this.port = port; | |
| 121 | + } | |
| 122 | + | |
| 123 | + public String getSsrc() { | |
| 124 | + return ssrc; | |
| 125 | + } | |
| 126 | + | |
| 127 | + public void setSsrc(String ssrc) { | |
| 128 | + this.ssrc = ssrc; | |
| 129 | + } | |
| 130 | + | |
| 131 | + public boolean isTcp() { | |
| 132 | + return tcp; | |
| 133 | + } | |
| 134 | + | |
| 135 | + public void setTcp(boolean tcp) { | |
| 136 | + this.tcp = tcp; | |
| 137 | + } | |
| 138 | + | |
| 139 | + public int getSrcPort() { | |
| 140 | + return srcPort; | |
| 141 | + } | |
| 142 | + | |
| 143 | + public void setSrcPort(int srcPort) { | |
| 144 | + this.srcPort = srcPort; | |
| 145 | + } | |
| 146 | + | |
| 147 | + public int getPt() { | |
| 148 | + return pt; | |
| 149 | + } | |
| 150 | + | |
| 151 | + public void setPt(int pt) { | |
| 152 | + this.pt = pt; | |
| 153 | + } | |
| 154 | + | |
| 155 | + public boolean isPs() { | |
| 156 | + return ps; | |
| 157 | + } | |
| 158 | + | |
| 159 | + public void setPs(boolean ps) { | |
| 160 | + this.ps = ps; | |
| 161 | + } | |
| 162 | + | |
| 163 | + public boolean isOnlyAudio() { | |
| 164 | + return onlyAudio; | |
| 165 | + } | |
| 166 | + | |
| 167 | + public void setOnlyAudio(boolean onlyAudio) { | |
| 168 | + this.onlyAudio = onlyAudio; | |
| 169 | + } | |
| 170 | +} | ... | ... |
src/main/java/com/genersoft/iot/vmp/service/bean/RequestSendItemMsg.java
0 → 100644
| 1 | +package com.genersoft.iot.vmp.service.bean; | |
| 2 | + | |
| 3 | +/** | |
| 4 | + * redis消息:请求下级回复推送信息 | |
| 5 | + * @author lin | |
| 6 | + */ | |
| 7 | +public class RequestSendItemMsg { | |
| 8 | + | |
| 9 | + /** | |
| 10 | + * 下级服务ID | |
| 11 | + */ | |
| 12 | + private String serverId; | |
| 13 | + | |
| 14 | + /** | |
| 15 | + * 下级服务ID | |
| 16 | + */ | |
| 17 | + private String mediaServerId; | |
| 18 | + | |
| 19 | + /** | |
| 20 | + * 流ID | |
| 21 | + */ | |
| 22 | + private String app; | |
| 23 | + | |
| 24 | + /** | |
| 25 | + * 应用名 | |
| 26 | + */ | |
| 27 | + private String stream; | |
| 28 | + | |
| 29 | + /** | |
| 30 | + * 目标IP | |
| 31 | + */ | |
| 32 | + private String ip; | |
| 33 | + | |
| 34 | + /** | |
| 35 | + * 目标端口 | |
| 36 | + */ | |
| 37 | + private int port; | |
| 38 | + | |
| 39 | + /** | |
| 40 | + * ssrc | |
| 41 | + */ | |
| 42 | + private String ssrc; | |
| 43 | + | |
| 44 | + /** | |
| 45 | + * 平台国标编号 | |
| 46 | + */ | |
| 47 | + private String platformId; | |
| 48 | + | |
| 49 | + /** | |
| 50 | + * 平台名称 | |
| 51 | + */ | |
| 52 | + private String platformName; | |
| 53 | + | |
| 54 | + /** | |
| 55 | + * 通道ID | |
| 56 | + */ | |
| 57 | + private String channelId; | |
| 58 | + | |
| 59 | + | |
| 60 | + /** | |
| 61 | + * 是否使用TCP | |
| 62 | + */ | |
| 63 | + private Boolean isTcp; | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + public static RequestSendItemMsg getInstance(String serverId, String mediaServerId, String app, String stream, String ip, int port, | |
| 69 | + String ssrc, String platformId, String channelId, Boolean isTcp, String platformName) { | |
| 70 | + RequestSendItemMsg requestSendItemMsg = new RequestSendItemMsg(); | |
| 71 | + requestSendItemMsg.setServerId(serverId); | |
| 72 | + requestSendItemMsg.setMediaServerId(mediaServerId); | |
| 73 | + requestSendItemMsg.setApp(app); | |
| 74 | + requestSendItemMsg.setStream(stream); | |
| 75 | + requestSendItemMsg.setIp(ip); | |
| 76 | + requestSendItemMsg.setPort(port); | |
| 77 | + requestSendItemMsg.setSsrc(ssrc); | |
| 78 | + requestSendItemMsg.setPlatformId(platformId); | |
| 79 | + requestSendItemMsg.setPlatformName(platformName); | |
| 80 | + requestSendItemMsg.setChannelId(channelId); | |
| 81 | + requestSendItemMsg.setTcp(isTcp); | |
| 82 | + | |
| 83 | + return requestSendItemMsg; | |
| 84 | + } | |
| 85 | + | |
| 86 | + public String getServerId() { | |
| 87 | + return serverId; | |
| 88 | + } | |
| 89 | + | |
| 90 | + public void setServerId(String serverId) { | |
| 91 | + this.serverId = serverId; | |
| 92 | + } | |
| 93 | + | |
| 94 | + public String getMediaServerId() { | |
| 95 | + return mediaServerId; | |
| 96 | + } | |
| 97 | + | |
| 98 | + public void setMediaServerId(String mediaServerId) { | |
| 99 | + this.mediaServerId = mediaServerId; | |
| 100 | + } | |
| 101 | + | |
| 102 | + public String getApp() { | |
| 103 | + return app; | |
| 104 | + } | |
| 105 | + | |
| 106 | + public void setApp(String app) { | |
| 107 | + this.app = app; | |
| 108 | + } | |
| 109 | + | |
| 110 | + public String getStream() { | |
| 111 | + return stream; | |
| 112 | + } | |
| 113 | + | |
| 114 | + public void setStream(String stream) { | |
| 115 | + this.stream = stream; | |
| 116 | + } | |
| 117 | + | |
| 118 | + public String getIp() { | |
| 119 | + return ip; | |
| 120 | + } | |
| 121 | + | |
| 122 | + public void setIp(String ip) { | |
| 123 | + this.ip = ip; | |
| 124 | + } | |
| 125 | + | |
| 126 | + public int getPort() { | |
| 127 | + return port; | |
| 128 | + } | |
| 129 | + | |
| 130 | + public void setPort(int port) { | |
| 131 | + this.port = port; | |
| 132 | + } | |
| 133 | + | |
| 134 | + public String getSsrc() { | |
| 135 | + return ssrc; | |
| 136 | + } | |
| 137 | + | |
| 138 | + public void setSsrc(String ssrc) { | |
| 139 | + this.ssrc = ssrc; | |
| 140 | + } | |
| 141 | + | |
| 142 | + public String getPlatformId() { | |
| 143 | + return platformId; | |
| 144 | + } | |
| 145 | + | |
| 146 | + public void setPlatformId(String platformId) { | |
| 147 | + this.platformId = platformId; | |
| 148 | + } | |
| 149 | + | |
| 150 | + public String getPlatformName() { | |
| 151 | + return platformName; | |
| 152 | + } | |
| 153 | + | |
| 154 | + public void setPlatformName(String platformName) { | |
| 155 | + this.platformName = platformName; | |
| 156 | + } | |
| 157 | + | |
| 158 | + public String getChannelId() { | |
| 159 | + return channelId; | |
| 160 | + } | |
| 161 | + | |
| 162 | + public void setChannelId(String channelId) { | |
| 163 | + this.channelId = channelId; | |
| 164 | + } | |
| 165 | + | |
| 166 | + public Boolean getTcp() { | |
| 167 | + return isTcp; | |
| 168 | + } | |
| 169 | + | |
| 170 | + public void setTcp(Boolean tcp) { | |
| 171 | + isTcp = tcp; | |
| 172 | + } | |
| 173 | +} | ... | ... |
src/main/java/com/genersoft/iot/vmp/service/bean/ResponseSendItemMsg.java
0 → 100644
| 1 | +package com.genersoft.iot.vmp.service.bean; | |
| 2 | + | |
| 3 | +import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem; | |
| 4 | +import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; | |
| 5 | + | |
| 6 | +/** | |
| 7 | + * redis消息:下级回复推送信息 | |
| 8 | + * @author lin | |
| 9 | + */ | |
| 10 | +public class ResponseSendItemMsg { | |
| 11 | + | |
| 12 | + private SendRtpItem sendRtpItem; | |
| 13 | + | |
| 14 | + private MediaServerItem mediaServerItem; | |
| 15 | + | |
| 16 | + public SendRtpItem getSendRtpItem() { | |
| 17 | + return sendRtpItem; | |
| 18 | + } | |
| 19 | + | |
| 20 | + public void setSendRtpItem(SendRtpItem sendRtpItem) { | |
| 21 | + this.sendRtpItem = sendRtpItem; | |
| 22 | + } | |
| 23 | + | |
| 24 | + public MediaServerItem getMediaServerItem() { | |
| 25 | + return mediaServerItem; | |
| 26 | + } | |
| 27 | + | |
| 28 | + public void setMediaServerItem(MediaServerItem mediaServerItem) { | |
| 29 | + this.mediaServerItem = mediaServerItem; | |
| 30 | + } | |
| 31 | +} | ... | ... |
src/main/java/com/genersoft/iot/vmp/service/bean/WvpRedisMsg.java
0 → 100644
| 1 | +package com.genersoft.iot.vmp.service.bean; | |
| 2 | + | |
| 3 | +/** | |
| 4 | + * @author lin | |
| 5 | + */ | |
| 6 | +public class WvpRedisMsg { | |
| 7 | + | |
| 8 | + public static WvpRedisMsg getInstance(String fromId, String toId, String type, String cmd, String serial, String content){ | |
| 9 | + WvpRedisMsg wvpRedisMsg = new WvpRedisMsg(); | |
| 10 | + wvpRedisMsg.setFromId(fromId); | |
| 11 | + wvpRedisMsg.setToId(toId); | |
| 12 | + wvpRedisMsg.setType(type); | |
| 13 | + wvpRedisMsg.setCmd(cmd); | |
| 14 | + wvpRedisMsg.setSerial(serial); | |
| 15 | + wvpRedisMsg.setContent(content); | |
| 16 | + return wvpRedisMsg; | |
| 17 | + } | |
| 18 | + | |
| 19 | + private String fromId; | |
| 20 | + | |
| 21 | + private String toId; | |
| 22 | + /** | |
| 23 | + * req 请求, res 回复 | |
| 24 | + */ | |
| 25 | + private String type; | |
| 26 | + private String cmd; | |
| 27 | + | |
| 28 | + /** | |
| 29 | + * 消息的ID | |
| 30 | + */ | |
| 31 | + private String serial; | |
| 32 | + private Object content; | |
| 33 | + | |
| 34 | + private final static String requestTag = "req"; | |
| 35 | + private final static String responseTag = "res"; | |
| 36 | + | |
| 37 | + public static WvpRedisMsg getRequestInstance(String fromId, String toId, String cmd, String serial, Object content) { | |
| 38 | + WvpRedisMsg wvpRedisMsg = new WvpRedisMsg(); | |
| 39 | + wvpRedisMsg.setType(requestTag); | |
| 40 | + wvpRedisMsg.setFromId(fromId); | |
| 41 | + wvpRedisMsg.setToId(toId); | |
| 42 | + wvpRedisMsg.setCmd(cmd); | |
| 43 | + wvpRedisMsg.setSerial(serial); | |
| 44 | + wvpRedisMsg.setContent(content); | |
| 45 | + return wvpRedisMsg; | |
| 46 | + } | |
| 47 | + | |
| 48 | + public static WvpRedisMsg getResponseInstance() { | |
| 49 | + WvpRedisMsg wvpRedisMsg = new WvpRedisMsg(); | |
| 50 | + wvpRedisMsg.setType(responseTag); | |
| 51 | + return wvpRedisMsg; | |
| 52 | + } | |
| 53 | + | |
| 54 | + public static WvpRedisMsg getResponseInstance(String fromId, String toId, String cmd, String serial, Object content) { | |
| 55 | + WvpRedisMsg wvpRedisMsg = new WvpRedisMsg(); | |
| 56 | + wvpRedisMsg.setType(responseTag); | |
| 57 | + wvpRedisMsg.setFromId(fromId); | |
| 58 | + wvpRedisMsg.setToId(toId); | |
| 59 | + wvpRedisMsg.setCmd(cmd); | |
| 60 | + wvpRedisMsg.setSerial(serial); | |
| 61 | + wvpRedisMsg.setContent(content); | |
| 62 | + return wvpRedisMsg; | |
| 63 | + } | |
| 64 | + | |
| 65 | + public static boolean isRequest(WvpRedisMsg wvpRedisMsg) { | |
| 66 | + return requestTag.equals(wvpRedisMsg.getType()); | |
| 67 | + } | |
| 68 | + | |
| 69 | + public String getSerial() { | |
| 70 | + return serial; | |
| 71 | + } | |
| 72 | + | |
| 73 | + public void setSerial(String serial) { | |
| 74 | + this.serial = serial; | |
| 75 | + } | |
| 76 | + | |
| 77 | + public String getFromId() { | |
| 78 | + return fromId; | |
| 79 | + } | |
| 80 | + | |
| 81 | + public void setFromId(String fromId) { | |
| 82 | + this.fromId = fromId; | |
| 83 | + } | |
| 84 | + | |
| 85 | + public String getToId() { | |
| 86 | + return toId; | |
| 87 | + } | |
| 88 | + | |
| 89 | + public void setToId(String toId) { | |
| 90 | + this.toId = toId; | |
| 91 | + } | |
| 92 | + | |
| 93 | + public String getType() { | |
| 94 | + return type; | |
| 95 | + } | |
| 96 | + | |
| 97 | + public void setType(String type) { | |
| 98 | + this.type = type; | |
| 99 | + } | |
| 100 | + | |
| 101 | + public String getCmd() { | |
| 102 | + return cmd; | |
| 103 | + } | |
| 104 | + | |
| 105 | + public void setCmd(String cmd) { | |
| 106 | + this.cmd = cmd; | |
| 107 | + } | |
| 108 | + | |
| 109 | + public Object getContent() { | |
| 110 | + return content; | |
| 111 | + } | |
| 112 | + | |
| 113 | + public void setContent(Object content) { | |
| 114 | + this.content = content; | |
| 115 | + } | |
| 116 | +} | ... | ... |
src/main/java/com/genersoft/iot/vmp/service/bean/WvpRedisMsgCmd.java
0 → 100644
src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java
| ... | ... | @@ -2,16 +2,21 @@ package com.genersoft.iot.vmp.service.impl; |
| 2 | 2 | |
| 3 | 3 | import com.genersoft.iot.vmp.conf.DynamicTask; |
| 4 | 4 | import com.genersoft.iot.vmp.gb28181.bean.Device; |
| 5 | +import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; | |
| 5 | 6 | import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction; |
| 6 | 7 | import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; |
| 8 | +import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask; | |
| 7 | 9 | import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander; |
| 8 | 10 | import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd.CatalogResponseMessageHandler; |
| 11 | +import com.genersoft.iot.vmp.gb28181.utils.Coordtransform; | |
| 9 | 12 | import com.genersoft.iot.vmp.service.IDeviceService; |
| 10 | 13 | import com.genersoft.iot.vmp.gb28181.task.impl.CatalogSubscribeTask; |
| 11 | 14 | import com.genersoft.iot.vmp.gb28181.task.impl.MobilePositionSubscribeTask; |
| 12 | 15 | import com.genersoft.iot.vmp.gb28181.bean.SyncStatus; |
| 13 | 16 | import com.genersoft.iot.vmp.service.IMediaServerService; |
| 14 | 17 | import com.genersoft.iot.vmp.storager.IRedisCatchStorage; |
| 18 | +import com.genersoft.iot.vmp.storager.IVideoManagerStorage; | |
| 19 | +import com.genersoft.iot.vmp.storager.dao.DeviceChannelMapper; | |
| 15 | 20 | import com.genersoft.iot.vmp.storager.dao.DeviceMapper; |
| 16 | 21 | import com.genersoft.iot.vmp.utils.DateUtil; |
| 17 | 22 | import org.slf4j.Logger; |
| ... | ... | @@ -50,6 +55,12 @@ public class DeviceServiceImpl implements IDeviceService { |
| 50 | 55 | private DeviceMapper deviceMapper; |
| 51 | 56 | |
| 52 | 57 | @Autowired |
| 58 | + private DeviceChannelMapper deviceChannelMapper; | |
| 59 | + | |
| 60 | + @Autowired | |
| 61 | + private IVideoManagerStorage storage; | |
| 62 | + | |
| 63 | + @Autowired | |
| 53 | 64 | private ISIPCommander commander; |
| 54 | 65 | |
| 55 | 66 | @Autowired |
| ... | ... | @@ -68,7 +79,6 @@ public class DeviceServiceImpl implements IDeviceService { |
| 68 | 79 | if (deviceInRedis != null && deviceInDb == null) { |
| 69 | 80 | // redis 存在脏数据 |
| 70 | 81 | redisCatchStorage.clearCatchByDeviceId(device.getDeviceId()); |
| 71 | - | |
| 72 | 82 | } |
| 73 | 83 | device.setUpdateTime(now); |
| 74 | 84 | device.setOnline(1); |
| ... | ... | @@ -77,13 +87,15 @@ public class DeviceServiceImpl implements IDeviceService { |
| 77 | 87 | if (device.getCreateTime() == null) { |
| 78 | 88 | device.setCreateTime(now); |
| 79 | 89 | logger.info("[设备上线,首次注册]: {},查询设备信息以及通道信息", device.getDeviceId()); |
| 90 | + deviceMapper.add(device); | |
| 91 | + redisCatchStorage.updateDevice(device); | |
| 80 | 92 | commander.deviceInfoQuery(device); |
| 81 | 93 | sync(device); |
| 82 | - deviceMapper.add(device); | |
| 83 | 94 | }else { |
| 84 | 95 | deviceMapper.update(device); |
| 96 | + redisCatchStorage.updateDevice(device); | |
| 85 | 97 | } |
| 86 | - redisCatchStorage.updateDevice(device); | |
| 98 | + | |
| 87 | 99 | // 上线添加订阅 |
| 88 | 100 | if (device.getSubscribeCycleForCatalog() > 0) { |
| 89 | 101 | // 查询在线设备那些开启了订阅,为设备开启定时的目录订阅 |
| ... | ... | @@ -94,7 +106,6 @@ public class DeviceServiceImpl implements IDeviceService { |
| 94 | 106 | } |
| 95 | 107 | // 刷新过期任务 |
| 96 | 108 | String registerExpireTaskKey = registerExpireTaskKeyPrefix + device.getDeviceId(); |
| 97 | - dynamicTask.stop(registerExpireTaskKey); | |
| 98 | 109 | dynamicTask.startDelay(registerExpireTaskKey, ()-> offline(device.getDeviceId()), device.getExpires() * 1000); |
| 99 | 110 | } |
| 100 | 111 | |
| ... | ... | @@ -143,8 +154,16 @@ public class DeviceServiceImpl implements IDeviceService { |
| 143 | 154 | if (device == null || device.getSubscribeCycleForCatalog() < 0) { |
| 144 | 155 | return false; |
| 145 | 156 | } |
| 146 | - logger.info("移除目录订阅: {}", device.getDeviceId()); | |
| 147 | - dynamicTask.stop(device.getDeviceId() + "catalog"); | |
| 157 | + logger.info("[移除目录订阅]: {}", device.getDeviceId()); | |
| 158 | + String taskKey = device.getDeviceId() + "catalog"; | |
| 159 | + if (device.getOnline() == 1) { | |
| 160 | + Runnable runnable = dynamicTask.get(taskKey); | |
| 161 | + if (runnable instanceof ISubscribeTask) { | |
| 162 | + ISubscribeTask subscribeTask = (ISubscribeTask) runnable; | |
| 163 | + subscribeTask.stop(); | |
| 164 | + } | |
| 165 | + } | |
| 166 | + dynamicTask.stop(taskKey); | |
| 148 | 167 | return true; |
| 149 | 168 | } |
| 150 | 169 | |
| ... | ... | @@ -168,8 +187,16 @@ public class DeviceServiceImpl implements IDeviceService { |
| 168 | 187 | if (device == null || device.getSubscribeCycleForCatalog() < 0) { |
| 169 | 188 | return false; |
| 170 | 189 | } |
| 171 | - logger.info("移除移动位置订阅: {}", device.getDeviceId()); | |
| 172 | - dynamicTask.stop(device.getDeviceId() + "mobile_position"); | |
| 190 | + logger.info("[移除移动位置订阅]: {}", device.getDeviceId()); | |
| 191 | + String taskKey = device.getDeviceId() + "mobile_position"; | |
| 192 | + if (device.getOnline() == 1) { | |
| 193 | + Runnable runnable = dynamicTask.get(taskKey); | |
| 194 | + if (runnable instanceof ISubscribeTask) { | |
| 195 | + ISubscribeTask subscribeTask = (ISubscribeTask) runnable; | |
| 196 | + subscribeTask.stop(); | |
| 197 | + } | |
| 198 | + } | |
| 199 | + dynamicTask.stop(taskKey); | |
| 173 | 200 | return true; |
| 174 | 201 | } |
| 175 | 202 | |
| ... | ... | @@ -275,6 +302,10 @@ public class DeviceServiceImpl implements IDeviceService { |
| 275 | 302 | removeMobilePositionSubscribe(deviceInStore); |
| 276 | 303 | } |
| 277 | 304 | } |
| 305 | + // 坐标系变化,需要重新计算GCJ02坐标和WGS84坐标 | |
| 306 | + if (!deviceInStore.getGeoCoordSys().equals(device.getGeoCoordSys())) { | |
| 307 | + updateDeviceChannelGeoCoordSys(device); | |
| 308 | + } | |
| 278 | 309 | |
| 279 | 310 | String now = DateUtil.getNow(); |
| 280 | 311 | device.setUpdateTime(now); |
| ... | ... | @@ -282,6 +313,32 @@ public class DeviceServiceImpl implements IDeviceService { |
| 282 | 313 | device.setUpdateTime(DateUtil.getNow()); |
| 283 | 314 | if (deviceMapper.update(device) > 0) { |
| 284 | 315 | redisCatchStorage.updateDevice(device); |
| 316 | + | |
| 285 | 317 | } |
| 286 | 318 | } |
| 319 | + | |
| 320 | + /** | |
| 321 | + * 更新通道坐标系 | |
| 322 | + */ | |
| 323 | + private void updateDeviceChannelGeoCoordSys(Device device) { | |
| 324 | + List<DeviceChannel> deviceChannels = deviceChannelMapper.getAllChannelWithCoordinate(device.getDeviceId()); | |
| 325 | + if (deviceChannels.size() > 0) { | |
| 326 | + for (DeviceChannel deviceChannel : deviceChannels) { | |
| 327 | + if ("WGS84".equals(device.getGeoCoordSys())) { | |
| 328 | + deviceChannel.setLongitudeWgs84(deviceChannel.getLongitude()); | |
| 329 | + deviceChannel.setLatitudeWgs84(deviceChannel.getLatitude()); | |
| 330 | + Double[] position = Coordtransform.WGS84ToGCJ02(deviceChannel.getLongitude(), deviceChannel.getLatitude()); | |
| 331 | + deviceChannel.setLongitudeGcj02(position[0]); | |
| 332 | + deviceChannel.setLatitudeGcj02(position[1]); | |
| 333 | + }else if ("GCJ02".equals(device.getGeoCoordSys())) { | |
| 334 | + deviceChannel.setLongitudeGcj02(deviceChannel.getLongitude()); | |
| 335 | + deviceChannel.setLatitudeGcj02(deviceChannel.getLatitude()); | |
| 336 | + Double[] position = Coordtransform.GCJ02ToWGS84(deviceChannel.getLongitude(), deviceChannel.getLatitude()); | |
| 337 | + deviceChannel.setLongitudeWgs84(position[0]); | |
| 338 | + deviceChannel.setLatitudeWgs84(position[1]); | |
| 339 | + } | |
| 340 | + } | |
| 341 | + } | |
| 342 | + storage.updateChannels(device.getDeviceId(), deviceChannels); | |
| 343 | + } | |
| 287 | 344 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/service/impl/GbStreamServiceImpl.java
| ... | ... | @@ -106,7 +106,8 @@ public class GbStreamServiceImpl implements IGbStreamService { |
| 106 | 106 | deviceChannel.setStatus(1); |
| 107 | 107 | deviceChannel.setParentId(catalogId ==null?gbStream.getCatalogId():catalogId); |
| 108 | 108 | deviceChannel.setRegisterWay(1); |
| 109 | - if (catalogId.length() <= 10) { // 父节点是行政区划,则设置CivilCode使用此行政区划 | |
| 109 | + if (catalogId.length() > 0 && catalogId.length() <= 10) { | |
| 110 | + // 父节点是行政区划,则设置CivilCode使用此行政区划 | |
| 110 | 111 | deviceChannel.setCivilCode(catalogId); |
| 111 | 112 | }else { |
| 112 | 113 | deviceChannel.setCivilCode(platform.getAdministrativeDivision()); | ... | ... |
src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java
| ... | ... | @@ -6,6 +6,7 @@ import com.alibaba.fastjson.JSONObject; |
| 6 | 6 | import com.genersoft.iot.vmp.common.VideoManagerConstants; |
| 7 | 7 | import com.genersoft.iot.vmp.conf.SipConfig; |
| 8 | 8 | import com.genersoft.iot.vmp.conf.UserSetting; |
| 9 | +import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction; | |
| 9 | 10 | import com.genersoft.iot.vmp.gb28181.event.EventPublisher; |
| 10 | 11 | import com.genersoft.iot.vmp.gb28181.session.SsrcConfig; |
| 11 | 12 | import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; |
| ... | ... | @@ -35,7 +36,9 @@ import org.springframework.util.StringUtils; |
| 35 | 36 | |
| 36 | 37 | import java.text.ParseException; |
| 37 | 38 | import java.text.SimpleDateFormat; |
| 39 | +import java.time.LocalDateTime; | |
| 38 | 40 | import java.util.*; |
| 41 | +import java.util.stream.Collectors; | |
| 39 | 42 | |
| 40 | 43 | /** |
| 41 | 44 | * 媒体服务器节点管理 |
| ... | ... | @@ -189,6 +192,7 @@ public class MediaServerServiceImpl implements IMediaServerService { |
| 189 | 192 | public void clearRTPServer(MediaServerItem mediaServerItem) { |
| 190 | 193 | mediaServerItem.setSsrcConfig(new SsrcConfig(mediaServerItem.getId(), null, sipConfig.getDomain())); |
| 191 | 194 | redisUtil.zAdd(VideoManagerConstants.MEDIA_SERVERS_ONLINE_PREFIX + userSetting.getServerId(), mediaServerItem.getId(), 0); |
| 195 | + | |
| 192 | 196 | } |
| 193 | 197 | |
| 194 | 198 | |
| ... | ... | @@ -229,11 +233,10 @@ public class MediaServerServiceImpl implements IMediaServerService { |
| 229 | 233 | } |
| 230 | 234 | result.sort((serverItem1, serverItem2)->{ |
| 231 | 235 | int sortResult = 0; |
| 232 | - try { | |
| 233 | - sortResult = DateUtil.format.parse(serverItem1.getCreateTime()).compareTo(DateUtil.format.parse(serverItem2.getCreateTime())); | |
| 234 | - } catch (ParseException e) { | |
| 235 | - e.printStackTrace(); | |
| 236 | - } | |
| 236 | + LocalDateTime localDateTime1 = LocalDateTime.parse(serverItem1.getCreateTime(), DateUtil.formatter); | |
| 237 | + LocalDateTime localDateTime2 = LocalDateTime.parse(serverItem2.getCreateTime(), DateUtil.formatter); | |
| 238 | + | |
| 239 | + sortResult = localDateTime1.compareTo(localDateTime2); | |
| 237 | 240 | return sortResult; |
| 238 | 241 | }); |
| 239 | 242 | return result; |
| ... | ... | @@ -495,14 +498,14 @@ public class MediaServerServiceImpl implements IMediaServerService { |
| 495 | 498 | param.put("api.secret",mediaServerItem.getSecret()); // -profile:v Baseline |
| 496 | 499 | param.put("ffmpeg.cmd","%s -fflags nobuffer -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s"); |
| 497 | 500 | param.put("hook.enable","1"); |
| 498 | - param.put("hook.on_flow_report",""); | |
| 501 | + param.put("hook.on_flow_report",String.format("%s/on_flow_report", hookPrex)); | |
| 499 | 502 | param.put("hook.on_play",String.format("%s/on_play", hookPrex)); |
| 500 | - param.put("hook.on_http_access",""); | |
| 503 | + param.put("hook.on_http_access",String.format("%s/on_http_access", hookPrex)); | |
| 501 | 504 | param.put("hook.on_publish", String.format("%s/on_publish", hookPrex)); |
| 502 | 505 | param.put("hook.on_record_mp4",recordHookPrex != null? String.format("%s/on_record_mp4", recordHookPrex): ""); |
| 503 | - param.put("hook.on_record_ts",""); | |
| 504 | - param.put("hook.on_rtsp_auth",""); | |
| 505 | - param.put("hook.on_rtsp_realm",""); | |
| 506 | + param.put("hook.on_record_ts",String.format("%s/on_record_ts", hookPrex)); | |
| 507 | + param.put("hook.on_rtsp_auth",String.format("%s/on_rtsp_auth", hookPrex)); | |
| 508 | + param.put("hook.on_rtsp_realm",String.format("%s/on_rtsp_realm", hookPrex)); | |
| 506 | 509 | param.put("hook.on_server_started",String.format("%s/on_server_started", hookPrex)); |
| 507 | 510 | param.put("hook.on_shell_login",String.format("%s/on_shell_login", hookPrex)); |
| 508 | 511 | param.put("hook.on_stream_changed",String.format("%s/on_stream_changed", hookPrex)); | ... | ... |
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
| ... | ... | @@ -21,6 +21,8 @@ import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe; |
| 21 | 21 | import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils; |
| 22 | 22 | import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; |
| 23 | 23 | import com.genersoft.iot.vmp.service.IMediaServerService; |
| 24 | +import com.genersoft.iot.vmp.service.IMediaService; | |
| 25 | +import com.genersoft.iot.vmp.service.IPlayService; | |
| 24 | 26 | import com.genersoft.iot.vmp.service.bean.InviteTimeOutCallback; |
| 25 | 27 | import com.genersoft.iot.vmp.service.bean.PlayBackCallback; |
| 26 | 28 | import com.genersoft.iot.vmp.service.bean.PlayBackResult; |
| ... | ... | @@ -32,8 +34,6 @@ import com.genersoft.iot.vmp.vmanager.bean.AudioBroadcastResult; |
| 32 | 34 | import com.genersoft.iot.vmp.vmanager.bean.WVPResult; |
| 33 | 35 | import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.AudioBroadcastEvent; |
| 34 | 36 | import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.PlayResult; |
| 35 | -import com.genersoft.iot.vmp.service.IMediaService; | |
| 36 | -import com.genersoft.iot.vmp.service.IPlayService; | |
| 37 | 37 | import gov.nist.javax.sip.stack.SIPDialog; |
| 38 | 38 | import org.slf4j.Logger; |
| 39 | 39 | import org.slf4j.LoggerFactory; |
| ... | ... | @@ -49,6 +49,7 @@ import javax.sip.SipException; |
| 49 | 49 | import java.io.FileNotFoundException; |
| 50 | 50 | import java.math.BigDecimal; |
| 51 | 51 | import java.text.ParseException; |
| 52 | +import java.math.RoundingMode; | |
| 52 | 53 | import java.util.*; |
| 53 | 54 | import java.util.stream.Collectors; |
| 54 | 55 | import java.util.stream.Stream; |
| ... | ... | @@ -75,9 +76,6 @@ public class PlayServiceImpl implements IPlayService { |
| 75 | 76 | private IRedisCatchStorage redisCatchStorage; |
| 76 | 77 | |
| 77 | 78 | @Autowired |
| 78 | - private RedisUtil redis; | |
| 79 | - | |
| 80 | - @Autowired | |
| 81 | 79 | private DeferredResultHolder resultHolder; |
| 82 | 80 | |
| 83 | 81 | @Autowired |
| ... | ... | @@ -140,36 +138,19 @@ public class PlayServiceImpl implements IPlayService { |
| 140 | 138 | result.onCompletion(()->{ |
| 141 | 139 | // 点播结束时调用截图接口 |
| 142 | 140 | // TODO 应该在上流时调用更好,结束也可能是错误结束 |
| 143 | - try { | |
| 144 | - String classPath = ResourceUtils.getURL("classpath:").getPath(); | |
| 145 | - // 兼容打包为jar的class路径 | |
| 146 | - if(classPath.contains("jar")) { | |
| 147 | - classPath = classPath.substring(0, classPath.lastIndexOf(".")); | |
| 148 | - classPath = classPath.substring(0, classPath.lastIndexOf("/") + 1); | |
| 149 | - } | |
| 150 | - if (classPath.startsWith("file:")) { | |
| 151 | - classPath = classPath.substring(classPath.indexOf(":") + 1); | |
| 141 | + String path = "static/static/snap/"; | |
| 142 | + String fileName = deviceId + "_" + channelId + ".jpg"; | |
| 143 | + ResponseEntity responseEntity = (ResponseEntity)result.getResult(); | |
| 144 | + if (responseEntity != null && responseEntity.getStatusCode() == HttpStatus.OK) { | |
| 145 | + WVPResult wvpResult = (WVPResult)responseEntity.getBody(); | |
| 146 | + if (Objects.requireNonNull(wvpResult).getCode() == 0) { | |
| 147 | + StreamInfo streamInfoForSuccess = (StreamInfo)wvpResult.getData(); | |
| 148 | + MediaServerItem mediaInfo = mediaServerService.getOne(streamInfoForSuccess.getMediaServerId()); | |
| 149 | + String streamUrl = streamInfoForSuccess.getFmp4(); | |
| 150 | + // 请求截图 | |
| 151 | + logger.info("[请求截图]: " + fileName); | |
| 152 | + zlmresTfulUtils.getSnap(mediaInfo, streamUrl, 15, 1, path, fileName); | |
| 152 | 153 | } |
| 153 | - String path = classPath + "static/static/snap/"; | |
| 154 | - // 兼容Windows系统路径(去除前面的“/”) | |
| 155 | - if(System.getProperty("os.name").contains("indows")) { | |
| 156 | - path = path.substring(1); | |
| 157 | - } | |
| 158 | - String fileName = deviceId + "_" + channelId + ".jpg"; | |
| 159 | - ResponseEntity responseEntity = (ResponseEntity)result.getResult(); | |
| 160 | - if (responseEntity != null && responseEntity.getStatusCode() == HttpStatus.OK) { | |
| 161 | - WVPResult wvpResult = (WVPResult)responseEntity.getBody(); | |
| 162 | - if (Objects.requireNonNull(wvpResult).getCode() == 0) { | |
| 163 | - StreamInfo streamInfoForSuccess = (StreamInfo)wvpResult.getData(); | |
| 164 | - MediaServerItem mediaInfo = mediaServerService.getOne(streamInfoForSuccess.getMediaServerId()); | |
| 165 | - String streamUrl = streamInfoForSuccess.getFmp4(); | |
| 166 | - // 请求截图 | |
| 167 | - logger.info("[请求截图]: " + fileName); | |
| 168 | - zlmresTfulUtils.getSnap(mediaInfo, streamUrl, 15, 1, path, fileName); | |
| 169 | - } | |
| 170 | - } | |
| 171 | - } catch (FileNotFoundException e) { | |
| 172 | - e.printStackTrace(); | |
| 173 | 154 | } |
| 174 | 155 | }); |
| 175 | 156 | if (streamInfo != null) { |
| ... | ... | @@ -186,24 +167,33 @@ public class PlayServiceImpl implements IPlayService { |
| 186 | 167 | MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId); |
| 187 | 168 | |
| 188 | 169 | JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaInfo, streamId); |
| 189 | - if (rtpInfo != null && rtpInfo.getBoolean("exist")) { | |
| 170 | + if(rtpInfo.getInteger("code") == 0){ | |
| 171 | + if (rtpInfo.getBoolean("exist")) { | |
| 190 | 172 | |
| 191 | - WVPResult wvpResult = new WVPResult(); | |
| 192 | - wvpResult.setCode(0); | |
| 193 | - wvpResult.setMsg("success"); | |
| 194 | - wvpResult.setData(streamInfo); | |
| 195 | - msg.setData(wvpResult); | |
| 173 | + WVPResult wvpResult = new WVPResult(); | |
| 174 | + wvpResult.setCode(0); | |
| 175 | + wvpResult.setMsg("success"); | |
| 176 | + wvpResult.setData(streamInfo); | |
| 177 | + msg.setData(wvpResult); | |
| 196 | 178 | |
| 197 | - resultHolder.invokeAllResult(msg); | |
| 198 | - if (hookEvent != null) { | |
| 199 | - hookEvent.response(mediaServerItem, JSONObject.parseObject(JSON.toJSONString(streamInfo))); | |
| 179 | + resultHolder.invokeAllResult(msg); | |
| 180 | + if (hookEvent != null) { | |
| 181 | + hookEvent.response(mediaServerItem, JSONObject.parseObject(JSON.toJSONString(streamInfo))); | |
| 182 | + } | |
| 183 | + }else { | |
| 184 | + redisCatchStorage.stopPlay(streamInfo); | |
| 185 | + storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId()); | |
| 186 | + streamInfo = null; | |
| 200 | 187 | } |
| 201 | 188 | }else { |
| 189 | + //zlm连接失败 | |
| 202 | 190 | redisCatchStorage.stopPlay(streamInfo); |
| 203 | 191 | storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId()); |
| 204 | 192 | streamInfo = null; |
| 193 | + | |
| 205 | 194 | } |
| 206 | 195 | |
| 196 | + | |
| 207 | 197 | } |
| 208 | 198 | if (streamInfo == null) { |
| 209 | 199 | String streamId = null; |
| ... | ... | @@ -256,33 +246,41 @@ public class PlayServiceImpl implements IPlayService { |
| 256 | 246 | if (ssrcInfo == null) { |
| 257 | 247 | ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, device.isSsrcCheck(), false); |
| 258 | 248 | } |
| 259 | - | |
| 249 | + logger.info("[点播开始] deviceId: {}, channelId: {}, SSRC: {}", device.getDeviceId(), channelId, ssrcInfo.getSsrc() ); | |
| 260 | 250 | // 超时处理 |
| 261 | 251 | String timeOutTaskKey = UUID.randomUUID().toString(); |
| 262 | 252 | SSRCInfo finalSsrcInfo = ssrcInfo; |
| 263 | 253 | dynamicTask.startDelay( timeOutTaskKey,()->{ |
| 264 | - logger.warn(String.format("设备点播超时,deviceId:%s ,channelId:%s", device.getDeviceId(), channelId)); | |
| 265 | 254 | |
| 266 | 255 | SIPDialog dialog = streamSession.getDialogByStream(device.getDeviceId(), channelId, finalSsrcInfo.getStream()); |
| 267 | 256 | if (dialog != null) { |
| 257 | + logger.info("[点播超时] 收流超时 deviceId: {}, channelId: {}", device.getDeviceId(), channelId); | |
| 268 | 258 | timeoutCallback.run(1, "收流超时"); |
| 269 | 259 | // 点播超时回复BYE 同时释放ssrc以及此次点播的资源 |
| 270 | 260 | cmder.streamByeCmd(device.getDeviceId(), channelId, finalSsrcInfo.getStream(), null); |
| 271 | 261 | }else { |
| 262 | + logger.info("[点播超时] 消息未响应 deviceId: {}, channelId: {}", device.getDeviceId(), channelId); | |
| 272 | 263 | timeoutCallback.run(0, "点播超时"); |
| 273 | 264 | mediaServerService.releaseSsrc(mediaServerItem.getId(), finalSsrcInfo.getSsrc()); |
| 274 | 265 | mediaServerService.closeRTPServer(device.getDeviceId(), channelId, finalSsrcInfo.getStream()); |
| 275 | 266 | streamSession.remove(device.getDeviceId(), channelId, finalSsrcInfo.getStream()); |
| 276 | 267 | } |
| 277 | - }, userSetting.getPlayTimeout()*1000); | |
| 268 | + }, userSetting.getPlayTimeout()); | |
| 278 | 269 | final String ssrc = ssrcInfo.getSsrc(); |
| 279 | 270 | final String stream = ssrcInfo.getStream(); |
| 271 | + //端口获取失败的ssrcInfo 没有必要发送点播指令 | |
| 272 | + if(ssrcInfo.getPort() <= 0){ | |
| 273 | + logger.info("[点播端口分配异常],deviceId={},channelId={},ssrcInfo={}", device.getDeviceId(), channelId, ssrcInfo); | |
| 274 | + return; | |
| 275 | + } | |
| 280 | 276 | cmder.playStreamCmd(mediaServerItem, ssrcInfo, device, channelId, (MediaServerItem mediaServerItemInuse, JSONObject response) -> { |
| 281 | 277 | logger.info("收到订阅消息: " + response.toJSONString()); |
| 282 | 278 | dynamicTask.stop(timeOutTaskKey); |
| 283 | 279 | // hook响应 |
| 284 | 280 | onPublishHandlerForPlay(mediaServerItemInuse, response, device.getDeviceId(), channelId, uuid); |
| 285 | 281 | hookEvent.response(mediaServerItemInuse, response); |
| 282 | + logger.info("[点播成功] deviceId: {}, channelId: {}", device.getDeviceId(), channelId); | |
| 283 | + | |
| 286 | 284 | }, (event) -> { |
| 287 | 285 | ResponseEvent responseEvent = (ResponseEvent)event.event; |
| 288 | 286 | String contentString = new String(responseEvent.getResponse().getRawContent()); |
| ... | ... | @@ -296,8 +294,10 @@ public class PlayServiceImpl implements IPlayService { |
| 296 | 294 | if (ssrc.equals(ssrcInResponse)) { |
| 297 | 295 | return; |
| 298 | 296 | } |
| 299 | - logger.info("[SIP 消息] 收到invite 200, 发现下级自定义了ssrc 开启修正"); | |
| 297 | + logger.info("[点播消息] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse ); | |
| 300 | 298 | if (!mediaServerItem.isRtpEnable() || device.isSsrcCheck()) { |
| 299 | + logger.info("[SIP 消息] SSRC修正 {}->{}", ssrc, ssrcInResponse); | |
| 300 | + | |
| 301 | 301 | if (!mediaServerItem.getSsrcConfig().checkSsrc(ssrcInResponse)) { |
| 302 | 302 | // ssrc 不可用 |
| 303 | 303 | // 释放ssrc |
| ... | ... | @@ -450,7 +450,7 @@ public class PlayServiceImpl implements IPlayService { |
| 450 | 450 | cmder.streamByeCmd(device.getDeviceId(), channelId, ssrcInfo.getStream(), null); |
| 451 | 451 | // 回复之前所有的点播请求 |
| 452 | 452 | playBackCallback.call(playBackResult); |
| 453 | - }, userSetting.getPlayTimeout()*1000); | |
| 453 | + }, userSetting.getPlayTimeout()); | |
| 454 | 454 | |
| 455 | 455 | cmder.playbackStreamCmd(mediaServerItem, ssrcInfo, device, channelId, startTime, endTime, infoCallBack, |
| 456 | 456 | (InviteStreamInfo inviteStreamInfo) -> { |
| ... | ... | @@ -539,7 +539,7 @@ public class PlayServiceImpl implements IPlayService { |
| 539 | 539 | cmder.streamByeCmd(device.getDeviceId(), channelId, ssrcInfo.getStream(), null); |
| 540 | 540 | // 回复之前所有的点播请求 |
| 541 | 541 | hookCallBack.call(downloadResult); |
| 542 | - }, userSetting.getPlayTimeout()*1000); | |
| 542 | + }, userSetting.getPlayTimeout()); | |
| 543 | 543 | cmder.downloadStreamCmd(mediaServerItem, ssrcInfo, device, channelId, startTime, endTime, downloadSpeed, infoCallBack, |
| 544 | 544 | inviteStreamInfo -> { |
| 545 | 545 | logger.info("收到订阅消息: " + inviteStreamInfo.getResponse().toJSONString()); |
| ... | ... | @@ -605,7 +605,7 @@ public class PlayServiceImpl implements IPlayService { |
| 605 | 605 | |
| 606 | 606 | BigDecimal currentCount = new BigDecimal(duration/1000); |
| 607 | 607 | BigDecimal totalCount = new BigDecimal(end-start); |
| 608 | - BigDecimal divide = currentCount.divide(totalCount,2, BigDecimal.ROUND_HALF_UP); | |
| 608 | + BigDecimal divide = currentCount.divide(totalCount,2, RoundingMode.HALF_UP); | |
| 609 | 609 | double process = divide.doubleValue(); |
| 610 | 610 | streamInfo.setProgress(process); |
| 611 | 611 | } |
| ... | ... | @@ -728,4 +728,9 @@ public class PlayServiceImpl implements IPlayService { |
| 728 | 728 | |
| 729 | 729 | |
| 730 | 730 | } |
| 731 | + | |
| 732 | + @Override | |
| 733 | + public void zlmServerOnline(String mediaServerId) { | |
| 734 | + // 似乎没啥需要做的 | |
| 735 | + } | |
| 731 | 736 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/service/impl/RedisGbPlayMsgListener.java
0 → 100644
| 1 | +package com.genersoft.iot.vmp.service.impl; | |
| 2 | + | |
| 3 | +import com.alibaba.fastjson.JSON; | |
| 4 | +import com.alibaba.fastjson.JSONObject; | |
| 5 | +import com.genersoft.iot.vmp.conf.DynamicTask; | |
| 6 | +import com.genersoft.iot.vmp.conf.UserSetting; | |
| 7 | +import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem; | |
| 8 | +import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe; | |
| 9 | +import com.genersoft.iot.vmp.media.zlm.ZLMMediaListManager; | |
| 10 | +import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory; | |
| 11 | +import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; | |
| 12 | +import com.genersoft.iot.vmp.service.IMediaServerService; | |
| 13 | +import com.genersoft.iot.vmp.service.bean.*; | |
| 14 | +import com.genersoft.iot.vmp.storager.IRedisCatchStorage; | |
| 15 | +import com.genersoft.iot.vmp.utils.redis.RedisUtil; | |
| 16 | +import com.genersoft.iot.vmp.vmanager.bean.WVPResult; | |
| 17 | +import org.slf4j.Logger; | |
| 18 | +import org.slf4j.LoggerFactory; | |
| 19 | +import org.springframework.beans.factory.annotation.Autowired; | |
| 20 | +import org.springframework.data.redis.connection.Message; | |
| 21 | +import org.springframework.data.redis.connection.MessageListener; | |
| 22 | +import org.springframework.stereotype.Component; | |
| 23 | + | |
| 24 | +import javax.sip.InvalidArgumentException; | |
| 25 | +import javax.sip.SipException; | |
| 26 | +import java.text.ParseException; | |
| 27 | +import java.util.HashMap; | |
| 28 | +import java.util.Map; | |
| 29 | +import java.util.UUID; | |
| 30 | +import java.util.concurrent.ConcurrentHashMap; | |
| 31 | + | |
| 32 | + | |
| 33 | +/** | |
| 34 | + * 监听下级发送推送信息,并发送国标推流消息上级 | |
| 35 | + * @author lin | |
| 36 | + */ | |
| 37 | +@Component | |
| 38 | +public class RedisGbPlayMsgListener implements MessageListener { | |
| 39 | + | |
| 40 | + private final static Logger logger = LoggerFactory.getLogger(RedisGbPlayMsgListener.class); | |
| 41 | + | |
| 42 | + public static final String WVP_PUSH_STREAM_KEY = "WVP_PUSH_STREAM"; | |
| 43 | + | |
| 44 | + /** | |
| 45 | + * 流媒体不存在的错误玛 | |
| 46 | + */ | |
| 47 | + public static final int ERROR_CODE_MEDIA_SERVER_NOT_FOUND = -1; | |
| 48 | + | |
| 49 | + /** | |
| 50 | + * 离线的错误玛 | |
| 51 | + */ | |
| 52 | + public static final int ERROR_CODE_OFFLINE = -2; | |
| 53 | + | |
| 54 | + /** | |
| 55 | + * 超时的错误玛 | |
| 56 | + */ | |
| 57 | + public static final int ERROR_CODE_TIMEOUT = -3; | |
| 58 | + | |
| 59 | + private Map<String, PlayMsgCallback> callbacks = new ConcurrentHashMap<>(); | |
| 60 | + private Map<String, PlayMsgCallbackForStartSendRtpStream> callbacksForStartSendRtpStream = new ConcurrentHashMap<>(); | |
| 61 | + private Map<String, PlayMsgErrorCallback> callbacksForError = new ConcurrentHashMap<>(); | |
| 62 | + | |
| 63 | + @Autowired | |
| 64 | + private UserSetting userSetting; | |
| 65 | + | |
| 66 | + @Autowired | |
| 67 | + private RedisUtil redis; | |
| 68 | + | |
| 69 | + @Autowired | |
| 70 | + private ZLMMediaListManager zlmMediaListManager; | |
| 71 | + | |
| 72 | + @Autowired | |
| 73 | + private ZLMRTPServerFactory zlmrtpServerFactory; | |
| 74 | + | |
| 75 | + @Autowired | |
| 76 | + private IMediaServerService mediaServerService; | |
| 77 | + | |
| 78 | + @Autowired | |
| 79 | + private IRedisCatchStorage redisCatchStorage; | |
| 80 | + | |
| 81 | + @Autowired | |
| 82 | + private DynamicTask dynamicTask; | |
| 83 | + | |
| 84 | + @Autowired | |
| 85 | + private ZLMMediaListManager mediaListManager; | |
| 86 | + | |
| 87 | + @Autowired | |
| 88 | + private ZLMHttpHookSubscribe subscribe; | |
| 89 | + | |
| 90 | + | |
| 91 | + public interface PlayMsgCallback{ | |
| 92 | + void handler(ResponseSendItemMsg responseSendItemMsg); | |
| 93 | + } | |
| 94 | + | |
| 95 | + public interface PlayMsgCallbackForStartSendRtpStream{ | |
| 96 | + void handler(JSONObject jsonObject); | |
| 97 | + } | |
| 98 | + | |
| 99 | + public interface PlayMsgErrorCallback{ | |
| 100 | + void handler(WVPResult wvpResult); | |
| 101 | + } | |
| 102 | + | |
| 103 | + @Override | |
| 104 | + public void onMessage(Message message, byte[] bytes) { | |
| 105 | + JSONObject msgJSON = JSON.parseObject(message.getBody(), JSONObject.class); | |
| 106 | + WvpRedisMsg wvpRedisMsg = JSON.toJavaObject(msgJSON, WvpRedisMsg.class); | |
| 107 | + if (!userSetting.getServerId().equals(wvpRedisMsg.getToId())) { | |
| 108 | + return; | |
| 109 | + } | |
| 110 | + if (WvpRedisMsg.isRequest(wvpRedisMsg)) { | |
| 111 | + logger.info("[收到REDIS通知] 请求: {}", new String(message.getBody())); | |
| 112 | + | |
| 113 | + switch (wvpRedisMsg.getCmd()){ | |
| 114 | + case WvpRedisMsgCmd.GET_SEND_ITEM: | |
| 115 | + RequestSendItemMsg content = JSON.toJavaObject((JSONObject)wvpRedisMsg.getContent(), RequestSendItemMsg.class); | |
| 116 | + requestSendItemMsgHand(content, wvpRedisMsg.getFromId(), wvpRedisMsg.getSerial()); | |
| 117 | + break; | |
| 118 | + case WvpRedisMsgCmd.REQUEST_PUSH_STREAM: | |
| 119 | + RequestPushStreamMsg param = JSON.toJavaObject((JSONObject)wvpRedisMsg.getContent(), RequestPushStreamMsg.class);; | |
| 120 | + requestPushStreamMsgHand(param, wvpRedisMsg.getFromId(), wvpRedisMsg.getSerial()); | |
| 121 | + break; | |
| 122 | + default: | |
| 123 | + break; | |
| 124 | + } | |
| 125 | + | |
| 126 | + }else { | |
| 127 | + logger.info("[收到REDIS通知] 回复: {}", new String(message.getBody())); | |
| 128 | + switch (wvpRedisMsg.getCmd()){ | |
| 129 | + case WvpRedisMsgCmd.GET_SEND_ITEM: | |
| 130 | + | |
| 131 | + WVPResult content = JSON.toJavaObject((JSONObject)wvpRedisMsg.getContent(), WVPResult.class); | |
| 132 | + | |
| 133 | + String key = wvpRedisMsg.getSerial(); | |
| 134 | + switch (content.getCode()) { | |
| 135 | + case 0: | |
| 136 | + ResponseSendItemMsg responseSendItemMsg =JSON.toJavaObject((JSONObject)content.getData(), ResponseSendItemMsg.class); | |
| 137 | + PlayMsgCallback playMsgCallback = callbacks.get(key); | |
| 138 | + if (playMsgCallback != null) { | |
| 139 | + callbacksForError.remove(key); | |
| 140 | + playMsgCallback.handler(responseSendItemMsg); | |
| 141 | + } | |
| 142 | + break; | |
| 143 | + case ERROR_CODE_MEDIA_SERVER_NOT_FOUND: | |
| 144 | + case ERROR_CODE_OFFLINE: | |
| 145 | + case ERROR_CODE_TIMEOUT: | |
| 146 | + PlayMsgErrorCallback errorCallback = callbacksForError.get(key); | |
| 147 | + if (errorCallback != null) { | |
| 148 | + callbacks.remove(key); | |
| 149 | + errorCallback.handler(content); | |
| 150 | + } | |
| 151 | + break; | |
| 152 | + default: | |
| 153 | + break; | |
| 154 | + } | |
| 155 | + break; | |
| 156 | + case WvpRedisMsgCmd.REQUEST_PUSH_STREAM: | |
| 157 | + WVPResult wvpResult = JSON.toJavaObject((JSONObject)wvpRedisMsg.getContent(), WVPResult.class); | |
| 158 | + String serial = wvpRedisMsg.getSerial(); | |
| 159 | + switch (wvpResult.getCode()) { | |
| 160 | + case 0: | |
| 161 | + JSONObject jsonObject = (JSONObject)wvpResult.getData(); | |
| 162 | + PlayMsgCallbackForStartSendRtpStream playMsgCallback = callbacksForStartSendRtpStream.get(serial); | |
| 163 | + if (playMsgCallback != null) { | |
| 164 | + callbacksForError.remove(serial); | |
| 165 | + playMsgCallback.handler(jsonObject); | |
| 166 | + } | |
| 167 | + break; | |
| 168 | + case ERROR_CODE_MEDIA_SERVER_NOT_FOUND: | |
| 169 | + case ERROR_CODE_OFFLINE: | |
| 170 | + case ERROR_CODE_TIMEOUT: | |
| 171 | + PlayMsgErrorCallback errorCallback = callbacksForError.get(serial); | |
| 172 | + if (errorCallback != null) { | |
| 173 | + callbacks.remove(serial); | |
| 174 | + errorCallback.handler(wvpResult); | |
| 175 | + } | |
| 176 | + break; | |
| 177 | + default: | |
| 178 | + break; | |
| 179 | + } | |
| 180 | + break; | |
| 181 | + default: | |
| 182 | + break; | |
| 183 | + } | |
| 184 | + } | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + } | |
| 190 | + | |
| 191 | + /** | |
| 192 | + * 处理收到的请求推流的请求 | |
| 193 | + */ | |
| 194 | + private void requestPushStreamMsgHand(RequestPushStreamMsg requestPushStreamMsg, String fromId, String serial) { | |
| 195 | + MediaServerItem mediaInfo = mediaServerService.getOne(requestPushStreamMsg.getMediaServerId()); | |
| 196 | + if (mediaInfo == null) { | |
| 197 | + // TODO 回复错误 | |
| 198 | + return; | |
| 199 | + } | |
| 200 | + String is_Udp = requestPushStreamMsg.isTcp() ? "0" : "1"; | |
| 201 | + Map<String, Object> param = new HashMap<>(); | |
| 202 | + param.put("vhost","__defaultVhost__"); | |
| 203 | + param.put("app",requestPushStreamMsg.getApp()); | |
| 204 | + param.put("stream",requestPushStreamMsg.getStream()); | |
| 205 | + param.put("ssrc", requestPushStreamMsg.getSsrc()); | |
| 206 | + param.put("dst_url",requestPushStreamMsg.getIp()); | |
| 207 | + param.put("dst_port", requestPushStreamMsg.getPort()); | |
| 208 | + param.put("is_udp", is_Udp); | |
| 209 | + param.put("src_port", requestPushStreamMsg.getSrcPort()); | |
| 210 | + param.put("pt", requestPushStreamMsg.getPt()); | |
| 211 | + param.put("use_ps", requestPushStreamMsg.isPs() ? "1" : "0"); | |
| 212 | + param.put("only_audio", requestPushStreamMsg.isOnlyAudio() ? "1" : "0"); | |
| 213 | + JSONObject jsonObject = zlmrtpServerFactory.startSendRtpStream(mediaInfo, param); | |
| 214 | + // 回复消息 | |
| 215 | + responsePushStream(jsonObject, fromId, serial); | |
| 216 | + } | |
| 217 | + | |
| 218 | + private void responsePushStream(JSONObject content, String toId, String serial) { | |
| 219 | + | |
| 220 | + WVPResult<JSONObject> result = new WVPResult<>(); | |
| 221 | + result.setCode(0); | |
| 222 | + result.setData(content); | |
| 223 | + | |
| 224 | + WvpRedisMsg response = WvpRedisMsg.getResponseInstance(userSetting.getServerId(), toId, | |
| 225 | + WvpRedisMsgCmd.REQUEST_PUSH_STREAM, serial, result); | |
| 226 | + JSONObject jsonObject = (JSONObject)JSON.toJSON(response); | |
| 227 | + redis.convertAndSend(WVP_PUSH_STREAM_KEY, jsonObject); | |
| 228 | + } | |
| 229 | + | |
| 230 | + /** | |
| 231 | + * 处理收到的请求sendItem的请求 | |
| 232 | + */ | |
| 233 | + private void requestSendItemMsgHand(RequestSendItemMsg content, String toId, String serial) { | |
| 234 | + MediaServerItem mediaServerItem = mediaServerService.getOne(content.getMediaServerId()); | |
| 235 | + if (mediaServerItem == null) { | |
| 236 | + logger.info("[回复推流信息] 流媒体{}不存在 ", content.getMediaServerId()); | |
| 237 | + | |
| 238 | + WVPResult<SendRtpItem> result = new WVPResult<>(); | |
| 239 | + result.setCode(ERROR_CODE_MEDIA_SERVER_NOT_FOUND); | |
| 240 | + result.setMsg("流媒体不存在"); | |
| 241 | + | |
| 242 | + WvpRedisMsg response = WvpRedisMsg.getResponseInstance(userSetting.getServerId(), toId, | |
| 243 | + WvpRedisMsgCmd.GET_SEND_ITEM, serial, result); | |
| 244 | + | |
| 245 | + JSONObject jsonObject = (JSONObject)JSON.toJSON(response); | |
| 246 | + redis.convertAndSend(WVP_PUSH_STREAM_KEY, jsonObject); | |
| 247 | + return; | |
| 248 | + } | |
| 249 | + // 确定流是否在线 | |
| 250 | + boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, content.getApp(), content.getStream()); | |
| 251 | + if (streamReady) { | |
| 252 | + logger.info("[回复推流信息] {}/{}", content.getApp(), content.getStream()); | |
| 253 | + responseSendItem(mediaServerItem, content, toId, serial); | |
| 254 | + }else { | |
| 255 | + // 流已经离线 | |
| 256 | + // 发送redis消息以使设备上线 | |
| 257 | + logger.info("[ app={}, stream={} ]通道离线,发送redis信息控制设备开始推流",content.getApp(), content.getStream()); | |
| 258 | + | |
| 259 | + String taskKey = UUID.randomUUID().toString(); | |
| 260 | + // 设置超时 | |
| 261 | + dynamicTask.startDelay(taskKey, ()->{ | |
| 262 | + logger.info("[ app={}, stream={} ] 等待设备开始推流超时", content.getApp(), content.getStream()); | |
| 263 | + WVPResult<SendRtpItem> result = new WVPResult<>(); | |
| 264 | + result.setCode(ERROR_CODE_TIMEOUT); | |
| 265 | + WvpRedisMsg response = WvpRedisMsg.getResponseInstance( | |
| 266 | + userSetting.getServerId(), toId, WvpRedisMsgCmd.GET_SEND_ITEM, serial, result | |
| 267 | + ); | |
| 268 | + JSONObject jsonObject = (JSONObject)JSON.toJSON(response); | |
| 269 | + redis.convertAndSend(WVP_PUSH_STREAM_KEY, jsonObject); | |
| 270 | + }, userSetting.getPlatformPlayTimeout()); | |
| 271 | + | |
| 272 | + // 添加订阅 | |
| 273 | + JSONObject subscribeKey = new JSONObject(); | |
| 274 | + subscribeKey.put("app", content.getApp()); | |
| 275 | + subscribeKey.put("stream", content.getStream()); | |
| 276 | + subscribeKey.put("regist", true); | |
| 277 | + subscribeKey.put("schema", "rtmp"); | |
| 278 | + subscribeKey.put("mediaServerId", mediaServerItem.getId()); | |
| 279 | + subscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey, | |
| 280 | + (MediaServerItem mediaServerItemInUse, JSONObject json)->{ | |
| 281 | + dynamicTask.stop(taskKey); | |
| 282 | + responseSendItem(mediaServerItem, content, toId, serial); | |
| 283 | + }); | |
| 284 | + | |
| 285 | + MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(1, content.getApp(), content.getStream(), | |
| 286 | + content.getChannelId(), content.getPlatformId(), content.getPlatformName(), content.getServerId(), | |
| 287 | + content.getMediaServerId()); | |
| 288 | + redisCatchStorage.sendStreamPushRequestedMsg(messageForPushChannel); | |
| 289 | + | |
| 290 | + } | |
| 291 | + } | |
| 292 | + | |
| 293 | + /** | |
| 294 | + * 将获取到的sendItem发送出去 | |
| 295 | + */ | |
| 296 | + private void responseSendItem(MediaServerItem mediaServerItem, RequestSendItemMsg content, String toId, String serial) { | |
| 297 | + SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, content.getIp(), | |
| 298 | + content.getPort(), content.getSsrc(), content.getPlatformId(), | |
| 299 | + content.getApp(), content.getStream(), content.getChannelId(), | |
| 300 | + content.getTcp()); | |
| 301 | + | |
| 302 | + WVPResult<ResponseSendItemMsg> result = new WVPResult<>(); | |
| 303 | + result.setCode(0); | |
| 304 | + ResponseSendItemMsg responseSendItemMsg = new ResponseSendItemMsg(); | |
| 305 | + responseSendItemMsg.setSendRtpItem(sendRtpItem); | |
| 306 | + responseSendItemMsg.setMediaServerItem(mediaServerItem); | |
| 307 | + result.setData(responseSendItemMsg); | |
| 308 | + | |
| 309 | + WvpRedisMsg response = WvpRedisMsg.getResponseInstance( | |
| 310 | + userSetting.getServerId(), toId, WvpRedisMsgCmd.GET_SEND_ITEM, serial, result | |
| 311 | + ); | |
| 312 | + JSONObject jsonObject = (JSONObject)JSON.toJSON(response); | |
| 313 | + redis.convertAndSend(WVP_PUSH_STREAM_KEY, jsonObject); | |
| 314 | + } | |
| 315 | + | |
| 316 | + /** | |
| 317 | + * 发送消息要求下级生成推流信息 | |
| 318 | + * @param serverId 下级服务ID | |
| 319 | + * @param app 应用名 | |
| 320 | + * @param stream 流ID | |
| 321 | + * @param ip 目标IP | |
| 322 | + * @param port 目标端口 | |
| 323 | + * @param ssrc ssrc | |
| 324 | + * @param platformId 平台国标编号 | |
| 325 | + * @param channelId 通道ID | |
| 326 | + * @param isTcp 是否使用TCP | |
| 327 | + * @param callback 得到信息的回调 | |
| 328 | + */ | |
| 329 | + public void sendMsg(String serverId, String mediaServerId, String app, String stream, String ip, int port, String ssrc, | |
| 330 | + String platformId, String channelId, boolean isTcp, String platformName, PlayMsgCallback callback, PlayMsgErrorCallback errorCallback) { | |
| 331 | + RequestSendItemMsg requestSendItemMsg = RequestSendItemMsg.getInstance( | |
| 332 | + serverId, mediaServerId, app, stream, ip, port, ssrc, platformId, channelId, isTcp, platformName); | |
| 333 | + requestSendItemMsg.setServerId(serverId); | |
| 334 | + String key = UUID.randomUUID().toString(); | |
| 335 | + WvpRedisMsg redisMsg = WvpRedisMsg.getRequestInstance(userSetting.getServerId(), serverId, WvpRedisMsgCmd.GET_SEND_ITEM, | |
| 336 | + key, requestSendItemMsg); | |
| 337 | + | |
| 338 | + JSONObject jsonObject = (JSONObject)JSON.toJSON(redisMsg); | |
| 339 | + logger.info("[请求推流SendItem] {}: {}", serverId, jsonObject); | |
| 340 | + callbacks.put(key, callback); | |
| 341 | + callbacksForError.put(key, errorCallback); | |
| 342 | + dynamicTask.startDelay(key, ()->{ | |
| 343 | + callbacks.remove(key); | |
| 344 | + callbacksForError.remove(key); | |
| 345 | + WVPResult<Object> wvpResult = new WVPResult<>(); | |
| 346 | + wvpResult.setCode(ERROR_CODE_TIMEOUT); | |
| 347 | + wvpResult.setMsg("timeout"); | |
| 348 | + errorCallback.handler(wvpResult); | |
| 349 | + }, userSetting.getPlatformPlayTimeout()); | |
| 350 | + redis.convertAndSend(WVP_PUSH_STREAM_KEY, jsonObject); | |
| 351 | + } | |
| 352 | + | |
| 353 | + /** | |
| 354 | + * 发送请求推流的消息 | |
| 355 | + * @param param 推流参数 | |
| 356 | + * @param callback 回调 | |
| 357 | + */ | |
| 358 | + public void sendMsgForStartSendRtpStream(String serverId, RequestPushStreamMsg param, PlayMsgCallbackForStartSendRtpStream callback) { | |
| 359 | + String key = UUID.randomUUID().toString(); | |
| 360 | + WvpRedisMsg redisMsg = WvpRedisMsg.getRequestInstance(userSetting.getServerId(), serverId, | |
| 361 | + WvpRedisMsgCmd.REQUEST_PUSH_STREAM, key, param); | |
| 362 | + | |
| 363 | + JSONObject jsonObject = (JSONObject)JSON.toJSON(redisMsg); | |
| 364 | + logger.info("[REDIS 请求其他平台推流] {}: {}", serverId, jsonObject); | |
| 365 | + dynamicTask.startDelay(key, ()->{ | |
| 366 | + callbacksForStartSendRtpStream.remove(key); | |
| 367 | + callbacksForError.remove(key); | |
| 368 | + }, userSetting.getPlatformPlayTimeout()); | |
| 369 | + callbacksForStartSendRtpStream.put(key, callback); | |
| 370 | + callbacksForError.put(key, (wvpResult)->{ | |
| 371 | + logger.info("[REDIS 请求其他平台推流] 失败: {}", wvpResult.getMsg()); | |
| 372 | + callbacksForStartSendRtpStream.remove(key); | |
| 373 | + callbacksForError.remove(key); | |
| 374 | + }); | |
| 375 | + redis.convertAndSend(WVP_PUSH_STREAM_KEY, jsonObject); | |
| 376 | + } | |
| 377 | +} | ... | ... |
src/main/java/com/genersoft/iot/vmp/service/impl/RedisGPSMsgListener.java renamed to src/main/java/com/genersoft/iot/vmp/service/impl/RedisGpsMsgListener.java
| ... | ... | @@ -3,6 +3,7 @@ package com.genersoft.iot.vmp.service.impl; |
| 3 | 3 | import com.alibaba.fastjson.JSON; |
| 4 | 4 | import com.genersoft.iot.vmp.service.bean.GPSMsgInfo; |
| 5 | 5 | import com.genersoft.iot.vmp.storager.IRedisCatchStorage; |
| 6 | +import org.jetbrains.annotations.NotNull; | |
| 6 | 7 | import org.slf4j.Logger; |
| 7 | 8 | import org.slf4j.LoggerFactory; |
| 8 | 9 | import org.springframework.beans.factory.annotation.Autowired; |
| ... | ... | @@ -10,17 +11,23 @@ import org.springframework.data.redis.connection.Message; |
| 10 | 11 | import org.springframework.data.redis.connection.MessageListener; |
| 11 | 12 | import org.springframework.stereotype.Component; |
| 12 | 13 | |
| 14 | +/** | |
| 15 | + * 接收来自redis的GPS更新通知 | |
| 16 | + * @author lin | |
| 17 | + */ | |
| 13 | 18 | @Component |
| 14 | -public class RedisGPSMsgListener implements MessageListener { | |
| 19 | +public class RedisGpsMsgListener implements MessageListener { | |
| 15 | 20 | |
| 16 | - private final static Logger logger = LoggerFactory.getLogger(RedisGPSMsgListener.class); | |
| 21 | + private final static Logger logger = LoggerFactory.getLogger(RedisGpsMsgListener.class); | |
| 17 | 22 | |
| 18 | 23 | @Autowired |
| 19 | 24 | private IRedisCatchStorage redisCatchStorage; |
| 20 | 25 | |
| 21 | 26 | @Override |
| 22 | - public void onMessage(Message message, byte[] bytes) { | |
| 23 | - logger.info("收到来自REDIS的GPS通知: {}", new String(message.getBody())); | |
| 27 | + public void onMessage(@NotNull Message message, byte[] bytes) { | |
| 28 | + if (logger.isDebugEnabled()) { | |
| 29 | + logger.debug("收到来自REDIS的GPS通知: {}", new String(message.getBody())); | |
| 30 | + } | |
| 24 | 31 | GPSMsgInfo gpsMsgInfo = JSON.parseObject(message.getBody(), GPSMsgInfo.class); |
| 25 | 32 | redisCatchStorage.updateGpsMsgInfo(gpsMsgInfo); |
| 26 | 33 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/service/impl/RedisStreamMsgListener.java
0 → 100644
| 1 | +package com.genersoft.iot.vmp.service.impl; | |
| 2 | + | |
| 3 | +import com.alibaba.fastjson.JSON; | |
| 4 | +import com.alibaba.fastjson.JSONObject; | |
| 5 | +import com.genersoft.iot.vmp.conf.UserSetting; | |
| 6 | +import com.genersoft.iot.vmp.gb28181.bean.AlarmChannelMessage; | |
| 7 | +import com.genersoft.iot.vmp.gb28181.bean.Device; | |
| 8 | +import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm; | |
| 9 | +import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; | |
| 10 | +import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander; | |
| 11 | +import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; | |
| 12 | +import com.genersoft.iot.vmp.media.zlm.ZLMMediaListManager; | |
| 13 | +import com.genersoft.iot.vmp.media.zlm.dto.MediaItem; | |
| 14 | +import com.genersoft.iot.vmp.storager.IVideoManagerStorage; | |
| 15 | +import com.genersoft.iot.vmp.utils.DateUtil; | |
| 16 | +import org.slf4j.Logger; | |
| 17 | +import org.slf4j.LoggerFactory; | |
| 18 | +import org.springframework.beans.factory.annotation.Autowired; | |
| 19 | +import org.springframework.data.redis.connection.Message; | |
| 20 | +import org.springframework.data.redis.connection.MessageListener; | |
| 21 | +import org.springframework.stereotype.Component; | |
| 22 | + | |
| 23 | + | |
| 24 | +/** | |
| 25 | + * @author lin | |
| 26 | + */ | |
| 27 | +@Component | |
| 28 | +public class RedisStreamMsgListener implements MessageListener { | |
| 29 | + | |
| 30 | + private final static Logger logger = LoggerFactory.getLogger(RedisStreamMsgListener.class); | |
| 31 | + | |
| 32 | + @Autowired | |
| 33 | + private ISIPCommander commander; | |
| 34 | + | |
| 35 | + @Autowired | |
| 36 | + private ISIPCommanderForPlatform commanderForPlatform; | |
| 37 | + | |
| 38 | + @Autowired | |
| 39 | + private IVideoManagerStorage storage; | |
| 40 | + | |
| 41 | + @Autowired | |
| 42 | + private UserSetting userSetting; | |
| 43 | + | |
| 44 | + @Autowired | |
| 45 | + private ZLMMediaListManager zlmMediaListManager; | |
| 46 | + | |
| 47 | + @Override | |
| 48 | + public void onMessage(Message message, byte[] bytes) { | |
| 49 | + | |
| 50 | + JSONObject steamMsgJson = JSON.parseObject(message.getBody(), JSONObject.class); | |
| 51 | + if (steamMsgJson == null) { | |
| 52 | + logger.warn("[REDIS的ALARM通知]消息解析失败"); | |
| 53 | + return; | |
| 54 | + } | |
| 55 | + String serverId = steamMsgJson.getString("serverId"); | |
| 56 | + | |
| 57 | + if (userSetting.getServerId().equals(serverId)) { | |
| 58 | + // 自己发送的消息忽略即可 | |
| 59 | + return; | |
| 60 | + } | |
| 61 | + logger.info("[REDIS通知] 流变化: {}", new String(message.getBody())); | |
| 62 | + String app = steamMsgJson.getString("app"); | |
| 63 | + String stream = steamMsgJson.getString("stream"); | |
| 64 | + boolean register = steamMsgJson.getBoolean("register"); | |
| 65 | + String mediaServerId = steamMsgJson.getString("mediaServerId"); | |
| 66 | + MediaItem mediaItem = new MediaItem(); | |
| 67 | + mediaItem.setSeverId(serverId); | |
| 68 | + mediaItem.setApp(app); | |
| 69 | + mediaItem.setStream(stream); | |
| 70 | + mediaItem.setRegist(register); | |
| 71 | + mediaItem.setMediaServerId(mediaServerId); | |
| 72 | + mediaItem.setCreateStamp(System.currentTimeMillis()/1000); | |
| 73 | + mediaItem.setAliveSecond(0L); | |
| 74 | + mediaItem.setTotalReaderCount("0"); | |
| 75 | + mediaItem.setOriginType(0); | |
| 76 | + mediaItem.setOriginTypeStr("0"); | |
| 77 | + mediaItem.setOriginTypeStr("unknown"); | |
| 78 | + | |
| 79 | + zlmMediaListManager.addPush(mediaItem); | |
| 80 | + | |
| 81 | + | |
| 82 | + } | |
| 83 | +} | ... | ... |
src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushServiceImpl.java
| ... | ... | @@ -107,6 +107,7 @@ public class StreamPushServiceImpl implements IStreamPushService { |
| 107 | 107 | streamPushItem.setStatus(true); |
| 108 | 108 | streamPushItem.setStreamType("push"); |
| 109 | 109 | streamPushItem.setVhost(item.getVhost()); |
| 110 | + streamPushItem.setServerId(item.getSeverId()); | |
| 110 | 111 | return streamPushItem; |
| 111 | 112 | } |
| 112 | 113 | ... | ... |
src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorage.java
| ... | ... | @@ -357,6 +357,15 @@ public interface IVideoManagerStorage { |
| 357 | 357 | |
| 358 | 358 | |
| 359 | 359 | /** |
| 360 | + * 获取但个推流 | |
| 361 | + * @param app | |
| 362 | + * @param stream | |
| 363 | + * @return | |
| 364 | + */ | |
| 365 | + StreamPushItem getMedia(String app, String stream); | |
| 366 | + | |
| 367 | + | |
| 368 | + /** | |
| 360 | 369 | * 清空推流列表 |
| 361 | 370 | */ |
| 362 | 371 | void clearMediaList(); | ... | ... |
src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.java
| ... | ... | @@ -17,10 +17,10 @@ public interface DeviceChannelMapper { |
| 17 | 17 | |
| 18 | 18 | @Insert("INSERT INTO device_channel (channelId, deviceId, name, manufacture, model, owner, civilCode, block, " + |
| 19 | 19 | "address, parental, parentId, safetyWay, registerWay, certNum, certifiable, errCode, secrecy, " + |
| 20 | - "ipAddress, port, password, PTZType, status, streamId, longitude, latitude, createTime, updateTime) " + | |
| 20 | + "ipAddress, port, password, PTZType, status, streamId, longitude, latitude, longitudeGcj02, latitudeGcj02, longitudeWgs84, latitudeWgs84, createTime, updateTime) " + | |
| 21 | 21 | "VALUES ('${channelId}', '${deviceId}', '${name}', '${manufacture}', '${model}', '${owner}', '${civilCode}', '${block}'," + |
| 22 | 22 | "'${address}', ${parental}, '${parentId}', ${safetyWay}, ${registerWay}, '${certNum}', ${certifiable}, ${errCode}, '${secrecy}', " + |
| 23 | - "'${ipAddress}', ${port}, '${password}', ${PTZType}, ${status}, '${streamId}', ${longitude}, ${latitude},'${createTime}', '${updateTime}')") | |
| 23 | + "'${ipAddress}', ${port}, '${password}', ${PTZType}, ${status}, '${streamId}', ${longitude}, ${latitude}, ${longitudeGcj02}, ${latitudeGcj02}, ${longitudeWgs84}, ${latitudeWgs84},'${createTime}', '${updateTime}')") | |
| 24 | 24 | int add(DeviceChannel channel); |
| 25 | 25 | |
| 26 | 26 | @Update(value = {" <script>" + |
| ... | ... | @@ -50,6 +50,10 @@ public interface DeviceChannelMapper { |
| 50 | 50 | "<if test='hasAudio != null'>, hasAudio=${hasAudio}</if>" + |
| 51 | 51 | "<if test='longitude != null'>, longitude=${longitude}</if>" + |
| 52 | 52 | "<if test='latitude != null'>, latitude=${latitude}</if>" + |
| 53 | + "<if test='longitudeGcj02 != null'>, longitudeGcj02=${longitudeGcj02}</if>" + | |
| 54 | + "<if test='latitudeGcj02 != null'>, latitudeGcj02=${latitudeGcj02}</if>" + | |
| 55 | + "<if test='longitudeWgs84 != null'>, longitudeWgs84=${longitudeWgs84}</if>" + | |
| 56 | + "<if test='latitudeWgs84 != null'>, latitudeWgs84=${latitudeWgs84}</if>" + | |
| 53 | 57 | "WHERE deviceId='${deviceId}' AND channelId='${channelId}'"+ |
| 54 | 58 | " </script>"}) |
| 55 | 59 | int update(DeviceChannel channel); |
| ... | ... | @@ -67,7 +71,7 @@ public interface DeviceChannelMapper { |
| 67 | 71 | " <if test='online == false' > AND dc.status=0</if>" + |
| 68 | 72 | " <if test='hasSubChannel == true' > AND dc.subCount > 0 </if>" + |
| 69 | 73 | " <if test='hasSubChannel == false' > AND dc.subCount = 0 </if>" + |
| 70 | - "GROUP BY dc.channelId " + | |
| 74 | + "ORDER BY dc.channelId " + | |
| 71 | 75 | " </script>"}) |
| 72 | 76 | List<DeviceChannel> queryChannels(String deviceId, String parentChannelId, String query, Boolean hasSubChannel, Boolean online); |
| 73 | 77 | |
| ... | ... | @@ -138,7 +142,8 @@ public interface DeviceChannelMapper { |
| 138 | 142 | "insert into device_channel " + |
| 139 | 143 | "(channelId, deviceId, name, manufacture, model, owner, civilCode, block, subCount, " + |
| 140 | 144 | " address, parental, parentId, safetyWay, registerWay, certNum, certifiable, errCode, secrecy, " + |
| 141 | - " ipAddress, port, password, PTZType, status, streamId, longitude, latitude, createTime, updateTime) " + | |
| 145 | + " ipAddress, port, password, PTZType, status, streamId, longitude, latitude, longitudeGcj02, latitudeGcj02, " + | |
| 146 | + " longitudeWgs84, latitudeWgs84, createTime, updateTime) " + | |
| 142 | 147 | "values " + |
| 143 | 148 | "<foreach collection='addChannels' index='index' item='item' separator=','> " + |
| 144 | 149 | "('${item.channelId}', '${item.deviceId}', '${item.name}', '${item.manufacture}', '${item.model}', " + |
| ... | ... | @@ -146,7 +151,8 @@ public interface DeviceChannelMapper { |
| 146 | 151 | "'${item.address}', ${item.parental}, '${item.parentId}', ${item.safetyWay}, ${item.registerWay}, " + |
| 147 | 152 | "'${item.certNum}', ${item.certifiable}, ${item.errCode}, '${item.secrecy}', " + |
| 148 | 153 | "'${item.ipAddress}', ${item.port}, '${item.password}', ${item.PTZType}, ${item.status}, " + |
| 149 | - "'${item.streamId}', ${item.longitude}, ${item.latitude},'${item.createTime}', '${item.updateTime}')" + | |
| 154 | + "'${item.streamId}', ${item.longitude}, ${item.latitude},${item.longitudeGcj02}, " + | |
| 155 | + "${item.latitudeGcj02},${item.longitudeWgs84}, ${item.latitudeWgs84},'${item.createTime}', '${item.updateTime}')" + | |
| 150 | 156 | "</foreach> " + |
| 151 | 157 | "ON DUPLICATE KEY UPDATE " + |
| 152 | 158 | "updateTime=VALUES(updateTime), " + |
| ... | ... | @@ -173,7 +179,11 @@ public interface DeviceChannelMapper { |
| 173 | 179 | "status=VALUES(status), " + |
| 174 | 180 | "streamId=VALUES(streamId), " + |
| 175 | 181 | "longitude=VALUES(longitude), " + |
| 176 | - "latitude=VALUES(latitude)" + | |
| 182 | + "latitude=VALUES(latitude), " + | |
| 183 | + "longitudeGcj02=VALUES(longitudeGcj02), " + | |
| 184 | + "latitudeGcj02=VALUES(latitudeGcj02), " + | |
| 185 | + "longitudeWgs84=VALUES(longitudeWgs84), " + | |
| 186 | + "latitudeWgs84=VALUES(latitudeWgs84) " + | |
| 177 | 187 | "</script>") |
| 178 | 188 | int batchAdd(List<DeviceChannel> addChannels); |
| 179 | 189 | |
| ... | ... | @@ -207,7 +217,11 @@ public interface DeviceChannelMapper { |
| 207 | 217 | "<if test='item.hasAudio != null'>, hasAudio=${item.hasAudio}</if>" + |
| 208 | 218 | "<if test='item.longitude != null'>, longitude=${item.longitude}</if>" + |
| 209 | 219 | "<if test='item.latitude != null'>, latitude=${item.latitude}</if>" + |
| 210 | - "WHERE deviceId=#{item.deviceId} AND channelId=#{item.channelId}"+ | |
| 220 | + "<if test='item.longitudeGcj02 != null'>, longitudeGcj02=${item.longitudeGcj02}</if>" + | |
| 221 | + "<if test='item.latitudeGcj02 != null'>, latitudeGcj02=${item.latitudeGcj02}</if>" + | |
| 222 | + "<if test='item.longitudeWgs84 != null'>, longitudeWgs84=${item.longitudeWgs84}</if>" + | |
| 223 | + "<if test='item.latitudeWgs84 != null'>, latitudeWgs84=${item.latitudeWgs84}</if>" + | |
| 224 | + "WHERE deviceId='${item.deviceId}' AND channelId='${item.channelId}'"+ | |
| 211 | 225 | "</foreach>" + |
| 212 | 226 | "</script>"}) |
| 213 | 227 | int batchUpdate(List<DeviceChannel> updateChannels); |
| ... | ... | @@ -261,4 +275,6 @@ public interface DeviceChannelMapper { |
| 261 | 275 | @Select("SELECT * FROM device_channel WHERE length(trim(streamId)) > 0") |
| 262 | 276 | List<DeviceChannel> getAllChannelInPlay(); |
| 263 | 277 | |
| 278 | + @Select("select * from device_channel where longitude*latitude > 0 and deviceId = #{deviceId}") | |
| 279 | + List<DeviceChannel> getAllChannelWithCoordinate(String deviceId); | |
| 264 | 280 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceMapper.java
| ... | ... | @@ -38,6 +38,7 @@ public interface DeviceMapper { |
| 38 | 38 | "mobilePositionSubmissionInterval," + |
| 39 | 39 | "subscribeCycleForAlarm," + |
| 40 | 40 | "ssrcCheck," + |
| 41 | + "geoCoordSys," + | |
| 41 | 42 | "online" + |
| 42 | 43 | ") VALUES (" + |
| 43 | 44 | "#{deviceId}," + |
| ... | ... | @@ -61,6 +62,7 @@ public interface DeviceMapper { |
| 61 | 62 | "#{mobilePositionSubmissionInterval}," + |
| 62 | 63 | "#{subscribeCycleForAlarm}," + |
| 63 | 64 | "#{ssrcCheck}," + |
| 65 | + "#{geoCoordSys}," + | |
| 64 | 66 | "#{online}" + |
| 65 | 67 | ")") |
| 66 | 68 | int add(Device device); |
| ... | ... | @@ -87,6 +89,7 @@ public interface DeviceMapper { |
| 87 | 89 | "<if test=\"mobilePositionSubmissionInterval != null\">, mobilePositionSubmissionInterval=${mobilePositionSubmissionInterval}</if>" + |
| 88 | 90 | "<if test=\"subscribeCycleForAlarm != null\">, subscribeCycleForAlarm=${subscribeCycleForAlarm}</if>" + |
| 89 | 91 | "<if test=\"ssrcCheck != null\">, ssrcCheck=${ssrcCheck}</if>" + |
| 92 | + "<if test=\"geoCoordSys != null\">, geoCoordSys=#{geoCoordSys}</if>" + | |
| 90 | 93 | "WHERE deviceId='${deviceId}'"+ |
| 91 | 94 | " </script>"}) |
| 92 | 95 | int update(Device device); | ... | ... |
src/main/java/com/genersoft/iot/vmp/storager/dao/StreamPushMapper.java
| ... | ... | @@ -14,9 +14,9 @@ import java.util.List; |
| 14 | 14 | public interface StreamPushMapper { |
| 15 | 15 | |
| 16 | 16 | @Insert("INSERT INTO stream_push (app, stream, totalReaderCount, originType, originTypeStr, " + |
| 17 | - "createStamp, aliveSecond, mediaServerId) VALUES" + | |
| 17 | + "createStamp, aliveSecond, mediaServerId, serverId) VALUES" + | |
| 18 | 18 | "('${app}', '${stream}', '${totalReaderCount}', '${originType}', '${originTypeStr}', " + |
| 19 | - "'${createStamp}', '${aliveSecond}', '${mediaServerId}' )") | |
| 19 | + "'${createStamp}', '${aliveSecond}', '${mediaServerId}' , '${serverId}' )") | |
| 20 | 20 | int add(StreamPushItem streamPushItem); |
| 21 | 21 | |
| 22 | 22 | @Update("UPDATE stream_push " + | ... | ... |
src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java
| ... | ... | @@ -587,11 +587,11 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage { |
| 587 | 587 | String scanKey = VideoManagerConstants.WVP_STREAM_GPS_MSG_PREFIX + userSetting.getServerId() + "_*"; |
| 588 | 588 | List<GPSMsgInfo> result = new ArrayList<>(); |
| 589 | 589 | List<Object> keys = redis.scan(scanKey); |
| 590 | - for (int i = 0; i < keys.size(); i++) { | |
| 591 | - String key = (String) keys.get(i); | |
| 590 | + for (Object o : keys) { | |
| 591 | + String key = (String) o; | |
| 592 | 592 | GPSMsgInfo gpsMsgInfo = (GPSMsgInfo) redis.get(key); |
| 593 | 593 | if (!gpsMsgInfo.isStored()) { // 只取没有存过得 |
| 594 | - result.add((GPSMsgInfo)redis.get(key)); | |
| 594 | + result.add((GPSMsgInfo) redis.get(key)); | |
| 595 | 595 | } |
| 596 | 596 | } |
| 597 | 597 | |
| ... | ... | @@ -667,7 +667,7 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage { |
| 667 | 667 | @Override |
| 668 | 668 | public void sendStreamPushRequestedMsg(MessageForPushChannel msg) { |
| 669 | 669 | String key = VideoManagerConstants.VM_MSG_STREAM_PUSH_REQUESTED; |
| 670 | - logger.info("[redis 推流被请求通知] {}: {}-{}", key, msg.getApp(), msg.getStream()); | |
| 670 | + logger.info("[redis 推流被请求通知] {}: {}/{}", key, msg.getApp(), msg.getStream()); | |
| 671 | 671 | redis.convertAndSend(key, (JSONObject)JSON.toJSON(msg)); |
| 672 | 672 | } |
| 673 | 673 | ... | ... |
src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java
| ... | ... | @@ -25,12 +25,13 @@ import org.springframework.stereotype.Component; |
| 25 | 25 | import org.springframework.transaction.TransactionDefinition; |
| 26 | 26 | import org.springframework.transaction.TransactionStatus; |
| 27 | 27 | import org.springframework.transaction.annotation.Transactional; |
| 28 | +import org.springframework.util.CollectionUtils; | |
| 28 | 29 | import org.springframework.util.StringUtils; |
| 29 | 30 | |
| 30 | 31 | import java.util.*; |
| 31 | 32 | import java.util.concurrent.ConcurrentHashMap; |
| 32 | 33 | |
| 33 | -/** | |
| 34 | +/** | |
| 34 | 35 | * 视频设备数据存储-jdbc实现 |
| 35 | 36 | * swwheihei |
| 36 | 37 | * 2020年5月6日 下午2:31:42 |
| ... | ... | @@ -195,7 +196,7 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage { |
| 195 | 196 | |
| 196 | 197 | @Override |
| 197 | 198 | public boolean resetChannels(String deviceId, List<DeviceChannel> deviceChannelList) { |
| 198 | - if (deviceChannelList == null) { | |
| 199 | + if (CollectionUtils.isEmpty(deviceChannelList)) { | |
| 199 | 200 | return false; |
| 200 | 201 | } |
| 201 | 202 | List<DeviceChannel> allChannelInPlay = deviceChannelMapper.getAllChannelInPlay(); |
| ... | ... | @@ -246,6 +247,10 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage { |
| 246 | 247 | if (stringBuilder.length() > 0) { |
| 247 | 248 | logger.info("[目录查询]收到的数据存在重复: {}" , stringBuilder); |
| 248 | 249 | } |
| 250 | + if(CollectionUtils.isEmpty(channels)){ | |
| 251 | + logger.info("通道重设,数据为空={}" , deviceChannelList); | |
| 252 | + return false; | |
| 253 | + } | |
| 249 | 254 | try { |
| 250 | 255 | int cleanChannelsResult = deviceChannelMapper.cleanChannelsNotInList(deviceId, channels); |
| 251 | 256 | int limitCount = 300; |
| ... | ... | @@ -315,6 +320,9 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage { |
| 315 | 320 | List<DeviceChannel> all; |
| 316 | 321 | if (catalogUnderDevice != null && catalogUnderDevice) { |
| 317 | 322 | all = deviceChannelMapper.queryChannels(deviceId, deviceId, query, hasSubChannel, online); |
| 323 | + // 海康设备的parentId是SIP id | |
| 324 | + List<DeviceChannel> deviceChannels = deviceChannelMapper.queryChannels(deviceId, sipConfig.getId(), query, hasSubChannel, online); | |
| 325 | + all.addAll(deviceChannels); | |
| 318 | 326 | }else { |
| 319 | 327 | all = deviceChannelMapper.queryChannels(deviceId, null, query, hasSubChannel, online); |
| 320 | 328 | } |
| ... | ... | @@ -877,6 +885,11 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage { |
| 877 | 885 | } |
| 878 | 886 | |
| 879 | 887 | @Override |
| 888 | + public StreamPushItem getMedia(String app, String stream) { | |
| 889 | + return streamPushMapper.selectOne(app, stream); | |
| 890 | + } | |
| 891 | + | |
| 892 | + @Override | |
| 880 | 893 | public void clearMediaList() { |
| 881 | 894 | streamPushMapper.clear(); |
| 882 | 895 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/utils/DateUtil.java
| 1 | 1 | package com.genersoft.iot.vmp.utils; |
| 2 | 2 | |
| 3 | 3 | |
| 4 | -import java.text.SimpleDateFormat; | |
| 5 | 4 | import java.time.Instant; |
| 6 | 5 | import java.time.LocalDate; |
| 7 | 6 | import java.time.LocalDateTime; |
| ... | ... | @@ -18,35 +17,63 @@ import java.util.Locale; |
| 18 | 17 | */ |
| 19 | 18 | public class DateUtil { |
| 20 | 19 | |
| 21 | - private static final String yyyy_MM_dd_T_HH_mm_ss_SSSXXX = "yyyy-MM-dd'T'HH:mm:ss"; | |
| 22 | - public static final String yyyy_MM_dd_HH_mm_ss = "yyyy-MM-dd HH:mm:ss"; | |
| 20 | + /** | |
| 21 | + * 兼容不规范的iso8601时间格式 | |
| 22 | + */ | |
| 23 | + private static final String ISO8601_COMPATIBLE_PATTERN = "yyyy-M-d'T'H:m:s"; | |
| 23 | 24 | |
| 24 | - public static final SimpleDateFormat formatISO8601 = new SimpleDateFormat(yyyy_MM_dd_T_HH_mm_ss_SSSXXX, Locale.getDefault()); | |
| 25 | - public static final SimpleDateFormat format = new SimpleDateFormat(yyyy_MM_dd_HH_mm_ss, Locale.getDefault()); | |
| 25 | + /** | |
| 26 | + * 用以输出标准的iso8601时间格式 | |
| 27 | + */ | |
| 28 | + private static final String ISO8601_PATTERN = "yyyy-MM-dd'T'HH:mm:ss"; | |
| 26 | 29 | |
| 27 | - public static final DateTimeFormatter formatterISO8601 = DateTimeFormatter.ofPattern(yyyy_MM_dd_T_HH_mm_ss_SSSXXX, Locale.getDefault()).withZone(ZoneId.systemDefault()); | |
| 28 | - public static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(yyyy_MM_dd_HH_mm_ss, Locale.getDefault()).withZone(ZoneId.systemDefault()); | |
| 30 | + /** | |
| 31 | + * wvp内部统一时间格式 | |
| 32 | + */ | |
| 33 | + public static final String PATTERN = "yyyy-MM-dd HH:mm:ss"; | |
| 34 | + | |
| 35 | + public static final String zoneStr = "Asia/Shanghai"; | |
| 36 | + | |
| 37 | + public static final DateTimeFormatter formatterCompatibleISO8601 = DateTimeFormatter.ofPattern(ISO8601_COMPATIBLE_PATTERN, Locale.getDefault()).withZone(ZoneId.of(zoneStr)); | |
| 38 | + public static final DateTimeFormatter formatterISO8601 = DateTimeFormatter.ofPattern(ISO8601_PATTERN, Locale.getDefault()).withZone(ZoneId.of(zoneStr)); | |
| 39 | + public static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(PATTERN, Locale.getDefault()).withZone(ZoneId.of(zoneStr)); | |
| 29 | 40 | |
| 30 | 41 | public static String yyyy_MM_dd_HH_mm_ssToISO8601(String formatTime) { |
| 42 | + | |
| 31 | 43 | return formatterISO8601.format(formatter.parse(formatTime)); |
| 32 | 44 | } |
| 33 | 45 | |
| 34 | 46 | public static String ISO8601Toyyyy_MM_dd_HH_mm_ss(String formatTime) { |
| 35 | - return formatter.format(formatterISO8601.parse(formatTime)); | |
| 47 | + return formatter.format(formatterCompatibleISO8601.parse(formatTime)); | |
| 36 | 48 | |
| 37 | 49 | } |
| 38 | - | |
| 50 | + | |
| 51 | + /** | |
| 52 | + * yyyy_MM_dd_HH_mm_ss 转时间戳 | |
| 53 | + * @param formatTime | |
| 54 | + * @return | |
| 55 | + */ | |
| 39 | 56 | public static long yyyy_MM_dd_HH_mm_ssToTimestamp(String formatTime) { |
| 40 | 57 | TemporalAccessor temporalAccessor = formatter.parse(formatTime); |
| 41 | 58 | Instant instant = Instant.from(temporalAccessor); |
| 42 | 59 | return instant.getEpochSecond(); |
| 43 | 60 | } |
| 44 | 61 | |
| 62 | + /** | |
| 63 | + * 获取当前时间 | |
| 64 | + * @return | |
| 65 | + */ | |
| 45 | 66 | public static String getNow() { |
| 46 | 67 | LocalDateTime nowDateTime = LocalDateTime.now(); |
| 47 | 68 | return formatter.format(nowDateTime); |
| 48 | 69 | } |
| 49 | 70 | |
| 71 | + /** | |
| 72 | + * 格式校验 | |
| 73 | + * @param timeStr 时间字符串 | |
| 74 | + * @param dateTimeFormatter 待校验的格式 | |
| 75 | + * @return | |
| 76 | + */ | |
| 50 | 77 | public static boolean verification(String timeStr, DateTimeFormatter dateTimeFormatter) { |
| 51 | 78 | try { |
| 52 | 79 | LocalDate.parse(timeStr, dateTimeFormatter); | ... | ... |
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/alarm/AlarmController.java
| ... | ... | @@ -24,6 +24,7 @@ import org.springframework.util.StringUtils; |
| 24 | 24 | import org.springframework.web.bind.annotation.*; |
| 25 | 25 | |
| 26 | 26 | import java.text.ParseException; |
| 27 | +import java.time.LocalDateTime; | |
| 27 | 28 | import java.util.Arrays; |
| 28 | 29 | import java.util.List; |
| 29 | 30 | |
| ... | ... | @@ -68,8 +69,8 @@ public class AlarmController { |
| 68 | 69 | @ApiImplicitParam(name="alarmMethod", value = "查询内容" ,dataTypeClass = String.class), |
| 69 | 70 | @ApiImplicitParam(name="alarmMethod", value = "查询内容" ,dataTypeClass = String.class), |
| 70 | 71 | @ApiImplicitParam(name="alarmType", value = "查询内容" ,dataTypeClass = String.class), |
| 71 | - @ApiImplicitParam(name="startTime", value = "查询内容" ,dataTypeClass = String.class), | |
| 72 | - @ApiImplicitParam(name="endTime", value = "查询内容" ,dataTypeClass = String.class), | |
| 72 | + @ApiImplicitParam(name="startTime", value = "开始时间" ,dataTypeClass = String.class), | |
| 73 | + @ApiImplicitParam(name="endTime", value = "结束时间" ,dataTypeClass = String.class), | |
| 73 | 74 | }) |
| 74 | 75 | public ResponseEntity<PageInfo<DeviceAlarm>> getAll( |
| 75 | 76 | @RequestParam int page, |
| ... | ... | @@ -98,14 +99,7 @@ public class AlarmController { |
| 98 | 99 | } |
| 99 | 100 | |
| 100 | 101 | |
| 101 | - try { | |
| 102 | - if (startTime != null) { | |
| 103 | - DateUtil.format.parse(startTime); | |
| 104 | - } | |
| 105 | - if (endTime != null) { | |
| 106 | - DateUtil.format.parse(endTime); | |
| 107 | - } | |
| 108 | - } catch (ParseException e) { | |
| 102 | + if (!DateUtil.verification(startTime, DateUtil.formatter) || !DateUtil.verification(endTime, DateUtil.formatter)){ | |
| 109 | 103 | return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST); |
| 110 | 104 | } |
| 111 | 105 | |
| ... | ... | @@ -144,11 +138,7 @@ public class AlarmController { |
| 144 | 138 | if (StringUtils.isEmpty(time)) { |
| 145 | 139 | time = null; |
| 146 | 140 | } |
| 147 | - try { | |
| 148 | - if (time != null) { | |
| 149 | - DateUtil.format.parse(time); | |
| 150 | - } | |
| 151 | - } catch (ParseException e) { | |
| 141 | + if (!DateUtil.verification(time, DateUtil.formatter) ){ | |
| 152 | 142 | return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST); |
| 153 | 143 | } |
| 154 | 144 | List<String> deviceIdList = null; |
| ... | ... | @@ -189,7 +179,7 @@ public class AlarmController { |
| 189 | 179 | deviceAlarm.setAlarmDescription("test"); |
| 190 | 180 | deviceAlarm.setAlarmMethod("1"); |
| 191 | 181 | deviceAlarm.setAlarmPriority("1"); |
| 192 | - deviceAlarm.setAlarmTime(DateUtil.formatISO8601.format(System.currentTimeMillis())); | |
| 182 | + deviceAlarm.setAlarmTime(DateUtil.formatterISO8601.format(LocalDateTime.now())); | |
| 193 | 183 | deviceAlarm.setAlarmType("1"); |
| 194 | 184 | deviceAlarm.setLongitude(115.33333); |
| 195 | 185 | deviceAlarm.setLatitude(39.33333); | ... | ... |
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceQuery.java
| ... | ... | @@ -21,16 +21,22 @@ import io.swagger.annotations.Api; |
| 21 | 21 | import io.swagger.annotations.ApiImplicitParam; |
| 22 | 22 | import io.swagger.annotations.ApiImplicitParams; |
| 23 | 23 | import io.swagger.annotations.ApiOperation; |
| 24 | +import org.apache.commons.compress.utils.IOUtils; | |
| 25 | +import org.apache.http.HttpResponse; | |
| 24 | 26 | import org.slf4j.Logger; |
| 25 | 27 | import org.slf4j.LoggerFactory; |
| 26 | 28 | import org.springframework.beans.factory.annotation.Autowired; |
| 27 | 29 | import org.springframework.http.HttpStatus; |
| 30 | +import org.springframework.http.MediaType; | |
| 28 | 31 | import org.springframework.http.ResponseEntity; |
| 29 | 32 | import org.springframework.util.StringUtils; |
| 30 | 33 | import org.springframework.web.bind.annotation.*; |
| 31 | 34 | import org.springframework.web.context.request.async.DeferredResult; |
| 32 | 35 | |
| 36 | +import javax.servlet.http.HttpServletResponse; | |
| 33 | 37 | import javax.sip.DialogState; |
| 38 | +import java.io.*; | |
| 39 | +import java.nio.file.Files; | |
| 34 | 40 | import java.util.*; |
| 35 | 41 | |
| 36 | 42 | @Api(tags = "国标设备查询", value = "国标设备查询") |
| ... | ... | @@ -200,6 +206,11 @@ public class DeviceQuery { |
| 200 | 206 | Set<String> allKeys = dynamicTask.getAllKeys(); |
| 201 | 207 | for (String key : allKeys) { |
| 202 | 208 | if (key.startsWith(deviceId)) { |
| 209 | + Runnable runnable = dynamicTask.get(key); | |
| 210 | + if (runnable instanceof ISubscribeTask) { | |
| 211 | + ISubscribeTask subscribeTask = (ISubscribeTask) runnable; | |
| 212 | + subscribeTask.stop(); | |
| 213 | + } | |
| 203 | 214 | dynamicTask.stop(key); |
| 204 | 215 | } |
| 205 | 216 | } |
| ... | ... | @@ -306,12 +317,7 @@ public class DeviceQuery { |
| 306 | 317 | public ResponseEntity<WVPResult<String>> updateDevice(Device device){ |
| 307 | 318 | |
| 308 | 319 | if (device != null && device.getDeviceId() != null) { |
| 309 | - | |
| 310 | - | |
| 311 | - // TODO 报警订阅相关的信息 | |
| 312 | - | |
| 313 | 320 | deviceService.updateDevice(device); |
| 314 | -// cmder.deviceInfoQuery(device); | |
| 315 | 321 | } |
| 316 | 322 | WVPResult<String> result = new WVPResult<>(); |
| 317 | 323 | result.setCode(0); |
| ... | ... | @@ -336,6 +342,11 @@ public class DeviceQuery { |
| 336 | 342 | Device device = storager.queryVideoDevice(deviceId); |
| 337 | 343 | String uuid = UUID.randomUUID().toString(); |
| 338 | 344 | String key = DeferredResultHolder.CALLBACK_CMD_DEVICESTATUS + deviceId; |
| 345 | + DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(2*1000L); | |
| 346 | + if(device == null) { | |
| 347 | + result.setResult(new ResponseEntity(String.format("设备%s不存在", deviceId),HttpStatus.OK)); | |
| 348 | + return result; | |
| 349 | + } | |
| 339 | 350 | cmder.deviceStatusQuery(device, event -> { |
| 340 | 351 | RequestMessage msg = new RequestMessage(); |
| 341 | 352 | msg.setId(uuid); |
| ... | ... | @@ -343,7 +354,6 @@ public class DeviceQuery { |
| 343 | 354 | msg.setData(String.format("获取设备状态失败,错误码: %s, %s", event.statusCode, event.msg)); |
| 344 | 355 | resultHolder.invokeResult(msg); |
| 345 | 356 | }); |
| 346 | - DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(2*1000L); | |
| 347 | 357 | result.onTimeout(()->{ |
| 348 | 358 | logger.warn(String.format("获取设备状态超时")); |
| 349 | 359 | // 释放rtpserver |
| ... | ... | @@ -456,4 +466,17 @@ public class DeviceQuery { |
| 456 | 466 | wvpResult.setData(dialogStateMap); |
| 457 | 467 | return wvpResult; |
| 458 | 468 | } |
| 469 | + | |
| 470 | + @GetMapping("/snap/{deviceId}/{channelId}") | |
| 471 | + @ApiOperation(value = "请求截图", notes = "请求截图") | |
| 472 | + public void getSnap(HttpServletResponse resp, @PathVariable String deviceId, @PathVariable String channelId) { | |
| 473 | + | |
| 474 | + try { | |
| 475 | + final InputStream in = Files.newInputStream(new File("snap" + File.separator + deviceId + "_" + channelId + ".jpg").toPath()); | |
| 476 | + resp.setContentType(MediaType.IMAGE_PNG_VALUE); | |
| 477 | + IOUtils.copy(in, resp.getOutputStream()); | |
| 478 | + } catch (IOException e) { | |
| 479 | + resp.setStatus(HttpServletResponse.SC_NOT_FOUND); | |
| 480 | + } | |
| 481 | + } | |
| 459 | 482 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/record/GBRecordController.java
| ... | ... | @@ -72,7 +72,7 @@ public class GBRecordController { |
| 72 | 72 | if (!DateUtil.verification(startTime, DateUtil.formatter)){ |
| 73 | 73 | WVPResult<RecordInfo> wvpResult = new WVPResult<>(); |
| 74 | 74 | wvpResult.setCode(-1); |
| 75 | - wvpResult.setMsg("startTime error, format is " + DateUtil.yyyy_MM_dd_HH_mm_ss); | |
| 75 | + wvpResult.setMsg("startTime error, format is " + DateUtil.PATTERN); | |
| 76 | 76 | |
| 77 | 77 | ResponseEntity<WVPResult<RecordInfo>> resultResponseEntity = new ResponseEntity<>(wvpResult, HttpStatus.OK); |
| 78 | 78 | result.setResult(resultResponseEntity); |
| ... | ... | @@ -81,7 +81,7 @@ public class GBRecordController { |
| 81 | 81 | if (!DateUtil.verification(endTime, DateUtil.formatter)){ |
| 82 | 82 | WVPResult<RecordInfo> wvpResult = new WVPResult<>(); |
| 83 | 83 | wvpResult.setCode(-1); |
| 84 | - wvpResult.setMsg("endTime error, format is " + DateUtil.yyyy_MM_dd_HH_mm_ss); | |
| 84 | + wvpResult.setMsg("endTime error, format is " + DateUtil.PATTERN); | |
| 85 | 85 | ResponseEntity<WVPResult<RecordInfo>> resultResponseEntity = new ResponseEntity<>(wvpResult, HttpStatus.OK); |
| 86 | 86 | result.setResult(resultResponseEntity); |
| 87 | 87 | return result; | ... | ... |
src/main/java/com/genersoft/iot/vmp/vmanager/log/LogController.java
| ... | ... | @@ -76,14 +76,7 @@ public class LogController { |
| 76 | 76 | logger.warn("自动记录日志功能已关闭,查询结果可能不完整。"); |
| 77 | 77 | } |
| 78 | 78 | |
| 79 | - try { | |
| 80 | - if (startTime != null) { | |
| 81 | - DateUtil.format.parse(startTime); | |
| 82 | - } | |
| 83 | - if (endTime != null) { | |
| 84 | - DateUtil.format.parse(endTime); | |
| 85 | - } | |
| 86 | - } catch (ParseException e) { | |
| 79 | + if (!DateUtil.verification(startTime, DateUtil.formatter) || !DateUtil.verification(endTime, DateUtil.formatter)){ | |
| 87 | 80 | return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST); |
| 88 | 81 | } |
| 89 | 82 | ... | ... |
src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiDeviceController.java
| ... | ... | @@ -146,8 +146,8 @@ public class ApiDeviceController { |
| 146 | 146 | // 2-基于口令的双向认证, |
| 147 | 147 | // 3-基于数字证书的双向认证 |
| 148 | 148 | deviceJOSNChannel.put("Status", deviceChannel.getStatus()); |
| 149 | - deviceJOSNChannel.put("Longitude", deviceChannel.getLongitude()); | |
| 150 | - deviceJOSNChannel.put("Latitude", deviceChannel.getLatitude()); | |
| 149 | + deviceJOSNChannel.put("Longitude", deviceChannel.getLongitudeWgs84()); | |
| 150 | + deviceJOSNChannel.put("Latitude", deviceChannel.getLatitudeWgs84()); | |
| 151 | 151 | deviceJOSNChannel.put("PTZType ", deviceChannel.getPTZType()); // 云台类型, 0 - 未知, 1 - 球机, 2 - 半球, |
| 152 | 152 | // 3 - 固定枪机, 4 - 遥控枪机 |
| 153 | 153 | deviceJOSNChannel.put("CustomPTZType", ""); | ... | ... |
src/main/resources/all-application.yml
| ... | ... | @@ -32,7 +32,7 @@ spring: |
| 32 | 32 | datasource: |
| 33 | 33 | type: com.alibaba.druid.pool.DruidDataSource |
| 34 | 34 | driver-class-name: com.mysql.cj.jdbc.Driver |
| 35 | - url: jdbc:mysql://127.0.0.1:3306/wvp?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true&serverTimezone=PRC&useSSL=false | |
| 35 | + url: jdbc:mysql://127.0.0.1:3306/wvp2?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true&serverTimezone=PRC&useSSL=false&allowMultiQueries=true | |
| 36 | 36 | username: root |
| 37 | 37 | password: root123 |
| 38 | 38 | druid: | ... | ... |
src/main/resources/application-dev.yml
| ... | ... | @@ -20,7 +20,7 @@ spring: |
| 20 | 20 | datasource: |
| 21 | 21 | type: com.alibaba.druid.pool.DruidDataSource |
| 22 | 22 | driver-class-name: com.mysql.cj.jdbc.Driver |
| 23 | - url: jdbc:mysql://127.0.0.1:3306/wvp?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true&serverTimezone=PRC&useSSL=false | |
| 23 | + url: jdbc:mysql://127.0.0.1:3306/wvp?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true&serverTimezone=PRC&useSSL=false&allowMultiQueries=true | |
| 24 | 24 | username: root |
| 25 | 25 | password: 123456 |
| 26 | 26 | druid: | ... | ... |
src/main/resources/application-docker.yml
| ... | ... | @@ -20,7 +20,7 @@ spring: |
| 20 | 20 | datasource: |
| 21 | 21 | # 使用mysql 打开23-28行注释, 删除29-36行 |
| 22 | 22 | name: wvp |
| 23 | - url: jdbc:mysql://127.0.0.1:3306/wvp?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true&allowMultiQueries=true&useSSL=false | |
| 23 | + url: jdbc:mysql://127.0.0.1:3306/wvp?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true&allowMultiQueries=true&useSSL=false&allowMultiQueries=true | |
| 24 | 24 | username: root |
| 25 | 25 | password: root |
| 26 | 26 | type: com.alibaba.druid.pool.DruidDataSource | ... | ... |
src/test/java/com/genersoft/iot/vmp/service/impl/DeviceAlarmServiceImplTest.java
| ... | ... | @@ -8,6 +8,10 @@ import org.springframework.boot.test.context.SpringBootTest; |
| 8 | 8 | import org.springframework.test.context.junit4.SpringRunner; |
| 9 | 9 | |
| 10 | 10 | import javax.annotation.Resource; |
| 11 | +import java.time.Instant; | |
| 12 | +import java.time.LocalDateTime; | |
| 13 | +import java.time.ZoneOffset; | |
| 14 | +import java.time.temporal.TemporalAccessor; | |
| 11 | 15 | import java.util.Date; |
| 12 | 16 | |
| 13 | 17 | |
| ... | ... | @@ -64,8 +68,8 @@ class DeviceAlarmServiceImplTest { |
| 64 | 68 | * * 7其他报警;可以为直接组合如12为电话报警或 设备报警- |
| 65 | 69 | */ |
| 66 | 70 | deviceAlarm.setAlarmMethod((int)(Math.random()*7 + 1) + ""); |
| 67 | - Date date = randomDate("2021-01-01 00:00:00", "2021-06-01 00:00:00"); | |
| 68 | - deviceAlarm.setAlarmTime(DateUtil.format.format(date)); | |
| 71 | + Instant date = randomDate("2021-01-01 00:00:00", "2021-06-01 00:00:00"); | |
| 72 | + deviceAlarm.setAlarmTime(DateUtil.formatter.format(date)); | |
| 69 | 73 | /** |
| 70 | 74 | * 报警级别, 1为一级警情, 2为二级警情, 3为三级警情, 4为四级 警情- |
| 71 | 75 | */ |
| ... | ... | @@ -85,17 +89,20 @@ class DeviceAlarmServiceImplTest { |
| 85 | 89 | |
| 86 | 90 | |
| 87 | 91 | |
| 88 | - private Date randomDate(String beginDate, String endDate) { | |
| 92 | + private Instant randomDate(String beginDate, String endDate) { | |
| 89 | 93 | try { |
| 90 | 94 | |
| 91 | - Date start = DateUtil.format.parse(beginDate);//构造开始日期 | |
| 92 | - Date end = DateUtil.format.parse(endDate);//构造结束日期 | |
| 95 | + //构造开始日期 | |
| 96 | + LocalDateTime start = LocalDateTime.parse(beginDate, DateUtil.formatter); | |
| 97 | + | |
| 98 | + //构造结束日期 | |
| 99 | + LocalDateTime end = LocalDateTime.parse(endDate, DateUtil.formatter); | |
| 93 | 100 | //getTime()表示返回自 1970 年 1 月 1 日 00:00:00 GMT 以来此 Date 对象表示的毫秒数。 |
| 94 | - if (start.getTime() >= end.getTime()) { | |
| 101 | + if (start.isAfter(end)) { | |
| 95 | 102 | return null; |
| 96 | 103 | } |
| 97 | - long date = random(start.getTime(), end.getTime()); | |
| 98 | - return new Date(date); | |
| 104 | + long date = random(start.toInstant(ZoneOffset.of("+8")).toEpochMilli(), end.toInstant(ZoneOffset.of("+8")).toEpochMilli()); | |
| 105 | + return Instant.ofEpochMilli(date); | |
| 99 | 106 | } catch (Exception e) { |
| 100 | 107 | e.printStackTrace(); |
| 101 | 108 | } | ... | ... |
web_src/package.json
web_src/src/App.vue
| ... | ... | @@ -76,7 +76,7 @@ body, |
| 76 | 76 | line-height: 60px; |
| 77 | 77 | } |
| 78 | 78 | .el-main { |
| 79 | - background-color: #e9eef3; | |
| 79 | + background-color: #f0f2f5; | |
| 80 | 80 | color: #333; |
| 81 | 81 | text-align: center; |
| 82 | 82 | padding-top: 0px !important; |
| ... | ... | @@ -101,4 +101,8 @@ body, |
| 101 | 101 | box-shadow: inset 0 0 6px rgba(0, 0, 0, .1); |
| 102 | 102 | -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, .1); |
| 103 | 103 | } |
| 104 | +.table-header { | |
| 105 | + color: #727272; | |
| 106 | + font-weight: 600; | |
| 107 | +} | |
| 104 | 108 | </style> | ... | ... |
web_src/src/components/CloudRecord.vue
| ... | ... | @@ -18,19 +18,17 @@ |
| 18 | 18 | <div v-if="!recordDetail"> |
| 19 | 19 | |
| 20 | 20 | <!--设备列表--> |
| 21 | - <el-table :data="recordList" border style="width: 100%" :height="winHeight"> | |
| 22 | - <el-table-column prop="app" label="应用名" align="center"> | |
| 21 | + <el-table :data="recordList" style="width: 100%" :height="winHeight"> | |
| 22 | + <el-table-column prop="app" label="应用名" > | |
| 23 | 23 | </el-table-column> |
| 24 | - <el-table-column prop="stream" label="流ID" align="center"> | |
| 24 | + <el-table-column prop="stream" label="流ID" > | |
| 25 | 25 | </el-table-column> |
| 26 | - <el-table-column prop="time" label="时间" align="center"> | |
| 26 | + <el-table-column prop="time" label="时间" > | |
| 27 | 27 | </el-table-column> |
| 28 | - <el-table-column label="操作" width="360" align="center" fixed="right"> | |
| 28 | + <el-table-column label="操作" width="360" fixed="right"> | |
| 29 | 29 | <template slot-scope="scope"> |
| 30 | - <el-button-group> | |
| 31 | - <el-button size="mini" icon="el-icon-video-camera-solid" type="primary" @click="showRecordDetail(scope.row)">查看</el-button> | |
| 32 | - <!-- <el-button size="mini" icon="el-icon-delete" type="danger" @click="deleteRecord(scope.row)">删除</el-button>--> | |
| 33 | - </el-button-group> | |
| 30 | + <el-button size="medium" icon="el-icon-folder-opened" type="text" @click="showRecordDetail(scope.row)">查看</el-button> | |
| 31 | + <!-- <el-button size="mini" icon="el-icon-delete" type="danger" @click="deleteRecord(scope.row)">删除</el-button>--> | |
| 34 | 32 | </template> |
| 35 | 33 | </el-table-column> |
| 36 | 34 | </el-table> | ... | ... |
web_src/src/components/DeviceList.vue
| ... | ... | @@ -7,34 +7,33 @@ |
| 7 | 7 | @click="getDeviceList()"></el-button> |
| 8 | 8 | </div> |
| 9 | 9 | </div> |
| 10 | - <!-- <devicePlayer ref="devicePlayer"></devicePlayer> --> | |
| 11 | 10 | <!--设备列表--> |
| 12 | - <el-table :data="deviceList" border style="width: 100%;font-size: 12px;" :height="winHeight"> | |
| 13 | - <el-table-column prop="name" label="名称" align="center"> | |
| 11 | + <el-table :data="deviceList" style="width: 100%;font-size: 12px;" :height="winHeight" header-row-class-name="table-header"> | |
| 12 | + <el-table-column prop="name" label="名称" min-width="160"> | |
| 14 | 13 | </el-table-column> |
| 15 | - <el-table-column prop="deviceId" label="设备编号" width="180" align="center"> | |
| 14 | + <el-table-column prop="deviceId" label="设备编号" min-width="200" > | |
| 16 | 15 | </el-table-column> |
| 17 | - <el-table-column label="地址" width="180" align="center"> | |
| 16 | + <el-table-column label="地址" min-width="160" > | |
| 18 | 17 | <template slot-scope="scope"> |
| 19 | 18 | <div slot="reference" class="name-wrapper"> |
| 20 | 19 | <el-tag size="medium">{{ scope.row.hostAddress }}</el-tag> |
| 21 | 20 | </div> |
| 22 | 21 | </template> |
| 23 | 22 | </el-table-column> |
| 24 | - <el-table-column prop="manufacturer" label="厂家" align="center"> | |
| 23 | + <el-table-column prop="manufacturer" label="厂家" min-width="120" > | |
| 25 | 24 | </el-table-column> |
| 26 | - <el-table-column label="流传输模式" align="center" width="120"> | |
| 25 | + <el-table-column label="流传输模式" min-width="160" > | |
| 27 | 26 | <template slot-scope="scope"> |
| 28 | - <el-select size="mini" @change="transportChange(scope.row)" v-model="scope.row.streamMode" placeholder="请选择"> | |
| 27 | + <el-select size="mini" @change="transportChange(scope.row)" v-model="scope.row.streamMode" placeholder="请选择" style="width: 120px"> | |
| 29 | 28 | <el-option key="UDP" label="UDP" value="UDP"></el-option> |
| 30 | 29 | <el-option key="TCP-ACTIVE" label="TCP主动模式" :disabled="true" value="TCP-ACTIVE"></el-option> |
| 31 | 30 | <el-option key="TCP-PASSIVE" label="TCP被动模式" value="TCP-PASSIVE"></el-option> |
| 32 | 31 | </el-select> |
| 33 | 32 | </template> |
| 34 | 33 | </el-table-column> |
| 35 | - <el-table-column prop="channelCount" label="通道数" align="center"> | |
| 34 | + <el-table-column prop="channelCount" label="通道数" min-width="120" > | |
| 36 | 35 | </el-table-column> |
| 37 | - <el-table-column label="状态" width="120" align="center"> | |
| 36 | + <el-table-column label="状态" min-width="120"> | |
| 38 | 37 | <template slot-scope="scope"> |
| 39 | 38 | <div slot="reference" class="name-wrapper"> |
| 40 | 39 | <el-tag size="medium" v-if="scope.row.online == 1">在线</el-tag> |
| ... | ... | @@ -42,30 +41,32 @@ |
| 42 | 41 | </div> |
| 43 | 42 | </template> |
| 44 | 43 | </el-table-column> |
| 45 | - <el-table-column prop="keepaliveTime" label="最近心跳" align="center" width="140"> | |
| 44 | + <el-table-column prop="keepaliveTime" label="最近心跳" min-width="160" > | |
| 46 | 45 | </el-table-column> |
| 47 | - <el-table-column prop="registerTime" label="最近注册" align="center" width="140"> | |
| 48 | - </el-table-column> | |
| 49 | - <el-table-column prop="updateTime" label="更新时间" align="center" width="140"> | |
| 50 | - </el-table-column> | |
| 51 | - <el-table-column prop="createTime" label="创建时间" align="center" width="140"> | |
| 46 | + <el-table-column prop="registerTime" label="最近注册" min-width="160"> | |
| 52 | 47 | </el-table-column> |
| 48 | +<!-- <el-table-column prop="updateTime" label="更新时间" width="140">--> | |
| 49 | +<!-- </el-table-column>--> | |
| 50 | +<!-- <el-table-column prop="createTime" label="创建时间" width="140">--> | |
| 51 | +<!-- </el-table-column>--> | |
| 53 | 52 | |
| 54 | - <el-table-column label="操作" width="450" align="center" fixed="right"> | |
| 53 | + <el-table-column label="操作" min-width="450" fixed="right"> | |
| 55 | 54 | <template slot-scope="scope"> |
| 56 | - <el-button size="mini" v-if="scope.row.online!=0" icon="el-icon-refresh" @click="refDevice(scope.row)" | |
| 55 | + <el-button type="text" size="medium" v-bind:disabled="scope.row.online==0" icon="el-icon-refresh" @click="refDevice(scope.row)" | |
| 57 | 56 | @mouseover="getTooltipContent(scope.row.deviceId)">刷新 |
| 58 | 57 | </el-button> |
| 59 | - <el-button-group> | |
| 60 | - <el-button size="mini" icon="el-icon-video-camera-solid" v-bind:disabled="scope.row.online==0" | |
| 61 | - type="primary" @click="showChannelList(scope.row)">通道 | |
| 62 | - </el-button> | |
| 63 | - <el-button size="mini" icon="el-icon-location" v-bind:disabled="scope.row.online==0" type="primary" | |
| 64 | - @click="showDevicePosition(scope.row)">定位 | |
| 65 | - </el-button> | |
| 66 | - <el-button size="mini" icon="el-icon-edit" type="primary" @click="edit(scope.row)">编辑</el-button> | |
| 67 | - <el-button size="mini" icon="el-icon-delete" type="danger" @click="deleteDevice(scope.row)">删除</el-button> | |
| 68 | - </el-button-group> | |
| 58 | + <el-divider direction="vertical"></el-divider> | |
| 59 | + <el-button type="text" size="medium" icon="el-icon-video-camera" v-bind:disabled="scope.row.online==0" | |
| 60 | + @click="showChannelList(scope.row)">通道 | |
| 61 | + </el-button> | |
| 62 | + <el-divider direction="vertical"></el-divider> | |
| 63 | + <el-button size="medium" icon="el-icon-location" v-bind:disabled="scope.row.online==0" type="text" | |
| 64 | + @click="showDevicePosition(scope.row)">定位 | |
| 65 | + </el-button> | |
| 66 | + <el-divider direction="vertical"></el-divider> | |
| 67 | + <el-button size="medium" icon="el-icon-edit" type="text" @click="edit(scope.row)">编辑</el-button> | |
| 68 | + <el-divider direction="vertical"></el-divider> | |
| 69 | + <el-button size="medium" icon="el-icon-delete" type="text" @click="deleteDevice(scope.row)" style="color: #f56c6c">删除</el-button> | |
| 69 | 70 | </template> |
| 70 | 71 | </el-table-column> |
| 71 | 72 | </el-table> |
| ... | ... | @@ -347,4 +348,5 @@ export default { |
| 347 | 348 | padding: 0.3rem; |
| 348 | 349 | width: 14.4rem; |
| 349 | 350 | } |
| 351 | + | |
| 350 | 352 | </style> | ... | ... |
web_src/src/components/MediaServerManger.vue
| ... | ... | @@ -15,7 +15,7 @@ |
| 15 | 15 | <span style="font-size: 16px">{{item.id}}</span> |
| 16 | 16 | <el-button v-if="!item.defaultServer" icon="el-icon-edit" style="padding: 0;float: right;" type="text" @click="edit(item)">编辑</el-button> |
| 17 | 17 | <el-button v-if="item.defaultServer" icon="el-icon-edit" style="padding: 0;float: right;" type="text" @click="edit(item)">查看</el-button> |
| 18 | - <el-button icon="el-icon-delete" style="margin-right: 10px;padding: 0;float: right;" type="text" @click="del(item)">移除</el-button> | |
| 18 | + <el-button v-if="!item.defaultServer" icon="el-icon-delete" style="margin-right: 10px;padding: 0;float: right;" type="text" @click="del(item)">移除</el-button> | |
| 19 | 19 | <div style="margin-top: 13px; line-height: 12px; "> |
| 20 | 20 | <span style="font-size: 14px; color: #999; margin-top: 5px; ">{{item.ip}}</span> |
| 21 | 21 | <span style="font-size: 14px; color: #999; margin-top: 5px; float: right;">{{item.createTime}}</span> | ... | ... |
web_src/src/components/ParentPlatformList.vue
| ... | ... | @@ -4,14 +4,15 @@ |
| 4 | 4 | <div class="page-title">上级平台列表</div> |
| 5 | 5 | <div class="page-header-btn"> |
| 6 | 6 | <el-button icon="el-icon-plus" size="mini" style="margin-right: 1rem;" type="primary" @click="addParentPlatform">添加</el-button> |
| 7 | + <el-button icon="el-icon-refresh-right" circle size="mini" @click="refresh()"></el-button> | |
| 7 | 8 | </div> |
| 8 | 9 | </div> |
| 9 | 10 | |
| 10 | 11 | <!--设备列表--> |
| 11 | - <el-table :data="platformList" border style="width: 100%" :height="winHeight"> | |
| 12 | - <el-table-column prop="name" label="名称" align="center"></el-table-column> | |
| 13 | - <el-table-column prop="serverGBId" label="平台编号" align="center"></el-table-column> | |
| 14 | - <el-table-column label="是否启用" width="120" align="center"> | |
| 12 | + <el-table :data="platformList" style="width: 100%" :height="winHeight"> | |
| 13 | + <el-table-column prop="name" label="名称" ></el-table-column> | |
| 14 | + <el-table-column prop="serverGBId" label="平台编号" min-width="200"></el-table-column> | |
| 15 | + <el-table-column label="是否启用" min-width="80" > | |
| 15 | 16 | <template slot-scope="scope"> |
| 16 | 17 | <div slot="reference" class="name-wrapper"> |
| 17 | 18 | <el-tag size="medium" v-if="scope.row.enable">已启用</el-tag> |
| ... | ... | @@ -19,7 +20,7 @@ |
| 19 | 20 | </div> |
| 20 | 21 | </template> |
| 21 | 22 | </el-table-column> |
| 22 | - <el-table-column label="状态" width="120" align="center"> | |
| 23 | + <el-table-column label="状态" min-width="80" > | |
| 23 | 24 | <template slot-scope="scope"> |
| 24 | 25 | <div slot="reference" class="name-wrapper"> |
| 25 | 26 | <el-tag size="medium" v-if="scope.row.status">在线</el-tag> |
| ... | ... | @@ -27,17 +28,17 @@ |
| 27 | 28 | </div> |
| 28 | 29 | </template> |
| 29 | 30 | </el-table-column> |
| 30 | - <el-table-column label="地址" width="180" align="center"> | |
| 31 | + <el-table-column label="地址" min-width="160" > | |
| 31 | 32 | <template slot-scope="scope"> |
| 32 | 33 | <div slot="reference" class="name-wrapper"> |
| 33 | 34 | <el-tag size="medium">{{ scope.row.serverIP}}:{{scope.row.serverPort }}</el-tag> |
| 34 | 35 | </div> |
| 35 | 36 | </template> |
| 36 | 37 | </el-table-column> |
| 37 | - <el-table-column prop="deviceGBId" label="设备国标编号" width="200" align="center"></el-table-column> | |
| 38 | - <el-table-column prop="transport" label="信令传输模式" width="120" align="center"></el-table-column> | |
| 39 | - <el-table-column prop="channelCount" label="通道数" width="120" align="center"></el-table-column> | |
| 40 | - <el-table-column label="订阅信息" width="240" align="center" fixed="right"> | |
| 38 | + <el-table-column prop="deviceGBId" label="设备国标编号" min-width="200" ></el-table-column> | |
| 39 | + <el-table-column prop="transport" label="信令传输模式" min-width="120" ></el-table-column> | |
| 40 | + <el-table-column prop="channelCount" label="通道数" min-width="120" ></el-table-column> | |
| 41 | + <el-table-column label="订阅信息" min-width="120" fixed="right"> | |
| 41 | 42 | <template slot-scope="scope"> |
| 42 | 43 | <i v-if="scope.row.alarmSubscribe" style="font-size: 20px" title="报警订阅" class="iconfont icon-gbaojings subscribe-on " ></i> |
| 43 | 44 | <i v-if="!scope.row.alarmSubscribe" style="font-size: 20px" title="报警订阅" class="iconfont icon-gbaojings subscribe-off " ></i> |
| ... | ... | @@ -48,11 +49,11 @@ |
| 48 | 49 | </template> |
| 49 | 50 | </el-table-column> |
| 50 | 51 | |
| 51 | - <el-table-column label="操作" width="300" align="center" fixed="right"> | |
| 52 | + <el-table-column label="操作" min-width="240" fixed="right"> | |
| 52 | 53 | <template slot-scope="scope"> |
| 53 | - <el-button size="mini" icon="el-icon-edit" @click="editPlatform(scope.row)">编辑</el-button> | |
| 54 | - <el-button size="mini" icon="el-icon-share" type="primary" @click="chooseChannel(scope.row)">选择通道</el-button> | |
| 55 | - <el-button size="mini" icon="el-icon-delete" type="danger" @click="deletePlatform(scope.row)">删除</el-button> | |
| 54 | + <el-button size="medium" icon="el-icon-edit" type="text" @click="editPlatform(scope.row)">编辑</el-button> | |
| 55 | + <el-button size="medium" icon="el-icon-share" type="text" @click="chooseChannel(scope.row)">选择通道</el-button> | |
| 56 | + <el-button size="medium" icon="el-icon-delete" type="text" style="color: #f56c6c" @click="deletePlatform(scope.row)">删除</el-button> | |
| 56 | 57 | </template> |
| 57 | 58 | </el-table-column> |
| 58 | 59 | </el-table> |
| ... | ... | @@ -168,6 +169,9 @@ export default { |
| 168 | 169 | console.log(error); |
| 169 | 170 | }); |
| 170 | 171 | |
| 172 | + }, | |
| 173 | + refresh: function (){ | |
| 174 | + this.initData(); | |
| 171 | 175 | } |
| 172 | 176 | |
| 173 | 177 | } | ... | ... |
web_src/src/components/PushVideoList.vue
| ... | ... | @@ -34,52 +34,54 @@ |
| 34 | 34 | <el-button icon="el-icon-delete" size="mini" style="margin-right: 1rem;" |
| 35 | 35 | :disabled="multipleSelection.length === 0" type="danger" @click="batchDel">批量移除 |
| 36 | 36 | </el-button> |
| 37 | + <el-button icon="el-icon-refresh-right" circle size="mini" @click="refresh()"></el-button> | |
| 37 | 38 | </div> |
| 38 | 39 | </div> |
| 39 | 40 | <devicePlayer ref="devicePlayer"></devicePlayer> |
| 40 | 41 | <addStreamTOGB ref="addStreamTOGB"></addStreamTOGB> |
| 41 | - <el-table ref="pushListTable" :data="pushList" border style="width: 100%" :height="winHeight" | |
| 42 | + <el-table ref="pushListTable" :data="pushList" style="width: 100%" :height="winHeight" | |
| 42 | 43 | @selection-change="handleSelectionChange" :row-key="(row)=> row.app + row.stream"> |
| 43 | - <el-table-column align="center" type="selection" :reserve-selection="true" width="55"> | |
| 44 | + <el-table-column type="selection" :reserve-selection="true" min-width="55"> | |
| 44 | 45 | </el-table-column> |
| 45 | - <el-table-column prop="name" label="名称" align="center"> | |
| 46 | + <el-table-column prop="name" label="名称" min-width="200"> | |
| 46 | 47 | </el-table-column> |
| 47 | - <el-table-column prop="app" label="APP" align="center"> | |
| 48 | + <el-table-column prop="app" label="APP" min-width="200"> | |
| 48 | 49 | </el-table-column> |
| 49 | - <el-table-column prop="stream" label="流ID" align="center"> | |
| 50 | + <el-table-column prop="stream" label="流ID" min-width="200"> | |
| 50 | 51 | </el-table-column> |
| 51 | - <el-table-column prop="gbId" label="国标编码" width="200" align="center"> | |
| 52 | + <el-table-column prop="gbId" label="国标编码" min-width="200" > | |
| 52 | 53 | </el-table-column> |
| 53 | - <el-table-column prop="mediaServerId" label="流媒体" width="200" align="center"> | |
| 54 | + <el-table-column prop="mediaServerId" label="流媒体" min-width="200" > | |
| 54 | 55 | </el-table-column> |
| 55 | - <el-table-column label="开始时间" align="center" width="200"> | |
| 56 | + <el-table-column label="开始时间" min-width="200"> | |
| 56 | 57 | <template slot-scope="scope"> |
| 57 | 58 | <el-button-group> |
| 58 | 59 | {{ dateFormat(parseInt(scope.row.createStamp)) }} |
| 59 | 60 | </el-button-group> |
| 60 | 61 | </template> |
| 61 | 62 | </el-table-column> |
| 62 | - <el-table-column label="正在推流" align="center" width="100"> | |
| 63 | + <el-table-column label="正在推流" min-width="100"> | |
| 63 | 64 | <template slot-scope="scope"> |
| 64 | 65 | {{ (scope.row.status == false && scope.row.gbId == null) || scope.row.status ? '是' : '否' }} |
| 65 | 66 | </template> |
| 66 | 67 | </el-table-column> |
| 67 | 68 | |
| 68 | - <el-table-column label="操作" width="360" align="center" fixed="right"> | |
| 69 | + <el-table-column label="操作" min-width="360" fixed="right"> | |
| 69 | 70 | <template slot-scope="scope"> |
| 70 | - <el-button-group> | |
| 71 | - <el-button size="mini" icon="el-icon-video-play" | |
| 72 | - v-if="(scope.row.status == false && scope.row.gbId == null) || scope.row.status" | |
| 73 | - @click="playPush(scope.row)">播放 | |
| 74 | - </el-button> | |
| 75 | - <el-button size="mini" icon="el-icon-delete" type="danger" @click="stopPush(scope.row)">移除</el-button> | |
| 76 | - <el-button size="mini" icon="el-icon-position" type="primary" v-if="!!!scope.row.gbId" | |
| 77 | - @click="addToGB(scope.row)">加入国标 | |
| 78 | - </el-button> | |
| 79 | - <el-button size="mini" icon="el-icon-position" type="primary" v-if="!!scope.row.gbId" | |
| 80 | - @click="removeFromGB(scope.row)">移出国标 | |
| 81 | - </el-button> | |
| 82 | - </el-button-group> | |
| 71 | + <el-button size="medium" icon="el-icon-video-play" | |
| 72 | + v-if="(scope.row.status == false && scope.row.gbId == null) || scope.row.status" | |
| 73 | + @click="playPush(scope.row)" type="text">播放 | |
| 74 | + </el-button> | |
| 75 | + <el-divider direction="vertical"></el-divider> | |
| 76 | + <el-button size="medium" icon="el-icon-delete" type="text" @click="stopPush(scope.row)" style="color: #f56c6c" >移除</el-button> | |
| 77 | + <el-divider direction="vertical"></el-divider> | |
| 78 | + <el-button size="medium" icon="el-icon-position" type="text" v-if="!!!scope.row.gbId" | |
| 79 | + @click="addToGB(scope.row)">加入国标 | |
| 80 | + </el-button> | |
| 81 | + <el-divider v-if="!!!scope.row.gbId" direction="vertical"></el-divider> | |
| 82 | + <el-button size="medium" icon="el-icon-position" type="text" v-if="!!scope.row.gbId" | |
| 83 | + @click="removeFromGB(scope.row)">移出国标 | |
| 84 | + </el-button> | |
| 83 | 85 | </template> |
| 84 | 86 | </el-table-column> |
| 85 | 87 | </el-table> |
| ... | ... | @@ -284,6 +286,9 @@ export default { |
| 284 | 286 | handleSelectionChange: function (val) { |
| 285 | 287 | this.multipleSelection = val; |
| 286 | 288 | }, |
| 289 | + refresh: function () { | |
| 290 | + this.initData(); | |
| 291 | + }, | |
| 287 | 292 | } |
| 288 | 293 | }; |
| 289 | 294 | </script> | ... | ... |
web_src/src/components/StreamProxyList.vue
| ... | ... | @@ -5,14 +5,15 @@ |
| 5 | 5 | <div class="page-header-btn"> |
| 6 | 6 | <el-button icon="el-icon-plus" size="mini" style="margin-right: 1rem;" type="primary" @click="addStreamProxy">添加代理</el-button> |
| 7 | 7 | <el-button v-if="false" icon="el-icon-search" size="mini" style="margin-right: 1rem;" type="primary" @click="addOnvif">搜索ONVIF</el-button> |
| 8 | + <el-button icon="el-icon-refresh-right" circle size="mini" @click="refresh()"></el-button> | |
| 8 | 9 | </div> |
| 9 | 10 | </div> |
| 10 | 11 | <devicePlayer ref="devicePlayer"></devicePlayer> |
| 11 | - <el-table :data="streamProxyList" border style="width: 100%" :height="winHeight"> | |
| 12 | - <el-table-column prop="name" label="名称" align="center" show-overflow-tooltip/> | |
| 13 | - <el-table-column prop="app" label="流应用名" align="center" show-overflow-tooltip/> | |
| 14 | - <el-table-column prop="stream" label="流ID" align="center" show-overflow-tooltip/> | |
| 15 | - <el-table-column label="流地址" width="400" align="center" show-overflow-tooltip > | |
| 12 | + <el-table :data="streamProxyList" style="width: 100%" :height="winHeight"> | |
| 13 | + <el-table-column prop="name" label="名称" min-width="120" show-overflow-tooltip/> | |
| 14 | + <el-table-column prop="app" label="流应用名" min-width="120" show-overflow-tooltip/> | |
| 15 | + <el-table-column prop="stream" label="流ID" min-width="120" show-overflow-tooltip/> | |
| 16 | + <el-table-column label="流地址" min-width="400" show-overflow-tooltip > | |
| 16 | 17 | <template slot-scope="scope"> |
| 17 | 18 | <div slot="reference" class="name-wrapper"> |
| 18 | 19 | |
| ... | ... | @@ -27,8 +28,8 @@ |
| 27 | 28 | </div> |
| 28 | 29 | </template> |
| 29 | 30 | </el-table-column> |
| 30 | - <el-table-column prop="mediaServerId" label="流媒体" width="150" align="center"></el-table-column> | |
| 31 | - <el-table-column label="类型" width="100" align="center"> | |
| 31 | + <el-table-column prop="mediaServerId" label="流媒体" min-width="180" ></el-table-column> | |
| 32 | + <el-table-column label="类型" width="100" > | |
| 32 | 33 | <template slot-scope="scope"> |
| 33 | 34 | <div slot="reference" class="name-wrapper"> |
| 34 | 35 | <el-tag size="medium">{{scope.row.type}}</el-tag> |
| ... | ... | @@ -36,8 +37,8 @@ |
| 36 | 37 | </template> |
| 37 | 38 | </el-table-column> |
| 38 | 39 | |
| 39 | - <el-table-column prop="gbId" label="国标编码" width="180" align="center" show-overflow-tooltip/> | |
| 40 | - <el-table-column label="状态" width="120" align="center"> | |
| 40 | + <el-table-column prop="gbId" label="国标编码" min-width="180" show-overflow-tooltip/> | |
| 41 | + <el-table-column label="状态" min-width="120" > | |
| 41 | 42 | <template slot-scope="scope"> |
| 42 | 43 | <div slot="reference" class="name-wrapper"> |
| 43 | 44 | <el-tag size="medium" v-if="scope.row.status">在线</el-tag> |
| ... | ... | @@ -45,7 +46,7 @@ |
| 45 | 46 | </div> |
| 46 | 47 | </template> |
| 47 | 48 | </el-table-column> |
| 48 | - <el-table-column label="启用" width="120" align="center"> | |
| 49 | + <el-table-column label="启用" min-width="120" > | |
| 49 | 50 | <template slot-scope="scope"> |
| 50 | 51 | <div slot="reference" class="name-wrapper"> |
| 51 | 52 | <el-tag size="medium" v-if="scope.row.enable">已启用</el-tag> |
| ... | ... | @@ -53,8 +54,8 @@ |
| 53 | 54 | </div> |
| 54 | 55 | </template> |
| 55 | 56 | </el-table-column> |
| 56 | - <el-table-column prop="createTime" label="创建时间" align="center" width="150" show-overflow-tooltip/> | |
| 57 | - <el-table-column label="转HLS" width="120" align="center"> | |
| 57 | + <el-table-column prop="createTime" label="创建时间" min-width="150" show-overflow-tooltip/> | |
| 58 | + <el-table-column label="转HLS" min-width="120" > | |
| 58 | 59 | <template slot-scope="scope"> |
| 59 | 60 | <div slot="reference" class="name-wrapper"> |
| 60 | 61 | <el-tag size="medium" v-if="scope.row.enable_hls">已启用</el-tag> |
| ... | ... | @@ -62,7 +63,7 @@ |
| 62 | 63 | </div> |
| 63 | 64 | </template> |
| 64 | 65 | </el-table-column> |
| 65 | - <el-table-column label="MP4录制" width="120" align="center"> | |
| 66 | + <el-table-column label="MP4录制" min-width="120" > | |
| 66 | 67 | <template slot-scope="scope"> |
| 67 | 68 | <div slot="reference" class="name-wrapper"> |
| 68 | 69 | <el-tag size="medium" v-if="scope.row.enable_mp4">已启用</el-tag> |
| ... | ... | @@ -70,7 +71,7 @@ |
| 70 | 71 | </div> |
| 71 | 72 | </template> |
| 72 | 73 | </el-table-column> |
| 73 | - <el-table-column label="无人观看自动删除" width="160" align="center"> | |
| 74 | + <el-table-column label="无人观看自动删除" min-width="160" > | |
| 74 | 75 | <template slot-scope="scope"> |
| 75 | 76 | <div slot="reference" class="name-wrapper"> |
| 76 | 77 | <el-tag size="medium" v-if="scope.row.enable_remove_none_reader">已启用</el-tag> |
| ... | ... | @@ -80,14 +81,15 @@ |
| 80 | 81 | </el-table-column> |
| 81 | 82 | |
| 82 | 83 | |
| 83 | - <el-table-column label="操作" width="360" align="center" fixed="right"> | |
| 84 | + <el-table-column label="操作" width="360" fixed="right"> | |
| 84 | 85 | <template slot-scope="scope"> |
| 85 | - <el-button-group> | |
| 86 | - <el-button size="mini" icon="el-icon-video-play" v-if="scope.row.enable" @click="play(scope.row)">播放</el-button> | |
| 87 | - <el-button size="mini" icon="el-icon-close" type="success" v-if="scope.row.enable" @click="stop(scope.row)">停用</el-button> | |
| 88 | - <el-button size="mini" icon="el-icon-check" type="primary" :loading="startBtnLaoding" v-if="!scope.row.enable" @click="start(scope.row)">启用</el-button> | |
| 89 | - <el-button size="mini" icon="el-icon-delete" type="danger" @click="deleteStreamProxy(scope.row)">删除</el-button> | |
| 90 | - </el-button-group> | |
| 86 | + <el-button size="medium" icon="el-icon-video-play" type="text" v-if="scope.row.enable" @click="play(scope.row)">播放</el-button> | |
| 87 | + <el-divider direction="vertical"></el-divider> | |
| 88 | + <el-button size="medium" icon="el-icon-switch-button" type="text" v-if="scope.row.enable" @click="stop(scope.row)">停用</el-button> | |
| 89 | + <el-divider direction="vertical"></el-divider> | |
| 90 | + <el-button size="medium" icon="el-icon-check" type="text" :loading="startBtnLaoding" v-if="!scope.row.enable" @click="start(scope.row)">启用</el-button> | |
| 91 | + <el-divider v-if="!scope.row.enable" direction="vertical"></el-divider> | |
| 92 | + <el-button size="medium" icon="el-icon-delete" type="text" style="color: #f56c6c" @click="deleteStreamProxy(scope.row)">删除</el-button> | |
| 91 | 93 | </template> |
| 92 | 94 | </el-table-column> |
| 93 | 95 | </el-table> |
| ... | ... | @@ -305,8 +307,10 @@ |
| 305 | 307 | console.log(error); |
| 306 | 308 | that.getListLoading = false; |
| 307 | 309 | }); |
| 308 | - } | |
| 309 | - | |
| 310 | + }, | |
| 311 | + refresh: function (){ | |
| 312 | + this.initData(); | |
| 313 | + } | |
| 310 | 314 | } |
| 311 | 315 | }; |
| 312 | 316 | </script> | ... | ... |
web_src/src/components/channelList.vue
| ... | ... | @@ -2,10 +2,10 @@ |
| 2 | 2 | <div id="channelList" style="width: 100%"> |
| 3 | 3 | <div class="page-header"> |
| 4 | 4 | <div class="page-title"> |
| 5 | - <el-button icon="el-icon-arrow-left" size="mini" style="margin-right: 1rem;" type="primary" @click="showDevice"> | |
| 6 | - 返回 | |
| 7 | - </el-button> | |
| 8 | - 通道列表({{ parentChannelId == 0 ? deviceId : parentChannelId }})</div> | |
| 5 | + <el-button icon="el-icon-back" size="mini" style="font-size: 20px; color: #000;" type="text" @click="showDevice" ></el-button> | |
| 6 | + <el-divider direction="vertical"></el-divider> | |
| 7 | + 通道列表 | |
| 8 | + </div> | |
| 9 | 9 | <div class="page-header-btn"> |
| 10 | 10 | 搜索: |
| 11 | 11 | <el-input @input="search" style="margin-right: 1rem; width: auto;" size="mini" placeholder="关键字" |
| ... | ... | @@ -25,84 +25,85 @@ |
| 25 | 25 | <el-option label="在线" value="true"></el-option> |
| 26 | 26 | <el-option label="离线" value="false"></el-option> |
| 27 | 27 | </el-select> |
| 28 | - <el-checkbox size="mini" v-model="autoList" @change="autoListChange"> | |
| 29 | - 自动刷新 | |
| 30 | - </el-checkbox> | |
| 28 | + <el-button icon="el-icon-refresh-right" circle size="mini" @click="refresh()"></el-button> | |
| 31 | 29 | </div> |
| 32 | 30 | </div> |
| 33 | 31 | <devicePlayer ref="devicePlayer" v-loading="isLoging"></devicePlayer> |
| 34 | 32 | <!--设备列表--> |
| 35 | - <el-table ref="channelListTable" :data="deviceChannelList" :height="winHeight" border style="width: 100%"> | |
| 36 | - <el-table-column prop="channelId" label="通道编号" width="200"> | |
| 33 | + <el-table ref="channelListTable" :data="deviceChannelList" :height="winHeight" style="width: 100%" header-row-class-name="table-header"> | |
| 34 | + <el-table-column prop="channelId" label="通道编号" min-width="200"> | |
| 37 | 35 | </el-table-column> |
| 38 | - <el-table-column prop="name" label="通道名称"> | |
| 36 | + <el-table-column prop="deviceId" label="设备编号" min-width="200"> | |
| 39 | 37 | </el-table-column> |
| 40 | - <el-table-column label="快照" width="80" align="center"> | |
| 41 | - <template slot-scope="scope"> | |
| 42 | - <img style="max-height: 3rem;max-width: 4rem;" | |
| 43 | - v-if="scope.row.subCount === 0 && scope.row.parental === 0" | |
| 44 | - :id="scope.row.deviceId + '_' + scope.row.channelId" | |
| 45 | - :src="getSnap(scope.row)" | |
| 46 | - @error="getSnapErrorEvent($event.target.id)" | |
| 47 | - alt=""> | |
| 48 | - <!-- <el-image--> | |
| 49 | - <!-- :id="'snapImg_' + scope.row.deviceId + '_' + scope.row.channelId"--> | |
| 50 | - <!-- :src="getSnap(scope.row)"--> | |
| 51 | - <!-- @error="getSnapErrorEvent($event, scope.row)"--> | |
| 52 | - <!-- :fit="'contain'">--> | |
| 53 | - <!-- <div slot="error" class="image-slot">--> | |
| 54 | - <!-- <i class="el-icon-picture-outline"></i>--> | |
| 55 | - <!-- </div>--> | |
| 56 | - <!-- </el-image>--> | |
| 38 | + <el-table-column prop="name" label="通道名称" min-width="200"> | |
| 39 | + </el-table-column> | |
| 40 | + <el-table-column label="快照" min-width="120"> | |
| 41 | + <template v-slot:default="scope"> | |
| 42 | + <el-image | |
| 43 | + :src="getSnap(scope.row)" | |
| 44 | + :preview-src-list="getBigSnap(scope.row)" | |
| 45 | + @error="getSnapErrorEvent(scope.row.deviceId, scope.row.channelId)" | |
| 46 | + :fit="'contain'" | |
| 47 | + style="width: 60px"> | |
| 48 | + <div slot="error" class="image-slot"> | |
| 49 | + <i class="el-icon-picture-outline"></i> | |
| 50 | + </div> | |
| 51 | + </el-image> | |
| 57 | 52 | </template> |
| 58 | 53 | </el-table-column> |
| 59 | - <el-table-column prop="subCount" label="子节点数"> | |
| 54 | + <el-table-column prop="subCount" label="子节点数" min-width="120"> | |
| 60 | 55 | </el-table-column> |
| 61 | - <el-table-column prop="manufacture" label="厂家"> | |
| 56 | + <el-table-column prop="manufacture" label="厂家" min-width="120"> | |
| 62 | 57 | </el-table-column> |
| 63 | - <el-table-column label="位置信息" align="center"> | |
| 58 | + <el-table-column label="位置信息" min-width="200"> | |
| 64 | 59 | <template slot-scope="scope"> |
| 65 | - <span>{{ scope.row.longitude }},{{ scope.row.latitude }}</span> | |
| 60 | + <span v-if="scope.row.longitude*scope.row.latitude > 0">{{ scope.row.longitude }},<br>{{ scope.row.latitude }}</span> | |
| 61 | + <span v-if="scope.row.longitude*scope.row.latitude === 0">无</span> | |
| 66 | 62 | </template> |
| 67 | 63 | </el-table-column> |
| 68 | - <el-table-column prop="ptztypeText" label="云台类型"/> | |
| 69 | - <el-table-column label="开启音频" align="center"> | |
| 64 | + <el-table-column prop="ptztypeText" label="云台类型" min-width="120"/> | |
| 65 | + <el-table-column label="开启音频" min-width="120"> | |
| 70 | 66 | <template slot-scope="scope"> |
| 71 | 67 | <el-switch @change="updateChannel(scope.row)" v-model="scope.row.hasAudio" active-color="#409EFF"> |
| 72 | 68 | </el-switch> |
| 73 | 69 | </template> |
| 74 | 70 | </el-table-column> |
| 75 | - <el-table-column label="状态" width="180" align="center"> | |
| 71 | + <el-table-column label="状态" min-width="120"> | |
| 76 | 72 | <template slot-scope="scope"> |
| 77 | 73 | <div slot="reference" class="name-wrapper"> |
| 78 | - <el-tag size="medium" v-if="scope.row.status == 1">开启</el-tag> | |
| 79 | - <el-tag size="medium" type="info" v-if="scope.row.status == 0">关闭</el-tag> | |
| 74 | + <el-tag size="medium" v-if="scope.row.status === 1">在线</el-tag> | |
| 75 | + <el-tag size="medium" type="info" v-if="scope.row.status === 0">离线</el-tag> | |
| 80 | 76 | </div> |
| 81 | 77 | </template> |
| 82 | 78 | </el-table-column> |
| 83 | 79 | |
| 84 | 80 | |
| 85 | - <el-table-column label="操作" width="280" align="center" fixed="right"> | |
| 81 | + <el-table-column label="操作" min-width="280" fixed="right"> | |
| 86 | 82 | <template slot-scope="scope"> |
| 87 | - <el-button-group> | |
| 88 | - <!-- <el-button size="mini" icon="el-icon-video-play" v-if="scope.row.parental == 0" @click="sendDevicePush(scope.row)">播放</el-button> --> | |
| 89 | - <el-button size="mini" icon="el-icon-video-play" @click="sendDevicePush(scope.row)">播放</el-button> | |
| 90 | - <el-button size="mini" icon="el-icon-switch-button" type="danger" v-if="!!scope.row.streamId" | |
| 91 | - @click="stopDevicePush(scope.row)">停止 | |
| 92 | - </el-button> | |
| 93 | - <el-button size="mini" icon="el-icon-s-open" type="primary" v-if="scope.row.subCount > 0 || scope.row.parental === 1" | |
| 94 | - @click="changeSubchannel(scope.row)">查看 | |
| 95 | - </el-button> | |
| 96 | - <el-button size="mini" icon="el-icon-video-camera" type="primary" @click="queryRecords(scope.row)">设备录像 | |
| 97 | - </el-button> | |
| 98 | - <!-- <el-button size="mini" @click="sendDevicePush(scope.row)">录像查询</el-button> --> | |
| 99 | - </el-button-group> | |
| 83 | + <!-- <el-button size="mini" icon="el-icon-video-play" v-if="scope.row.parental == 0" @click="sendDevicePush(scope.row)">播放</el-button> --> | |
| 84 | + <el-button size="medium" icon="el-icon-video-play" type="text" @click="sendDevicePush(scope.row)">播放</el-button> | |
| 85 | + <el-button size="medium" icon="el-icon-switch-button" type="text" style="color: #f56c6c" v-if="!!scope.row.streamId" | |
| 86 | + @click="stopDevicePush(scope.row)">停止 | |
| 87 | + </el-button> | |
| 88 | + <el-divider direction="vertical"></el-divider> | |
| 89 | + <el-button size="medium" icon="el-icon-s-open" type="text" v-if="scope.row.subCount > 0 || scope.row.parental === 1" | |
| 90 | + @click="changeSubchannel(scope.row)">查看 | |
| 91 | + </el-button> | |
| 92 | + <el-divider v-if="scope.row.subCount > 0 || scope.row.parental === 1" direction="vertical"></el-divider> | |
| 93 | + <el-button size="medium" icon="el-icon-video-camera" type="text" @click="queryRecords(scope.row)">设备录像 | |
| 94 | + </el-button> | |
| 100 | 95 | </template> |
| 101 | 96 | </el-table-column> |
| 102 | 97 | </el-table> |
| 103 | - <el-pagination style="float: right" @size-change="handleSizeChange" @current-change="currentChange" | |
| 104 | - :current-page="currentPage" :page-size="count" :page-sizes="[15, 20, 30, 50]" | |
| 105 | - layout="total, sizes, prev, pager, next" :total="total"> | |
| 98 | + <el-pagination | |
| 99 | + style="float: right" | |
| 100 | + @size-change="handleSizeChange" | |
| 101 | + @current-change="currentChange" | |
| 102 | + :current-page="currentPage" | |
| 103 | + :page-size="count" | |
| 104 | + :page-sizes="[15, 25, 35, 50]" | |
| 105 | + layout="total, sizes, prev, pager, next" | |
| 106 | + :total="total"> | |
| 106 | 107 | </el-pagination> |
| 107 | 108 | </div> |
| 108 | 109 | </template> |
| ... | ... | @@ -111,6 +112,8 @@ |
| 111 | 112 | import devicePlayer from './dialog/devicePlayer.vue' |
| 112 | 113 | import uiHeader from '../layout/UiHeader.vue' |
| 113 | 114 | import moment from "moment"; |
| 115 | +import DviceService from "./service/DeviceService"; | |
| 116 | +import DeviceService from "./service/DeviceService"; | |
| 114 | 117 | |
| 115 | 118 | export default { |
| 116 | 119 | name: 'channelList', |
| ... | ... | @@ -120,6 +123,8 @@ export default { |
| 120 | 123 | }, |
| 121 | 124 | data() { |
| 122 | 125 | return { |
| 126 | + deviceService: new DeviceService(), | |
| 127 | + device: null, | |
| 123 | 128 | deviceId: this.$route.params.deviceId, |
| 124 | 129 | parentChannelId: this.$route.params.parentChannelId, |
| 125 | 130 | deviceChannelList: [], |
| ... | ... | @@ -135,16 +140,21 @@ export default { |
| 135 | 140 | total: 0, |
| 136 | 141 | beforeUrl: "/deviceList", |
| 137 | 142 | isLoging: false, |
| 138 | - autoList: true, | |
| 139 | 143 | loadSnap: {} |
| 140 | 144 | }; |
| 141 | 145 | }, |
| 142 | 146 | |
| 143 | 147 | mounted() { |
| 144 | - this.initData(); | |
| 145 | - if (this.autoList) { | |
| 146 | - this.updateLooper = setInterval(this.initData, 5000); | |
| 148 | + if (this.deviceId) { | |
| 149 | + this.deviceService.getDevice(this.deviceId, (result)=>{ | |
| 150 | + this.device = result; | |
| 151 | + | |
| 152 | + }, (error)=>{ | |
| 153 | + console.log("获取设备信息失败") | |
| 154 | + console.error(error) | |
| 155 | + }) | |
| 147 | 156 | } |
| 157 | + this.initData(); | |
| 148 | 158 | |
| 149 | 159 | }, |
| 150 | 160 | destroyed() { |
| ... | ... | @@ -177,12 +187,8 @@ export default { |
| 177 | 187 | }) |
| 178 | 188 | }, |
| 179 | 189 | handleSizeChange: function (val) { |
| 180 | - var url = `/${this.$router.currentRoute.name}/${this.$router.params.deviceId}/${this.$router.params.parentChannelId}/${val}/1` | |
| 181 | - this.$router.push(url).then(() => { | |
| 182 | - this.initParam(); | |
| 183 | - this.initData(); | |
| 184 | - }) | |
| 185 | - | |
| 190 | + this.count = val; | |
| 191 | + this.getDeviceChannelList(); | |
| 186 | 192 | }, |
| 187 | 193 | getDeviceChannelList: function () { |
| 188 | 194 | let that = this; |
| ... | ... | @@ -227,7 +233,7 @@ export default { |
| 227 | 233 | setTimeout(() => { |
| 228 | 234 | |
| 229 | 235 | let snapId = deviceId + "_" + channelId; |
| 230 | - that.loadSnap[snapId] = 0; | |
| 236 | + that.loadSnap[deviceId + channelId] = 0; | |
| 231 | 237 | that.getSnapErrorEvent(snapId) |
| 232 | 238 | }, 5000) |
| 233 | 239 | that.$refs.devicePlayer.openDialog("media", deviceId, channelId, { |
| ... | ... | @@ -269,19 +275,24 @@ export default { |
| 269 | 275 | }); |
| 270 | 276 | }, |
| 271 | 277 | getSnap: function (row) { |
| 272 | - return '/static/snap/' + row.deviceId + '_' + row.channelId + '.jpg' | |
| 278 | + let url = (process.env.NODE_ENV === 'development'? "debug": "") + '/api/device/query/snap/' + row.deviceId + '/' + row.channelId | |
| 279 | + return url | |
| 273 | 280 | }, |
| 274 | - getSnapErrorEvent: function (id) { | |
| 281 | + getBigSnap: function (row) { | |
| 282 | + return [this.getSnap(row)] | |
| 283 | + }, | |
| 284 | + getSnapErrorEvent: function (deviceId, channelId) { | |
| 275 | 285 | |
| 276 | - if (typeof (this.loadSnap[id]) != "undefined") { | |
| 277 | - console.log("下载截图" + this.loadSnap[id]) | |
| 278 | - if (this.loadSnap[id] > 5) { | |
| 279 | - delete this.loadSnap[id]; | |
| 286 | + if (typeof (this.loadSnap[deviceId + channelId]) != "undefined") { | |
| 287 | + console.log("下载截图" + this.loadSnap[deviceId + channelId]) | |
| 288 | + if (this.loadSnap[deviceId + channelId] > 5) { | |
| 289 | + delete this.loadSnap[deviceId + channelId]; | |
| 280 | 290 | return; |
| 281 | 291 | } |
| 282 | 292 | setTimeout(() => { |
| 283 | - this.loadSnap[id]++ | |
| 284 | - document.getElementById(id).setAttribute("src", '/static/snap/' + id + '.jpg?' + new Date().getTime()) | |
| 293 | + let url = (process.env.NODE_ENV === 'development'? "debug": "") + '/api/device/query/snap/' + deviceId + '/' + channelId | |
| 294 | + this.loadSnap[deviceId + channelId]++ | |
| 295 | + document.getElementById(deviceId + channelId).setAttribute("src", url + '?' + new Date().getTime()) | |
| 285 | 296 | }, 1000) |
| 286 | 297 | |
| 287 | 298 | } |
| ... | ... | @@ -342,12 +353,8 @@ export default { |
| 342 | 353 | console.log(JSON.stringify(res)); |
| 343 | 354 | }); |
| 344 | 355 | }, |
| 345 | - autoListChange: function () { | |
| 346 | - if (this.autoList) { | |
| 347 | - this.updateLooper = setInterval(this.initData, 1500); | |
| 348 | - } else { | |
| 349 | - window.clearInterval(this.updateLooper); | |
| 350 | - } | |
| 356 | + refresh: function () { | |
| 357 | + this.initData(); | |
| 351 | 358 | } |
| 352 | 359 | |
| 353 | 360 | } | ... | ... |
web_src/src/components/common/DeviceTree.vue
| ... | ... | @@ -84,22 +84,34 @@ export default { |
| 84 | 84 | }else { |
| 85 | 85 | resolve([]) |
| 86 | 86 | } |
| 87 | + }, (list)=>{ | |
| 88 | + console.log("设备加载完成") | |
| 87 | 89 | }, (error)=>{ |
| 88 | 90 | |
| 89 | 91 | }) |
| 90 | 92 | } |
| 91 | 93 | if (node.level === 1) { |
| 92 | - this.deviceService.getAllChannel(true, true, node.data.id, (catalogData) => { | |
| 93 | - this.deviceService.getAllChannel(false, true, node.data.id, (channelData) => { | |
| 94 | - let data = catalogData.concat(channelData) | |
| 95 | - this.channelDataHandler(data, resolve) | |
| 94 | + let channelArray = [] | |
| 95 | + this.deviceService.getAllChannel(true, true, node.data.id, catalogData =>{ | |
| 96 | + channelArray = channelArray.concat(catalogData) | |
| 97 | + this.channelDataHandler(channelArray, resolve) | |
| 98 | + },(endCatalogData) => { | |
| 99 | + this.deviceService.getAllChannel(false, true, node.data.id, channelData => { | |
| 100 | + channelArray = channelArray.concat(channelData) | |
| 101 | + this.channelDataHandler(channelArray, resolve) | |
| 102 | + }, endChannelList => { | |
| 103 | + | |
| 96 | 104 | }) |
| 97 | 105 | }) |
| 98 | 106 | }else if (node.level > 1){ |
| 107 | + let channelArray = [] | |
| 99 | 108 | this.deviceService.getAllSubChannel(true, node.data.deviceId, node.data.id, (catalogData)=>{ |
| 109 | + channelArray = channelArray.concat(catalogData) | |
| 110 | + this.channelDataHandler(channelArray, resolve) | |
| 111 | + }, (endCatalogData)=>{ | |
| 100 | 112 | this.deviceService.getAllSubChannel(false, node.data.deviceId, node.data.id, (channelData)=>{ |
| 101 | - let data = catalogData.concat(channelData) | |
| 102 | - this.channelDataHandler(data, resolve) | |
| 113 | + channelArray = channelArray.concat(channelData) | |
| 114 | + this.channelDataHandler(channelArray, resolve) | |
| 103 | 115 | }) |
| 104 | 116 | }) |
| 105 | 117 | } | ... | ... |
web_src/src/components/common/jessibuca.vue
| ... | ... | @@ -23,11 +23,11 @@ |
| 23 | 23 | </template> |
| 24 | 24 | |
| 25 | 25 | <script> |
| 26 | +let jessibucaPlayer = {}; | |
| 26 | 27 | export default { |
| 27 | 28 | name: 'jessibuca', |
| 28 | 29 | data() { |
| 29 | 30 | return { |
| 30 | - jessibuca: null, | |
| 31 | 31 | playing: false, |
| 32 | 32 | isNotMute: false, |
| 33 | 33 | quieting: false, |
| ... | ... | @@ -49,6 +49,7 @@ export default { |
| 49 | 49 | window.onerror = (msg) => { |
| 50 | 50 | // console.error(msg) |
| 51 | 51 | }; |
| 52 | + console.log(this._uid) | |
| 52 | 53 | let paramUrl = decodeURIComponent(this.$route.params.url) |
| 53 | 54 | this.$nextTick(() => { |
| 54 | 55 | this.updatePlayerDomSize() |
| ... | ... | @@ -88,7 +89,7 @@ export default { |
| 88 | 89 | let options = {}; |
| 89 | 90 | console.log("hasAudio " + this.hasAudio) |
| 90 | 91 | |
| 91 | - this.jessibuca = new window.Jessibuca(Object.assign( | |
| 92 | + jessibucaPlayer[this._uid] = new window.Jessibuca(Object.assign( | |
| 92 | 93 | { |
| 93 | 94 | container: this.$refs.container, |
| 94 | 95 | videoBuffer: 0.2, // 最大缓冲时长,单位秒 |
| ... | ... | @@ -117,70 +118,70 @@ export default { |
| 117 | 118 | }, |
| 118 | 119 | options |
| 119 | 120 | )); |
| 120 | - | |
| 121 | + let jessibuca = jessibucaPlayer[this._uid]; | |
| 121 | 122 | let _this = this; |
| 122 | - this.jessibuca.on("load", function () { | |
| 123 | + jessibuca.on("load", function () { | |
| 123 | 124 | console.log("on load init"); |
| 124 | 125 | }); |
| 125 | 126 | |
| 126 | - this.jessibuca.on("log", function (msg) { | |
| 127 | + jessibuca.on("log", function (msg) { | |
| 127 | 128 | console.log("on log", msg); |
| 128 | 129 | }); |
| 129 | - this.jessibuca.on("record", function (msg) { | |
| 130 | + jessibuca.on("record", function (msg) { | |
| 130 | 131 | console.log("on record:", msg); |
| 131 | 132 | }); |
| 132 | - this.jessibuca.on("pause", function () { | |
| 133 | + jessibuca.on("pause", function () { | |
| 133 | 134 | _this.playing = false; |
| 134 | 135 | }); |
| 135 | - this.jessibuca.on("play", function () { | |
| 136 | + jessibuca.on("play", function () { | |
| 136 | 137 | _this.playing = true; |
| 137 | 138 | }); |
| 138 | - this.jessibuca.on("fullscreen", function (msg) { | |
| 139 | + jessibuca.on("fullscreen", function (msg) { | |
| 139 | 140 | console.log("on fullscreen", msg); |
| 140 | 141 | _this.fullscreen = msg |
| 141 | 142 | }); |
| 142 | 143 | |
| 143 | - this.jessibuca.on("mute", function (msg) { | |
| 144 | + jessibuca.on("mute", function (msg) { | |
| 144 | 145 | console.log("on mute", msg); |
| 145 | 146 | _this.isNotMute = !msg; |
| 146 | 147 | }); |
| 147 | - this.jessibuca.on("audioInfo", function (msg) { | |
| 148 | + jessibuca.on("audioInfo", function (msg) { | |
| 148 | 149 | // console.log("audioInfo", msg); |
| 149 | 150 | }); |
| 150 | 151 | |
| 151 | - this.jessibuca.on("videoInfo", function (msg) { | |
| 152 | + jessibuca.on("videoInfo", function (msg) { | |
| 152 | 153 | // this.videoInfo = msg; |
| 153 | 154 | console.log("videoInfo", msg); |
| 154 | 155 | |
| 155 | 156 | }); |
| 156 | 157 | |
| 157 | - this.jessibuca.on("bps", function (bps) { | |
| 158 | + jessibuca.on("bps", function (bps) { | |
| 158 | 159 | // console.log('bps', bps); |
| 159 | 160 | |
| 160 | 161 | }); |
| 161 | 162 | let _ts = 0; |
| 162 | - this.jessibuca.on("timeUpdate", function (ts) { | |
| 163 | + jessibuca.on("timeUpdate", function (ts) { | |
| 163 | 164 | // console.log('timeUpdate,old,new,timestamp', _ts, ts, ts - _ts); |
| 164 | 165 | _ts = ts; |
| 165 | 166 | }); |
| 166 | 167 | |
| 167 | - this.jessibuca.on("videoInfo", function (info) { | |
| 168 | + jessibuca.on("videoInfo", function (info) { | |
| 168 | 169 | console.log("videoInfo", info); |
| 169 | 170 | }); |
| 170 | 171 | |
| 171 | - this.jessibuca.on("error", function (error) { | |
| 172 | + jessibuca.on("error", function (error) { | |
| 172 | 173 | console.log("error", error); |
| 173 | 174 | }); |
| 174 | 175 | |
| 175 | - this.jessibuca.on("timeout", function () { | |
| 176 | + jessibuca.on("timeout", function () { | |
| 176 | 177 | console.log("timeout"); |
| 177 | 178 | }); |
| 178 | 179 | |
| 179 | - this.jessibuca.on('start', function () { | |
| 180 | + jessibuca.on('start', function () { | |
| 180 | 181 | console.log('start'); |
| 181 | 182 | }) |
| 182 | 183 | |
| 183 | - this.jessibuca.on("performance", function (performance) { | |
| 184 | + jessibuca.on("performance", function (performance) { | |
| 184 | 185 | let show = "卡顿"; |
| 185 | 186 | if (performance === 2) { |
| 186 | 187 | show = "非常流畅"; |
| ... | ... | @@ -189,25 +190,25 @@ export default { |
| 189 | 190 | } |
| 190 | 191 | _this.performance = show; |
| 191 | 192 | }); |
| 192 | - this.jessibuca.on('buffer', function (buffer) { | |
| 193 | + jessibuca.on('buffer', function (buffer) { | |
| 193 | 194 | // console.log('buffer', buffer); |
| 194 | 195 | }) |
| 195 | 196 | |
| 196 | - this.jessibuca.on('stats', function (stats) { | |
| 197 | + jessibuca.on('stats', function (stats) { | |
| 197 | 198 | // console.log('stats', stats); |
| 198 | 199 | }) |
| 199 | 200 | |
| 200 | - this.jessibuca.on('kBps', function (kBps) { | |
| 201 | + jessibuca.on('kBps', function (kBps) { | |
| 201 | 202 | _this.kBps = Math.round(kBps); |
| 202 | 203 | }); |
| 203 | 204 | |
| 204 | 205 | // 显示时间戳 PTS |
| 205 | - this.jessibuca.on('videoFrame', function () { | |
| 206 | + jessibuca.on('videoFrame', function () { | |
| 206 | 207 | |
| 207 | 208 | }) |
| 208 | 209 | |
| 209 | 210 | // |
| 210 | - this.jessibuca.on('metadata', function () { | |
| 211 | + jessibuca.on('metadata', function () { | |
| 211 | 212 | |
| 212 | 213 | }); |
| 213 | 214 | }, |
| ... | ... | @@ -216,40 +217,40 @@ export default { |
| 216 | 217 | }, |
| 217 | 218 | play: function (url) { |
| 218 | 219 | console.log(url) |
| 219 | - if (this.jessibuca) { | |
| 220 | + if (jessibucaPlayer[this._uid]) { | |
| 220 | 221 | this.destroy(); |
| 221 | 222 | } |
| 222 | 223 | this.create(); |
| 223 | - this.jessibuca.on("play", () => { | |
| 224 | + jessibucaPlayer[this._uid].on("play", () => { | |
| 224 | 225 | this.playing = true; |
| 225 | 226 | this.loaded = true; |
| 226 | - this.quieting = this.jessibuca.quieting; | |
| 227 | + this.quieting = jessibuca.quieting; | |
| 227 | 228 | }); |
| 228 | - if (this.jessibuca.hasLoaded()) { | |
| 229 | - this.jessibuca.play(url); | |
| 229 | + if (jessibucaPlayer[this._uid].hasLoaded()) { | |
| 230 | + jessibucaPlayer[this._uid].play(url); | |
| 230 | 231 | } else { |
| 231 | - this.jessibuca.on("load", () => { | |
| 232 | + jessibucaPlayer[this._uid].on("load", () => { | |
| 232 | 233 | console.log("load 播放") |
| 233 | - this.jessibuca.play(url); | |
| 234 | + jessibucaPlayer[this._uid].play(url); | |
| 234 | 235 | }); |
| 235 | 236 | } |
| 236 | 237 | }, |
| 237 | 238 | pause: function () { |
| 238 | - if (this.jessibuca) { | |
| 239 | - this.jessibuca.pause(); | |
| 239 | + if (jessibucaPlayer[this._uid]) { | |
| 240 | + jessibucaPlayer[this._uid].pause(); | |
| 240 | 241 | } |
| 241 | 242 | this.playing = false; |
| 242 | 243 | this.err = ""; |
| 243 | 244 | this.performance = ""; |
| 244 | 245 | }, |
| 245 | 246 | destroy: function () { |
| 246 | - if (this.jessibuca) { | |
| 247 | - this.jessibuca.destroy(); | |
| 247 | + if (jessibucaPlayer[this._uid]) { | |
| 248 | + jessibucaPlayer[this._uid].destroy(); | |
| 248 | 249 | } |
| 249 | 250 | if (document.getElementById("buttonsBox") == null) { |
| 250 | 251 | this.$refs.container.appendChild(this.btnDom) |
| 251 | 252 | } |
| 252 | - this.jessibuca = null; | |
| 253 | + jessibucaPlayer[this._uid] = null; | |
| 253 | 254 | this.playing = false; |
| 254 | 255 | this.err = ""; |
| 255 | 256 | this.performance = ""; |
| ... | ... | @@ -262,7 +263,7 @@ export default { |
| 262 | 263 | }, |
| 263 | 264 | fullscreenSwich: function () { |
| 264 | 265 | let isFull = this.isFullscreen() |
| 265 | - this.jessibuca.setFullscreen(!isFull) | |
| 266 | + jessibucaPlayer[this._uid].setFullscreen(!isFull) | |
| 266 | 267 | this.fullscreen = !isFull; |
| 267 | 268 | }, |
| 268 | 269 | isFullscreen: function () { |
| ... | ... | @@ -273,8 +274,8 @@ export default { |
| 273 | 274 | } |
| 274 | 275 | }, |
| 275 | 276 | destroyed() { |
| 276 | - if (this.jessibuca) { | |
| 277 | - this.jessibuca.destroy(); | |
| 277 | + if (jessibucaPlayer[this._uid]) { | |
| 278 | + jessibucaPlayer[this._uid].destroy(); | |
| 278 | 279 | } |
| 279 | 280 | this.playing = false; |
| 280 | 281 | this.loaded = false; | ... | ... |
web_src/src/components/control.vue
| ... | ... | @@ -235,10 +235,8 @@ |
| 235 | 235 | <el-table-column prop="local_ip" label="本地"></el-table-column> |
| 236 | 236 | <el-table-column prop="typeid" label="类型"></el-table-column> |
| 237 | 237 | <el-table-column align="right"> |
| 238 | - <template slot="header" slot-scope="scope"> | |
| 239 | - <el-button icon="el-icon-refresh-right" circle @click="getAllSession()"></el-button> | |
| 240 | - </template> | |
| 241 | - <template slot-scope="scope"> | |
| 238 | + <template v-slot:default="scope"> | |
| 239 | + <el-button size="mini" icon="el-icon-refresh-right" circle @click="getAllSession()"></el-button> | |
| 242 | 240 | <el-button @click.native.prevent="deleteRow(scope.$index, allSessionData)" type="text" size="small">移除 |
| 243 | 241 | </el-button> |
| 244 | 242 | </template> | ... | ... |
web_src/src/components/dialog/deviceEdit.vue
| ... | ... | @@ -39,6 +39,12 @@ |
| 39 | 39 | <el-form-item label="语音发送通道" prop="name"> |
| 40 | 40 | <el-input v-model="form.audioChannelForReceive" clearable></el-input> |
| 41 | 41 | </el-form-item> |
| 42 | + <el-form-item label="地理坐标系" prop="geoCoordSys" > | |
| 43 | + <el-select v-model="form.geoCoordSys" style="float: left; width: 100%" > | |
| 44 | + <el-option key="WGS84" label="WGS84" value="WGS84"></el-option> | |
| 45 | + <el-option key="GCJ02" label="GCJ02" value="GCJ02"></el-option> | |
| 46 | + </el-select> | |
| 47 | + </el-form-item> | |
| 42 | 48 | <el-form-item label="目录订阅" title="0为取消订阅" prop="subscribeCycleForCatalog" > |
| 43 | 49 | <el-input v-model="form.subscribeCycleForCatalog" clearable ></el-input> |
| 44 | 50 | </el-form-item> | ... | ... |
web_src/src/components/dialog/devicePlayer.vue
| ... | ... | @@ -3,9 +3,23 @@ |
| 3 | 3 | |
| 4 | 4 | <el-dialog title="视频播放" top="0" :close-on-click-modal="false" :visible.sync="showVideoDialog" @close="close()"> |
| 5 | 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" :height="false" :hasAudio="hasAudio" fluent autoplay live ></player> | |
| 6 | + <div style="width: 100%; height: 100%"> | |
| 7 | + <el-tabs type="card" :stretch="true" v-model="activePlayer" @tab-click="changePlayer" v-if="Object.keys(this.player).length > 1"> | |
| 8 | + <el-tab-pane label="Jessibuca" name="jessibuca"> | |
| 9 | + <jessibucaPlayer v-if="activePlayer === 'jessibuca'" ref="jessibuca" :visible.sync="showVideoDialog" :videoUrl="videoUrl" :error="videoError" :message="videoError" height="100px" :hasAudio="hasAudio" fluent autoplay live ></jessibucaPlayer> | |
| 10 | + </el-tab-pane> | |
| 11 | + <el-tab-pane label="WebRTC" name="webRTC"> | |
| 12 | + <rtc-player v-if="activePlayer === 'webRTC'" ref="webRTC" :visible.sync="showVideoDialog" :videoUrl="videoUrl" :error="videoError" :message="videoError" height="100px" :hasAudio="hasAudio" fluent autoplay live ></rtc-player> | |
| 13 | + </el-tab-pane> | |
| 14 | + <el-tab-pane label="h265web">h265web敬请期待</el-tab-pane> | |
| 15 | + <el-tab-pane label="wsPlayer">wsPlayer 敬请期待</el-tab-pane> | |
| 16 | + </el-tabs> | |
| 17 | + <jessibucaPlayer v-if="Object.keys(this.player).length == 1 && this.player.jessibuca" ref="jessibuca" :visible.sync="showVideoDialog" :videoUrl="videoUrl" :error="videoError" :message="videoError" height="100px" :hasAudio="hasAudio" fluent autoplay live ></jessibucaPlayer> | |
| 18 | + <rtc-player v-if="Object.keys(this.player).length == 1 && this.player.webRTC" ref="jessibuca" :visible.sync="showVideoDialog" :videoUrl="videoUrl" :error="videoError" :message="videoError" height="100px" :hasAudio="hasAudio" fluent autoplay live ></rtc-player> | |
| 19 | + | |
| 20 | + </div> | |
| 7 | 21 | <div id="shared" style="text-align: right; margin-top: 1rem;"> |
| 8 | - <el-tabs v-model="tabActiveName" @tab-click="tabHandleClick"> | |
| 22 | + <el-tabs v-model="tabActiveName" @tab-click="tabHandleClick" > | |
| 9 | 23 | <el-tab-pane label="实时视频" name="media"> |
| 10 | 24 | <div style="margin-bottom: 0.5rem;"> |
| 11 | 25 | <!-- <el-button type="primary" size="small" @click="playRecord(true, '')">播放</el-button>--> |
| ... | ... | @@ -31,10 +45,100 @@ |
| 31 | 45 | <div style="display: flex; margin-bottom: 0.5rem; height: 2.5rem;"> |
| 32 | 46 | <span style="width: 5rem; line-height: 2.5rem; text-align: right;">资源地址:</span> |
| 33 | 47 | <el-input v-model="getPlayerShared.sharedRtmp" :disabled="true" > |
| 34 | - <template slot="append"> | |
| 35 | - <i class="cpoy-btn el-icon-document-copy" title="点击拷贝" v-clipboard="getPlayerShared.sharedRtmp" @success="$message({type:'success', message:'成功拷贝到粘贴板'})"></i> | |
| 36 | - </template> | |
| 48 | + <el-button slot="append" icon="el-icon-document-copy" title="点击拷贝" v-clipboard="getPlayerShared.sharedRtmp" @success="$message({type:'success', message:'成功拷贝到粘贴板'})"></el-button> | |
| 49 | + <el-dropdown slot="prepend" v-if="streamInfo" trigger="click" @command="copyUrl"> | |
| 50 | + <el-button > | |
| 51 | + 更多地址<i class="el-icon-arrow-down el-icon--right"></i> | |
| 52 | + </el-button> | |
| 53 | + <el-dropdown-menu slot="dropdown" > | |
| 54 | + <el-dropdown-item :command="streamInfo.flv"> | |
| 55 | + <el-tag >FLV:</el-tag> | |
| 56 | + <span>{{ streamInfo.flv }}</span> | |
| 57 | + </el-dropdown-item> | |
| 58 | + <el-dropdown-item :command="streamInfo.https_flv"> | |
| 59 | + <el-tag >FLV(https):</el-tag> | |
| 60 | + <span>{{ streamInfo.https_flv }}</span> | |
| 61 | + </el-dropdown-item> | |
| 62 | + <el-dropdown-item :command="streamInfo.ws_flv"> | |
| 63 | + <el-tag >FLV(ws):</el-tag> | |
| 64 | + <span >{{ streamInfo.ws_flv }}</span> | |
| 65 | + </el-dropdown-item> | |
| 66 | + <el-dropdown-item :command="streamInfo.wss_flv"> | |
| 67 | + <el-tag >FLV(wss):</el-tag> | |
| 68 | + <span>{{ streamInfo.wss_flv }}</span> | |
| 69 | + </el-dropdown-item> | |
| 70 | + <el-dropdown-item :command="streamInfo.fmp4"> | |
| 71 | + <el-tag >FMP4:</el-tag> | |
| 72 | + <span>{{ streamInfo.fmp4 }}</span> | |
| 73 | + </el-dropdown-item> | |
| 74 | + <el-dropdown-item :command="streamInfo.https_fmp4"> | |
| 75 | + <el-tag >FMP4(https):</el-tag> | |
| 76 | + <span>{{ streamInfo.https_fmp4 }}</span> | |
| 77 | + </el-dropdown-item> | |
| 78 | + <el-dropdown-item :command="streamInfo.ws_fmp4"> | |
| 79 | + <el-tag >FMP4(ws):</el-tag> | |
| 80 | + <span>{{ streamInfo.ws_fmp4 }}</span> | |
| 81 | + </el-dropdown-item> | |
| 82 | + <el-dropdown-item :command="streamInfo.wss_fmp4"> | |
| 83 | + <el-tag >FMP4(wss):</el-tag> | |
| 84 | + <span>{{ streamInfo.wss_fmp4 }}</span> | |
| 85 | + </el-dropdown-item> | |
| 86 | + <el-dropdown-item :command="streamInfo.hls"> | |
| 87 | + <el-tag>HLS:</el-tag> | |
| 88 | + <span>{{ streamInfo.hls }}</span> | |
| 89 | + </el-dropdown-item> | |
| 90 | + <el-dropdown-item :command="streamInfo.https_hls"> | |
| 91 | + <el-tag >HLS(https):</el-tag> | |
| 92 | + <span>{{ streamInfo.https_hls }}</span> | |
| 93 | + </el-dropdown-item> | |
| 94 | + <el-dropdown-item :command="streamInfo.ws_hls"> | |
| 95 | + <el-tag >HLS(ws):</el-tag> | |
| 96 | + <span>{{ streamInfo.ws_hls }}</span> | |
| 97 | + </el-dropdown-item> | |
| 98 | + <el-dropdown-item :command="streamInfo.wss_hls"> | |
| 99 | + <el-tag >HLS(wss):</el-tag> | |
| 100 | + <span>{{ streamInfo.wss_hls }}</span> | |
| 101 | + </el-dropdown-item> | |
| 102 | + <el-dropdown-item :command="streamInfo.ts"> | |
| 103 | + <el-tag>TS:</el-tag> | |
| 104 | + <span>{{ streamInfo.ts }}</span> | |
| 105 | + </el-dropdown-item> | |
| 106 | + <el-dropdown-item :command="streamInfo.https_ts"> | |
| 107 | + <el-tag>TS(https):</el-tag> | |
| 108 | + <span>{{ streamInfo.https_ts }}</span> | |
| 109 | + </el-dropdown-item> | |
| 110 | + <el-dropdown-item :command="streamInfo.ws_ts"> | |
| 111 | + <el-tag>TS(ws):</el-tag> | |
| 112 | + <span>{{ streamInfo.ws_ts }}</span> | |
| 113 | + </el-dropdown-item> | |
| 114 | + <el-dropdown-item :command="streamInfo.wss_ts"> | |
| 115 | + <el-tag>TS(wss):</el-tag> | |
| 116 | + <span>{{ streamInfo.wss_ts }}</span> | |
| 117 | + </el-dropdown-item> | |
| 118 | + <el-dropdown-item :command="streamInfo.rtc"> | |
| 119 | + <el-tag >RTC:</el-tag> | |
| 120 | + <span>{{ streamInfo.rtc }}</span> | |
| 121 | + </el-dropdown-item> | |
| 122 | + <el-dropdown-item :command="streamInfo.rtmp"> | |
| 123 | + <el-tag >RTMP:</el-tag> | |
| 124 | + <span>{{ streamInfo.rtmp }}</span> | |
| 125 | + </el-dropdown-item> | |
| 126 | + <el-dropdown-item :command="streamInfo.rtmps"> | |
| 127 | + <el-tag >RTMPS:</el-tag> | |
| 128 | + <span>{{ streamInfo.rtmps }}</span> | |
| 129 | + </el-dropdown-item> | |
| 130 | + <el-dropdown-item :command="streamInfo.rtsp"> | |
| 131 | + <el-tag >RTSP:</el-tag> | |
| 132 | + <span>{{ streamInfo.rtsp }}</span> | |
| 133 | + </el-dropdown-item> | |
| 134 | + <el-dropdown-item :command="streamInfo.rtsps"> | |
| 135 | + <el-tag >RTSPS:</el-tag> | |
| 136 | + <span>{{ streamInfo.rtsps }}</span> | |
| 137 | + </el-dropdown-item> | |
| 138 | + </el-dropdown-menu> | |
| 139 | + </el-dropdown> | |
| 37 | 140 | </el-input> |
| 141 | + | |
| 38 | 142 | </div> |
| 39 | 143 | </el-tab-pane> |
| 40 | 144 | <!--{"code":0,"data":{"paths":["22-29-30.mp4"],"rootPath":"/home/kkkkk/Documents/ZLMediaKit/release/linux/Debug/www/record/hls/kkkkk/2020-05-11/"}}--> |
| ... | ... | @@ -115,27 +219,27 @@ |
| 115 | 219 | |
| 116 | 220 | <div class="control-panel"> |
| 117 | 221 | <el-button-group> |
| 118 | - <el-tag style="position :absolute; left: 0rem; top: 0rem; width: 5rem; text-align: center" size="medium" type="info">预置位编号</el-tag> | |
| 222 | + <el-tag style="position :absolute; left: 0rem; top: 0rem; width: 5rem; text-align: center" size="medium">预置位编号</el-tag> | |
| 119 | 223 | <el-input-number style="position: absolute; left: 5rem; top: 0rem; width: 6rem" size="mini" v-model="presetPos" controls-position="right" :precision="0" :step="1" :min="1" :max="255"></el-input-number> |
| 120 | 224 | <el-button style="position: absolute; left: 11rem; top: 0rem; width: 5rem" size="mini" icon="el-icon-add-location" @click="presetPosition(129, presetPos)">设置</el-button> |
| 121 | 225 | <el-button style="position: absolute; left: 27rem; top: 0rem; width: 5rem" size="mini" type="primary" icon="el-icon-place" @click="presetPosition(130, presetPos)">调用</el-button> |
| 122 | 226 | <el-button style="position: absolute; left: 16rem; top: 0rem; width: 5rem" size="mini" icon="el-icon-delete-location" @click="presetPosition(131, presetPos)">删除</el-button> |
| 123 | - <el-tag style="position :absolute; left: 0rem; top: 2.5rem; width: 5rem; text-align: center" size="medium" type="info">巡航速度</el-tag> | |
| 227 | + <el-tag style="position :absolute; left: 0rem; top: 2.5rem; width: 5rem; text-align: center" size="medium">巡航速度</el-tag> | |
| 124 | 228 | <el-input-number style="position: absolute; left: 5rem; top: 2.5rem; width: 6rem" size="mini" v-model="cruisingSpeed" controls-position="right" :precision="0" :min="1" :max="4095"></el-input-number> |
| 125 | 229 | <el-button style="position: absolute; left: 11rem; top: 2.5rem; width: 5rem" size="mini" icon="el-icon-loading" @click="setSpeedOrTime(134, cruisingGroup, cruisingSpeed)">设置</el-button> |
| 126 | - <el-tag style="position :absolute; left: 16rem; top: 2.5rem; width: 5rem; text-align: center" size="medium" type="info">停留时间</el-tag> | |
| 230 | + <el-tag style="position :absolute; left: 16rem; top: 2.5rem; width: 5rem; text-align: center" size="medium">停留时间</el-tag> | |
| 127 | 231 | <el-input-number style="position: absolute; left: 21rem; top: 2.5rem; width: 6rem" size="mini" v-model="cruisingTime" controls-position="right" :precision="0" :min="1" :max="4095"></el-input-number> |
| 128 | 232 | <el-button style="position: absolute; left: 27rem; top: 2.5rem; width: 5rem" size="mini" icon="el-icon-timer" @click="setSpeedOrTime(135, cruisingGroup, cruisingTime)">设置</el-button> |
| 129 | - <el-tag style="position :absolute; left: 0rem; top: 4.5rem; width: 5rem; text-align: center" size="medium" type="info">巡航组编号</el-tag> | |
| 233 | + <el-tag style="position :absolute; left: 0rem; top: 4.5rem; width: 5rem; text-align: center" size="medium">巡航组编号</el-tag> | |
| 130 | 234 | <el-input-number style="position: absolute; left: 5rem; top: 4.5rem; width: 6rem" size="mini" v-model="cruisingGroup" controls-position="right" :precision="0" :min="0" :max="255"></el-input-number> |
| 131 | 235 | <el-button style="position: absolute; left: 11rem; top: 4.5rem; width: 5rem" size="mini" icon="el-icon-add-location" @click="setCommand(132, cruisingGroup, presetPos)">添加点</el-button> |
| 132 | 236 | <el-button style="position: absolute; left: 16rem; top: 4.5rem; width: 5rem" size="mini" icon="el-icon-delete-location" @click="setCommand(133, cruisingGroup, presetPos)">删除点</el-button> |
| 133 | 237 | <el-button style="position: absolute; left: 21rem; top: 4.5rem; width: 5rem" size="mini" icon="el-icon-delete" @click="setCommand(133, cruisingGroup, 0)">删除组</el-button> |
| 134 | 238 | <el-button style="position: absolute; left: 27rem; top: 5rem; width: 5rem" size="mini" type="primary" icon="el-icon-video-camera-solid" @click="setCommand(136, cruisingGroup, 0)">巡航</el-button> |
| 135 | - <el-tag style="position :absolute; left: 0rem; top: 7rem; width: 5rem; text-align: center" size="medium" type="info">扫描速度</el-tag> | |
| 239 | + <el-tag style="position :absolute; left: 0rem; top: 7rem; width: 5rem; text-align: center" size="medium">扫描速度</el-tag> | |
| 136 | 240 | <el-input-number style="position: absolute; left: 5rem; top: 7rem; width: 6rem" size="mini" v-model="scanSpeed" controls-position="right" :precision="0" :min="1" :max="4095"></el-input-number> |
| 137 | 241 | <el-button style="position: absolute; left: 11rem; top: 7rem; width: 5rem" size="mini" icon="el-icon-loading" @click="setSpeedOrTime(138, scanGroup, scanSpeed)">设置</el-button> |
| 138 | - <el-tag style="position :absolute; left: 0rem; top: 9rem; width: 5rem; text-align: center" size="medium" type="info">扫描组编号</el-tag> | |
| 242 | + <el-tag style="position :absolute; left: 0rem; top: 9rem; width: 5rem; text-align: center" size="medium">扫描组编号</el-tag> | |
| 139 | 243 | <el-input-number style="position: absolute; left: 5rem; top: 9rem; width: 6rem" size="mini" v-model="scanGroup" controls-position="right" :precision="0" :step="1" :min="0" :max="255"></el-input-number> |
| 140 | 244 | <el-button style="position: absolute; left: 11rem; top: 9rem; width: 5rem" size="mini" icon="el-icon-d-arrow-left" @click="setCommand(137, scanGroup, 1)">左边界</el-button> |
| 141 | 245 | <el-button style="position: absolute; left: 16rem; top: 9rem; width: 5rem" size="mini" icon="el-icon-d-arrow-right" @click="setCommand(137, scanGroup, 2)">右边界</el-button> |
| ... | ... | @@ -172,26 +276,28 @@ |
| 172 | 276 | </div> |
| 173 | 277 | |
| 174 | 278 | </el-tab-pane> |
| 279 | + | |
| 175 | 280 | </el-tabs> |
| 176 | 281 | </div> |
| 177 | 282 | </el-dialog> |
| 283 | + <recordDownload ref="recordDownload"></recordDownload> | |
| 178 | 284 | </div> |
| 179 | 285 | </template> |
| 180 | 286 | |
| 181 | 287 | <script> |
| 182 | -import player from '../dialog/rtcPlayer.vue' | |
| 288 | +import rtcPlayer from '../dialog/rtcPlayer.vue' | |
| 183 | 289 | // import LivePlayer from '@liveqing/liveplayer' |
| 184 | 290 | // import player from '../dialog/easyPlayer.vue' |
| 185 | -// import player from '../dialog/jessibuca.vue' | |
| 291 | +import jessibucaPlayer from '../common/jessibuca.vue' | |
| 292 | +import recordDownload from '../dialog/recordDownload.vue' | |
| 186 | 293 | export default { |
| 187 | 294 | name: 'devicePlayer', |
| 188 | 295 | props: {}, |
| 189 | 296 | components: { |
| 190 | - player, | |
| 297 | + jessibucaPlayer, rtcPlayer, recordDownload, | |
| 191 | 298 | }, |
| 192 | 299 | computed: { |
| 193 | 300 | getPlayerShared: function () { |
| 194 | - | |
| 195 | 301 | return { |
| 196 | 302 | sharedUrl: window.location.origin + '/#/play/wasm/' + encodeURIComponent(this.videoUrl), |
| 197 | 303 | sharedIframe: '<iframe src="' + window.location.origin + '/#/play/wasm/' + encodeURIComponent(this.videoUrl) + '"></iframe>', |
| ... | ... | @@ -199,11 +305,22 @@ export default { |
| 199 | 305 | }; |
| 200 | 306 | } |
| 201 | 307 | }, |
| 202 | - created() {}, | |
| 308 | + created() { | |
| 309 | + console.log(this.player) | |
| 310 | + if (Object.keys(this.player).length === 1) { | |
| 311 | + this.activePlayer = Object.keys(this.player)[0] | |
| 312 | + } | |
| 313 | + }, | |
| 203 | 314 | data() { |
| 204 | 315 | return { |
| 205 | 316 | video: 'http://lndxyj.iqilu.com/public/upload/2019/10/14/8c001ea0c09cdc59a57829dabc8010fa.mp4', |
| 206 | 317 | videoUrl: '', |
| 318 | + activePlayer: "jessibuca", | |
| 319 | + // 如何你只是用一种播放器,直接注释掉不用的部分即可 | |
| 320 | + player: { | |
| 321 | + jessibuca : ["ws_flv", "wss_flv"], | |
| 322 | + webRTC: ["rtc", "rtc"], | |
| 323 | + }, | |
| 207 | 324 | videoHistory: { |
| 208 | 325 | date: '', |
| 209 | 326 | searchHistoryResult: [] //媒体流历史记录搜索结果 |
| ... | ... | @@ -241,6 +358,7 @@ export default { |
| 241 | 358 | seekTime: 0, |
| 242 | 359 | recordStartTime: 0, |
| 243 | 360 | showTimeText: "00:00:00", |
| 361 | + streamInfo: null, | |
| 244 | 362 | }; |
| 245 | 363 | }, |
| 246 | 364 | methods: { |
| ... | ... | @@ -250,7 +368,7 @@ export default { |
| 250 | 368 | that.tracks = []; |
| 251 | 369 | that.tracksLoading = true; |
| 252 | 370 | that.tracksNotLoaded = false; |
| 253 | - if (tab.name == "codec") { | |
| 371 | + if (tab.name === "codec") { | |
| 254 | 372 | this.$axios({ |
| 255 | 373 | method: 'get', |
| 256 | 374 | url: '/zlm/' +this.mediaServerId+ '/index/api/getMediaInfo?vhost=__defaultVhost__&schema=rtmp&app='+ this.app +'&stream='+ this.streamId |
| ... | ... | @@ -269,6 +387,12 @@ export default { |
| 269 | 387 | }).catch(function (e) {}); |
| 270 | 388 | } |
| 271 | 389 | }, |
| 390 | + changePlayer: function (tab) { | |
| 391 | + console.log(this.player[tab.name][0]) | |
| 392 | + this.activePlayer = tab.name; | |
| 393 | + this.videoUrl = this.streamInfo[this.player[tab.name][0]] | |
| 394 | + console.log(this.videoUrl) | |
| 395 | + }, | |
| 272 | 396 | openDialog: function (tab, deviceId, channelId, param) { |
| 273 | 397 | this.tabActiveName = tab; |
| 274 | 398 | this.channelId = channelId; |
| ... | ... | @@ -277,8 +401,8 @@ export default { |
| 277 | 401 | this.mediaServerId = ""; |
| 278 | 402 | this.app = ""; |
| 279 | 403 | this.videoUrl = "" |
| 280 | - if (!!this.$refs.videoPlayer) { | |
| 281 | - this.$refs.videoPlayer.pause(); | |
| 404 | + if (!!this.$refs[this.activePlayer]) { | |
| 405 | + this.$refs[this.activePlayer].pause(); | |
| 282 | 406 | } |
| 283 | 407 | switch (tab) { |
| 284 | 408 | case "media": |
| ... | ... | @@ -303,44 +427,32 @@ export default { |
| 303 | 427 | console.log(val) |
| 304 | 428 | }, |
| 305 | 429 | play: function (streamInfo, hasAudio) { |
| 430 | + this.streamInfo = streamInfo; | |
| 306 | 431 | this.hasAudio = hasAudio; |
| 307 | 432 | this.isLoging = false; |
| 308 | - this.videoUrl = streamInfo.rtc; | |
| 309 | - // this.videoUrl = this.getUrlByStreamInfo(streamInfo); | |
| 310 | - this.streamId = streamInfo.streamId; | |
| 433 | + // this.videoUrl = streamInfo.rtc; | |
| 434 | + this.videoUrl = this.getUrlByStreamInfo(); | |
| 435 | + this.streamId = streamInfo.stream; | |
| 311 | 436 | this.app = streamInfo.app; |
| 312 | 437 | this.mediaServerId = streamInfo.mediaServerId; |
| 313 | 438 | this.playFromStreamInfo(false, streamInfo) |
| 314 | 439 | }, |
| 315 | - getUrlByStreamInfo(streamInfo){ | |
| 316 | - let baseZlmApi = process.env.NODE_ENV === 'development'?`${location.host}/debug/zlm`:`${location.host}/zlm` | |
| 317 | - // return `${baseZlmApi}/${streamInfo.app}/${streamInfo.streamId}.flv`; | |
| 318 | - // return `http://${baseZlmApi}/${streamInfo.app}/${streamInfo.streamId}.flv`; | |
| 440 | + getUrlByStreamInfo(){ | |
| 319 | 441 | if (location.protocol === "https:") { |
| 320 | - if (streamInfo.wss_flv === null) { | |
| 321 | - console.error("媒体服务器未配置ssl端口, 使用http端口") | |
| 322 | - // this.$message({ | |
| 323 | - // showClose: true, | |
| 324 | - // message: '媒体服务器未配置ssl端口, ', | |
| 325 | - // type: 'error' | |
| 326 | - // }); | |
| 327 | - return streamInfo.ws_flv | |
| 328 | - }else { | |
| 329 | - return streamInfo.wss_flv; | |
| 330 | - } | |
| 331 | - | |
| 442 | + this.videoUrl = this.streamInfo[this.player[this.activePlayer][1]] | |
| 332 | 443 | }else { |
| 333 | - return streamInfo.ws_flv; | |
| 444 | + this.videoUrl = this.streamInfo[this.player[this.activePlayer][0]] | |
| 334 | 445 | } |
| 446 | + return this.videoUrl; | |
| 335 | 447 | |
| 336 | 448 | }, |
| 337 | 449 | coverPlay: function () { |
| 338 | 450 | var that = this; |
| 339 | 451 | this.coverPlaying = true; |
| 340 | - this.$refs.videoPlayer.pause() | |
| 452 | + this.$refs[this.activePlayer].pause() | |
| 341 | 453 | that.$axios({ |
| 342 | 454 | method: 'post', |
| 343 | - url: '/api/play/convert/' + that.streamId | |
| 455 | + url: '/api/gb_record/convert/' + that.streamId | |
| 344 | 456 | }).then(function (res) { |
| 345 | 457 | if (res.data.code == 0) { |
| 346 | 458 | that.convertKey = res.data.key; |
| ... | ... | @@ -369,7 +481,7 @@ export default { |
| 369 | 481 | }, |
| 370 | 482 | convertStopClick: function() { |
| 371 | 483 | this.convertStop(()=>{ |
| 372 | - this.$refs.videoPlayer.play(this.videoUrl) | |
| 484 | + this.$refs[this.activePlayer].play(this.videoUrl) | |
| 373 | 485 | }); |
| 374 | 486 | }, |
| 375 | 487 | convertStop: function(callback) { |
| ... | ... | @@ -394,12 +506,12 @@ export default { |
| 394 | 506 | playFromStreamInfo: function (realHasAudio, streamInfo) { |
| 395 | 507 | this.showVideoDialog = true; |
| 396 | 508 | this.hasaudio = realHasAudio && this.hasaudio; |
| 397 | - this.$refs.videoPlayer.play(this.getUrlByStreamInfo(streamInfo)) | |
| 509 | + this.$refs[this.activePlayer].play(this.getUrlByStreamInfo(streamInfo)) | |
| 398 | 510 | }, |
| 399 | 511 | close: function () { |
| 400 | 512 | console.log('关闭视频'); |
| 401 | - if (!!this.$refs.videoPlayer){ | |
| 402 | - this.$refs.videoPlayer.pause(); | |
| 513 | + if (!!this.$refs[this.activePlayer]){ | |
| 514 | + this.$refs[this.activePlayer].pause(); | |
| 403 | 515 | } |
| 404 | 516 | this.videoUrl = ''; |
| 405 | 517 | this.coverPlaying = false; |
| ... | ... | @@ -450,9 +562,19 @@ export default { |
| 450 | 562 | method: 'get', |
| 451 | 563 | url: '/api/gb_record/query/' + this.deviceId + '/' + this.channelId + '?startTime=' + startTime + '&endTime=' + endTime |
| 452 | 564 | }).then(function (res) { |
| 453 | - // 处理时间信息 | |
| 454 | - that.videoHistory.searchHistoryResult = res.data.recordList; | |
| 455 | - that.recordsLoading = false; | |
| 565 | + console.log(res) | |
| 566 | + if(res.data.code === 0) { | |
| 567 | + // 处理时间信息 | |
| 568 | + that.videoHistory.searchHistoryResult = res.data.data.recordList; | |
| 569 | + that.recordsLoading = false; | |
| 570 | + }else { | |
| 571 | + this.$message({ | |
| 572 | + showClose: true, | |
| 573 | + message: res.data.msg, | |
| 574 | + type: "error", | |
| 575 | + }); | |
| 576 | + } | |
| 577 | + | |
| 456 | 578 | }).catch(function (e) { |
| 457 | 579 | console.log(e.message); |
| 458 | 580 | // that.videoHistory.searchHistoryResult = falsificationData.recordData; |
| ... | ... | @@ -474,8 +596,8 @@ export default { |
| 474 | 596 | console.log(this.seekTime) |
| 475 | 597 | if (that.streamId != "") { |
| 476 | 598 | that.stopPlayRecord(function () { |
| 477 | - that.streamId = "", | |
| 478 | - that.playRecord(row); | |
| 599 | + that.streamId = ""; | |
| 600 | + that.playRecord(row); | |
| 479 | 601 | }) |
| 480 | 602 | } else { |
| 481 | 603 | this.$axios({ |
| ... | ... | @@ -483,21 +605,22 @@ export default { |
| 483 | 605 | url: '/api/playback/start/' + this.deviceId + '/' + this.channelId + '?startTime=' + row.startTime + '&endTime=' + |
| 484 | 606 | row.endTime |
| 485 | 607 | }).then(function (res) { |
| 486 | - var streamInfo = res.data; | |
| 487 | - that.app = streamInfo.app; | |
| 488 | - that.streamId = streamInfo.streamId; | |
| 489 | - that.mediaServerId = streamInfo.mediaServerId; | |
| 490 | - that.videoUrl = that.getUrlByStreamInfo(streamInfo); | |
| 608 | + that.streamInfo = res.data; | |
| 609 | + that.app = that.streamInfo.app; | |
| 610 | + that.streamId = that.streamInfo.stream; | |
| 611 | + that.mediaServerId = that.streamInfo.mediaServerId; | |
| 612 | + that.ssrc = that.streamInfo.ssrc; | |
| 613 | + that.videoUrl = that.getUrlByStreamInfo(); | |
| 491 | 614 | that.recordPlay = true; |
| 492 | 615 | }); |
| 493 | 616 | } |
| 494 | 617 | }, |
| 495 | 618 | stopPlayRecord: function (callback) { |
| 496 | - this.$refs.videoPlayer.pause(); | |
| 619 | + this.$refs[this.activePlayer].pause(); | |
| 497 | 620 | this.videoUrl = ''; |
| 498 | 621 | this.$axios({ |
| 499 | 622 | method: 'get', |
| 500 | - url: '/api/playback/stop/' + this.deviceId + "/" + this.channelId | |
| 623 | + url: '/api/playback/stop/' + this.deviceId + "/" + this.channelId + "/" + this.streamId | |
| 501 | 624 | }).then(function (res) { |
| 502 | 625 | if (callback) callback() |
| 503 | 626 | }); |
| ... | ... | @@ -505,33 +628,47 @@ export default { |
| 505 | 628 | downloadRecord: function (row) { |
| 506 | 629 | let that = this; |
| 507 | 630 | if (that.streamId != "") { |
| 508 | - that.stopDownloadRecord(function () { | |
| 509 | - that.streamId = "", | |
| 510 | - that.downloadRecord(row); | |
| 631 | + that.stopDownloadRecord(function (res) { | |
| 632 | + if (res.code == 0) { | |
| 633 | + that.streamId = ""; | |
| 634 | + that.downloadRecord(row); | |
| 635 | + }else { | |
| 636 | + this.$message({ | |
| 637 | + showClose: true, | |
| 638 | + message: res.data.msg, | |
| 639 | + type: "error", | |
| 640 | + }); | |
| 641 | + } | |
| 642 | + | |
| 511 | 643 | }) |
| 512 | 644 | } else { |
| 513 | 645 | this.$axios({ |
| 514 | 646 | method: 'get', |
| 515 | - url: '/api/download/start/' + this.deviceId + '/' + this.channelId + '?startTime=' + row.startTime + '&endTime=' + | |
| 647 | + url: '/api/gb_record/download/start/' + this.deviceId + '/' + this.channelId + '?startTime=' + row.startTime + '&endTime=' + | |
| 516 | 648 | row.endTime + '&downloadSpeed=4' |
| 517 | 649 | }).then(function (res) { |
| 518 | - var streamInfo = res.data; | |
| 519 | - that.app = streamInfo.app; | |
| 520 | - that.streamId = streamInfo.streamId; | |
| 521 | - that.mediaServerId = streamInfo.mediaServerId; | |
| 522 | - that.videoUrl = that.getUrlByStreamInfo(streamInfo); | |
| 523 | - that.recordPlay = true; | |
| 650 | + if (res.data.code == 0) { | |
| 651 | + let streamInfo = res.data.data; | |
| 652 | + that.recordPlay = false; | |
| 653 | + that.$refs.recordDownload.openDialog(that.deviceId, that.channelId, streamInfo.app, streamInfo.stream, streamInfo.mediaServerId); | |
| 654 | + }else { | |
| 655 | + that.$message({ | |
| 656 | + showClose: true, | |
| 657 | + message: res.data.msg, | |
| 658 | + type: "error", | |
| 659 | + }); | |
| 660 | + } | |
| 524 | 661 | }); |
| 525 | 662 | } |
| 526 | 663 | }, |
| 527 | 664 | stopDownloadRecord: function (callback) { |
| 528 | - this.$refs.videoPlayer.pause(); | |
| 665 | + this.$refs[this.activePlayer].pause(); | |
| 529 | 666 | this.videoUrl = ''; |
| 530 | 667 | this.$axios({ |
| 531 | 668 | method: 'get', |
| 532 | - url: '/api/download/stop/' + this.deviceId + "/" + this.channelId | |
| 533 | - }).then(function (res) { | |
| 534 | - if (callback) callback() | |
| 669 | + url: '/api/gb_record/download/stop/' + this.deviceId + "/" + this.channelId+ "/" + this.streamId | |
| 670 | + }).then((res)=> { | |
| 671 | + if (callback) callback(res) | |
| 535 | 672 | }); |
| 536 | 673 | }, |
| 537 | 674 | ptzCamera: function (command) { |
| ... | ... | @@ -539,8 +676,6 @@ export default { |
| 539 | 676 | let that = this; |
| 540 | 677 | this.$axios({ |
| 541 | 678 | method: 'post', |
| 542 | - // url: '/api/ptz/' + this.deviceId + '/' + this.channelId + '?leftRight=' + leftRight + '&upDown=' + upDown + | |
| 543 | - // '&inOut=' + zoom + '&moveSpeed=50&zoomSpeed=50' | |
| 544 | 679 | url: '/api/ptz/control/' + this.deviceId + '/' + this.channelId + '?command=' + command + '&horizonSpeed=' + this.controSpeed + '&verticalSpeed=' + this.controSpeed + '&zoomSpeed=' + this.controSpeed |
| 545 | 680 | }).then(function (res) {}); |
| 546 | 681 | }, |
| ... | ... | @@ -620,13 +755,21 @@ export default { |
| 620 | 755 | console.log(resultArray) |
| 621 | 756 | return resultArray; |
| 622 | 757 | }, |
| 758 | + copyUrl: function (dropdownItem){ | |
| 759 | + console.log(dropdownItem) | |
| 760 | + this.$copyText(dropdownItem).then((e)=> { | |
| 761 | + this.$message.success("成功拷贝到粘贴板"); | |
| 762 | + }, (e)=> { | |
| 763 | + | |
| 764 | + }) | |
| 765 | + }, | |
| 623 | 766 | gbPlay(){ |
| 624 | 767 | console.log('前端控制:播放'); |
| 625 | 768 | this.$axios({ |
| 626 | 769 | method: 'get', |
| 627 | 770 | url: '/api/playback/resume/' + this.streamId |
| 628 | 771 | }).then((res)=> { |
| 629 | - this.$refs.videoPlayer.play(this.videoUrl) | |
| 772 | + this.$refs[this.activePlayer].play(this.videoUrl) | |
| 630 | 773 | }); |
| 631 | 774 | }, |
| 632 | 775 | gbPause(){ |
| ... | ... | @@ -655,8 +798,13 @@ export default { |
| 655 | 798 | this.$axios({ |
| 656 | 799 | method: 'get', |
| 657 | 800 | url: `/api/playback/seek/${this.streamId }/` + Math.floor(this.seekTime * val / 100000) |
| 658 | - }).then(function (res) {}); | |
| 659 | - } | |
| 801 | + }).then( (res)=> { | |
| 802 | + setTimeout(()=>{ | |
| 803 | + this.$refs[this.activePlayer].play(this.videoUrl) | |
| 804 | + }, 600) | |
| 805 | + }); | |
| 806 | + }, | |
| 807 | + | |
| 660 | 808 | |
| 661 | 809 | } |
| 662 | 810 | }; | ... | ... |
web_src/src/components/dialog/rtcPlayer.vue
| ... | ... | @@ -7,11 +7,11 @@ |
| 7 | 7 | </template> |
| 8 | 8 | |
| 9 | 9 | <script> |
| 10 | +let webrtcPlayer = null; | |
| 10 | 11 | export default { |
| 11 | 12 | name: 'rtcPlayer', |
| 12 | 13 | data() { |
| 13 | 14 | return { |
| 14 | - webrtcPlayer: null, | |
| 15 | 15 | timer: null |
| 16 | 16 | }; |
| 17 | 17 | }, |
| ... | ... | @@ -35,7 +35,7 @@ export default { |
| 35 | 35 | }, |
| 36 | 36 | methods: { |
| 37 | 37 | play: function (url) { |
| 38 | - this.webrtcPlayer = new ZLMRTCClient.Endpoint({ | |
| 38 | + webrtcPlayer = new ZLMRTCClient.Endpoint({ | |
| 39 | 39 | element: document.getElementById('webRtcPlayerBox'),// video 标签 |
| 40 | 40 | debug: true,// 是否打印日志 |
| 41 | 41 | zlmsdpUrl: url,//流地址 |
| ... | ... | @@ -45,17 +45,17 @@ export default { |
| 45 | 45 | videoEnable: false, |
| 46 | 46 | recvOnly: true, |
| 47 | 47 | }) |
| 48 | - this.webrtcPlayer.on(ZLMRTCClient.Events.WEBRTC_ICE_CANDIDATE_ERROR,(e)=>{// ICE 协商出错 | |
| 48 | + webrtcPlayer.on(ZLMRTCClient.Events.WEBRTC_ICE_CANDIDATE_ERROR,(e)=>{// ICE 协商出错 | |
| 49 | 49 | console.error('ICE 协商出错') |
| 50 | 50 | this.eventcallbacK("ICE ERROR", "ICE 协商出错") |
| 51 | 51 | }); |
| 52 | 52 | |
| 53 | - this.webrtcPlayer.on(ZLMRTCClient.Events.WEBRTC_ON_REMOTE_STREAMS,(e)=>{//获取到了远端流,可以播放 | |
| 53 | + webrtcPlayer.on(ZLMRTCClient.Events.WEBRTC_ON_REMOTE_STREAMS,(e)=>{//获取到了远端流,可以播放 | |
| 54 | 54 | console.error('播放成功',e.streams) |
| 55 | 55 | this.eventcallbacK("playing", "播放成功") |
| 56 | 56 | }); |
| 57 | 57 | |
| 58 | - this.webrtcPlayer.on(ZLMRTCClient.Events.WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED,(e)=>{// offer anwser 交换失败 | |
| 58 | + webrtcPlayer.on(ZLMRTCClient.Events.WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED,(e)=>{// offer anwser 交换失败 | |
| 59 | 59 | console.error('offer anwser 交换失败',e) |
| 60 | 60 | this.eventcallbacK("OFFER ANSWER ERROR ", "offer anwser 交换失败") |
| 61 | 61 | if (e.code ==-400 && e.msg=="流不存在"){ |
| ... | ... | @@ -68,7 +68,7 @@ export default { |
| 68 | 68 | } |
| 69 | 69 | }); |
| 70 | 70 | |
| 71 | - this.webrtcPlayer.on(ZLMRTCClient.Events.WEBRTC_ON_LOCAL_STREAM,(s)=>{// 获取到了本地流 | |
| 71 | + webrtcPlayer.on(ZLMRTCClient.Events.WEBRTC_ON_LOCAL_STREAM,(s)=>{// 获取到了本地流 | |
| 72 | 72 | |
| 73 | 73 | // document.getElementById('selfVideo').srcObject=s; |
| 74 | 74 | this.eventcallbacK("LOCAL STREAM", "获取到了本地流") |
| ... | ... | @@ -76,9 +76,9 @@ export default { |
| 76 | 76 | |
| 77 | 77 | }, |
| 78 | 78 | pause: function () { |
| 79 | - if (this.webrtcPlayer != null) { | |
| 80 | - this.webrtcPlayer.close(); | |
| 81 | - this.webrtcPlayer = null; | |
| 79 | + if (webrtcPlayer != null) { | |
| 80 | + webrtcPlayer.close(); | |
| 81 | + webrtcPlayer = null; | |
| 82 | 82 | } |
| 83 | 83 | |
| 84 | 84 | }, | ... | ... |
web_src/src/components/devicePosition.vue renamed to web_src/src/components/map.vue
| ... | ... | @@ -49,7 +49,7 @@ import devicePlayer from './dialog/devicePlayer.vue' |
| 49 | 49 | import queryTrace from './dialog/queryTrace.vue' |
| 50 | 50 | |
| 51 | 51 | export default { |
| 52 | - name: "devicePosition", | |
| 52 | + name: "map", | |
| 53 | 53 | components: { |
| 54 | 54 | MapComponent, |
| 55 | 55 | DeviceTree, |
| ... | ... | @@ -183,12 +183,27 @@ export default { |
| 183 | 183 | this.clean() |
| 184 | 184 | this.closeInfoBox() |
| 185 | 185 | let params = []; |
| 186 | + let longitudeStr; | |
| 187 | + let latitudeStr; | |
| 188 | + if (window.mapParam.coordinateSystem == "GCJ-02") { | |
| 189 | + longitudeStr = "longitudeGcj02"; | |
| 190 | + latitudeStr = "latitudeGcj02"; | |
| 191 | + }else if (window.mapParam.coordinateSystem == "WGS84") { | |
| 192 | + longitudeStr = "longitudeWgs84"; | |
| 193 | + latitudeStr = "latitudeWgs84"; | |
| 194 | + }else { | |
| 195 | + longitudeStr = "longitude"; | |
| 196 | + latitudeStr = "latitude"; | |
| 197 | + } | |
| 198 | + | |
| 186 | 199 | for (let i = 0; i < channels.length; i++) { |
| 187 | - if (channels[i].longitude * channels[i].latitude === 0) { | |
| 200 | + let longitude = channels[i][longitudeStr]; | |
| 201 | + let latitude = channels[i][latitudeStr]; | |
| 202 | + if (longitude * latitude === 0) { | |
| 188 | 203 | continue; |
| 189 | 204 | } |
| 190 | 205 | let item = { |
| 191 | - position: [channels[i].longitude, channels[i].latitude], | |
| 206 | + position: [longitude, latitude], | |
| 192 | 207 | image: { |
| 193 | 208 | src: this.getImageByChannel(channels[i]), |
| 194 | 209 | anchor: [0.5, 1] |
| ... | ... | @@ -202,7 +217,7 @@ export default { |
| 202 | 217 | this.layer = this.$refs.map.addLayer(params, this.featureClickEvent) |
| 203 | 218 | console.log(4) |
| 204 | 219 | if (params.length === 1) { |
| 205 | - this.$refs.map.panTo([channels[0].longitude, channels[0].latitude], mapParam.maxZoom) | |
| 220 | + this.$refs.map.panTo([channels[0][longitudeStr], channels[0][latitudeStr]], mapParam.maxZoom) | |
| 206 | 221 | } else if (params.length > 1) { |
| 207 | 222 | this.$refs.map.fit(this.layer) |
| 208 | 223 | } else { |
| ... | ... | @@ -251,7 +266,20 @@ export default { |
| 251 | 266 | this.channel = channels[0] |
| 252 | 267 | } |
| 253 | 268 | this.$nextTick(() => { |
| 254 | - this.infoBoxId = this.$refs.map.openInfoBox([this.channel.longitude, this.channel.latitude], this.$refs.infobox, [0, -50]) | |
| 269 | + let longitudeStr; | |
| 270 | + let latitudeStr; | |
| 271 | + if (window.mapParam.coordinateSystem == "GCJ-02") { | |
| 272 | + longitudeStr = "longitudeGcj02"; | |
| 273 | + latitudeStr = "latitudeGcj02"; | |
| 274 | + }else if (window.mapParam.coordinateSystem == "WGS84") { | |
| 275 | + longitudeStr = "longitudeWgs84"; | |
| 276 | + latitudeStr = "latitudeWgs84"; | |
| 277 | + }else { | |
| 278 | + longitudeStr = "longitude"; | |
| 279 | + latitudeStr = "latitude"; | |
| 280 | + } | |
| 281 | + let position = [this.channel[longitudeStr], this.channel[latitudeStr]]; | |
| 282 | + this.infoBoxId = this.$refs.map.openInfoBox(position, this.$refs.infobox, [0, -50]) | |
| 255 | 283 | }) |
| 256 | 284 | }, |
| 257 | 285 | closeInfoBox: function () { | ... | ... |
web_src/src/components/service/DeviceService.js
| ... | ... | @@ -21,47 +21,60 @@ class DeviceService{ |
| 21 | 21 | if (typeof (errorCallback) == "function") errorCallback(error) |
| 22 | 22 | }); |
| 23 | 23 | } |
| 24 | - getAllDeviceList(callback, errorCallback) { | |
| 24 | + | |
| 25 | + getDevice(deviceId, callback, errorCallback){ | |
| 26 | + this.$axios({ | |
| 27 | + method: 'get', | |
| 28 | + url:`/api/device/query/devices/${deviceId}`, | |
| 29 | + }).then((res) => { | |
| 30 | + if (typeof (callback) == "function") callback(res.data) | |
| 31 | + }).catch((error) => { | |
| 32 | + console.log(error); | |
| 33 | + if (typeof (errorCallback) == "function") errorCallback(error) | |
| 34 | + }); | |
| 35 | + } | |
| 36 | + | |
| 37 | + getAllDeviceList(callback,endCallback, errorCallback) { | |
| 25 | 38 | let currentPage = 1; |
| 26 | 39 | let count = 100; |
| 27 | 40 | let deviceList = [] |
| 28 | - this.getAllDeviceListIteration(deviceList, currentPage, count, (data) => { | |
| 29 | - if (typeof (callback) == "function") callback(data) | |
| 30 | - }, errorCallback) | |
| 41 | + this.getAllDeviceListIteration(deviceList, currentPage, count, callback, endCallback, errorCallback) | |
| 31 | 42 | } |
| 32 | 43 | |
| 33 | - getAllDeviceListIteration(deviceList, currentPage, count, callback, errorCallback) { | |
| 44 | + getAllDeviceListIteration(deviceList, currentPage, count, callback, endCallback, errorCallback) { | |
| 34 | 45 | this.getDeviceList(currentPage, count, (data) => { |
| 35 | 46 | if (data.list) { |
| 47 | + if (typeof (callback) == "function") callback(data.list) | |
| 36 | 48 | deviceList = deviceList.concat(data.list); |
| 37 | 49 | if (deviceList.length < data.total) { |
| 38 | 50 | currentPage ++ |
| 39 | - this.getAllDeviceListIteration(deviceList, currentPage, count, callback, errorCallback) | |
| 51 | + this.getAllDeviceListIteration(deviceList, currentPage, count, callback, endCallback, errorCallback) | |
| 40 | 52 | }else { |
| 41 | - if (typeof (callback) == "function") callback(deviceList) | |
| 53 | + if (typeof (endCallback) == "function") endCallback(deviceList) | |
| 42 | 54 | } |
| 43 | 55 | } |
| 44 | 56 | }, errorCallback) |
| 45 | 57 | } |
| 46 | 58 | |
| 47 | 59 | |
| 48 | - getAllChannel(isCatalog, catalogUnderDevice, deviceId, callback, errorCallback) { | |
| 60 | + getAllChannel(isCatalog, catalogUnderDevice, deviceId, callback, endCallback, errorCallback) { | |
| 49 | 61 | let currentPage = 1; |
| 50 | 62 | let count = 100; |
| 51 | 63 | let catalogList = [] |
| 52 | - this.getAllChannelIteration(isCatalog, catalogUnderDevice, deviceId, catalogList, currentPage, count, callback, errorCallback) | |
| 64 | + this.getAllChannelIteration(isCatalog, catalogUnderDevice, deviceId, catalogList, currentPage, count, callback, endCallback, errorCallback) | |
| 53 | 65 | } |
| 54 | 66 | |
| 55 | - getAllChannelIteration(isCatalog, catalogUnderDevice, deviceId, catalogList, currentPage, count, callback, errorCallback) { | |
| 67 | + getAllChannelIteration(isCatalog, catalogUnderDevice, deviceId, catalogList, currentPage, count, callback, endCallback, errorCallback) { | |
| 56 | 68 | this.getChanel(isCatalog, catalogUnderDevice, deviceId, currentPage, count, (data) => { |
| 57 | 69 | if (data.list) { |
| 70 | + if (typeof (callback) == "function") callback(data.list) | |
| 58 | 71 | catalogList = catalogList.concat(data.list); |
| 59 | 72 | if (catalogList.length < data.total) { |
| 60 | 73 | currentPage ++ |
| 61 | 74 | this.getAllChannelIteration(isCatalog,catalogUnderDevice, deviceId, catalogList, currentPage, count, callback, errorCallback) |
| 62 | 75 | }else { |
| 63 | 76 | console.log(1) |
| 64 | - if (typeof (callback) == "function") callback(catalogList) | |
| 77 | + if (typeof (endCallback) == "function") endCallback(catalogList) | |
| 65 | 78 | } |
| 66 | 79 | } |
| 67 | 80 | }, errorCallback) |
| ... | ... | @@ -84,22 +97,23 @@ class DeviceService{ |
| 84 | 97 | } |
| 85 | 98 | |
| 86 | 99 | |
| 87 | - getAllSubChannel(isCatalog, deviceId, channelId, callback, errorCallback) { | |
| 100 | + getAllSubChannel(isCatalog, deviceId, channelId, callback, endCallback, errorCallback) { | |
| 88 | 101 | let currentPage = 1; |
| 89 | 102 | let count = 100; |
| 90 | 103 | let catalogList = [] |
| 91 | - this.getAllSubChannelIteration(isCatalog, deviceId, channelId, catalogList, currentPage, count, callback, errorCallback) | |
| 104 | + this.getAllSubChannelIteration(isCatalog, deviceId, channelId, catalogList, currentPage, count, callback, endCallback, errorCallback) | |
| 92 | 105 | } |
| 93 | 106 | |
| 94 | - getAllSubChannelIteration(isCatalog, deviceId,channelId, catalogList, currentPage, count, callback, errorCallback) { | |
| 107 | + getAllSubChannelIteration(isCatalog, deviceId,channelId, catalogList, currentPage, count, callback, endCallback, errorCallback) { | |
| 95 | 108 | this.getSubChannel(isCatalog, deviceId, channelId, currentPage, count, (data) => { |
| 96 | 109 | if (data.list) { |
| 110 | + if (typeof (callback) == "function") callback(data.list) | |
| 97 | 111 | catalogList = catalogList.concat(data.list); |
| 98 | 112 | if (catalogList.length < data.total) { |
| 99 | 113 | currentPage ++ |
| 100 | - this.getAllSubChannelIteration(isCatalog, deviceId, channelId, catalogList, currentPage, count, callback, errorCallback) | |
| 114 | + this.getAllSubChannelIteration(isCatalog, deviceId, channelId, catalogList, currentPage, count, callback, endCallback, errorCallback) | |
| 101 | 115 | }else { |
| 102 | - if (typeof (callback) == "function") callback(catalogList) | |
| 116 | + if (typeof (endCallback) == "function") endCallback(catalogList) | |
| 103 | 117 | } |
| 104 | 118 | } |
| 105 | 119 | }, errorCallback) | ... | ... |
web_src/src/layout/UiHeader.vue
| 1 | 1 | <template> |
| 2 | 2 | <div id="UiHeader"> |
| 3 | - <el-menu router :default-active="activeIndex" menu-trigger="click" background-color="#545c64" text-color="#fff" | |
| 4 | - active-text-color="#ffd04b" mode="horizontal"> | |
| 3 | + | |
| 4 | + <el-menu router :default-active="activeIndex" menu-trigger="click" background-color="#001529" text-color="#fff" | |
| 5 | + active-text-color="#1890ff" mode="horizontal"> | |
| 6 | + | |
| 5 | 7 | <el-menu-item index="/control">控制台</el-menu-item> |
| 6 | - <el-menu-item index="/live">实时监控</el-menu-item> | |
| 8 | + <el-menu-item index="/live">分屏监控</el-menu-item> | |
| 7 | 9 | <el-menu-item index="/deviceList">国标设备</el-menu-item> |
| 8 | 10 | <el-menu-item index="/map">电子地图</el-menu-item> |
| 9 | 11 | <el-menu-item index="/pushVideoList">推流列表</el-menu-item> |
| ... | ... | @@ -148,4 +150,8 @@ export default { |
| 148 | 150 | #UiHeader .el-switch__label.is-active{ |
| 149 | 151 | color: #409EFF; |
| 150 | 152 | } |
| 153 | +#UiHeader .el-menu-item.is-active { | |
| 154 | + color: #fff!important; | |
| 155 | + background-color: #1890ff!important; | |
| 156 | +} | |
| 151 | 157 | </style> | ... | ... |
web_src/src/router/index.js
| ... | ... | @@ -7,7 +7,7 @@ import deviceList from '../components/DeviceList.vue' |
| 7 | 7 | import channelList from '../components/channelList.vue' |
| 8 | 8 | import pushVideoList from '../components/PushVideoList.vue' |
| 9 | 9 | import streamProxyList from '../components/StreamProxyList.vue' |
| 10 | -import devicePosition from '../components/devicePosition.vue' | |
| 10 | +import map from '../components/map.vue' | |
| 11 | 11 | import login from '../components/Login.vue' |
| 12 | 12 | import parentPlatformList from '../components/ParentPlatformList.vue' |
| 13 | 13 | import cloudRecord from '../components/CloudRecord.vue' |
| ... | ... | @@ -69,9 +69,9 @@ export default new VueRouter({ |
| 69 | 69 | component: parentPlatformList, |
| 70 | 70 | }, |
| 71 | 71 | { |
| 72 | - path: '/devicePosition/:deviceId/:parentChannelId/:count/:page', | |
| 73 | - name: 'devicePosition', | |
| 74 | - component: devicePosition, | |
| 72 | + path: '/map/:deviceId/:parentChannelId/:count/:page', | |
| 73 | + name: 'map', | |
| 74 | + component: map, | |
| 75 | 75 | }, |
| 76 | 76 | { |
| 77 | 77 | path: '/cloudRecord', |
| ... | ... | @@ -100,8 +100,8 @@ export default new VueRouter({ |
| 100 | 100 | }, |
| 101 | 101 | { |
| 102 | 102 | path: '/map', |
| 103 | - name: 'devicePosition', | |
| 104 | - component: devicePosition, | |
| 103 | + name: 'map', | |
| 104 | + component: map, | |
| 105 | 105 | }, |
| 106 | 106 | ] |
| 107 | 107 | }, | ... | ... |