Merge branch '648540858:wvp-28181-2.0' into wvp-28181-2.0
|  |  |  | 
|---|
|  |  |  | - [X] 添加RTMP视频 | 
|---|
|  |  |  | - [X] 云端录像(需要部署单独服务配合使用) | 
|---|
|  |  |  | - [X] 多流媒体节点,自动选择负载最低的节点使用。 | 
|---|
|  |  |  | - [X] 支持使用mysql作为数据库,默认sqlite3,开箱即用。 | 
|---|
|  |  |  | - [X] WEB端支持播放H264与H265,音频支持G.711A/G.711U/AAC,覆盖国标常用编码格式。 | 
|---|
|  |  |  |  | 
|---|
|  |  |  | [//]: # (# docker快速体验) | 
|---|
|  |  |  | 
|---|
|  |  |  | <version>8.0.22</version> | 
|---|
|  |  |  | </dependency> | 
|---|
|  |  |  |  | 
|---|
|  |  |  | <!-- 添加sqlite-jdbc数据库驱动 --> | 
|---|
|  |  |  | <dependency> | 
|---|
|  |  |  | <groupId>org.xerial</groupId> | 
|---|
|  |  |  | <artifactId>sqlite-jdbc</artifactId> | 
|---|
|  |  |  | <version>3.32.3.2</version> | 
|---|
|  |  |  | </dependency> | 
|---|
|  |  |  |  | 
|---|
|  |  |  | <!--Mybatis分页插件 --> | 
|---|
|  |  |  | <dependency> | 
|---|
|  |  |  | <groupId>com.github.pagehelper</groupId> | 
|---|
|  |  |  | 
|---|
|  |  |  | package com.genersoft.iot.vmp.conf; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask; | 
|---|
|  |  |  | import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd.CatalogResponseMessageHandler; | 
|---|
|  |  |  | import org.slf4j.Logger; | 
|---|
|  |  |  | import org.slf4j.LoggerFactory; | 
|---|
|  |  |  | import org.springframework.beans.factory.annotation.Autowired; | 
|---|
|  |  |  | 
|---|
|  |  |  | import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; | 
|---|
|  |  |  | import org.springframework.stereotype.Component; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | import java.util.Date; | 
|---|
|  |  |  | import java.time.Instant; | 
|---|
|  |  |  | import java.util.Map; | 
|---|
|  |  |  | import java.util.Set; | 
|---|
|  |  |  | import java.util.concurrent.ConcurrentHashMap; | 
|---|
|  |  |  | import java.util.concurrent.ScheduledFuture; | 
|---|
|  |  |  | import java.util.concurrent.TimeUnit; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | /** | 
|---|
|  |  |  | * 动态定时任务 | 
|---|
|  |  |  | * @author lin | 
|---|
|  |  |  | */ | 
|---|
|  |  |  | @Component | 
|---|
|  |  |  | public class DynamicTask { | 
|---|
|  |  |  |  | 
|---|
|  |  |  | private Logger logger = LoggerFactory.getLogger(DynamicTask.class); | 
|---|
|  |  |  | private final Logger logger = LoggerFactory.getLogger(DynamicTask.class); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | @Autowired | 
|---|
|  |  |  | private ThreadPoolTaskScheduler threadPoolTaskScheduler; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | private Map<String, ScheduledFuture<?>> futureMap = new ConcurrentHashMap<>(); | 
|---|
|  |  |  | private Map<String, Runnable> runnableMap = new ConcurrentHashMap<>(); | 
|---|
|  |  |  | private final Map<String, ScheduledFuture<?>> futureMap = new ConcurrentHashMap<>(); | 
|---|
|  |  |  | private final Map<String, Runnable> runnableMap = new ConcurrentHashMap<>(); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | @Bean | 
|---|
|  |  |  | public ThreadPoolTaskScheduler threadPoolTaskScheduler() { | 
|---|
|  |  |  | 
|---|
|  |  |  | * @return | 
|---|
|  |  |  | */ | 
|---|
|  |  |  | public void startCron(String key, Runnable task, int cycleForCatalog) { | 
|---|
|  |  |  | ScheduledFuture future = futureMap.get(key); | 
|---|
|  |  |  | ScheduledFuture<?> future = futureMap.get(key); | 
|---|
|  |  |  | if (future != null) { | 
|---|
|  |  |  | if (future.isCancelled()) { | 
|---|
|  |  |  | logger.debug("任务【{}】已存在但是关闭状态!!!", key); | 
|---|
|  |  |  | 
|---|
|  |  |  | */ | 
|---|
|  |  |  | public void startDelay(String key, Runnable task, int delay) { | 
|---|
|  |  |  | stop(key); | 
|---|
|  |  |  | Date starTime = new Date(System.currentTimeMillis() + delay); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 获取执行的时刻 | 
|---|
|  |  |  | Instant startInstant = Instant.now().plusMillis(TimeUnit.MILLISECONDS.toMillis(delay)); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | ScheduledFuture future = futureMap.get(key); | 
|---|
|  |  |  | if (future != null) { | 
|---|
|  |  |  | 
|---|
|  |  |  | } | 
|---|
|  |  |  | } | 
|---|
|  |  |  | // scheduleWithFixedDelay 必须等待上一个任务结束才开始计时period, cycleForCatalog表示执行的间隔 | 
|---|
|  |  |  | future = threadPoolTaskScheduler.schedule(task, starTime); | 
|---|
|  |  |  | future = threadPoolTaskScheduler.schedule(task, startInstant); | 
|---|
|  |  |  | if (future != null){ | 
|---|
|  |  |  | futureMap.put(key, future); | 
|---|
|  |  |  | runnableMap.put(key, task); | 
|---|
|  |  |  | 
|---|
|  |  |  | tcpSipProvider.setDialogErrorsAutomaticallyHandled(); | 
|---|
|  |  |  | tcpSipProvider.addSipListener(sipProcessorObserver); | 
|---|
|  |  |  | //            tcpSipProvider.setAutomaticDialogSupportEnabled(false); | 
|---|
|  |  |  | logger.info("Sip Server TCP 启动成功 port {" + sipConfig.getMonitorIp() + ":" + sipConfig.getPort() + "}"); | 
|---|
|  |  |  | logger.info("[Sip Server] TCP 启动成功 {}:{}", sipConfig.getMonitorIp(), sipConfig.getPort()); | 
|---|
|  |  |  | } catch (TransportNotSupportedException e) { | 
|---|
|  |  |  | e.printStackTrace(); | 
|---|
|  |  |  | } catch (InvalidArgumentException e) { | 
|---|
|  |  |  | logger.error("无法使用 [ {}:{} ]作为SIP[ TCP ]服务,可排查: 1. sip.monitor-ip 是否为本机网卡IP; 2. sip.port 是否已被占用" | 
|---|
|  |  |  | logger.error("[Sip Server]  无法使用 [ {}:{} ]作为SIP[ TCP ]服务,可排查: 1. sip.monitor-ip 是否为本机网卡IP; 2. sip.port 是否已被占用" | 
|---|
|  |  |  | , sipConfig.getMonitorIp(), sipConfig.getPort()); | 
|---|
|  |  |  | } catch (TooManyListenersException e) { | 
|---|
|  |  |  | e.printStackTrace(); | 
|---|
|  |  |  | 
|---|
|  |  |  | } catch (TransportNotSupportedException e) { | 
|---|
|  |  |  | e.printStackTrace(); | 
|---|
|  |  |  | } catch (InvalidArgumentException e) { | 
|---|
|  |  |  | logger.error("无法使用 [ {}:{} ]作为SIP[ UDP ]服务,可排查: 1. sip.monitor-ip 是否为本机网卡IP; 2. sip.port 是否已被占用" | 
|---|
|  |  |  | logger.error("[Sip Server]  无法使用 [ {}:{} ]作为SIP[ UDP ]服务,可排查: 1. sip.monitor-ip 是否为本机网卡IP; 2. sip.port 是否已被占用" | 
|---|
|  |  |  | , sipConfig.getMonitorIp(), sipConfig.getPort()); | 
|---|
|  |  |  | } catch (TooManyListenersException e) { | 
|---|
|  |  |  | e.printStackTrace(); | 
|---|
|  |  |  | } catch (ObjectInUseException e) { | 
|---|
|  |  |  | e.printStackTrace(); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | logger.info("Sip Server UDP 启动成功 port [" + sipConfig.getMonitorIp() + ":" + sipConfig.getPort() + "]"); | 
|---|
|  |  |  | logger.info("[Sip Server] UDP 启动成功 {}:{}", sipConfig.getMonitorIp(), sipConfig.getPort()); | 
|---|
|  |  |  | return udpSipProvider; | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | 
|---|
|  |  |  |  | 
|---|
|  |  |  | import java.security.MessageDigest; | 
|---|
|  |  |  | import java.security.NoSuchAlgorithmException; | 
|---|
|  |  |  | import java.text.DecimalFormat; | 
|---|
|  |  |  | import java.util.Date; | 
|---|
|  |  |  | import java.time.Instant; | 
|---|
|  |  |  | import java.util.Random; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | import javax.sip.address.URI; | 
|---|
|  |  |  | 
|---|
|  |  |  | * @return a generated nonce. | 
|---|
|  |  |  | */ | 
|---|
|  |  |  | private String generateNonce() { | 
|---|
|  |  |  | // Get the time of day and run MD5 over it. | 
|---|
|  |  |  | Date date = new Date(); | 
|---|
|  |  |  | long time = date.getTime(); | 
|---|
|  |  |  | long time = Instant.now().toEpochMilli(); | 
|---|
|  |  |  | Random rand = new Random(); | 
|---|
|  |  |  | long pad = rand.nextLong(); | 
|---|
|  |  |  | // String nonceString = (new Long(time)).toString() | 
|---|
|  |  |  | //         + (new Long(pad)).toString(); | 
|---|
|  |  |  | String nonceString = Long.valueOf(time).toString() | 
|---|
|  |  |  | + Long.valueOf(pad).toString(); | 
|---|
|  |  |  | byte mdbytes[] = messageDigest.digest(nonceString.getBytes()); | 
|---|
|  |  |  | // Convert the mdbytes array into a hex string. | 
|---|
|  |  |  | return toHexString(mdbytes); | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | 
|---|
|  |  |  | package com.genersoft.iot.vmp.gb28181.bean; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | import java.util.Date; | 
|---|
|  |  |  | import java.time.Instant; | 
|---|
|  |  |  | import java.util.List; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | public class CatalogData { | 
|---|
|  |  |  | private int sn; // 命令序列号 | 
|---|
|  |  |  | private int total; | 
|---|
|  |  |  | private List<DeviceChannel> channelList; | 
|---|
|  |  |  | private Date lastTime; | 
|---|
|  |  |  | private Instant lastTime; | 
|---|
|  |  |  | private Device device; | 
|---|
|  |  |  | private String errorMsg; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | 
|---|
|  |  |  | this.channelList = channelList; | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | public Date getLastTime() { | 
|---|
|  |  |  | public Instant getLastTime() { | 
|---|
|  |  |  | return lastTime; | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | public void setLastTime(Date lastTime) { | 
|---|
|  |  |  | public void setLastTime(Instant lastTime) { | 
|---|
|  |  |  | this.lastTime = lastTime; | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | 
|---|
|  |  |  | package com.genersoft.iot.vmp.gb28181.bean; | 
|---|
|  |  |  |  | 
|---|
|  |  |  |  | 
|---|
|  |  |  | //import gov.nist.javax.sip.header.SIPDate; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | import java.time.Instant; | 
|---|
|  |  |  | import java.util.List; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | /** | 
|---|
|  |  |  | 
|---|
|  |  |  | private String name; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | private int sumNum; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | private Instant lastTime; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | private List<RecordItem> recordList; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | 
|---|
|  |  |  | public void setSn(String sn) { | 
|---|
|  |  |  | this.sn = sn; | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | public Instant getLastTime() { | 
|---|
|  |  |  | return lastTime; | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | public void setLastTime(Instant lastTime) { | 
|---|
|  |  |  | this.lastTime = lastTime; | 
|---|
|  |  |  | } | 
|---|
|  |  |  | } | 
|---|
|  |  |  | 
|---|
|  |  |  | import org.jetbrains.annotations.NotNull; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | import java.text.ParseException; | 
|---|
|  |  |  | import java.util.Date; | 
|---|
|  |  |  | import java.time.Instant; | 
|---|
|  |  |  | import java.time.temporal.TemporalAccessor; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | /** | 
|---|
|  |  |  | * @description:设备录像bean | 
|---|
|  |  |  | 
|---|
|  |  |  |  | 
|---|
|  |  |  | @Override | 
|---|
|  |  |  | public int compareTo(@NotNull RecordItem recordItem) { | 
|---|
|  |  |  | try { | 
|---|
|  |  |  | Date startTime_now = DateUtil.format.parse(startTime); | 
|---|
|  |  |  | Date startTime_param = DateUtil.format.parse(recordItem.getStartTime()); | 
|---|
|  |  |  | if (startTime_param.compareTo(startTime_now) > 0) { | 
|---|
|  |  |  | return -1; | 
|---|
|  |  |  | }else { | 
|---|
|  |  |  | return 1; | 
|---|
|  |  |  | } | 
|---|
|  |  |  | } catch (ParseException e) { | 
|---|
|  |  |  | e.printStackTrace(); | 
|---|
|  |  |  | TemporalAccessor startTimeNow = DateUtil.formatter.parse(startTime); | 
|---|
|  |  |  | TemporalAccessor startTimeParam = DateUtil.formatter.parse(recordItem.getStartTime()); | 
|---|
|  |  |  | Instant startTimeParamInstant = Instant.from(startTimeParam); | 
|---|
|  |  |  | Instant startTimeNowInstant = Instant.from(startTimeNow); | 
|---|
|  |  |  | if (startTimeNowInstant.equals(startTimeParamInstant)) { | 
|---|
|  |  |  | return 0; | 
|---|
|  |  |  | }else if (Instant.from(startTimeParam).isAfter(Instant.from(startTimeNow)) ) { | 
|---|
|  |  |  | return -1; | 
|---|
|  |  |  | }else { | 
|---|
|  |  |  | return 1; | 
|---|
|  |  |  | } | 
|---|
|  |  |  | return 0; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | } | 
|---|
|  |  |  | } | 
|---|
|  |  |  | 
|---|
|  |  |  | dynamicTask.stop(taskOverdueKey); | 
|---|
|  |  |  | // 添加任务处理订阅过期 | 
|---|
|  |  |  | dynamicTask.startDelay(taskOverdueKey, () -> { | 
|---|
|  |  |  | System.out.println("订阅过期"); | 
|---|
|  |  |  | removeMobilePositionSubscribe(subscribeInfo.getId()); | 
|---|
|  |  |  | }, | 
|---|
|  |  |  | subscribeInfo.getExpires() * 1000); | 
|---|
|  |  |  | 
|---|
|  |  |  | import javax.sip.*; | 
|---|
|  |  |  | import javax.sip.header.CallIdHeader; | 
|---|
|  |  |  | import javax.sip.message.Response; | 
|---|
|  |  |  | import java.util.Calendar; | 
|---|
|  |  |  | import java.util.Date; | 
|---|
|  |  |  | import java.time.Instant; | 
|---|
|  |  |  | import java.util.Map; | 
|---|
|  |  |  | import java.util.concurrent.ConcurrentHashMap; | 
|---|
|  |  |  | import java.util.concurrent.TimeUnit; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | /** | 
|---|
|  |  |  | * @author lin | 
|---|
|  |  |  | */ | 
|---|
|  |  |  | @Component | 
|---|
|  |  |  | public class SipSubscribe { | 
|---|
|  |  |  |  | 
|---|
|  |  |  | 
|---|
|  |  |  |  | 
|---|
|  |  |  | private Map<String, SipSubscribe.Event> okSubscribes = new ConcurrentHashMap<>(); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | private Map<String, Date> okTimeSubscribes = new ConcurrentHashMap<>(); | 
|---|
|  |  |  | private Map<String, Date> errorTimeSubscribes = new ConcurrentHashMap<>(); | 
|---|
|  |  |  | private Map<String, Instant> okTimeSubscribes = new ConcurrentHashMap<>(); | 
|---|
|  |  |  | private Map<String, Instant> errorTimeSubscribes = new ConcurrentHashMap<>(); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | //    @Scheduled(cron="*/5 * * * * ?")   //每五秒执行一次 | 
|---|
|  |  |  | //    @Scheduled(fixedRate= 100 * 60 * 60 ) | 
|---|
|  |  |  | //    @Scheduled(fixedRate= 100 * 60 * 60 ) | 
|---|
|  |  |  | @Scheduled(cron="0 0/5 * * * ?")   //每5分钟执行一次 | 
|---|
|  |  |  | public void execute(){ | 
|---|
|  |  |  | logger.info("[定时任务] 清理过期的SIP订阅信息"); | 
|---|
|  |  |  | Calendar calendar = Calendar.getInstance(); | 
|---|
|  |  |  | calendar.setTime(new Date()); | 
|---|
|  |  |  | calendar.set(Calendar.MINUTE, calendar.get(Calendar.MINUTE) - 5); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | Instant instant = Instant.now().minusMillis(TimeUnit.MINUTES.toMillis(5)); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | for (String key : okTimeSubscribes.keySet()) { | 
|---|
|  |  |  | if (okTimeSubscribes.get(key).before(calendar.getTime())){ | 
|---|
|  |  |  | //                logger.info("[定时任务] 清理过期的订阅信息: {}", key); | 
|---|
|  |  |  | if (okTimeSubscribes.get(key).isBefore(instant)){ | 
|---|
|  |  |  | okSubscribes.remove(key); | 
|---|
|  |  |  | okTimeSubscribes.remove(key); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | } | 
|---|
|  |  |  | for (String key : errorTimeSubscribes.keySet()) { | 
|---|
|  |  |  | if (errorTimeSubscribes.get(key).before(calendar.getTime())){ | 
|---|
|  |  |  | //                logger.info("[定时任务] 清理过期的订阅信息: {}", key); | 
|---|
|  |  |  | if (errorTimeSubscribes.get(key).isBefore(instant)){ | 
|---|
|  |  |  | errorSubscribes.remove(key); | 
|---|
|  |  |  | errorTimeSubscribes.remove(key); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | 
|---|
|  |  |  |  | 
|---|
|  |  |  | public void addErrorSubscribe(String key, SipSubscribe.Event event) { | 
|---|
|  |  |  | errorSubscribes.put(key, event); | 
|---|
|  |  |  | errorTimeSubscribes.put(key, new Date()); | 
|---|
|  |  |  | errorTimeSubscribes.put(key, Instant.now()); | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | public void addOkSubscribe(String key, SipSubscribe.Event event) { | 
|---|
|  |  |  | okSubscribes.put(key, event); | 
|---|
|  |  |  | okTimeSubscribes.put(key, new Date()); | 
|---|
|  |  |  | okTimeSubscribes.put(key, Instant.now()); | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | public SipSubscribe.Event getErrorSubscribe(String key) { | 
|---|
|  |  |  | 
|---|
|  |  |  | import com.genersoft.iot.vmp.gb28181.bean.Device; | 
|---|
|  |  |  | import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; | 
|---|
|  |  |  | import com.genersoft.iot.vmp.gb28181.bean.SyncStatus; | 
|---|
|  |  |  | import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; | 
|---|
|  |  |  | import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage; | 
|---|
|  |  |  | import com.genersoft.iot.vmp.storager.IVideoManagerStorage; | 
|---|
|  |  |  | import com.genersoft.iot.vmp.vmanager.bean.WVPResult; | 
|---|
|  |  |  | import org.springframework.beans.factory.annotation.Autowired; | 
|---|
|  |  |  | import org.springframework.scheduling.annotation.Scheduled; | 
|---|
|  |  |  | import org.springframework.stereotype.Component; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | import java.time.Instant; | 
|---|
|  |  |  | import java.util.*; | 
|---|
|  |  |  | import java.util.concurrent.ConcurrentHashMap; | 
|---|
|  |  |  | import java.util.concurrent.TimeUnit; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | @Component | 
|---|
|  |  |  | public class CatalogDataCatch { | 
|---|
|  |  |  |  | 
|---|
|  |  |  | public static Map<String, CatalogData> data = new ConcurrentHashMap<>(); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | @Autowired | 
|---|
|  |  |  | private DeferredResultHolder deferredResultHolder; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | @Autowired | 
|---|
|  |  |  | private IVideoManagerStorage storager; | 
|---|
|  |  |  | 
|---|
|  |  |  | catalogData.setDevice(device); | 
|---|
|  |  |  | catalogData.setSn(sn); | 
|---|
|  |  |  | catalogData.setStatus(CatalogData.CatalogDataStatus.ready); | 
|---|
|  |  |  | catalogData.setLastTime(new Date(System.currentTimeMillis())); | 
|---|
|  |  |  | catalogData.setLastTime(Instant.now()); | 
|---|
|  |  |  | data.put(device.getDeviceId(), catalogData); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | } | 
|---|
|  |  |  | 
|---|
|  |  |  | catalogData.setDevice(device); | 
|---|
|  |  |  | catalogData.setChannelList(Collections.synchronizedList(new ArrayList<>())); | 
|---|
|  |  |  | catalogData.setStatus(CatalogData.CatalogDataStatus.runIng); | 
|---|
|  |  |  | catalogData.setLastTime(new Date(System.currentTimeMillis())); | 
|---|
|  |  |  | catalogData.setLastTime(Instant.now()); | 
|---|
|  |  |  | data.put(deviceId, catalogData); | 
|---|
|  |  |  | }else { | 
|---|
|  |  |  | // 同一个设备的通道同步请求只考虑一个,其他的直接忽略 | 
|---|
|  |  |  | 
|---|
|  |  |  | catalogData.setDevice(device); | 
|---|
|  |  |  | catalogData.setStatus(CatalogData.CatalogDataStatus.runIng); | 
|---|
|  |  |  | catalogData.getChannelList().addAll(deviceChannelList); | 
|---|
|  |  |  | catalogData.setLastTime(new Date(System.currentTimeMillis())); | 
|---|
|  |  |  | catalogData.setLastTime(Instant.now()); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | 
|---|
|  |  |  | @Scheduled(fixedRate = 5 * 1000)   //每5秒执行一次, 发现数据5秒未更新则移除数据并认为数据接收超时 | 
|---|
|  |  |  | private void timerTask(){ | 
|---|
|  |  |  | Set<String> keys = data.keySet(); | 
|---|
|  |  |  | Calendar calendarBefore5S = Calendar.getInstance(); | 
|---|
|  |  |  | calendarBefore5S.setTime(new Date()); | 
|---|
|  |  |  | calendarBefore5S.set(Calendar.SECOND, calendarBefore5S.get(Calendar.SECOND) - 5); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | Calendar calendarBefore30S = Calendar.getInstance(); | 
|---|
|  |  |  | calendarBefore30S.setTime(new Date()); | 
|---|
|  |  |  | calendarBefore30S.set(Calendar.SECOND, calendarBefore30S.get(Calendar.SECOND) - 30); | 
|---|
|  |  |  | Instant instantBefore5S = Instant.now().minusMillis(TimeUnit.SECONDS.toMillis(5)); | 
|---|
|  |  |  | Instant instantBefore30S = Instant.now().minusMillis(TimeUnit.SECONDS.toMillis(30)); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | for (String deviceId : keys) { | 
|---|
|  |  |  | CatalogData catalogData = data.get(deviceId); | 
|---|
|  |  |  | if ( catalogData.getLastTime().before(calendarBefore5S.getTime())) { // 超过五秒收不到消息任务超时, 只更新这一部分数据 | 
|---|
|  |  |  | if ( catalogData.getLastTime().isBefore(instantBefore5S)) { // 超过五秒收不到消息任务超时, 只更新这一部分数据 | 
|---|
|  |  |  | if (catalogData.getStatus().equals(CatalogData.CatalogDataStatus.runIng)) { | 
|---|
|  |  |  | storager.resetChannels(catalogData.getDevice().getDeviceId(), catalogData.getChannelList()); | 
|---|
|  |  |  | if (catalogData.getTotal() != catalogData.getChannelList().size()) { | 
|---|
|  |  |  | 
|---|
|  |  |  | } | 
|---|
|  |  |  | catalogData.setStatus(CatalogData.CatalogDataStatus.end); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | if (catalogData.getStatus().equals(CatalogData.CatalogDataStatus.end) && catalogData.getLastTime().before(calendarBefore30S.getTime())) { // 超过三十秒,如果标记为end则删除 | 
|---|
|  |  |  | if (catalogData.getStatus().equals(CatalogData.CatalogDataStatus.end) && catalogData.getLastTime().isBefore(instantBefore30S)) { // 超过三十秒,如果标记为end则删除 | 
|---|
|  |  |  | data.remove(deviceId); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | } | 
|---|
| New file | 
|  |  |  | 
|---|
|  |  |  | package com.genersoft.iot.vmp.gb28181.session; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | import com.genersoft.iot.vmp.gb28181.bean.*; | 
|---|
|  |  |  | import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; | 
|---|
|  |  |  | import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage; | 
|---|
|  |  |  | import com.genersoft.iot.vmp.vmanager.bean.WVPResult; | 
|---|
|  |  |  | import org.springframework.beans.factory.annotation.Autowired; | 
|---|
|  |  |  | import org.springframework.scheduling.annotation.Scheduled; | 
|---|
|  |  |  | import org.springframework.stereotype.Component; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | import java.time.Instant; | 
|---|
|  |  |  | import java.util.*; | 
|---|
|  |  |  | import java.util.concurrent.ConcurrentHashMap; | 
|---|
|  |  |  | import java.util.concurrent.TimeUnit; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | /** | 
|---|
|  |  |  | * @author lin | 
|---|
|  |  |  | */ | 
|---|
|  |  |  | @Component | 
|---|
|  |  |  | public class RecordDataCatch { | 
|---|
|  |  |  |  | 
|---|
|  |  |  | public static Map<String, RecordInfo> data = new ConcurrentHashMap<>(); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | @Autowired | 
|---|
|  |  |  | private DeferredResultHolder deferredResultHolder; | 
|---|
|  |  |  |  | 
|---|
|  |  |  |  | 
|---|
|  |  |  | public int put(String deviceId, String sn, int sumNum, List<RecordItem> recordItems) { | 
|---|
|  |  |  | String key = deviceId + sn; | 
|---|
|  |  |  | RecordInfo recordInfo = data.get(key); | 
|---|
|  |  |  | if (recordInfo == null) { | 
|---|
|  |  |  | recordInfo = new RecordInfo(); | 
|---|
|  |  |  | recordInfo.setDeviceId(deviceId); | 
|---|
|  |  |  | recordInfo.setSn(sn.trim()); | 
|---|
|  |  |  | recordInfo.setSumNum(sumNum); | 
|---|
|  |  |  | recordInfo.setRecordList(Collections.synchronizedList(new ArrayList<>())); | 
|---|
|  |  |  | recordInfo.setLastTime(Instant.now()); | 
|---|
|  |  |  | recordInfo.getRecordList().addAll(recordItems); | 
|---|
|  |  |  | data.put(key, recordInfo); | 
|---|
|  |  |  | }else { | 
|---|
|  |  |  | // 同一个设备的通道同步请求只考虑一个,其他的直接忽略 | 
|---|
|  |  |  | if (!Objects.equals(sn.trim(), recordInfo.getSn())) { | 
|---|
|  |  |  | return 0; | 
|---|
|  |  |  | } | 
|---|
|  |  |  | recordInfo.getRecordList().addAll(recordItems); | 
|---|
|  |  |  | recordInfo.setLastTime(Instant.now()); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | return recordInfo.getRecordList().size(); | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | @Scheduled(fixedRate = 5 * 1000)   //每5秒执行一次, 发现数据5秒未更新则移除数据并认为数据接收超时 | 
|---|
|  |  |  | private void timerTask(){ | 
|---|
|  |  |  | Set<String> keys = data.keySet(); | 
|---|
|  |  |  | // 获取五秒前的时刻 | 
|---|
|  |  |  | Instant instantBefore5S = Instant.now().minusMillis(TimeUnit.SECONDS.toMillis(5)); | 
|---|
|  |  |  | for (String key : keys) { | 
|---|
|  |  |  | RecordInfo recordInfo = data.get(key); | 
|---|
|  |  |  | // 超过五秒收不到消息任务超时, 只更新这一部分数据 | 
|---|
|  |  |  | if ( recordInfo.getLastTime().isBefore(instantBefore5S)) { | 
|---|
|  |  |  | // 处理录像数据, 返回给前端 | 
|---|
|  |  |  | String msgKey = DeferredResultHolder.CALLBACK_CMD_RECORDINFO + recordInfo.getDeviceId() + recordInfo.getSn(); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | WVPResult<RecordInfo> wvpResult = new WVPResult<>(); | 
|---|
|  |  |  | wvpResult.setCode(0); | 
|---|
|  |  |  | wvpResult.setMsg("success"); | 
|---|
|  |  |  | // 对数据进行排序 | 
|---|
|  |  |  | Collections.sort(recordInfo.getRecordList()); | 
|---|
|  |  |  | wvpResult.setData(recordInfo); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | RequestMessage msg = new RequestMessage(); | 
|---|
|  |  |  | msg.setKey(msgKey); | 
|---|
|  |  |  | msg.setData(wvpResult); | 
|---|
|  |  |  | deferredResultHolder.invokeAllResult(msg); | 
|---|
|  |  |  | data.remove(key); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | } | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | public boolean isComplete(String deviceId, String sn) { | 
|---|
|  |  |  | RecordInfo recordInfo = data.get(deviceId + sn); | 
|---|
|  |  |  | return recordInfo != null && recordInfo.getRecordList().size() == recordInfo.getSumNum(); | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | public RecordInfo getRecordInfo(String deviceId, String sn) { | 
|---|
|  |  |  | return data.get(deviceId + sn); | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | public void remove(String deviceId, String sn) { | 
|---|
|  |  |  | data.remove(deviceId + sn); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | } | 
|---|
|  |  |  | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  |  | 
|---|
|  |  |  | public ClientTransaction getTransactionByStream(String deviceId, String channelId, String stream){ | 
|---|
|  |  |  | SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId, null, stream); | 
|---|
|  |  |  | public ClientTransaction getTransaction(String deviceId, String channelId, String stream, String callId){ | 
|---|
|  |  |  | SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId, callId, stream); | 
|---|
|  |  |  | if (ssrcTransaction == null) { | 
|---|
|  |  |  | return null; | 
|---|
|  |  |  | } | 
|---|
|  |  |  | 
|---|
|  |  |  | // | 
|---|
|  |  |  | StringBuffer content = new StringBuffer(200); | 
|---|
|  |  |  | content.append("v=0\r\n"); | 
|---|
|  |  |  | content.append("o="+ sipConfig.getId()+" 0 0 IN IP4 "+ mediaServerItem.getSdpIp() +"\r\n"); | 
|---|
|  |  |  | content.append("o="+ channelId+" 0 0 IN IP4 "+ mediaServerItem.getSdpIp() +"\r\n"); | 
|---|
|  |  |  | content.append("s=Play\r\n"); | 
|---|
|  |  |  | content.append("c=IN IP4 "+ mediaServerItem.getSdpIp() +"\r\n"); | 
|---|
|  |  |  | content.append("t=0 0\r\n"); | 
|---|
|  |  |  | 
|---|
|  |  |  | content.append("a=rtpmap:126 H264/90000\r\n"); | 
|---|
|  |  |  | content.append("a=rtpmap:125 H264S/90000\r\n"); | 
|---|
|  |  |  | content.append("a=fmtp:125 profile-level-id=42e01e\r\n"); | 
|---|
|  |  |  | content.append("a=rtpmap:99 MP4V-ES/90000\r\n"); | 
|---|
|  |  |  | content.append("a=fmtp:99 profile-level-id=3\r\n"); | 
|---|
|  |  |  | content.append("a=rtpmap:99 H265/90000\r\n"); | 
|---|
|  |  |  | content.append("a=rtpmap:98 H264/90000\r\n"); | 
|---|
|  |  |  | content.append("a=rtpmap:97 MPEG4/90000\r\n"); | 
|---|
|  |  |  | if("TCP-PASSIVE".equals(streamMode)){ // tcp被动模式 | 
|---|
|  |  |  | 
|---|
|  |  |  | } | 
|---|
|  |  |  | }else { | 
|---|
|  |  |  | if("TCP-PASSIVE".equals(streamMode)) { | 
|---|
|  |  |  | content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 98 97\r\n"); | 
|---|
|  |  |  | content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 97 98 99\r\n"); | 
|---|
|  |  |  | }else if ("TCP-ACTIVE".equals(streamMode)) { | 
|---|
|  |  |  | content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 98 97\r\n"); | 
|---|
|  |  |  | content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 97 98 99\r\n"); | 
|---|
|  |  |  | }else if("UDP".equals(streamMode)) { | 
|---|
|  |  |  | content.append("m=video "+ ssrcInfo.getPort() +" RTP/AVP 96 98 97\r\n"); | 
|---|
|  |  |  | content.append("m=video "+ ssrcInfo.getPort() +" RTP/AVP 96 97 98 99\r\n"); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | content.append("a=recvonly\r\n"); | 
|---|
|  |  |  | content.append("a=rtpmap:96 PS/90000\r\n"); | 
|---|
|  |  |  | content.append("a=rtpmap:98 H264/90000\r\n"); | 
|---|
|  |  |  | content.append("a=rtpmap:97 MPEG4/90000\r\n"); | 
|---|
|  |  |  | content.append("a=rtpmap:99 H265/90000\r\n"); | 
|---|
|  |  |  | if ("TCP-PASSIVE".equals(streamMode)) { // tcp被动模式 | 
|---|
|  |  |  | content.append("a=setup:passive\r\n"); | 
|---|
|  |  |  | content.append("a=connection:new\r\n"); | 
|---|
|  |  |  | 
|---|
|  |  |  |  | 
|---|
|  |  |  | StringBuffer content = new StringBuffer(200); | 
|---|
|  |  |  | content.append("v=0\r\n"); | 
|---|
|  |  |  | content.append("o="+sipConfig.getId()+" 0 0 IN IP4 " + mediaServerItem.getSdpIp() + "\r\n"); | 
|---|
|  |  |  | content.append("o="+channelId+" 0 0 IN IP4 " + mediaServerItem.getSdpIp() + "\r\n"); | 
|---|
|  |  |  | content.append("s=Playback\r\n"); | 
|---|
|  |  |  | content.append("u="+channelId+":0\r\n"); | 
|---|
|  |  |  | content.append("c=IN IP4 "+mediaServerItem.getSdpIp()+"\r\n"); | 
|---|
|  |  |  | 
|---|
|  |  |  | content.append("a=rtpmap:126 H264/90000\r\n"); | 
|---|
|  |  |  | content.append("a=rtpmap:125 H264S/90000\r\n"); | 
|---|
|  |  |  | content.append("a=fmtp:125 profile-level-id=42e01e\r\n"); | 
|---|
|  |  |  | content.append("a=rtpmap:99 MP4V-ES/90000\r\n"); | 
|---|
|  |  |  | content.append("a=fmtp:99 profile-level-id=3\r\n"); | 
|---|
|  |  |  | content.append("a=rtpmap:99 H265/90000\r\n"); | 
|---|
|  |  |  | content.append("a=rtpmap:98 H264/90000\r\n"); | 
|---|
|  |  |  | content.append("a=rtpmap:97 MPEG4/90000\r\n"); | 
|---|
|  |  |  | if("TCP-PASSIVE".equals(streamMode)){ // tcp被动模式 | 
|---|
|  |  |  | 
|---|
|  |  |  | } | 
|---|
|  |  |  | }else { | 
|---|
|  |  |  | if("TCP-PASSIVE".equals(streamMode)) { | 
|---|
|  |  |  | content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 98 97\r\n"); | 
|---|
|  |  |  | content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 97 98 99\r\n"); | 
|---|
|  |  |  | }else if ("TCP-ACTIVE".equals(streamMode)) { | 
|---|
|  |  |  | content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 98 97\r\n"); | 
|---|
|  |  |  | content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 97 98 99\r\n"); | 
|---|
|  |  |  | }else if("UDP".equals(streamMode)) { | 
|---|
|  |  |  | content.append("m=video "+ ssrcInfo.getPort() +" RTP/AVP 96 98 97\r\n"); | 
|---|
|  |  |  | content.append("m=video "+ ssrcInfo.getPort() +" RTP/AVP 96 97 98 99\r\n"); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | content.append("a=recvonly\r\n"); | 
|---|
|  |  |  | content.append("a=rtpmap:96 PS/90000\r\n"); | 
|---|
|  |  |  | content.append("a=rtpmap:98 H264/90000\r\n"); | 
|---|
|  |  |  | content.append("a=rtpmap:97 MPEG4/90000\r\n"); | 
|---|
|  |  |  | content.append("a=rtpmap:98 H264/90000\r\n"); | 
|---|
|  |  |  | content.append("a=rtpmap:99 H265/90000\r\n"); | 
|---|
|  |  |  | if("TCP-PASSIVE".equals(streamMode)){ // tcp被动模式 | 
|---|
|  |  |  | content.append("a=setup:passive\r\n"); | 
|---|
|  |  |  | content.append("a=connection:new\r\n"); | 
|---|
|  |  |  | 
|---|
|  |  |  |  | 
|---|
|  |  |  | StringBuffer content = new StringBuffer(200); | 
|---|
|  |  |  | content.append("v=0\r\n"); | 
|---|
|  |  |  | content.append("o="+sipConfig.getId()+" 0 0 IN IP4 " + mediaServerItem.getSdpIp() + "\r\n"); | 
|---|
|  |  |  | content.append("o="+channelId+" 0 0 IN IP4 " + mediaServerItem.getSdpIp() + "\r\n"); | 
|---|
|  |  |  | content.append("s=Download\r\n"); | 
|---|
|  |  |  | content.append("u="+channelId+":0\r\n"); | 
|---|
|  |  |  | content.append("c=IN IP4 "+mediaServerItem.getSdpIp()+"\r\n"); | 
|---|
|  |  |  | 
|---|
|  |  |  | } | 
|---|
|  |  |  | }else { | 
|---|
|  |  |  | if("TCP-PASSIVE".equals(streamMode)) { | 
|---|
|  |  |  | content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 98 97\r\n"); | 
|---|
|  |  |  | content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 97 98 99\r\n"); | 
|---|
|  |  |  | }else if ("TCP-ACTIVE".equals(streamMode)) { | 
|---|
|  |  |  | content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 98 97\r\n"); | 
|---|
|  |  |  | content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 97 98 99\r\n"); | 
|---|
|  |  |  | }else if("UDP".equals(streamMode)) { | 
|---|
|  |  |  | content.append("m=video "+ ssrcInfo.getPort() +" RTP/AVP 96 98 97\r\n"); | 
|---|
|  |  |  | content.append("m=video "+ ssrcInfo.getPort() +" RTP/AVP 96 97 98 99\r\n"); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | content.append("a=recvonly\r\n"); | 
|---|
|  |  |  | content.append("a=rtpmap:96 PS/90000\r\n"); | 
|---|
|  |  |  | content.append("a=rtpmap:98 H264/90000\r\n"); | 
|---|
|  |  |  | content.append("a=rtpmap:97 MPEG4/90000\r\n"); | 
|---|
|  |  |  | content.append("a=rtpmap:98 H264/90000\r\n"); | 
|---|
|  |  |  | content.append("a=rtpmap:99 H265/90000\r\n"); | 
|---|
|  |  |  | if("TCP-PASSIVE".equals(streamMode)){ // tcp被动模式 | 
|---|
|  |  |  | content.append("a=setup:passive\r\n"); | 
|---|
|  |  |  | content.append("a=connection:new\r\n"); | 
|---|
|  |  |  | 
|---|
|  |  |  | (MediaServerItem mediaServerItemInUse, JSONObject json)->{ | 
|---|
|  |  |  | hookEvent.call(new InviteStreamInfo(mediaServerItem, json, callIdHeader.getCallId(), "rtp", ssrcInfo.getStream())); | 
|---|
|  |  |  | subscribe.removeSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey); | 
|---|
|  |  |  | subscribeKey.put("regist", false); | 
|---|
|  |  |  | subscribeKey.put("schema", "rtmp"); | 
|---|
|  |  |  | // 添加流注销的订阅,注销了后向设备发送bye | 
|---|
|  |  |  | subscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey, | 
|---|
|  |  |  | (MediaServerItem mediaServerItemForEnd, JSONObject jsonForEnd)->{ | 
|---|
|  |  |  | ClientTransaction transaction = streamSession.getTransaction(device.getDeviceId(), channelId, ssrcInfo.getStream(), callIdHeader.getCallId()); | 
|---|
|  |  |  | if (transaction != null) { | 
|---|
|  |  |  | logger.info("[录像]下载结束, 发送BYE"); | 
|---|
|  |  |  | streamByeCmd(device.getDeviceId(), channelId, ssrcInfo.getStream(), callIdHeader.getCallId()); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | }); | 
|---|
|  |  |  | }); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), null, "fromplybck" + tm, null, callIdHeader, ssrcInfo.getSsrc()); | 
|---|
|  |  |  | 
|---|
|  |  |  | @Override | 
|---|
|  |  |  | public void streamByeCmd(String deviceId, String channelId, String stream, String callId, SipSubscribe.Event okEvent) { | 
|---|
|  |  |  | try { | 
|---|
|  |  |  | SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(deviceId, channelId, null, stream); | 
|---|
|  |  |  | ClientTransaction transaction = streamSession.getTransactionByStream(deviceId, channelId, stream); | 
|---|
|  |  |  | SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(deviceId, channelId, callId, stream); | 
|---|
|  |  |  | ClientTransaction transaction = streamSession.getTransaction(deviceId, channelId, stream, callId); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | if (transaction == null) { | 
|---|
|  |  |  | if (transaction == null ) { | 
|---|
|  |  |  | logger.warn("[ {} -> {}]停止视频流的时候发现事务已丢失", deviceId, channelId); | 
|---|
|  |  |  | SipSubscribe.EventResult<Object> eventResult = new SipSubscribe.EventResult<>(); | 
|---|
|  |  |  | if (okEvent != null) { | 
|---|
|  |  |  | 
|---|
|  |  |  | sipSubscribe.addErrorSubscribe(callIdHeader.getCallId(), (eventResult -> { | 
|---|
|  |  |  | errorEvent.response(eventResult); | 
|---|
|  |  |  | sipSubscribe.removeErrorSubscribe(eventResult.callId); | 
|---|
|  |  |  | sipSubscribe.removeOkSubscribe(eventResult.callId); | 
|---|
|  |  |  | })); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | // 添加订阅 | 
|---|
|  |  |  | 
|---|
|  |  |  | sipSubscribe.addOkSubscribe(callIdHeader.getCallId(), eventResult ->{ | 
|---|
|  |  |  | okEvent.response(eventResult); | 
|---|
|  |  |  | sipSubscribe.removeOkSubscribe(eventResult.callId); | 
|---|
|  |  |  | sipSubscribe.removeErrorSubscribe(eventResult.callId); | 
|---|
|  |  |  | }); | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | 
|---|
|  |  |  | import javax.sip.message.Request; | 
|---|
|  |  |  | import javax.sip.message.Response; | 
|---|
|  |  |  | import java.text.ParseException; | 
|---|
|  |  |  | import java.util.Date; | 
|---|
|  |  |  | import java.time.Instant; | 
|---|
|  |  |  | import java.util.Vector; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | /** | 
|---|
|  |  |  | 
|---|
|  |  |  |  | 
|---|
|  |  |  | Long startTime = null; | 
|---|
|  |  |  | Long stopTime = null; | 
|---|
|  |  |  | Date start = null; | 
|---|
|  |  |  | Date end = null; | 
|---|
|  |  |  | Instant start = null; | 
|---|
|  |  |  | Instant end = null; | 
|---|
|  |  |  | if (sdp.getTimeDescriptions(false) != null && sdp.getTimeDescriptions(false).size() > 0) { | 
|---|
|  |  |  | TimeDescriptionImpl timeDescription = (TimeDescriptionImpl)(sdp.getTimeDescriptions(false).get(0)); | 
|---|
|  |  |  | TimeField startTimeFiled = (TimeField)timeDescription.getTime(); | 
|---|
|  |  |  | startTime = startTimeFiled.getStartTime(); | 
|---|
|  |  |  | stopTime = startTimeFiled.getStopTime(); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | start = new Date(startTime*1000); | 
|---|
|  |  |  | end = new Date(stopTime*1000); | 
|---|
|  |  |  | start = Instant.ofEpochMilli(startTime*1000); | 
|---|
|  |  |  | end = Instant.ofEpochMilli(stopTime*1000); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | //  获取支持的格式 | 
|---|
|  |  |  | Vector mediaDescriptions = sdp.getMediaDescriptions(true); | 
|---|
|  |  |  | 
|---|
|  |  |  | sendRtpItem.setApp("rtp"); | 
|---|
|  |  |  | if ("Playback".equals(sessionName)) { | 
|---|
|  |  |  | sendRtpItem.setPlayType(InviteStreamType.PLAYBACK); | 
|---|
|  |  |  | SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, null, true); | 
|---|
|  |  |  | SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, null, true, true); | 
|---|
|  |  |  | sendRtpItem.setStreamId(ssrcInfo.getStream()); | 
|---|
|  |  |  | // 写入redis, 超时时回复 | 
|---|
|  |  |  | redisCatchStorage.updateSendRTPSever(sendRtpItem); | 
|---|
|  |  |  | playService.playBack(mediaServerItem, ssrcInfo, device.getDeviceId(), channelId, DateUtil.format.format(start), | 
|---|
|  |  |  | DateUtil.format.format(end), null, result -> { | 
|---|
|  |  |  | playService.playBack(mediaServerItem, ssrcInfo, device.getDeviceId(), channelId, DateUtil.formatter.format(start), | 
|---|
|  |  |  | DateUtil.formatter.format(end), null, result -> { | 
|---|
|  |  |  | if (result.getCode() != 0){ | 
|---|
|  |  |  | logger.warn("录像回放失败"); | 
|---|
|  |  |  | if (result.getEvent() != null) { | 
|---|
|  |  |  | 
|---|
|  |  |  | if (mediaServerItem.isRtpEnable()) { | 
|---|
|  |  |  | streamId = String.format("%s_%s", device.getDeviceId(), channelId); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, true); | 
|---|
|  |  |  | SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, true, false); | 
|---|
|  |  |  | sendRtpItem.setStreamId(ssrcInfo.getStream()); | 
|---|
|  |  |  | // 写入redis, 超时时回复 | 
|---|
|  |  |  | redisCatchStorage.updateSendRTPSever(sendRtpItem); | 
|---|
|  |  |  | 
|---|
|  |  |  | try { | 
|---|
|  |  |  | RequestEventExt evtExt = (RequestEventExt) evt; | 
|---|
|  |  |  | String requestAddress = evtExt.getRemoteIpAddress() + ":" + evtExt.getRemotePort(); | 
|---|
|  |  |  | logger.info("[{}] 收到注册请求,开始处理", requestAddress); | 
|---|
|  |  |  | logger.info("[注册请求] 开始处理: {}", requestAddress); | 
|---|
|  |  |  | Request request = evt.getRequest(); | 
|---|
|  |  |  | ExpiresHeader expiresHeader = (ExpiresHeader) request.getHeader(Expires.NAME); | 
|---|
|  |  |  | Response response = null; | 
|---|
|  |  |  | 
|---|
|  |  |  |  | 
|---|
|  |  |  | AuthorizationHeader authHead = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME); | 
|---|
|  |  |  | if (authHead == null) { | 
|---|
|  |  |  | logger.info("[{}] 未携带授权头 回复401", requestAddress); | 
|---|
|  |  |  | logger.info("[注册请求] 未携带授权头 回复401: {}", requestAddress); | 
|---|
|  |  |  | response = getMessageFactory().createResponse(Response.UNAUTHORIZED, request); | 
|---|
|  |  |  | new DigestServerAuthenticationHelper().generateChallenge(getHeaderFactory(), response, sipConfig.getDomain()); | 
|---|
|  |  |  | sendResponse(evt, response); | 
|---|
|  |  |  | 
|---|
|  |  |  | // 注册失败 | 
|---|
|  |  |  | response = getMessageFactory().createResponse(Response.FORBIDDEN, request); | 
|---|
|  |  |  | response.setReasonPhrase("wrong password"); | 
|---|
|  |  |  | logger.info("[{}] 密码/SIP服务器ID错误, 回复403", requestAddress); | 
|---|
|  |  |  | logger.info("[注册请求] 密码/SIP服务器ID错误, 回复403: {}", requestAddress); | 
|---|
|  |  |  | sendResponse(evt, response); | 
|---|
|  |  |  | return; | 
|---|
|  |  |  | } | 
|---|
|  |  |  | 
|---|
|  |  |  | // 注册成功 | 
|---|
|  |  |  | // 保存到redis | 
|---|
|  |  |  | if (registerFlag) { | 
|---|
|  |  |  | logger.info("[{}] 注册成功! deviceId:" + deviceId, requestAddress); | 
|---|
|  |  |  | logger.info("[注册成功] deviceId: {}->{}",  deviceId, requestAddress); | 
|---|
|  |  |  | device.setRegisterTime(DateUtil.getNow()); | 
|---|
|  |  |  | deviceService.online(device); | 
|---|
|  |  |  | } else { | 
|---|
|  |  |  | logger.info("[{}] 注销成功! deviceId:" + deviceId, requestAddress); | 
|---|
|  |  |  | logger.info("[注销成功] deviceId: {}->{}" ,deviceId, requestAddress); | 
|---|
|  |  |  | deviceService.offline(deviceId); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | } catch (SipException | InvalidArgumentException | NoSuchAlgorithmException | ParseException e) { | 
|---|
|  |  |  | 
|---|
|  |  |  | private void sendResponse(RequestEvent evt, Response response) throws InvalidArgumentException, SipException { | 
|---|
|  |  |  | ServerTransaction serverTransaction = getServerTransaction(evt); | 
|---|
|  |  |  | if (serverTransaction == null) { | 
|---|
|  |  |  | logger.warn("回复失败:{}", response); | 
|---|
|  |  |  | logger.warn("[回复失败]:{}", response); | 
|---|
|  |  |  | return; | 
|---|
|  |  |  | } | 
|---|
|  |  |  | serverTransaction.sendResponse(response); | 
|---|
|  |  |  | 
|---|
|  |  |  | import javax.sip.header.ViaHeader; | 
|---|
|  |  |  | import javax.sip.message.Response; | 
|---|
|  |  |  | import java.text.ParseException; | 
|---|
|  |  |  | import java.util.Calendar; | 
|---|
|  |  |  | import java.util.Date; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | @Component | 
|---|
|  |  |  | public class KeepaliveNotifyMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler { | 
|---|
|  |  |  | 
|---|
|  |  |  | CallIdHeader callIdHeader = (CallIdHeader)evt.getRequest().getHeader(CallIdHeader.NAME); | 
|---|
|  |  |  | String NotifyType =getText(rootElement, "NotifyType"); | 
|---|
|  |  |  | if (NotifyType.equals("121")){ | 
|---|
|  |  |  | logger.info("媒体播放完毕,通知关流"); | 
|---|
|  |  |  | logger.info("[录像流]推送完毕,收到关流通知"); | 
|---|
|  |  |  | String channelId =getText(rootElement, "DeviceID"); | 
|---|
|  |  |  | //            redisCatchStorage.stopPlayback(device.getDeviceId(), channelId, null, callIdHeader.getCallId()); | 
|---|
|  |  |  | //            redisCatchStorage.stopDownload(device.getDeviceId(), channelId, null, callIdHeader.getCallId()); | 
|---|
|  |  |  | // 查询是设备 | 
|---|
|  |  |  | StreamInfo streamInfo = redisCatchStorage.queryDownload(device.getDeviceId(), channelId, null, callIdHeader.getCallId()); | 
|---|
|  |  |  | // 设置进度100% | 
|---|
|  |  |  | streamInfo.setProgress(1); | 
|---|
|  |  |  | 
|---|
|  |  |  | import com.genersoft.iot.vmp.gb28181.bean.RecordInfo; | 
|---|
|  |  |  | import com.genersoft.iot.vmp.gb28181.bean.RecordItem; | 
|---|
|  |  |  | import com.genersoft.iot.vmp.gb28181.event.EventPublisher; | 
|---|
|  |  |  | import com.genersoft.iot.vmp.gb28181.transmit.callback.CheckForAllRecordsThread; | 
|---|
|  |  |  | import com.genersoft.iot.vmp.gb28181.session.RecordDataCatch; | 
|---|
|  |  |  | import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; | 
|---|
|  |  |  | import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage; | 
|---|
|  |  |  | import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; | 
|---|
|  |  |  | import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler; | 
|---|
|  |  |  | import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.ResponseMessageHandler; | 
|---|
|  |  |  | import com.genersoft.iot.vmp.utils.DateUtil; | 
|---|
|  |  |  | import com.genersoft.iot.vmp.utils.redis.RedisUtil; | 
|---|
|  |  |  | import com.genersoft.iot.vmp.vmanager.bean.WVPResult; | 
|---|
|  |  |  | import org.dom4j.DocumentException; | 
|---|
|  |  |  | import org.dom4j.Element; | 
|---|
|  |  |  | import org.slf4j.Logger; | 
|---|
|  |  |  | 
|---|
|  |  |  | import org.springframework.beans.factory.InitializingBean; | 
|---|
|  |  |  | import org.springframework.beans.factory.annotation.Autowired; | 
|---|
|  |  |  | import org.springframework.stereotype.Component; | 
|---|
|  |  |  | import org.springframework.util.StringUtils; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | import javax.sip.InvalidArgumentException; | 
|---|
|  |  |  | import javax.sip.RequestEvent; | 
|---|
|  |  |  | import javax.sip.SipException; | 
|---|
|  |  |  | import javax.sip.message.Response; | 
|---|
|  |  |  | import java.text.ParseException; | 
|---|
|  |  |  | import java.util.ArrayList; | 
|---|
|  |  |  | import java.util.Iterator; | 
|---|
|  |  |  | import java.util.List; | 
|---|
|  |  |  | import java.util.UUID; | 
|---|
|  |  |  | import java.util.*; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | /** | 
|---|
|  |  |  | * @author lin | 
|---|
|  |  |  | */ | 
|---|
|  |  |  | @Component | 
|---|
|  |  |  | public class RecordInfoResponseMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler { | 
|---|
|  |  |  |  | 
|---|
|  |  |  | 
|---|
|  |  |  | private ResponseMessageHandler responseMessageHandler; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | @Autowired | 
|---|
|  |  |  | private RedisUtil redis; | 
|---|
|  |  |  | private RecordDataCatch recordDataCatch; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | @Autowired | 
|---|
|  |  |  | private DeferredResultHolder deferredResultHolder; | 
|---|
|  |  |  |  | 
|---|
|  |  |  |  | 
|---|
|  |  |  |  | 
|---|
|  |  |  | @Autowired | 
|---|
|  |  |  | private EventPublisher eventPublisher; | 
|---|
|  |  |  | 
|---|
|  |  |  | responseAck(evt, Response.OK); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | rootElement = getRootElement(evt, device.getCharset()); | 
|---|
|  |  |  | String uuid = UUID.randomUUID().toString().replace("-", ""); | 
|---|
|  |  |  | RecordInfo recordInfo = new RecordInfo(); | 
|---|
|  |  |  | String sn = getText(rootElement, "SN"); | 
|---|
|  |  |  | String key = DeferredResultHolder.CALLBACK_CMD_RECORDINFO + device.getDeviceId() + sn; | 
|---|
|  |  |  | recordInfo.setDeviceId(device.getDeviceId()); | 
|---|
|  |  |  | recordInfo.setSn(sn); | 
|---|
|  |  |  | recordInfo.setName(getText(rootElement, "Name")); | 
|---|
|  |  |  | if (getText(rootElement, "SumNum") == null || getText(rootElement, "SumNum") == "") { | 
|---|
|  |  |  | recordInfo.setSumNum(0); | 
|---|
|  |  |  | } else { | 
|---|
|  |  |  | recordInfo.setSumNum(Integer.parseInt(getText(rootElement, "SumNum"))); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | String sumNumStr = getText(rootElement, "SumNum"); | 
|---|
|  |  |  | int sumNum = 0; | 
|---|
|  |  |  | if (!StringUtils.isEmpty(sumNumStr)) { | 
|---|
|  |  |  | sumNum = Integer.parseInt(sumNumStr); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | Element recordListElement = rootElement.element("RecordList"); | 
|---|
|  |  |  | if (recordListElement == null || recordInfo.getSumNum() == 0) { | 
|---|
|  |  |  | if (recordListElement == null || sumNum == 0) { | 
|---|
|  |  |  | logger.info("无录像数据"); | 
|---|
|  |  |  | eventPublisher.recordEndEventPush(recordInfo); | 
|---|
|  |  |  | RequestMessage msg = new RequestMessage(); | 
|---|
|  |  |  | msg.setKey(key); | 
|---|
|  |  |  | msg.setData(recordInfo); | 
|---|
|  |  |  | deferredResultHolder.invokeAllResult(msg); | 
|---|
|  |  |  | recordDataCatch.put(device.getDeviceId(), sn, sumNum, new ArrayList<>()); | 
|---|
|  |  |  | releaseRequest(device.getDeviceId(), sn); | 
|---|
|  |  |  | } else { | 
|---|
|  |  |  | Iterator<Element> recordListIterator = recordListElement.elementIterator(); | 
|---|
|  |  |  | List<RecordItem> recordList = new ArrayList<RecordItem>(); | 
|---|
|  |  |  | if (recordListIterator != null) { | 
|---|
|  |  |  | RecordItem record = new RecordItem(); | 
|---|
|  |  |  | logger.info("处理录像列表数据..."); | 
|---|
|  |  |  | List<RecordItem> recordList = new ArrayList<>(); | 
|---|
|  |  |  | // 遍历DeviceList | 
|---|
|  |  |  | while (recordListIterator.hasNext()) { | 
|---|
|  |  |  | Element itemRecord = recordListIterator.next(); | 
|---|
|  |  |  | 
|---|
|  |  |  | logger.info("记录为空,下一个..."); | 
|---|
|  |  |  | continue; | 
|---|
|  |  |  | } | 
|---|
|  |  |  | record = new RecordItem(); | 
|---|
|  |  |  | RecordItem record = new RecordItem(); | 
|---|
|  |  |  | record.setDeviceId(getText(itemRecord, "DeviceID")); | 
|---|
|  |  |  | record.setName(getText(itemRecord, "Name")); | 
|---|
|  |  |  | record.setFilePath(getText(itemRecord, "FilePath")); | 
|---|
|  |  |  | record.setFileSize(getText(itemRecord, "FileSize")); | 
|---|
|  |  |  | record.setAddress(getText(itemRecord, "Address")); | 
|---|
|  |  |  | record.setStartTime( | 
|---|
|  |  |  | DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(getText(itemRecord, "StartTime"))); | 
|---|
|  |  |  | record.setEndTime( | 
|---|
|  |  |  | DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(getText(itemRecord, "EndTime"))); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | String startTimeStr = getText(itemRecord, "StartTime"); | 
|---|
|  |  |  | record.setStartTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(startTimeStr)); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | String endTimeStr = getText(itemRecord, "EndTime"); | 
|---|
|  |  |  | record.setEndTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(endTimeStr)); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | record.setSecrecy(itemRecord.element("Secrecy") == null ? 0 | 
|---|
|  |  |  | : Integer.parseInt(getText(itemRecord, "Secrecy"))); | 
|---|
|  |  |  | record.setType(getText(itemRecord, "Type")); | 
|---|
|  |  |  | record.setRecorderId(getText(itemRecord, "RecorderID")); | 
|---|
|  |  |  | recordList.add(record); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | recordInfo.setRecordList(recordList); | 
|---|
|  |  |  | int count = recordDataCatch.put(device.getDeviceId(), sn, sumNum, recordList); | 
|---|
|  |  |  | logger.info("[国标录像], {}->{}: {}/{}", device.getDeviceId(), sn, count, sumNum); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | eventPublisher.recordEndEventPush(recordInfo); | 
|---|
|  |  |  | // 改用单独线程统计已获取录像文件数量,避免多包并行分别统计不完整的问题 | 
|---|
|  |  |  | String cacheKey = CACHE_RECORDINFO_KEY + device.getDeviceId() + sn; | 
|---|
|  |  |  | redis.set(cacheKey + "_" + uuid, recordList, 90); | 
|---|
|  |  |  | if (!threadNameList.contains(cacheKey)) { | 
|---|
|  |  |  | threadNameList.add(cacheKey); | 
|---|
|  |  |  | CheckForAllRecordsThread chk = new CheckForAllRecordsThread(cacheKey, recordInfo); | 
|---|
|  |  |  | chk.setName(cacheKey); | 
|---|
|  |  |  | chk.setDeferredResultHolder(deferredResultHolder); | 
|---|
|  |  |  | chk.setRedis(redis); | 
|---|
|  |  |  | chk.setLogger(logger); | 
|---|
|  |  |  | chk.start(); | 
|---|
|  |  |  | if (logger.isDebugEnabled()) { | 
|---|
|  |  |  | logger.debug("Start Thread " + cacheKey + "."); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | } else { | 
|---|
|  |  |  | if (logger.isDebugEnabled()) { | 
|---|
|  |  |  | logger.debug("Thread " + cacheKey + " already started."); | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | if (recordDataCatch.isComplete(device.getDeviceId(), sn)){ | 
|---|
|  |  |  | releaseRequest(device.getDeviceId(), sn); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | } | 
|---|
|  |  |  | } catch (SipException e) { | 
|---|
|  |  |  | 
|---|
|  |  |  | public void handForPlatform(RequestEvent evt, ParentPlatform parentPlatform, Element element) { | 
|---|
|  |  |  |  | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | public void releaseRequest(String deviceId, String sn){ | 
|---|
|  |  |  | String key = DeferredResultHolder.CALLBACK_CMD_RECORDINFO + deviceId + sn; | 
|---|
|  |  |  | WVPResult<RecordInfo> wvpResult = new WVPResult<>(); | 
|---|
|  |  |  | wvpResult.setCode(0); | 
|---|
|  |  |  | wvpResult.setMsg("success"); | 
|---|
|  |  |  | // 对数据进行排序 | 
|---|
|  |  |  | Collections.sort(recordDataCatch.getRecordInfo(deviceId, sn).getRecordList()); | 
|---|
|  |  |  | wvpResult.setData(recordDataCatch.getRecordInfo(deviceId, sn)); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | RequestMessage msg = new RequestMessage(); | 
|---|
|  |  |  | msg.setKey(key); | 
|---|
|  |  |  | msg.setData(wvpResult); | 
|---|
|  |  |  | deferredResultHolder.invokeAllResult(msg); | 
|---|
|  |  |  | recordDataCatch.remove(deviceId, sn); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | } | 
|---|
|  |  |  | 
|---|
|  |  |  | }); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 获取zlm信息 | 
|---|
|  |  |  | logger.info("[zlm接入]等待默认zlm中..."); | 
|---|
|  |  |  | logger.info("[zlm] 等待默认zlm中..."); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 获取所有的zlm, 并开启主动连接 | 
|---|
|  |  |  | List<MediaServerItem> all = mediaServerService.getAllFromDatabase(); | 
|---|
|  |  |  | 
|---|
|  |  |  | @Async | 
|---|
|  |  |  | @EventListener | 
|---|
|  |  |  | public void onApplicationEvent(ZLMOnlineEvent event) { | 
|---|
|  |  |  |  | 
|---|
|  |  |  | logger.info("【ZLM上线】ID:" + event.getMediaServerId()); | 
|---|
|  |  |  | logger.info("[ZLM] 上线 ID:" + event.getMediaServerId()); | 
|---|
|  |  |  | streamPushService.zlmServerOnline(event.getMediaServerId()); | 
|---|
|  |  |  | streamProxyService.zlmServerOnline(event.getMediaServerId()); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | 
|---|
|  |  |  | @EventListener | 
|---|
|  |  |  | public void onApplicationEvent(ZLMOfflineEvent event) { | 
|---|
|  |  |  |  | 
|---|
|  |  |  | logger.info("ZLM离线事件触发,ID:" + event.getMediaServerId()); | 
|---|
|  |  |  | logger.info("[ZLM] 离线,ID:" + event.getMediaServerId()); | 
|---|
|  |  |  | // 处理ZLM离线 | 
|---|
|  |  |  | mediaServerService.zlmServerOffline(event.getMediaServerId()); | 
|---|
|  |  |  | streamProxyService.zlmServerOffline(event.getMediaServerId()); | 
|---|
|  |  |  | 
|---|
|  |  |  |  | 
|---|
|  |  |  | void updateVmServer(List<MediaServerItem>  mediaServerItemList); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, boolean ssrcCheck); | 
|---|
|  |  |  | SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, boolean ssrcCheck, boolean isPlayback); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, String ssrc, boolean ssrcCheck, boolean isPlayback); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | 
|---|
|  |  |  | import com.genersoft.iot.vmp.gb28181.task.impl.MobilePositionSubscribeTask; | 
|---|
|  |  |  | import com.genersoft.iot.vmp.gb28181.bean.SyncStatus; | 
|---|
|  |  |  | import com.genersoft.iot.vmp.service.IMediaServerService; | 
|---|
|  |  |  | import com.genersoft.iot.vmp.service.IMediaService; | 
|---|
|  |  |  | import com.genersoft.iot.vmp.storager.IRedisCatchStorage; | 
|---|
|  |  |  | import com.genersoft.iot.vmp.storager.dao.DeviceMapper; | 
|---|
|  |  |  | import com.genersoft.iot.vmp.utils.DateUtil; | 
|---|
|  |  |  | import org.slf4j.Logger; | 
|---|
|  |  |  | import org.slf4j.LoggerFactory; | 
|---|
|  |  |  | import org.springframework.beans.factory.annotation.Autowired; | 
|---|
|  |  |  | import org.springframework.beans.factory.annotation.Qualifier; | 
|---|
|  |  |  | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; | 
|---|
|  |  |  | import org.springframework.stereotype.Service; | 
|---|
|  |  |  | import org.springframework.util.StringUtils; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | import javax.sip.DialogState; | 
|---|
|  |  |  | import javax.sip.TimeoutEvent; | 
|---|
|  |  |  | import java.text.ParseException; | 
|---|
|  |  |  | import java.util.Calendar; | 
|---|
|  |  |  | import java.util.Date; | 
|---|
|  |  |  | import java.time.Instant; | 
|---|
|  |  |  | import java.util.List; | 
|---|
|  |  |  | import java.util.concurrent.TimeUnit; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | /** | 
|---|
|  |  |  | * 设备业务(目录订阅) | 
|---|
|  |  |  | 
|---|
|  |  |  |  | 
|---|
|  |  |  | @Override | 
|---|
|  |  |  | public void online(Device device) { | 
|---|
|  |  |  | logger.info("[设备上线],deviceId:" + device.getDeviceId()); | 
|---|
|  |  |  | logger.info("[设备上线] deviceId:{}->{}:{}", device.getDeviceId(), device.getIp(), device.getPort()); | 
|---|
|  |  |  | Device deviceInRedis = redisCatchStorage.getDevice(device.getDeviceId()); | 
|---|
|  |  |  | Device deviceInDb = deviceMapper.getDeviceByDeviceId(device.getDeviceId()); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | 
|---|
|  |  |  | // 刷新过期任务 | 
|---|
|  |  |  | String registerExpireTaskKey = registerExpireTaskKeyPrefix + device.getDeviceId(); | 
|---|
|  |  |  | dynamicTask.stop(registerExpireTaskKey); | 
|---|
|  |  |  | dynamicTask.startDelay(registerExpireTaskKey, ()->{ | 
|---|
|  |  |  | offline(device.getDeviceId()); | 
|---|
|  |  |  | }, device.getExpires() * 1000); | 
|---|
|  |  |  | dynamicTask.startDelay(registerExpireTaskKey, ()-> offline(device.getDeviceId()), device.getExpires() * 1000); | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | @Override | 
|---|
|  |  |  | 
|---|
|  |  |  |  | 
|---|
|  |  |  | @Override | 
|---|
|  |  |  | public boolean expire(Device device) { | 
|---|
|  |  |  | Date registerTimeDate; | 
|---|
|  |  |  | try { | 
|---|
|  |  |  | registerTimeDate = DateUtil.format.parse(device.getRegisterTime()); | 
|---|
|  |  |  | } catch (ParseException e) { | 
|---|
|  |  |  | logger.error("设备时间格式化失败:{}->{} ", device.getDeviceId(), device.getRegisterTime() ); | 
|---|
|  |  |  | return false; | 
|---|
|  |  |  | } | 
|---|
|  |  |  | int expires = device.getExpires(); | 
|---|
|  |  |  | Calendar calendarForExpire = Calendar.getInstance(); | 
|---|
|  |  |  | calendarForExpire.setTime(registerTimeDate); | 
|---|
|  |  |  | calendarForExpire.set(Calendar.SECOND, calendarForExpire.get(Calendar.SECOND) + expires); | 
|---|
|  |  |  | return calendarForExpire.before(DateUtil.getNow()); | 
|---|
|  |  |  | Instant registerTimeDate = Instant.from(DateUtil.formatter.parse(device.getRegisterTime())); | 
|---|
|  |  |  | Instant expireInstant = registerTimeDate.plusMillis(TimeUnit.SECONDS.toMillis(device.getExpires())); | 
|---|
|  |  |  | return expireInstant.isBefore(Instant.now()); | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | @Override | 
|---|
|  |  |  | 
|---|
|  |  |  | */ | 
|---|
|  |  |  | @Override | 
|---|
|  |  |  | public void updateVmServer(List<MediaServerItem>  mediaServerItemList) { | 
|---|
|  |  |  | logger.info("[缓存初始化] Media Server "); | 
|---|
|  |  |  | logger.info("[zlm] 缓存初始化 "); | 
|---|
|  |  |  | for (MediaServerItem mediaServerItem : mediaServerItemList) { | 
|---|
|  |  |  | if (StringUtils.isEmpty(mediaServerItem.getId())) { | 
|---|
|  |  |  | continue; | 
|---|
|  |  |  | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | @Override | 
|---|
|  |  |  | public SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, boolean ssrcCheck) { | 
|---|
|  |  |  | return openRTPServer(mediaServerItem, streamId, null, ssrcCheck,false); | 
|---|
|  |  |  | public SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, boolean ssrcCheck, boolean isPlayback) { | 
|---|
|  |  |  | return openRTPServer(mediaServerItem, streamId, null, ssrcCheck,isPlayback); | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | @Override | 
|---|
|  |  |  | 
|---|
|  |  |  | */ | 
|---|
|  |  |  | @Override | 
|---|
|  |  |  | public void zlmServerOnline(ZLMServerConfig zlmServerConfig) { | 
|---|
|  |  |  | logger.info("[ ZLM:{} ]-[ {}:{} ]正在连接", | 
|---|
|  |  |  | logger.info("[ZLM] 正在连接 : {} -> {}:{}", | 
|---|
|  |  |  | zlmServerConfig.getGeneralMediaServerId(), zlmServerConfig.getIp(), zlmServerConfig.getHttpPort()); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | MediaServerItem serverItem = mediaServerMapper.queryOne(zlmServerConfig.getGeneralMediaServerId()); | 
|---|
|  |  |  | 
|---|
|  |  |  | setZLMConfig(serverItem, "0".equals(zlmServerConfig.getHookEnable())); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | publisher.zlmOnlineEventPublish(serverItem.getId()); | 
|---|
|  |  |  | logger.info("[ ZLM:{} ]-[ {}:{} ]连接成功", | 
|---|
|  |  |  | logger.info("[ZLM] 连接成功 {} - {}:{} ", | 
|---|
|  |  |  | zlmServerConfig.getGeneralMediaServerId(), zlmServerConfig.getIp(), zlmServerConfig.getHttpPort()); | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | 
|---|
|  |  |  | */ | 
|---|
|  |  |  | @Override | 
|---|
|  |  |  | public void setZLMConfig(MediaServerItem mediaServerItem, boolean restart) { | 
|---|
|  |  |  | logger.info("[ ZLM:{} ]-[ {}:{} ]正在设置zlm", | 
|---|
|  |  |  | logger.info("[ZLM] 正在设置 :{} -> {}:{}", | 
|---|
|  |  |  | mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort()); | 
|---|
|  |  |  | String protocol = sslEnabled ? "https" : "http"; | 
|---|
|  |  |  | String hookPrex = String.format("%s://%s:%s/index/hook", protocol, mediaServerItem.getHookIp(), serverPort); | 
|---|
|  |  |  | 
|---|
|  |  |  |  | 
|---|
|  |  |  | if (responseJSON != null && responseJSON.getInteger("code") == 0) { | 
|---|
|  |  |  | if (restart) { | 
|---|
|  |  |  | logger.info("[ ZLM:{} ]-[ {}:{} ]设置zlm成功, 开始重启以保证配置生效", | 
|---|
|  |  |  | logger.info("[ZLM] 设置成功,开始重启以保证配置生效 {} -> {}:{}", | 
|---|
|  |  |  | mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort()); | 
|---|
|  |  |  | zlmresTfulUtils.restartServer(mediaServerItem); | 
|---|
|  |  |  | }else { | 
|---|
|  |  |  | logger.info("[ ZLM:{} ]-[ {}:{} ]设置zlm成功", | 
|---|
|  |  |  | logger.info("[ZLM] 设置成功 {} -> {}:{}", | 
|---|
|  |  |  | mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort()); | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  |  | 
|---|
|  |  |  | }else { | 
|---|
|  |  |  | logger.info("[ ZLM:{} ]-[ {}:{} ]设置zlm失败", | 
|---|
|  |  |  | logger.info("[ZLM] 设置zlm失败 {} -> {}:{}", | 
|---|
|  |  |  | mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort()); | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | 
|---|
|  |  |  | if (mediaServerItem.isRtpEnable()) { | 
|---|
|  |  |  | streamId = String.format("%s_%s", device.getDeviceId(), channelId); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, device.isSsrcCheck()); | 
|---|
|  |  |  | SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, device.isSsrcCheck(), false); | 
|---|
|  |  |  | play(mediaServerItem, ssrcInfo, device, channelId, (mediaServerItemInUse, response)->{ | 
|---|
|  |  |  | if (hookEvent != null) { | 
|---|
|  |  |  | hookEvent.response(mediaServerItem, response); | 
|---|
|  |  |  | 
|---|
|  |  |  | streamId = String.format("%s_%s", device.getDeviceId(), channelId); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | if (ssrcInfo == null) { | 
|---|
|  |  |  | ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, device.isSsrcCheck()); | 
|---|
|  |  |  | ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, device.isSsrcCheck(), false); | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 超时处理 | 
|---|
|  |  |  | 
|---|
|  |  |  | return null; | 
|---|
|  |  |  | } | 
|---|
|  |  |  | MediaServerItem newMediaServerItem = getNewMediaServerItem(device); | 
|---|
|  |  |  | SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, null, true); | 
|---|
|  |  |  | SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, null, true, true); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | return playBack(newMediaServerItem, ssrcInfo, deviceId, channelId, startTime, endTime, inviteStreamCallback, callback); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | 
|---|
|  |  |  | return null; | 
|---|
|  |  |  | } | 
|---|
|  |  |  | MediaServerItem newMediaServerItem = getNewMediaServerItem(device); | 
|---|
|  |  |  | SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, null, true); | 
|---|
|  |  |  | SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, null, true, true); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | return download(newMediaServerItem, ssrcInfo, deviceId, channelId, startTime, endTime, downloadSpeed,infoCallBack, hookCallBack); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | 
|---|
|  |  |  | package com.genersoft.iot.vmp.utils; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | import java.text.ParseException; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | import java.text.SimpleDateFormat; | 
|---|
|  |  |  | import java.util.Date; | 
|---|
|  |  |  | import java.time.Instant; | 
|---|
|  |  |  | import java.time.LocalDate; | 
|---|
|  |  |  | import java.time.LocalDateTime; | 
|---|
|  |  |  | import java.time.ZoneId; | 
|---|
|  |  |  | import java.time.format.DateTimeFormatter; | 
|---|
|  |  |  | import java.time.format.DateTimeParseException; | 
|---|
|  |  |  | import java.time.temporal.TemporalAccessor; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | import java.util.Locale; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | /** | 
|---|
|  |  |  | * 全局时间工具类 | 
|---|
|  |  |  | * @author swwheihei | 
|---|
|  |  |  | * @author lin | 
|---|
|  |  |  | */ | 
|---|
|  |  |  | public class DateUtil { | 
|---|
|  |  |  |  | 
|---|
|  |  |  | private static final String yyyy_MM_dd_T_HH_mm_ss_SSSXXX = "yyyy-MM-dd'T'HH:mm:ss"; | 
|---|
|  |  |  | private static final String yyyy_MM_dd_HH_mm_ss = "yyyy-MM-dd HH:mm:ss"; | 
|---|
|  |  |  | public static final String yyyy_MM_dd_HH_mm_ss = "yyyy-MM-dd HH:mm:ss"; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | public static final SimpleDateFormat formatISO8601 = new SimpleDateFormat(yyyy_MM_dd_T_HH_mm_ss_SSSXXX, Locale.getDefault()); | 
|---|
|  |  |  | public static final SimpleDateFormat format = new SimpleDateFormat(yyyy_MM_dd_HH_mm_ss, Locale.getDefault()); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | public static String yyyy_MM_dd_HH_mm_ssToISO8601(String formatTime) { | 
|---|
|  |  |  | public static final DateTimeFormatter formatterISO8601 = DateTimeFormatter.ofPattern(yyyy_MM_dd_T_HH_mm_ss_SSSXXX, Locale.getDefault()).withZone(ZoneId.systemDefault()); | 
|---|
|  |  |  | public static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(yyyy_MM_dd_HH_mm_ss, Locale.getDefault()).withZone(ZoneId.systemDefault()); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | try { | 
|---|
|  |  |  | return formatISO8601.format(format.parse(formatTime)); | 
|---|
|  |  |  | } catch (ParseException e) { | 
|---|
|  |  |  | e.printStackTrace(); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | return ""; | 
|---|
|  |  |  | public static String yyyy_MM_dd_HH_mm_ssToISO8601(String formatTime) { | 
|---|
|  |  |  | return formatterISO8601.format(formatter.parse(formatTime)); | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | public static String ISO8601Toyyyy_MM_dd_HH_mm_ss(String formatTime) { | 
|---|
|  |  |  | return formatter.format(formatterISO8601.parse(formatTime)); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | try { | 
|---|
|  |  |  | return format.format(formatISO8601.parse(formatTime)); | 
|---|
|  |  |  | } catch (ParseException e) { | 
|---|
|  |  |  | e.printStackTrace(); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | return ""; | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | public static long yyyy_MM_dd_HH_mm_ssToTimestamp(String formatTime) { | 
|---|
|  |  |  | //设置要读取的时间字符串格式 | 
|---|
|  |  |  | Date date; | 
|---|
|  |  |  | try { | 
|---|
|  |  |  | date = format.parse(formatTime); | 
|---|
|  |  |  | Long timestamp=date.getTime()/1000; | 
|---|
|  |  |  | //转换为Date类 | 
|---|
|  |  |  | return timestamp; | 
|---|
|  |  |  | } catch (ParseException e) { | 
|---|
|  |  |  | e.printStackTrace(); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | return 0; | 
|---|
|  |  |  | TemporalAccessor temporalAccessor = formatter.parse(formatTime); | 
|---|
|  |  |  | Instant instant = Instant.from(temporalAccessor); | 
|---|
|  |  |  | return instant.getEpochSecond(); | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | public static String getNow() { | 
|---|
|  |  |  | return format.format(System.currentTimeMillis()); | 
|---|
|  |  |  | LocalDateTime nowDateTime = LocalDateTime.now(); | 
|---|
|  |  |  | return formatter.format(nowDateTime); | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | public static boolean verification(String timeStr, DateTimeFormatter dateTimeFormatter) { | 
|---|
|  |  |  | try { | 
|---|
|  |  |  | LocalDate.parse(timeStr, dateTimeFormatter); | 
|---|
|  |  |  | return true; | 
|---|
|  |  |  | }catch (DateTimeParseException exception) { | 
|---|
|  |  |  | return false; | 
|---|
|  |  |  | } | 
|---|
|  |  |  | } | 
|---|
|  |  |  | } | 
|---|
|  |  |  | 
|---|
|  |  |  | import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage; | 
|---|
|  |  |  | import com.genersoft.iot.vmp.service.IMediaServerService; | 
|---|
|  |  |  | import com.genersoft.iot.vmp.service.IPlayService; | 
|---|
|  |  |  | import com.genersoft.iot.vmp.utils.DateUtil; | 
|---|
|  |  |  | import com.genersoft.iot.vmp.vmanager.bean.WVPResult; | 
|---|
|  |  |  | import io.swagger.annotations.Api; | 
|---|
|  |  |  | import io.swagger.annotations.ApiImplicitParam; | 
|---|
|  |  |  | import io.swagger.annotations.ApiImplicitParams; | 
|---|
|  |  |  | 
|---|
|  |  |  | import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; | 
|---|
|  |  |  | import com.genersoft.iot.vmp.storager.IVideoManagerStorage; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | import java.time.LocalDate; | 
|---|
|  |  |  | import java.util.UUID; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | @Api(tags = "国标录像") | 
|---|
|  |  |  | 
|---|
|  |  |  | @ApiImplicitParam(name = "endTime", value = "结束时间", dataTypeClass = String.class), | 
|---|
|  |  |  | }) | 
|---|
|  |  |  | @GetMapping("/query/{deviceId}/{channelId}") | 
|---|
|  |  |  | public DeferredResult<ResponseEntity<RecordInfo>> recordinfo(@PathVariable String deviceId,@PathVariable String channelId, String startTime,  String endTime){ | 
|---|
|  |  |  | public DeferredResult<ResponseEntity<WVPResult<RecordInfo>>> recordinfo(@PathVariable String deviceId, @PathVariable String channelId, String startTime, String endTime){ | 
|---|
|  |  |  |  | 
|---|
|  |  |  | if (logger.isDebugEnabled()) { | 
|---|
|  |  |  | logger.debug(String.format("录像信息查询 API调用,deviceId:%s ,startTime:%s, endTime:%s",deviceId, startTime, endTime)); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | DeferredResult<ResponseEntity<WVPResult<RecordInfo>>> result = new DeferredResult<>(); | 
|---|
|  |  |  | if (!DateUtil.verification(startTime, DateUtil.formatter)){ | 
|---|
|  |  |  | WVPResult<RecordInfo> wvpResult = new WVPResult<>(); | 
|---|
|  |  |  | wvpResult.setCode(-1); | 
|---|
|  |  |  | wvpResult.setMsg("startTime error, format is " + DateUtil.yyyy_MM_dd_HH_mm_ss); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | ResponseEntity<WVPResult<RecordInfo>> resultResponseEntity = new ResponseEntity<>(wvpResult, HttpStatus.OK); | 
|---|
|  |  |  | result.setResult(resultResponseEntity); | 
|---|
|  |  |  | return result; | 
|---|
|  |  |  | } | 
|---|
|  |  |  | if (!DateUtil.verification(endTime, DateUtil.formatter)){ | 
|---|
|  |  |  | WVPResult<RecordInfo> wvpResult = new WVPResult<>(); | 
|---|
|  |  |  | wvpResult.setCode(-1); | 
|---|
|  |  |  | wvpResult.setMsg("endTime error, format is " + DateUtil.yyyy_MM_dd_HH_mm_ss); | 
|---|
|  |  |  | ResponseEntity<WVPResult<RecordInfo>> resultResponseEntity = new ResponseEntity<>(wvpResult, HttpStatus.OK); | 
|---|
|  |  |  | result.setResult(resultResponseEntity); | 
|---|
|  |  |  | return result; | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | Device device = storager.queryVideoDevice(deviceId); | 
|---|
|  |  |  | // 指定超时时间 1分钟30秒 | 
|---|
|  |  |  | DeferredResult<ResponseEntity<RecordInfo>> result = new DeferredResult<>(90*1000L); | 
|---|
|  |  |  | String uuid = UUID.randomUUID().toString(); | 
|---|
|  |  |  | int sn  =  (int)((Math.random()*9+1)*100000); | 
|---|
|  |  |  | String key = DeferredResultHolder.CALLBACK_CMD_RECORDINFO + deviceId + sn; | 
|---|
|  |  |  | 
|---|
|  |  |  | msg.setId(uuid); | 
|---|
|  |  |  | msg.setKey(key); | 
|---|
|  |  |  | cmder.recordInfoQuery(device, channelId, startTime, endTime, sn, null, null, null, (eventResult -> { | 
|---|
|  |  |  | msg.setData("查询录像失败, status: " +  eventResult.statusCode + ", message: " + eventResult.msg ); | 
|---|
|  |  |  | WVPResult<RecordInfo> wvpResult = new WVPResult<>(); | 
|---|
|  |  |  | wvpResult.setCode(-1); | 
|---|
|  |  |  | wvpResult.setMsg("查询录像失败, status: " +  eventResult.statusCode + ", message: " + eventResult.msg); | 
|---|
|  |  |  | msg.setData(wvpResult); | 
|---|
|  |  |  | resultHolder.invokeResult(msg); | 
|---|
|  |  |  | })); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | 
|---|
|  |  |  | resultHolder.put(key, uuid, result); | 
|---|
|  |  |  | result.onTimeout(()->{ | 
|---|
|  |  |  | msg.setData("timeout"); | 
|---|
|  |  |  | WVPResult<RecordInfo> wvpResult = new WVPResult<>(); | 
|---|
|  |  |  | wvpResult.setCode(-1); | 
|---|
|  |  |  | wvpResult.setMsg("timeout"); | 
|---|
|  |  |  | msg.setData(wvpResult); | 
|---|
|  |  |  | resultHolder.invokeResult(msg); | 
|---|
|  |  |  | }); | 
|---|
|  |  |  | return result; | 
|---|
|  |  |  | 
|---|
|  |  |  | poolMaxIdle: 500 | 
|---|
|  |  |  | # [可选] 最大的等待时间(秒) | 
|---|
|  |  |  | poolMaxWait: 5 | 
|---|
|  |  |  | # [可选] jdbc数据库配置, 项目使用sqlite作为数据库,一般不需要配置 | 
|---|
|  |  |  | # [必选] jdbc数据库配置 | 
|---|
|  |  |  | datasource: | 
|---|
|  |  |  | type: com.alibaba.druid.pool.DruidDataSource | 
|---|
|  |  |  | driver-class-name: com.mysql.cj.jdbc.Driver | 
|---|
|  |  |  | 
|---|
|  |  |  | password: 123456 | 
|---|
|  |  |  | # [可选] 超时时间 | 
|---|
|  |  |  | timeout: 10000 | 
|---|
|  |  |  | # [可选] jdbc数据库配置, 项目使用sqlite作为数据库,一般不需要配置 | 
|---|
|  |  |  | # mysql数据源 | 
|---|
|  |  |  | datasource: | 
|---|
|  |  |  | type: com.alibaba.druid.pool.DruidDataSource | 
|---|
|  |  |  | 
|---|
|  |  |  | password: ${REDIS_PWD:root} | 
|---|
|  |  |  | # [可选] 超时时间 | 
|---|
|  |  |  | timeout: 10000 | 
|---|
|  |  |  | # [可选] jdbc数据库配置, 项目使用sqlite作为数据库,一般不需要配置 | 
|---|
|  |  |  | # [必选] jdbc数据库配置 | 
|---|
|  |  |  | datasource: | 
|---|
|  |  |  | # 使用mysql 打开23-28行注释, 删除29-36行 | 
|---|
|  |  |  | name: wvp | 
|---|
|  |  |  | 
|---|
|  |  |  | }); | 
|---|
|  |  |  | }, | 
|---|
|  |  |  | queryRecords: function (itemData) { | 
|---|
|  |  |  | var format = moment().format("YYYY-M-D"); | 
|---|
|  |  |  | var format = moment().format("yyyy-MM-DD"); | 
|---|
|  |  |  | let deviceId = this.deviceId; | 
|---|
|  |  |  | let channelId = itemData.channelId; | 
|---|
|  |  |  | this.$refs.devicePlayer.openDialog("record", deviceId, channelId, {date: format}) | 
|---|
|  |  |  | 
|---|
|  |  |  | method: 'get', | 
|---|
|  |  |  | url: '/api/gb_record/query/' + this.deviceId + '/' + this.channelId + '?startTime=' + startTime + '&endTime=' + endTime | 
|---|
|  |  |  | }).then(function (res) { | 
|---|
|  |  |  | // 处理时间信息 | 
|---|
|  |  |  | that.videoHistory.searchHistoryResult = res.data.recordList; | 
|---|
|  |  |  | that.recordsLoading = false; | 
|---|
|  |  |  | console.log(res) | 
|---|
|  |  |  | if(res.data.code === 0) { | 
|---|
|  |  |  | // 处理时间信息 | 
|---|
|  |  |  | that.videoHistory.searchHistoryResult = res.data.data.recordList; | 
|---|
|  |  |  | that.recordsLoading = false; | 
|---|
|  |  |  | }else { | 
|---|
|  |  |  | this.$message({ | 
|---|
|  |  |  | showClose: true, | 
|---|
|  |  |  | message: res.data.msg, | 
|---|
|  |  |  | type: "error", | 
|---|
|  |  |  | }); | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | }).catch(function (e) { | 
|---|
|  |  |  | console.log(e.message); | 
|---|
|  |  |  | // that.videoHistory.searchHistoryResult = falsificationData.recordData; | 
|---|
|  |  |  | 
|---|
|  |  |  | this.$axios({ | 
|---|
|  |  |  | method: 'get', | 
|---|
|  |  |  | url: `/api/playback/seek/${this.streamId }/` + Math.floor(this.seekTime * val / 100000) | 
|---|
|  |  |  | }).then(function (res) {}); | 
|---|
|  |  |  | }).then( (res)=> { | 
|---|
|  |  |  | setTimeout(()=>{ | 
|---|
|  |  |  | this.$refs.videoPlayer.play(this.videoUrl) | 
|---|
|  |  |  | }, 600) | 
|---|
|  |  |  | }); | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | } | 
|---|
|  |  |  | 
|---|
|  |  |  | isEnd: true, | 
|---|
|  |  |  | } | 
|---|
|  |  |  | }).then((res) => { | 
|---|
|  |  |  | console.log(res) | 
|---|
|  |  |  | if (res.data.code == 0) { | 
|---|
|  |  |  | this.percentage = parseFloat(res.data.data.percentage)*100 | 
|---|
|  |  |  | if (res.data.data[0].percentage === '1') { | 
|---|