Repository: 648540858/wvp-GB28181-pro Branch: master Commit: 20986f4a48fa Files: 1142 Total size: 7.3 MB Directory structure: gitextract_3q5h2l1l/ ├── .dockerignore ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug.md │ │ ├── new.md │ │ └── solve.md │ └── workflows/ │ └── build.yml ├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── bin/ │ └── wvp.sh ├── doc/ │ ├── README.md │ ├── _content/ │ │ ├── ability/ │ │ │ ├── auto_play.md │ │ │ ├── cascade.md │ │ │ ├── cascade2.md │ │ │ ├── channel.md │ │ │ ├── cloud_record.md │ │ │ ├── continuous_broadcast.md │ │ │ ├── continuous_recording.md │ │ │ ├── device.md │ │ │ ├── device_use.md │ │ │ ├── gis.md │ │ │ ├── node_manager.md │ │ │ ├── online_doc.md │ │ │ ├── proxy.md │ │ │ ├── push.md │ │ │ └── user.md │ │ ├── about_doc.md │ │ ├── broadcast.md │ │ ├── disclaimers.md │ │ ├── introduction/ │ │ │ ├── compile.md │ │ │ ├── config.md │ │ │ └── deployment.md │ │ ├── qa/ │ │ │ ├── bug.md │ │ │ ├── development.md │ │ │ ├── play_error.md │ │ │ ├── regiser_error.md │ │ │ └── start_error.md │ │ ├── skill/ │ │ │ └── tcpdump.md │ │ └── theory/ │ │ ├── broadcast_cascade.md │ │ ├── code.md │ │ ├── play.md │ │ └── register.md │ ├── _coverpage.md │ ├── _navbar.md │ ├── _sidebar.md │ ├── index.html │ └── lib/ │ ├── css/ │ │ └── vue.css │ └── js/ │ └── docsify@4.js ├── docker/ │ ├── README.md │ ├── build.sh │ ├── docker-compose.yml │ ├── docker-upgrade.sh │ ├── media/ │ │ ├── Dockerfile │ │ ├── build.sh │ │ └── config.ini │ ├── mysql/ │ │ ├── Dockerfile │ │ ├── build.sh │ │ └── db/ │ │ ├── privileges.sql │ │ └── wvp.sql │ ├── nginx/ │ │ ├── Dockerfile │ │ ├── build.sh │ │ ├── config.js │ │ └── templates/ │ │ └── nginx.conf.template │ ├── push.sh │ ├── redis/ │ │ ├── Dockerfile │ │ ├── build.sh │ │ └── conf/ │ │ └── redis.conf │ └── wvp/ │ ├── Dockerfile │ ├── build.sh │ └── wvp/ │ ├── application-base.yml │ ├── application-docker.yml │ └── application.yml ├── install.sh ├── libs/ │ ├── jdbc-aarch/ │ │ ├── kingbase8-8.6.0.jar │ │ ├── kingbase8-8.6.0.jre7.jar │ │ ├── postgresql-42.2.9.jar │ │ └── postgresql-42.2.9.jre7.jar │ └── jdbc-x86/ │ ├── bcprov-jdk15on-1.70.jar │ ├── kingbase8-8.6.0.jar │ ├── kingbase8-8.6.0.jre6.jar │ ├── kingbase8-8.6.0.jre7.jar │ ├── postgresql-42.2.9.jar │ ├── postgresql-42.2.9.jre6.jar │ └── postgresql-42.2.9.jre7.jar ├── pom.xml ├── run.sh ├── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── genersoft/ │ │ │ └── iot/ │ │ │ └── vmp/ │ │ │ ├── VManageBootstrap.java │ │ │ ├── common/ │ │ │ │ ├── CivilCodePo.java │ │ │ │ ├── CommonCallback.java │ │ │ │ ├── DeviceStatusCallback.java │ │ │ │ ├── InviteInfo.java │ │ │ │ ├── InviteSessionStatus.java │ │ │ │ ├── InviteSessionType.java │ │ │ │ ├── RemoteAddressInfo.java │ │ │ │ ├── ServerInfo.java │ │ │ │ ├── StatisticsInfo.java │ │ │ │ ├── StreamInfo.java │ │ │ │ ├── StreamURL.java │ │ │ │ ├── SubscribeCallback.java │ │ │ │ ├── SystemAllInfo.java │ │ │ │ ├── VersionPo.java │ │ │ │ ├── VideoManagerConstants.java │ │ │ │ └── enums/ │ │ │ │ ├── ChannelDataType.java │ │ │ │ ├── DeviceControlType.java │ │ │ │ └── MediaApp.java │ │ │ ├── conf/ │ │ │ │ ├── CivilCodeFileConf.java │ │ │ │ ├── CloudRecordTimer.java │ │ │ │ ├── DynamicTask.java │ │ │ │ ├── GlobalExceptionHandler.java │ │ │ │ ├── GlobalResponseAdvice.java │ │ │ │ ├── MediaConfig.java │ │ │ │ ├── MediaStatusTimerTask.java │ │ │ │ ├── MybatisConfig.java │ │ │ │ ├── ScheduleConfig.java │ │ │ │ ├── ServiceInfo.java │ │ │ │ ├── SipConfig.java │ │ │ │ ├── SpringDocConfig.java │ │ │ │ ├── SystemInfoTimerTask.java │ │ │ │ ├── ThreadPoolTaskConfig.java │ │ │ │ ├── UserSetting.java │ │ │ │ ├── VersionConfig.java │ │ │ │ ├── VersionInfo.java │ │ │ │ ├── WVPTimerTask.java │ │ │ │ ├── exception/ │ │ │ │ │ ├── ControllerException.java │ │ │ │ │ ├── ServiceException.java │ │ │ │ │ └── SsrcTransactionNotFoundException.java │ │ │ │ ├── ftpServer/ │ │ │ │ │ ├── FileCallback.java │ │ │ │ │ ├── FtpAuthority.java │ │ │ │ │ ├── FtpFileSystemFactory.java │ │ │ │ │ ├── FtpFileSystemView.java │ │ │ │ │ ├── FtpServerConfig.java │ │ │ │ │ ├── FtpSetting.java │ │ │ │ │ ├── Ftplet.java │ │ │ │ │ ├── UserManager.java │ │ │ │ │ └── VirtualFtpFile.java │ │ │ │ ├── redis/ │ │ │ │ │ ├── RedisMsgListenConfig.java │ │ │ │ │ ├── RedisRpcConfig.java │ │ │ │ │ ├── RedisTemplateConfig.java │ │ │ │ │ └── bean/ │ │ │ │ │ ├── RedisRpcClassHandler.java │ │ │ │ │ ├── RedisRpcMessage.java │ │ │ │ │ ├── RedisRpcRequest.java │ │ │ │ │ └── RedisRpcResponse.java │ │ │ │ ├── security/ │ │ │ │ │ ├── AnonymousAuthenticationEntryPoint.java │ │ │ │ │ ├── DefaultUserDetailsServiceImpl.java │ │ │ │ │ ├── JwtAuthenticationFilter.java │ │ │ │ │ ├── JwtUtils.java │ │ │ │ │ ├── LogoutHandler.java │ │ │ │ │ ├── SecurityUtils.java │ │ │ │ │ ├── WebSecurityConfig.java │ │ │ │ │ └── dto/ │ │ │ │ │ ├── JwtUser.java │ │ │ │ │ └── LoginUser.java │ │ │ │ ├── webLog/ │ │ │ │ │ ├── LogChannel.java │ │ │ │ │ └── WebSocketAppender.java │ │ │ │ └── websocket/ │ │ │ │ └── WebSocketConfig.java │ │ │ ├── gb28181/ │ │ │ │ ├── SipLayer.java │ │ │ │ ├── auth/ │ │ │ │ │ └── DigestServerAuthenticationHelper.java │ │ │ │ ├── bean/ │ │ │ │ │ ├── AlarmChannelMessage.java │ │ │ │ │ ├── AudioBroadcastCatch.java │ │ │ │ │ ├── AudioBroadcastCatchStatus.java │ │ │ │ │ ├── BaiduPoint.java │ │ │ │ │ ├── BasicParam.java │ │ │ │ │ ├── CatalogChannelEvent.java │ │ │ │ │ ├── CatalogData.java │ │ │ │ │ ├── CmdType.java │ │ │ │ │ ├── CommonGBChannel.java │ │ │ │ │ ├── CommonRecordInfo.java │ │ │ │ │ ├── Device.java │ │ │ │ │ ├── DeviceAlarm.java │ │ │ │ │ ├── DeviceAlarmMethod.java │ │ │ │ │ ├── DeviceChannel.java │ │ │ │ │ ├── DeviceChannelInPlatform.java │ │ │ │ │ ├── DeviceNotFoundEvent.java │ │ │ │ │ ├── DeviceType.java │ │ │ │ │ ├── DeviceTypeEnum.java │ │ │ │ │ ├── DragZoomParam.java │ │ │ │ │ ├── DragZoomRequest.java │ │ │ │ │ ├── DrawThinProcess.java │ │ │ │ │ ├── FrontEndCode.java │ │ │ │ │ ├── FrontEndControlCodeForAuxiliary.java │ │ │ │ │ ├── FrontEndControlCodeForFI.java │ │ │ │ │ ├── FrontEndControlCodeForPTZ.java │ │ │ │ │ ├── FrontEndControlCodeForPreset.java │ │ │ │ │ ├── FrontEndControlCodeForScan.java │ │ │ │ │ ├── FrontEndControlCodeForTour.java │ │ │ │ │ ├── FrontEndControlCodeForWiper.java │ │ │ │ │ ├── FrontEndControlType.java │ │ │ │ │ ├── GBStringMsgParser.java │ │ │ │ │ ├── Gb28181Sdp.java │ │ │ │ │ ├── GbCode.java │ │ │ │ │ ├── GbSipDate.java │ │ │ │ │ ├── GbSteamIdentification.java │ │ │ │ │ ├── GbStream.java │ │ │ │ │ ├── GbStringMsgParserFactory.java │ │ │ │ │ ├── Group.java │ │ │ │ │ ├── GroupTree.java │ │ │ │ │ ├── HandlerCatchData.java │ │ │ │ │ ├── HomePositionRequest.java │ │ │ │ │ ├── Host.java │ │ │ │ │ ├── IFrontEndControlCode.java │ │ │ │ │ ├── IndustryCodeType.java │ │ │ │ │ ├── IndustryCodeTypeEnum.java │ │ │ │ │ ├── InviteDecodeException.java │ │ │ │ │ ├── InviteMessageInfo.java │ │ │ │ │ ├── InviteStreamCallback.java │ │ │ │ │ ├── InviteStreamInfo.java │ │ │ │ │ ├── InviteStreamType.java │ │ │ │ │ ├── MessageResponseTask.java │ │ │ │ │ ├── MobilePosition.java │ │ │ │ │ ├── NetworkIdentificationType.java │ │ │ │ │ ├── NetworkIdentificationTypeEnum.java │ │ │ │ │ ├── NotifyCatalogChannel.java │ │ │ │ │ ├── OpenRTPServerResult.java │ │ │ │ │ ├── Platform.java │ │ │ │ │ ├── PlatformCatalog.java │ │ │ │ │ ├── PlatformCatch.java │ │ │ │ │ ├── PlatformChannel.java │ │ │ │ │ ├── PlatformGbStream.java │ │ │ │ │ ├── PlatformKeepaliveCallback.java │ │ │ │ │ ├── PlatformRegister.java │ │ │ │ │ ├── PlayException.java │ │ │ │ │ ├── Preset.java │ │ │ │ │ ├── RecordInfo.java │ │ │ │ │ ├── RecordItem.java │ │ │ │ │ ├── RedisGroupMessage.java │ │ │ │ │ ├── Region.java │ │ │ │ │ ├── RegionTree.java │ │ │ │ │ ├── SDPInfo.java │ │ │ │ │ ├── SendRtpInfo.java │ │ │ │ │ ├── SipMsgInfo.java │ │ │ │ │ ├── SipSendFailEvent.java │ │ │ │ │ ├── SipTransactionInfo.java │ │ │ │ │ ├── SsrcTransaction.java │ │ │ │ │ ├── SubscribeHolder.java │ │ │ │ │ ├── SubscribeInfo.java │ │ │ │ │ ├── SyncStatus.java │ │ │ │ │ ├── TalkRtpInfo.java │ │ │ │ │ └── VectorTileSource.java │ │ │ │ ├── conf/ │ │ │ │ │ ├── DefaultProperties.java │ │ │ │ │ ├── ServerLoggerImpl.java │ │ │ │ │ └── StackLoggerImpl.java │ │ │ │ ├── controller/ │ │ │ │ │ ├── AlarmController.java │ │ │ │ │ ├── ChannelController.java │ │ │ │ │ ├── ChannelFrontEndController.java │ │ │ │ │ ├── DeviceConfig.java │ │ │ │ │ ├── DeviceControl.java │ │ │ │ │ ├── DeviceQuery.java │ │ │ │ │ ├── GBRecordController.java │ │ │ │ │ ├── GroupController.java │ │ │ │ │ ├── MediaController.java │ │ │ │ │ ├── MobilePositionController.java │ │ │ │ │ ├── PlatformController.java │ │ │ │ │ ├── PlayController.java │ │ │ │ │ ├── PlaybackController.java │ │ │ │ │ ├── PtzController.java │ │ │ │ │ ├── RegionController.java │ │ │ │ │ ├── SseController.java │ │ │ │ │ └── bean/ │ │ │ │ │ ├── AudioBroadcastEvent.java │ │ │ │ │ ├── ChannelForThin.java │ │ │ │ │ ├── ChannelListForRpcParam.java │ │ │ │ │ ├── ChannelReduce.java │ │ │ │ │ ├── ChannelToGroupByGbDeviceParam.java │ │ │ │ │ ├── ChannelToGroupParam.java │ │ │ │ │ ├── ChannelToRegionByGbDeviceParam.java │ │ │ │ │ ├── ChannelToRegionParam.java │ │ │ │ │ ├── DrawThinParam.java │ │ │ │ │ ├── Extent.java │ │ │ │ │ ├── PlayResult.java │ │ │ │ │ ├── ResetParam.java │ │ │ │ │ └── UpdateChannelParam.java │ │ │ │ ├── dao/ │ │ │ │ │ ├── CommonGBChannelMapper.java │ │ │ │ │ ├── DeviceAlarmMapper.java │ │ │ │ │ ├── DeviceChannelMapper.java │ │ │ │ │ ├── DeviceMapper.java │ │ │ │ │ ├── DeviceMobilePositionMapper.java │ │ │ │ │ ├── GroupMapper.java │ │ │ │ │ ├── PlatformChannelMapper.java │ │ │ │ │ ├── PlatformMapper.java │ │ │ │ │ ├── RegionMapper.java │ │ │ │ │ └── provider/ │ │ │ │ │ ├── ChannelProvider.java │ │ │ │ │ └── DeviceChannelProvider.java │ │ │ │ ├── event/ │ │ │ │ │ ├── EventPublisher.java │ │ │ │ │ ├── MessageSubscribe.java │ │ │ │ │ ├── SipSubscribe.java │ │ │ │ │ ├── alarm/ │ │ │ │ │ │ ├── AlarmEvent.java │ │ │ │ │ │ └── AlarmEventListener.java │ │ │ │ │ ├── channel/ │ │ │ │ │ │ └── ChannelEvent.java │ │ │ │ │ ├── record/ │ │ │ │ │ │ ├── RecordInfoEndEvent.java │ │ │ │ │ │ ├── RecordInfoEvent.java │ │ │ │ │ │ └── RecordInfoEventListener.java │ │ │ │ │ ├── sip/ │ │ │ │ │ │ ├── MessageEvent.java │ │ │ │ │ │ └── SipEvent.java │ │ │ │ │ └── subscribe/ │ │ │ │ │ ├── catalog/ │ │ │ │ │ │ ├── CatalogEvent.java │ │ │ │ │ │ └── CatalogEventLister.java │ │ │ │ │ └── mobilePosition/ │ │ │ │ │ ├── MobilePositionEvent.java │ │ │ │ │ └── MobilePositionEventLister.java │ │ │ │ ├── service/ │ │ │ │ │ ├── IDeviceAlarmService.java │ │ │ │ │ ├── IDeviceChannelService.java │ │ │ │ │ ├── IDeviceService.java │ │ │ │ │ ├── IGbChannelControlService.java │ │ │ │ │ ├── IGbChannelPlayService.java │ │ │ │ │ ├── IGbChannelService.java │ │ │ │ │ ├── IGroupService.java │ │ │ │ │ ├── IInviteStreamService.java │ │ │ │ │ ├── IPTZService.java │ │ │ │ │ ├── IPlatformChannelService.java │ │ │ │ │ ├── IPlatformService.java │ │ │ │ │ ├── IPlayService.java │ │ │ │ │ ├── IRegionService.java │ │ │ │ │ ├── ISourceDownloadService.java │ │ │ │ │ ├── ISourcePTZService.java │ │ │ │ │ ├── ISourcePlayService.java │ │ │ │ │ ├── ISourcePlaybackService.java │ │ │ │ │ └── impl/ │ │ │ │ │ ├── DeviceAlarmServiceImpl.java │ │ │ │ │ ├── DeviceChannelServiceImpl.java │ │ │ │ │ ├── DeviceServiceImpl.java │ │ │ │ │ ├── GbChannelControlServiceImpl.java │ │ │ │ │ ├── GbChannelPlayServiceImpl.java │ │ │ │ │ ├── GbChannelServiceImpl.java │ │ │ │ │ ├── GroupServiceImpl.java │ │ │ │ │ ├── InviteStreamServiceImpl.java │ │ │ │ │ ├── PTZServiceImpl.java │ │ │ │ │ ├── PlatformChannelServiceImpl.java │ │ │ │ │ ├── PlatformServiceImpl.java │ │ │ │ │ ├── PlayServiceImpl.java │ │ │ │ │ ├── RegionServiceImpl.java │ │ │ │ │ ├── SourceDownloadServiceForGbImpl.java │ │ │ │ │ ├── SourcePTZServiceForGbImpl.java │ │ │ │ │ ├── SourcePlayServiceForGbImpl.java │ │ │ │ │ └── SourcePlaybackServiceForGbImpl.java │ │ │ │ ├── session/ │ │ │ │ │ ├── AudioBroadcastManager.java │ │ │ │ │ ├── CatalogDataManager.java │ │ │ │ │ ├── CommonSessionManager.java │ │ │ │ │ ├── SSRCFactory.java │ │ │ │ │ ├── SipInviteSessionManager.java │ │ │ │ │ └── SseSessionManager.java │ │ │ │ ├── task/ │ │ │ │ │ ├── deviceStatus/ │ │ │ │ │ │ ├── DeviceStatusTask.java │ │ │ │ │ │ ├── DeviceStatusTaskInfo.java │ │ │ │ │ │ └── DeviceStatusTaskRunner.java │ │ │ │ │ ├── deviceSubscribe/ │ │ │ │ │ │ ├── SubscribeTask.java │ │ │ │ │ │ ├── SubscribeTaskInfo.java │ │ │ │ │ │ ├── SubscribeTaskRunner.java │ │ │ │ │ │ └── impl/ │ │ │ │ │ │ ├── SubscribeTaskForCatalog.java │ │ │ │ │ │ └── SubscribeTaskForMobilPosition.java │ │ │ │ │ └── platformStatus/ │ │ │ │ │ ├── PlatformKeepaliveTask.java │ │ │ │ │ ├── PlatformRegisterTask.java │ │ │ │ │ ├── PlatformRegisterTaskInfo.java │ │ │ │ │ └── PlatformStatusTaskRunner.java │ │ │ │ ├── transmit/ │ │ │ │ │ ├── ISIPProcessorObserver.java │ │ │ │ │ ├── SIPProcessorObserver.java │ │ │ │ │ ├── SIPSender.java │ │ │ │ │ ├── callback/ │ │ │ │ │ │ ├── DeferredResultHolder.java │ │ │ │ │ │ └── RequestMessage.java │ │ │ │ │ ├── cmd/ │ │ │ │ │ │ ├── ISIPCommander.java │ │ │ │ │ │ ├── ISIPCommanderForPlatform.java │ │ │ │ │ │ ├── SIPRequestHeaderPlarformProvider.java │ │ │ │ │ │ ├── SIPRequestHeaderProvider.java │ │ │ │ │ │ └── impl/ │ │ │ │ │ │ ├── SIPCommander.java │ │ │ │ │ │ └── SIPCommanderForPlatform.java │ │ │ │ │ └── event/ │ │ │ │ │ ├── request/ │ │ │ │ │ │ ├── ISIPRequestProcessor.java │ │ │ │ │ │ ├── SIPRequestProcessorParent.java │ │ │ │ │ │ └── impl/ │ │ │ │ │ │ ├── AckRequestProcessor.java │ │ │ │ │ │ ├── ByeRequestProcessor.java │ │ │ │ │ │ ├── CancelRequestProcessor.java │ │ │ │ │ │ ├── InviteRequestProcessor.java │ │ │ │ │ │ ├── NotifyRequestForCatalogProcessor.java │ │ │ │ │ │ ├── NotifyRequestForMobilePositionProcessor.java │ │ │ │ │ │ ├── NotifyRequestProcessor.java │ │ │ │ │ │ ├── RegisterRequestProcessor.java │ │ │ │ │ │ ├── SubscribeRequestProcessor.java │ │ │ │ │ │ ├── info/ │ │ │ │ │ │ │ └── InfoRequestProcessor.java │ │ │ │ │ │ └── message/ │ │ │ │ │ │ ├── IMessageHandler.java │ │ │ │ │ │ ├── MessageHandlerAbstract.java │ │ │ │ │ │ ├── MessageRequestProcessor.java │ │ │ │ │ │ ├── control/ │ │ │ │ │ │ │ ├── ControlMessageHandler.java │ │ │ │ │ │ │ └── cmd/ │ │ │ │ │ │ │ └── DeviceControlQueryMessageHandler.java │ │ │ │ │ │ ├── notify/ │ │ │ │ │ │ │ ├── NotifyMessageHandler.java │ │ │ │ │ │ │ └── cmd/ │ │ │ │ │ │ │ ├── AlarmNotifyMessageHandler.java │ │ │ │ │ │ │ ├── BroadcastNotifyMessageHandler.java │ │ │ │ │ │ │ ├── KeepaliveNotifyMessageHandler.java │ │ │ │ │ │ │ ├── MediaStatusNotifyMessageHandler.java │ │ │ │ │ │ │ └── MobilePositionNotifyMessageHandler.java │ │ │ │ │ │ ├── query/ │ │ │ │ │ │ │ ├── QueryMessageHandler.java │ │ │ │ │ │ │ └── cmd/ │ │ │ │ │ │ │ ├── AlarmQueryMessageHandler.java │ │ │ │ │ │ │ ├── CatalogQueryMessageHandler.java │ │ │ │ │ │ │ ├── DeviceInfoQueryMessageHandler.java │ │ │ │ │ │ │ ├── DeviceStatusQueryMessageHandler.java │ │ │ │ │ │ │ └── RecordInfoQueryMessageHandler.java │ │ │ │ │ │ └── response/ │ │ │ │ │ │ ├── ResponseMessageHandler.java │ │ │ │ │ │ └── cmd/ │ │ │ │ │ │ ├── AlarmResponseMessageHandler.java │ │ │ │ │ │ ├── BroadcastResponseMessageHandler.java │ │ │ │ │ │ ├── CatalogResponseMessageHandler.java │ │ │ │ │ │ ├── ConfigDownloadResponseMessageHandler.java │ │ │ │ │ │ ├── DeviceInfoResponseMessageHandler.java │ │ │ │ │ │ ├── DeviceStatusResponseMessageHandler.java │ │ │ │ │ │ ├── MobilePositionResponseMessageHandler.java │ │ │ │ │ │ ├── PresetQueryResponseMessageHandler.java │ │ │ │ │ │ └── RecordInfoResponseMessageHandler.java │ │ │ │ │ └── response/ │ │ │ │ │ ├── ISIPResponseProcessor.java │ │ │ │ │ ├── SIPResponseProcessorAbstract.java │ │ │ │ │ └── impl/ │ │ │ │ │ ├── ByeResponseProcessor.java │ │ │ │ │ ├── CancelResponseProcessor.java │ │ │ │ │ ├── InviteResponseProcessor.java │ │ │ │ │ └── RegisterResponseProcessor.java │ │ │ │ └── utils/ │ │ │ │ ├── Coordtransform.java │ │ │ │ ├── MessageElement.java │ │ │ │ ├── MessageElementForCatalog.java │ │ │ │ ├── NumericUtil.java │ │ │ │ ├── SipUtils.java │ │ │ │ ├── VectorTileCatch.java │ │ │ │ └── XmlUtil.java │ │ │ ├── jt1078/ │ │ │ │ ├── annotation/ │ │ │ │ │ └── MsgId.java │ │ │ │ ├── bean/ │ │ │ │ │ ├── JTAlarmSign.java │ │ │ │ │ ├── JTAreaAttribute.java │ │ │ │ │ ├── JTAreaOrRoute.java │ │ │ │ │ ├── JTChannel.java │ │ │ │ │ ├── JTCircleArea.java │ │ │ │ │ ├── JTCommunicationModuleAttribute.java │ │ │ │ │ ├── JTConfirmationAlarmMessageType.java │ │ │ │ │ ├── JTDevice.java │ │ │ │ │ ├── JTDeviceAttribute.java │ │ │ │ │ ├── JTDeviceConfig.java │ │ │ │ │ ├── JTDeviceConnectionControl.java │ │ │ │ │ ├── JTDeviceType.java │ │ │ │ │ ├── JTDriverInformation.java │ │ │ │ │ ├── JTGnssAttribute.java │ │ │ │ │ ├── JTMediaAttribute.java │ │ │ │ │ ├── JTMediaDataInfo.java │ │ │ │ │ ├── JTMediaEventInfo.java │ │ │ │ │ ├── JTMediaStreamType.java │ │ │ │ │ ├── JTPassengerNum.java │ │ │ │ │ ├── JTPhoneBookContact.java │ │ │ │ │ ├── JTPolygonArea.java │ │ │ │ │ ├── JTPolygonPoint.java │ │ │ │ │ ├── JTPositionAdditionalInfo.java │ │ │ │ │ ├── JTPositionBaseInfo.java │ │ │ │ │ ├── JTPositionInfo.java │ │ │ │ │ ├── JTQueryMediaDataCommand.java │ │ │ │ │ ├── JTRecordDownloadCatch.java │ │ │ │ │ ├── JTRectangleArea.java │ │ │ │ │ ├── JTRoute.java │ │ │ │ │ ├── JTRouteAttribute.java │ │ │ │ │ ├── JTRoutePoint.java │ │ │ │ │ ├── JTRouteSectionAttribute.java │ │ │ │ │ ├── JTShootingCommand.java │ │ │ │ │ ├── JTStatus.java │ │ │ │ │ ├── JTTextSign.java │ │ │ │ │ ├── JTVehicleControl.java │ │ │ │ │ ├── JTVideoAlarm.java │ │ │ │ │ ├── common/ │ │ │ │ │ │ └── ConfigAttribute.java │ │ │ │ │ └── config/ │ │ │ │ │ ├── JTAlarmRecordingParam.java │ │ │ │ │ ├── JTAloneChanel.java │ │ │ │ │ ├── JTAnalyzeAlarmParam.java │ │ │ │ │ ├── JTAwakenParam.java │ │ │ │ │ ├── JTCameraTimer.java │ │ │ │ │ ├── JTChanelConfig.java │ │ │ │ │ ├── JTChannelListParam.java │ │ │ │ │ ├── JTChannelParam.java │ │ │ │ │ ├── JTCollisionAlarmParams.java │ │ │ │ │ ├── JTDeviceSubConfig.java │ │ │ │ │ ├── JTGnssPositioningMode.java │ │ │ │ │ ├── JTIllegalDrivingPeriods.java │ │ │ │ │ ├── JTOSDConfig.java │ │ │ │ │ ├── JTVideoAlarmBit.java │ │ │ │ │ └── JTVideoParam.java │ │ │ │ ├── cmd/ │ │ │ │ │ └── JT1078Template.java │ │ │ │ ├── codec/ │ │ │ │ │ ├── decode/ │ │ │ │ │ │ ├── Jt808Decoder.java │ │ │ │ │ │ └── MultiPacketManager.java │ │ │ │ │ ├── encode/ │ │ │ │ │ │ ├── Jt808Encoder.java │ │ │ │ │ │ └── Jt808EncoderCmd.java │ │ │ │ │ └── netty/ │ │ │ │ │ ├── Jt808Handler.java │ │ │ │ │ └── TcpServer.java │ │ │ │ ├── config/ │ │ │ │ │ ├── JT1078AutoConfiguration.java │ │ │ │ │ └── JT1078Config.java │ │ │ │ ├── controller/ │ │ │ │ │ ├── JT1078Controller.java │ │ │ │ │ ├── JT1078TerminalController.java │ │ │ │ │ └── bean/ │ │ │ │ │ ├── ConfirmationAlarmMessageParam.java │ │ │ │ │ ├── ConnectionControlParam.java │ │ │ │ │ ├── QueryMediaDataParam.java │ │ │ │ │ ├── SetAreaParam.java │ │ │ │ │ ├── SetConfigParam.java │ │ │ │ │ ├── SetPhoneBookParam.java │ │ │ │ │ ├── ShootingParam.java │ │ │ │ │ └── TextMessageParam.java │ │ │ │ ├── dao/ │ │ │ │ │ ├── JTChannelMapper.java │ │ │ │ │ ├── JTTerminalMapper.java │ │ │ │ │ └── provider/ │ │ │ │ │ └── JTChannelProvider.java │ │ │ │ ├── event/ │ │ │ │ │ ├── ConnectChangeEvent.java │ │ │ │ │ ├── DeviceUpdateEvent.java │ │ │ │ │ ├── FtpUploadEvent.java │ │ │ │ │ ├── JTPositionEvent.java │ │ │ │ │ └── eventListener/ │ │ │ │ │ └── ConnectChangeEventListener.java │ │ │ │ ├── proc/ │ │ │ │ │ ├── Header.java │ │ │ │ │ ├── entity/ │ │ │ │ │ │ └── Cmd.java │ │ │ │ │ ├── factory/ │ │ │ │ │ │ └── CodecFactory.java │ │ │ │ │ ├── request/ │ │ │ │ │ │ ├── J0001.java │ │ │ │ │ │ ├── J0002.java │ │ │ │ │ │ ├── J0003.java │ │ │ │ │ │ ├── J0004.java │ │ │ │ │ │ ├── J0100.java │ │ │ │ │ │ ├── J0102.java │ │ │ │ │ │ ├── J0104.java │ │ │ │ │ │ ├── J0107.java │ │ │ │ │ │ ├── J0200.java │ │ │ │ │ │ ├── J0201.java │ │ │ │ │ │ ├── J0500.java │ │ │ │ │ │ ├── J0608.java │ │ │ │ │ │ ├── J0702.java │ │ │ │ │ │ ├── J0704.java │ │ │ │ │ │ ├── J0800.java │ │ │ │ │ │ ├── J0801.java │ │ │ │ │ │ ├── J0802.java │ │ │ │ │ │ ├── J0805.java │ │ │ │ │ │ ├── J0900.java │ │ │ │ │ │ ├── J0901.java │ │ │ │ │ │ ├── J0A00.java │ │ │ │ │ │ ├── J1003.java │ │ │ │ │ │ ├── J1005.java │ │ │ │ │ │ ├── J1205.java │ │ │ │ │ │ ├── J1206.java │ │ │ │ │ │ └── Re.java │ │ │ │ │ └── response/ │ │ │ │ │ ├── J8001.java │ │ │ │ │ ├── J8100.java │ │ │ │ │ ├── J8103.java │ │ │ │ │ ├── J8104.java │ │ │ │ │ ├── J8105.java │ │ │ │ │ ├── J8106.java │ │ │ │ │ ├── J8107.java │ │ │ │ │ ├── J8201.java │ │ │ │ │ ├── J8202.java │ │ │ │ │ ├── J8203.java │ │ │ │ │ ├── J8204.java │ │ │ │ │ ├── J8300.java │ │ │ │ │ ├── J8400.java │ │ │ │ │ ├── J8401.java │ │ │ │ │ ├── J8500.java │ │ │ │ │ ├── J8600.java │ │ │ │ │ ├── J8601.java │ │ │ │ │ ├── J8602.java │ │ │ │ │ ├── J8603.java │ │ │ │ │ ├── J8604.java │ │ │ │ │ ├── J8605.java │ │ │ │ │ ├── J8606.java │ │ │ │ │ ├── J8607.java │ │ │ │ │ ├── J8608.java │ │ │ │ │ ├── J8702.java │ │ │ │ │ ├── J8801.java │ │ │ │ │ ├── J8802.java │ │ │ │ │ ├── J8803.java │ │ │ │ │ ├── J8804.java │ │ │ │ │ ├── J8805.java │ │ │ │ │ ├── J8900.java │ │ │ │ │ ├── J8A00.java │ │ │ │ │ ├── J9003.java │ │ │ │ │ ├── J9101.java │ │ │ │ │ ├── J9102.java │ │ │ │ │ ├── J9201.java │ │ │ │ │ ├── J9202.java │ │ │ │ │ ├── J9205.java │ │ │ │ │ ├── J9206.java │ │ │ │ │ ├── J9207.java │ │ │ │ │ ├── J9301.java │ │ │ │ │ ├── J9302.java │ │ │ │ │ ├── J9303.java │ │ │ │ │ ├── J9304.java │ │ │ │ │ ├── J9305.java │ │ │ │ │ ├── J9306.java │ │ │ │ │ └── Rs.java │ │ │ │ ├── service/ │ │ │ │ │ ├── Ijt1078PlayService.java │ │ │ │ │ ├── Ijt1078Service.java │ │ │ │ │ └── impl/ │ │ │ │ │ ├── SourcePTZServiceForJTImpl.java │ │ │ │ │ ├── SourcePlayServiceForJTImpl.java │ │ │ │ │ ├── SourcePlaybackServiceForJTImpl.java │ │ │ │ │ ├── jt1078PlayServiceImpl.java │ │ │ │ │ └── jt1078ServiceImpl.java │ │ │ │ ├── session/ │ │ │ │ │ ├── FtpDownloadManager.java │ │ │ │ │ ├── Session.java │ │ │ │ │ └── SessionManager.java │ │ │ │ └── util/ │ │ │ │ ├── BCDUtil.java │ │ │ │ ├── Bin.java │ │ │ │ └── ClassUtil.java │ │ │ ├── media/ │ │ │ │ ├── MediaServerConfig.java │ │ │ │ ├── abl/ │ │ │ │ │ ├── ABLHttpHookListener.java │ │ │ │ │ ├── ABLMediaNodeServerService.java │ │ │ │ │ ├── ABLMediaServerStatusManger.java │ │ │ │ │ ├── ABLRESTfulUtils.java │ │ │ │ │ ├── bean/ │ │ │ │ │ │ ├── ABLMedia.java │ │ │ │ │ │ ├── ABLRecordFile.java │ │ │ │ │ │ ├── ABLResult.java │ │ │ │ │ │ ├── ABLUrls.java │ │ │ │ │ │ ├── AblServerConfig.java │ │ │ │ │ │ ├── ConfigKeyId.java │ │ │ │ │ │ └── hook/ │ │ │ │ │ │ ├── ABLHookParam.java │ │ │ │ │ │ ├── OnPlayABLHookParam.java │ │ │ │ │ │ ├── OnPublishABLHookParam.java │ │ │ │ │ │ ├── OnRecordMp4ABLHookParam.java │ │ │ │ │ │ ├── OnRecordProgressABLHookParam.java │ │ │ │ │ │ ├── OnServerKeepaliveABLHookParam.java │ │ │ │ │ │ ├── OnServerStaredABLHookParam.java │ │ │ │ │ │ └── OnStreamArriveABLHookParam.java │ │ │ │ │ └── event/ │ │ │ │ │ ├── HookAblServerKeepaliveEvent.java │ │ │ │ │ └── HookAblServerStartEvent.java │ │ │ │ ├── bean/ │ │ │ │ │ ├── MediaInfo.java │ │ │ │ │ ├── MediaServer.java │ │ │ │ │ ├── RecordInfo.java │ │ │ │ │ └── ResultForOnPublish.java │ │ │ │ ├── event/ │ │ │ │ │ ├── hook/ │ │ │ │ │ │ ├── Hook.java │ │ │ │ │ │ ├── HookData.java │ │ │ │ │ │ ├── HookSubscribe.java │ │ │ │ │ │ └── HookType.java │ │ │ │ │ ├── media/ │ │ │ │ │ │ ├── MediaArrivalEvent.java │ │ │ │ │ │ ├── MediaDepartureEvent.java │ │ │ │ │ │ ├── MediaEvent.java │ │ │ │ │ │ ├── MediaNotFoundEvent.java │ │ │ │ │ │ ├── MediaPublishEvent.java │ │ │ │ │ │ ├── MediaRecordMp4Event.java │ │ │ │ │ │ ├── MediaRecordProcessEvent.java │ │ │ │ │ │ └── MediaRtpServerTimeoutEvent.java │ │ │ │ │ └── mediaServer/ │ │ │ │ │ ├── MediaSendRtpStoppedEvent.java │ │ │ │ │ ├── MediaServerChangeEvent.java │ │ │ │ │ ├── MediaServerDeleteEvent.java │ │ │ │ │ ├── MediaServerEventAbstract.java │ │ │ │ │ ├── MediaServerOfflineEvent.java │ │ │ │ │ ├── MediaServerOnlineEvent.java │ │ │ │ │ └── MediaServerStatusEventListener.java │ │ │ │ ├── service/ │ │ │ │ │ ├── IMediaNodeServerService.java │ │ │ │ │ ├── IMediaServerService.java │ │ │ │ │ └── impl/ │ │ │ │ │ └── MediaServerServiceImpl.java │ │ │ │ └── zlm/ │ │ │ │ ├── AssistRESTfulUtils.java │ │ │ │ ├── ZLMHttpHookListener.java │ │ │ │ ├── ZLMMediaNodeServerService.java │ │ │ │ ├── ZLMMediaServerStatusManager.java │ │ │ │ ├── ZLMRESTfulUtils.java │ │ │ │ ├── ZLMServerFactory.java │ │ │ │ ├── dto/ │ │ │ │ │ ├── ChannelOnlineEvent.java │ │ │ │ │ ├── FlagData.java │ │ │ │ │ ├── RtpServerResult.java │ │ │ │ │ ├── ServerKeepaliveData.java │ │ │ │ │ ├── SessionData.java │ │ │ │ │ ├── StreamAuthorityInfo.java │ │ │ │ │ ├── StreamProxyResult.java │ │ │ │ │ ├── ZLMResult.java │ │ │ │ │ ├── ZLMRunInfo.java │ │ │ │ │ ├── ZLMServerConfig.java │ │ │ │ │ └── hook/ │ │ │ │ │ ├── HookParam.java │ │ │ │ │ ├── HookResult.java │ │ │ │ │ ├── HookResultForOnPublish.java │ │ │ │ │ ├── OnPlayHookParam.java │ │ │ │ │ ├── OnPublishHookParam.java │ │ │ │ │ ├── OnRecordMp4HookParam.java │ │ │ │ │ ├── OnRtpServerTimeoutHookParam.java │ │ │ │ │ ├── OnSendRtpStoppedHookParam.java │ │ │ │ │ ├── OnServerKeepaliveHookParam.java │ │ │ │ │ ├── OnStreamChangedHookParam.java │ │ │ │ │ ├── OnStreamNoneReaderHookParam.java │ │ │ │ │ ├── OnStreamNotFoundHookParam.java │ │ │ │ │ └── OriginType.java │ │ │ │ └── event/ │ │ │ │ ├── HookZlmServerKeepaliveEvent.java │ │ │ │ └── HookZlmServerStartEvent.java │ │ │ ├── service/ │ │ │ │ ├── ICloudRecordService.java │ │ │ │ ├── ILogService.java │ │ │ │ ├── IMapService.java │ │ │ │ ├── IMediaService.java │ │ │ │ ├── IMobilePositionService.java │ │ │ │ ├── IReceiveRtpServerService.java │ │ │ │ ├── IRecordPlanService.java │ │ │ │ ├── IRoleService.java │ │ │ │ ├── ISendRtpServerService.java │ │ │ │ ├── IUserApiKeyService.java │ │ │ │ ├── IUserService.java │ │ │ │ ├── bean/ │ │ │ │ │ ├── CloudRecordItem.java │ │ │ │ │ ├── DownloadFileInfo.java │ │ │ │ │ ├── ErrorCallback.java │ │ │ │ │ ├── GPSMsgInfo.java │ │ │ │ │ ├── InviteErrorCode.java │ │ │ │ │ ├── InviteTimeOutCallback.java │ │ │ │ │ ├── LogFileInfo.java │ │ │ │ │ ├── MediaServerLoad.java │ │ │ │ │ ├── MessageForPushChannel.java │ │ │ │ │ ├── MessageForPushChannelResponse.java │ │ │ │ │ ├── PlayBackCallback.java │ │ │ │ │ ├── PlayBackResult.java │ │ │ │ │ ├── PushStreamStatusChangeFromRedisDto.java │ │ │ │ │ ├── RTPServerParam.java │ │ │ │ │ ├── RecordPlan.java │ │ │ │ │ ├── RecordPlanItem.java │ │ │ │ │ ├── RequestPushStreamMsg.java │ │ │ │ │ ├── RequestSendItemMsg.java │ │ │ │ │ ├── RequestStopPushStreamMsg.java │ │ │ │ │ ├── ResponseSendItemMsg.java │ │ │ │ │ ├── SSRCInfo.java │ │ │ │ │ ├── StreamPushItemFromRedis.java │ │ │ │ │ ├── ThirdPartyGB.java │ │ │ │ │ ├── WvpRedisMsg.java │ │ │ │ │ └── WvpRedisMsgCmd.java │ │ │ │ ├── impl/ │ │ │ │ │ ├── CloudRecordServiceImpl.java │ │ │ │ │ ├── LogServiceImpl.java │ │ │ │ │ ├── MediaServiceImpl.java │ │ │ │ │ ├── MobilePositionServiceImpl.java │ │ │ │ │ ├── RecordPlanServiceImpl.java │ │ │ │ │ ├── RoleServerImpl.java │ │ │ │ │ ├── RtpServerServiceImpl.java │ │ │ │ │ ├── SendRtpServerServiceImpl.java │ │ │ │ │ ├── UserApiKeyServiceImpl.java │ │ │ │ │ └── UserServiceImpl.java │ │ │ │ └── redisMsg/ │ │ │ │ ├── IRedisRpcPlayService.java │ │ │ │ ├── IRedisRpcService.java │ │ │ │ ├── RedisAlarmMsgListener.java │ │ │ │ ├── RedisCloseStreamMsgListener.java │ │ │ │ ├── RedisGpsMsgListener.java │ │ │ │ ├── RedisGroupChangeListener.java │ │ │ │ ├── RedisGroupMsgListener.java │ │ │ │ ├── RedisPushStreamListMsgListener.java │ │ │ │ ├── RedisPushStreamResponseListener.java │ │ │ │ ├── RedisPushStreamStatusMsgListener.java │ │ │ │ ├── control/ │ │ │ │ │ ├── RedisRpcChannelPlayController.java │ │ │ │ │ ├── RedisRpcCloudRecordController.java │ │ │ │ │ ├── RedisRpcDeviceController.java │ │ │ │ │ ├── RedisRpcDevicePlayController.java │ │ │ │ │ ├── RedisRpcGbDeviceController.java │ │ │ │ │ ├── RedisRpcPlatformController.java │ │ │ │ │ ├── RedisRpcSendRtpController.java │ │ │ │ │ ├── RedisRpcStreamProxyController.java │ │ │ │ │ └── RedisRpcStreamPushController.java │ │ │ │ ├── dto/ │ │ │ │ │ ├── RedisRpcController.java │ │ │ │ │ ├── RedisRpcMapping.java │ │ │ │ │ └── RpcController.java │ │ │ │ └── service/ │ │ │ │ ├── RedisRpcPlayServiceImpl.java │ │ │ │ └── RedisRpcServiceImpl.java │ │ │ ├── storager/ │ │ │ │ ├── IRedisCatchStorage.java │ │ │ │ ├── dao/ │ │ │ │ │ ├── CloudRecordServiceMapper.java │ │ │ │ │ ├── MediaServerMapper.java │ │ │ │ │ ├── RecordPlanMapper.java │ │ │ │ │ ├── RoleMapper.java │ │ │ │ │ ├── UserApiKeyMapper.java │ │ │ │ │ ├── UserMapper.java │ │ │ │ │ └── dto/ │ │ │ │ │ ├── ChannelSourceInfo.java │ │ │ │ │ ├── LogDto.java │ │ │ │ │ ├── PlatformRegisterInfo.java │ │ │ │ │ ├── RecordInfo.java │ │ │ │ │ ├── Role.java │ │ │ │ │ ├── User.java │ │ │ │ │ └── UserApiKey.java │ │ │ │ └── impl/ │ │ │ │ └── RedisCatchStorageImpl.java │ │ │ ├── streamProxy/ │ │ │ │ ├── bean/ │ │ │ │ │ ├── StreamProxy.java │ │ │ │ │ └── StreamProxyParam.java │ │ │ │ ├── controller/ │ │ │ │ │ └── StreamProxyController.java │ │ │ │ ├── dao/ │ │ │ │ │ ├── StreamProxyMapper.java │ │ │ │ │ └── provider/ │ │ │ │ │ └── StreamProxyProvider.java │ │ │ │ └── service/ │ │ │ │ ├── IStreamProxyPlayService.java │ │ │ │ ├── IStreamProxyService.java │ │ │ │ └── impl/ │ │ │ │ ├── SourcePlayServiceForStreamProxyImpl.java │ │ │ │ ├── StreamProxyPlayServiceImpl.java │ │ │ │ └── StreamProxyServiceImpl.java │ │ │ ├── streamPush/ │ │ │ │ ├── bean/ │ │ │ │ │ ├── BatchRemoveParam.java │ │ │ │ │ ├── RedisPushStreamMessage.java │ │ │ │ │ ├── StreamPush.java │ │ │ │ │ └── StreamPushExcelDto.java │ │ │ │ ├── controller/ │ │ │ │ │ └── StreamPushController.java │ │ │ │ ├── dao/ │ │ │ │ │ └── StreamPushMapper.java │ │ │ │ ├── enent/ │ │ │ │ │ └── StreamPushUploadFileHandler.java │ │ │ │ └── service/ │ │ │ │ ├── IStreamPushPlayService.java │ │ │ │ ├── IStreamPushService.java │ │ │ │ └── impl/ │ │ │ │ ├── SourcePlayServiceForStreamPushImpl.java │ │ │ │ ├── StreamPushPlayServiceImpl.java │ │ │ │ └── StreamPushServiceImpl.java │ │ │ ├── utils/ │ │ │ │ ├── CivilCodeUtil.java │ │ │ │ ├── Coordtransform.java │ │ │ │ ├── DateUtil.java │ │ │ │ ├── GitUtil.java │ │ │ │ ├── GpsUtil.java │ │ │ │ ├── HttpUtils.java │ │ │ │ ├── IpPortUtil.java │ │ │ │ ├── JsonUtil.java │ │ │ │ ├── MediaServerUtils.java │ │ │ │ ├── SSLSocketClientUtil.java │ │ │ │ ├── SpringBeanFactory.java │ │ │ │ ├── SystemInfoUtils.java │ │ │ │ ├── TileUtils.java │ │ │ │ ├── UJson.java │ │ │ │ └── redis/ │ │ │ │ ├── FastJsonRedisSerializer.java │ │ │ │ ├── RedisUtil.java │ │ │ │ └── RedisUtil2.java │ │ │ ├── vmanager/ │ │ │ │ ├── TestController.java │ │ │ │ ├── bean/ │ │ │ │ │ ├── AudioBroadcastResult.java │ │ │ │ │ ├── BatchGBStreamParam.java │ │ │ │ │ ├── DeferredResultEx.java │ │ │ │ │ ├── DeferredResultFilter.java │ │ │ │ │ ├── ErrorCode.java │ │ │ │ │ ├── MapConfig.java │ │ │ │ │ ├── MapModelIcon.java │ │ │ │ │ ├── OtherPsSendInfo.java │ │ │ │ │ ├── OtherRtpSendInfo.java │ │ │ │ │ ├── PlayTypeEnum.java │ │ │ │ │ ├── RecordFile.java │ │ │ │ │ ├── ResourceBaseInfo.java │ │ │ │ │ ├── ResourceInfo.java │ │ │ │ │ ├── SnapPath.java │ │ │ │ │ ├── StreamContent.java │ │ │ │ │ ├── SystemConfigInfo.java │ │ │ │ │ ├── TablePageInfo.java │ │ │ │ │ └── WVPResult.java │ │ │ │ ├── cloudRecord/ │ │ │ │ │ ├── CloudRecordController.java │ │ │ │ │ └── bean/ │ │ │ │ │ └── CloudRecordUrl.java │ │ │ │ ├── log/ │ │ │ │ │ └── LogController.java │ │ │ │ ├── ps/ │ │ │ │ │ └── PsController.java │ │ │ │ ├── recordPlan/ │ │ │ │ │ ├── RecordPlanController.java │ │ │ │ │ └── bean/ │ │ │ │ │ └── RecordPlanParam.java │ │ │ │ ├── rtp/ │ │ │ │ │ └── RtpController.java │ │ │ │ ├── server/ │ │ │ │ │ └── ServerController.java │ │ │ │ └── user/ │ │ │ │ ├── RoleController.java │ │ │ │ ├── UserApiKeyController.java │ │ │ │ └── UserController.java │ │ │ └── web/ │ │ │ ├── custom/ │ │ │ │ ├── CameraChannelController.java │ │ │ │ ├── bean/ │ │ │ │ │ ├── CameraChannel.java │ │ │ │ │ ├── CameraCount.java │ │ │ │ │ ├── CameraGroup.java │ │ │ │ │ ├── CameraStreamContent.java │ │ │ │ │ ├── CameraStreamInfo.java │ │ │ │ │ ├── ChannelParam.java │ │ │ │ │ ├── IdsQueryParam.java │ │ │ │ │ ├── Point.java │ │ │ │ │ ├── PolygonQueryParam.java │ │ │ │ │ └── SYMember.java │ │ │ │ ├── conf/ │ │ │ │ │ ├── CachedBodyHttpServletRequest.java │ │ │ │ │ ├── SignAuthenticationFilter.java │ │ │ │ │ └── SyTokenManager.java │ │ │ │ └── service/ │ │ │ │ ├── CameraChannelService.java │ │ │ │ └── SyServiceImpl.java │ │ │ └── gb28181/ │ │ │ ├── ApiControlController.java │ │ │ ├── ApiController.java │ │ │ ├── ApiDeviceController.java │ │ │ ├── ApiStreamController.java │ │ │ ├── AuthController.java │ │ │ └── dto/ │ │ │ └── DeviceChannelExtend.java │ │ └── resources/ │ │ ├── application.yml │ │ ├── banner.txt │ │ ├── civilCode.csv │ │ ├── index.html │ │ ├── install.sh │ │ ├── jwk.json │ │ ├── local.jks │ │ ├── logback-spring.xml │ │ └── 配置详情.yml │ └── test/ │ └── java/ │ └── com/ │ └── genersoft/ │ └── iot/ │ └── vmp/ │ └── jt1078/ │ └── JT1078ServerTest.java ├── web/ │ ├── .editorconfig │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .gitignore │ ├── .travis.yml │ ├── LICENSE │ ├── README-zh.md │ ├── README.md │ ├── babel.config.js │ ├── build/ │ │ └── index.js │ ├── jest.config.js │ ├── jsconfig.json │ ├── mock/ │ │ ├── index.js │ │ ├── mock-server.js │ │ ├── table.js │ │ ├── user.js │ │ └── utils.js │ ├── package.json │ ├── postcss.config.js │ ├── public/ │ │ ├── index.html │ │ ├── libDecoder.wasm │ │ └── static/ │ │ ├── file/ │ │ │ └── 设置电话本模板.xlsx │ │ └── js/ │ │ ├── ZLMRTCClient.js │ │ ├── config.js │ │ ├── h265web/ │ │ │ ├── h265webjs-v20221106.js │ │ │ ├── index.d.ts │ │ │ ├── index.js │ │ │ ├── missile-v20221120.wasm │ │ │ └── missile.js │ │ └── jessibuca/ │ │ ├── decoder.js │ │ ├── decoder.wasm │ │ ├── jessibuca.d.ts │ │ └── jessibuca.js │ ├── src/ │ │ ├── App.vue │ │ ├── api/ │ │ │ ├── cloudRecord.js │ │ │ ├── commonChannel.js │ │ │ ├── device.js │ │ │ ├── frontEnd.js │ │ │ ├── gbRecord.js │ │ │ ├── group.js │ │ │ ├── jtDevice.js │ │ │ ├── log.js │ │ │ ├── platform.js │ │ │ ├── play.js │ │ │ ├── playback.js │ │ │ ├── recordPlan.js │ │ │ ├── region.js │ │ │ ├── role.js │ │ │ ├── server.js │ │ │ ├── streamProxy.js │ │ │ ├── streamPush.js │ │ │ ├── table.js │ │ │ ├── user.js │ │ │ └── userApiKey.js │ │ ├── components/ │ │ │ ├── Breadcrumb/ │ │ │ │ └── index.vue │ │ │ ├── Hamburger/ │ │ │ │ └── index.vue │ │ │ └── SvgIcon/ │ │ │ └── index.vue │ │ ├── directive/ │ │ │ └── el-drag-dialog/ │ │ │ ├── drag.js │ │ │ └── index.js │ │ ├── icons/ │ │ │ ├── index.js │ │ │ └── svgo.yml │ │ ├── layout/ │ │ │ ├── components/ │ │ │ │ ├── AppMain.vue │ │ │ │ ├── Navbar.vue │ │ │ │ ├── Sidebar/ │ │ │ │ │ ├── FixiOSBug.js │ │ │ │ │ ├── Item.vue │ │ │ │ │ ├── Link.vue │ │ │ │ │ ├── Logo.vue │ │ │ │ │ ├── SidebarItem.vue │ │ │ │ │ └── index.vue │ │ │ │ ├── TagsView/ │ │ │ │ │ ├── ScrollPane.vue │ │ │ │ │ └── index.vue │ │ │ │ ├── dialog/ │ │ │ │ │ └── changePassword.vue │ │ │ │ └── index.js │ │ │ ├── index.vue │ │ │ └── mixin/ │ │ │ └── ResizeHandler.js │ │ ├── main.js │ │ ├── permission.js │ │ ├── router/ │ │ │ └── index.js │ │ ├── settings.js │ │ ├── store/ │ │ │ ├── getters.js │ │ │ ├── index.js │ │ │ └── modules/ │ │ │ ├── app.js │ │ │ ├── cloudRecord.js │ │ │ ├── commonChanel.js │ │ │ ├── device.js │ │ │ ├── frontEnd.js │ │ │ ├── gbRecord.js │ │ │ ├── group.js │ │ │ ├── jtDevice.js │ │ │ ├── log.js │ │ │ ├── platform.js │ │ │ ├── play.js │ │ │ ├── playback.js │ │ │ ├── recordPlan.js │ │ │ ├── region.js │ │ │ ├── role.js │ │ │ ├── server.js │ │ │ ├── settings.js │ │ │ ├── streamProxy.js │ │ │ ├── streamPush.js │ │ │ ├── tagsView.js │ │ │ ├── user.js │ │ │ └── userApiKeys.js │ │ ├── styles/ │ │ │ ├── element-ui.scss │ │ │ ├── iconfont.scss │ │ │ ├── index.scss │ │ │ ├── mixin.scss │ │ │ ├── sidebar.scss │ │ │ ├── transition.scss │ │ │ └── variables.scss │ │ ├── utils/ │ │ │ ├── auth.js │ │ │ ├── diff.js │ │ │ ├── get-page-title.js │ │ │ ├── index.js │ │ │ ├── request.js │ │ │ └── validate.js │ │ └── views/ │ │ ├── 404.vue │ │ ├── channel/ │ │ │ ├── edit.vue │ │ │ ├── group/ │ │ │ │ └── index.vue │ │ │ ├── index.vue │ │ │ ├── record.vue │ │ │ └── region/ │ │ │ └── index.vue │ │ ├── cloudRecord/ │ │ │ ├── cloudRecordPlayer.vue │ │ │ ├── detail.vue │ │ │ ├── index.vue │ │ │ └── playerDialog.vue │ │ ├── common/ │ │ │ ├── CommonChannelEdit.vue │ │ │ ├── DeviceTree.vue │ │ │ ├── GroupTree.vue │ │ │ ├── MapComponent.vue │ │ │ ├── MapComponent_bak.vue │ │ │ ├── RegionTree.vue │ │ │ ├── VideoTimeLine/ │ │ │ │ ├── WindowListItem.vue │ │ │ │ ├── constant.js │ │ │ │ └── index.vue │ │ │ ├── channelPlayer/ │ │ │ │ ├── chooseChannelForJt.vue │ │ │ │ ├── index.vue │ │ │ │ ├── jtDeviceEdit.vue │ │ │ │ ├── jtDevicePlayer.vue │ │ │ │ ├── ptzCruising.vue │ │ │ │ ├── ptzPreset.vue │ │ │ │ ├── ptzScan.vue │ │ │ │ ├── ptzSwitch.vue │ │ │ │ └── ptzWiper.vue │ │ │ ├── h265web.vue │ │ │ ├── jessibuca.vue │ │ │ ├── map/ │ │ │ │ ├── DragInteraction.js │ │ │ │ └── TransformLonLat.js │ │ │ ├── mediaInfo.vue │ │ │ ├── ptzCruising.vue │ │ │ ├── ptzPreset.vue │ │ │ ├── ptzScan.vue │ │ │ ├── ptzSwitch.vue │ │ │ ├── ptzWiper.vue │ │ │ ├── rtcPlayer.vue │ │ │ └── weekTimePicker.vue │ │ ├── dashboard/ │ │ │ ├── console/ │ │ │ │ ├── ConsoleCPU.vue │ │ │ │ ├── ConsoleDisk.vue │ │ │ │ ├── ConsoleMEM.vue │ │ │ │ ├── ConsoleMediaServer.vue │ │ │ │ ├── ConsoleNet.vue │ │ │ │ ├── ConsoleNodeLoad.vue │ │ │ │ └── ConsoleResource.vue │ │ │ └── index.vue │ │ ├── device/ │ │ │ ├── channel/ │ │ │ │ ├── edit.vue │ │ │ │ ├── index.vue │ │ │ │ └── record.vue │ │ │ ├── edit.vue │ │ │ ├── index.vue │ │ │ └── list.vue │ │ ├── dialog/ │ │ │ ├── GbChannelSelect.vue │ │ │ ├── GbDeviceSelect.vue │ │ │ ├── MediaServerEdit.vue │ │ │ ├── SyncChannelProgress.vue │ │ │ ├── UnusualGroupChannelSelect.vue │ │ │ ├── UnusualRegionChannelSelect.vue │ │ │ ├── addUser.vue │ │ │ ├── addUserApiKey.vue │ │ │ ├── catalogEdit.vue │ │ │ ├── changePasswordForAdmin.vue │ │ │ ├── changePushKey.vue │ │ │ ├── channelCode.vue │ │ │ ├── channelMapInfobox.vue │ │ │ ├── chooseCivilCode.vue │ │ │ ├── chooseGroup.vue │ │ │ ├── chooseTimeRange.vue │ │ │ ├── commonChannelEditDialog.vue │ │ │ ├── configInfo.vue │ │ │ ├── devicePlayer.vue │ │ │ ├── editRecordPlan.vue │ │ │ ├── groupEdit.vue │ │ │ ├── hasStreamChannel.vue │ │ │ ├── importChannel.vue │ │ │ ├── importChannelShowErrorData.vue │ │ │ ├── linkChannelRecord.vue │ │ │ ├── pushStreamEdit.vue │ │ │ ├── queryTrace.vue │ │ │ ├── recordDownload.vue │ │ │ ├── regionCode.vue │ │ │ ├── regionEdit.vue │ │ │ ├── remarkUserApiKey.vue │ │ │ ├── resetChannel.vue │ │ │ ├── shareChannel.vue │ │ │ └── shareChannelAdd.vue │ │ ├── form/ │ │ │ └── index.vue │ │ ├── jtDevice/ │ │ │ ├── channel/ │ │ │ │ ├── edit.vue │ │ │ │ ├── index.vue │ │ │ │ └── record.vue │ │ │ ├── deviceParam/ │ │ │ │ ├── alarm.vue │ │ │ │ ├── alarmSign.vue │ │ │ │ ├── awakenParam.vue │ │ │ │ ├── cameraTimer.vue │ │ │ │ ├── canCollectionParam.vue │ │ │ │ ├── carInfo.vue │ │ │ │ ├── communication.vue │ │ │ │ ├── driving.vue │ │ │ │ ├── gnssParam.vue │ │ │ │ ├── imageConfig.vue │ │ │ │ ├── index.vue │ │ │ │ ├── phoneNumber.vue │ │ │ │ ├── position.vue │ │ │ │ ├── server.vue │ │ │ │ ├── videoAlarmSign.vue │ │ │ │ └── videoParam.vue │ │ │ ├── dialog/ │ │ │ │ ├── attribute.vue │ │ │ │ ├── connectionServer.vue │ │ │ │ ├── controlDoor.vue │ │ │ │ ├── driverInfo.vue │ │ │ │ ├── jtDevicePlayer.vue │ │ │ │ ├── mediaAttribute.vue │ │ │ │ ├── phoneBook.vue │ │ │ │ ├── position.vue │ │ │ │ ├── queryMediaList.vue │ │ │ │ ├── queryMediaListDialog.vue │ │ │ │ ├── shootingNow.vue │ │ │ │ ├── telephoneCallback.vue │ │ │ │ └── textMsg.vue │ │ │ ├── edit.vue │ │ │ ├── index.vue │ │ │ └── list.vue │ │ ├── live/ │ │ │ └── index.vue │ │ ├── login/ │ │ │ └── index.vue │ │ ├── map/ │ │ │ ├── dialog/ │ │ │ │ └── drawThinProgress.vue │ │ │ ├── index.vue │ │ │ └── queryTrace.vue │ │ ├── mediaServer/ │ │ │ └── index.vue │ │ ├── operations/ │ │ │ ├── historyLog.vue │ │ │ ├── realLog.vue │ │ │ ├── showLog.vue │ │ │ └── systemInfo.vue │ │ ├── platform/ │ │ │ ├── edit.vue │ │ │ └── index.vue │ │ ├── recordPlan/ │ │ │ └── index.vue │ │ ├── streamProxy/ │ │ │ ├── edit.vue │ │ │ └── index.vue │ │ ├── streamPush/ │ │ │ ├── buildPushStreamUrl.vue │ │ │ ├── edit.vue │ │ │ └── index.vue │ │ └── user/ │ │ ├── apiKeyManager.vue │ │ └── index.vue │ ├── tests/ │ │ └── unit/ │ │ ├── .eslintrc.js │ │ ├── components/ │ │ │ ├── Breadcrumb.spec.js │ │ │ ├── Hamburger.spec.js │ │ │ └── SvgIcon.spec.js │ │ └── utils/ │ │ ├── formatTime.spec.js │ │ ├── param2Obj.spec.js │ │ ├── parseTime.spec.js │ │ └── validate.spec.js │ └── vue.config.js ├── 打包/ │ └── config/ │ └── config.ini └── 数据库/ ├── 2.6.9/ │ ├── 初始化-mysql-2.6.9.sql │ ├── 初始化-postgresql-kingbase-2.6.9.sql │ ├── 更新-mysql-2.6.9.sql │ └── 更新-postgresql-kingbase-2.6.9.sql ├── 2.7.0/ │ ├── 初始化-mysql-2.7.0.sql │ ├── 初始化-postgresql-kingbase-2.7.0.sql │ ├── 更新-mysql-2.7.0.sql │ └── 更新-postgresql-kingbase-2.7.0.sql ├── 2.7.1/ │ ├── 初始化-mysql-2.7.1.sql │ ├── 初始化-postgresql-kingbase-2.7.1.sql │ ├── 更新-mysql-2.7.1.sql │ └── 更新-postgresql-kingbase-2.7.1.sql ├── 2.7.3/ │ ├── 初始化-mysql-2.7.3.sql │ ├── 初始化-postgresql-kingbase-2.7.3.sql │ ├── 更新-mysql-2.7.1升级到2.7.3.sql │ ├── 更新-mysql-2.7.3.sql │ ├── 更新-postgresql-kingbase-2.7.1升级到2.7.3.sql │ └── 更新-postgresql-kingbase-2.7.3.sql ├── 2.7.4/ │ ├── 初始化-mysql-2.7.4.sql │ ├── 初始化-postgresql-kingbase-2.7.4.sql │ ├── 更新-mysql-2.7.4.sql │ └── 更新-postgresql-kingbase-2.7.4.sql ├── 2.7.4-h2/ │ ├── h2-data.sql │ └── h2-schema.sql └── old/ ├── 2.6.6-2.6.7更新.sql ├── 2.6.8升级2.6.9.sql ├── 2.6.8补丁更新.sql └── clean.sql ================================================ FILE CONTENTS ================================================ ================================================ FILE: .dockerignore ================================================ docker/volumes ================================================ FILE: .github/ISSUE_TEMPLATE/bug.md ================================================ --- name: "[ BUG ] " about: 关于wvp的bug,与zlm有关的建议直接在zlm的issue中提问 title: 'BUG' labels: 'wvp的bug' assignees: '' --- **环境信息:** - 1. 部署方式 wvp-pro docker / zlm(docker) + 编译wvp-pro/ wvp-prp + zlm都是编译部署/ - 2. 部署环境 windows / ubuntu/ centos ... - 3. 端口开放情况 - 4. 是否是公网部署 - 5. 是否使用https - 6. 接入设备/平台品牌 - 7. 你做过哪些尝试 - 8. 代码更新时间 - 9. 是否是4G设备接入 **描述错误** 描述下您遇到的问题 **如何复现** 有明确复现步骤的问题会很容易被解决 **截图** **抓包文件** **日志** ``` 日志内容放这里, 文件的话请直接上传 ``` ================================================ FILE: .github/ISSUE_TEMPLATE/new.md ================================================ --- name: "[ 新功能 ]" about: 新功能 title: '希望wVP实现的新功能,此功能应与你的具体业务无关' labels: '' assignees: '' --- **项目的详细需求** **这样的实现什么作用** ================================================ FILE: .github/ISSUE_TEMPLATE/solve.md ================================================ --- name: "[ 技术咨询 ] " about: 对于使用中遇到问题 title: '技术咨询' labels: '技术咨询' assignees: '' --- **环境信息:** - 1. 部署方式 wvp-pro docker / zlm(docker) + 编译wvp-pro/ wvp-prp + zlm都是编译部署/ - 2. 部署环境 windows / ubuntu/ centos ... - 3. 端口开放情况 - 4. 是否是公网部署 - 5. 是否使用https - 6. 方便的话提供下使用的设备品牌或平台 - 7. 你做过哪些尝试 - 8. 代码更新时间(旧版本请更新最新版本代码测试) **内容描述:** **截图** **抓包文件** **日志** ``` 日志内容放这里, 文件的话请直接上传 ``` ================================================ FILE: .github/workflows/build.yml ================================================ name: release-ubuntu on: push: tags: - "v*.*.*" # 触发条件是推送标签 如git tag v2.7.4 git push origin v2.7.4 jobs: build-ubuntu: runs-on: ubuntu-latest strategy: matrix: arch: [amd64] max-parallel: 1 # 最大并行数 steps: - name: Checkout uses: actions/checkout@v4 # github action运行环境 - name: Create release # 创建文件夹 run: | rm -rf release mkdir release echo ${{ github.sha }} > Release.txt cp Release.txt LICENSE release/ cat Release.txt - name: Set up JDK 21 uses: actions/setup-java@v4 with: # Eclipse基金会维护的开源Java发行版 因为github action参考java的用这个 所以用这个 # 还有microsoft(微软维护的openjdk发行版) oracle(商用SDK)等 distribution: 'temurin' java-version: '21' - name: Set up Node.js uses: actions/setup-node@v4 with: node-version: '20.x' # Node.js版本 20系列的最新稳定版 - name: clean maven cache run: rm -rf ~/.m2/repository/io/github/git-commit-id - name: Build Backend run: | mvn clean package - name: Compile frontend run: | cd ./web npm install npm run build:prod cd ../ - name: Package Files run: | cp -r ./src/main/resources/static release/ # 复制前端文件 cp ./target/*.jar release/ # 复制 JAR 文件 cp ./src/main/resources/application-dev.yml release/application.yml BRANCH=${{ github.event.base_ref }} BRANCH_NAME=$(echo "$BRANCH" | grep -oP 'refs/heads/\K.*') echo "BRANCH_NAME= ${BRANCH_NAME}" # 如果无法获取,使用默认分支 if [[ -z "BRANCH_NAME" ]]; then BRANCH_NAME="${{ github.event.repository.default_branch }}" fi TAG_NAME="${GITHUB_REF#refs/tags/}" ZIP_FILE_NAME="${BRANCH_NAME}-${TAG_NAME}.zip" zip -r "$ZIP_FILE_NAME" release echo "ZIP_FILE_NAME=$ZIP_FILE_NAME" >> $GITHUB_ENV - name: Release uses: softprops/action-gh-release@v2 if: startsWith(github.ref, 'refs/tags/') with: files: ${{ env.ZIP_FILE_NAME }} ================================================ FILE: .gitignore ================================================ # Compiled class file *.class # Log file *.log logs/* # BlueJ files *.ctxt # Mobile Tools for Java (J2ME) .mtj.tmp/ src/main/resources/application-*.yml # Package Files # #*.jar *.war *.nar *.ear *.zip *.tar.gz *.rar *.iml # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* /.idea/* /target/* /.idea/ /target/ /src/main/resources/static/ certificates /.vs /docker/volumes /docker/wvp/config/jwk.json ================================================ FILE: .gitmodules ================================================ [submodule "be.teletask.onvif-java"] path = be.teletask.onvif-java url = https://gitee.com/pan648540858/be.teletask.onvif-java.git ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2020 swwhaha Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ ![logo](doc/_media/logo.png) # 开箱即用的国标28181和部标808+1078协议视频平台 [![Build Status](https://travis-ci.org/xia-chu/ZLMediaKit.svg?branch=master)](https://travis-ci.org/xia-chu/ZLMediaKit) [![license](http://img.shields.io/badge/license-MIT-green.svg)](https://github.com/xia-chu/ZLMediaKit/blob/master/LICENSE) [![JAVA](https://img.shields.io/badge/language-java-red.svg)](https://en.cppreference.com/) [![platform](https://img.shields.io/badge/platform-linux%20|%20macos%20|%20windows-blue.svg)](https://github.com/xia-chu/ZLMediaKit) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-yellow.svg)](https://github.com/xia-chu/ZLMediaKit/pulls) WEB VIDEO PLATFORM是一个基于GB28181-2016、部标808、部标1078标准实现的开箱即用的网络视频平台,负责实现核心信令与设备管理后台部分,支持NAT穿透,支持海康、大华、宇视等品牌的IPC、NVR接入。支持国标级联,支持将不带国标功能的摄像机/直播流/直播推流转发到其他国标平台。 流媒体服务基于@夏楚 ZLMediaKit [https://github.com/ZLMediaKit/ZLMediaKit](https://github.com/ZLMediaKit/ZLMediaKit) 播放器使用@dexter jessibuca [https://github.com/langhuihui/jessibuca/tree/v3](https://github.com/langhuihui/jessibuca/tree/v3) 播放器使用@Numberwolf-Yanlong h265web.js [https://github.com/numberwolf/h265web.js](https://github.com/numberwolf/h265web.js) 前端页面基于vue-admin-template构建 [https://github.com/PanJiaChen/vue-admin-template?tab=readme-ov-file](https://github.com/PanJiaChen/vue-admin-template?tab=readme-ov-file) # 应用场景: - 支持浏览器无插件播放摄像头视频。 - 支持国标设备(摄像机、平台、NVR等)设备接入 - 支持rtsp, rtmp,直播设备设备接入,充分利旧。 - 支持国标级联。多平台级联。跨网视频预览。 - 支持跨网网闸平台互联。 # 文档 wvp使用文档 [https://doc.wvp-pro.cn](https://doc.wvp-pro.cn) ZLM使用文档 [https://github.com/ZLMediaKit/ZLMediaKit](https://github.com/ZLMediaKit/ZLMediaKit) # gitee仓库 https://gitee.com/pan648540858/wvp-GB28181-pro.git # 截图
登录页面
首页
分屏播放
国标设备列表
行政区划管理
业务分组管理
录制计划
平台信息
# 功能特性 - [X] 集成web界面 - [X] 兼容性良好 - [X] 跨平台服务,一次编译多端部署, 可以同时用于x86和arm架构 - [X] 接入设备 - [X] 视频预览 - [X] 支持主码流子码流切换 - [X] 无限制接入路数,能接入多少设备只取决于你的服务器性能 - [X] 云台控制,控制设备转向,拉近,拉远 - [X] 预置位查询,使用与设置 - [X] 查询NVR/IPC上的录像与播放,支持指定时间播放与下载 - [X] 无人观看自动断流,节省流量 - [X] 视频设备信息同步 - [X] 离在线监控 - [X] 支持直接输出RTSP、RTMP、HTTP-FLV、Websocket-FLV、HLS多种协议流地址 - [X] 支持通过一个流地址直接观看摄像头,无需登录以及调用任何接口 - [X] 支持UDP和TCP两种国标信令传输模式 - [X] 支持UDP和TCP两种国标流传输模式 - [X] 支持检索,通道筛选 - [X] 支持通道子目录查询 - [X] 支持过滤音频,防止杂音影响观看 - [X] 支持国标网络校时 - [X] 支持播放H264和H265 - [X] 报警信息处理,支持向前端推送报警信息 - [X] 语音对讲 - [X] 支持业务分组和行政区划树自定义展示以及级联推送 - [X] 支持订阅与通知方法 - [X] 移动位置订阅 - [X] 移动位置通知处理 - [X] 报警事件订阅 - [X] 报警事件通知处理 - [X] 设备目录订阅 - [X] 设备目录通知处理 - [X] 移动位置查询和显示 - [X] 支持手动添加设备和给设备设置单独的密码 - [X] 支持平台对接接入 - [X] 支持国标级联 - [X] 国标通道向上级联 - [X] WEB添加上级平台 - [X] 注册 - [X] 心跳保活 - [X] 通道选择 - [X] 支持通道编号自定义, 支持每个平台使用不同的通道编号 - [X] 通道推送 - [X] 点播 - [X] 云台控制 - [X] 平台状态查询 - [X] 平台信息查询 - [X] 平台远程启动 - [X] 每个级联平台可自定义的虚拟目录 - [X] 目录订阅与通知 - [X] 录像查看与播放 - [X] GPS订阅与通知(直播推流) - [X] 语音对讲 - [X] 支持同时级联到多个上级平台 - [X] 支持自动配置ZLM媒体服务, 减少因配置问题所出现的问题; - [X] 支持流媒体节点集群,负载均衡。 - [X] 支持启用udp多端口模式, 提高udp模式下媒体传输性能; - [X] 支持公网部署; - [X] 支持wvp与zlm分开部署,提升平台并发能力 - [X] 支持拉流RTSP/RTMP,分发为各种流格式,或者推送到其他国标平台 - [X] 支持推流RTSP/RTMP,分发为各种流格式,或者推送到其他国标平台 - [X] 支持推流鉴权 - [X] 支持接口鉴权 - [X] 云端录像,推流/代理/国标视频均可以录制在云端服务器,支持预览和下载 - [X] 支持打包可执行jar和war - [X] 支持跨域请求,支持前后端分离部署 - [X] 支持Mysql,Postgresql,金仓等数据库 - [X] 支持录制计划, 根据设定的时间对通道进行录制. 暂不支持将录制的内容转发到国标上级 - [X] 支持国标信令集群 - [X] 新增支持部标808和部标1078,大量新特性不一一列表了。支持作为网关被国标上级调用部标设备 - [X] 支持电子地图。支持展示通道位置,支持在地图上修改通道位置。支持了数据分层抽稀数据能力,百万级数据也可以轻松展示。提供标准的矢量瓦片图层,常见地图引擎都可以直接展示。 - [X] 借用zlm闭源版本新能力,可以支持录像保存至s3存储,支持minio。 # 闭源内容 - [X] 国标增强版: 支持国标28181-2022协议,支持巡航轨迹查询,PTZ精准控制,存储卡格式化,设备软件升级,OSD配置,h265+aac,支持辅码流,录像倒放等。 - [X] 全功能版: - [X] 支持开源所有功能 - [X] ONVIF协议 - 设备检索 - 实时图像预览 - 录像回放、回放倍速控制 - 云台控制、预置位控制、云台绝对定位、看守位 - 聚焦控制 - 设备重启 - 设备时间设置以及跟系统时间的差值比较 - 恢复出厂设置 - 自动获取设备品牌等信息、支持展示DNS信息、支持协议的展示 - 国标级联点播、自动点播等。 - [X] 国网B接口协议 - 设备注册 - 资源获取 - 预览 - 云台控制 - 预置位控制等, - 可免费定制支持语音对讲、录像回放和抓拍图像。 - [X] 支持按权限分配可以使用的通道 - [X] 支持表格导出 - [X] 拉流代理支持按照品牌拼接url。 - [X] 播放鉴权,更加安全。 # 授权协议 本项目自有代码使用宽松的MIT协议,在保留版权信息的情况下可以自由应用于各自商用、非商业的项目。 但是本项目也零碎的使用了一些其他的开源代码,在商用的情况下请自行替代或剔除; 由于使用本项目而产生的商业纠纷或侵权行为一概与本项目及开发者无关,请自行承担法律风险。 在使用本项目代码时,也应该在授权协议中同时表明本项目依赖的第三方库的协议 # 技术支持 # 付费社群 > 付费社群即可以对作者提供支持,也可以为大家更加快速的解决问题。如果暂时无法加入,给项目点个星也是极大的鼓励。 [知识星球](https://t.zsxq.com/0d8VAD3Dm)专栏列表:, - [使用入门系列一:WVP-PRO能做什么](https://t.zsxq.com/0dLguVoSp) 有偿技术支持,一对一开发辅导,闭源内容合作请发送邮件到648540858@qq.com咨询 # 致谢 感谢作者[夏楚](https://github.com/xia-chu) 提供这么棒的开源流媒体服务框架,并在开发过程中给予支持与帮助。 感谢作者[dexter langhuihui](https://github.com/langhuihui)和[Numberwolf-Yanlong](https://github.com/numberwolf/h265web.js) 开源这么好用的WEB播放器。 感谢各位大佬的赞助以及对项目的指正与帮助。包括但不限于代码贡献、问题反馈、资金捐赠等各种方式的支持!以下排名不分先后: [lawrencehj](https://github.com/lawrencehj) [Smallwhitepig](https://github.com/Smallwhitepig) [swwhaha](https://github.com/swwheihei) [hotcoffie](https://github.com/hotcoffie) [xiaomu](https://github.com/nikmu) [TristingChen](https://github.com/TristingChen) [chenparty](https://github.com/chenparty) [Hotleave](https://github.com/hotleave) [ydwxb](https://github.com/ydwxb) [ydpd](https://github.com/ydpd) [szy833](https://github.com/szy833) [ydwxb](https://github.com/ydwxb) [Albertzhu666](https://github.com/Albertzhu666) [mk1990](https://github.com/mk1990) [SaltFish001](https://github.com/SaltFish001) ================================================ FILE: bin/wvp.sh ================================================ #!/bin/bash RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[0;33m' BLUE='\033[0;34m' NC='\033[0m' function log() { message="[Polaris Log]: $1 " case "$1" in *"Fail"* | *"Error"* | *"请使用 root 或 sudo 权限运行此脚本"*) echo -e "${RED}${message}${NC}" 2>&1 | tee -a ;; *"Success"*) echo -e "${GREEN}${message}${NC}" 2>&1 | tee -a ;; *"Ignore"* | *"Jump"*) echo -e "${YELLOW}${message}${NC}" 2>&1 | tee -a ;; *) echo -e "${BLUE}${message}${NC}" 2>&1 | tee -a ;; esac } echo cat < 开箱即用的28181协议视频平台 # 概述 - WVP-PRO基于GB/T 28181-2016标准实现的流媒体平台,依托优秀的开源流媒体服务[ZLMediaKit](https://github.com/ZLMediaKit/ZLMediaKit) ,提供完善丰富的功能。 - GB/T 28181-2016 中文标准名称是《公共安全视频监控联网系统信息传输、交换、控制技术要求》是监控领域的国家标准。大量应用于政府视频平台。 - 通过28181协议你可以将IPC摄像头接入平台,可以观看也可以使用28181/rtsp/rtmp/flv等协议将视频流分发到其他平台。 # 特性 - 实现标准的28181信令,兼容常见的品牌设备,比如海康、大华、宇视等品牌的IPC、NVR以及平台。 - 支持将国标设备级联到其他国标平台,也支持将不支持国标的设备的图像或者直播推送到其他国标平台 - 前端完善,自带完整前端页面,无需二次开发可直接部署使用。 - 完全开源,且使用MIT许可协议。保留版权的情况下可以用于商业项目。 - 支持多流媒体节点负载均衡。 # 付费社群 [![社群](_media/shequ.png "shequ")](https://t.zsxq.com/0d8VAD3Dm) > 收费是为了提供更好的服务,也是对作者更大的激励。加入星球的用户三天后可以私信我留下微信号,我会拉大家入群。加入三天内不满意可以直接退款,大家不需要有顾虑,来白嫖三天也不是不可以。 # 我们实现了哪些国标功能 **作为上级平台** - [X] 注册 - [X] 注销 - [X] 实时视音频点播 - [X] 设备控制 - [X] 云台控制 - [X] 远程启动 - [X] 录像控制 - [X] 报警布防/撤防 - [X] 报警复位 - [X] 强制关键帧 - [X] 拉框放大 - [X] 拉框缩小 - [X] 看守位控制 - [X] 设备配置 - [X] 报警事件通知和分发 - [X] 设备目录订阅 - [X] 网络设备信息查询 - [X] 设备目录查询 - [X] 设备状态查询 - [X] 设备配置查询 - [X] 设备预置位查询 - [X] 状态信息报送 - [X] 设备视音频文件检索 - [X] 历史视音频的回放 - [X] 播放 - [X] 暂停 - [X] 进/退 - [X] 停止 - [X] 视音频文件下载 - [X] 校时 - [X] 订阅和通知 - [X] 事件订阅 - [X] 移动设备位置订阅 - [X] 报警订阅 - [X] 目录订阅 - [X] 语音广播 - [X] 语音喊话 **国标级联** - [X] 注册 - [X] 注销 - [X] 实时视音频点播 - [X] 设备控制 - [X] 云台控制 - [ ] 远程启动 - [X] 录像控制 - [X] 报警布防/撤防 - [X] 报警复位 - [X] 强制关键帧 - [X] 拉框放大 - [X] 拉框缩小 - [X] 看守位控制 - [ ] 设备配置 - [ ] 报警事件通知和分发 - [X] 设备目录订阅 - [X] 网络设备信息查询 - [X] 设备目录查询 - [X] 设备状态查询 - [ ] 设备配置查询 - [X] 设备预置位查询 - [X] 状态信息报送 - [X] 设备视音频文件检索 - [X] 历史视音频的回放 - [X] 播放 - [x] 暂停 - [x] 进/退 - [x] 停止 - [X] 视音频文件下载 - [ ] ~~校时~~ - [X] 订阅和通知 - [X] 事件订阅 - [X] 移动设备位置订阅 - [ ] 报警订阅 - [X] 目录订阅 - [X] 语音广播 - [X] 语音喊话 **闭源版本** - [X] 国标28181-2022 相比28181-2016标准,28181-2022标准新增了以下功能 - [X] 云台控制精准位置查询和控制,支持设置具体的水平角度、垂直角度和缩放倍数,支持订阅和通知。 - [X] 支持标准的H265视频编码和AAC音频编码格式 - [X] 支持主辅码流 - [X] 支持图像抓拍,无需拉流即可获取当前图像,推理获取图片友好。 - [X] 支持GB18030编码,不输UTF-8的编码格式,2016中使用的GB2312编码的升级,基本告别中文乱码了 - [X] 支持存储卡格式化 - [X] 支持设备软件升级 - [X] 支持OSD配置 - [X] 支持录像倒放功能 - [X] 支持目标跟踪控制 - [X] 支持视频参数属性配置 - [X] 支持视频遮挡配置 - [X] 支持设备软件升级 # 社区 代码目前托管在GitHub和Gitee,Gitee目前作为加速仓库使用,不接受issue。 GitHub: [https://github.com/648540858/wvp-GB28181-pro](https://github.com/648540858/wvp-GB28181-pro) Gitee: [https://gitee.com/pan648540858/wvp-GB28181-pro](https://gitee.com/pan648540858/wvp-GB28181-pro) ================================================ FILE: doc/_content/ability/auto_play.md ================================================ # 自动点播 ================================================ FILE: doc/_content/ability/cascade.md ================================================ # 国标级联的使用 国标28181不同平台之间支持两种连接方式,平级和上下级,WVP目前支持向上级级联。 ## 1 接入平台 ### 1.1 wvp-pro #### 1.1.1 wvp-pro管理页面点击添加 ![cascade1](_media/cascade1.png) #### 1.1.2 填入wvp-pro上级平台信息 ![cascade1](_media/img_4.png) ![cascade1](_media/img_5.png) #### 1.1.3 编辑wvp-pro上级设备信息,开启订阅 ![cascade1](_media/img_6.png) ### 1.2 大华平台 ### 1.3 海康平台 ### 1.4 liveGBS #### 1.4.1. wvp-pro管理页面点击添加 ![添加](_media/cascade1.png) #### 1.4.2. 填入liveGBS平台信息 ![填入liveGBS平台信息1](_media/cascade2.png) ![填入liveGBS平台信息2](_media/cascade3.png) #### 1.4.3. 编辑liveGBS设备信息,开启目录订阅 ![cascade1](_media/cascade4.png) #### 1.4.4. 编辑liveGBS设备信息,开启GPS订阅 ![cascade1](_media/img_7.png) ## 2 添加目录与通道 1. 级联平台添加目录信息 ![cascade1](_media/img_1.png) 2. 为目录添加通道 ![cascade1](_media/img_2.png) 3. 设置默认流目录 如果需要后续自动生成的流信息都在某一个节点下,可以在对应节点右键设置为默认 ![cascade1](_media/img_3.png) ================================================ FILE: doc/_content/ability/cascade2.md ================================================ # 国标级联的使用 国标28181不同平台之间支持两种连接方式,平级和上下级,WVP目前支持向上级级联。 ## 添加上级平台 在国标级联页面点击“添加”按钮,以推送到上级WVP为例子,参看[接入设备](./_content/ability/device.md) ![cascade17](_media/img_17.png) 1. 名称 上级平台看到的下级平台名称; 2. 本地IP 本地连接上级使用的具体哪个网卡; 3. SIP认证用户名 可以设置为与"设备国标编号"一致; 4. 注册周期 间隔多久发起一次注册,单位秒; 5. 心跳周期 间隔多久发送一次心跳,一般上级平台三次收不到心跳就会认为下级离线了, 所以建议{心跳周期}x3 < 注册周期; 6. SDP发流IP 调用媒体节点发送视频流给上级时,使用的本地IP; 7. 信令传输 信令传输模式,支持udp和TCP,没有特殊需求,默认UDP即可; 8. 目录分组 上级发送"CATALOG"消息查询通道信息,每一条消息中携带几条通道信息,默认为1,增大该值,可以加快通道发送速度; 9. 字符集 发送给上级"MESSAGE"消息中的消息体使用的编码格式,国标28181-2016默认为GB2312; 10. 行政区划 如果勾选"其他选项/推送平台信息"选项,会给上级推送平台信息,这里就是平台的行政区划信息 11. 平台厂商 如果勾选"其他选项/推送平台信息"选项,会给上级推送平台信息,这里就是平台的平台厂商信息 12. 平台型号 如果勾选"其他选项/推送平台信息"选项,会给上级推送平台信息,这里就是平台的平台型号信息 13. 平台安装地址 如果勾选"其他选项/推送平台信息"选项,会给上级推送平台信息,这里就是平台的平台安装地址信息 14. 其他选项 - RTCP保活 在上级的流传输模式为UDP时,因为UDP的无状态特性,会无法知道上级是否在正常收流,启用RTCP保活时,就可以主动发送RTCP消息确认上级是否在正常收流, 异常情况下,可以下级主动停止发流; - 消息通道 支持通过报警消息给上级级WVP推送消息,消息内容由redis消息发送给wvp,wvp编辑成报警消息发送给上级; - 主动推送通道 WVP模拟一条目录订阅信息,然后在共享通道变化时,发送CATAOLOG事件给上级,通知具体的通道变化, 目前支持的状态有: 状态改变事件 ON:上线,OFF:离线,VLOST:视频丢失,DEFECT:故障,ADD:增加,DEL:删除,UPDATE:更新; - 推送平台信息 勾选此项,上级收到的通道信息中会多出一个平台信息的通道.内容在平台的编辑中修改; - 推送分组信息 勾选此项,如果你共享的通道分配了具体的业务分组以及虚拟组织,那么上级收到的通道中会包括业务分组以及虚拟组织节点信息; - 推送行政区划 勾选此项,如果你共享的通道分配了具体的行政区划,那么上级收到的通道中会包括行政区划信息; 国标级联列表出现了级联的这个平台;同时状态显示为在线,如果状态为离线那么可能是你的服务信息配置有误或者网络不通。 订阅信息列有三个图标,表示上级开启订阅,从左到右依次是:报警订阅,目录订阅,移动位置订阅。 ## 通道共享 点击你要推送的平台的“通道共享”按钮。 ![cascade18](_media/img_18.png) 1. 添加状态选择"未共享"可以将具体的通道共享给上级; 2. 添加状态选择"已共享"可以看到已经共享的通道,并且支持为这个通道在这个平台设备专门的名称和编号; 3. 点击"按设备添加"可以将某个国标设备下的所有通道共享给上级; 4. 点击"按设备移除"可以将某个国标设备下的所有通道取消共享给上级; 5. 点击"全部添加"可以将所有通道共享给上级; 6. 点击"全部移除"可以将所有通道共享给上级; ## 推送通道 WVP会将所有通道信息按照目录订阅消息通知形式,发送ADD事件给上级. ================================================ FILE: doc/_content/ability/channel.md ================================================ # 通道管理 通道管理为了对已经分配国标编号的通道进行统一的行政区划和业务分组管理,国标中对于组织结构有两种表示方式,一种是按照行政区划,一种是业务分组+虚拟组织的方式. 行政区划结构固定,比如: 北京/市辖区/昌平区, 通道可以挂载道何一级行政区划下. 业务分组比较灵活, 可以按照自己的随意取名, 但是通道只能放在业务分组下的虚拟组织里,不能放在业务分组下. ## 行政区划 左侧树结构为行政区划结构, 通过数据鼠标右键可以操作,包括: 刷新节点,新建节点,编辑节点,删除节点,添加设备( 可以将某个国标设备下的通道全部添加道某一个节点下),移除设备(可以将某个国标设备下的通道全部从这个节点移除) 右侧伪通道列表, 对于非国标接入的设备只有配置了国标编号后才可以在这里进行操作。 选择左侧的节点后,可以点击右侧的“添加通道”, 选择需要的通道添加到改节点下,如果找不到通道, 可以选择“异常挂载通道”,点击清理后重新回来选择。 ![行政区划](_media/img_21.png) ## 业务分组 左侧树结构为业务分组结构, 通过数据鼠标右键可以操作,包括: 刷新节点,新建节点,编辑节点,删除节点,添加设备( 可以将某个国标设备下的通道全部添加道某一个节点下),移除设备(可以将某个国标设备下的通道全部从这个节点移除) 业务分组下不能挂载设备,所以没有选择该节点的单选框. 右侧为通道列表, 对于非国标接入的设备只有配置了国标编号后才可以在这里进行操作。 选择左侧的节点后,可以点击右侧的“添加通道”, 选择需要的通道添加到改节点下。 如果找不到通道, 可以选择“异常挂载通道”,点击清理后重新回来选择。 注意,根资源组下的那一级为业务分组类型不可以直接挂载设备,需要继续建立节点,后续的节点的都是虚拟组织类型, 就可以挂载通道了。 ![业务分组](_media/img_22.png) ================================================ FILE: doc/_content/ability/cloud_record.md ================================================ # 云端录像 ![云端录像](_media/img_26.png) 云端录像是对录制在zlm服务下的录像文件的管理,录像的文件路径默认在ZLM/www/record下。 - 国标设备是否录像: 可以再WVP的配置中user-settings.record-sip设置为true那么每次点播以及录像回放都会录像; - 推流设备是否录像: 可以再WVP的配置中user-settings.record-push-live设置为true; - 拉流代理的是否录像: 在添加和编辑拉流代理时可以指定, 每次点播都会进行录像 - 录像文件存储路径配置: 可以修改media.record-path来修改录像路径,但是如果有旧的录像文件,请不要迁移,因为数据库记录了每一个录像的绝对路径,一旦修改会造成找到文件,无法定时移除以及播放 - 录像保存时间: 可以修改media.record-day来修改录像保存时间,单位是天; ================================================ FILE: doc/_content/ability/continuous_broadcast.md ================================================ # 语音对讲 ## 流程和原理 语音对讲在国标28181-2016中分为broadcast(广播)和talk(对讲)两种模式,broadcast模式是从服务端把音频传送到设备端,是单向的, 需要结合点播视频来实现双向对讲,talk模式支持双向,不过wvp只处理了和broadcast一样的把音频传递设备,这样两种模式可以使用一样的逻辑处理即可。 不同的设备对于两种模式的支持不同且通常差异很大,不同的设备对同一个设备的支持也有一些不同,所以语音对讲中的兼容和适配也是问题最多的。talk模式因为在国标28181-2022中已经移除,所以这里不再讨论它了。 ### 1. broadcast模式流程 ```plantuml @startuml "WVP-PRO" -> "设备": 语音广播通知 "WVP-PRO" <-- "设备": 200OK "WVP-PRO" <- "设备": 语音广播应答 "WVP-PRO" --> "设备": 200OK "WVP-PRO" <- "设备": Invite "WVP-PRO" --> "设备": 200OK(携带SDP消息体) "WVP-PRO" <-- "设备": ACK "ZLMediaKit" -> "设备": 向设备发送语音流 @enduml ``` 与点播的流程不同的是,这里的invite消息是由设备发送给wvp的,wvp按照invite协商的方式给设备推送语音流,所有对讲的使用那种方式(UDP/TCP被动/TCP主动)传输语音流由设备决定 ## 使用条件与限制 因为invite消息是由设备发送给wvp的,这决定了发送语音流的方式,这也就决定了有的设备不能用于公网对讲,比如大部分的海康设备只支持udp方式收流( 目前新版的海康设备已经在着手解决这个问题),那么wvp发流时只能按照sdp中指定的ip端口发流,所以如果wvp在公网,设备在内网中,那么wvp无法连接设备提供的IP,发流也就失败了。 与海康不同的,大华以及很多执法记录仪厂商是支持tcp主动方式取流的,这样是可以实现公网对讲的。 ## 使用ffmpeg快速测试 由于浏览器对于音频的采集需要网页支持https才可以,所以如果想要实现网页音频对讲,那么你必须给wvp和zlm配置证书以使用https。 测试阶段如果只是想测试功能可以用ffmpeg来模拟语音流,推送到wvp后可以实现把音频文件推送到摄像头。 测试命令格式如下: ```shell ffmpeg -re -i {音频文件} -acodec pcm_alaw -ar 8000 -ac 1 -f rtsp 'rtsp://{zlm的IP}:{zlm的RTSP端口}/broadcast/{设备国标编号}_{通道国标编号}?sign={md5(pushKey)}' ``` 例如 ```shell ffmpeg -re -i test.mp3 -acodec pcm_alaw -ar 8000 -ac 1 -f rtsp 'rtsp://192.168.1.3:22554/broadcast/34020000001320000001_34020000001320000001?sign=41db35390ddad33f83944f44b8b75ded' ``` 测试流程如下: ```plantuml @startuml "FFMPEG" -> "ZLMediaKit": 推流到zlm "WVP-PRO" <- "ZLMediaKit": 通知收到语音对讲推流,携带设备和通道信息 "WVP-PRO" -> "设备": 开始语音对讲 "WVP-PRO" <-- "设备": 语音对讲建立成功,携带收流端口 "WVP-PRO" -> "ZLMediaKit": 通知zlm将流推送到设备收流端口 "ZLMediaKit" -> "设备": 向设备推流 @enduml ``` 如果听到设备播放你推送的音频,那么意味着调用成功,此过程推流即可需要调用任何接口 ## 生产环境网页发起语音对讲 生产环境下使用语音对讲,如果是自己的客户端设备那么直接上面的ffmpeg测试方式,按照固定格式推流到zlm即可。 对于WEB程序,主要是局域网和公网的区别,两个原因: 1. 很多设备不支持公网对讲 2. 公网和局域网获取证书实现https支持的方式不同 ### 公网使用 公网你可以直接使用证书厂商或者云服务器厂商提供的证书,这是很方便的。 ### 局域网使用 局域网你需要为wvp和zlm生成自签名证书,这里我推荐一种生成自签名证书相对方便的方式, 此方式为linux下的一种方式。 下载证书生成工具: [https://github.com/FiloSottile/mkcert/releases/tag/v1.4.4](https://github.com/FiloSottile/mkcert/releases/tag/v1.4.4) 安装此工具, 进入解压的工具目录,执行 ```shell ./mkcert-v1.4.4-linux-amd64 -install ``` 生成pem证书 ```shell ./mkcert-v1.4.4-linux-amd64 局域网IP 局域网IP2 局域网IP3 ``` 你会得到两文件*-key.pem和*.pem, 此文件配置到wvp后既可实现证书的加载 生成zlm使用的证书 ```shell cat *.pem *-key.pem> ./zlm.pem ``` 得到的文件就是可以给zlm使用的证书 zlm下使用证书有两种方式: 1. 替换zlm下的default.pem, 即删除此文件并把zlm.pem重命名为default.pem 2. 在启动zlm的使用添加 `-s zlm.pem` ================================================ FILE: doc/_content/ability/continuous_recording.md ================================================ # 7*24不间断录像 目前如果要实现不间断录像如果只是关闭无人观看停止推流是不够的,设备可能经历断网,重启,都会导致录像的中断,目前给大家提供一种可用的临时方案。 **原理:** wvp支持使用流地址自动点播,即你拿到一个流地址直接去播放,即使设备处于未点播状态,wvp会自动帮你点播;ZLM 的拉流代理成功后会无限重试,只要流一恢复就可以拉起来,基于这两个原理。 **方案如下:** 1. wvp的配置中user-settings->auto-apply-play设置为团true,开启自动点播; 2. 点击你要录像的通道,点击播放页面左下角的“更多地址”,点击rtsp,此时复制了rtsp地址到剪贴板; 3. 在拉流代理中添加一路流,地址填写你复制的地址,启用成功即可。 **前提:** 1. wvp使用多端口收流,不然你无法得到一个固定的流地址,也就无法实现自动点播。 ================================================ FILE: doc/_content/ability/device.md ================================================ # 接入设备 ## 国标28181设备 设备接入主要是需要在设备上配置28181上级也就是WVP-PRO的信息,只有信息一致的情况才可以注册成功。设备注册成功后打开WVP-> 国标设备,可以看到新增加的设备;[设备使用](./_content/ability/device_use.md), 主要有以下字段需要配置: - sip->port 28181服务监听的端口 - sip->domain domain宜采用ID统一编码的前十位编码。 - sip->id 28181服务ID - sip->password 28181服务密码 - 配置信息在如下位置 ![_media/img_16.png](_media/img_16.png) *** ### 1. 大华摄像头 ![_media/img_10.png](_media/img_10.png) ### 2. 大华NVR ![_media/img_11.png](_media/img_11.png) ### 3. 宇视科技 ![_media/img_25.png](_media/img_25.png) ### 3. 艾科威视摄像头 ![_media/img_15.png](_media/img_15.png) ### 4. 水星摄像头 ![_media/img_12.png](_media/img_12.png) ### 5. 海康摄像头 ![_media/img_9.png](_media/img_9.png) ## 直播推流设备 这里以obs推流为例,很多无人机也是一样的,设置下推流地址就可以接入了 1. 从wvp获取推流地址, 选择节点管理菜单,查看要推流的节点; ![_media/img_19.png](_media/img_19.png) 2. 拼接推流地址 得到的rtsp地址就是: rtsp://{流IP}:{RTSP PORT}/{app}/{stream} 得到的rtmp地址就是: rtsp://{流IP}:{RTMP PORT}/{app}/{stream} 其中流IP是设备可以连接到zlm的IP,端口是对应协议的端口号, app和stream自己定义就可以. 3. 增加推流鉴权信息 wvp默认开启推流鉴权,拼接好的地址是不能直接推送的,会被返回鉴权失败,参考[推流规则](_content/ability/push?id=推流规则) 4. 推流成功后可以再推流列表中看到推流设备,可以播放 此方式只支持设备实时流的播放,无其他功能, 推流信息在推流结束后会自动移除,在列表里就看不到了,如果需要推流信息需要为设备配置国标编号,这样才可以作为wvp的一个永久通道存在. ## 接入非国标IPC设备或者其他流地址形式的设备 这类设备的接入主要通过拉流代理的方式接入,原理就是zlm主动像播放器一样拉取这个流缓存在自己服务器供其他人播放.可以解决源设备并发访问能力差的问题. 在拉流代理/添加代理后可以直接播放, 拉流代理也是同样只支持播放当前配置的流. [设备使用](_content/ability/device_use.md) ================================================ FILE: doc/_content/ability/device_use.md ================================================ # 国标设备 ### 更新设备通道 点击列表末尾的“刷新”按钮,可以看到一个圆形进度条,等进度结束提示成功后即可更新完成,如果通道数量有变化你可以看点击左上角的![刷新](_media/img_14.png) 即可看到通道数量的变化;如果通道数量仍未0,那么可能时对方尚未推送通道给你。 ### 查看设备通道 点击列表末尾的“通道”按钮, ### 编辑设备 点击列表末尾的“编辑”按钮,即可在打开的弹窗中对设备功能进行修改 - 设备名称 如何未能从设备里读取到设备名称或者需要自己重命名,那么可以修改此选项。 - 密码 支持为设备配置独立的密码. - 收流IP 如果你需要设备从指定的网络地址接入视频流,那么可以配置此IP,设备将会发流到这个IP,比如多网卡接入的服务器.或者存在网络映射的情况. - 流媒体ID 固定设备使用的流媒体ID,默认根据负载自动分配. - 字符集 修改读取设备数据时使用的字符集,默认为GB2312,但是GB2312收录的汉字不全,所以有时候回遇到乱码,可以修改为UTF-8来解决。 - 目录订阅 填写订阅周期即可对设备开启目录订阅,设备如果支持目录订阅那么设备在通道信息发生变化时就会通知WVP哪些通道发生了那些变化,包括通道增加/删除/更新/上线/下线/视频丢失/故障。0为取消订阅。 一般NVR和平台对接可以开启此选项,直接接摄像机开启此选项意义不大。 - 移动位置订阅 对设备开启移动位置订阅,设备如果支持目录订阅那么设备位置发生变化时会通知到WVP,一般执法记录仪可以开启此选项,对固定位置的设备意义不大。 - SSRC校验 为了解决部分设备出现的串流问题,可以打开此选项。ZLM会严格按照给定的ssrc处理视频流。部分设备流信息不标准,开启可能导致无法点播。 - 作为消息通道 wvp支持通过报警消息给下级WVP互相推送消息,消息内容由redis消息发送给wvp,wvp编辑成报警消息发送给下级 - 收到ACK后发流 语音对讲策略: 不同的设备对于语音对讲的收流时机要求不一,勾选后会在收到设备发送的ack后再开始发流,不勾选则在回复200OK后开始发流,目前已知大华设备不勾选,海康需要勾选. ### 删除设备 可以删除WVP中的设备信息,如果设备28181配置未更改,那么设备在下一次注册后仍然会注册上来。 ### 点播视频 进入通道列表后,点击列表末尾的“播放”按钮,稍等即可弹出播放页面 ### 设备录像 进入通道列表后,点击列表末尾的“设备录像”按钮,也可以在播放页面点击录像查询进入录像查看页面,选择要查看的日期即可对录像进行播放和下载。 ### 云台控制 可以对支持云台功能的设备进行上下左右的转动以及拉近拉远的操作。 ### 获取视频的播放器地址 视频点播成功后在实时视频页面,点击“更多地址”可以看到所有的播放地址,地址是否可以播放与你是否完整编译启用zlm功能有关,更与网络有关。 ### 语音对讲 [语音对讲](_content/ability/continuous_broadcast.md) ================================================ FILE: doc/_content/ability/gis.md ================================================ # 电子地图 WVP提供了简单的电子地图用于设备的定位以及移动设备的轨迹信息,电子地图基于开源的地图引擎openlayers开发。 ### 查看设备定位 1. 可以在设备列表点击“定位”按钮,自动跳转到电子地图页面; 2. 在电子地图页面在设备上右键点击“定位”获取设备/平台下的所有通道位置。 3. 单击通道信息可以定位到具体的通道 ### 查询设备轨迹 查询轨迹需要提前配置save-position-history选项开启轨迹信息的保存,目前WVP此处未支持分库分表,对于大数据量的轨迹信息无法胜任,有需求请自行二次开发或者定制开发。 在电子地图页面在设备上右键点击“查询轨迹”获取设备轨迹信息。 PS: 目前的底图仅用用作演示和学习,商用情况请自行购买授权使用。 ### 更换底图以及底图配置 目前WVP支持使用了更换底图,配置文件在web_src/static/js/config.js,请修改后重新编译前端文件。 ```javascript window.mapParam = { // 开启/关闭地图功能 enable: true, // 坐标系 GCJ-02 WGS-84, coordinateSystem: "GCJ-02", // 地图瓦片地址 tilesUrl: "http://webrd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scale=1&style=8", // 瓦片大小 tileSize: 256, // 默认层级 zoom:10, // 默认地图中心点 center:[116.41020, 39.915119], // 地图最大层级 maxZoom:18, // 地图最小层级 minZoom: 3 } ``` ================================================ FILE: doc/_content/ability/node_manager.md ================================================ # 节点管理 ![节点管理](_media/img_26.png) WVP支持单个WVP多个ZLM的方案来扩展WVP的视频并发能力,并发点播是因为带宽和性能的原因,单个ZLM节点能支持的路数有限,所以WVP增加了ZLM集群来扩展并发并且保证ZLM的高可用。 ## 默认节点 WVP中为了保证功能的完整性,ZLM节点至少要有一个默认节点,这个节点不是在管理页面添加的,而是在WVP的配置文件中配置的,这个节点不可在页面删除。每次启动会自动从配置文件中读取配置写入数据库备用。 ## 新增节点 启动你要添加的zlm节点,然后点击“添加节点”按钮输入zlm的ip, http端口,SECRET。点击测试测试完成则开始对节点进行详细的设置,如果你的zlm是使用docker启动的,可能存在zlm使用的端口与宿主机端口不一致的情况,需要在这里一一配置。 ## wvp使用多个节点的原理 wvp会把连接的节点统一记录在redis中,并记录zlm的负载情况,当新的请求到来时,会取出负载最低的那个zlm进行使用。以此保证节点负载均衡。 ================================================ FILE: doc/_content/ability/online_doc.md ================================================ # 在线文档 ================================================ FILE: doc/_content/ability/proxy.md ================================================ # 拉流代理 不是所有的摄像机都支持国标或者推流的,但是这些设备可以得到一个视频播放地址,通常为rtsp协议, 以大华为例: ```text rtsp://{user}:{passwd}@{ipc_ip}:{rtsp_port}/cam/realmonitor?channel=1&subtype=0 ``` 可以得到这样一个流地址,可以直接用vlc进行播放,此时我们可以通过拉流代理功能将这个设备推送给其他国标平台了。 流程如下: ```plantuml @startuml "摄像机" <- "ZLMediaKit": 1. 流去流信息到ZLM "ZLMediaKit" -> "WVP-PRO": 2. 收到hook通知得到流信息 "上级国标平台" -> "WVP-PRO": 3. 点播这路视频 "WVP-PRO" -> "ZLMediaKit": 4. 通知推流到上级国标平台 @enduml ``` ## 添加代理 拉流代理支持两种方式: 1. ZLM中直接代理流,支持RTSP/RTMP,不支持转码; 2. 借助ffmpeg完成拉转,可以通过修改ffmpeg拉转参数完成转码。 点击页面的“添加代理”,添加信息后保存即可,如果你需要共享推流信息到其他国标平台,那么你需要编辑/国标通道配置,配置国标编码. `PS: ffmpeg默认模板不需修改,需要修改`参数自行去ZLM配置文件中添加一个即可。 ================================================ FILE: doc/_content/ability/push.md ================================================ # 推流列表 ## 功能说明 WVP支持三种图像输入方式,直播,[拉流代理](_content/ability/proxy.md),[国标](_content/ability/device.md),直播设备接入流程如下 ```plantuml @startuml "直播设备" -> "ZLMediaKit": 1. 发起推流 "ZLMediaKit" -> "WVP-PRO": 2. 收到hook通知得到流信息 "上级国标平台" -> "WVP-PRO": 3. 点播这路视频 "WVP-PRO" -> "ZLMediaKit": 4. 通知推流到上级国标平台 @enduml ``` 1. 默认情况下WVP收到推流信息后,列表中出现这条推流信息,如果你需要共享推流信息到其他国标平台,那么你需要编辑/国标通道配置,配置国标编码. 2. WVP也支持推流前导入大量通道直接推送给上级,点击“下载模板”按钮,根据示例修改模板后,点击“通道导入”按钮导入通道数据. ## 生成推流地址 可以在推流列表里点击‘生成推流地址’按钮,得到新地址后直接复制到推流设备。 ## 推拉流鉴权规则 为了保护服务器的WVP默认开启推流鉴权(目前不支持关闭此功能) ### 推流规则 推流时需要携带推流鉴权的签名sign,sign=md5(pushKey),pushKey来自用户表,每个用户会有一个不同的pushKey. 例如app=test,stream=live,pushKey=1000,ip=192.168.1.4, port=10554 那么推流地址为: ``` rtsp://192.168.1.4:10554/test/live?sign=a9b7ba70783b617e9998dc4dd82eb3c5 ``` 支持推流时自定义播放鉴权Id,参数名为callId,此时sign=md5(callId_pushKey) 例如app=test,stream=live,pushKey=1000,callId=12345678, ip=192.168.1.4, port=10554 那么推流地址为: ``` rtsp://192.168.1.4:10554/test/live?callId=12345678&sign=c8e6e01dde2d60c66dcea8d2498ffef1 ``` ### 播放规则 默认情况播放不需要鉴权,但是如果推流时携带了callId,那么播放时必须携带callId 例如app=test,stream=live,无callId, ip=192.168.1.4, port=10554 那么播放地址为: ``` rtsp://192.168.1.4:10554/test/live ``` 例如app=test,stream=live,callId=12345678, ip=192.168.1.4, port=10554 那么播放地址为: ``` rtsp://192.168.1.4:10554/test/live?callId=12345678 ``` ================================================ FILE: doc/_content/ability/user.md ================================================ # 用户管理 ================================================ FILE: doc/_content/about_doc.md ================================================ # 关于本文档 本文档开源在gitee上,[https://gitee.com/pan648540858/wvp-pro-doc.git](https://gitee.com/pan648540858/wvp-pro-doc.git) ,如果文档出现任何错误或者不易理解的语句,请大家提ISSUE帮助我及时更正。欢迎大家提交PR一起维护这份文档,让更多的人可以使用到这个开源的视频平台。 ================================================ FILE: doc/_content/broadcast.md ================================================ # 原理图 ## 使用ffmpeg测试语音对讲原理 ```plantuml @startuml "FFMPEG" -> "ZLMediaKit": 推流到zlm "WVP-PRO" <- "ZLMediaKit": 通知收到语音对讲推流,携带设备和通道信息 "WVP-PRO" -> "设备": 开始语音对讲 "WVP-PRO" <-- "设备": 语音对讲建立成功,携带收流端口 "WVP-PRO" -> "ZLMediaKit": 通知zlm将流推送到设备收流端口 "ZLMediaKit" -> "设备": 向设备推流 @enduml ``` ## 使用网页测试语音对讲原理 ```plantuml @startuml "前端页面" -> "WVP-PRO": 请求推流地址 "前端页面" <-- "WVP-PRO": 返回推流地址 "前端页面" -> "ZLMediaKit": 使用webrtc推流到zlm,以下过程相同 "WVP-PRO" <- "ZLMediaKit": 通知收到语音对讲推流,携带设备和通道信息 "WVP-PRO" -> "设备": 开始语音对讲 "WVP-PRO" <-- "设备": 语音对讲建立成功,携带收流端口 "WVP-PRO" -> "ZLMediaKit": 通知zlm将流推送到设备收流端口 "ZLMediaKit" -> "设备": 向设备推流 @enduml ``` ================================================ FILE: doc/_content/disclaimers.md ================================================ # 免责声明 WVP-PRO自有代码使用宽松的MIT协议,在保留版权信息的情况下可以自由应用于各自商用、非商业的项目。 但是本项目也零碎的使用了一些其他的开源代码,在商用的情况下请自行替代或剔除; 由于使用本项目而产生的商业纠纷或侵权行为一概与本项目及开发者无关,请自行承担法律风险。 在使用本项目代码时,也应该在授权协议中同时表明本项目依赖的第三方库的协议 ================================================ FILE: doc/_content/introduction/compile.md ================================================ # 编译 WVP-PRO不只是实现了国标28181的协议,本身也是一个完整的视频平台。所以对于新手来说,你可能需要一些耐心来完成。遇到问题不要焦躁,你可以 1. 百度 2. 加入星球体提问;[知识星球](https://t.zsxq.com/0d8VAD3Dm) 3. 向作者发送邮件648540858@qq.com,寻求技术支持(有偿); WVP-PRO使用Spring boot开发,maven管理依赖。对于熟悉spring开发的朋友是很容易进行编译部署以及运行的。 下面将提供一种通用方法方便大家运行项目。 ## 1 服务介绍 | 服务 | 作用 | 是否必须 | |------------|------------------------------------------|------| | WVP-PRO | 实现国标28181的信令以及视频平台相关的功能 | 是 | | ZLMediaKit | 为WVP-PRO提供国标28181的媒体部分的实现,以及各种视频流格式的分发支持 | 是 | ## 2 安装依赖 | 依赖 | 版本 | 用途 | 开发环境需要 | 生产环境需要 | |--------|-------|-------------|--------|--------| | jdk | >=21 | 运行与编译java代码 | 是 | 是 | | maven | >=3.3 | 管理java代码依赖 | 否 | 否 | | git | | 下载/更新/提交代码 | 否 | 否 | | nodejs | | 编译于运行前端文件 | 否 | 否 | | npm | | 管理前端文件依赖 | 否 | 否 | 如果你是一个新手,建议你使用linux或者macOS平台。windows不推荐。 ubuntu环境,以ubuntu 18为例: ``` bash apt-get install -y openjdk-21-jdk git maven nodejs npm ``` window环境,以windows10为例: ```bash 这里不细说了,百度或者谷歌一搜一大把,基本都是下一步下一步,然后配置环境变量。 ``` ## 3 安装mysql以及redis 这里依然是参考网上教程,自行安装吧。 ## 4 编译ZLMediaKit > 现在zlm提供最新版本的打包直接下载了, 地址为:[各平台二进制包下载](https://github.com/ZLMediaKit/ZLMediaKit/issues/483) 参考ZLMediaKit[WIKI](https://github.com/ZLMediaKit/ZLMediaKit/wiki) ,如果需要使用语音对讲功能,请参考[zlm启用webrtc编译指南](https://github.com/ZLMediaKit/ZLMediaKit/wiki/zlm%E5%90%AF%E7%94%A8webrtc%E7%BC%96%E8%AF%91%E6%8C%87%E5%8D%97) ,开启zlm的webrtc功能。截取一下关键步骤: ```bash # 国内用户推荐从同步镜像网站gitee下载 git clone --depth 1 https://gitee.com/xia-chu/ZLMediaKit cd ZLMediaKit # 千万不要忘记执行这句命令 git submodule update --init ``` ## 5 编译WVP-PRO ### 5.1 可以通过git克隆,也可以在项目下载点击下载 ![点击下载](_media/img_1.png) ![点击下载](_media/img_2.png) 从gitee克隆 ```bash git clone https://gitee.com/pan648540858/wvp-GB28181-pro.git ``` 从github克隆 ```bash git clone https://github.com/648540858/wvp-GB28181-pro.git ``` ### 5.2 编译前端页面 ```shell script cd wvp-GB28181-pro/web/ npm --registry=https://registry.npmmirror.com install npm run build:prod ``` 编译如果报错, 一般都是网络问题, 导致的依赖包下载失败 编译完成后在src/main/resources下出现static目录 **编译完成一般是这个样子,中间没有报红的错误信息** ![编译成功](_media/img.png) ### 5.3 生成可执行jar ```bash cd wvp-GB28181-pro mvn package ``` ### 5.4 生成war ```bash cd wvp-GB28181-pro mvn package -P war ``` 编译如果报错, 一般都是网络问题, 导致的依赖包下载失败 编译完成后在target目录下出现 `wvp-pro-VERSION.jar` 和 `wvp-pro-VERSION.war` 文件。 接下来[配置服务](./_content/introduction/config.md) ================================================ FILE: doc/_content/introduction/config.md ================================================ # 配置 对于首次测试或者新手同学,我建议在局域网测试,并且关闭服务器与客户机的防火墙测试。建议部署在linux进行测试。 ```plantuml @startuml "WVP-PRO" -> "ZLMediaKit": RESTful 接口 "WVP-PRO" <-- "ZLMediaKit": Web Hook 接口 @enduml ``` WVP-PRO通过调用ZLMediaKit的RESTful接口实现对ZLMediaKit行为的控制; ZLMediaKit通过Web Hook 接口把消息通知WVP-PRO。通过这种方式,实现了两者的互通。 对于最简单的配置,你不需要修改ZLMediaKit的任何默认配置。你只需要在WVP-PRO中配置的ZLMediaKit信息即可 ## 1 WVP配置文件位置 基于spring boot的开发方式,配置文件的加载是很灵活的。默认在src/main/resources/application.yml,部分配置项是可选,你不需要全部配置在配置文件中, 完全的配置说明可以参看"src/main/resources/配置详情.yml"。 ### 1.1 默认加载配置文件方式 使用maven打包后的target里,已经存在了配置文件,默认加载配置文件为application.yml,查看内容发现其中spring.profiles.active配置的内容,将入配置的值为dev,那么具体加载的配置文件就是application-dev.yml,如果配置的值找不到对应的配置文件,修改值为dev。 ```shell cd wvp-GB28181-pro/target java -jar wvp-pro-*.jar ``` ## 2 配置WVP-PRO wvp支持多种数据库,包括Mysql,Postgresql,金仓等,配置任选一种即可。 ### 2.1 数据库配置 #### 2.1.1 初始化数据库 首先使用创建数据库,然后使用sql/初始化.sql初始化数据库,如果是从旧版升级上来的,使用升级sql更新。 #### 2.1.2 Mysql数据库配置 数据库名称以wvp为例 ```yaml spring: datasource: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/wvp?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true&serverTimezone=PRC&useSSL=false&allowMultiQueries=true username: root password: root123 ``` #### 2.1.3 Postgresql数据库配置 数据库名称以wvp为例 ```yaml spring: datasource: type: com.zaxxer.hikari.HikariDataSource driver-class-name: org.postgresql.Driver url: jdbc:postgresql://127.0.0.1:3306/wvp?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true&serverTimezone=PRC&useSSL=false&allowMultiQueries=true&allowPublicKeyRetrieval=true username: root password: 12345678 pagehelper: helper-dialect: postgresql ``` #### 2.1.4 金仓数据库配置 数据库名称以wvp为例 ```yaml spring: datasource: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.kingbase8.Driver url: jdbc:kingbase8://127.0.0.1:3306/wvp?useUnicode=true&characterEncoding=utf8 username: root password: 12345678 pagehelper: helper-dialect: postgresql ``` ### 2.2 Redis数据库配置 配置wvp中的redis连接信息,建议wvp自己单独使用一个db。 ### 2.3 配置服务启动端口(可直接使用默认配置) ```yaml # [可选] WVP监听的HTTP端口, 网页和接口调用都是这个端口 server: port: 18080 ``` ### 2.4 配置28181相关信息(可直接使用默认配置) ```yaml # 作为28181服务器的配置 sip: # [可选] 28181服务监听的端口 port: 5060 # 根据国标6.1.2中规定,domain宜采用ID统一编码的前十位编码。国标附录D中定义前8位为中心编码(由省级、市级、区级、基层编号组成,参照GB/T 2260-2007) # 后两位为行业编码,定义参照附录D.3 # 3701020049标识山东济南历下区 信息行业接入 # [可选] domain: 3402000000 # [可选] id: 34020000002000000001 # [可选] 默认设备认证密码,后续扩展使用设备单独密码, 移除密码将不进行校验 password: 12345678 ``` ### 2.5 配置ZLMediaKit连接信息 ```yaml #zlm 默认服务器配置 media: id: zlmediakit-local # [必须修改] zlm服务器的内网IP ip: 172.19.128.50 # [可选] 有公网IP就配置公网IP, 不可用域名 wan_ip: # [必须修改] zlm服务器的http.port http-port: 9092 # [可选] zlm服务器访问WVP所使用的IP, 默认使用127.0.0.1,zlm和wvp没有部署在同一台服务器时必须配置 hook-ip: 172.19.128.50 # [必选选] zlm服务器的hook.admin_params=secret secret: TWSYFgYJOQWB4ftgeYut8DW4wbs7pQnj # 启用多端口模式, 多端口模式使用端口区分每路流,兼容性更好。 单端口使用流的ssrc区分, 点播超时建议使用多端口测试 rtp: # [可选] 是否启用多端口模式, 开启后会在portRange范围内选择端口用于媒体流传输 enable: true # [可选] 在此范围内选择端口用于媒体流传输, 必须提前在zlm上配置该属性,不然自动配置此属性可能不成功 port-range: 30000,35000 # 端口范围 # [可选] 国标级联在此范围内选择端口发送媒体流, send-port-range: 40000,40300 # 端口范围 ``` ### 2.4 策略配置 ```yaml # [根据业务需求配置] user-settings: # 点播/录像回放 等待超时时间,单位:毫秒 play-timeout: 180000 # [可选] 自动点播, 使用固定流地址进行播放时,如果未点播则自动进行点播, 需要rtp.enable=true auto-apply-play: true # 推流直播是否录制 record-push-live: true # 国标是否录制 record-sip: true # 国标点播 按需拉流, true:有人观看拉流,无人观看释放, false:拉起后不自动释放 stream-on-demand: true ``` 更多完整的配置信息参考"src/main/resources/配置详情.yml"文件,需要那个配置项,复制到正在使用的配置文件中对应的文件即可。 如果配置信息无误,你可以启动zlm,再启动wvp来测试了,启动成功的话,你可以在wvp的日志下看到zlm已连接的提示。 接下来[部署到服务器](./_content/introduction/deployment.md), 如果你只是本地运行直接在本地运行即可。 ================================================ FILE: doc/_content/introduction/deployment.md ================================================ # 部署 **请仔细阅读以下内容** 1. WVP-PRO与ZLM支持分开部署; 2. 需要开放的端口 | 服务 | 端口 | 类型 | 必选 | |-----|:-------------------------|-------------|-------| | wvp | server.port | tcp | 是 | | wvp | sip.port | udp and tcp | 是 | | zlm | http.port | tcp | 是 | | zlm | http.sslport | tcp | 否 | | zlm | rtmp.port | tcp | 否 | | zlm | rtmp.sslport | tcp | 否 | | zlm | rtsp.port | udp and tcp | 否 | | zlm | rtsp.sslport | udp and tcp | 否 | | zlm | rtp_proxy.port | udp and tcp | 单端口开放 | | zlm | rtp.port-range(在wvp中配置) | udp and tcp | 多端口开放 | 3. 测试环境部署建议所有服务部署在一台主机,关闭防火墙,减少因网络出现问题的可能; 4. 生产环境按需开放端口,但是建议修改默认端口,尤其是5060端口,易受到攻击; 5. zlm使用docker部署的情况,请使用host模式,或者端口映射一致,比如映射5060,应将外部端口也映射为5060端口; 6. zlm与wvp会保持高频率的通信,所以不要去将wvp与zlm分属在两个网络,比如wvp在内网,zlm却在公网的情况。 7. 启动服务,以linux为例 **启动WVP-PRO** ```shell nohup java -jar wvp-pro-*.jar & ``` **war包:** 下载Tomcat后将war包放入webapps中,启动Tomcat以解压war包,停止Tomcat后,删除ROOT目录以及war包,将解压后的war包目录重命名为ROOT,将配置文件中的Server.port配置为与Tomcat端口一致 然后启动Tomcat。 **启动ZLM** ```shell nohup ./MediaServer -d -m 3 & ``` ### 前后端分离部署 前端基于 [vue-admin-template](https://github.com/PanJiaChen/vue-admin-template/blob/master/README-zh.md) 构建, 参考这儿即可。 ### 默认账号和密码 部署完毕后,可以通过访问 ip加端口的方式访问 WVP ,WVP的默认登录账号和密码均为 admin。 ================================================ FILE: doc/_content/qa/bug.md ================================================ # 反馈bug 代码是在不断的完善的,不断修改会修复旧的问题也有可能引入新的问题,所以遇到BUG是很正常的一件事。所以遇到问题不要烦燥,咱们就事论事就好了。 ## 如何反馈 1. 在知识星球提问。 2. 更新代码,很可能你遇到问题别人已经更早的遇到了,或者是作者自己发现了,已经解决了,所以你可以更新代码再次进行测试; 3. 可以在github提ISSUE,我几乎每天都会去看issue,你的问题我会尽快给予答复; > 有偿支持可以给我发邮件, 648540858@qq.com ## 社群 [![社群](../../_media/shequ.png "shequ")](https://t.zsxq.com/0d8VAD3Dm) > 收费是为了提供更好的服务,也是对作者更大的激励。加入星球的用户三天后可以私信我留下微信号,我会拉大家入群。加入三天内不满意可以直接退款,大家不需要有顾虑,来白嫖三天也不是不可以。 ================================================ FILE: doc/_content/qa/development.md ================================================ # 参与到开发中来 非常欢迎有兴趣的小伙伴一起来维护这个项目 ## 与开发有关的信息 - 开发语言:后端java + 前端vue; - jdk版本: 1.8; - 作者自用开发ide: jetbrains intellij idea; - nodejs/npm版本:v10.19.0/6.14.4; - 后端使用Spring boot框架开发; - 项目大量使用了异步操作; - 跟代码学流程需要参考28181文档,只看代码你会很懵的; - 必须学会[抓包](_content/skill/tcpdump.md),这是必须的 ## 提交代码 大家可以通过fork项目的方式提交自己的代码,然后提交PR,我来合并到主线。提交代码的过程中我们需要遵循“**阿里编码规约** ”,现有代码也有很多代码没有做到,但是我们在朝这个方向努力。 ================================================ FILE: doc/_content/qa/play_error.md ================================================ # 点播错误 排查点播错误你首先要清楚[点播的基本流程](_content/theory/play.md),一般的流程如下: ```plantuml @startuml "WEB用户" -> "WVP-PRO": 1. 发起点播请求 "设备" <- "WVP-PRO": 2. Invite(携带SDP消息体) "设备" --> "WVP-PRO": 3. 200OK(携带SDP消息体) "设备" <-- "WVP-PRO": 4. Ack "设备" -> "ZLMediaKit": 5. 发送实时流 "WVP-PRO" <- "ZLMediaKit": 6. 流改变事件 "WEB用户" <-- "WVP-PRO": 7. 回复流播放地址(携带流地址) "WVP-PRO" <- "ZLMediaKit": 8. 无人观看事件 "设备" <- "WVP-PRO": 9 Bye消息 "设备" --> "WVP-PRO": 10 200OK @enduml ``` 针对几种常见的错误,我们来分析一下,也方便大家对号入座解决常见的问题 ## 点播收到错误码 这个错误一般表现为点击"播放"按钮后很快得到一个错误。 1. **400错误码** 出现400错误玛时一般是这样的流程是这样的 ```plantuml @startuml "WEB用户" -> "WVP-PRO": 1. 发起点播请求 "设备" <- "WVP-PRO": 2. Invite(携带SDP消息体) "设备" --> "WVP-PRO": 3. 400错误 @enduml ``` 此时通常是设备认为WVP发送了错误的消息给它,它认为消息不全或者错误所以直接返回400错误,此时我们需要[抓包](_content/skill/tcpdump.md) 来分析是否缺失了内容,也可以直接联系对方询问为什么返回了400。 WVP不能保证兼容所有的设备,有些实现不规范的设备可能在对接时就会出现上述问题,你可以联系作者帮忙对接。 2. **500错误码** 500或者大于500小于600的错误码一般多是设备内部出了问题,解决方式有两个,第一种直接联系设备/平台客服寻求解决;第二种,如果你有确定可以对接这个设备的平台那么可以把对接这个平台的抓包和对接wvp的抓包同时发送给我,我来尝试解决。 ## 点播超时 点播超时的情况大致分为两种:点播超时和收流超时 1. **点播超时** 点播超时错误一般为信令的超时,比如长时间为收到对方的回复,可能出现在流程中 “3. 200OK(携带SDP消息体) ”这个位置,即我们发送点播消息,但是设备没有回复,可能的原因: > 1. 设备内部错误,未能回复消息 > 2. 网络原因消息未到到达设备 大部分时候是原因2,所以遇到这个错误我们首先要排查我们我的网路,如果你是公网部署,那么也可能时心跳周期太长,导致的路由NAT失效,WVP的消息无法通道原来的IP端口号发送给设备。 2. **收流超时** 收流超时可能发生在流程中的5和6,可能的原因有: > 1. 设备发送了流但是发送到了错误的ip和端口上,而这个信息是在invite消息的sdp中指定的,就是流程2Invite(携带SDP消息体) 中,而这个错误很可能来自你的配置错误,比如你设置了127.0.0.1导致设备网127.0.0.1上发流,或者是你WVP在公网,但是你给设备了一个内网ip,导致设备无法把流发送过来; > 2. 设备内部错误未发送流; > 2. 设备发送了流,但是流无法识别,可能存在于流不规范和网络很差的情况下; > 3. 设备发送了流,zlm也收到了,但是zlm无法通过hook通知到wvp,此时原因是你可以检查zlm的配置文件中的hook配置,看看是否无法从zlm连接到wvp; > 4. 设备发送了流,但是开启SSRC校验,设备的流不够规范采用错误的ssrc,导致zlm选择丢弃; 针对这些可能的错误原因我建议的排查顺序: - 关闭ssrc校验; - 查看zlm配置的hook是否可以连接到zlm; - 查看zlm日志是否有流注册; - 抓包查看流的信息,看看流是否正常发送,甚至可以导出发送原始流,用vlc播放,看看是否可以播放。 ================================================ FILE: doc/_content/qa/regiser_error.md ================================================ # 设备注册不上来的解决办法 一般的原因有两个 1. 信息填写错误,比如密码错误; 2. 网络不通导致注册消息无法发送到WVP; 遇到问题首先仔细校验填写信息,例如海康可能需要勾选鉴权才可以输入密码。网络问题请自行测试。 ================================================ FILE: doc/_content/qa/start_error.md ================================================ # 启动时报错 启动时的报错大部分时候是因为你的配置有问题,比如mysql没连接上,redis没连接上,18080/15060端口占用了,这些都会导致启动是报错,修改配置配置之后都可以解决; 下面我整理的一些常见的错误,大家可以先对号入座的简单排查下。 > **常见错误** ![_media/img.png](_media/img.png) **错误原因:** redis配置错误,可能原因: redis未启动/ip错误/端口错误/网络不通 --- ![_media/img_1.png](_media/img_1.png) **错误原因:** redis配置错误,可能原因: 密码错误 --- ![_media/img_2.png](_media/img_2.png) **错误原因:** mysql配置错误,可能原因: mysql未启动/ip错误/端口错误/网络不通 --- ![_media/img_3.png](_media/img_3.png) **错误原因:** mysql配置错误,可能原因: 用户名/密码错误 --- ![_media/img_4.png](_media/img_4.png) **错误原因:** SIP配置错误,可能原因: SIP端口被占用 --- ![_media/img_5.png](_media/img_5.png) **错误原因:** WVP Tomcat端口配置错误,可能原因: server.port端口被占用 --- ================================================ FILE: doc/_content/skill/tcpdump.md ================================================ # 抓包 如果说对于网络编程,有什么工具是必会的,我觉得抓包肯定是其中之一了。作为GB/T 28181调试过程中最重要的手段,我觉得如果你真对他有兴趣,或者系统遇到问题可以最快的得到解决,那么抓包你就一定要学会了。 ## 抓包工具的选择 ### 1. Wireshark 在具备图形界面的系统上,比如windows,linux发行版ubuntu,opensuse等,我一般直接使用Wireshark直接进行抓包,也方便进行内容的查看。 ### 2. Tcpdump 在使用命令行的系统,比如linux服务器,我一般使用Tcpdump进行抓包,无需额外安装,系统一般自带,抓包的到的文件,可以使用Wireshark打开,在图形界面下方便查看内容。 ## 工具安装 Wireshark的安装很简单,根据提示一步步点击就好了,在linux需要解决权限的问题,如果和我一样使用图形界面的linux发行版的话,可以参看如下步骤; windows的小伙伴直接略过即可 ```shell # 1. 添加wireshark用户组 sudo groupadd wireshark # 2. 将dumpcap更改为wireshark用户组 sudo chgrp wireshark /usr/bin/dumpcap # 3. 让wireshark用户组有root权限使用dumpcap sudo chmod 4755 /usr/bin/dumpcap # 4. 将需要使用的用户名加入wireshark用户组 sudo gpasswd -a $USER wireshark ``` tcpdump一般linux都是自带,无需安装,可以这样验证;显示版本信息即是已安装 ```shell tcpdump --version ``` ## 开始抓包 ### 使用Wireshark 在28181中我一般只关注sip包和rtp包,所以我一般是直接过滤sip和rtp,可以输入框输入 `sip or rtp`这样即可,如果设备来源比较多还可以加上ip和端口号的过滤 `(sip or rtp )and ip.addr==192.168.1.3 and udp.port==5060` 详细的过滤规则可以自行百度,我可以提供一些常用的给大家参考 ![img.png](_media/img.png) **只过滤SIP:** ```shell sip ``` **只获取rtp数据:** ```shell rtp ``` **默认方式:** ```shell sip or rtp ``` **过滤IP:** ```shell sip and ip.addr==192.168.1.3 ``` **过滤端口:** ```shell sip and udp.port==5060 ``` 输入命令开启抓包后,此时可以进行操作,比如点播,录像回访等,操作完成回到Wireshark点击红色的停止即可,需要保存文件可以点击 `文件->导出特定分组`导出过滤后的数据,也可以直接`文件->另存为`保存未过滤的数据。 ### 使用tcpdump 对于服务器抓包,为了得到足够完整的数据,我一般会要求直接抓取网卡数据而不过滤,如下: 抓取网卡首先需要获取网卡名,在linux我一般使用`ip addr`获取网卡信息,如下所示: ![img_1.png](_media/img_1.png) ```shell sudo tcpdump -i wlp3s0 -w demo.pcap ``` ![img_2.png](_media/img_2.png) 命令行会停留在这个位置,此时可以进行操作,比如点播,录像回放等,操作完成回到命令行使用`Ctrl+C` 结束命令行,在当前目录下得到demo.pcap,将这个文件下载到图形界面操作系统里,即可使用Wireshark查看了 更多的操作可以参考: [https://www.cnblogs.com/jiujuan/p/9017495.html](https://www.cnblogs.com/jiujuan/p/9017495.html) ================================================ FILE: doc/_content/theory/broadcast_cascade.md ================================================ # 点播流程 > 以下为WVP-PRO级联语音喊话流程。 ```plantuml @startuml "上级平台" -> "下级平台": 1. 发起语音喊话请求 "上级平台" <-- "下级平台": 2. 200OK "上级平台" <- "下级平台": 3. 回复Result OK "上级平台" --> "下级平台": 4. 200OK "下级平台" -> "设备": 5. 发起语音喊话请求 "下级平台" <-- "设备": 6. 200OK "下级平台" <- "设备": 7. 回复Result OK "下级平台" --> "设备": 8. 200OK "下级平台" <- "设备": 9. invite(broadcast) "下级平台" --> "设备": 10. 100 trying "下级平台" --> "设备": 11. 200OK SDP "下级平台" <-- "设备": 12. ack "上级平台" <- "下级平台": 13. invite(broadcast) "上级平台" --> "下级平台": 14. 100 trying "上级平台" --> "下级平台": 15. 200OK SDP "上级平台" <-- "下级平台": 16. ack "上级平台" -> "下级平台": 17. 推送RTP "下级平台" -> "设备": 18. 推送RTP @enduml ``` ## 注册流程描述如下: 1. 用户从网页或调用接口发起点播请求; 2. WVP-PRO向摄像机发送Invite消息,消息头域中携带 Subject字段,表明点播的视频源ID、发送方媒体流序列号、ZLMediaKit接收流使用的IP、端口号、 接收端媒体流序列号等参数,SDP消息体中 s字段为“Play”代表实时点播,y字段描述SSRC值,f字段描述媒体参数。 3. 摄像机向WVP-PRO回复200OK,消息体中描述了媒体流发送者发送媒体流的IP、端口、媒体格式、SSRC字段等内容。 4. WVP-PRO向设备回复Ack, 会话建立成功。 5. 设备向ZLMediaKit发送实时流。 6. ZLMediaKit向WVP-PRO发送流改变事件。 7. WVP-PRO向WEB用户回复播放地址。 8. ZLMediaKit向WVP发送流无人观看事件。 9. WVP-PRO向设备回复Bye, 结束会话。 10. 设备回复200OK,会话结束成功。 ================================================ FILE: doc/_content/theory/code.md ================================================ # 统一编码规则 ## D.1 编码规则 A >   编码规则 A 由中心编码(8位)、行业编码(2位)、类型编码(3位)和序号(7位)四个码段共20位十 > 进制数字字符构成,即系统编码 =中心编码 + 行业编码 + 类型编码 + 序号。 >   编码规则 A 的详细说明见表 D.1。其中,中心编码指用户或设备所归属的监控中心的编码,按照监控中心所在地的行政区划代码确定, > 当不是基层单位时空余位为0。行政区划代码采用 GB/T2260— 2007规定的行政区划代码表示。行业编码是指用户或设备所归属的行业,行业编码对照表见 > D.3。 > 类型编码指定了设备或用户的具体类型,其中的前端设备包含公安系统和非公安系统的前端设备,终端用 > 户包含公安系统和非公安系统的终端用户。 ![img_7.png](_media/img_7.png) ![img_1.png](_media/img_1.png) ![img_2.png](_media/img_2.png) ## D.2 编码规则 B >   编码规则 B由中心编码(8位)、行业编码(2位)、序号(4位)和类型编码(2位)四个码段构成,即系 > 统编码 =中心编码 + 行业编码 +序号+类型编码。编码规则 B的详细说明见表 D.2。 ![img_3.png](_media/img_3.png) ![img_4.png](_media/img_4.png) ## D.3 行业编码对照表 >   行业编码对照表见表 D.3。 ![img_5.png](_media/img_5.png) ![img_6.png](_media/img_6.png) ================================================ FILE: doc/_content/theory/play.md ================================================ # 点播流程 > 以下为WVP-PRO点播流程。点播成功前的任何一个环节出现问题都可能出现点播超时,这也是排查点播超时的依据。 ```plantuml @startuml "WEB用户" -> "WVP-PRO": 1. 发起点播请求 "设备" <- "WVP-PRO": 2. Invite(携带SDP消息体) "设备" --> "WVP-PRO": 3. 200OK(携带SDP消息体) "设备" <-- "WVP-PRO": 4. Ack "设备" -> "ZLMediaKit": 5. 发送实时流 "WVP-PRO" <- "ZLMediaKit": 6. 流改变事件 "WEB用户" <-- "WVP-PRO": 7. 回复流播放地址(携带流地址) "WVP-PRO" <- "ZLMediaKit": 8. 无人观看事件 "设备" <- "WVP-PRO": 9 Bye消息 "设备" --> "WVP-PRO": 10 200OK @enduml ``` ## 注册流程描述如下: 1. 用户从网页或调用接口发起点播请求; 2. WVP-PRO向摄像机发送Invite消息,消息头域中携带 Subject字段,表明点播的视频源ID、发送方媒体流序列号、ZLMediaKit接收流使用的IP、端口号、 接收端媒体流序列号等参数,SDP消息体中 s字段为“Play”代表实时点播,y字段描述SSRC值,f字段描述媒体参数。 3. 摄像机向WVP-PRO回复200OK,消息体中描述了媒体流发送者发送媒体流的IP、端口、媒体格式、SSRC字段等内容。 4. WVP-PRO向设备回复Ack, 会话建立成功。 5. 设备向ZLMediaKit发送实时流。 6. ZLMediaKit向WVP-PRO发送流改变事件。 7. WVP-PRO向WEB用户回复播放地址。 8. ZLMediaKit向WVP发送流无人观看事件。 9. WVP-PRO向设备回复Bye, 结束会话。 10. 设备回复200OK,会话结束成功。 ================================================ FILE: doc/_content/theory/register.md ================================================ # 注册流程 WVP-PRO目前仅支持国标中描述的基本注册流程,也是最常用的, > 基本注册即采用IETFRFC3261规定的基于数字摘要的挑战应答式安全技术进行注册. ```plantuml @startuml "设备" -> "WVP-PRO": 1. Register "设备" <-- "WVP-PRO": 2. 401 Unauthorized "设备" -> "WVP-PRO": 3. Register "设备" <-- "WVP-PRO": 4. 200 OK @enduml ``` > 注册流程描述如下: > 1. 摄像机向WVP-PRO服务器发送 Register请求; > 2. WVP-PRO向摄像机发送响应401,并在响应的消息头 WWW_Authenticate字段中给出适合摄像机的认证体制和参数; > 3. 摄像机重新向WVP-PRO发送 Register请求,在请求的 Authorization字段给出信任书, 包含认证信息; > 4. WVP-PRO对请求进行验证,如果检查出 摄像机身份合法,向摄像机发送成功响应 200OK,如果身份不合法则发送拒绝服务应答。 ================================================ FILE: doc/_coverpage.md ================================================ ![logo](_media/logo-mini.png) # WVP-PRO 2.0 > 开箱即用的28181协议视频平台。 - 基于GB/T28181-2016标准信令实现,兼容GB/T28181-2011。 - 自带完整前端页面,开箱即用。 - 完全开源,且使用MIT许可协议。可以在保留版权信息的基础上商用。 [GitHub](https://github.com/648540858/wvp-GB28181-pro) [Gitee](https://gitee.com/pan648540858/wvp-GB28181-pro) [//]: # ([comment]: <> (![color](#f0f0f0))) ================================================ FILE: doc/_navbar.md ================================================ ================================================ FILE: doc/_sidebar.md ================================================ * **编译与部署** * [编译](_content/introduction/compile.md) * [配置](_content/introduction/config.md) * [部署](_content/introduction/deployment.md) * **功能与使用** * [接入设备](_content/ability/device.md) * [国标设备](_content/ability/device_use.md) * [推流列表](_content/ability/push.md) * [拉流代理](_content/ability/proxy.md) * [云端录像](_content/ability/cloud_record.md) * [节点管理](_content/ability/node_manager.md) * [通道管理](_content/ability/channel.md) * [国标级联](_content/ability/cascade2.md) * **流程与原理** * [统一编码规则](_content/theory/code.md) * [注册流程](_content/theory/register.md) * [点播流程](_content/theory/play.md) * [级联语音喊话流程](_content/theory/broadcast_cascade.md) * [语音对讲](_content/ability/continuous_broadcast.md) * **必备技巧** * [抓包](_content/skill/tcpdump.md) * **常见问答** - [如何反馈BUG](_content/qa/bug.md) - [如何参与开发](_content/qa/development.md) - [启动报错的解决办法](_content/qa/start_error.md) - [设备注册不上来的解决办法](_content/qa/regiser_error.md) - [点播超时/报错的解决办法](_content/qa/play_error.md) * [**免责声明**](_content/disclaimers.md) * [**关于本文档**](_content/about_doc.md) ================================================ FILE: doc/index.html ================================================ WVP-PRO文档
加载中
================================================ FILE: doc/lib/css/vue.css ================================================ @import url("https://fonts.googleapis.com/css?family=Roboto+Mono|Source+Sans+Pro:300,400,600");*{-webkit-font-smoothing:antialiased;-webkit-overflow-scrolling:touch;-webkit-tap-highlight-color:rgba(0,0,0,0);-webkit-text-size-adjust:none;-webkit-touch-callout:none;box-sizing:border-box}body:not(.ready){overflow:hidden}body:not(.ready) .app-nav,body:not(.ready)>nav,body:not(.ready) [data-cloak]{display:none}div#app{font-size:30px;font-weight:lighter;margin:40vh auto;text-align:center}div#app:empty:before{content:"Loading..."}img.emoji{height:1.2em}img.emoji,span.emoji{vertical-align:middle}span.emoji{font-family:Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-size:1.2em}.progress{background-color:#42b983;background-color:var(--theme-color,#42b983);height:2px;left:0;position:fixed;right:0;top:0;transition:width .2s,opacity .4s;width:0;z-index:999999}.search .search-keyword,.search a:hover{color:#42b983;color:var(--theme-color,#42b983)}.search .search-keyword{font-style:normal;font-weight:700}body,html{height:100%}body{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;color:#34495e;font-family:Source Sans Pro,Helvetica Neue,Arial,sans-serif;font-size:15px;letter-spacing:0;margin:0;overflow-x:hidden}img{max-width:100%}a[disabled]{cursor:not-allowed;opacity:.6}kbd{border:1px solid #ccc;border-radius:3px;display:inline-block;font-size:12px!important;line-height:12px;margin-bottom:3px;padding:3px 5px;vertical-align:middle}li input[type=checkbox]{margin:0 .2em .25em 0;vertical-align:middle}.app-nav{margin:25px 60px 0 0;position:absolute;right:0;text-align:right;z-index:10}.app-nav.no-badge{margin-right:25px}.app-nav p{margin:0}.app-nav>a{margin:0 1rem;padding:5px 0}.app-nav li,.app-nav ul{display:inline-block;list-style:none;margin:0}.app-nav a{color:inherit;font-size:16px;text-decoration:none;transition:color .3s}.app-nav a.active,.app-nav a:hover{color:#42b983;color:var(--theme-color,#42b983)}.app-nav a.active{border-bottom:2px solid #42b983;border-bottom:2px solid var(--theme-color,#42b983)}.app-nav li{display:inline-block;margin:0 1rem;padding:5px 0;position:relative;cursor:pointer}.app-nav li ul{background-color:#fff;border:1px solid;border-color:#ddd #ddd #ccc;border-radius:4px;box-sizing:border-box;display:none;max-height:calc(100vh - 61px);overflow-y:auto;padding:10px 0;position:absolute;right:-15px;text-align:left;top:100%;white-space:nowrap}.app-nav li ul li{display:block;font-size:14px;line-height:1rem;margin:8px 14px;white-space:nowrap}.app-nav li ul a{display:block;font-size:inherit;margin:0;padding:0}.app-nav li ul a.active{border-bottom:0}.app-nav li:hover ul{display:block}.github-corner{border-bottom:0;position:fixed;right:0;text-decoration:none;top:0;z-index:1}.github-corner:hover .octo-arm{animation:octocat-wave .56s ease-in-out}.github-corner svg{color:#fff;fill:#42b983;fill:var(--theme-color,#42b983);height:80px;width:80px}main{display:block;position:relative;width:100vw;height:100%;z-index:0}main.hidden{display:none}.anchor{display:inline-block;text-decoration:none;transition:all .3s}.anchor span{color:#34495e}.anchor:hover{text-decoration:underline}.sidebar{border-right:1px solid rgba(0,0,0,.07);overflow-y:auto;padding:40px 0 0;position:absolute;top:0;bottom:0;left:0;transition:transform .25s ease-out;width:300px;z-index:20}.sidebar>h1{margin:0 auto 1rem;font-size:1.5rem;font-weight:300;text-align:center}.sidebar>h1 a{color:inherit;text-decoration:none}.sidebar>h1 .app-nav{display:block;position:static}.sidebar .sidebar-nav{line-height:2em;padding-bottom:40px}.sidebar li.collapse .app-sub-sidebar{display:none}.sidebar ul{margin:0 0 0 15px;padding:0}.sidebar li>p{font-weight:700;margin:0}.sidebar ul,.sidebar ul li{list-style:none}.sidebar ul li a{border-bottom:none;display:block}.sidebar ul li ul{padding-left:20px}.sidebar::-webkit-scrollbar{width:4px}.sidebar::-webkit-scrollbar-thumb{background:transparent;border-radius:4px}.sidebar:hover::-webkit-scrollbar-thumb{background:hsla(0,0%,53.3%,.4)}.sidebar:hover::-webkit-scrollbar-track{background:hsla(0,0%,53.3%,.1)}.sidebar-toggle{background-color:transparent;background-color:hsla(0,0%,100%,.8);border:0;outline:none;padding:10px;position:absolute;bottom:0;left:0;text-align:center;transition:opacity .3s;width:284px;z-index:30;cursor:pointer}.sidebar-toggle:hover .sidebar-toggle-button{opacity:.4}.sidebar-toggle span{background-color:#42b983;background-color:var(--theme-color,#42b983);display:block;margin-bottom:4px;width:16px;height:2px}body.sticky .sidebar,body.sticky .sidebar-toggle{position:fixed}.content{padding-top:60px;position:absolute;top:0;right:0;bottom:0;left:300px;transition:left .25s ease}.markdown-section{margin:0 auto;max-width:80%;padding:30px 15px 40px;position:relative}.markdown-section>*{box-sizing:border-box;font-size:inherit}.markdown-section>:first-child{margin-top:0!important}.markdown-section hr{border:none;border-bottom:1px solid #eee;margin:2em 0}.markdown-section iframe{border:1px solid #eee;width:1px;min-width:100%}.markdown-section table{border-collapse:collapse;border-spacing:0;display:block;margin-bottom:1rem;overflow:auto;width:100%}.markdown-section th{font-weight:700}.markdown-section td,.markdown-section th{border:1px solid #ddd;padding:6px 13px}.markdown-section tr{border-top:1px solid #ccc}.markdown-section p.tip,.markdown-section tr:nth-child(2n){background-color:#f8f8f8}.markdown-section p.tip{border-bottom-right-radius:2px;border-left:4px solid #f66;border-top-right-radius:2px;margin:2em 0;padding:12px 24px 12px 30px;position:relative}.markdown-section p.tip:before{background-color:#f66;border-radius:100%;color:#fff;content:"!";font-family:Dosis,Source Sans Pro,Helvetica Neue,Arial,sans-serif;font-size:14px;font-weight:700;left:-12px;line-height:20px;position:absolute;height:20px;width:20px;text-align:center;top:14px}.markdown-section p.tip code{background-color:#efefef}.markdown-section p.tip em{color:#34495e}.markdown-section p.warn{background:rgba(66,185,131,.1);border-radius:2px;padding:1rem}.markdown-section ul.task-list>li{list-style-type:none}body.close .sidebar{transform:translateX(-300px)}body.close .sidebar-toggle{width:auto}body.close .content{left:0}@media print{.app-nav,.github-corner,.sidebar,.sidebar-toggle{display:none}}@media screen and (max-width:768px){.github-corner,.sidebar,.sidebar-toggle{position:fixed}.app-nav{margin-top:16px}.app-nav li ul{top:30px}main{height:auto;min-height:100vh;overflow-x:hidden}.sidebar{left:-300px;transition:transform .25s ease-out}.content{left:0;max-width:100vw;position:static;padding-top:20px;transition:transform .25s ease}.app-nav,.github-corner{transition:transform .25s ease-out}.sidebar-toggle{background-color:transparent;width:auto;padding:30px 30px 10px 10px}body.close .sidebar{transform:translateX(300px)}body.close .sidebar-toggle{background-color:hsla(0,0%,100%,.8);transition:background-color 1s;width:284px;padding:10px}body.close .content{transform:translateX(300px)}body.close .app-nav,body.close .github-corner{display:none}.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave .56s ease-in-out}}@keyframes octocat-wave{0%,to{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}section.cover{position:relative;align-items:center;background-position:50%;background-repeat:no-repeat;background-size:cover;min-height:100vh;width:100%;display:none}section.cover.show{display:flex}section.cover.has-mask .mask{background-color:#fff;opacity:.8;position:absolute;top:0;bottom:0;width:100%}section.cover .cover-main{flex:1;margin:0 16px;text-align:center;position:relative}section.cover a{color:inherit}section.cover a,section.cover a:hover{text-decoration:none}section.cover p{line-height:1.5rem;margin:1em 0}section.cover h1{color:inherit;font-size:2.5rem;font-weight:300;margin:.625rem 0 2.5rem;position:relative;text-align:center}section.cover h1 a{display:block}section.cover h1 small{bottom:-.4375rem;font-size:1rem;position:absolute}section.cover blockquote{font-size:1.5rem;text-align:center}section.cover ul{line-height:1.8;list-style-type:none;margin:1em auto;max-width:500px;padding:0}section.cover .cover-main>p:last-child a{border-radius:2rem;border:1px solid #42b983;border-color:var(--theme-color,#42b983);box-sizing:border-box;color:#42b983;color:var(--theme-color,#42b983);display:inline-block;font-size:1.05rem;letter-spacing:.1rem;margin:.5rem 1rem;padding:.75em 2rem;text-decoration:none;transition:all .15s ease}section.cover .cover-main>p:last-child a:last-child{background-color:#42b983;background-color:var(--theme-color,#42b983);color:#fff}section.cover .cover-main>p:last-child a:last-child:hover{color:inherit;opacity:.8}section.cover .cover-main>p:last-child a:hover{color:inherit}section.cover blockquote>p>a{border-bottom:2px solid #42b983;border-bottom:2px solid var(--theme-color,#42b983);transition:color .3s}section.cover blockquote>p>a:hover{color:#42b983;color:var(--theme-color,#42b983)}.sidebar,body{background-color:#fff}.sidebar{color:#364149}.sidebar li{margin:6px 0}.sidebar ul li a{color:#505d6b;font-size:14px;font-weight:400;overflow:hidden;text-decoration:none;text-overflow:ellipsis;white-space:nowrap}.sidebar ul li a:hover{text-decoration:underline}.sidebar ul li ul{padding:0}.sidebar ul li.active>a{border-right:2px solid;color:#42b983;color:var(--theme-color,#42b983);font-weight:600}.app-sub-sidebar li:before{content:"-";padding-right:4px;float:left}.markdown-section h1,.markdown-section h2,.markdown-section h3,.markdown-section h4,.markdown-section strong{color:#2c3e50;font-weight:600}.markdown-section a{color:#42b983;color:var(--theme-color,#42b983);font-weight:600}.markdown-section h1{font-size:2rem;margin:0 0 1rem}.markdown-section h2{font-size:1.75rem;margin:45px 0 .8rem}.markdown-section h3{font-size:1.5rem;margin:40px 0 .6rem}.markdown-section h4{font-size:1.25rem}.markdown-section h5{font-size:1rem}.markdown-section h6{color:#777;font-size:1rem}.markdown-section figure,.markdown-section p{margin:1.2em 0}.markdown-section ol,.markdown-section p,.markdown-section ul{line-height:1.6rem;word-spacing:.05rem}.markdown-section ol,.markdown-section ul{padding-left:1.5rem}.markdown-section blockquote{border-left:4px solid #42b983;border-left:4px solid var(--theme-color,#42b983);color:#858585;margin:2em 0;padding-left:20px}.markdown-section blockquote p{font-weight:600;margin-left:0}.markdown-section iframe{margin:1em 0}.markdown-section em{color:#7f8c8d}.markdown-section code,.markdown-section output:after,.markdown-section pre{font-family:Roboto Mono,Monaco,courier,monospace}.markdown-section code,.markdown-section pre{background-color:#f8f8f8}.markdown-section output,.markdown-section pre{margin:1.2em 0;position:relative}.markdown-section output,.markdown-section pre>code{border-radius:2px;display:block}.markdown-section output:after,.markdown-section pre>code{-moz-osx-font-smoothing:initial;-webkit-font-smoothing:initial}.markdown-section code{border-radius:2px;color:#e96900;margin:0 2px;padding:3px 5px;white-space:pre-wrap}.markdown-section>:not(h1):not(h2):not(h3):not(h4):not(h5):not(h6) code{font-size:.8rem}.markdown-section pre{padding:0 1.4rem;line-height:1.5rem;overflow:auto;word-wrap:normal}.markdown-section pre>code{color:#525252;font-size:.8rem;padding:2.2em 5px;line-height:inherit;margin:0 2px;max-width:inherit;overflow:inherit;white-space:inherit}.markdown-section output{padding:1.7rem 1.4rem;border:1px dotted #ccc}.markdown-section output>:first-child{margin-top:0}.markdown-section output>:last-child{margin-bottom:0}.markdown-section code:after,.markdown-section code:before,.markdown-section output:after,.markdown-section output:before{letter-spacing:.05rem}.markdown-section output:after,.markdown-section pre:after{color:#ccc;font-size:.6rem;font-weight:600;height:15px;line-height:15px;padding:5px 10px 0;position:absolute;right:0;text-align:right;top:0;content:attr(data-lang)}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#8e908c}.token.namespace{opacity:.7}.token.boolean,.token.number{color:#c76b29}.token.punctuation{color:#525252}.token.property{color:#c08b30}.token.tag{color:#2973b7}.token.string{color:#42b983;color:var(--theme-color,#42b983)}.token.selector{color:#6679cc}.token.attr-name{color:#2973b7}.language-css .token.string,.style .token.string,.token.entity,.token.url{color:#22a2c9}.token.attr-value,.token.control,.token.directive,.token.unit{color:#42b983;color:var(--theme-color,#42b983)}.token.function,.token.keyword{color:#e96900}.token.atrule,.token.regex,.token.statement{color:#22a2c9}.token.placeholder,.token.variable{color:#3d8fd1}.token.deleted{text-decoration:line-through}.token.inserted{border-bottom:1px dotted #202746;text-decoration:none}.token.italic{font-style:italic}.token.bold,.token.important{font-weight:700}.token.important{color:#c94922}.token.entity{cursor:help}code .token{-moz-osx-font-smoothing:initial;-webkit-font-smoothing:initial;min-height:1.5rem;position:relative;left:auto} ================================================ FILE: doc/lib/js/docsify@4.js ================================================ !function(){function c(i){var o=Object.create(null);return function(e){var n=f(e)?e:JSON.stringify(e);return o[n]||(o[n]=i(e))}}var a=c(function(e){return e.replace(/([A-Z])/g,function(e){return"-"+e.toLowerCase()})}),u=Object.prototype.hasOwnProperty,m=Object.assign||function(e){for(var n=arguments,i=1;i=e||n.classList.contains("hidden")?S(h,"add","sticky"):S(h,"remove","sticky"))}function ee(e,n,o,i){var t=[];null!=(n=l(n))&&(t=k(n,"a"));var a,r=decodeURI(e.toURL(e.getCurrentPath()));return t.sort(function(e,n){return n.href.length-e.href.length}).forEach(function(e){var n=decodeURI(e.getAttribute("href")),i=o?e.parentNode:e;e.title=e.title||e.innerText,0!==r.indexOf(n)||a?S(i,"remove","active"):(a=e,S(i,"add","active"))}),i&&(v.title=a?a.title||a.innerText+" - "+J:J),a}function ne(e,n){for(var i=0;ithis.end&&e>=this.next}[this.direction]}},{key:"_defaultEase",value:function(e,n,i,o){return(e/=o/2)<1?i/2*e*e+n:-i/2*(--e*(e-2)-1)+n}}]),re);function re(){var e=0c){n=n||p;break}n=p}!n||(r=fe[ve(e,n.getAttribute("data-id"))])&&r!==a&&(a&&a.classList.remove("active"),r.classList.add("active"),a=r,!pe&&h.classList.contains("sticky")&&(e=i.clientHeight,r=a.offsetTop+a.clientHeight+40,a=a.offsetTop>=t.scrollTop&&r<=t.scrollTop+e,i.scrollTop=a?t.scrollTop:+r"']/),xe=/[&<>"']/g,Se=/[<>"']|&(?!#?\w+;)/,Ae=/[<>"']|&(?!#?\w+;)/g,$e={"&":"&","<":"<",">":">",'"':""","'":"'"};var ze=/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/gi;function Fe(e){return e.replace(ze,function(e,n){return"colon"===(n=n.toLowerCase())?":":"#"===n.charAt(0)?"x"===n.charAt(1)?String.fromCharCode(parseInt(n.substring(2),16)):String.fromCharCode(+n.substring(1)):""})}var Ee=/(^|[^\[])\^/g;var Te=/[^\w:]/g,Ce=/^$|^[a-z][a-z0-9+.-]*:|^[?#]/i;var Re={},je=/^[^:]+:\/*[^/]*$/,Oe=/^([^:]+:)[\s\S]*$/,Le=/^([^:]+:\/*[^/]*)[\s\S]*$/;function qe(e,n){Re[" "+e]||(je.test(e)?Re[" "+e]=e+"/":Re[" "+e]=Pe(e,"/",!0));var i=-1===(e=Re[" "+e]).indexOf(":");return"//"===n.substring(0,2)?i?n:e.replace(Oe,"$1")+n:"/"===n.charAt(0)?i?n:e.replace(Le,"$1")+n:e+n}function Pe(e,n,i){var o=e.length;if(0===o)return"";for(var t=0;tn)i.splice(n);else for(;i.length>=1,e+=e;return i+e},We=we.defaults,Xe=Be,Qe=Ze,Je=Me,Ke=Ve;function en(e,n,i){var o=n.href,t=n.title?Je(n.title):null,n=e[1].replace(/\\([\[\]])/g,"$1");return"!"!==e[0].charAt(0)?{type:"link",raw:i,href:o,title:t,text:n}:{type:"image",raw:i,href:o,title:t,text:Je(n)}}var nn=function(){function e(e){this.options=e||We}return e.prototype.space=function(e){e=this.rules.block.newline.exec(e);if(e)return 1=i.length?e.slice(i.length):e}).join("\n")}(i,n[3]||"");return{type:"code",raw:i,lang:n[2]&&n[2].trim(),text:e}}},e.prototype.heading=function(e){var n=this.rules.block.heading.exec(e);if(n){var i=n[2].trim();return/#$/.test(i)&&(e=Xe(i,"#"),!this.options.pedantic&&e&&!/ $/.test(e)||(i=e.trim())),{type:"heading",raw:n[0],depth:n[1].length,text:i}}},e.prototype.nptable=function(e){e=this.rules.block.nptable.exec(e);if(e){var n={type:"table",header:Qe(e[1].replace(/^ *| *\| *$/g,"")),align:e[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:e[3]?e[3].replace(/\n$/,"").split("\n"):[],raw:e[0]};if(n.header.length===n.align.length){for(var i=n.align.length,o=0;o ?/gm,"");return{type:"blockquote",raw:n[0],text:e}}},e.prototype.list=function(e){e=this.rules.block.list.exec(e);if(e){for(var n,i,o,t,a,r=e[0],c=e[2],u=1s[1].length:o[1].length>s[0].length||3/i.test(e[0])&&(n=!1),!i&&/^<(pre|code|kbd|script)(\s|>)/i.test(e[0])?i=!0:i&&/^<\/(pre|code|kbd|script)(\s|>)/i.test(e[0])&&(i=!1),{type:this.options.sanitize?"text":"html",raw:e[0],inLink:n,inRawBlock:i,text:this.options.sanitize?this.options.sanitizer?this.options.sanitizer(e[0]):Je(e[0]):e[0]}},e.prototype.link=function(e){var n=this.rules.inline.link.exec(e);if(n){e=n[2].trim();if(!this.options.pedantic&&/^$/.test(e))return;var i=Xe(e.slice(0,-1),"\\");if((e.length-i.length)%2==0)return}else{var o=Ke(n[2],"()");-1$/.test(e)?i.slice(1):i.slice(1,-1):i)&&i.replace(this.rules.inline._escapes,"$1"),title:o&&o.replace(this.rules.inline._escapes,"$1")},n[0])}},e.prototype.reflink=function(e,n){if((i=this.rules.inline.reflink.exec(e))||(i=this.rules.inline.nolink.exec(e))){var e=(i[2]||i[1]).replace(/\s+/g," ");if((e=n[e.toLowerCase()])&&e.href)return en(i,e,i[0]);var i=i[0].charAt(0);return{type:"text",raw:i,text:i}}},e.prototype.strong=function(e,n,i){void 0===i&&(i="");var o=this.rules.inline.strong.start.exec(e);if(o&&(!o[1]||o[1]&&(""===i||this.rules.inline.punctuation.exec(i)))){n=n.slice(-1*e.length);var t,a="**"===o[0]?this.rules.inline.strong.endAst:this.rules.inline.strong.endUnd;for(a.lastIndex=0;null!=(o=a.exec(n));)if(t=this.rules.inline.strong.middle.exec(n.slice(0,o.index+3)))return{type:"strong",raw:e.slice(0,t[0].length),text:e.slice(2,t[0].length-2)}}},e.prototype.em=function(e,n,i){void 0===i&&(i="");var o=this.rules.inline.em.start.exec(e);if(o&&(!o[1]||o[1]&&(""===i||this.rules.inline.punctuation.exec(i)))){n=n.slice(-1*e.length);var t,a="*"===o[0]?this.rules.inline.em.endAst:this.rules.inline.em.endUnd;for(a.lastIndex=0;null!=(o=a.exec(n));)if(t=this.rules.inline.em.middle.exec(n.slice(0,o.index+2)))return{type:"em",raw:e.slice(0,t[0].length),text:e.slice(1,t[0].length-1)}}},e.prototype.codespan=function(e){var n=this.rules.inline.code.exec(e);if(n){var i=n[2].replace(/\n/g," "),o=/[^ ]/.test(i),e=/^ /.test(i)&&/ $/.test(i);return o&&e&&(i=i.substring(1,i.length-1)),i=Je(i,!0),{type:"codespan",raw:n[0],text:i}}},e.prototype.br=function(e){e=this.rules.inline.br.exec(e);if(e)return{type:"br",raw:e[0]}},e.prototype.del=function(e){e=this.rules.inline.del.exec(e);if(e)return{type:"del",raw:e[0],text:e[2]}},e.prototype.autolink=function(e,n){e=this.rules.inline.autolink.exec(e);if(e){var i,n="@"===e[2]?"mailto:"+(i=Je(this.options.mangle?n(e[1]):e[1])):i=Je(e[1]);return{type:"link",raw:e[0],text:i,href:n,tokens:[{type:"text",raw:i,text:i}]}}},e.prototype.url=function(e,n){var i,o,t,a;if(i=this.rules.inline.url.exec(e)){if("@"===i[2])t="mailto:"+(o=Je(this.options.mangle?n(i[0]):i[0]));else{for(;a=i[0],i[0]=this.rules.inline._backpedal.exec(i[0])[0],a!==i[0];);o=Je(i[0]),t="www."===i[1]?"http://"+o:o}return{type:"link",raw:i[0],text:o,href:t,tokens:[{type:"text",raw:o,text:o}]}}},e.prototype.inlineText=function(e,n,i){e=this.rules.inline.text.exec(e);if(e){i=n?this.options.sanitize?this.options.sanitizer?this.options.sanitizer(e[0]):Je(e[0]):e[0]:Je(this.options.smartypants?i(e[0]):e[0]);return{type:"text",raw:e[0],text:i}}},e}(),Ze=De,Ve=Ne,De=Ue,Ne={newline:/^(?: *(?:\n|$))+/,code:/^( {4}[^\n]+(?:\n(?: *(?:\n|$))*)?)+/,fences:/^ {0,3}(`{3,}(?=[^`\n]*\n)|~{3,})([^\n]*)\n(?:|([\s\S]*?)\n)(?: {0,3}\1[~`]* *(?:\n+|$)|$)/,hr:/^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/,heading:/^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/,blockquote:/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/,list:/^( {0,3})(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?! {0,3}bull )\n*|\s*$)/,html:"^ {0,3}(?:<(script|pre|style)[\\s>][\\s\\S]*?(?:[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>\\n*|$)|\\n*|$)|\\n*|$)|)[\\s\\S]*?(?:\\n{2,}|$)|<(?!script|pre|style)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:\\n{2,}|$)|(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:\\n{2,}|$))",def:/^ {0,3}\[(label)\]: *\n? *]+)>?(?:(?: +\n? *| *\n *)(title))? *(?:\n+|$)/,nptable:Ze,table:Ze,lheading:/^([^\n]+)\n {0,3}(=+|-+) *(?:\n+|$)/,_paragraph:/^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html| +\n)[^\n]+)*)/,text:/^[^\n]+/,_label:/(?!\s*\])(?:\\[\[\]]|[^\[\]])+/,_title:/(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/};Ne.def=Ve(Ne.def).replace("label",Ne._label).replace("title",Ne._title).getRegex(),Ne.bullet=/(?:[*+-]|\d{1,9}[.)])/,Ne.item=/^( *)(bull) ?[^\n]*(?:\n(?! *bull ?)[^\n]*)*/,Ne.item=Ve(Ne.item,"gm").replace(/bull/g,Ne.bullet).getRegex(),Ne.listItemStart=Ve(/^( *)(bull)/).replace("bull",Ne.bullet).getRegex(),Ne.list=Ve(Ne.list).replace(/bull/g,Ne.bullet).replace("hr","\\n+(?=\\1?(?:(?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$))").replace("def","\\n+(?="+Ne.def.source+")").getRegex(),Ne._tag="address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul",Ne._comment=/|$)/,Ne.html=Ve(Ne.html,"i").replace("comment",Ne._comment).replace("tag",Ne._tag).replace("attribute",/ +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(),Ne.paragraph=Ve(Ne._paragraph).replace("hr",Ne.hr).replace("heading"," {0,3}#{1,6} ").replace("|lheading","").replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|!--)").replace("tag",Ne._tag).getRegex(),Ne.blockquote=Ve(Ne.blockquote).replace("paragraph",Ne.paragraph).getRegex(),Ne.normal=De({},Ne),Ne.gfm=De({},Ne.normal,{nptable:"^ *([^|\\n ].*\\|.*)\\n {0,3}([-:]+ *\\|[-| :]*)(?:\\n((?:(?!\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)",table:"^ *\\|(.+)\\n {0,3}\\|?( *[-:]+[-| :]*)(?:\\n *((?:(?!\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)"}),Ne.gfm.nptable=Ve(Ne.gfm.nptable).replace("hr",Ne.hr).replace("heading"," {0,3}#{1,6} ").replace("blockquote"," {0,3}>").replace("code"," {4}[^\\n]").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|!--)").replace("tag",Ne._tag).getRegex(),Ne.gfm.table=Ve(Ne.gfm.table).replace("hr",Ne.hr).replace("heading"," {0,3}#{1,6} ").replace("blockquote"," {0,3}>").replace("code"," {4}[^\\n]").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|!--)").replace("tag",Ne._tag).getRegex(),Ne.pedantic=De({},Ne.normal,{html:Ve("^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+? *(?:\\n{2,}|\\s*$)|\\s]*)*?/?> *(?:\\n{2,}|\\s*$))").replace("comment",Ne._comment).replace(/tag/g,"(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(),def:/^ *\[([^\]]+)\]: *]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,heading:/^(#{1,6})(.*)(?:\n+|$)/,fences:Ze,paragraph:Ve(Ne.normal._paragraph).replace("hr",Ne.hr).replace("heading"," *#{1,6} *[^\n]").replace("lheading",Ne.lheading).replace("blockquote"," {0,3}>").replace("|fences","").replace("|list","").replace("|html","").getRegex()});Ze={escape:/^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/,autolink:/^<(scheme:[^\s\x00-\x1f<>]*|email)>/,url:Ze,tag:"^comment|^|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^|^",link:/^!?\[(label)\]\(\s*(href)(?:\s+(title))?\s*\)/,reflink:/^!?\[(label)\]\[(?!\s*\])((?:\\[\[\]]?|[^\[\]\\])+)\]/,nolink:/^!?\[(?!\s*\])((?:\[[^\[\]]*\]|\\[\[\]]|[^\[\]])*)\](?:\[\])?/,reflinkSearch:"reflink|nolink(?!\\()",strong:{start:/^(?:(\*\*(?=[*punctuation]))|\*\*)(?![\s])|__/,middle:/^\*\*(?:(?:(?!overlapSkip)(?:[^*]|\\\*)|overlapSkip)|\*(?:(?!overlapSkip)(?:[^*]|\\\*)|overlapSkip)*?\*)+?\*\*$|^__(?![\s])((?:(?:(?!overlapSkip)(?:[^_]|\\_)|overlapSkip)|_(?:(?!overlapSkip)(?:[^_]|\\_)|overlapSkip)*?_)+?)__$/,endAst:/[^punctuation\s]\*\*(?!\*)|[punctuation]\*\*(?!\*)(?:(?=[punctuation_\s]|$))/,endUnd:/[^\s]__(?!_)(?:(?=[punctuation*\s])|$)/},em:{start:/^(?:(\*(?=[punctuation]))|\*)(?![*\s])|_/,middle:/^\*(?:(?:(?!overlapSkip)(?:[^*]|\\\*)|overlapSkip)|\*(?:(?!overlapSkip)(?:[^*]|\\\*)|overlapSkip)*?\*)+?\*$|^_(?![_\s])(?:(?:(?!overlapSkip)(?:[^_]|\\_)|overlapSkip)|_(?:(?!overlapSkip)(?:[^_]|\\_)|overlapSkip)*?_)+?_$/,endAst:/[^punctuation\s]\*(?!\*)|[punctuation]\*(?!\*)(?:(?=[punctuation_\s]|$))/,endUnd:/[^\s]_(?!_)(?:(?=[punctuation*\s])|$)/},code:/^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,br:/^( {2,}|\\)\n(?!\s*$)/,del:Ze,text:/^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\?@\\[\\]`^{|}~"};Ze.punctuation=Ve(Ze.punctuation).replace(/punctuation/g,Ze._punctuation).getRegex(),Ze._blockSkip="\\[[^\\]]*?\\]\\([^\\)]*?\\)|`[^`]*?`|<[^>]*?>",Ze._overlapSkip="__[^_]*?__|\\*\\*\\[^\\*\\]*?\\*\\*",Ze._comment=Ve(Ne._comment).replace("(?:--\x3e|$)","--\x3e").getRegex(),Ze.em.start=Ve(Ze.em.start).replace(/punctuation/g,Ze._punctuation).getRegex(),Ze.em.middle=Ve(Ze.em.middle).replace(/punctuation/g,Ze._punctuation).replace(/overlapSkip/g,Ze._overlapSkip).getRegex(),Ze.em.endAst=Ve(Ze.em.endAst,"g").replace(/punctuation/g,Ze._punctuation).getRegex(),Ze.em.endUnd=Ve(Ze.em.endUnd,"g").replace(/punctuation/g,Ze._punctuation).getRegex(),Ze.strong.start=Ve(Ze.strong.start).replace(/punctuation/g,Ze._punctuation).getRegex(),Ze.strong.middle=Ve(Ze.strong.middle).replace(/punctuation/g,Ze._punctuation).replace(/overlapSkip/g,Ze._overlapSkip).getRegex(),Ze.strong.endAst=Ve(Ze.strong.endAst,"g").replace(/punctuation/g,Ze._punctuation).getRegex(),Ze.strong.endUnd=Ve(Ze.strong.endUnd,"g").replace(/punctuation/g,Ze._punctuation).getRegex(),Ze.blockSkip=Ve(Ze._blockSkip,"g").getRegex(),Ze.overlapSkip=Ve(Ze._overlapSkip,"g").getRegex(),Ze._escapes=/\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/g,Ze._scheme=/[a-zA-Z][a-zA-Z0-9+.-]{1,31}/,Ze._email=/[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/,Ze.autolink=Ve(Ze.autolink).replace("scheme",Ze._scheme).replace("email",Ze._email).getRegex(),Ze._attribute=/\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/,Ze.tag=Ve(Ze.tag).replace("comment",Ze._comment).replace("attribute",Ze._attribute).getRegex(),Ze._label=/(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/,Ze._href=/<(?:\\.|[^\n<>\\])+>|[^\s\x00-\x1f]*/,Ze._title=/"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/,Ze.link=Ve(Ze.link).replace("label",Ze._label).replace("href",Ze._href).replace("title",Ze._title).getRegex(),Ze.reflink=Ve(Ze.reflink).replace("label",Ze._label).getRegex(),Ze.reflinkSearch=Ve(Ze.reflinkSearch,"g").replace("reflink",Ze.reflink).replace("nolink",Ze.nolink).getRegex(),Ze.normal=De({},Ze),Ze.pedantic=De({},Ze.normal,{strong:{start:/^__|\*\*/,middle:/^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,endAst:/\*\*(?!\*)/g,endUnd:/__(?!_)/g},em:{start:/^_|\*/,middle:/^()\*(?=\S)([\s\S]*?\S)\*(?!\*)|^_(?=\S)([\s\S]*?\S)_(?!_)/,endAst:/\*(?!\*)/g,endUnd:/_(?!_)/g},link:Ve(/^!?\[(label)\]\((.*?)\)/).replace("label",Ze._label).getRegex(),reflink:Ve(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label",Ze._label).getRegex()}),Ze.gfm=De({},Ze.normal,{escape:Ve(Ze.escape).replace("])","~|])").getRegex(),_extended_email:/[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/,url:/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/,_backpedal:/(?:[^?!.,:;*_~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_~)]+(?!$))+/,del:/^(~~?)(?=[^\s~])([\s\S]*?[^\s~])\1(?=[^~]|$)/,text:/^([`~]+|[^`~])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\'+(i?e:gn(e,!0))+"\n":"
"+(i?e:gn(e,!0))+"
\n"},e.prototype.blockquote=function(e){return"
\n"+e+"
\n"},e.prototype.html=function(e){return e},e.prototype.heading=function(e,n,i,o){return this.options.headerIds?"'+e+"\n":""+e+"\n"},e.prototype.hr=function(){return this.options.xhtml?"
\n":"
\n"},e.prototype.list=function(e,n,i){var o=n?"ol":"ul";return"<"+o+(n&&1!==i?' start="'+i+'"':"")+">\n"+e+"\n"},e.prototype.listitem=function(e){return"
  • "+e+"
  • \n"},e.prototype.checkbox=function(e){return" "},e.prototype.paragraph=function(e){return"

    "+e+"

    \n"},e.prototype.table=function(e,n){return"\n\n"+e+"\n"+(n=n&&""+n+"")+"
    \n"},e.prototype.tablerow=function(e){return"\n"+e+"\n"},e.prototype.tablecell=function(e,n){var i=n.header?"th":"td";return(n.align?"<"+i+' align="'+n.align+'">':"<"+i+">")+e+"\n"},e.prototype.strong=function(e){return""+e+""},e.prototype.em=function(e){return""+e+""},e.prototype.codespan=function(e){return""+e+""},e.prototype.br=function(){return this.options.xhtml?"
    ":"
    "},e.prototype.del=function(e){return""+e+""},e.prototype.link=function(e,n,i){if(null===(e=dn(this.options.sanitize,this.options.baseUrl,e)))return i;e='"},e.prototype.image=function(e,n,i){if(null===(e=dn(this.options.sanitize,this.options.baseUrl,e)))return i;i=''+i+'":">"},e.prototype.text=function(e){return e},e}(),ln=function(){function e(){}return e.prototype.strong=function(e){return e},e.prototype.em=function(e){return e},e.prototype.codespan=function(e){return e},e.prototype.del=function(e){return e},e.prototype.html=function(e){return e},e.prototype.text=function(e){return e},e.prototype.link=function(e,n,i){return""+i},e.prototype.image=function(e,n,i){return""+i},e.prototype.br=function(){return""},e}(),vn=function(){function e(){this.seen={}}return e.prototype.serialize=function(e){return e.toLowerCase().trim().replace(/<[!\/a-z].*?>/gi,"").replace(/[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,./:;<=>?@[\]^`{|}~]/g,"").replace(/\s/g,"-")},e.prototype.getNextSafeSlug=function(e,n){var i=e,o=0;if(this.seen.hasOwnProperty(i))for(o=this.seen[e];i=e+"-"+ ++o,this.seen.hasOwnProperty(i););return n||(this.seen[e]=o,this.seen[i]=0),i},e.prototype.slug=function(e,n){void 0===n&&(n={});e=this.serialize(e);return this.getNextSafeSlug(e,n.dryrun)},e}(),hn=we.defaults,_n=Ie,mn=function(){function i(e){this.options=e||hn,this.options.renderer=this.options.renderer||new sn,this.renderer=this.options.renderer,this.renderer.options=this.options,this.textRenderer=new ln,this.slugger=new vn}return i.parse=function(e,n){return new i(n).parse(e)},i.parseInline=function(e,n){return new i(n).parseInline(e)},i.prototype.parse=function(e,n){void 0===n&&(n=!0);for(var i,o,t,a,r,c,u,f,p,d,g,s,l,v,h,_="",m=e.length,b=0;bAn error occurred:

    "+wn(e.message+"",!0)+"
    ";throw e}}xn.options=xn.setOptions=function(e){return bn(xn.defaults,e),yn(xn.defaults),xn},xn.getDefaults=Me,xn.defaults=we,xn.use=function(a){var n,e=bn({},a);if(a.renderer){var i,r=xn.defaults.renderer||new sn;for(i in a.renderer)!function(o){var t=r[o];r[o]=function(){for(var e=[],n=arguments.length;n--;)e[n]=arguments[n];var i=a.renderer[o].apply(r,e);return i=!1===i?t.apply(r,e):i}}(i);e.renderer=r}if(a.tokenizer){var t,c=xn.defaults.tokenizer||new nn;for(t in a.tokenizer)!function(){var o=c[t];c[t]=function(){for(var e=[],n=arguments.length;n--;)e[n]=arguments[n];var i=a.tokenizer[t].apply(c,e);return i=!1===i?o.apply(c,e):i}}();e.tokenizer=c}a.walkTokens&&(n=xn.defaults.walkTokens,e.walkTokens=function(e){a.walkTokens(e),n&&n(e)}),xn.setOptions(e)},xn.walkTokens=function(e,n){for(var i=0,o=e;iAn error occurred:

    "+wn(e.message+"",!0)+"
    ";throw e}},xn.Parser=mn,xn.parser=mn.parse,xn.Renderer=sn,xn.TextRenderer=ln,xn.Lexer=fn,xn.lexer=fn.lex,xn.Tokenizer=nn,xn.Slugger=vn;var Sn=xn.parse=xn;function An(e,i){if(void 0===i&&(i='
      {inner}
    '),!e||!e.length)return"";var o="";return e.forEach(function(e){var n=e.title.replace(/(<([^>]+)>)/g,"");o+='
  • '+e.title+"
  • ",e.children&&(o+=An(e.children,i))}),i.replace("{inner}",o)}function $n(e,n){return'

    '+n.slice(5).trim()+"

    "}function zn(e,o){var t=[],a={};return e.forEach(function(e){var n=e.level||1,i=n-1;o?@[\]^`{|}~]/g;function Tn(e){return e.toLowerCase()}function Cn(e){if("string"!=typeof e)return"";var n=e.trim().replace(/[A-Z]+/g,Tn).replace(/<[^>]+>/g,"").replace(En,"").replace(/\s/g,"-").replace(/-+/g,"-").replace(/^(\d)/,"_$1"),e=Fn[n],e=u.call(Fn,n)?e+1:0;return n=(Fn[n]=e)?n+"-"+e:n}Cn.clear=function(){Fn={}};var Rn={baseURL:"https://github.githubassets.com/images/icons/emoji/",data:{100:"unicode/1f4af.png?v8",1234:"unicode/1f522.png?v8","+1":"unicode/1f44d.png?v8","-1":"unicode/1f44e.png?v8","1st_place_medal":"unicode/1f947.png?v8","2nd_place_medal":"unicode/1f948.png?v8","3rd_place_medal":"unicode/1f949.png?v8","8ball":"unicode/1f3b1.png?v8",a:"unicode/1f170.png?v8",ab:"unicode/1f18e.png?v8",abacus:"unicode/1f9ee.png?v8",abc:"unicode/1f524.png?v8",abcd:"unicode/1f521.png?v8",accept:"unicode/1f251.png?v8",accordion:"unicode/1fa97.png?v8",adhesive_bandage:"unicode/1fa79.png?v8",adult:"unicode/1f9d1.png?v8",aerial_tramway:"unicode/1f6a1.png?v8",afghanistan:"unicode/1f1e6-1f1eb.png?v8",airplane:"unicode/2708.png?v8",aland_islands:"unicode/1f1e6-1f1fd.png?v8",alarm_clock:"unicode/23f0.png?v8",albania:"unicode/1f1e6-1f1f1.png?v8",alembic:"unicode/2697.png?v8",algeria:"unicode/1f1e9-1f1ff.png?v8",alien:"unicode/1f47d.png?v8",ambulance:"unicode/1f691.png?v8",american_samoa:"unicode/1f1e6-1f1f8.png?v8",amphora:"unicode/1f3fa.png?v8",anatomical_heart:"unicode/1fac0.png?v8",anchor:"unicode/2693.png?v8",andorra:"unicode/1f1e6-1f1e9.png?v8",angel:"unicode/1f47c.png?v8",anger:"unicode/1f4a2.png?v8",angola:"unicode/1f1e6-1f1f4.png?v8",angry:"unicode/1f620.png?v8",anguilla:"unicode/1f1e6-1f1ee.png?v8",anguished:"unicode/1f627.png?v8",ant:"unicode/1f41c.png?v8",antarctica:"unicode/1f1e6-1f1f6.png?v8",antigua_barbuda:"unicode/1f1e6-1f1ec.png?v8",apple:"unicode/1f34e.png?v8",aquarius:"unicode/2652.png?v8",argentina:"unicode/1f1e6-1f1f7.png?v8",aries:"unicode/2648.png?v8",armenia:"unicode/1f1e6-1f1f2.png?v8",arrow_backward:"unicode/25c0.png?v8",arrow_double_down:"unicode/23ec.png?v8",arrow_double_up:"unicode/23eb.png?v8",arrow_down:"unicode/2b07.png?v8",arrow_down_small:"unicode/1f53d.png?v8",arrow_forward:"unicode/25b6.png?v8",arrow_heading_down:"unicode/2935.png?v8",arrow_heading_up:"unicode/2934.png?v8",arrow_left:"unicode/2b05.png?v8",arrow_lower_left:"unicode/2199.png?v8",arrow_lower_right:"unicode/2198.png?v8",arrow_right:"unicode/27a1.png?v8",arrow_right_hook:"unicode/21aa.png?v8",arrow_up:"unicode/2b06.png?v8",arrow_up_down:"unicode/2195.png?v8",arrow_up_small:"unicode/1f53c.png?v8",arrow_upper_left:"unicode/2196.png?v8",arrow_upper_right:"unicode/2197.png?v8",arrows_clockwise:"unicode/1f503.png?v8",arrows_counterclockwise:"unicode/1f504.png?v8",art:"unicode/1f3a8.png?v8",articulated_lorry:"unicode/1f69b.png?v8",artificial_satellite:"unicode/1f6f0.png?v8",artist:"unicode/1f9d1-1f3a8.png?v8",aruba:"unicode/1f1e6-1f1fc.png?v8",ascension_island:"unicode/1f1e6-1f1e8.png?v8",asterisk:"unicode/002a-20e3.png?v8",astonished:"unicode/1f632.png?v8",astronaut:"unicode/1f9d1-1f680.png?v8",athletic_shoe:"unicode/1f45f.png?v8",atm:"unicode/1f3e7.png?v8",atom:"atom.png?v8",atom_symbol:"unicode/269b.png?v8",australia:"unicode/1f1e6-1f1fa.png?v8",austria:"unicode/1f1e6-1f1f9.png?v8",auto_rickshaw:"unicode/1f6fa.png?v8",avocado:"unicode/1f951.png?v8",axe:"unicode/1fa93.png?v8",azerbaijan:"unicode/1f1e6-1f1ff.png?v8",b:"unicode/1f171.png?v8",baby:"unicode/1f476.png?v8",baby_bottle:"unicode/1f37c.png?v8",baby_chick:"unicode/1f424.png?v8",baby_symbol:"unicode/1f6bc.png?v8",back:"unicode/1f519.png?v8",bacon:"unicode/1f953.png?v8",badger:"unicode/1f9a1.png?v8",badminton:"unicode/1f3f8.png?v8",bagel:"unicode/1f96f.png?v8",baggage_claim:"unicode/1f6c4.png?v8",baguette_bread:"unicode/1f956.png?v8",bahamas:"unicode/1f1e7-1f1f8.png?v8",bahrain:"unicode/1f1e7-1f1ed.png?v8",balance_scale:"unicode/2696.png?v8",bald_man:"unicode/1f468-1f9b2.png?v8",bald_woman:"unicode/1f469-1f9b2.png?v8",ballet_shoes:"unicode/1fa70.png?v8",balloon:"unicode/1f388.png?v8",ballot_box:"unicode/1f5f3.png?v8",ballot_box_with_check:"unicode/2611.png?v8",bamboo:"unicode/1f38d.png?v8",banana:"unicode/1f34c.png?v8",bangbang:"unicode/203c.png?v8",bangladesh:"unicode/1f1e7-1f1e9.png?v8",banjo:"unicode/1fa95.png?v8",bank:"unicode/1f3e6.png?v8",bar_chart:"unicode/1f4ca.png?v8",barbados:"unicode/1f1e7-1f1e7.png?v8",barber:"unicode/1f488.png?v8",baseball:"unicode/26be.png?v8",basecamp:"basecamp.png?v8",basecampy:"basecampy.png?v8",basket:"unicode/1f9fa.png?v8",basketball:"unicode/1f3c0.png?v8",basketball_man:"unicode/26f9-2642.png?v8",basketball_woman:"unicode/26f9-2640.png?v8",bat:"unicode/1f987.png?v8",bath:"unicode/1f6c0.png?v8",bathtub:"unicode/1f6c1.png?v8",battery:"unicode/1f50b.png?v8",beach_umbrella:"unicode/1f3d6.png?v8",bear:"unicode/1f43b.png?v8",bearded_person:"unicode/1f9d4.png?v8",beaver:"unicode/1f9ab.png?v8",bed:"unicode/1f6cf.png?v8",bee:"unicode/1f41d.png?v8",beer:"unicode/1f37a.png?v8",beers:"unicode/1f37b.png?v8",beetle:"unicode/1fab2.png?v8",beginner:"unicode/1f530.png?v8",belarus:"unicode/1f1e7-1f1fe.png?v8",belgium:"unicode/1f1e7-1f1ea.png?v8",belize:"unicode/1f1e7-1f1ff.png?v8",bell:"unicode/1f514.png?v8",bell_pepper:"unicode/1fad1.png?v8",bellhop_bell:"unicode/1f6ce.png?v8",benin:"unicode/1f1e7-1f1ef.png?v8",bento:"unicode/1f371.png?v8",bermuda:"unicode/1f1e7-1f1f2.png?v8",beverage_box:"unicode/1f9c3.png?v8",bhutan:"unicode/1f1e7-1f1f9.png?v8",bicyclist:"unicode/1f6b4.png?v8",bike:"unicode/1f6b2.png?v8",biking_man:"unicode/1f6b4-2642.png?v8",biking_woman:"unicode/1f6b4-2640.png?v8",bikini:"unicode/1f459.png?v8",billed_cap:"unicode/1f9e2.png?v8",biohazard:"unicode/2623.png?v8",bird:"unicode/1f426.png?v8",birthday:"unicode/1f382.png?v8",bison:"unicode/1f9ac.png?v8",black_cat:"unicode/1f408-2b1b.png?v8",black_circle:"unicode/26ab.png?v8",black_flag:"unicode/1f3f4.png?v8",black_heart:"unicode/1f5a4.png?v8",black_joker:"unicode/1f0cf.png?v8",black_large_square:"unicode/2b1b.png?v8",black_medium_small_square:"unicode/25fe.png?v8",black_medium_square:"unicode/25fc.png?v8",black_nib:"unicode/2712.png?v8",black_small_square:"unicode/25aa.png?v8",black_square_button:"unicode/1f532.png?v8",blond_haired_man:"unicode/1f471-2642.png?v8",blond_haired_person:"unicode/1f471.png?v8",blond_haired_woman:"unicode/1f471-2640.png?v8",blonde_woman:"unicode/1f471-2640.png?v8",blossom:"unicode/1f33c.png?v8",blowfish:"unicode/1f421.png?v8",blue_book:"unicode/1f4d8.png?v8",blue_car:"unicode/1f699.png?v8",blue_heart:"unicode/1f499.png?v8",blue_square:"unicode/1f7e6.png?v8",blueberries:"unicode/1fad0.png?v8",blush:"unicode/1f60a.png?v8",boar:"unicode/1f417.png?v8",boat:"unicode/26f5.png?v8",bolivia:"unicode/1f1e7-1f1f4.png?v8",bomb:"unicode/1f4a3.png?v8",bone:"unicode/1f9b4.png?v8",book:"unicode/1f4d6.png?v8",bookmark:"unicode/1f516.png?v8",bookmark_tabs:"unicode/1f4d1.png?v8",books:"unicode/1f4da.png?v8",boom:"unicode/1f4a5.png?v8",boomerang:"unicode/1fa83.png?v8",boot:"unicode/1f462.png?v8",bosnia_herzegovina:"unicode/1f1e7-1f1e6.png?v8",botswana:"unicode/1f1e7-1f1fc.png?v8",bouncing_ball_man:"unicode/26f9-2642.png?v8",bouncing_ball_person:"unicode/26f9.png?v8",bouncing_ball_woman:"unicode/26f9-2640.png?v8",bouquet:"unicode/1f490.png?v8",bouvet_island:"unicode/1f1e7-1f1fb.png?v8",bow:"unicode/1f647.png?v8",bow_and_arrow:"unicode/1f3f9.png?v8",bowing_man:"unicode/1f647-2642.png?v8",bowing_woman:"unicode/1f647-2640.png?v8",bowl_with_spoon:"unicode/1f963.png?v8",bowling:"unicode/1f3b3.png?v8",bowtie:"bowtie.png?v8",boxing_glove:"unicode/1f94a.png?v8",boy:"unicode/1f466.png?v8",brain:"unicode/1f9e0.png?v8",brazil:"unicode/1f1e7-1f1f7.png?v8",bread:"unicode/1f35e.png?v8",breast_feeding:"unicode/1f931.png?v8",bricks:"unicode/1f9f1.png?v8",bride_with_veil:"unicode/1f470-2640.png?v8",bridge_at_night:"unicode/1f309.png?v8",briefcase:"unicode/1f4bc.png?v8",british_indian_ocean_territory:"unicode/1f1ee-1f1f4.png?v8",british_virgin_islands:"unicode/1f1fb-1f1ec.png?v8",broccoli:"unicode/1f966.png?v8",broken_heart:"unicode/1f494.png?v8",broom:"unicode/1f9f9.png?v8",brown_circle:"unicode/1f7e4.png?v8",brown_heart:"unicode/1f90e.png?v8",brown_square:"unicode/1f7eb.png?v8",brunei:"unicode/1f1e7-1f1f3.png?v8",bubble_tea:"unicode/1f9cb.png?v8",bucket:"unicode/1faa3.png?v8",bug:"unicode/1f41b.png?v8",building_construction:"unicode/1f3d7.png?v8",bulb:"unicode/1f4a1.png?v8",bulgaria:"unicode/1f1e7-1f1ec.png?v8",bullettrain_front:"unicode/1f685.png?v8",bullettrain_side:"unicode/1f684.png?v8",burkina_faso:"unicode/1f1e7-1f1eb.png?v8",burrito:"unicode/1f32f.png?v8",burundi:"unicode/1f1e7-1f1ee.png?v8",bus:"unicode/1f68c.png?v8",business_suit_levitating:"unicode/1f574.png?v8",busstop:"unicode/1f68f.png?v8",bust_in_silhouette:"unicode/1f464.png?v8",busts_in_silhouette:"unicode/1f465.png?v8",butter:"unicode/1f9c8.png?v8",butterfly:"unicode/1f98b.png?v8",cactus:"unicode/1f335.png?v8",cake:"unicode/1f370.png?v8",calendar:"unicode/1f4c6.png?v8",call_me_hand:"unicode/1f919.png?v8",calling:"unicode/1f4f2.png?v8",cambodia:"unicode/1f1f0-1f1ed.png?v8",camel:"unicode/1f42b.png?v8",camera:"unicode/1f4f7.png?v8",camera_flash:"unicode/1f4f8.png?v8",cameroon:"unicode/1f1e8-1f1f2.png?v8",camping:"unicode/1f3d5.png?v8",canada:"unicode/1f1e8-1f1e6.png?v8",canary_islands:"unicode/1f1ee-1f1e8.png?v8",cancer:"unicode/264b.png?v8",candle:"unicode/1f56f.png?v8",candy:"unicode/1f36c.png?v8",canned_food:"unicode/1f96b.png?v8",canoe:"unicode/1f6f6.png?v8",cape_verde:"unicode/1f1e8-1f1fb.png?v8",capital_abcd:"unicode/1f520.png?v8",capricorn:"unicode/2651.png?v8",car:"unicode/1f697.png?v8",card_file_box:"unicode/1f5c3.png?v8",card_index:"unicode/1f4c7.png?v8",card_index_dividers:"unicode/1f5c2.png?v8",caribbean_netherlands:"unicode/1f1e7-1f1f6.png?v8",carousel_horse:"unicode/1f3a0.png?v8",carpentry_saw:"unicode/1fa9a.png?v8",carrot:"unicode/1f955.png?v8",cartwheeling:"unicode/1f938.png?v8",cat:"unicode/1f431.png?v8",cat2:"unicode/1f408.png?v8",cayman_islands:"unicode/1f1f0-1f1fe.png?v8",cd:"unicode/1f4bf.png?v8",central_african_republic:"unicode/1f1e8-1f1eb.png?v8",ceuta_melilla:"unicode/1f1ea-1f1e6.png?v8",chad:"unicode/1f1f9-1f1e9.png?v8",chains:"unicode/26d3.png?v8",chair:"unicode/1fa91.png?v8",champagne:"unicode/1f37e.png?v8",chart:"unicode/1f4b9.png?v8",chart_with_downwards_trend:"unicode/1f4c9.png?v8",chart_with_upwards_trend:"unicode/1f4c8.png?v8",checkered_flag:"unicode/1f3c1.png?v8",cheese:"unicode/1f9c0.png?v8",cherries:"unicode/1f352.png?v8",cherry_blossom:"unicode/1f338.png?v8",chess_pawn:"unicode/265f.png?v8",chestnut:"unicode/1f330.png?v8",chicken:"unicode/1f414.png?v8",child:"unicode/1f9d2.png?v8",children_crossing:"unicode/1f6b8.png?v8",chile:"unicode/1f1e8-1f1f1.png?v8",chipmunk:"unicode/1f43f.png?v8",chocolate_bar:"unicode/1f36b.png?v8",chopsticks:"unicode/1f962.png?v8",christmas_island:"unicode/1f1e8-1f1fd.png?v8",christmas_tree:"unicode/1f384.png?v8",church:"unicode/26ea.png?v8",cinema:"unicode/1f3a6.png?v8",circus_tent:"unicode/1f3aa.png?v8",city_sunrise:"unicode/1f307.png?v8",city_sunset:"unicode/1f306.png?v8",cityscape:"unicode/1f3d9.png?v8",cl:"unicode/1f191.png?v8",clamp:"unicode/1f5dc.png?v8",clap:"unicode/1f44f.png?v8",clapper:"unicode/1f3ac.png?v8",classical_building:"unicode/1f3db.png?v8",climbing:"unicode/1f9d7.png?v8",climbing_man:"unicode/1f9d7-2642.png?v8",climbing_woman:"unicode/1f9d7-2640.png?v8",clinking_glasses:"unicode/1f942.png?v8",clipboard:"unicode/1f4cb.png?v8",clipperton_island:"unicode/1f1e8-1f1f5.png?v8",clock1:"unicode/1f550.png?v8",clock10:"unicode/1f559.png?v8",clock1030:"unicode/1f565.png?v8",clock11:"unicode/1f55a.png?v8",clock1130:"unicode/1f566.png?v8",clock12:"unicode/1f55b.png?v8",clock1230:"unicode/1f567.png?v8",clock130:"unicode/1f55c.png?v8",clock2:"unicode/1f551.png?v8",clock230:"unicode/1f55d.png?v8",clock3:"unicode/1f552.png?v8",clock330:"unicode/1f55e.png?v8",clock4:"unicode/1f553.png?v8",clock430:"unicode/1f55f.png?v8",clock5:"unicode/1f554.png?v8",clock530:"unicode/1f560.png?v8",clock6:"unicode/1f555.png?v8",clock630:"unicode/1f561.png?v8",clock7:"unicode/1f556.png?v8",clock730:"unicode/1f562.png?v8",clock8:"unicode/1f557.png?v8",clock830:"unicode/1f563.png?v8",clock9:"unicode/1f558.png?v8",clock930:"unicode/1f564.png?v8",closed_book:"unicode/1f4d5.png?v8",closed_lock_with_key:"unicode/1f510.png?v8",closed_umbrella:"unicode/1f302.png?v8",cloud:"unicode/2601.png?v8",cloud_with_lightning:"unicode/1f329.png?v8",cloud_with_lightning_and_rain:"unicode/26c8.png?v8",cloud_with_rain:"unicode/1f327.png?v8",cloud_with_snow:"unicode/1f328.png?v8",clown_face:"unicode/1f921.png?v8",clubs:"unicode/2663.png?v8",cn:"unicode/1f1e8-1f1f3.png?v8",coat:"unicode/1f9e5.png?v8",cockroach:"unicode/1fab3.png?v8",cocktail:"unicode/1f378.png?v8",coconut:"unicode/1f965.png?v8",cocos_islands:"unicode/1f1e8-1f1e8.png?v8",coffee:"unicode/2615.png?v8",coffin:"unicode/26b0.png?v8",coin:"unicode/1fa99.png?v8",cold_face:"unicode/1f976.png?v8",cold_sweat:"unicode/1f630.png?v8",collision:"unicode/1f4a5.png?v8",colombia:"unicode/1f1e8-1f1f4.png?v8",comet:"unicode/2604.png?v8",comoros:"unicode/1f1f0-1f1f2.png?v8",compass:"unicode/1f9ed.png?v8",computer:"unicode/1f4bb.png?v8",computer_mouse:"unicode/1f5b1.png?v8",confetti_ball:"unicode/1f38a.png?v8",confounded:"unicode/1f616.png?v8",confused:"unicode/1f615.png?v8",congo_brazzaville:"unicode/1f1e8-1f1ec.png?v8",congo_kinshasa:"unicode/1f1e8-1f1e9.png?v8",congratulations:"unicode/3297.png?v8",construction:"unicode/1f6a7.png?v8",construction_worker:"unicode/1f477.png?v8",construction_worker_man:"unicode/1f477-2642.png?v8",construction_worker_woman:"unicode/1f477-2640.png?v8",control_knobs:"unicode/1f39b.png?v8",convenience_store:"unicode/1f3ea.png?v8",cook:"unicode/1f9d1-1f373.png?v8",cook_islands:"unicode/1f1e8-1f1f0.png?v8",cookie:"unicode/1f36a.png?v8",cool:"unicode/1f192.png?v8",cop:"unicode/1f46e.png?v8",copyright:"unicode/00a9.png?v8",corn:"unicode/1f33d.png?v8",costa_rica:"unicode/1f1e8-1f1f7.png?v8",cote_divoire:"unicode/1f1e8-1f1ee.png?v8",couch_and_lamp:"unicode/1f6cb.png?v8",couple:"unicode/1f46b.png?v8",couple_with_heart:"unicode/1f491.png?v8",couple_with_heart_man_man:"unicode/1f468-2764-1f468.png?v8",couple_with_heart_woman_man:"unicode/1f469-2764-1f468.png?v8",couple_with_heart_woman_woman:"unicode/1f469-2764-1f469.png?v8",couplekiss:"unicode/1f48f.png?v8",couplekiss_man_man:"unicode/1f468-2764-1f48b-1f468.png?v8",couplekiss_man_woman:"unicode/1f469-2764-1f48b-1f468.png?v8",couplekiss_woman_woman:"unicode/1f469-2764-1f48b-1f469.png?v8",cow:"unicode/1f42e.png?v8",cow2:"unicode/1f404.png?v8",cowboy_hat_face:"unicode/1f920.png?v8",crab:"unicode/1f980.png?v8",crayon:"unicode/1f58d.png?v8",credit_card:"unicode/1f4b3.png?v8",crescent_moon:"unicode/1f319.png?v8",cricket:"unicode/1f997.png?v8",cricket_game:"unicode/1f3cf.png?v8",croatia:"unicode/1f1ed-1f1f7.png?v8",crocodile:"unicode/1f40a.png?v8",croissant:"unicode/1f950.png?v8",crossed_fingers:"unicode/1f91e.png?v8",crossed_flags:"unicode/1f38c.png?v8",crossed_swords:"unicode/2694.png?v8",crown:"unicode/1f451.png?v8",cry:"unicode/1f622.png?v8",crying_cat_face:"unicode/1f63f.png?v8",crystal_ball:"unicode/1f52e.png?v8",cuba:"unicode/1f1e8-1f1fa.png?v8",cucumber:"unicode/1f952.png?v8",cup_with_straw:"unicode/1f964.png?v8",cupcake:"unicode/1f9c1.png?v8",cupid:"unicode/1f498.png?v8",curacao:"unicode/1f1e8-1f1fc.png?v8",curling_stone:"unicode/1f94c.png?v8",curly_haired_man:"unicode/1f468-1f9b1.png?v8",curly_haired_woman:"unicode/1f469-1f9b1.png?v8",curly_loop:"unicode/27b0.png?v8",currency_exchange:"unicode/1f4b1.png?v8",curry:"unicode/1f35b.png?v8",cursing_face:"unicode/1f92c.png?v8",custard:"unicode/1f36e.png?v8",customs:"unicode/1f6c3.png?v8",cut_of_meat:"unicode/1f969.png?v8",cyclone:"unicode/1f300.png?v8",cyprus:"unicode/1f1e8-1f1fe.png?v8",czech_republic:"unicode/1f1e8-1f1ff.png?v8",dagger:"unicode/1f5e1.png?v8",dancer:"unicode/1f483.png?v8",dancers:"unicode/1f46f.png?v8",dancing_men:"unicode/1f46f-2642.png?v8",dancing_women:"unicode/1f46f-2640.png?v8",dango:"unicode/1f361.png?v8",dark_sunglasses:"unicode/1f576.png?v8",dart:"unicode/1f3af.png?v8",dash:"unicode/1f4a8.png?v8",date:"unicode/1f4c5.png?v8",de:"unicode/1f1e9-1f1ea.png?v8",deaf_man:"unicode/1f9cf-2642.png?v8",deaf_person:"unicode/1f9cf.png?v8",deaf_woman:"unicode/1f9cf-2640.png?v8",deciduous_tree:"unicode/1f333.png?v8",deer:"unicode/1f98c.png?v8",denmark:"unicode/1f1e9-1f1f0.png?v8",department_store:"unicode/1f3ec.png?v8",derelict_house:"unicode/1f3da.png?v8",desert:"unicode/1f3dc.png?v8",desert_island:"unicode/1f3dd.png?v8",desktop_computer:"unicode/1f5a5.png?v8",detective:"unicode/1f575.png?v8",diamond_shape_with_a_dot_inside:"unicode/1f4a0.png?v8",diamonds:"unicode/2666.png?v8",diego_garcia:"unicode/1f1e9-1f1ec.png?v8",disappointed:"unicode/1f61e.png?v8",disappointed_relieved:"unicode/1f625.png?v8",disguised_face:"unicode/1f978.png?v8",diving_mask:"unicode/1f93f.png?v8",diya_lamp:"unicode/1fa94.png?v8",dizzy:"unicode/1f4ab.png?v8",dizzy_face:"unicode/1f635.png?v8",djibouti:"unicode/1f1e9-1f1ef.png?v8",dna:"unicode/1f9ec.png?v8",do_not_litter:"unicode/1f6af.png?v8",dodo:"unicode/1f9a4.png?v8",dog:"unicode/1f436.png?v8",dog2:"unicode/1f415.png?v8",dollar:"unicode/1f4b5.png?v8",dolls:"unicode/1f38e.png?v8",dolphin:"unicode/1f42c.png?v8",dominica:"unicode/1f1e9-1f1f2.png?v8",dominican_republic:"unicode/1f1e9-1f1f4.png?v8",door:"unicode/1f6aa.png?v8",doughnut:"unicode/1f369.png?v8",dove:"unicode/1f54a.png?v8",dragon:"unicode/1f409.png?v8",dragon_face:"unicode/1f432.png?v8",dress:"unicode/1f457.png?v8",dromedary_camel:"unicode/1f42a.png?v8",drooling_face:"unicode/1f924.png?v8",drop_of_blood:"unicode/1fa78.png?v8",droplet:"unicode/1f4a7.png?v8",drum:"unicode/1f941.png?v8",duck:"unicode/1f986.png?v8",dumpling:"unicode/1f95f.png?v8",dvd:"unicode/1f4c0.png?v8","e-mail":"unicode/1f4e7.png?v8",eagle:"unicode/1f985.png?v8",ear:"unicode/1f442.png?v8",ear_of_rice:"unicode/1f33e.png?v8",ear_with_hearing_aid:"unicode/1f9bb.png?v8",earth_africa:"unicode/1f30d.png?v8",earth_americas:"unicode/1f30e.png?v8",earth_asia:"unicode/1f30f.png?v8",ecuador:"unicode/1f1ea-1f1e8.png?v8",egg:"unicode/1f95a.png?v8",eggplant:"unicode/1f346.png?v8",egypt:"unicode/1f1ea-1f1ec.png?v8",eight:"unicode/0038-20e3.png?v8",eight_pointed_black_star:"unicode/2734.png?v8",eight_spoked_asterisk:"unicode/2733.png?v8",eject_button:"unicode/23cf.png?v8",el_salvador:"unicode/1f1f8-1f1fb.png?v8",electric_plug:"unicode/1f50c.png?v8",electron:"electron.png?v8",elephant:"unicode/1f418.png?v8",elevator:"unicode/1f6d7.png?v8",elf:"unicode/1f9dd.png?v8",elf_man:"unicode/1f9dd-2642.png?v8",elf_woman:"unicode/1f9dd-2640.png?v8",email:"unicode/1f4e7.png?v8",end:"unicode/1f51a.png?v8",england:"unicode/1f3f4-e0067-e0062-e0065-e006e-e0067-e007f.png?v8",envelope:"unicode/2709.png?v8",envelope_with_arrow:"unicode/1f4e9.png?v8",equatorial_guinea:"unicode/1f1ec-1f1f6.png?v8",eritrea:"unicode/1f1ea-1f1f7.png?v8",es:"unicode/1f1ea-1f1f8.png?v8",estonia:"unicode/1f1ea-1f1ea.png?v8",ethiopia:"unicode/1f1ea-1f1f9.png?v8",eu:"unicode/1f1ea-1f1fa.png?v8",euro:"unicode/1f4b6.png?v8",european_castle:"unicode/1f3f0.png?v8",european_post_office:"unicode/1f3e4.png?v8",european_union:"unicode/1f1ea-1f1fa.png?v8",evergreen_tree:"unicode/1f332.png?v8",exclamation:"unicode/2757.png?v8",exploding_head:"unicode/1f92f.png?v8",expressionless:"unicode/1f611.png?v8",eye:"unicode/1f441.png?v8",eye_speech_bubble:"unicode/1f441-1f5e8.png?v8",eyeglasses:"unicode/1f453.png?v8",eyes:"unicode/1f440.png?v8",face_exhaling:"unicode/1f62e-1f4a8.png?v8",face_in_clouds:"unicode/1f636-1f32b.png?v8",face_with_head_bandage:"unicode/1f915.png?v8",face_with_spiral_eyes:"unicode/1f635-1f4ab.png?v8",face_with_thermometer:"unicode/1f912.png?v8",facepalm:"unicode/1f926.png?v8",facepunch:"unicode/1f44a.png?v8",factory:"unicode/1f3ed.png?v8",factory_worker:"unicode/1f9d1-1f3ed.png?v8",fairy:"unicode/1f9da.png?v8",fairy_man:"unicode/1f9da-2642.png?v8",fairy_woman:"unicode/1f9da-2640.png?v8",falafel:"unicode/1f9c6.png?v8",falkland_islands:"unicode/1f1eb-1f1f0.png?v8",fallen_leaf:"unicode/1f342.png?v8",family:"unicode/1f46a.png?v8",family_man_boy:"unicode/1f468-1f466.png?v8",family_man_boy_boy:"unicode/1f468-1f466-1f466.png?v8",family_man_girl:"unicode/1f468-1f467.png?v8",family_man_girl_boy:"unicode/1f468-1f467-1f466.png?v8",family_man_girl_girl:"unicode/1f468-1f467-1f467.png?v8",family_man_man_boy:"unicode/1f468-1f468-1f466.png?v8",family_man_man_boy_boy:"unicode/1f468-1f468-1f466-1f466.png?v8",family_man_man_girl:"unicode/1f468-1f468-1f467.png?v8",family_man_man_girl_boy:"unicode/1f468-1f468-1f467-1f466.png?v8",family_man_man_girl_girl:"unicode/1f468-1f468-1f467-1f467.png?v8",family_man_woman_boy:"unicode/1f468-1f469-1f466.png?v8",family_man_woman_boy_boy:"unicode/1f468-1f469-1f466-1f466.png?v8",family_man_woman_girl:"unicode/1f468-1f469-1f467.png?v8",family_man_woman_girl_boy:"unicode/1f468-1f469-1f467-1f466.png?v8",family_man_woman_girl_girl:"unicode/1f468-1f469-1f467-1f467.png?v8",family_woman_boy:"unicode/1f469-1f466.png?v8",family_woman_boy_boy:"unicode/1f469-1f466-1f466.png?v8",family_woman_girl:"unicode/1f469-1f467.png?v8",family_woman_girl_boy:"unicode/1f469-1f467-1f466.png?v8",family_woman_girl_girl:"unicode/1f469-1f467-1f467.png?v8",family_woman_woman_boy:"unicode/1f469-1f469-1f466.png?v8",family_woman_woman_boy_boy:"unicode/1f469-1f469-1f466-1f466.png?v8",family_woman_woman_girl:"unicode/1f469-1f469-1f467.png?v8",family_woman_woman_girl_boy:"unicode/1f469-1f469-1f467-1f466.png?v8",family_woman_woman_girl_girl:"unicode/1f469-1f469-1f467-1f467.png?v8",farmer:"unicode/1f9d1-1f33e.png?v8",faroe_islands:"unicode/1f1eb-1f1f4.png?v8",fast_forward:"unicode/23e9.png?v8",fax:"unicode/1f4e0.png?v8",fearful:"unicode/1f628.png?v8",feather:"unicode/1fab6.png?v8",feelsgood:"feelsgood.png?v8",feet:"unicode/1f43e.png?v8",female_detective:"unicode/1f575-2640.png?v8",female_sign:"unicode/2640.png?v8",ferris_wheel:"unicode/1f3a1.png?v8",ferry:"unicode/26f4.png?v8",field_hockey:"unicode/1f3d1.png?v8",fiji:"unicode/1f1eb-1f1ef.png?v8",file_cabinet:"unicode/1f5c4.png?v8",file_folder:"unicode/1f4c1.png?v8",film_projector:"unicode/1f4fd.png?v8",film_strip:"unicode/1f39e.png?v8",finland:"unicode/1f1eb-1f1ee.png?v8",finnadie:"finnadie.png?v8",fire:"unicode/1f525.png?v8",fire_engine:"unicode/1f692.png?v8",fire_extinguisher:"unicode/1f9ef.png?v8",firecracker:"unicode/1f9e8.png?v8",firefighter:"unicode/1f9d1-1f692.png?v8",fireworks:"unicode/1f386.png?v8",first_quarter_moon:"unicode/1f313.png?v8",first_quarter_moon_with_face:"unicode/1f31b.png?v8",fish:"unicode/1f41f.png?v8",fish_cake:"unicode/1f365.png?v8",fishing_pole_and_fish:"unicode/1f3a3.png?v8",fist:"unicode/270a.png?v8",fist_left:"unicode/1f91b.png?v8",fist_oncoming:"unicode/1f44a.png?v8",fist_raised:"unicode/270a.png?v8",fist_right:"unicode/1f91c.png?v8",five:"unicode/0035-20e3.png?v8",flags:"unicode/1f38f.png?v8",flamingo:"unicode/1f9a9.png?v8",flashlight:"unicode/1f526.png?v8",flat_shoe:"unicode/1f97f.png?v8",flatbread:"unicode/1fad3.png?v8",fleur_de_lis:"unicode/269c.png?v8",flight_arrival:"unicode/1f6ec.png?v8",flight_departure:"unicode/1f6eb.png?v8",flipper:"unicode/1f42c.png?v8",floppy_disk:"unicode/1f4be.png?v8",flower_playing_cards:"unicode/1f3b4.png?v8",flushed:"unicode/1f633.png?v8",fly:"unicode/1fab0.png?v8",flying_disc:"unicode/1f94f.png?v8",flying_saucer:"unicode/1f6f8.png?v8",fog:"unicode/1f32b.png?v8",foggy:"unicode/1f301.png?v8",fondue:"unicode/1fad5.png?v8",foot:"unicode/1f9b6.png?v8",football:"unicode/1f3c8.png?v8",footprints:"unicode/1f463.png?v8",fork_and_knife:"unicode/1f374.png?v8",fortune_cookie:"unicode/1f960.png?v8",fountain:"unicode/26f2.png?v8",fountain_pen:"unicode/1f58b.png?v8",four:"unicode/0034-20e3.png?v8",four_leaf_clover:"unicode/1f340.png?v8",fox_face:"unicode/1f98a.png?v8",fr:"unicode/1f1eb-1f1f7.png?v8",framed_picture:"unicode/1f5bc.png?v8",free:"unicode/1f193.png?v8",french_guiana:"unicode/1f1ec-1f1eb.png?v8",french_polynesia:"unicode/1f1f5-1f1eb.png?v8",french_southern_territories:"unicode/1f1f9-1f1eb.png?v8",fried_egg:"unicode/1f373.png?v8",fried_shrimp:"unicode/1f364.png?v8",fries:"unicode/1f35f.png?v8",frog:"unicode/1f438.png?v8",frowning:"unicode/1f626.png?v8",frowning_face:"unicode/2639.png?v8",frowning_man:"unicode/1f64d-2642.png?v8",frowning_person:"unicode/1f64d.png?v8",frowning_woman:"unicode/1f64d-2640.png?v8",fu:"unicode/1f595.png?v8",fuelpump:"unicode/26fd.png?v8",full_moon:"unicode/1f315.png?v8",full_moon_with_face:"unicode/1f31d.png?v8",funeral_urn:"unicode/26b1.png?v8",gabon:"unicode/1f1ec-1f1e6.png?v8",gambia:"unicode/1f1ec-1f1f2.png?v8",game_die:"unicode/1f3b2.png?v8",garlic:"unicode/1f9c4.png?v8",gb:"unicode/1f1ec-1f1e7.png?v8",gear:"unicode/2699.png?v8",gem:"unicode/1f48e.png?v8",gemini:"unicode/264a.png?v8",genie:"unicode/1f9de.png?v8",genie_man:"unicode/1f9de-2642.png?v8",genie_woman:"unicode/1f9de-2640.png?v8",georgia:"unicode/1f1ec-1f1ea.png?v8",ghana:"unicode/1f1ec-1f1ed.png?v8",ghost:"unicode/1f47b.png?v8",gibraltar:"unicode/1f1ec-1f1ee.png?v8",gift:"unicode/1f381.png?v8",gift_heart:"unicode/1f49d.png?v8",giraffe:"unicode/1f992.png?v8",girl:"unicode/1f467.png?v8",globe_with_meridians:"unicode/1f310.png?v8",gloves:"unicode/1f9e4.png?v8",goal_net:"unicode/1f945.png?v8",goat:"unicode/1f410.png?v8",goberserk:"goberserk.png?v8",godmode:"godmode.png?v8",goggles:"unicode/1f97d.png?v8",golf:"unicode/26f3.png?v8",golfing:"unicode/1f3cc.png?v8",golfing_man:"unicode/1f3cc-2642.png?v8",golfing_woman:"unicode/1f3cc-2640.png?v8",gorilla:"unicode/1f98d.png?v8",grapes:"unicode/1f347.png?v8",greece:"unicode/1f1ec-1f1f7.png?v8",green_apple:"unicode/1f34f.png?v8",green_book:"unicode/1f4d7.png?v8",green_circle:"unicode/1f7e2.png?v8",green_heart:"unicode/1f49a.png?v8",green_salad:"unicode/1f957.png?v8",green_square:"unicode/1f7e9.png?v8",greenland:"unicode/1f1ec-1f1f1.png?v8",grenada:"unicode/1f1ec-1f1e9.png?v8",grey_exclamation:"unicode/2755.png?v8",grey_question:"unicode/2754.png?v8",grimacing:"unicode/1f62c.png?v8",grin:"unicode/1f601.png?v8",grinning:"unicode/1f600.png?v8",guadeloupe:"unicode/1f1ec-1f1f5.png?v8",guam:"unicode/1f1ec-1f1fa.png?v8",guard:"unicode/1f482.png?v8",guardsman:"unicode/1f482-2642.png?v8",guardswoman:"unicode/1f482-2640.png?v8",guatemala:"unicode/1f1ec-1f1f9.png?v8",guernsey:"unicode/1f1ec-1f1ec.png?v8",guide_dog:"unicode/1f9ae.png?v8",guinea:"unicode/1f1ec-1f1f3.png?v8",guinea_bissau:"unicode/1f1ec-1f1fc.png?v8",guitar:"unicode/1f3b8.png?v8",gun:"unicode/1f52b.png?v8",guyana:"unicode/1f1ec-1f1fe.png?v8",haircut:"unicode/1f487.png?v8",haircut_man:"unicode/1f487-2642.png?v8",haircut_woman:"unicode/1f487-2640.png?v8",haiti:"unicode/1f1ed-1f1f9.png?v8",hamburger:"unicode/1f354.png?v8",hammer:"unicode/1f528.png?v8",hammer_and_pick:"unicode/2692.png?v8",hammer_and_wrench:"unicode/1f6e0.png?v8",hamster:"unicode/1f439.png?v8",hand:"unicode/270b.png?v8",hand_over_mouth:"unicode/1f92d.png?v8",handbag:"unicode/1f45c.png?v8",handball_person:"unicode/1f93e.png?v8",handshake:"unicode/1f91d.png?v8",hankey:"unicode/1f4a9.png?v8",hash:"unicode/0023-20e3.png?v8",hatched_chick:"unicode/1f425.png?v8",hatching_chick:"unicode/1f423.png?v8",headphones:"unicode/1f3a7.png?v8",headstone:"unicode/1faa6.png?v8",health_worker:"unicode/1f9d1-2695.png?v8",hear_no_evil:"unicode/1f649.png?v8",heard_mcdonald_islands:"unicode/1f1ed-1f1f2.png?v8",heart:"unicode/2764.png?v8",heart_decoration:"unicode/1f49f.png?v8",heart_eyes:"unicode/1f60d.png?v8",heart_eyes_cat:"unicode/1f63b.png?v8",heart_on_fire:"unicode/2764-1f525.png?v8",heartbeat:"unicode/1f493.png?v8",heartpulse:"unicode/1f497.png?v8",hearts:"unicode/2665.png?v8",heavy_check_mark:"unicode/2714.png?v8",heavy_division_sign:"unicode/2797.png?v8",heavy_dollar_sign:"unicode/1f4b2.png?v8",heavy_exclamation_mark:"unicode/2757.png?v8",heavy_heart_exclamation:"unicode/2763.png?v8",heavy_minus_sign:"unicode/2796.png?v8",heavy_multiplication_x:"unicode/2716.png?v8",heavy_plus_sign:"unicode/2795.png?v8",hedgehog:"unicode/1f994.png?v8",helicopter:"unicode/1f681.png?v8",herb:"unicode/1f33f.png?v8",hibiscus:"unicode/1f33a.png?v8",high_brightness:"unicode/1f506.png?v8",high_heel:"unicode/1f460.png?v8",hiking_boot:"unicode/1f97e.png?v8",hindu_temple:"unicode/1f6d5.png?v8",hippopotamus:"unicode/1f99b.png?v8",hocho:"unicode/1f52a.png?v8",hole:"unicode/1f573.png?v8",honduras:"unicode/1f1ed-1f1f3.png?v8",honey_pot:"unicode/1f36f.png?v8",honeybee:"unicode/1f41d.png?v8",hong_kong:"unicode/1f1ed-1f1f0.png?v8",hook:"unicode/1fa9d.png?v8",horse:"unicode/1f434.png?v8",horse_racing:"unicode/1f3c7.png?v8",hospital:"unicode/1f3e5.png?v8",hot_face:"unicode/1f975.png?v8",hot_pepper:"unicode/1f336.png?v8",hotdog:"unicode/1f32d.png?v8",hotel:"unicode/1f3e8.png?v8",hotsprings:"unicode/2668.png?v8",hourglass:"unicode/231b.png?v8",hourglass_flowing_sand:"unicode/23f3.png?v8",house:"unicode/1f3e0.png?v8",house_with_garden:"unicode/1f3e1.png?v8",houses:"unicode/1f3d8.png?v8",hugs:"unicode/1f917.png?v8",hungary:"unicode/1f1ed-1f1fa.png?v8",hurtrealbad:"hurtrealbad.png?v8",hushed:"unicode/1f62f.png?v8",hut:"unicode/1f6d6.png?v8",ice_cream:"unicode/1f368.png?v8",ice_cube:"unicode/1f9ca.png?v8",ice_hockey:"unicode/1f3d2.png?v8",ice_skate:"unicode/26f8.png?v8",icecream:"unicode/1f366.png?v8",iceland:"unicode/1f1ee-1f1f8.png?v8",id:"unicode/1f194.png?v8",ideograph_advantage:"unicode/1f250.png?v8",imp:"unicode/1f47f.png?v8",inbox_tray:"unicode/1f4e5.png?v8",incoming_envelope:"unicode/1f4e8.png?v8",india:"unicode/1f1ee-1f1f3.png?v8",indonesia:"unicode/1f1ee-1f1e9.png?v8",infinity:"unicode/267e.png?v8",information_desk_person:"unicode/1f481.png?v8",information_source:"unicode/2139.png?v8",innocent:"unicode/1f607.png?v8",interrobang:"unicode/2049.png?v8",iphone:"unicode/1f4f1.png?v8",iran:"unicode/1f1ee-1f1f7.png?v8",iraq:"unicode/1f1ee-1f1f6.png?v8",ireland:"unicode/1f1ee-1f1ea.png?v8",isle_of_man:"unicode/1f1ee-1f1f2.png?v8",israel:"unicode/1f1ee-1f1f1.png?v8",it:"unicode/1f1ee-1f1f9.png?v8",izakaya_lantern:"unicode/1f3ee.png?v8",jack_o_lantern:"unicode/1f383.png?v8",jamaica:"unicode/1f1ef-1f1f2.png?v8",japan:"unicode/1f5fe.png?v8",japanese_castle:"unicode/1f3ef.png?v8",japanese_goblin:"unicode/1f47a.png?v8",japanese_ogre:"unicode/1f479.png?v8",jeans:"unicode/1f456.png?v8",jersey:"unicode/1f1ef-1f1ea.png?v8",jigsaw:"unicode/1f9e9.png?v8",jordan:"unicode/1f1ef-1f1f4.png?v8",joy:"unicode/1f602.png?v8",joy_cat:"unicode/1f639.png?v8",joystick:"unicode/1f579.png?v8",jp:"unicode/1f1ef-1f1f5.png?v8",judge:"unicode/1f9d1-2696.png?v8",juggling_person:"unicode/1f939.png?v8",kaaba:"unicode/1f54b.png?v8",kangaroo:"unicode/1f998.png?v8",kazakhstan:"unicode/1f1f0-1f1ff.png?v8",kenya:"unicode/1f1f0-1f1ea.png?v8",key:"unicode/1f511.png?v8",keyboard:"unicode/2328.png?v8",keycap_ten:"unicode/1f51f.png?v8",kick_scooter:"unicode/1f6f4.png?v8",kimono:"unicode/1f458.png?v8",kiribati:"unicode/1f1f0-1f1ee.png?v8",kiss:"unicode/1f48b.png?v8",kissing:"unicode/1f617.png?v8",kissing_cat:"unicode/1f63d.png?v8",kissing_closed_eyes:"unicode/1f61a.png?v8",kissing_heart:"unicode/1f618.png?v8",kissing_smiling_eyes:"unicode/1f619.png?v8",kite:"unicode/1fa81.png?v8",kiwi_fruit:"unicode/1f95d.png?v8",kneeling_man:"unicode/1f9ce-2642.png?v8",kneeling_person:"unicode/1f9ce.png?v8",kneeling_woman:"unicode/1f9ce-2640.png?v8",knife:"unicode/1f52a.png?v8",knot:"unicode/1faa2.png?v8",koala:"unicode/1f428.png?v8",koko:"unicode/1f201.png?v8",kosovo:"unicode/1f1fd-1f1f0.png?v8",kr:"unicode/1f1f0-1f1f7.png?v8",kuwait:"unicode/1f1f0-1f1fc.png?v8",kyrgyzstan:"unicode/1f1f0-1f1ec.png?v8",lab_coat:"unicode/1f97c.png?v8",label:"unicode/1f3f7.png?v8",lacrosse:"unicode/1f94d.png?v8",ladder:"unicode/1fa9c.png?v8",lady_beetle:"unicode/1f41e.png?v8",lantern:"unicode/1f3ee.png?v8",laos:"unicode/1f1f1-1f1e6.png?v8",large_blue_circle:"unicode/1f535.png?v8",large_blue_diamond:"unicode/1f537.png?v8",large_orange_diamond:"unicode/1f536.png?v8",last_quarter_moon:"unicode/1f317.png?v8",last_quarter_moon_with_face:"unicode/1f31c.png?v8",latin_cross:"unicode/271d.png?v8",latvia:"unicode/1f1f1-1f1fb.png?v8",laughing:"unicode/1f606.png?v8",leafy_green:"unicode/1f96c.png?v8",leaves:"unicode/1f343.png?v8",lebanon:"unicode/1f1f1-1f1e7.png?v8",ledger:"unicode/1f4d2.png?v8",left_luggage:"unicode/1f6c5.png?v8",left_right_arrow:"unicode/2194.png?v8",left_speech_bubble:"unicode/1f5e8.png?v8",leftwards_arrow_with_hook:"unicode/21a9.png?v8",leg:"unicode/1f9b5.png?v8",lemon:"unicode/1f34b.png?v8",leo:"unicode/264c.png?v8",leopard:"unicode/1f406.png?v8",lesotho:"unicode/1f1f1-1f1f8.png?v8",level_slider:"unicode/1f39a.png?v8",liberia:"unicode/1f1f1-1f1f7.png?v8",libra:"unicode/264e.png?v8",libya:"unicode/1f1f1-1f1fe.png?v8",liechtenstein:"unicode/1f1f1-1f1ee.png?v8",light_rail:"unicode/1f688.png?v8",link:"unicode/1f517.png?v8",lion:"unicode/1f981.png?v8",lips:"unicode/1f444.png?v8",lipstick:"unicode/1f484.png?v8",lithuania:"unicode/1f1f1-1f1f9.png?v8",lizard:"unicode/1f98e.png?v8",llama:"unicode/1f999.png?v8",lobster:"unicode/1f99e.png?v8",lock:"unicode/1f512.png?v8",lock_with_ink_pen:"unicode/1f50f.png?v8",lollipop:"unicode/1f36d.png?v8",long_drum:"unicode/1fa98.png?v8",loop:"unicode/27bf.png?v8",lotion_bottle:"unicode/1f9f4.png?v8",lotus_position:"unicode/1f9d8.png?v8",lotus_position_man:"unicode/1f9d8-2642.png?v8",lotus_position_woman:"unicode/1f9d8-2640.png?v8",loud_sound:"unicode/1f50a.png?v8",loudspeaker:"unicode/1f4e2.png?v8",love_hotel:"unicode/1f3e9.png?v8",love_letter:"unicode/1f48c.png?v8",love_you_gesture:"unicode/1f91f.png?v8",low_brightness:"unicode/1f505.png?v8",luggage:"unicode/1f9f3.png?v8",lungs:"unicode/1fac1.png?v8",luxembourg:"unicode/1f1f1-1f1fa.png?v8",lying_face:"unicode/1f925.png?v8",m:"unicode/24c2.png?v8",macau:"unicode/1f1f2-1f1f4.png?v8",macedonia:"unicode/1f1f2-1f1f0.png?v8",madagascar:"unicode/1f1f2-1f1ec.png?v8",mag:"unicode/1f50d.png?v8",mag_right:"unicode/1f50e.png?v8",mage:"unicode/1f9d9.png?v8",mage_man:"unicode/1f9d9-2642.png?v8",mage_woman:"unicode/1f9d9-2640.png?v8",magic_wand:"unicode/1fa84.png?v8",magnet:"unicode/1f9f2.png?v8",mahjong:"unicode/1f004.png?v8",mailbox:"unicode/1f4eb.png?v8",mailbox_closed:"unicode/1f4ea.png?v8",mailbox_with_mail:"unicode/1f4ec.png?v8",mailbox_with_no_mail:"unicode/1f4ed.png?v8",malawi:"unicode/1f1f2-1f1fc.png?v8",malaysia:"unicode/1f1f2-1f1fe.png?v8",maldives:"unicode/1f1f2-1f1fb.png?v8",male_detective:"unicode/1f575-2642.png?v8",male_sign:"unicode/2642.png?v8",mali:"unicode/1f1f2-1f1f1.png?v8",malta:"unicode/1f1f2-1f1f9.png?v8",mammoth:"unicode/1f9a3.png?v8",man:"unicode/1f468.png?v8",man_artist:"unicode/1f468-1f3a8.png?v8",man_astronaut:"unicode/1f468-1f680.png?v8",man_beard:"unicode/1f9d4-2642.png?v8",man_cartwheeling:"unicode/1f938-2642.png?v8",man_cook:"unicode/1f468-1f373.png?v8",man_dancing:"unicode/1f57a.png?v8",man_facepalming:"unicode/1f926-2642.png?v8",man_factory_worker:"unicode/1f468-1f3ed.png?v8",man_farmer:"unicode/1f468-1f33e.png?v8",man_feeding_baby:"unicode/1f468-1f37c.png?v8",man_firefighter:"unicode/1f468-1f692.png?v8",man_health_worker:"unicode/1f468-2695.png?v8",man_in_manual_wheelchair:"unicode/1f468-1f9bd.png?v8",man_in_motorized_wheelchair:"unicode/1f468-1f9bc.png?v8",man_in_tuxedo:"unicode/1f935-2642.png?v8",man_judge:"unicode/1f468-2696.png?v8",man_juggling:"unicode/1f939-2642.png?v8",man_mechanic:"unicode/1f468-1f527.png?v8",man_office_worker:"unicode/1f468-1f4bc.png?v8",man_pilot:"unicode/1f468-2708.png?v8",man_playing_handball:"unicode/1f93e-2642.png?v8",man_playing_water_polo:"unicode/1f93d-2642.png?v8",man_scientist:"unicode/1f468-1f52c.png?v8",man_shrugging:"unicode/1f937-2642.png?v8",man_singer:"unicode/1f468-1f3a4.png?v8",man_student:"unicode/1f468-1f393.png?v8",man_teacher:"unicode/1f468-1f3eb.png?v8",man_technologist:"unicode/1f468-1f4bb.png?v8",man_with_gua_pi_mao:"unicode/1f472.png?v8",man_with_probing_cane:"unicode/1f468-1f9af.png?v8",man_with_turban:"unicode/1f473-2642.png?v8",man_with_veil:"unicode/1f470-2642.png?v8",mandarin:"unicode/1f34a.png?v8",mango:"unicode/1f96d.png?v8",mans_shoe:"unicode/1f45e.png?v8",mantelpiece_clock:"unicode/1f570.png?v8",manual_wheelchair:"unicode/1f9bd.png?v8",maple_leaf:"unicode/1f341.png?v8",marshall_islands:"unicode/1f1f2-1f1ed.png?v8",martial_arts_uniform:"unicode/1f94b.png?v8",martinique:"unicode/1f1f2-1f1f6.png?v8",mask:"unicode/1f637.png?v8",massage:"unicode/1f486.png?v8",massage_man:"unicode/1f486-2642.png?v8",massage_woman:"unicode/1f486-2640.png?v8",mate:"unicode/1f9c9.png?v8",mauritania:"unicode/1f1f2-1f1f7.png?v8",mauritius:"unicode/1f1f2-1f1fa.png?v8",mayotte:"unicode/1f1fe-1f1f9.png?v8",meat_on_bone:"unicode/1f356.png?v8",mechanic:"unicode/1f9d1-1f527.png?v8",mechanical_arm:"unicode/1f9be.png?v8",mechanical_leg:"unicode/1f9bf.png?v8",medal_military:"unicode/1f396.png?v8",medal_sports:"unicode/1f3c5.png?v8",medical_symbol:"unicode/2695.png?v8",mega:"unicode/1f4e3.png?v8",melon:"unicode/1f348.png?v8",memo:"unicode/1f4dd.png?v8",men_wrestling:"unicode/1f93c-2642.png?v8",mending_heart:"unicode/2764-1fa79.png?v8",menorah:"unicode/1f54e.png?v8",mens:"unicode/1f6b9.png?v8",mermaid:"unicode/1f9dc-2640.png?v8",merman:"unicode/1f9dc-2642.png?v8",merperson:"unicode/1f9dc.png?v8",metal:"unicode/1f918.png?v8",metro:"unicode/1f687.png?v8",mexico:"unicode/1f1f2-1f1fd.png?v8",microbe:"unicode/1f9a0.png?v8",micronesia:"unicode/1f1eb-1f1f2.png?v8",microphone:"unicode/1f3a4.png?v8",microscope:"unicode/1f52c.png?v8",middle_finger:"unicode/1f595.png?v8",military_helmet:"unicode/1fa96.png?v8",milk_glass:"unicode/1f95b.png?v8",milky_way:"unicode/1f30c.png?v8",minibus:"unicode/1f690.png?v8",minidisc:"unicode/1f4bd.png?v8",mirror:"unicode/1fa9e.png?v8",mobile_phone_off:"unicode/1f4f4.png?v8",moldova:"unicode/1f1f2-1f1e9.png?v8",monaco:"unicode/1f1f2-1f1e8.png?v8",money_mouth_face:"unicode/1f911.png?v8",money_with_wings:"unicode/1f4b8.png?v8",moneybag:"unicode/1f4b0.png?v8",mongolia:"unicode/1f1f2-1f1f3.png?v8",monkey:"unicode/1f412.png?v8",monkey_face:"unicode/1f435.png?v8",monocle_face:"unicode/1f9d0.png?v8",monorail:"unicode/1f69d.png?v8",montenegro:"unicode/1f1f2-1f1ea.png?v8",montserrat:"unicode/1f1f2-1f1f8.png?v8",moon:"unicode/1f314.png?v8",moon_cake:"unicode/1f96e.png?v8",morocco:"unicode/1f1f2-1f1e6.png?v8",mortar_board:"unicode/1f393.png?v8",mosque:"unicode/1f54c.png?v8",mosquito:"unicode/1f99f.png?v8",motor_boat:"unicode/1f6e5.png?v8",motor_scooter:"unicode/1f6f5.png?v8",motorcycle:"unicode/1f3cd.png?v8",motorized_wheelchair:"unicode/1f9bc.png?v8",motorway:"unicode/1f6e3.png?v8",mount_fuji:"unicode/1f5fb.png?v8",mountain:"unicode/26f0.png?v8",mountain_bicyclist:"unicode/1f6b5.png?v8",mountain_biking_man:"unicode/1f6b5-2642.png?v8",mountain_biking_woman:"unicode/1f6b5-2640.png?v8",mountain_cableway:"unicode/1f6a0.png?v8",mountain_railway:"unicode/1f69e.png?v8",mountain_snow:"unicode/1f3d4.png?v8",mouse:"unicode/1f42d.png?v8",mouse2:"unicode/1f401.png?v8",mouse_trap:"unicode/1faa4.png?v8",movie_camera:"unicode/1f3a5.png?v8",moyai:"unicode/1f5ff.png?v8",mozambique:"unicode/1f1f2-1f1ff.png?v8",mrs_claus:"unicode/1f936.png?v8",muscle:"unicode/1f4aa.png?v8",mushroom:"unicode/1f344.png?v8",musical_keyboard:"unicode/1f3b9.png?v8",musical_note:"unicode/1f3b5.png?v8",musical_score:"unicode/1f3bc.png?v8",mute:"unicode/1f507.png?v8",mx_claus:"unicode/1f9d1-1f384.png?v8",myanmar:"unicode/1f1f2-1f1f2.png?v8",nail_care:"unicode/1f485.png?v8",name_badge:"unicode/1f4db.png?v8",namibia:"unicode/1f1f3-1f1e6.png?v8",national_park:"unicode/1f3de.png?v8",nauru:"unicode/1f1f3-1f1f7.png?v8",nauseated_face:"unicode/1f922.png?v8",nazar_amulet:"unicode/1f9ff.png?v8",neckbeard:"neckbeard.png?v8",necktie:"unicode/1f454.png?v8",negative_squared_cross_mark:"unicode/274e.png?v8",nepal:"unicode/1f1f3-1f1f5.png?v8",nerd_face:"unicode/1f913.png?v8",nesting_dolls:"unicode/1fa86.png?v8",netherlands:"unicode/1f1f3-1f1f1.png?v8",neutral_face:"unicode/1f610.png?v8",new:"unicode/1f195.png?v8",new_caledonia:"unicode/1f1f3-1f1e8.png?v8",new_moon:"unicode/1f311.png?v8",new_moon_with_face:"unicode/1f31a.png?v8",new_zealand:"unicode/1f1f3-1f1ff.png?v8",newspaper:"unicode/1f4f0.png?v8",newspaper_roll:"unicode/1f5de.png?v8",next_track_button:"unicode/23ed.png?v8",ng:"unicode/1f196.png?v8",ng_man:"unicode/1f645-2642.png?v8",ng_woman:"unicode/1f645-2640.png?v8",nicaragua:"unicode/1f1f3-1f1ee.png?v8",niger:"unicode/1f1f3-1f1ea.png?v8",nigeria:"unicode/1f1f3-1f1ec.png?v8",night_with_stars:"unicode/1f303.png?v8",nine:"unicode/0039-20e3.png?v8",ninja:"unicode/1f977.png?v8",niue:"unicode/1f1f3-1f1fa.png?v8",no_bell:"unicode/1f515.png?v8",no_bicycles:"unicode/1f6b3.png?v8",no_entry:"unicode/26d4.png?v8",no_entry_sign:"unicode/1f6ab.png?v8",no_good:"unicode/1f645.png?v8",no_good_man:"unicode/1f645-2642.png?v8",no_good_woman:"unicode/1f645-2640.png?v8",no_mobile_phones:"unicode/1f4f5.png?v8",no_mouth:"unicode/1f636.png?v8",no_pedestrians:"unicode/1f6b7.png?v8",no_smoking:"unicode/1f6ad.png?v8","non-potable_water":"unicode/1f6b1.png?v8",norfolk_island:"unicode/1f1f3-1f1eb.png?v8",north_korea:"unicode/1f1f0-1f1f5.png?v8",northern_mariana_islands:"unicode/1f1f2-1f1f5.png?v8",norway:"unicode/1f1f3-1f1f4.png?v8",nose:"unicode/1f443.png?v8",notebook:"unicode/1f4d3.png?v8",notebook_with_decorative_cover:"unicode/1f4d4.png?v8",notes:"unicode/1f3b6.png?v8",nut_and_bolt:"unicode/1f529.png?v8",o:"unicode/2b55.png?v8",o2:"unicode/1f17e.png?v8",ocean:"unicode/1f30a.png?v8",octocat:"octocat.png?v8",octopus:"unicode/1f419.png?v8",oden:"unicode/1f362.png?v8",office:"unicode/1f3e2.png?v8",office_worker:"unicode/1f9d1-1f4bc.png?v8",oil_drum:"unicode/1f6e2.png?v8",ok:"unicode/1f197.png?v8",ok_hand:"unicode/1f44c.png?v8",ok_man:"unicode/1f646-2642.png?v8",ok_person:"unicode/1f646.png?v8",ok_woman:"unicode/1f646-2640.png?v8",old_key:"unicode/1f5dd.png?v8",older_adult:"unicode/1f9d3.png?v8",older_man:"unicode/1f474.png?v8",older_woman:"unicode/1f475.png?v8",olive:"unicode/1fad2.png?v8",om:"unicode/1f549.png?v8",oman:"unicode/1f1f4-1f1f2.png?v8",on:"unicode/1f51b.png?v8",oncoming_automobile:"unicode/1f698.png?v8",oncoming_bus:"unicode/1f68d.png?v8",oncoming_police_car:"unicode/1f694.png?v8",oncoming_taxi:"unicode/1f696.png?v8",one:"unicode/0031-20e3.png?v8",one_piece_swimsuit:"unicode/1fa71.png?v8",onion:"unicode/1f9c5.png?v8",open_book:"unicode/1f4d6.png?v8",open_file_folder:"unicode/1f4c2.png?v8",open_hands:"unicode/1f450.png?v8",open_mouth:"unicode/1f62e.png?v8",open_umbrella:"unicode/2602.png?v8",ophiuchus:"unicode/26ce.png?v8",orange:"unicode/1f34a.png?v8",orange_book:"unicode/1f4d9.png?v8",orange_circle:"unicode/1f7e0.png?v8",orange_heart:"unicode/1f9e1.png?v8",orange_square:"unicode/1f7e7.png?v8",orangutan:"unicode/1f9a7.png?v8",orthodox_cross:"unicode/2626.png?v8",otter:"unicode/1f9a6.png?v8",outbox_tray:"unicode/1f4e4.png?v8",owl:"unicode/1f989.png?v8",ox:"unicode/1f402.png?v8",oyster:"unicode/1f9aa.png?v8",package:"unicode/1f4e6.png?v8",page_facing_up:"unicode/1f4c4.png?v8",page_with_curl:"unicode/1f4c3.png?v8",pager:"unicode/1f4df.png?v8",paintbrush:"unicode/1f58c.png?v8",pakistan:"unicode/1f1f5-1f1f0.png?v8",palau:"unicode/1f1f5-1f1fc.png?v8",palestinian_territories:"unicode/1f1f5-1f1f8.png?v8",palm_tree:"unicode/1f334.png?v8",palms_up_together:"unicode/1f932.png?v8",panama:"unicode/1f1f5-1f1e6.png?v8",pancakes:"unicode/1f95e.png?v8",panda_face:"unicode/1f43c.png?v8",paperclip:"unicode/1f4ce.png?v8",paperclips:"unicode/1f587.png?v8",papua_new_guinea:"unicode/1f1f5-1f1ec.png?v8",parachute:"unicode/1fa82.png?v8",paraguay:"unicode/1f1f5-1f1fe.png?v8",parasol_on_ground:"unicode/26f1.png?v8",parking:"unicode/1f17f.png?v8",parrot:"unicode/1f99c.png?v8",part_alternation_mark:"unicode/303d.png?v8",partly_sunny:"unicode/26c5.png?v8",partying_face:"unicode/1f973.png?v8",passenger_ship:"unicode/1f6f3.png?v8",passport_control:"unicode/1f6c2.png?v8",pause_button:"unicode/23f8.png?v8",paw_prints:"unicode/1f43e.png?v8",peace_symbol:"unicode/262e.png?v8",peach:"unicode/1f351.png?v8",peacock:"unicode/1f99a.png?v8",peanuts:"unicode/1f95c.png?v8",pear:"unicode/1f350.png?v8",pen:"unicode/1f58a.png?v8",pencil:"unicode/1f4dd.png?v8",pencil2:"unicode/270f.png?v8",penguin:"unicode/1f427.png?v8",pensive:"unicode/1f614.png?v8",people_holding_hands:"unicode/1f9d1-1f91d-1f9d1.png?v8",people_hugging:"unicode/1fac2.png?v8",performing_arts:"unicode/1f3ad.png?v8",persevere:"unicode/1f623.png?v8",person_bald:"unicode/1f9d1-1f9b2.png?v8",person_curly_hair:"unicode/1f9d1-1f9b1.png?v8",person_feeding_baby:"unicode/1f9d1-1f37c.png?v8",person_fencing:"unicode/1f93a.png?v8",person_in_manual_wheelchair:"unicode/1f9d1-1f9bd.png?v8",person_in_motorized_wheelchair:"unicode/1f9d1-1f9bc.png?v8",person_in_tuxedo:"unicode/1f935.png?v8",person_red_hair:"unicode/1f9d1-1f9b0.png?v8",person_white_hair:"unicode/1f9d1-1f9b3.png?v8",person_with_probing_cane:"unicode/1f9d1-1f9af.png?v8",person_with_turban:"unicode/1f473.png?v8",person_with_veil:"unicode/1f470.png?v8",peru:"unicode/1f1f5-1f1ea.png?v8",petri_dish:"unicode/1f9eb.png?v8",philippines:"unicode/1f1f5-1f1ed.png?v8",phone:"unicode/260e.png?v8",pick:"unicode/26cf.png?v8",pickup_truck:"unicode/1f6fb.png?v8",pie:"unicode/1f967.png?v8",pig:"unicode/1f437.png?v8",pig2:"unicode/1f416.png?v8",pig_nose:"unicode/1f43d.png?v8",pill:"unicode/1f48a.png?v8",pilot:"unicode/1f9d1-2708.png?v8",pinata:"unicode/1fa85.png?v8",pinched_fingers:"unicode/1f90c.png?v8",pinching_hand:"unicode/1f90f.png?v8",pineapple:"unicode/1f34d.png?v8",ping_pong:"unicode/1f3d3.png?v8",pirate_flag:"unicode/1f3f4-2620.png?v8",pisces:"unicode/2653.png?v8",pitcairn_islands:"unicode/1f1f5-1f1f3.png?v8",pizza:"unicode/1f355.png?v8",placard:"unicode/1faa7.png?v8",place_of_worship:"unicode/1f6d0.png?v8",plate_with_cutlery:"unicode/1f37d.png?v8",play_or_pause_button:"unicode/23ef.png?v8",pleading_face:"unicode/1f97a.png?v8",plunger:"unicode/1faa0.png?v8",point_down:"unicode/1f447.png?v8",point_left:"unicode/1f448.png?v8",point_right:"unicode/1f449.png?v8",point_up:"unicode/261d.png?v8",point_up_2:"unicode/1f446.png?v8",poland:"unicode/1f1f5-1f1f1.png?v8",polar_bear:"unicode/1f43b-2744.png?v8",police_car:"unicode/1f693.png?v8",police_officer:"unicode/1f46e.png?v8",policeman:"unicode/1f46e-2642.png?v8",policewoman:"unicode/1f46e-2640.png?v8",poodle:"unicode/1f429.png?v8",poop:"unicode/1f4a9.png?v8",popcorn:"unicode/1f37f.png?v8",portugal:"unicode/1f1f5-1f1f9.png?v8",post_office:"unicode/1f3e3.png?v8",postal_horn:"unicode/1f4ef.png?v8",postbox:"unicode/1f4ee.png?v8",potable_water:"unicode/1f6b0.png?v8",potato:"unicode/1f954.png?v8",potted_plant:"unicode/1fab4.png?v8",pouch:"unicode/1f45d.png?v8",poultry_leg:"unicode/1f357.png?v8",pound:"unicode/1f4b7.png?v8",pout:"unicode/1f621.png?v8",pouting_cat:"unicode/1f63e.png?v8",pouting_face:"unicode/1f64e.png?v8",pouting_man:"unicode/1f64e-2642.png?v8",pouting_woman:"unicode/1f64e-2640.png?v8",pray:"unicode/1f64f.png?v8",prayer_beads:"unicode/1f4ff.png?v8",pregnant_woman:"unicode/1f930.png?v8",pretzel:"unicode/1f968.png?v8",previous_track_button:"unicode/23ee.png?v8",prince:"unicode/1f934.png?v8",princess:"unicode/1f478.png?v8",printer:"unicode/1f5a8.png?v8",probing_cane:"unicode/1f9af.png?v8",puerto_rico:"unicode/1f1f5-1f1f7.png?v8",punch:"unicode/1f44a.png?v8",purple_circle:"unicode/1f7e3.png?v8",purple_heart:"unicode/1f49c.png?v8",purple_square:"unicode/1f7ea.png?v8",purse:"unicode/1f45b.png?v8",pushpin:"unicode/1f4cc.png?v8",put_litter_in_its_place:"unicode/1f6ae.png?v8",qatar:"unicode/1f1f6-1f1e6.png?v8",question:"unicode/2753.png?v8",rabbit:"unicode/1f430.png?v8",rabbit2:"unicode/1f407.png?v8",raccoon:"unicode/1f99d.png?v8",racehorse:"unicode/1f40e.png?v8",racing_car:"unicode/1f3ce.png?v8",radio:"unicode/1f4fb.png?v8",radio_button:"unicode/1f518.png?v8",radioactive:"unicode/2622.png?v8",rage:"unicode/1f621.png?v8",rage1:"rage1.png?v8",rage2:"rage2.png?v8",rage3:"rage3.png?v8",rage4:"rage4.png?v8",railway_car:"unicode/1f683.png?v8",railway_track:"unicode/1f6e4.png?v8",rainbow:"unicode/1f308.png?v8",rainbow_flag:"unicode/1f3f3-1f308.png?v8",raised_back_of_hand:"unicode/1f91a.png?v8",raised_eyebrow:"unicode/1f928.png?v8",raised_hand:"unicode/270b.png?v8",raised_hand_with_fingers_splayed:"unicode/1f590.png?v8",raised_hands:"unicode/1f64c.png?v8",raising_hand:"unicode/1f64b.png?v8",raising_hand_man:"unicode/1f64b-2642.png?v8",raising_hand_woman:"unicode/1f64b-2640.png?v8",ram:"unicode/1f40f.png?v8",ramen:"unicode/1f35c.png?v8",rat:"unicode/1f400.png?v8",razor:"unicode/1fa92.png?v8",receipt:"unicode/1f9fe.png?v8",record_button:"unicode/23fa.png?v8",recycle:"unicode/267b.png?v8",red_car:"unicode/1f697.png?v8",red_circle:"unicode/1f534.png?v8",red_envelope:"unicode/1f9e7.png?v8",red_haired_man:"unicode/1f468-1f9b0.png?v8",red_haired_woman:"unicode/1f469-1f9b0.png?v8",red_square:"unicode/1f7e5.png?v8",registered:"unicode/00ae.png?v8",relaxed:"unicode/263a.png?v8",relieved:"unicode/1f60c.png?v8",reminder_ribbon:"unicode/1f397.png?v8",repeat:"unicode/1f501.png?v8",repeat_one:"unicode/1f502.png?v8",rescue_worker_helmet:"unicode/26d1.png?v8",restroom:"unicode/1f6bb.png?v8",reunion:"unicode/1f1f7-1f1ea.png?v8",revolving_hearts:"unicode/1f49e.png?v8",rewind:"unicode/23ea.png?v8",rhinoceros:"unicode/1f98f.png?v8",ribbon:"unicode/1f380.png?v8",rice:"unicode/1f35a.png?v8",rice_ball:"unicode/1f359.png?v8",rice_cracker:"unicode/1f358.png?v8",rice_scene:"unicode/1f391.png?v8",right_anger_bubble:"unicode/1f5ef.png?v8",ring:"unicode/1f48d.png?v8",ringed_planet:"unicode/1fa90.png?v8",robot:"unicode/1f916.png?v8",rock:"unicode/1faa8.png?v8",rocket:"unicode/1f680.png?v8",rofl:"unicode/1f923.png?v8",roll_eyes:"unicode/1f644.png?v8",roll_of_paper:"unicode/1f9fb.png?v8",roller_coaster:"unicode/1f3a2.png?v8",roller_skate:"unicode/1f6fc.png?v8",romania:"unicode/1f1f7-1f1f4.png?v8",rooster:"unicode/1f413.png?v8",rose:"unicode/1f339.png?v8",rosette:"unicode/1f3f5.png?v8",rotating_light:"unicode/1f6a8.png?v8",round_pushpin:"unicode/1f4cd.png?v8",rowboat:"unicode/1f6a3.png?v8",rowing_man:"unicode/1f6a3-2642.png?v8",rowing_woman:"unicode/1f6a3-2640.png?v8",ru:"unicode/1f1f7-1f1fa.png?v8",rugby_football:"unicode/1f3c9.png?v8",runner:"unicode/1f3c3.png?v8",running:"unicode/1f3c3.png?v8",running_man:"unicode/1f3c3-2642.png?v8",running_shirt_with_sash:"unicode/1f3bd.png?v8",running_woman:"unicode/1f3c3-2640.png?v8",rwanda:"unicode/1f1f7-1f1fc.png?v8",sa:"unicode/1f202.png?v8",safety_pin:"unicode/1f9f7.png?v8",safety_vest:"unicode/1f9ba.png?v8",sagittarius:"unicode/2650.png?v8",sailboat:"unicode/26f5.png?v8",sake:"unicode/1f376.png?v8",salt:"unicode/1f9c2.png?v8",samoa:"unicode/1f1fc-1f1f8.png?v8",san_marino:"unicode/1f1f8-1f1f2.png?v8",sandal:"unicode/1f461.png?v8",sandwich:"unicode/1f96a.png?v8",santa:"unicode/1f385.png?v8",sao_tome_principe:"unicode/1f1f8-1f1f9.png?v8",sari:"unicode/1f97b.png?v8",sassy_man:"unicode/1f481-2642.png?v8",sassy_woman:"unicode/1f481-2640.png?v8",satellite:"unicode/1f4e1.png?v8",satisfied:"unicode/1f606.png?v8",saudi_arabia:"unicode/1f1f8-1f1e6.png?v8",sauna_man:"unicode/1f9d6-2642.png?v8",sauna_person:"unicode/1f9d6.png?v8",sauna_woman:"unicode/1f9d6-2640.png?v8",sauropod:"unicode/1f995.png?v8",saxophone:"unicode/1f3b7.png?v8",scarf:"unicode/1f9e3.png?v8",school:"unicode/1f3eb.png?v8",school_satchel:"unicode/1f392.png?v8",scientist:"unicode/1f9d1-1f52c.png?v8",scissors:"unicode/2702.png?v8",scorpion:"unicode/1f982.png?v8",scorpius:"unicode/264f.png?v8",scotland:"unicode/1f3f4-e0067-e0062-e0073-e0063-e0074-e007f.png?v8",scream:"unicode/1f631.png?v8",scream_cat:"unicode/1f640.png?v8",screwdriver:"unicode/1fa9b.png?v8",scroll:"unicode/1f4dc.png?v8",seal:"unicode/1f9ad.png?v8",seat:"unicode/1f4ba.png?v8",secret:"unicode/3299.png?v8",see_no_evil:"unicode/1f648.png?v8",seedling:"unicode/1f331.png?v8",selfie:"unicode/1f933.png?v8",senegal:"unicode/1f1f8-1f1f3.png?v8",serbia:"unicode/1f1f7-1f1f8.png?v8",service_dog:"unicode/1f415-1f9ba.png?v8",seven:"unicode/0037-20e3.png?v8",sewing_needle:"unicode/1faa1.png?v8",seychelles:"unicode/1f1f8-1f1e8.png?v8",shallow_pan_of_food:"unicode/1f958.png?v8",shamrock:"unicode/2618.png?v8",shark:"unicode/1f988.png?v8",shaved_ice:"unicode/1f367.png?v8",sheep:"unicode/1f411.png?v8",shell:"unicode/1f41a.png?v8",shield:"unicode/1f6e1.png?v8",shinto_shrine:"unicode/26e9.png?v8",ship:"unicode/1f6a2.png?v8",shipit:"shipit.png?v8",shirt:"unicode/1f455.png?v8",shit:"unicode/1f4a9.png?v8",shoe:"unicode/1f45e.png?v8",shopping:"unicode/1f6cd.png?v8",shopping_cart:"unicode/1f6d2.png?v8",shorts:"unicode/1fa73.png?v8",shower:"unicode/1f6bf.png?v8",shrimp:"unicode/1f990.png?v8",shrug:"unicode/1f937.png?v8",shushing_face:"unicode/1f92b.png?v8",sierra_leone:"unicode/1f1f8-1f1f1.png?v8",signal_strength:"unicode/1f4f6.png?v8",singapore:"unicode/1f1f8-1f1ec.png?v8",singer:"unicode/1f9d1-1f3a4.png?v8",sint_maarten:"unicode/1f1f8-1f1fd.png?v8",six:"unicode/0036-20e3.png?v8",six_pointed_star:"unicode/1f52f.png?v8",skateboard:"unicode/1f6f9.png?v8",ski:"unicode/1f3bf.png?v8",skier:"unicode/26f7.png?v8",skull:"unicode/1f480.png?v8",skull_and_crossbones:"unicode/2620.png?v8",skunk:"unicode/1f9a8.png?v8",sled:"unicode/1f6f7.png?v8",sleeping:"unicode/1f634.png?v8",sleeping_bed:"unicode/1f6cc.png?v8",sleepy:"unicode/1f62a.png?v8",slightly_frowning_face:"unicode/1f641.png?v8",slightly_smiling_face:"unicode/1f642.png?v8",slot_machine:"unicode/1f3b0.png?v8",sloth:"unicode/1f9a5.png?v8",slovakia:"unicode/1f1f8-1f1f0.png?v8",slovenia:"unicode/1f1f8-1f1ee.png?v8",small_airplane:"unicode/1f6e9.png?v8",small_blue_diamond:"unicode/1f539.png?v8",small_orange_diamond:"unicode/1f538.png?v8",small_red_triangle:"unicode/1f53a.png?v8",small_red_triangle_down:"unicode/1f53b.png?v8",smile:"unicode/1f604.png?v8",smile_cat:"unicode/1f638.png?v8",smiley:"unicode/1f603.png?v8",smiley_cat:"unicode/1f63a.png?v8",smiling_face_with_tear:"unicode/1f972.png?v8",smiling_face_with_three_hearts:"unicode/1f970.png?v8",smiling_imp:"unicode/1f608.png?v8",smirk:"unicode/1f60f.png?v8",smirk_cat:"unicode/1f63c.png?v8",smoking:"unicode/1f6ac.png?v8",snail:"unicode/1f40c.png?v8",snake:"unicode/1f40d.png?v8",sneezing_face:"unicode/1f927.png?v8",snowboarder:"unicode/1f3c2.png?v8",snowflake:"unicode/2744.png?v8",snowman:"unicode/26c4.png?v8",snowman_with_snow:"unicode/2603.png?v8",soap:"unicode/1f9fc.png?v8",sob:"unicode/1f62d.png?v8",soccer:"unicode/26bd.png?v8",socks:"unicode/1f9e6.png?v8",softball:"unicode/1f94e.png?v8",solomon_islands:"unicode/1f1f8-1f1e7.png?v8",somalia:"unicode/1f1f8-1f1f4.png?v8",soon:"unicode/1f51c.png?v8",sos:"unicode/1f198.png?v8",sound:"unicode/1f509.png?v8",south_africa:"unicode/1f1ff-1f1e6.png?v8",south_georgia_south_sandwich_islands:"unicode/1f1ec-1f1f8.png?v8",south_sudan:"unicode/1f1f8-1f1f8.png?v8",space_invader:"unicode/1f47e.png?v8",spades:"unicode/2660.png?v8",spaghetti:"unicode/1f35d.png?v8",sparkle:"unicode/2747.png?v8",sparkler:"unicode/1f387.png?v8",sparkles:"unicode/2728.png?v8",sparkling_heart:"unicode/1f496.png?v8",speak_no_evil:"unicode/1f64a.png?v8",speaker:"unicode/1f508.png?v8",speaking_head:"unicode/1f5e3.png?v8",speech_balloon:"unicode/1f4ac.png?v8",speedboat:"unicode/1f6a4.png?v8",spider:"unicode/1f577.png?v8",spider_web:"unicode/1f578.png?v8",spiral_calendar:"unicode/1f5d3.png?v8",spiral_notepad:"unicode/1f5d2.png?v8",sponge:"unicode/1f9fd.png?v8",spoon:"unicode/1f944.png?v8",squid:"unicode/1f991.png?v8",sri_lanka:"unicode/1f1f1-1f1f0.png?v8",st_barthelemy:"unicode/1f1e7-1f1f1.png?v8",st_helena:"unicode/1f1f8-1f1ed.png?v8",st_kitts_nevis:"unicode/1f1f0-1f1f3.png?v8",st_lucia:"unicode/1f1f1-1f1e8.png?v8",st_martin:"unicode/1f1f2-1f1eb.png?v8",st_pierre_miquelon:"unicode/1f1f5-1f1f2.png?v8",st_vincent_grenadines:"unicode/1f1fb-1f1e8.png?v8",stadium:"unicode/1f3df.png?v8",standing_man:"unicode/1f9cd-2642.png?v8",standing_person:"unicode/1f9cd.png?v8",standing_woman:"unicode/1f9cd-2640.png?v8",star:"unicode/2b50.png?v8",star2:"unicode/1f31f.png?v8",star_and_crescent:"unicode/262a.png?v8",star_of_david:"unicode/2721.png?v8",star_struck:"unicode/1f929.png?v8",stars:"unicode/1f320.png?v8",station:"unicode/1f689.png?v8",statue_of_liberty:"unicode/1f5fd.png?v8",steam_locomotive:"unicode/1f682.png?v8",stethoscope:"unicode/1fa7a.png?v8",stew:"unicode/1f372.png?v8",stop_button:"unicode/23f9.png?v8",stop_sign:"unicode/1f6d1.png?v8",stopwatch:"unicode/23f1.png?v8",straight_ruler:"unicode/1f4cf.png?v8",strawberry:"unicode/1f353.png?v8",stuck_out_tongue:"unicode/1f61b.png?v8",stuck_out_tongue_closed_eyes:"unicode/1f61d.png?v8",stuck_out_tongue_winking_eye:"unicode/1f61c.png?v8",student:"unicode/1f9d1-1f393.png?v8",studio_microphone:"unicode/1f399.png?v8",stuffed_flatbread:"unicode/1f959.png?v8",sudan:"unicode/1f1f8-1f1e9.png?v8",sun_behind_large_cloud:"unicode/1f325.png?v8",sun_behind_rain_cloud:"unicode/1f326.png?v8",sun_behind_small_cloud:"unicode/1f324.png?v8",sun_with_face:"unicode/1f31e.png?v8",sunflower:"unicode/1f33b.png?v8",sunglasses:"unicode/1f60e.png?v8",sunny:"unicode/2600.png?v8",sunrise:"unicode/1f305.png?v8",sunrise_over_mountains:"unicode/1f304.png?v8",superhero:"unicode/1f9b8.png?v8",superhero_man:"unicode/1f9b8-2642.png?v8",superhero_woman:"unicode/1f9b8-2640.png?v8",supervillain:"unicode/1f9b9.png?v8",supervillain_man:"unicode/1f9b9-2642.png?v8",supervillain_woman:"unicode/1f9b9-2640.png?v8",surfer:"unicode/1f3c4.png?v8",surfing_man:"unicode/1f3c4-2642.png?v8",surfing_woman:"unicode/1f3c4-2640.png?v8",suriname:"unicode/1f1f8-1f1f7.png?v8",sushi:"unicode/1f363.png?v8",suspect:"suspect.png?v8",suspension_railway:"unicode/1f69f.png?v8",svalbard_jan_mayen:"unicode/1f1f8-1f1ef.png?v8",swan:"unicode/1f9a2.png?v8",swaziland:"unicode/1f1f8-1f1ff.png?v8",sweat:"unicode/1f613.png?v8",sweat_drops:"unicode/1f4a6.png?v8",sweat_smile:"unicode/1f605.png?v8",sweden:"unicode/1f1f8-1f1ea.png?v8",sweet_potato:"unicode/1f360.png?v8",swim_brief:"unicode/1fa72.png?v8",swimmer:"unicode/1f3ca.png?v8",swimming_man:"unicode/1f3ca-2642.png?v8",swimming_woman:"unicode/1f3ca-2640.png?v8",switzerland:"unicode/1f1e8-1f1ed.png?v8",symbols:"unicode/1f523.png?v8",synagogue:"unicode/1f54d.png?v8",syria:"unicode/1f1f8-1f1fe.png?v8",syringe:"unicode/1f489.png?v8","t-rex":"unicode/1f996.png?v8",taco:"unicode/1f32e.png?v8",tada:"unicode/1f389.png?v8",taiwan:"unicode/1f1f9-1f1fc.png?v8",tajikistan:"unicode/1f1f9-1f1ef.png?v8",takeout_box:"unicode/1f961.png?v8",tamale:"unicode/1fad4.png?v8",tanabata_tree:"unicode/1f38b.png?v8",tangerine:"unicode/1f34a.png?v8",tanzania:"unicode/1f1f9-1f1ff.png?v8",taurus:"unicode/2649.png?v8",taxi:"unicode/1f695.png?v8",tea:"unicode/1f375.png?v8",teacher:"unicode/1f9d1-1f3eb.png?v8",teapot:"unicode/1fad6.png?v8",technologist:"unicode/1f9d1-1f4bb.png?v8",teddy_bear:"unicode/1f9f8.png?v8",telephone:"unicode/260e.png?v8",telephone_receiver:"unicode/1f4de.png?v8",telescope:"unicode/1f52d.png?v8",tennis:"unicode/1f3be.png?v8",tent:"unicode/26fa.png?v8",test_tube:"unicode/1f9ea.png?v8",thailand:"unicode/1f1f9-1f1ed.png?v8",thermometer:"unicode/1f321.png?v8",thinking:"unicode/1f914.png?v8",thong_sandal:"unicode/1fa74.png?v8",thought_balloon:"unicode/1f4ad.png?v8",thread:"unicode/1f9f5.png?v8",three:"unicode/0033-20e3.png?v8",thumbsdown:"unicode/1f44e.png?v8",thumbsup:"unicode/1f44d.png?v8",ticket:"unicode/1f3ab.png?v8",tickets:"unicode/1f39f.png?v8",tiger:"unicode/1f42f.png?v8",tiger2:"unicode/1f405.png?v8",timer_clock:"unicode/23f2.png?v8",timor_leste:"unicode/1f1f9-1f1f1.png?v8",tipping_hand_man:"unicode/1f481-2642.png?v8",tipping_hand_person:"unicode/1f481.png?v8",tipping_hand_woman:"unicode/1f481-2640.png?v8",tired_face:"unicode/1f62b.png?v8",tm:"unicode/2122.png?v8",togo:"unicode/1f1f9-1f1ec.png?v8",toilet:"unicode/1f6bd.png?v8",tokelau:"unicode/1f1f9-1f1f0.png?v8",tokyo_tower:"unicode/1f5fc.png?v8",tomato:"unicode/1f345.png?v8",tonga:"unicode/1f1f9-1f1f4.png?v8",tongue:"unicode/1f445.png?v8",toolbox:"unicode/1f9f0.png?v8",tooth:"unicode/1f9b7.png?v8",toothbrush:"unicode/1faa5.png?v8",top:"unicode/1f51d.png?v8",tophat:"unicode/1f3a9.png?v8",tornado:"unicode/1f32a.png?v8",tr:"unicode/1f1f9-1f1f7.png?v8",trackball:"unicode/1f5b2.png?v8",tractor:"unicode/1f69c.png?v8",traffic_light:"unicode/1f6a5.png?v8",train:"unicode/1f68b.png?v8",train2:"unicode/1f686.png?v8",tram:"unicode/1f68a.png?v8",transgender_flag:"unicode/1f3f3-26a7.png?v8",transgender_symbol:"unicode/26a7.png?v8",triangular_flag_on_post:"unicode/1f6a9.png?v8",triangular_ruler:"unicode/1f4d0.png?v8",trident:"unicode/1f531.png?v8",trinidad_tobago:"unicode/1f1f9-1f1f9.png?v8",tristan_da_cunha:"unicode/1f1f9-1f1e6.png?v8",triumph:"unicode/1f624.png?v8",trolleybus:"unicode/1f68e.png?v8",trollface:"trollface.png?v8",trophy:"unicode/1f3c6.png?v8",tropical_drink:"unicode/1f379.png?v8",tropical_fish:"unicode/1f420.png?v8",truck:"unicode/1f69a.png?v8",trumpet:"unicode/1f3ba.png?v8",tshirt:"unicode/1f455.png?v8",tulip:"unicode/1f337.png?v8",tumbler_glass:"unicode/1f943.png?v8",tunisia:"unicode/1f1f9-1f1f3.png?v8",turkey:"unicode/1f983.png?v8",turkmenistan:"unicode/1f1f9-1f1f2.png?v8",turks_caicos_islands:"unicode/1f1f9-1f1e8.png?v8",turtle:"unicode/1f422.png?v8",tuvalu:"unicode/1f1f9-1f1fb.png?v8",tv:"unicode/1f4fa.png?v8",twisted_rightwards_arrows:"unicode/1f500.png?v8",two:"unicode/0032-20e3.png?v8",two_hearts:"unicode/1f495.png?v8",two_men_holding_hands:"unicode/1f46c.png?v8",two_women_holding_hands:"unicode/1f46d.png?v8",u5272:"unicode/1f239.png?v8",u5408:"unicode/1f234.png?v8",u55b6:"unicode/1f23a.png?v8",u6307:"unicode/1f22f.png?v8",u6708:"unicode/1f237.png?v8",u6709:"unicode/1f236.png?v8",u6e80:"unicode/1f235.png?v8",u7121:"unicode/1f21a.png?v8",u7533:"unicode/1f238.png?v8",u7981:"unicode/1f232.png?v8",u7a7a:"unicode/1f233.png?v8",uganda:"unicode/1f1fa-1f1ec.png?v8",uk:"unicode/1f1ec-1f1e7.png?v8",ukraine:"unicode/1f1fa-1f1e6.png?v8",umbrella:"unicode/2614.png?v8",unamused:"unicode/1f612.png?v8",underage:"unicode/1f51e.png?v8",unicorn:"unicode/1f984.png?v8",united_arab_emirates:"unicode/1f1e6-1f1ea.png?v8",united_nations:"unicode/1f1fa-1f1f3.png?v8",unlock:"unicode/1f513.png?v8",up:"unicode/1f199.png?v8",upside_down_face:"unicode/1f643.png?v8",uruguay:"unicode/1f1fa-1f1fe.png?v8",us:"unicode/1f1fa-1f1f8.png?v8",us_outlying_islands:"unicode/1f1fa-1f1f2.png?v8",us_virgin_islands:"unicode/1f1fb-1f1ee.png?v8",uzbekistan:"unicode/1f1fa-1f1ff.png?v8",v:"unicode/270c.png?v8",vampire:"unicode/1f9db.png?v8",vampire_man:"unicode/1f9db-2642.png?v8",vampire_woman:"unicode/1f9db-2640.png?v8",vanuatu:"unicode/1f1fb-1f1fa.png?v8",vatican_city:"unicode/1f1fb-1f1e6.png?v8",venezuela:"unicode/1f1fb-1f1ea.png?v8",vertical_traffic_light:"unicode/1f6a6.png?v8",vhs:"unicode/1f4fc.png?v8",vibration_mode:"unicode/1f4f3.png?v8",video_camera:"unicode/1f4f9.png?v8",video_game:"unicode/1f3ae.png?v8",vietnam:"unicode/1f1fb-1f1f3.png?v8",violin:"unicode/1f3bb.png?v8",virgo:"unicode/264d.png?v8",volcano:"unicode/1f30b.png?v8",volleyball:"unicode/1f3d0.png?v8",vomiting_face:"unicode/1f92e.png?v8",vs:"unicode/1f19a.png?v8",vulcan_salute:"unicode/1f596.png?v8",waffle:"unicode/1f9c7.png?v8",wales:"unicode/1f3f4-e0067-e0062-e0077-e006c-e0073-e007f.png?v8",walking:"unicode/1f6b6.png?v8",walking_man:"unicode/1f6b6-2642.png?v8",walking_woman:"unicode/1f6b6-2640.png?v8",wallis_futuna:"unicode/1f1fc-1f1eb.png?v8",waning_crescent_moon:"unicode/1f318.png?v8",waning_gibbous_moon:"unicode/1f316.png?v8",warning:"unicode/26a0.png?v8",wastebasket:"unicode/1f5d1.png?v8",watch:"unicode/231a.png?v8",water_buffalo:"unicode/1f403.png?v8",water_polo:"unicode/1f93d.png?v8",watermelon:"unicode/1f349.png?v8",wave:"unicode/1f44b.png?v8",wavy_dash:"unicode/3030.png?v8",waxing_crescent_moon:"unicode/1f312.png?v8",waxing_gibbous_moon:"unicode/1f314.png?v8",wc:"unicode/1f6be.png?v8",weary:"unicode/1f629.png?v8",wedding:"unicode/1f492.png?v8",weight_lifting:"unicode/1f3cb.png?v8",weight_lifting_man:"unicode/1f3cb-2642.png?v8",weight_lifting_woman:"unicode/1f3cb-2640.png?v8",western_sahara:"unicode/1f1ea-1f1ed.png?v8",whale:"unicode/1f433.png?v8",whale2:"unicode/1f40b.png?v8",wheel_of_dharma:"unicode/2638.png?v8",wheelchair:"unicode/267f.png?v8",white_check_mark:"unicode/2705.png?v8",white_circle:"unicode/26aa.png?v8",white_flag:"unicode/1f3f3.png?v8",white_flower:"unicode/1f4ae.png?v8",white_haired_man:"unicode/1f468-1f9b3.png?v8",white_haired_woman:"unicode/1f469-1f9b3.png?v8",white_heart:"unicode/1f90d.png?v8",white_large_square:"unicode/2b1c.png?v8",white_medium_small_square:"unicode/25fd.png?v8",white_medium_square:"unicode/25fb.png?v8",white_small_square:"unicode/25ab.png?v8",white_square_button:"unicode/1f533.png?v8",wilted_flower:"unicode/1f940.png?v8",wind_chime:"unicode/1f390.png?v8",wind_face:"unicode/1f32c.png?v8",window:"unicode/1fa9f.png?v8",wine_glass:"unicode/1f377.png?v8",wink:"unicode/1f609.png?v8",wolf:"unicode/1f43a.png?v8",woman:"unicode/1f469.png?v8",woman_artist:"unicode/1f469-1f3a8.png?v8",woman_astronaut:"unicode/1f469-1f680.png?v8",woman_beard:"unicode/1f9d4-2640.png?v8",woman_cartwheeling:"unicode/1f938-2640.png?v8",woman_cook:"unicode/1f469-1f373.png?v8",woman_dancing:"unicode/1f483.png?v8",woman_facepalming:"unicode/1f926-2640.png?v8",woman_factory_worker:"unicode/1f469-1f3ed.png?v8",woman_farmer:"unicode/1f469-1f33e.png?v8",woman_feeding_baby:"unicode/1f469-1f37c.png?v8",woman_firefighter:"unicode/1f469-1f692.png?v8",woman_health_worker:"unicode/1f469-2695.png?v8",woman_in_manual_wheelchair:"unicode/1f469-1f9bd.png?v8",woman_in_motorized_wheelchair:"unicode/1f469-1f9bc.png?v8",woman_in_tuxedo:"unicode/1f935-2640.png?v8",woman_judge:"unicode/1f469-2696.png?v8",woman_juggling:"unicode/1f939-2640.png?v8",woman_mechanic:"unicode/1f469-1f527.png?v8",woman_office_worker:"unicode/1f469-1f4bc.png?v8",woman_pilot:"unicode/1f469-2708.png?v8",woman_playing_handball:"unicode/1f93e-2640.png?v8",woman_playing_water_polo:"unicode/1f93d-2640.png?v8",woman_scientist:"unicode/1f469-1f52c.png?v8",woman_shrugging:"unicode/1f937-2640.png?v8",woman_singer:"unicode/1f469-1f3a4.png?v8",woman_student:"unicode/1f469-1f393.png?v8",woman_teacher:"unicode/1f469-1f3eb.png?v8",woman_technologist:"unicode/1f469-1f4bb.png?v8",woman_with_headscarf:"unicode/1f9d5.png?v8",woman_with_probing_cane:"unicode/1f469-1f9af.png?v8",woman_with_turban:"unicode/1f473-2640.png?v8",woman_with_veil:"unicode/1f470-2640.png?v8",womans_clothes:"unicode/1f45a.png?v8",womans_hat:"unicode/1f452.png?v8",women_wrestling:"unicode/1f93c-2640.png?v8",womens:"unicode/1f6ba.png?v8",wood:"unicode/1fab5.png?v8",woozy_face:"unicode/1f974.png?v8",world_map:"unicode/1f5fa.png?v8",worm:"unicode/1fab1.png?v8",worried:"unicode/1f61f.png?v8",wrench:"unicode/1f527.png?v8",wrestling:"unicode/1f93c.png?v8",writing_hand:"unicode/270d.png?v8",x:"unicode/274c.png?v8",yarn:"unicode/1f9f6.png?v8",yawning_face:"unicode/1f971.png?v8",yellow_circle:"unicode/1f7e1.png?v8",yellow_heart:"unicode/1f49b.png?v8",yellow_square:"unicode/1f7e8.png?v8",yemen:"unicode/1f1fe-1f1ea.png?v8",yen:"unicode/1f4b4.png?v8",yin_yang:"unicode/262f.png?v8",yo_yo:"unicode/1fa80.png?v8",yum:"unicode/1f60b.png?v8",zambia:"unicode/1f1ff-1f1f2.png?v8",zany_face:"unicode/1f92a.png?v8",zap:"unicode/26a1.png?v8",zebra:"unicode/1f993.png?v8",zero:"unicode/0030-20e3.png?v8",zimbabwe:"unicode/1f1ff-1f1fc.png?v8",zipper_mouth_face:"unicode/1f910.png?v8",zombie:"unicode/1f9df.png?v8",zombie_man:"unicode/1f9df-2642.png?v8",zombie_woman:"unicode/1f9df-2640.png?v8",zzz:"unicode/1f4a4.png?v8"}};function jn(e,t){return e.replace(/<(code|pre|script|template)[^>]*?>[\s\S]+?<\/(code|pre|script|template)>/g,function(e){return e.replace(/:/g,"__colon__")}).replace(//g,function(e){return e.replace(/:/g,"__colon__")}).replace(/([a-z]{2,}:)?\/\/[^\s'">)]+/gi,function(e){return e.replace(/:/g,"__colon__")}).replace(/:([a-z0-9_\-+]+?):/g,function(e,n){return i=e,o=n,e=t,n=Rn.data[o],i,i=n?e&&/unicode/.test(n)?''+n.replace("unicode/","").replace(/\.png.*/,"").split("-").map(function(e){return"&#x"+e+";"}).join("‍").concat("︎")+"":''+o+'':i;var i,o}).replace(/__colon__/g,":")}function On(e){var o={};return{str:e=(e=void 0===e?"":e)&&e.replace(/^('|")/,"").replace(/('|")$/,"").replace(/(?:^|\s):([\w-]+:?)=?([\w-%]+)?/g,function(e,n,i){return-1===n.indexOf(":")?(o[n]=i&&i.replace(/"/g,"")||!0,""):e}).trim(),config:o}}function Ln(e){return(e=void 0===e?"":e).replace(/(<\/?a.*?>)/gi,"")}var qn,Pn=be(function(e){var u,f,p,d,n,g=function(u){var i=/(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i,n=0,e={},T={manual:u.Prism&&u.Prism.manual,disableWorkerMessageHandler:u.Prism&&u.Prism.disableWorkerMessageHandler,util:{encode:function e(n){return n instanceof C?new C(n.type,e(n.content),n.alias):Array.isArray(n)?n.map(e):n.replace(/&/g,"&").replace(/=r.reach);m+=_.value.length,_=_.next){var b=_.value;if(i.length>n.length)return;if(!(b instanceof C)){var k,w=1;if(l){if(!(k=R(h,m,n,s))||k.index>=n.length)break;var y=k.index,x=k.index+k[0].length,S=m;for(S+=_.value.length;S<=y;)_=_.next,S+=_.value.length;if(S-=_.value.length,m=S,_.value instanceof C)continue;for(var A=_;A!==i.tail&&(Sr.reach&&(r.reach=E);b=_.prev;z&&(b=j(i,b,z),m+=z.length),O(i,b,w);$=new C(c,g?T.tokenize($,g):$,v,$);_=j(i,b,$),F&&j(i,_,F),1r.reach&&(r.reach=E.reach))}}}}}(e,t,n,t.head,0),function(e){var n=[],i=e.head.next;for(;i!==e.tail;)n.push(i.value),i=i.next;return n}(t)},hooks:{all:{},add:function(e,n){var i=T.hooks.all;i[e]=i[e]||[],i[e].push(n)},run:function(e,n){var i=T.hooks.all[e];if(i&&i.length)for(var o,t=0;o=i[t++];)o(n)}},Token:C};function C(e,n,i,o){this.type=e,this.content=n,this.alias=i,this.length=0|(o||"").length}function R(e,n,i,o){e.lastIndex=n;i=e.exec(i);return i&&o&&i[1]&&(o=i[1].length,i.index+=o,i[0]=i[0].slice(o)),i}function a(){var e={value:null,prev:null,next:null},n={value:null,prev:e,next:null};e.next=n,this.head=e,this.tail=n,this.length=0}function j(e,n,i){var o=n.next,i={value:i,prev:n,next:o};return n.next=i,o.prev=i,e.length++,i}function O(e,n,i){for(var o=n.next,t=0;t"+t.content+""},!u.document)return u.addEventListener&&(T.disableWorkerMessageHandler||u.addEventListener("message",function(e){var n=JSON.parse(e.data),i=n.language,e=n.code,n=n.immediateClose;u.postMessage(T.highlight(e,T.languages[i],i)),n&&u.close()},!1)),T;var o=T.util.currentScript();function t(){T.manual||T.highlightAll()}return o&&(T.filename=o.src,o.hasAttribute("data-manual")&&(T.manual=!0)),T.manual||("loading"===(e=document.readyState)||"interactive"===e&&o&&o.defer?document.addEventListener("DOMContentLoaded",t):window.requestAnimationFrame?window.requestAnimationFrame(t):window.setTimeout(t,16)),T}("undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{});e.exports&&(e.exports=g),void 0!==me&&(me.Prism=g),g.languages.markup={comment:{pattern://,greedy:!0},prolog:{pattern:/<\?[\s\S]+?\?>/,greedy:!0},doctype:{pattern:/"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(^[^\[]*\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^$|[[\]]/,"doctype-tag":/^DOCTYPE/i,name:/[^\s<>'"]+/}},cdata:{pattern://i,greedy:!0},tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"special-attr":[],"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/&#x?[\da-f]{1,8};/i]},g.languages.markup.tag.inside["attr-value"].inside.entity=g.languages.markup.entity,g.languages.markup.doctype.inside["internal-subset"].inside=g.languages.markup,g.hooks.add("wrap",function(e){"entity"===e.type&&(e.attributes.title=e.content.replace(/&/,"&"))}),Object.defineProperty(g.languages.markup.tag,"addInlined",{value:function(e,n){var i={};i["language-"+n]={pattern:/(^$)/i,lookbehind:!0,inside:g.languages[n]},i.cdata=/^$/i;i={"included-cdata":{pattern://i,inside:i}};i["language-"+n]={pattern:/[\s\S]+/,inside:g.languages[n]};n={};n[e]={pattern:RegExp(/(<__[^>]*>)(?:))*\]\]>|(?!)/.source.replace(/__/g,function(){return e}),"i"),lookbehind:!0,greedy:!0,inside:i},g.languages.insertBefore("markup","cdata",n)}}),Object.defineProperty(g.languages.markup.tag,"addAttribute",{value:function(e,n){g.languages.markup.tag.inside["special-attr"].push({pattern:RegExp(/(^|["'\s])/.source+"(?:"+e+")"+/\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))/.source,"i"),lookbehind:!0,inside:{"attr-name":/^[^\s=]+/,"attr-value":{pattern:/=[\s\S]+/,inside:{value:{pattern:/(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,lookbehind:!0,alias:[n,"language-"+n],inside:g.languages[n]},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}}}})}}),g.languages.html=g.languages.markup,g.languages.mathml=g.languages.markup,g.languages.svg=g.languages.markup,g.languages.xml=g.languages.extend("markup",{}),g.languages.ssml=g.languages.xml,g.languages.atom=g.languages.xml,g.languages.rss=g.languages.xml,function(e){var n=/(?:"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n])*')/;e.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:/@[\w-](?:[^;{\s]|\s+(?![\s{]))*(?:;|(?=\s*\{))/,inside:{rule:/^@[\w-]+/,"selector-function-argument":{pattern:/(\bselector\s*\(\s*(?![\s)]))(?:[^()\s]|\s+(?![\s)])|\((?:[^()]|\([^()]*\))*\))+(?=\s*\))/,lookbehind:!0,alias:"selector"},keyword:{pattern:/(^|[^\w-])(?:and|not|only|or)(?![\w-])/,lookbehind:!0}}},url:{pattern:RegExp("\\burl\\((?:"+n.source+"|"+/(?:[^\\\r\n()"']|\\[\s\S])*/.source+")\\)","i"),greedy:!0,inside:{function:/^url/i,punctuation:/^\(|\)$/,string:{pattern:RegExp("^"+n.source+"$"),alias:"url"}}},selector:{pattern:RegExp("(^|[{}\\s])[^{}\\s](?:[^{};\"'\\s]|\\s+(?![\\s{])|"+n.source+")*(?=\\s*\\{)"),lookbehind:!0},string:{pattern:n,greedy:!0},property:{pattern:/(^|[^-\w\xA0-\uFFFF])(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*(?=\s*:)/i,lookbehind:!0},important:/!important\b/i,function:{pattern:/(^|[^-a-z0-9])[-a-z0-9]+(?=\()/i,lookbehind:!0},punctuation:/[(){};:,]/},e.languages.css.atrule.inside.rest=e.languages.css;e=e.languages.markup;e&&(e.tag.addInlined("style","css"),e.tag.addAttribute("style","css"))}(g),g.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|extends|implements|instanceof|interface|new|trait)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:break|catch|continue|do|else|finally|for|function|if|in|instanceof|new|null|return|throw|try|while)\b/,boolean:/\b(?:false|true)\b/,function:/\b\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/},g.languages.javascript=g.languages.extend("clike",{"class-name":[g.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$A-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\.(?:constructor|prototype))/,lookbehind:!0}],keyword:[{pattern:/((?:^|\})\s*)catch\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|assert(?=\s*\{)|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally(?=\s*(?:\{|$))|for|from(?=\s*(?:['"]|$))|function|(?:get|set)(?=\s*(?:[#\[$\w\xA0-\uFFFF]|$))|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],function:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,number:{pattern:RegExp(/(^|[^\w$])/.source+"(?:"+/NaN|Infinity/.source+"|"+/0[bB][01]+(?:_[01]+)*n?/.source+"|"+/0[oO][0-7]+(?:_[0-7]+)*n?/.source+"|"+/0[xX][\dA-Fa-f]+(?:_[\dA-Fa-f]+)*n?/.source+"|"+/\d+(?:_\d+)*n/.source+"|"+/(?:\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\.\d+(?:_\d+)*)(?:[Ee][+-]?\d+(?:_\d+)*)?/.source+")"+/(?![\w$])/.source),lookbehind:!0},operator:/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),g.languages.javascript["class-name"][0].pattern=/(\b(?:class|extends|implements|instanceof|interface|new)\s+)[\w.\\]+/,g.languages.insertBefore("javascript","keyword",{regex:{pattern:/((?:^|[^$\w\xA0-\uFFFF."'\])\s]|\b(?:return|yield))\s*)\/(?:\[(?:[^\]\\\r\n]|\\.)*\]|\\.|[^/\\\[\r\n])+\/[dgimyus]{0,7}(?=(?:\s|\/\*(?:[^*]|\*(?!\/))*\*\/)*(?:$|[\r\n,.;:})\]]|\/\/))/,lookbehind:!0,greedy:!0,inside:{"regex-source":{pattern:/^(\/)[\s\S]+(?=\/[a-z]*$)/,lookbehind:!0,alias:"language-regex",inside:g.languages.regex},"regex-delimiter":/^\/|\/$/,"regex-flags":/^[a-z]+$/}},"function-variable":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/,lookbehind:!0,inside:g.languages.javascript},{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,lookbehind:!0,inside:g.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/,lookbehind:!0,inside:g.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/,lookbehind:!0,inside:g.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),g.languages.insertBefore("javascript","string",{hashbang:{pattern:/^#!.*/,greedy:!0,alias:"comment"},"template-string":{pattern:/`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:g.languages.javascript}},string:/[\s\S]+/}},"string-property":{pattern:/((?:^|[,{])[ \t]*)(["'])(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2(?=\s*:)/m,lookbehind:!0,greedy:!0,alias:"property"}}),g.languages.insertBefore("javascript","operator",{"literal-property":{pattern:/((?:^|[,{])[ \t]*)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/m,lookbehind:!0,alias:"property"}}),g.languages.markup&&(g.languages.markup.tag.addInlined("script","javascript"),g.languages.markup.tag.addAttribute(/on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)/.source,"javascript")),g.languages.js=g.languages.javascript,void 0!==g&&"undefined"!=typeof document&&(Element.prototype.matches||(Element.prototype.matches=Element.prototype.msMatchesSelector||Element.prototype.webkitMatchesSelector),u={js:"javascript",py:"python",rb:"ruby",ps1:"powershell",psm1:"powershell",sh:"bash",bat:"batch",h:"c",tex:"latex"},d="pre[data-src]:not(["+(f="data-src-status")+'="loaded"]):not(['+f+'="'+(p="loading")+'"])',g.hooks.add("before-highlightall",function(e){e.selector+=", "+d}),g.hooks.add("before-sanity-check",function(e){var t,n,i,o,a,r,c=e.element;c.matches(d)&&(e.code="",c.setAttribute(f,p),(t=c.appendChild(document.createElement("CODE"))).textContent="Loading…",i=c.getAttribute("data-src"),"none"===(e=e.language)&&(n=(/\.(\w+)$/.exec(i)||[,"none"])[1],e=u[n]||n),g.util.setLanguage(t,e),g.util.setLanguage(c,e),(n=g.plugins.autoloader)&&n.loadLanguages(e),i=i,o=function(e){c.setAttribute(f,"loaded");var n,i,o=function(e){if(i=/^\s*(\d+)\s*(?:(,)\s*(?:(\d+)\s*)?)?$/.exec(e||"")){var n=Number(i[1]),e=i[2],i=i[3];return e?i?[n,Number(i)]:[n,void 0]:[n,n]}}(c.getAttribute("data-range"));o&&(n=e.split(/\r\n?|\n/g),i=o[0],o=null==o[1]?n.length:o[1],i<0&&(i+=n.length),i=Math.max(0,Math.min(i-1,n.length)),o<0&&(o+=n.length),o=Math.max(0,Math.min(o,n.length)),e=n.slice(i,o).join("\n"),c.hasAttribute("data-start")||c.setAttribute("data-start",String(i+1))),t.textContent=e,g.highlightElement(t)},a=function(e){c.setAttribute(f,"failed"),t.textContent=e},(r=new XMLHttpRequest).open("GET",i,!0),r.onreadystatechange=function(){4==r.readyState&&(r.status<400&&r.responseText?o(r.responseText):400<=r.status?a("✖ Error "+r.status+" while fetching file: "+r.statusText):a("✖ Error: File does not exist or is empty"))},r.send(null))}),n=!(g.plugins.fileHighlight={highlight:function(e){for(var n,i=(e||document).querySelectorAll(d),o=0;n=i[o++];)g.highlightElement(n)}}),g.fileHighlight=function(){n||(console.warn("Prism.fileHighlight is deprecated. Use `Prism.plugins.fileHighlight.highlight` instead."),n=!0),g.plugins.fileHighlight.highlight.apply(this,arguments)})});function Mn(e,n){return"___"+e.toUpperCase()+n+"___"}qn=Prism,Object.defineProperties(qn.languages["markup-templating"]={},{buildPlaceholders:{value:function(o,t,e,a){var r;o.language===t&&(r=o.tokenStack=[],o.code=o.code.replace(e,function(e){if("function"==typeof a&&!a(e))return e;for(var n,i=r.length;-1!==o.code.indexOf(n=Mn(t,i));)++i;return r[i]=e,n}),o.grammar=qn.languages.markup)}},tokenizePlaceholders:{value:function(f,p){var d,g;f.language===p&&f.tokenStack&&(f.grammar=qn.languages[p],d=0,g=Object.keys(f.tokenStack),function e(n){for(var i=0;i=g.length);i++){var o,t,a,r,c,u=n[i];"string"==typeof u||u.content&&"string"==typeof u.content?(t=g[d],a=f.tokenStack[t],o="string"==typeof u?u:u.content,c=Mn(p,t),-1<(r=o.indexOf(c))&&(++d,t=o.substring(0,r),a=new qn.Token(p,qn.tokenize(a,f.grammar),"language-"+p,a),r=o.substring(r+c.length),c=[],t&&c.push.apply(c,e([t])),c.push(a),r&&c.push.apply(c,e([r])),"string"==typeof u?n.splice.apply(n,[i,1].concat(c)):u.content=c)):u.content&&e(u.content)}return n}(f.tokens))}}});function In(t,e){var a=this;this.config=t,this.router=e,this.cacheTree={},this.toc=[],this.cacheTOC={},this.linkTarget=t.externalLinkTarget||"_blank",this.linkRel="_blank"===this.linkTarget?t.externalLinkRel||"noopener":"",this.contentBase=e.getBasePath();var n=this._initRenderer();this.heading=n.heading;var r=o(e=t.markdown||{})?e(Sn,n):(Sn.setOptions(m(e,{renderer:m(n,e.renderer)})),Sn);this._marked=r,this.compile=function(i){var o=!0,e=c(function(e){o=!1;var n="";return i&&(n=f(i)?r(i):r.parser(i),n=t.noEmoji?n:jn(n,t.nativeEmoji),Cn.clear(),n)})(i),n=a.router.parse().file;return o?a.toc=a.cacheTOC[n]:a.cacheTOC[n]=[].concat(a.toc),e}}var Nn={},Hn={markdown:function(e){return{url:e}},mermaid:function(e){return{url:e}},iframe:function(e,n){return{html:'"}},video:function(e,n){return{html:'"}},audio:function(e,n){return{html:'"}},code:function(e,n){var i=e.match(/\.(\w+)$/);return{url:e,lang:i="md"===(i=n||i&&i[1])?"markdown":i}}};In.prototype.compileEmbed=function(e,n){var i,o,t=On(n),a=t.str,t=t.config;if(n=a,t.include)return T(e)||(e=q(this.contentBase,R(this.router.getCurrentPath()),e)),t.type&&(o=Hn[t.type])?(i=o.call(this,e,n)).type=t.type:(o="code",/\.(md|markdown)/.test(e)?o="markdown":/\.mmd/.test(e)?o="mermaid":/\.html?/.test(e)?o="iframe":/\.(mp4|ogg)/.test(e)?o="video":/\.mp3/.test(e)&&(o="audio"),(i=Hn[o].call(this,e,n)).type=o),i.fragment=t.fragment,i},In.prototype._matchNotCompileLink=function(e){for(var n=this.config.noCompileLinks||[],i=0;i/g.test(o)&&(o=o.replace("\x3c!-- {docsify-ignore} --\x3e",""),e.title=Ln(o),e.ignoreSubHeading=!0),/{docsify-ignore}/g.test(o)&&(o=o.replace("{docsify-ignore}",""),e.title=Ln(o),e.ignoreSubHeading=!0),//g.test(o)&&(o=o.replace("\x3c!-- {docsify-ignore-all} --\x3e",""),e.title=Ln(o),e.ignoreAllSubs=!0),/{docsify-ignore-all}/g.test(o)&&(o=o.replace("{docsify-ignore-all}",""),e.title=Ln(o),e.ignoreAllSubs=!0);i=Cn(t.id||o),t=a.toURL(a.getCurrentPath(),{id:i});return e.slug=t,g.toc.push(e),"'+o+""},t.code={renderer:e}.renderer.code=function(e,n){var i=Pn.languages[n=void 0===n?"markup":n]||Pn.languages.markup;return'
    '+Pn.highlight(e.replace(/@DOCSIFY_QM@/g,"`"),i,n)+"
    "},t.link=(i=(n={renderer:e,router:a,linkTarget:n,linkRel:i,compilerClass:g}).renderer,c=n.router,u=n.linkTarget,n.linkRel,f=n.compilerClass,i.link=function(e,n,i){var o=[],t=On(n=void 0===n?"":n),a=t.str,t=t.config;return u=t.target||u,r="_blank"===u?f.config.externalLinkRel||"noopener":"",n=a,T(e)||f._matchNotCompileLink(e)||t.ignore?(T(e)||"./"!==e.slice(0,2)||(e=document.URL.replace(/\/(?!.*\/).*/,"/").replace("#/./","")+e),o.push(0===e.indexOf("mailto:")?"":'target="'+u+'"'),o.push(0!==e.indexOf("mailto:")&&""!==r?' rel="'+r+'"':"")):(e===f.config.homepage&&(e="README"),e=c.toURL(e,null,c.getCurrentPath())),t.crossorgin&&"_self"===u&&"history"===f.config.routerMode&&-1===f.config.crossOriginLinks.indexOf(e)&&f.config.crossOriginLinks.push(e),t.disabled&&(o.push("disabled"),e="javascript:void(0)"),t.class&&o.push('class="'+t.class+'"'),t.id&&o.push('id="'+t.id+'"'),n&&o.push('title="'+n+'"'),'"+i+""}),t.paragraph={renderer:e}.renderer.paragraph=function(e){e=/^!>/.test(e)?$n("tip",e):/^\?>/.test(e)?$n("warn",e):"

    "+e+"

    ";return e},t.image=(o=(i={renderer:e,contentBase:o,router:a}).renderer,p=i.contentBase,d=i.router,o.image=function(e,n,i){var o=e,t=[],a=On(n),r=a.str,a=a.config;return n=r,a["no-zoom"]&&t.push("data-no-zoom"),n&&t.push('title="'+n+'"'),a.size&&(n=(r=a.size.split("x"))[0],(r=r[1])?t.push('width="'+n+'" height="'+r+'"'):t.push('width="'+n+'"')),a.class&&t.push('class="'+a.class+'"'),a.id&&t.push('id="'+a.id+'"'),T(e)||(o=q(p,R(d.getCurrentPath()),e)),0":''+i+'"}),t.list={renderer:e}.renderer.list=function(e,n,i){n=n?"ol":"ul";return"<"+n+" "+[/
  • /.test(e.split('class="task-list"')[0])?'class="task-list"':"",i&&1"+e+""},t.listitem={renderer:e}.renderer.listitem=function(e){return/^(]*>)/.test(e)?'
  • ":"
  • "+e+"
  • "},e.origin=t,e},In.prototype.sidebar=function(e,n){var i=this.toc,o=this.router.getCurrentPath(),t="";if(e)t=this.compile(e);else{for(var a=0;a{inner}");this.cacheTree[o]=n}return t},In.prototype.subSidebar=function(e){if(e){var n=this.router.getCurrentPath(),i=this.cacheTree,o=this.toc;o[0]&&o[0].ignoreAllSubs&&o.splice(0),o[0]&&1===o[0].level&&o.shift();for(var t=0;t\n'+e+"\n"}]).links={}:(n=[{type:"html",text:e}]).links={}),a({token:t,embedToken:n}),++u>=c&&a({})}}(n);n.embed.url?X(n.embed.url).then(o):o(n.embed.html)}}({compile:i,embedTokens:c,fetch:n},function(e){var n,i=e.embedToken,e=e.token;e?(n=e.index,p.forEach(function(e){n>e.start&&(n+=e.length)}),m(f,i.links),r=r.slice(0,n).concat(i,r.slice(n+1)),p.push({start:n,length:i.length-1})):(Bn[t]=r.concat(),r.links=Bn[t].links=f,o(r))})}function Yn(e,n,i){var o,t,a,r;return n="function"==typeof i?i(n):"string"==typeof i?(a=[],r=0,(o=i).replace(V,function(n,e,i){a.push(o.substring(r,i-1)),r=i+=n.length+1,a.push(t&&t[n]||function(e){return("00"+("string"==typeof Y[n]?e[Y[n]]():Y[n](e))).slice(-n.length)})}),r!==o.length&&a.push(o.substring(r)),function(e){for(var n="",i=0,o=e||new Date;i404 - Not found","Vue"in window)for(var a=0,r=k(".markdown-section > *").filter(n);ascript").filter(function(e){return!/template/.test(e.type)})[0])||(e=e.innerText.trim())&&new Function(e)()),"Vue"in window){var u,f,p=[],d=Object.keys(i.vueComponents||{});2===t&&d.length&&d.forEach(function(e){window.Vue.options.components[e]||window.Vue.component(e,i.vueComponents[e])}),!Un&&i.vueGlobalOptions&&"function"==typeof i.vueGlobalOptions.data&&(Un=i.vueGlobalOptions.data()),p.push.apply(p,Object.keys(i.vueMounts||{}).map(function(e){return[b(o,e),i.vueMounts[e]]}).filter(function(e){var n=e[0];e[1];return n})),(i.vueGlobalOptions||d.length)&&(u=/{{2}[^{}]*}{2}/,f=/<[^>/]+\s([@:]|v-)[\w-:.[\]]+[=>\s]/,p.push.apply(p,k(".markdown-section > *").filter(function(i){return!p.some(function(e){var n=e[0];e[1];return n===i})}).filter(function(e){return e.tagName.toLowerCase()in(i.vueComponents||{})||e.querySelector(d.join(",")||null)||u.test(e.outerHTML)||f.test(e.outerHTML)}).map(function(e){var n=m({},i.vueGlobalOptions||{});return Un&&(n.data=function(){return Un}),[e,n]})));for(var g=0,s=p;g([^<]*?)

    $'))&&("color"===n[2]?o.style.background=n[1]+(n[3]||""):(e=n[1],S(o,"add","has-mask"),T(n[1])||(e=q(this.router.getBasePath(),n[1])),o.style.backgroundImage="url("+e+")",o.style.backgroundSize="cover",o.style.backgroundPosition="center center"),i=i.replace(n[0],"")),this._renderTo(".cover-main",i),K()):S(o,"remove","show")},n.prototype._updateRender=function(){var e,n,i,o;e=this,n=l(".app-name-link"),i=e.config.nameLink,o=e.route.path,n&&(f(e.config.nameLink)?n.setAttribute("href",i):"object"==typeof i&&(e=Object.keys(i).filter(function(e){return-1':"")),e.coverpage&&(f+=(o=", 100%, 85%",'
    \x3c!--cover--\x3e
    ')),e.logo&&(o=/^data:image/.test(e.logo),n=/(?:http[s]?:)?\/\//.test(e.logo),i=/^\./.test(e.logo),o||n||i||(e.logo=q(this.router.getBasePath(),e.logo))),f+=(i=(n=e).name||"","
    "+('')+'
    \x3c!--main--\x3e
    '),this._renderTo(u,f,!0)):this.rendered=!0,e.mergeNavbar&&s?p=b(".sidebar"):(c.classList.add("app-nav"),e.repo||c.classList.add("no-badge")),e.loadNavbar&&y(p,c),e.themeColor&&(v.head.appendChild(w("div","").firstElementChild),a=e.themeColor,window.CSS&&window.CSS.supports&&window.CSS.supports("(--v:red)")||(e=k("style:not(.inserted),link"),[].forEach.call(e,function(e){"STYLE"===e.nodeName?Q(e,a):"LINK"===e.nodeName&&(e=e.getAttribute("href"),/\.css$/.test(e)&&X(e).then(function(e){e=w("style",e);_.appendChild(e),Q(e,a)}))}))),this._updateRender(),S(h,"ready")},n}(function(e){function n(){e.apply(this,arguments)}return e&&(n.__proto__=e),((n.prototype=Object.create(e&&e.prototype)).constructor=n).prototype.routes=function(){return this.config.routes||{}},n.prototype.matchVirtualRoute=function(t){var a=this.routes(),r=Object.keys(a),c=function(){return null};function u(){var e=r.shift();if(!e)return c(null);var n=A(o=(i="^",0===(o=e).indexOf(i)?o:"^"+o),"$")?o:o+"$",i=t.match(n);if(!i)return u();var o=a[e];if("string"==typeof o)return c(o);if("function"!=typeof o)return u();n=o,e=Xn(),o=e[0];return(0,e[1])(function(e){return"string"==typeof e?c(e):!1===e?c(null):u()}),n.length<=2?o(n(t,i)):n(t,i,o)}return{then:function(e){c=e,u()}}},n}(function(i){function e(){for(var e=[],n=arguments.length;n--;)e[n]=arguments[n];i.apply(this,e),this.route={}}return i&&(e.__proto__=i),((e.prototype=Object.create(i&&i.prototype)).constructor=e).prototype.updateRender=function(){this.router.normalize(),this.route=this.router.parse(),h.setAttribute("data-page",this.route.file)},e.prototype.initRouter=function(){var n=this,e=this.config,e=new("history"===(e.routerMode||"hash")&&t?D:H)(e);this.router=e,this.updateRender(),U=this.route,e.onchange(function(e){n.updateRender(),n._updateRender(),U.path!==n.route.path?(n.$fetch(d,n.$resetEvents.bind(n,e.source)),U=n.route):n.$resetEvents(e.source)})},e}(function(e){function n(){e.apply(this,arguments)}return e&&(n.__proto__=e),((n.prototype=Object.create(e&&e.prototype)).constructor=n).prototype.initLifecycle=function(){var i=this;this._hooks={},this._lifecycle={},["init","mounted","beforeEach","afterEach","doneEach","ready"].forEach(function(e){var n=i._hooks[e]=[];i._lifecycle[e]=function(e){return n.push(e)}})},n.prototype.callHook=function(e,t,a){void 0===a&&(a=d);var r=this._hooks[e],c=this.config.catchPluginErrors,u=function(n){var e=r[n];if(n>=r.length)a(t);else if("function"==typeof e){var i="Docsify plugin error";if(2===e.length)try{e(t,function(e){t=e,u(n+1)})}catch(e){if(!c)throw e;console.error(i,e),u(n+1)}else try{var o=e(t);t=void 0===o?t:o,u(n+1)}catch(e){if(!c)throw e;console.error(i,e),u(n+1)}}else u(n+1)};u(0)},n}(we))))))));function Kn(e,n,i){return Qn&&Qn.abort&&Qn.abort(),Qn=X(e,!0,i)}window.Docsify={util:Me,dom:n,get:X,slugify:Cn,version:"4.13.0"},window.DocsifyCompiler=In,window.marked=Sn,window.Prism=Pn,e(function(e){return new Jn})}(); ================================================ FILE: docker/README.md ================================================ 可以在当前目录下: 使用`docker compose up -d`直接运行。 使用`docker compose up -d --build --force-recreate`强制重新构建所有服务的镜像并删除旧容器重新运行 `.env`用来配置环境变量,在这里配好之后,其它的配置会自动联动的。 `build.sh`用来以日期为tag构建镜像,推送到指定的容器注册表内(Windows下可以使用`Git Bash`运行) 其它的文件的作用暂不明确 ================================================ FILE: docker/build.sh ================================================ #!/bin/bash # 获取当前日期作为标签(格式:YYYYMMDD) date_tag=$(date +%Y%m%d) # 切换到脚本所在目录的上一级目录作为工作目录 cd "$(dirname "$0")/.." || { echo "错误:无法切换到上级目录" exit 1 } echo "已切换工作目录到:$(pwd)" # 检查私有仓库环境变量 if [ -z "$DOCKER_REGISTRY" ]; then echo "未设置DOCKER_REGISTRY环境变量" read -p "请输入私有Docker注册库地址(如不推送请留空): " input_registry docker_registry="$input_registry" else docker_registry="$DOCKER_REGISTRY" fi # 定义要构建的镜像和对应的Dockerfile路径(相对当前工作目录) images=( "wvp-service:docker/wvp/Dockerfile" "wvp-nginx:docker/nginx/Dockerfile" ) # 构建镜像的函数 build_image() { local image_name="$1" local dockerfile_path="$2" # 检查Dockerfile是否存在 if [ ! -f "$dockerfile_path" ]; then echo "错误:未找到Dockerfile - \"$dockerfile_path\",跳过构建" return 1 fi # 构建镜像 local full_image_name="${image_name}:${date_tag}" echo echo "==============================================" echo "开始构建镜像:${full_image_name}" echo "Dockerfile路径:${dockerfile_path}" docker build -t "${full_image_name}" -f "${dockerfile_path}" . if [ $? -ne 0 ]; then echo "镜像${full_image_name}构建失败" return 1 fi # 推送镜像(如果设置了仓库地址) if [ -n "$docker_registry" ]; then local registry_image="${docker_registry}/${full_image_name}" echo "给镜像打标签:${registry_image}" docker tag "${full_image_name}" "${registry_image}" echo "推送镜像到注册库" docker push "${registry_image}" if [ $? -eq 0 ]; then echo "镜像${registry_image}推送成功" else echo "镜像${registry_image}推送失败" fi else echo "未提供注册库地址,不执行推送" fi echo "==============================================" echo } # 循环构建所有镜像 for item in "${images[@]}"; do IFS=':' read -r image_name dockerfile_path <<< "$item" build_image "$image_name" "$dockerfile_path" done echo "所有镜像处理完成" exit 0 ================================================ FILE: docker/docker-compose.yml ================================================ version: '3' services: polaris-redis: image: redis:latest # 使用官方Redis镜像 restart: unless-stopped healthcheck: test: [ "CMD", "redis-cli", "--raw", "incr", "ping" ] interval: 15s timeout: 5s retries: 10 start_period: 10s networks: - media-net # ports: # - 6379:6379 volumes: - ./redis/conf/redis.conf:/opt/polaris/redis/redis.conf - ./volumes/redis/data/:/data environment: TZ: "Asia/Shanghai" command: redis-server /opt/polaris/redis/redis.conf --appendonly yes polaris-mysql: image: mysql:8 # 使用官方MySQL 8镜像 restart: unless-stopped healthcheck: test: [ "CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/3306" ] interval: 15s timeout: 5s retries: 10 start_period: 10s networks: - media-net environment: MYSQL_DATABASE: wvp MYSQL_ROOT_PASSWORD: root MYSQL_USER: wvp_user MYSQL_PASSWORD: wvp_password TZ: Asia/Shanghai # ports: # - 3306:3306 volumes: - ./mysql/conf:/etc/mysql/conf.d - ./logs/mysql:/logs - ./volumes/mysql/data:/var/lib/mysql - ../数据库/2.7.4/初始化-mysql-2.7.4.sql:/docker-entrypoint-initdb.d/init.sql # 初始化SQL脚本目录 command: [ # '--default-authentication-plugin=mysql_native_password', '--innodb-buffer-pool-size=80M', '--character-set-server=utf8mb4', '--collation-server=utf8mb4_general_ci', '--default-time-zone=+8:00', '--lower-case-table-names=1' ] polaris-media: image: zlmediakit/zlmediakit:master # 替换为官方镜像 restart: always networks: - media-net ports: #- "6080:80/tcp" # [播流]HTTP 安全考虑-非测试阶段需要注释掉,改为由nginx代理播流地址 #- "4443:443/tcp" # [播流]HTTPS 安全考虑-非测试阶段需要注释掉,改为由nginx代理播流地址 - "${MediaRtmp:-10935}:${MediaRtmp:-10935}/tcp" # [收流]RTMP - "${MediaRtmp:-10935}:${MediaRtmp:-10935}/udp" # [收流]RTMP #- "41935:41935/tcp" # [收流]RTMPS 无效 - "${MediaRtsp:-5540}:${MediaRtsp:-5540}/tcp" # [收流]RTSP - "${MediaRtsp:-5540}:${MediaRtsp:-5540}/udp" # [收流]RTSP #- "45540:45540/tcp" # [收流]RTSPS 无效 - "${MediaRtp:-10000}:${MediaRtp:-10000}/tcp" # [收流]RTP - "${MediaRtp:-10000}:${MediaRtp:-10000}/udp" # [收流]RTP volumes: - ./volumes/video:/opt/media/bin/www/record/ - ./logs/media:/opt/media/log/ - ./media/config.ini:/conf/config.ini command: [ 'MediaServer', '-c', '/conf/config.ini', '-l', '0' ] polaris-wvp: # 显式指定构建上下文和Dockerfile路径 build: context: .. # 构建上下文的根路径 dockerfile: ./docker/wvp/Dockerfile # 相对于上下文路径的Dockerfile位置 restart: always networks: - media-net ports: - "18978:18978" - "${SIP_Port:-8116}:${SIP_Port:-8116}/udp" - "${SIP_Port:-8116}:${SIP_Port:-8116}/tcp" depends_on: - polaris-redis - polaris-mysql - polaris-media volumes: - ./wvp/config:/opt/wvp/config - ./wvp/wvp/:/opt/ylcx/wvp/ - ./logs/wvp:/opt/wvp/logs/ environment: TZ: "Asia/Shanghai" # 流链接的IP Stream_IP: ${Stream_IP} # SDP里的IP SDP_IP: ${SDP_IP} # [可选] zlm服务器访问WVP所使用的IP, 默认使用127.0.0.1,zlm和wvp没有部署在同一台服务器时必须配置 ZLM_HOOK_HOST: polaris-wvp ZLM_HOST: polaris-media ZLM_SERCERT: su6TiedN2rVAmBbIDX0aa0QTiBJLBdcf MediaHttp: ${WebHttp:-8080} #MediaHttps: ${WebHttps:-8081} MediaRtmp: ${MediaRtmp:-10935} MediaRtsp: ${MediaRtsp:-5540} MediaRtp: ${MediaRtp:-10000} REDIS_HOST: polaris-redis REDIS_PORT: 6379 DATABASE_HOST: polaris-mysql DATABASE_PORT: 3306 DATABASE_USER: wvp_user DATABASE_PASSWORD: wvp_password SIP_ShowIP: ${SIP_ShowIP} SIP_Port: ${SIP_Port:-8116} SIP_Domain: ${SIP_Domain} SIP_Id: ${SIP_Id} SIP_Password: ${SIP_Password} RecordSip: ${RecordSip} RecordPushLive: ${RecordPushLive} polaris-nginx: # 显式指定构建上下文和Dockerfile路径 build: context: .. # 构建上下文的根路径 dockerfile: ./docker/nginx/Dockerfile # 相对于上下文路径的Dockerfile位置 ports: - "${WebHttp:-8080}:8080" depends_on: - polaris-wvp volumes: - ./nginx/templates/:/etc/nginx/templates - ./logs/nginx:/var/log/nginx environment: # 流链接的IP Stream_IP: ${Stream_IP} networks: - media-net networks: media-net: driver: bridge ================================================ FILE: docker/docker-upgrade.sh ================================================ #/bin/bash set -e docker compose down docker compose up -d --remove-orphans ================================================ FILE: docker/media/Dockerfile ================================================ FROM ubuntu:20.04 AS build #shell,rtmp,rtsp,rtsps,http,rtp EXPOSE 10935/tcp EXPOSE 5540/tcp EXPOSE 6080/tcp EXPOSE 10000/udp EXPOSE 10000/tcp EXPOSE 8000/udp EXPOSE 8000/tcp EXPOSE 9000/udp # ADD sources.list /etc/apt/sources.list RUN apt-get update && \ DEBIAN_FRONTEND="noninteractive" \ apt-get install -y --no-install-recommends \ build-essential \ cmake \ git \ curl \ vim \ wget \ ca-certificates \ tzdata \ libssl-dev \ gcc \ g++ \ gdb && \ apt-get autoremove -y && \ apt-get clean -y && \ rm -rf /var/lib/apt/lists/* RUN mkdir -p /opt/media WORKDIR /opt/media RUN git clone --depth 1 https://gitee.com/xia-chu/ZLMediaKit && \ cd ZLMediaKit && git submodule update --init # 3rdpart init WORKDIR /opt/media/ZLMediaKit/3rdpart RUN wget https://polaris-tian-generic.pkg.coding.net/qt/dependencies/openssl-1.1.1k.tar.gz?version=latest -O openssl-1.1.1k.tar.gz && \ tar -xvzf openssl-1.1.1k.tar.gz && \ cd openssl-1.1.1k && ./config shared --openssldir=/usr/local/openssl --prefix=/usr/local/openssl && \ make && make install && \ echo "/usr/local/lib64/" >> /etc/ld.so.conf && \ echo "/usr/local/openssl/lib" >> /etc/ld.so.conf && \ ldconfig && \ ln -s /usr/local/openssl/bin/openssl /usr/local/bin/openssl WORKDIR /opt/media/ZLMediaKit/3rdpart RUN wget https://github.com/cisco/libsrtp/archive/v2.3.0.tar.gz -O libsrtp-2.3.0.tar.gz && \ tar xfv libsrtp-2.3.0.tar.gz && \ mv libsrtp-2.3.0 libsrtp && \ cd libsrtp && ./configure --enable-openssl --with-openssl-dir=/usr/local/openssl && make -j $(nproc) && make install WORKDIR /opt/media/ZLMediaKit/build RUN cmake .. -DENABLE_WEBRTC=true -DOPENSSL_ROOT_DIR=/usr/local/openssl -DOPENSSL_LIBRARIES=/usr/local/openssl/lib && \ cmake --build . --target MediaServer COPY config.ini /opt/media/ZLMediaKit/release/linux/Debug/ FROM ubuntu:20.04 RUN apt-get update && \ DEBIAN_FRONTEND="noninteractive" \ apt-get install -y --no-install-recommends \ vim \ wget \ ca-certificates \ tzdata \ curl \ libssl-dev \ ffmpeg \ gcc \ g++ \ gdb && \ apt-get autoremove -y && \ apt-get clean -y && \ rm -rf /var/lib/apt/lists/* ENV TZ=Asia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime \ && echo $TZ > /etc/timezone && \ mkdir -p /opt/media/bin/www WORKDIR /opt/media/bin/ COPY --from=build /opt/media/ZLMediaKit/release/linux/Debug/MediaServer /opt/media/ZLMediaKit/default.pem /opt/media/bin/ COPY --from=build /opt/media/ZLMediaKit/release/linux/Debug/config.ini /opt/media/conf/ COPY --from=build /opt/media/ZLMediaKit/www/ /opt/media/bin/www/ ENV PATH /opt/media/bin:$PATH CMD ["./MediaServer","-s", "default.pem", "-c", "../conf/config.ini", "-l","0"] ================================================ FILE: docker/media/build.sh ================================================ #/bin/bash set -e version=2.7.3 docker build -t polaris-media:${version} . docker tag polaris-media:${version} polaris-tian-docker.pkg.coding.net/qt/polaris/polaris-media:${version} docker tag polaris-media:${version} polaris-tian-docker.pkg.coding.net/qt/polaris/polaris-media:latest ================================================ FILE: docker/media/config.ini ================================================ ; auto-generated by mINI class { [api] apiDebug=1 defaultSnap=./www/logo.png downloadRoot=./www; secret=su6TiedN2rVAmBbIDX0aa0QTiBJLBdcf snapRoot=./www/snap/ [cluster] origin_url= retry_count=3 timeout_sec=15 [ffmpeg] bin=/usr/bin/ffmpeg cmd=%s -re -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s log=./ffmpeg/ffmpeg.log restart_sec=0 snap=%s -rtsp_transport tcp -i %s -y -f mjpeg -frames:v 1 %s [general] broadcast_player_count_changed=0 check_nvidia_dev=1 enableVhost=0 enable_ffmpeg_log=0 flowThreshold=1024 listen_ip=:: maxStreamWaitMS=15000 mediaServerId=polaris mergeWriteMS=0 resetWhenRePlay=1 streamNoneReaderDelayMS=20000 unready_frame_cache=100 wait_add_track_ms=3000 wait_audio_track_data_ms=1000 wait_track_ready_ms=10000 [hls] broadcastRecordTs=0 deleteDelaySec=10 fastRegister=0 fileBufSize=65536 segDelay=0 segDur=2 segKeep=0 segNum=3 segRetain=5 [hook] alive_interval=10.0 enable=1 on_flow_report= on_http_access= on_play=http://polaris-wvp:18978/index/hook/on_play on_publish=http://polaris-wvp:18978/index/hook/on_publish on_record_mp4=http://polaris-wvp:18978/index/hook/on_record_mp4 on_record_ts= on_rtp_server_timeout=http://polaris-wvp:18978/index/hook/on_rtp_server_timeout on_rtsp_auth= on_rtsp_realm= on_send_rtp_stopped=http://polaris-wvp:18978/index/hook/on_send_rtp_stopped on_server_exited= on_server_keepalive=http://polaris-wvp:18978/index/hook/on_server_keepalive on_server_started=http://polaris-wvp:18978/index/hook/on_server_started on_shell_login= on_stream_changed=http://polaris-wvp:18978/index/hook/on_stream_changed on_stream_none_reader=http://polaris-wvp:18978/index/hook/on_stream_none_reader on_stream_not_found=http://polaris-wvp:18978/index/hook/on_stream_not_found retry=1 retry_delay=3.0 stream_changed_schemas=rtsp/rtmp/fmp4/ts/hls/hls.fmp4 timeoutSec=30 [http] allow_cross_domains=1 allow_ip_range= charSet=utf-8 dirMenu=1 forbidCacheSuffix= forwarded_ip_header= keepAliveSecond=30 maxReqSize=40960 notFound=404 Not Found

    您访问的资源不存在!


    ZLMediaKit(git hash:8ccb4e9/%aI,branch:master,build time:2024-11-07T10:34:19)
    port=80 rootPath=./www sendBufSize=65536 sslport=443 virtualPath= [multicast] addrMax=239.255.255.255 addrMin=239.0.0.0 udpTTL=64 [protocol] add_mute_audio=1 auto_close=0 continue_push_ms=3000 enable_audio=1 enable_fmp4=1 enable_hls=0 enable_hls_fmp4=0 enable_mp4=0 enable_rtmp=1 enable_rtsp=1 enable_ts=1 fmp4_demand=0 hls_demand=0 hls_save_path=./www modify_stamp=2 mp4_as_player=0 mp4_max_second=3600 mp4_save_path=/opt/media/bin/www paced_sender_ms=0 rtmp_demand=0 rtsp_demand=0 ts_demand=0 [record] appName=record enableFmp4=1 fastStart=0 fileBufSize=65536 fileRepeat=0 sampleMS=500 [rtc] bfilter=0 datachannel_echo=0 externIP= maxRtpCacheMS=5000 maxRtpCacheSize=2048 max_bitrate=0 min_bitrate=0 nackIntervalRatio=1.0 nackMaxCount=15 nackMaxMS=3000 nackMaxSize=2048 nackRtpSize=8 port=8000 preferredCodecA=PCMA,PCMU,opus,mpeg4-generic preferredCodecV=H264,H265,AV1,VP9,VP8 rembBitRate=0 start_bitrate=0 tcpPort=8000 timeoutSec=30 [rtmp] directProxy=1 enhanced=0 handshakeSecond=15 keepAliveSecond=15 port=10001 sslport=0 [rtp] audioMtuSize=600 h264_stap_a=1 lowLatency=0 rtpMaxSize=10 videoMtuSize=1400 [rtp_proxy] dumpDir= gop_cache=1 h264_pt=98 h265_pt=99 merge_frame=1 opus_pt=100 port=10003 port_range=30000-30500 ps_pt=96 rtp_g711_dur_ms=100 timeoutSec=15 udp_recv_socket_buffer=4194304 [rtsp] authBasic=0 directProxy=1 handshakeSecond=15 keepAliveSecond=15 lowLatency=0 port=10002 rtpTransportType=-1 sslport=0 [shell] maxReqSize=1024 port=0 [srt] latencyMul=4 passPhrase= pktBufSize=8192 port=9000 timeoutSec=5 ; } --- ================================================ FILE: docker/mysql/Dockerfile ================================================ FROM mysql:8.0.32 ADD ./db/*.sql /docker-entrypoint-initdb.d/ ================================================ FILE: docker/mysql/build.sh ================================================ #/bin/bash set -e version=2.7.3 docker build -t polaris-mysql:${version} . docker tag polaris-mysql:${version} polaris-tian-docker.pkg.coding.net/qt/polaris/polaris-mysql:${version} docker tag polaris-mysql:${version} polaris-tian-docker.pkg.coding.net/qt/polaris/polaris-mysql:latest ================================================ FILE: docker/mysql/db/privileges.sql ================================================ use mysql; grant all privileges on wvp.* to 'ylcx'@'%'; flush privileges; ================================================ FILE: docker/mysql/db/wvp.sql ================================================ /*建库*/ DROP DATABASE IF EXISTS `wvp`; CREATE DATABASE `wvp` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; USE `wvp`; /*建表*/ drop table IF EXISTS wvp_device; create table IF NOT EXISTS wvp_device ( id serial primary key, device_id character varying(50) not null, name character varying(255), manufacturer character varying(255), model character varying(255), firmware character varying(255), transport character varying(50), stream_mode character varying(50), on_line bool default false, register_time character varying(50), keepalive_time character varying(50), ip character varying(50), create_time character varying(50), update_time character varying(50), port integer, expires integer, subscribe_cycle_for_catalog integer DEFAULT 0, subscribe_cycle_for_mobile_position integer DEFAULT 0, mobile_position_submission_interval integer DEFAULT 5, subscribe_cycle_for_alarm integer DEFAULT 0, host_address character varying(50), charset character varying(50), ssrc_check bool default false, geo_coord_sys character varying(50), media_server_id character varying(50) default 'auto', custom_name character varying(255), sdp_ip character varying(50), local_ip character varying(50), password character varying(255), as_message_channel bool default false, heart_beat_interval integer, heart_beat_count integer, position_capability integer, broadcast_push_after_ack bool default false, server_id character varying(50), constraint uk_device_device unique (device_id) ); drop table IF EXISTS wvp_device_alarm; create table IF NOT EXISTS wvp_device_alarm ( id serial primary key, device_id character varying(50) not null, channel_id character varying(50) not null, alarm_priority character varying(50), alarm_method character varying(50), alarm_time character varying(50), alarm_description character varying(255), longitude double precision, latitude double precision, alarm_type character varying(50), create_time character varying(50) not null ); drop table IF EXISTS wvp_device_mobile_position; create table IF NOT EXISTS wvp_device_mobile_position ( id serial primary key, device_id character varying(50) not null, channel_id character varying(50) not null, device_name character varying(255), time character varying(50), longitude double precision, latitude double precision, altitude double precision, speed double precision, direction double precision, report_source character varying(50), create_time character varying(50) ); drop table IF EXISTS wvp_device_channel; create table IF NOT EXISTS wvp_device_channel ( id serial primary key, device_id character varying(50), name character varying(255), manufacturer character varying(50), model character varying(50), owner character varying(50), civil_code character varying(50), block character varying(50), address character varying(50), parental integer, parent_id character varying(50), safety_way integer, register_way integer, cert_num character varying(50), certifiable integer, err_code integer, end_time character varying(50), secrecy integer, ip_address character varying(50), port integer, password character varying(255), status character varying(50), longitude double precision, latitude double precision, ptz_type integer, position_type integer, room_type integer, use_type integer, supply_light_type integer, direction_type integer, resolution character varying(255), business_group_id character varying(255), download_speed character varying(255), svc_space_support_mod integer, svc_time_support_mode integer, create_time character varying(50) not null, update_time character varying(50) not null, sub_count integer, stream_id character varying(255), has_audio bool default false, gps_time character varying(50), stream_identification character varying(50), channel_type int default 0 not null, gb_device_id character varying(50), gb_name character varying(255), gb_manufacturer character varying(255), gb_model character varying(255), gb_owner character varying(255), gb_civil_code character varying(255), gb_block character varying(255), gb_address character varying(255), gb_parental integer, gb_parent_id character varying(255), gb_safety_way integer, gb_register_way integer, gb_cert_num character varying(50), gb_certifiable integer, gb_err_code integer, gb_end_time character varying(50), gb_secrecy integer, gb_ip_address character varying(50), gb_port integer, gb_password character varying(50), gb_status character varying(50), gb_longitude double, gb_latitude double, gb_business_group_id character varying(50), gb_ptz_type integer, gb_position_type integer, gb_room_type integer, gb_use_type integer, gb_supply_light_type integer, gb_direction_type integer, gb_resolution character varying(255), gb_download_speed character varying(255), gb_svc_space_support_mod integer, gb_svc_time_support_mode integer, record_plan_id integer, data_type integer not null, data_device_id integer not null, gps_speed double precision, gps_altitude double precision, gps_direction double precision, index (data_type), index (data_device_id), constraint uk_wvp_unique_channel unique (gb_device_id) ); drop table IF EXISTS wvp_media_server; create table IF NOT EXISTS wvp_media_server ( id character varying(255) primary key, ip character varying(50), hook_ip character varying(50), sdp_ip character varying(50), stream_ip character varying(50), http_port integer, http_ssl_port integer, rtmp_port integer, rtmp_ssl_port integer, rtp_proxy_port integer, rtsp_port integer, rtsp_ssl_port integer, flv_port integer, flv_ssl_port integer, ws_flv_port integer, ws_flv_ssl_port integer, auto_config bool default false, secret character varying(50), type character varying(50) default 'zlm', rtp_enable bool default false, rtp_port_range character varying(50), send_rtp_port_range character varying(50), record_assist_port integer, default_server bool default false, create_time character varying(50), update_time character varying(50), hook_alive_interval integer, record_path character varying(255), record_day integer default 7, transcode_suffix character varying(255), server_id character varying(50), constraint uk_media_server_unique_ip_http_port unique (ip, http_port, server_id) ); drop table IF EXISTS wvp_platform; create table IF NOT EXISTS wvp_platform ( id serial primary key, enable bool default false, name character varying(255), server_gb_id character varying(50), server_gb_domain character varying(50), server_ip character varying(50), server_port integer, device_gb_id character varying(50), device_ip character varying(50), device_port character varying(50), username character varying(255), password character varying(50), expires character varying(50), keep_timeout character varying(50), transport character varying(50), civil_code character varying(50), manufacturer character varying(255), model character varying(255), address character varying(255), character_set character varying(50), ptz bool default false, rtcp bool default false, status bool default false, catalog_group integer, register_way integer, secrecy integer, create_time character varying(50), update_time character varying(50), as_message_channel bool default false, catalog_with_platform integer default 1, catalog_with_group integer default 1, catalog_with_region integer default 1, auto_push_channel bool default true, send_stream_ip character varying(50), server_id character varying(50), constraint uk_platform_unique_server_gb_id unique (server_gb_id) ); drop table IF EXISTS wvp_platform_channel; create table IF NOT EXISTS wvp_platform_channel ( id serial primary key, platform_id integer, device_channel_id integer, custom_device_id character varying(50), custom_name character varying(255), custom_manufacturer character varying(50), custom_model character varying(50), custom_owner character varying(50), custom_civil_code character varying(50), custom_block character varying(50), custom_address character varying(50), custom_parental integer, custom_parent_id character varying(50), custom_safety_way integer, custom_register_way integer, custom_cert_num character varying(50), custom_certifiable integer, custom_err_code integer, custom_end_time character varying(50), custom_secrecy integer, custom_ip_address character varying(50), custom_port integer, custom_password character varying(255), custom_status character varying(50), custom_longitude double precision, custom_latitude double precision, custom_ptz_type integer, custom_position_type integer, custom_room_type integer, custom_use_type integer, custom_supply_light_type integer, custom_direction_type integer, custom_resolution character varying(255), custom_business_group_id character varying(255), custom_download_speed character varying(255), custom_svc_space_support_mod integer, custom_svc_time_support_mode integer, constraint uk_platform_gb_channel_platform_id_catalog_id_device_channel_id unique (platform_id, device_channel_id), constraint uk_platform_gb_channel_device_id unique (custom_device_id) ); drop table IF EXISTS wvp_platform_group; create table IF NOT EXISTS wvp_platform_group ( id serial primary key, platform_id integer, group_id integer, constraint uk_wvp_platform_group_platform_id_group_id unique (platform_id, group_id) ); drop table IF EXISTS wvp_platform_region; create table IF NOT EXISTS wvp_platform_region ( id serial primary key, platform_id integer, region_id integer, constraint uk_wvp_platform_region_platform_id_group_id unique (platform_id, region_id) ); drop table IF EXISTS wvp_stream_proxy; create table IF NOT EXISTS wvp_stream_proxy ( id serial primary key, type character varying(50), app character varying(255), stream character varying(255), src_url character varying(255), timeout integer, ffmpeg_cmd_key character varying(255), rtsp_type character varying(50), media_server_id character varying(50), enable_audio bool default false, enable_mp4 bool default false, pulling bool default false, enable bool default false, enable_remove_none_reader bool default false, create_time character varying(50), name character varying(255), update_time character varying(50), stream_key character varying(255), server_id character varying(50), enable_disable_none_reader bool default false, relates_media_server_id character varying(50), constraint uk_stream_proxy_app_stream unique (app, stream) ); drop table IF EXISTS wvp_stream_push; create table IF NOT EXISTS wvp_stream_push ( id serial primary key, app character varying(255), stream character varying(255), create_time character varying(50), media_server_id character varying(50), server_id character varying(50), push_time character varying(50), status bool default false, update_time character varying(50), pushing bool default false, self bool default false, start_offline_push bool default true, constraint uk_stream_push_app_stream unique (app, stream) ); drop table IF EXISTS wvp_cloud_record; create table IF NOT EXISTS wvp_cloud_record ( id serial primary key, app character varying(255), stream character varying(255), call_id character varying(255), start_time bigint, end_time bigint, media_server_id character varying(50), server_id character varying(50), file_name character varying(255), folder character varying(500), file_path character varying(500), collect bool default false, file_size bigint, time_len bigint ); drop table IF EXISTS wvp_user; create table IF NOT EXISTS wvp_user ( id serial primary key, username character varying(255), password character varying(255), role_id integer, create_time character varying(50), update_time character varying(50), push_key character varying(50), constraint uk_user_username unique (username) ); drop table IF EXISTS wvp_user_role; create table IF NOT EXISTS wvp_user_role ( id serial primary key, name character varying(50), authority character varying(50), create_time character varying(50), update_time character varying(50) ); drop table IF EXISTS wvp_user_api_key; create table IF NOT EXISTS wvp_user_api_key ( id serial primary key, user_id bigint, app character varying(255), api_key text, expired_at bigint, remark character varying(255), enable bool default true, create_time character varying(50), update_time character varying(50) ); /*初始数据*/ INSERT INTO wvp_user VALUES (1, 'admin', '21232f297a57a5a743894a0e4a801fc3', 1, '2021-04-13 14:14:57', '2021-04-13 14:14:57', '3e80d1762a324d5b0ff636e0bd16f1e3'); INSERT INTO wvp_user_role VALUES (1, 'admin', '0', '2021-04-13 14:14:57', '2021-04-13 14:14:57'); drop table IF EXISTS wvp_common_group; create table IF NOT EXISTS wvp_common_group ( id serial primary key, device_id varchar(50) NOT NULL, name varchar(255) NOT NULL, parent_id int, parent_device_id varchar(50) DEFAULT NULL, business_group varchar(50) NOT NULL, create_time varchar(50) NOT NULL, update_time varchar(50) NOT NULL, civil_code varchar(50) default null, constraint uk_common_group_device_platform unique (device_id) ); drop table IF EXISTS wvp_common_region; create table IF NOT EXISTS wvp_common_region ( id serial primary key, device_id varchar(50) NOT NULL, name varchar(255) NOT NULL, parent_id int, parent_device_id varchar(50) DEFAULT NULL, create_time varchar(50) NOT NULL, update_time varchar(50) NOT NULL, constraint uk_common_region_device_id unique (device_id) ); drop table IF EXISTS wvp_record_plan; create table IF NOT EXISTS wvp_record_plan ( id serial primary key, snap bool default false, name varchar(255) NOT NULL, create_time character varying(50), update_time character varying(50) ); drop table IF EXISTS wvp_record_plan_item; create table IF NOT EXISTS wvp_record_plan_item ( id serial primary key, start int, stop int, week_day int, plan_id int, create_time character varying(50), update_time character varying(50) ); /* * 20240528 */ DELIMITER // -- 重定义分隔符避免分号冲突 CREATE PROCEDURE `wvp_20240528`() BEGIN IF NOT EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_media_server' and column_name = 'transcode_suffix') THEN ALTER TABLE wvp_media_server ADD transcode_suffix character varying(255); END IF; IF not EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_media_server' and column_name = 'type') THEN alter table wvp_media_server add type character varying(50) default 'zlm'; END IF; IF not EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_media_server' and column_name = 'flv_port') THEN alter table wvp_media_server add flv_port integer; END IF; IF not EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_media_server' and column_name = 'flv_ssl_port') THEN alter table wvp_media_server add flv_ssl_port integer; END IF; IF not EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_media_server' and column_name = 'ws_flv_port') THEN alter table wvp_media_server add ws_flv_port integer; END IF; IF not EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_media_server' and column_name = 'ws_flv_ssl_port') THEN alter table wvp_media_server add ws_flv_ssl_port integer; END IF; END; // call wvp_20240528(); DROP PROCEDURE wvp_20240528; DELIMITER ; create table IF NOT EXISTS wvp_user_api_key ( id serial primary key , user_id bigint, app character varying(255) , api_key text, expired_at bigint, remark character varying(255), enable bool default true, create_time character varying(50), update_time character varying(50) ); /* * 20241222 */ DELIMITER // -- 重定义分隔符避免分号冲突 CREATE PROCEDURE `wvp_20241222`() BEGIN IF EXISTS (SELECT column_name FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_device_channel' and INDEX_NAME = 'uk_wvp_device_channel_unique_device_channel') THEN alter table wvp_device_channel drop index uk_wvp_device_channel_unique_device_channel; END IF; IF EXISTS (SELECT column_name FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_device_channel' and INDEX_NAME = 'uk_wvp_unique_stream_push_id') THEN alter table wvp_device_channel drop index uk_wvp_unique_stream_push_id; END IF; IF EXISTS (SELECT column_name FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_device_channel' and INDEX_NAME = 'uk_wvp_unique_stream_proxy_id') THEN alter table wvp_device_channel drop index uk_wvp_unique_stream_proxy_id; END IF; IF not EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_device_channel' and column_name = 'data_type') THEN alter table wvp_device_channel add data_type integer not null; END IF; IF not EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_device_channel' and column_name = 'data_device_id') THEN alter table wvp_device_channel add data_device_id integer not null; END IF; IF EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_device_channel' and column_name = 'device_db_id') THEN update wvp_device_channel wdc INNER JOIN (SELECT id, device_db_id from wvp_device_channel where device_db_id is not null ) ct on ct.id = wdc.id set wdc.data_type = 1, wdc.data_device_id = ct.device_db_id where wdc.device_db_id is not null; alter table wvp_device_channel drop device_db_id; END IF; IF EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_device_channel' and column_name = 'stream_push_id') THEN update wvp_device_channel wdc INNER JOIN (SELECT id, stream_push_id from wvp_device_channel where stream_push_id is not null ) ct on ct.id = wdc.id set wdc.data_type = 2, wdc.data_device_id = ct.stream_push_id where wdc.stream_push_id is not null; alter table wvp_device_channel drop stream_push_id; END IF; IF EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_device_channel' and column_name = 'stream_proxy_id') THEN update wvp_device_channel wdc INNER JOIN (SELECT id, stream_proxy_id from wvp_device_channel where stream_proxy_id is not null ) ct on ct.id = wdc.id set wdc.data_type = 3, wdc.data_device_id = ct.stream_proxy_id where wdc.stream_proxy_id is not null; alter table wvp_device_channel drop stream_proxy_id; END IF; END; // call wvp_20241222(); DROP PROCEDURE wvp_20241222; DELIMITER ; /* * 20241231 */ DELIMITER // CREATE PROCEDURE `wvp_20241231`() BEGIN IF not EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_stream_proxy' and column_name = 'relates_media_server_id') THEN alter table wvp_stream_proxy add relates_media_server_id character varying(50); END IF; END; // call wvp_20241231(); DROP PROCEDURE wvp_20241231; DELIMITER ; /* * 20250111 */ DELIMITER // -- 重定义分隔符避免分号冲突 CREATE PROCEDURE `wvp_20250111`() BEGIN IF EXISTS (SELECT column_name FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_cloud_record' and INDEX_NAME = 'uk_stream_push_app_stream_path') THEN alter table wvp_cloud_record drop index uk_stream_push_app_stream_path ; END IF; IF EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_cloud_record' and column_name = 'folder') THEN alter table wvp_cloud_record modify folder varchar(500) null; END IF; IF EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_cloud_record' and column_name = 'file_path') THEN alter table wvp_cloud_record modify file_path varchar(500) null; END IF; END; // call wvp_20250111(); DROP PROCEDURE wvp_20250111; DELIMITER ; /* * 20250211 */ DELIMITER // -- 重定义分隔符避免分号冲突 CREATE PROCEDURE `wvp_20250211`() BEGIN IF EXISTS (SELECT column_name FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_device' and column_name = 'keepalive_interval_time') THEN alter table wvp_device change keepalive_interval_time heart_beat_interval integer after as_message_channel; END IF; IF not EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_device' and column_name = 'heart_beat_count') THEN alter table wvp_device add heart_beat_count integer; END IF; IF not EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_device' and column_name = 'position_capability') THEN alter table wvp_device add position_capability integer; END IF; END; // call wvp_20250211(); DROP PROCEDURE wvp_20250211; DELIMITER ; /** * 20250312 */ DELIMITER // -- 重定义分隔符避免分号冲突 CREATE PROCEDURE `wvp_20250312`() BEGIN DECLARE serverId VARCHAR(32) DEFAULT '你的服务ID'; IF not EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_device' and column_name = 'server_id') THEN alter table wvp_device add server_id character varying(50); update wvp_device set server_id = serverId; END IF; IF not EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_media_server' and column_name = 'server_id') THEN alter table wvp_media_server add server_id character varying(50); update wvp_media_server set server_id = serverId; END IF; IF not EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_stream_proxy' and column_name = 'server_id') THEN alter table wvp_stream_proxy add server_id character varying(50); update wvp_stream_proxy set server_id = serverId; END IF; IF not EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_cloud_record' and column_name = 'server_id') THEN alter table wvp_cloud_record add server_id character varying(50); update wvp_cloud_record set server_id = serverId; END IF; IF not EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_platform' and column_name = 'server_id') THEN alter table wvp_platform add server_id character varying(50); END IF; END; // call wvp_20250312(); DROP PROCEDURE wvp_20250312; DELIMITER ; /* * 20250319 */ DELIMITER // -- 重定义分隔符避免分号冲突 CREATE PROCEDURE `wvp_20250319`() BEGIN IF NOT EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_device_channel' and column_name = 'gps_speed') THEN alter table wvp_device_channel add gps_speed double precision; END IF; IF NOT EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_device_channel' and column_name = 'gps_altitude') THEN alter table wvp_device_channel add gps_altitude double precision; END IF; IF NOT EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_device_channel' and column_name = 'gps_direction') THEN alter table wvp_device_channel add gps_direction double precision; END IF; END; // call wvp_20250319(); DROP PROCEDURE wvp_20250319; DELIMITER ; /* * 20250402 */ DELIMITER // -- 重定义分隔符避免分号冲突 CREATE PROCEDURE `wvp_20250402`() BEGIN IF NOT EXISTS (SELECT column_name FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_device_channel' and INDEX_NAME = 'data_type') THEN create index data_type on wvp_device_channel (data_type); END IF; IF NOT EXISTS (SELECT column_name FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_device_channel' and INDEX_NAME = 'data_device_id') THEN create index data_device_id on wvp_device_channel (data_device_id); END IF; END; // call wvp_20250402(); DROP PROCEDURE wvp_20250402; DELIMITER ; ================================================ FILE: docker/nginx/Dockerfile ================================================ FROM ubuntu:24.04 AS builder RUN apt-get update && \ apt-get install -y nodejs npm && \ rm -rf /var/lib/apt/lists/* COPY ./web /build WORKDIR /build RUN npm --registry=https://registry.npmmirror.com install RUN npm run build:prod WORKDIR /src/main/resources RUN ls WORKDIR /src/main/resources/static RUN ls FROM nginx:alpine ARG TZ=Asia/Shanghai COPY --from=builder /src/main/resources/static /opt/dist CMD ["nginx","-g","daemon off;"] ================================================ FILE: docker/nginx/build.sh ================================================ #/bin/bash set -e version=2.7.3 rm ./dist/static/js/config.js cp ./config.js ./dist/static/js/ docker build -t polaris-nginx:${version} . docker tag polaris-nginx:${version} polaris-tian-docker.pkg.coding.net/qt/polaris/polaris-nginx:${version} docker tag polaris-nginx:${version} polaris-tian-docker.pkg.coding.net/qt/polaris/polaris-nginx:latest ================================================ FILE: docker/nginx/config.js ================================================ window.baseUrl = "http://10.10.1.124:18978" // map组件全局参数, 注释此内容可以关闭地图功能 window.mapParam = { // 开启/关闭地图功能 enable: true, // 坐标系 GCJ-02 WGS-84, coordinateSystem: "GCJ-02", // 地图瓦片地址 tilesUrl: "http://webrd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scale=1&style=8", // 瓦片大小 tileSize: 256, // 默认层级 zoom:10, // 默认地图中心点 center:[116.41020, 39.915119], // 地图最大层级 maxZoom:18, // 地图最小层级 minZoom: 3 } ================================================ FILE: docker/nginx/templates/nginx.conf.template ================================================ server { listen 8080; server_name localhost; location / { root /opt/dist; index index.html index.htm; } location /record_proxy/{ proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://polaris-wvp:18978/; } location /api/ { proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://polaris-wvp:18978; # 从环境变量获取原始主机地址(x.x.x.x) set $original_host ${Stream_IP}; # 执行字符串替换 # 将媒体资源文件替换为Nginx输出的相对地址 sub_filter "http://$original_host/index/api/downloadFile" "mediaserver/api/downloadFile"; sub_filter "http://$original_host:80/index/api/downloadFile" "mediaserver/api/downloadFile"; sub_filter "https://$original_host/index/api/downloadFile" "mediaserver/api/downloadFile"; sub_filter "https://$original_host:443/index/api/downloadFile" "mediaserver/api/downloadFile"; sub_filter "http://$original_host/mp4_record" "mp4_record"; sub_filter "http://$original_host:80/mp4_record" "mp4_record"; sub_filter "https://$original_host/mp4_record" "mp4_record"; sub_filter "https://$original_host:443/mp4_record" "mp4_record"; # 设置为off表示替换所有匹配项,而不仅仅是第一个 sub_filter_once off; # 确保响应被正确处理 sub_filter_types application/json; # 只对JSON响应进行处理 } # 将mediaserver/record转发到目标地址 location /mediaserver/api/downloadFile { # 目标服务器地址 proxy_pass http://polaris-media:80/index/api/downloadFile; # 以下是常用的反向代理设置 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # 超时设置,根据需要调整 proxy_connect_timeout 300s; proxy_send_timeout 300s; proxy_read_timeout 300s; } # 仅允许代理/rtp/开头的路径 location ^~ /rtp/ { # 代理到ZLMediakit服务 proxy_pass http://polaris-media:80; # 基础HTTP代理配置 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # WebSocket支持配置 proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; # 超时设置,根据实际需求调整 proxy_connect_timeout 60s; proxy_read_timeout 3600s; proxy_send_timeout 60s; } # 仅允许代理/rtp/开头的路径 location ^~ /mp4_record/ { # 代理到ZLMediakit服务 proxy_pass http://polaris-media:80; # 基础HTTP代理配置 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # WebSocket支持配置 proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; # 超时设置,根据实际需求调整 proxy_connect_timeout 60s; proxy_read_timeout 3600s; proxy_send_timeout 60s; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } ================================================ FILE: docker/push.sh ================================================ #/bin/bash set -e version=2.7.3 docker push polaris-tian-docker.pkg.coding.net/qt/polaris/ylcx-media:latest docker push polaris-tian-docker.pkg.coding.net/qt/polaris/ylcx-mysql:latest docker push polaris-tian-docker.pkg.coding.net/qt/polaris/ylcx-redis:latest docker push polaris-tian-docker.pkg.coding.net/qt/polaris/ylcx-wvp:latest docker push polaris-tian-docker.pkg.coding.net/qt/polaris/ylcx-nginx:latest docker push polaris-tian-docker.pkg.coding.net/qt/polaris/ylcx-media:${version} docker push polaris-tian-docker.pkg.coding.net/qt/polaris/ylcx-mysql:${version} docker push polaris-tian-docker.pkg.coding.net/qt/polaris/ylcx-redis:${version} docker push polaris-tian-docker.pkg.coding.net/qt/polaris/ylcx-wvp:${version} docker push polaris-tian-docker.pkg.coding.net/qt/polaris/ylcx-nginx:${version} ================================================ FILE: docker/redis/Dockerfile ================================================ FROM redis RUN mkdir -p /opt/polaris/redis WORKDIR /opt/polaris/redis COPY ./conf/redis.conf /opt/polaris/redis/redis.conf ================================================ FILE: docker/redis/build.sh ================================================ #/bin/bash set -e version=2.7.3 docker build -t polaris-redis:${version} . docker tag polaris-redis:${version} polaris-tian-docker.pkg.coding.net/qt/polaris/polaris-redis:${version} docker tag polaris-redis:${version} polaris-tian-docker.pkg.coding.net/qt/polaris/polaris-redis:latest ================================================ FILE: docker/redis/conf/redis.conf ================================================ #requirepass root bind 0.0.0.0 ================================================ FILE: docker/wvp/Dockerfile ================================================ FROM ringcentral/jdk:11 AS builder EXPOSE 18978/tcp EXPOSE 8116/tcp EXPOSE 8116/udp EXPOSE 8080/tcp #RUN apt-get update && \ #DEBIAN_FRONTEND="noninteractive" \ #apt-get install -y --no-install-recommends \ #wget \ #cmake \ #maven \ #git \ #ca-certificates \ #tzdata \ #curl \ #libpcre3 \ #libpcre3-dev \ #zlib1g-dev \ #openssl \ #libssl-dev \ #gdb && \ #apt-get autoremove -y && \ #apt-get clean -y && \ #rm -rf /var/lib/apt/lists/* ## install jdk1.8 #RUN mkdir -p /opt/download #WORKDIR /opt/download #RUN if [ "$Platfrom" = "arm64" ]; \ #then \ #wget https://polaris-tian-generic.pkg.coding.net/qt/autopliot/jdk-8u411-linux-aarch64.tar.gz?version=latest --no-check-certificate -O jdk-8.tar.gz && \ #tar -zxvf /opt/download/jdk-8.tar.gz -C /usr/local/ --transform 's/jdk1.8.0_411/java/' && \ #rm /opt/download/jdk-8.tar.gz; \ #else \ #wget https://polaris-tian-generic.pkg.coding.net/qt/autopliot/jdk-8u202-linux-x64.tar.gz?version=latest --no-check-certificate -O jdk-8.tar.gz && \ #tar -zxvf /opt/download/jdk-8.tar.gz -C /usr/local/ --transform 's/jdk1.8.0_202/java/' && \ #rm /opt/download/jdk-8.tar.gz; \ #fi #ENV JAVA_HOME /usr/local/java/ #ENV JRE_HOME ${JAVA_HOME}/jre #ENV CLASSPATH .:${JAVA_HOME}/lib:${JRE_HOME}/lib #ENV PATH ${JAVA_HOME}/bin:$PATH RUN java -version && javac -version #RUN sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list.d/debian.sources && \ RUN apt-get update && \ apt-get install -y maven && \ rm -rf /var/lib/apt/lists/* COPY . /build WORKDIR /build RUN ls && mvn clean package -Dmaven.test.skip=true WORKDIR /build/target #ȷļһ RUN mv wvp-pro-*.jar wvp.jar FROM ringcentral/jdk:11 RUN mkdir -p /opt/wvp WORKDIR /opt/wvp COPY --from=builder /build/target /opt/wvp COPY ./docker/wvp/wvp /opt/wvp ENTRYPOINT ["java", "-Xms512m", "-Xmx1024m", "-XX:+HeapDumpOnOutOfMemoryError", "-XX:HeapDumpPath=/opt/ylcx/", "-jar", "wvp.jar", "--spring.config.location=/opt/ylcx/wvp/application.yml"] #RUN mkdir -p /opt/wvp #WORKDIR /opt/wvp #COPY ./wvp /opt/wvp # #WORKDIR /home #RUN cd /home && \ #git clone https://gitee.com/pan648540858/wvp-GB28181-pro.git # #RUN cd /home/wvp-GB28181-pro && \ #mvn clean package -Dmaven.test.skip=true && \ #cp /home/wvp-GB28181-pro/target/*.jar /opt/wvp/wvp.jar # #WORKDIR /opt/wvp #ENTRYPOINT ["java", "-Xms512m", "-Xmx1024m", "-XX:+HeapDumpOnOutOfMemoryError", "-XX:HeapDumpPath=/opt/ylcx/", "-jar", "wvp.jar", "--spring.config.location=/opt/ylcx/wvp/application.yml"] ================================================ FILE: docker/wvp/build.sh ================================================ #/bin/bash set -e version=2.7.3 docker build -t polaris-wvp:${version} . docker tag polaris-wvp:${version} polaris-tian-docker.pkg.coding.net/qt/polaris/polaris-wvp:${version} docker tag polaris-wvp:${version} polaris-tian-docker.pkg.coding.net/qt/polaris/polaris-wvp:latest ================================================ FILE: docker/wvp/wvp/application-base.yml ================================================ spring: # 设置接口超时时间 mvc: async: request-timeout: 20000 thymeleaf: cache: false # [可选]上传文件大小限制 servlet: multipart: max-file-size: 10MB max-request-size: 100MB data: # REDIS数据库配置 redis: # [必须修改] Redis服务器IP, REDIS安装在本机的,使用127.0.0.1 host: 127.0.0.1 # [必须修改] 端口号 port: 6379 # [可选] 数据库 DB database: 1 # [可选] 访问密码,若你的redis服务器没有设置密码,就不需要用密码去连接 password: # [可选] 超时时间 timeout: 30000 # mysql数据源 datasource: dynamic: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/wvp?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true&serverTimezone=PRC&useSSL=false&allowMultiQueries=true username: root password: root #[可选] 监听的HTTP端口, 网页和接口调用都是这个端口 server: port: 18978 ssl: # [可选] 是否开启HTTPS访问 enabled: false # 作为28181服务器的配置 sip: # [必须修改] 本机的IP ip: 127.0.0.1 # [可选] port: 8116 # [可选] domain: 3402000000 # [可选] id: 34020000002000000001 password: alarm: true # 默认服务器配置 media: id: polaris # [必须修改]内网IP ip: 127.0.0.1 http-port: 6080 # [可选] 返回流地址时的ip,置空使用 media.ip stream-ip: 127.0.0.1 # [可选] wvp在国标信令中使用的ip,此ip为摄像机可以访问到的ip, 置空使用 media.ip sdp-ip: 127.0.0.1 # [可选] Hook IP, 默认使用sip.ip hook-ip: 127.0.0.1 # [可选] sslport http-ssl-port: 4443 rtp-proxy-port: 10000 rtmp-port: 10935 rtmp-ssl-port: 41935 rtsp-port: 5540 rtsp-ssl-port: 45540 # [可选] secret: su6TiedN2rVAmBbIDX0aa0QTiBJLBdcf # 启用多端口模式, 多端口模式使用端口区分每路流,兼容性更好。 单端口使用流的ssrc区分, 点播超时建议使用多端口测试 rtp: # [可选] 是否启用多端口模式, 开启后会在portRange范围内选择端口用于媒体流传输 enable: false # [可选] port-range: 30000,30500 # [可选] send-port-range: 50502,50506 record-path: /opt/media/record record-day: 7 record-assist-port: 0 user-settings: auto-apply-play: true play-timeout: 30000 wait-track: false record-push-live: false record-sip: false stream-on-demand: true interface-authentication: false broadcast-for-platform: TCP-PASSIVE push-stream-after-ack: true send-to-platforms-when-id-lost: true interface-authentication-excludes: - /api/** push-authority: false allowed-origins: - http://localhost:8080 - http://127.0.0.1:8080 - http://0.0.0.0:8080 logging: config: classpath:logback-spring.xml ================================================ FILE: docker/wvp/wvp/application-docker.yml ================================================ spring: cache: type: redis thymeleaf: cache: false # 设置接口超时时间 mvc: async: request-timeout: 20000 # [可选]上传文件大小限制 servlet: multipart: max-file-size: 10MB max-request-size: 100MB data: # REDIS数据库配置 redis: # [必须修改] Redis服务器IP, REDIS安装在本机的,使用127.0.0.1 host: ${REDIS_HOST:127.0.0.1} # [必须修改] 端口号 port: ${REDIS_PORT:6379} # [可选] 数据库 DB database: 1 # [可选] 访问密码,若你的redis服务器没有设置密码,就不需要用密码去连接 password: # [可选] 超时时间 timeout: 10000 ## [可选] 一个pool最多可分配多少个jedis实例 #poolMaxTotal: 1000 ## [可选] 一个pool最多有多少个状态为idle(空闲)的jedis实例 #poolMaxIdle: 500 ## [可选] 最大的等待时间(秒) #poolMaxWait: 5 # [必选] jdbc数据库配置 datasource: # mysql数据源 type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://${DATABASE_HOST:127.0.0.1}:${DATABASE_PORT:3306}/wvp?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true&serverTimezone=PRC&useSSL=false&allowMultiQueries=true&allowPublicKeyRetrieval=true username: ${DATABASE_USER:root} password: ${DATABASE_PASSWORD:root} #[可选] 监听的HTTP端口, 网页和接口调用都是这个端口 server: port: 18978 ssl: # [可选] 是否开启HTTPS访问 # docker里运行,内部不需要HTTPS enabled: false # 作为28181服务器的配置 sip: # [必须修改] 本机的IP,对应你的网卡,监听什么ip就是使用什么网卡, # 如果要监听多张网卡,可以使用逗号分隔多个IP, 例如: 192.168.1.4,10.0.0.4 # 如果不明白,就使用0.0.0.0,大部分情况都是可以的 # 请不要使用127.0.0.1,任何包括localhost在内的域名都是不可以的。 ip: 0.0.0.0 # [可选] 没有任何业务需求,仅仅是在前端展示的时候用 show-ip: ${SIP_ShowIP} # [可选] port: ${SIP_Port:8116} # 根据国标6.1.2中规定,domain宜采用ID统一编码的前十位编码。国标附录D中定义前8位为中心编码(由省级、市级、区级、基层编号组成,参照GB/T 2260-2007) # 后两位为行业编码,定义参照附录D.3 # 3701020049标识山东济南历下区 信息行业接入 # [可选] domain: ${SIP_Domain:3402000000} # [可选] id: ${SIP_Id:34020000002000000001} # [可选] 默认设备认证密码,后续扩展使用设备单独密码, 移除密码将不进行校验 password: ${SIP_Password} # [可选] 国标级联注册失败,再次发起注册的时间间隔。 默认60秒 register-time-interval: 60 # [可选] 云台控制速度 ptz-speed: 50 # TODO [可选] 收到心跳后自动上线, 重启服务后会将所有设备置为离线,默认false,等待注册后上线。设置为true则收到心跳设置为上线。 # keepalliveToOnline: false # 是否存储alarm信息 alarm: true # 命令发送等待回复的超时时间, 单位:毫秒 timeout: 1000 # 默认服务器配置 media: id: polaris # [必须修改] ZLM 内网IP与端口 ip: ${ZLM_HOST:127.0.0.1} http-port: 80 # [可选] 返回流地址时的ip,置空使用 media.ip stream-ip: ${Stream_IP} # [可选] wvp在国标信令中使用的ip,此ip为摄像机可以访问到的ip, 置空使用 media.ip sdp-ip: ${SDP_IP} # [可选] zlm服务器访问WVP所使用的IP, 默认使用127.0.0.1,zlm和wvp没有部署在同一台服务器时必须配置 hook-ip: ${ZLM_HOOK_HOST} # [可选] sslport http-ssl-port: 0 flv-port: ${MediaHttp:} flv-ssl-port: ${MediaHttps:} ws-flv-port: ${MediaHttp:} ws-flv-ssl-port: ${MediaHttps:} rtp-proxy-port: ${MediaRtp:} rtmp-port: ${MediaRtmp:} rtmp-ssl-port: 0 rtsp-port: ${MediaRtsp:} rtsp-ssl-port: 0 # [可选] 是否自动配置ZLM, 如果希望手动配置ZLM, 可以设为false, 不建议新接触的用户修改 auto-config: true # [可选] secret: ${ZLM_SERCERT} # 启用多端口模式, 多端口模式使用端口区分每路流,兼容性更好。 单端口使用流的ssrc区分, 点播超时建议使用多端口测试 rtp: # [可选] 是否启用多端口模式, 开启后会在portRange范围内选择端口用于媒体流传输 enable: false # [可选] port-range: 30000,30500 # [可选] send-port-range: 50502,50506 record-path: /opt/media/bin/www/record/ record-day: 7 record-assist-port: 0 user-settings: auto-apply-play: true play-timeout: 30000 wait-track: false record-push-live: ${RecordPushLive:false} record-sip: ${RecordSip:false} stream-on-demand: true interface-authentication: true broadcast-for-platform: TCP-PASSIVE push-stream-after-ack: true send-to-platforms-when-id-lost: true interface-authentication-excludes: # - /api/** push-authority: true # allowed-origins: # - http://localhost:8080 # - http://127.0.0.1:8080 # - http://0.0.0.0:8080 # - ${NGINX_HOST} jwkFile: classpath:xxxxxxxxxxx.json logging: config: classpath:logback-spring.xml ================================================ FILE: docker/wvp/wvp/application.yml ================================================ spring: application: name: wvp profiles: active: docker ================================================ FILE: install.sh ================================================ #! /bin/bash WORD_DIR=$(cd $(dirname $0); pwd) SERVICE_NAME="wvp" # 检查是否为 root 用户 if [ "$(id -u)" -ne 0 ]; then echo "提示: 建议使用 root 用户执行此脚本,否则可能权限不足!" read -p "继续?(y/n) " -n 1 -r if [[ ! $REPLY =~ ^[Yy]$ ]]; then exit 1 fi echo fi # 当前目录直接搜索(不含子目录) bugfix: 这里使用了需要bash才可以支持的内容 jar_files=(*.jar) if [ ${#jar_files[@]} -eq 0 ]; then echo "当前目录无 JAR 文件!" exit 1 fi # 遍历结果 for jar in "${jar_files[@]}"; do echo "找到 JAR 文件: $jar" done # 写文件 # 生成 Systemd 服务文件内容 SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service" cat << EOF | sudo tee "$SERVICE_FILE" > /dev/null [Unit] Description=${SERVICE_NAME} After=syslog.target [Service] User=$USER WorkingDirectory=${WORD_DIR} ExecStart=java -jar ${jar_files} SuccessExitStatus=143 Restart=on-failure RestartSec=10s Environment=SPRING_PROFILES_ACTIVE=prod [Install] WantedBy=multi-user.target EOF # 重载 Systemd 并启动服务 sudo systemctl daemon-reload sudo systemctl enable "$SERVICE_NAME" sudo systemctl start "$SERVICE_NAME" # 验证服务状态 echo "服务已安装!执行以下命令查看状态:" echo "sudo systemctl status $SERVICE_NAME" ================================================ FILE: pom.xml ================================================ 4.0.0 org.springframework.boot spring-boot-starter-parent 3.4.4 com.genersoft wvp-pro 2.7.4 web video platform 国标28181视频平台 ${project.packaging} nexus-aliyun Nexus aliyun https://maven.aliyun.com/repository/public default false true ECC https://maven.ecc.no/releases nexus-aliyun Nexus aliyun https://maven.aliyun.com/repository/public false true UTF-8 MMddHHmm ${project.build.directory}/generated-snippets ${project.basedir}/docs/asciidoc ${project.build.directory}/asciidoc ${project.build.directory}/asciidoc/html ${project.build.directory}/asciidoc/pdf 21 21 jar true jar war war org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-jetty javax.servlet javax.servlet-api 3.1.0 provided org.springframework.boot spring-boot-starter-data-redis org.springframework.boot spring-boot-starter-cache org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-websocket org.springframework.boot spring-boot-starter-aop org.springframework.session spring-session-core org.springframework.boot spring-boot-configuration-processor true org.mybatis.spring.boot mybatis-spring-boot-starter 3.0.4 com.zaxxer HikariCP org.springframework.boot spring-boot-starter-security org.springframework.boot spring-boot-starter-jdbc com.h2database h2 2.3.232 com.mysql mysql-connector-j 8.2.0 org.postgresql postgresql 42.5.1 cn.com.kingbase kingbase8 8.6.0 com.github.pagehelper pagehelper-spring-boot-starter 2.1.1 org.springdoc springdoc-openapi-starter-webmvc-ui 2.8.5 org.springdoc springdoc-openapi-starter-webmvc-api 2.8.5 com.github.xiaoymin knife4j-openapi3-jakarta-spring-boot-starter 4.5.0 javax.sip jain-sip-ri 1.3.0-91 org.slf4j log4j-over-slf4j 2.0.17 org.dom4j dom4j 2.1.4 com.alibaba.fastjson2 fastjson2 2.0.57 com.alibaba.fastjson2 fastjson2-extension 2.0.57 com.alibaba.fastjson2 fastjson2-extension-spring5 2.0.57 com.squareup.okhttp3 okhttp 4.12.0 com.squareup.okhttp3 logging-interceptor 4.12.0 io.github.rburgst okhttp-digest 3.1.1 org.bitbucket.b_c jose4j 0.9.6 org.apache.httpcomponents httpclient 4.5.14 com.alibaba easyexcel 4.0.3 org.apache.commons commons-compress org.apache.commons commons-compress 1.27.1 com.github.oshi oshi-core 6.6.5 com.google.guava guava 33.4.8-jre org.apache.ftpserver ftpserver-core 1.2.1 org.apache.ftpserver ftplet-api 1.2.1 org.projectlombok lombok 1.18.38 provided io.github.sevdokimov.logviewer log-viewer-spring-boot 1.0.10 cn.hutool hutool-all 5.8.38 org.bouncycastle bcpkix-jdk18on 1.78.1 no.ecc.vectortile java-vector-tile 1.4.1 org.locationtech.jts jts-core 1.18.2 org.apache.commons commons-pool2 org.springframework.boot spring-boot-starter-test test ${project.artifactId}-${project.version}-${maven.build.timestamp} org.springframework.boot spring-boot-maven-plugin 3.4.10 true true org.apache.maven.plugins maven-compiler-plugin 3.14.0 21 21 org.projectlombok lombok 1.18.38 io.github.git-commit-id git-commit-id-maven-plugin 8.0.0 get-the-git-infos revision initialize true ${project.build.outputDirectory}/git.properties full org.apache.maven.plugins maven-surefire-plugin 3.2.5 true org.apache.maven.plugins maven-jar-plugin 3.4.2 **/配置详情.yml **/application.yml **/application-*.yml **/local.jks **/install.sh maven-resources-plugin copy-resources package copy-resources src/main/resources application.yml application-*.yml install.sh ${project.build.directory} src/main/resources src/main/java **/*.xml ================================================ FILE: run.sh ================================================ #!/bin/bash # JDK路径 export JAVA_HOME=/usr/local/java/jdk1.8.0_202 export JRE_HOME=${JAVA_HOME}/jre export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib export PATH=${JAVA_HOME}/bin:$PATH RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[0;33m' BLUE='\033[0;34m' NC='\033[0m' function log() { message="[Polaris Log]: $1 " case "$1" in *"失败"* | *"错误"* | *"请使用 root 或 sudo 权限运行此脚本"*) echo -e "${RED}${message}${NC}" 2>&1 | tee -a ;; *"成功"*) echo -e "${GREEN}${message}${NC}" 2>&1 | tee -a ;; *"忽略"* | *"跳过"*) echo -e "${YELLOW}${message}${NC}" 2>&1 | tee -a ;; *) echo -e "${BLUE}${message}${NC}" 2>&1 | tee -a ;; esac } echo cat < /dev/null 2>&1 & fi log "流媒体服务开启成功" } function stop() { log "======================= 停止流媒体服务 =======================" PID="" query() { PID=$(ps -ef | grep java | grep $AppName | grep -v grep | awk '{print $2}') } query if [ x"$PID" != x"" ]; then log "进程PID: $PID" kill -TERM $PID log "$AppName (pid:$PID) exiting..." while [ x"$PID" != x"" ]; do sleep 1 query done log "成功:$AppName exited." else log "忽略:进程不存在" fi } function status() { log "======================= 运行状态 =======================" log "" PID=`ps -ef |grep java|grep $AppName|grep -v grep|awk '{print $2}'` if [ $PID != 0 ]; then log "进程PID: $PID" log "$AppName is running..." else log "$AppName is not running..." fi log "" log "========================================================" } function restart() { stop sleep 3 start } case $1 in start) start ;; stop) stop ;; restart) restart ;; status) status ;; *) ;; esac ================================================ FILE: src/main/java/com/genersoft/iot/vmp/VManageBootstrap.java ================================================ package com.genersoft.iot.vmp; import com.genersoft.iot.vmp.jt1078.util.ClassUtil; import com.genersoft.iot.vmp.utils.GitUtil; import com.genersoft.iot.vmp.utils.SpringBeanFactory; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.web.servlet.ServletComponentScan; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.scheduling.annotation.EnableScheduling; import jakarta.servlet.ServletContext; import jakarta.servlet.ServletException; import jakarta.servlet.SessionCookieConfig; import jakarta.servlet.SessionTrackingMode; import java.util.Collections; /** * 启动类 */ @ServletComponentScan("com.genersoft.iot.vmp.conf") @SpringBootApplication @EnableScheduling @EnableCaching @Slf4j public class VManageBootstrap extends SpringBootServletInitializer { private static String[] args; private static ConfigurableApplicationContext context; public static void main(String[] args) { VManageBootstrap.args = args; VManageBootstrap.context = SpringApplication.run(VManageBootstrap.class, args); ClassUtil.context = VManageBootstrap.context; GitUtil gitUtil = SpringBeanFactory.getBean("gitUtil"); if (gitUtil == null) { log.info("获取版本信息失败"); }else { log.info("构建版本: {}", gitUtil.getBuildVersion()); log.info("构建时间: {}", gitUtil.getBuildDate()); log.info("GIT信息: 分支: {}, ID: {}, 时间: {}", gitUtil.getBranch(), gitUtil.getCommitIdShort(), gitUtil.getCommitTime()); } } // 项目重启 public static void restart() { context.close(); VManageBootstrap.context = SpringApplication.run(VManageBootstrap.class, args); } @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { return application.sources(VManageBootstrap.class); } @Override public void onStartup(ServletContext servletContext) throws ServletException { super.onStartup(servletContext); servletContext.setSessionTrackingModes( Collections.singleton(SessionTrackingMode.COOKIE) ); SessionCookieConfig sessionCookieConfig = servletContext.getSessionCookieConfig(); sessionCookieConfig.setHttpOnly(true); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/common/CivilCodePo.java ================================================ package com.genersoft.iot.vmp.common; import org.springframework.util.ObjectUtils; public class CivilCodePo { private String code; private String name; private String parentCode; public static CivilCodePo getInstance(String[] infoArray) { CivilCodePo civilCodePo = new CivilCodePo(); civilCodePo.setCode(infoArray[0]); civilCodePo.setName(infoArray[1]); if (!ObjectUtils.isEmpty(infoArray[2])) { civilCodePo.setParentCode(infoArray[2]); } return civilCodePo; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getParentCode() { return parentCode; } public void setParentCode(String parentCode) { this.parentCode = parentCode; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/common/CommonCallback.java ================================================ package com.genersoft.iot.vmp.common; public interface CommonCallback{ public void run(T t); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/common/DeviceStatusCallback.java ================================================ package com.genersoft.iot.vmp.common; import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo; public interface DeviceStatusCallback { public void run(String deviceId, SipTransactionInfo transactionInfo); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/common/InviteInfo.java ================================================ package com.genersoft.iot.vmp.common; import com.genersoft.iot.vmp.service.bean.SSRCInfo; import lombok.Data; /** * 记录每次发送invite消息的状态 */ @Data public class InviteInfo { private String deviceId; private Integer channelId; private String stream; private SSRCInfo ssrcInfo; private String receiveIp; private Integer receivePort; private String streamMode; private InviteSessionType type; private InviteSessionStatus status; private StreamInfo streamInfo; private String mediaServerId; private Long expirationTime; private Long createTime; private Boolean record; private String startTime; private String endTime; public static InviteInfo getInviteInfo(String deviceId, Integer channelId, String stream, SSRCInfo ssrcInfo, String mediaServerId, String receiveIp, Integer receivePort, String streamMode, InviteSessionType type, InviteSessionStatus status, Boolean record) { InviteInfo inviteInfo = new InviteInfo(); inviteInfo.setDeviceId(deviceId); inviteInfo.setChannelId(channelId); inviteInfo.setStream(stream); inviteInfo.setSsrcInfo(ssrcInfo); inviteInfo.setReceiveIp(receiveIp); inviteInfo.setReceivePort(receivePort); inviteInfo.setStreamMode(streamMode); inviteInfo.setType(type); inviteInfo.setStatus(status); inviteInfo.setMediaServerId(mediaServerId); inviteInfo.setRecord(record); return inviteInfo; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/common/InviteSessionStatus.java ================================================ package com.genersoft.iot.vmp.common; /** * 标识invite消息发出后的各个状态, * 收到ok钱停止invite发送cancel, * 收到200ok后发送BYE停止invite */ public enum InviteSessionStatus { ready, ok, } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/common/InviteSessionType.java ================================================ package com.genersoft.iot.vmp.common; public enum InviteSessionType { PLAY, PLAYBACK, DOWNLOAD, BROADCAST, TALK } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/common/RemoteAddressInfo.java ================================================ package com.genersoft.iot.vmp.common; public class RemoteAddressInfo { private String ip; private int port; public RemoteAddressInfo(String ip, int port) { this.ip = ip; this.port = port; } public String getIp() { return ip; } public void setIp(String ip) { this.ip = ip; } public int getPort() { return port; } public void setPort(int port) { this.port = port; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/common/ServerInfo.java ================================================ package com.genersoft.iot.vmp.common; import com.genersoft.iot.vmp.utils.DateUtil; import lombok.Data; @Data public class ServerInfo { private String ip; private int port; private String createTime; public static ServerInfo create(String ip, int port) { ServerInfo serverInfo = new ServerInfo(); serverInfo.setIp(ip); serverInfo.setPort(port); serverInfo.setCreateTime(DateUtil.getNow()); return serverInfo; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/common/StatisticsInfo.java ================================================ package com.genersoft.iot.vmp.common; import lombok.Data; /** * 统计信息 */ @Data public class StatisticsInfo { private long id; /** * ID */ private String deviceId; /** * 分支 */ private String branch; /** * git提交版本ID */ private String gitCommitId; /** * git地址 */ private String gitUrl; /** * 构建版本 */ private String version; /** * 操作系统名称 */ private String osName; /** * 是否是docker环境 */ private Boolean docker; /** * 架构 */ private String arch; /** * jdk版本 */ private String jdkVersion; /** * redis版本 */ private String redisVersion; /** * sql数据库版本 */ private String sqlVersion; /** * sql数据库类型, mysql/postgresql/金仓等 */ private String sqlType; /** * 创建时间 */ private String time; @Override public String toString() { return "StatisticsInfo{" + "id=" + id + ", deviceId='" + deviceId + '\'' + ", branch='" + branch + '\'' + ", gitCommitId='" + gitCommitId + '\'' + ", gitUrl='" + gitUrl + '\'' + ", version='" + version + '\'' + ", osName='" + osName + '\'' + ", docker=" + docker + ", arch='" + arch + '\'' + ", jdkVersion='" + jdkVersion + '\'' + ", redisVersion='" + redisVersion + '\'' + ", sqlVersion='" + sqlVersion + '\'' + ", sqlType='" + sqlType + '\'' + ", time='" + time + '\'' + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java ================================================ package com.genersoft.iot.vmp.common; import com.genersoft.iot.vmp.media.bean.MediaInfo; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.service.bean.DownloadFileInfo; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.io.Serializable; import java.util.Objects; @Data @Schema(description = "流信息") public class StreamInfo implements Serializable, Cloneable{ @Schema(description = "应用名") private String app; @Schema(description = "流ID") private String stream; @Schema(description = "设备编号") private String deviceId; @Schema(description = "通道ID") private Integer channelId; @Schema(description = "IP") private String ip; @Schema(description = "HTTP-FLV流地址") private StreamURL flv; @Schema(description = "HTTPS-FLV流地址") private StreamURL https_flv; @Schema(description = "Websocket-FLV流地址") private StreamURL ws_flv; @Schema(description = "Websockets-FLV流地址") private StreamURL wss_flv; @Schema(description = "HTTP-FMP4流地址") private StreamURL fmp4; @Schema(description = "HTTPS-FMP4流地址") private StreamURL https_fmp4; @Schema(description = "Websocket-FMP4流地址") private StreamURL ws_fmp4; @Schema(description = "Websockets-FMP4流地址") private StreamURL wss_fmp4; @Schema(description = "HLS流地址") private StreamURL hls; @Schema(description = "HTTPS-HLS流地址") private StreamURL https_hls; @Schema(description = "Websocket-HLS流地址") private StreamURL ws_hls; @Schema(description = "Websockets-HLS流地址") private StreamURL wss_hls; @Schema(description = "HTTP-TS流地址") private StreamURL ts; @Schema(description = "HTTPS-TS流地址") private StreamURL https_ts; @Schema(description = "Websocket-TS流地址") private StreamURL ws_ts; @Schema(description = "Websockets-TS流地址") private StreamURL wss_ts; @Schema(description = "RTMP流地址") private StreamURL rtmp; @Schema(description = "RTMPS流地址") private StreamURL rtmps; @Schema(description = "RTSP流地址") private StreamURL rtsp; @Schema(description = "RTSPS流地址") private StreamURL rtsps; @Schema(description = "RTC流地址") private StreamURL rtc; @Schema(description = "RTCS流地址") private StreamURL rtcs; @Schema(description = "流媒体节点") private MediaServer mediaServer; @Schema(description = "流编码信息") private MediaInfo mediaInfo; @Schema(description = "开始时间") private String startTime; @Schema(description = "结束时间") private String endTime; @Schema(description = "时长(回放时使用)") private Double duration; @Schema(description = "进度(录像下载使用)") private double progress; @Schema(description = "文件下载地址(录像下载使用)") private DownloadFileInfo downLoadFilePath; @Schema(description = "点播请求的callId") private String callId; @Schema(description = "是否暂停(录像回放使用)") private boolean pause; @Schema(description = "产生源类型,包括 unknown = 0,rtmp_push=1,rtsp_push=2,rtp_push=3,pull=4,ffmpeg_pull=5,mp4_vod=6,device_chn=7") private int originType; @Schema(description = "originType的文本描述") private String originTypeStr; @Schema(description = "转码后的视频流") private StreamInfo transcodeStream; @Schema(description = "使用的WVP ID") private String serverId; @Schema(description = "流绑定的流媒体操作key") private String key; public void setRtmp(String host, Integer port, Integer sslPort, String app, String stream, String callIdParam) { String file = String.format("%s/%s%s", app, stream, callIdParam); if (port != null && port > 0) { this.rtmp = new StreamURL("rtmp", host, port, file); } if (sslPort != null && sslPort > 0) { this.rtmps = new StreamURL("rtmps", host, sslPort, file); } } public void setRtsp(String host, Integer port, Integer sslPort, String app, String stream, String callIdParam) { String file = String.format("%s/%s%s", app, stream, callIdParam); if (port != null && port > 0) { this.rtsp = new StreamURL("rtsp", host, port, file); } if (sslPort != null && sslPort > 0) { this.rtsps = new StreamURL("rtsps", host, sslPort, file); } } public void setFlv(String host, Integer port, Integer sslPort, String file) { if (port != null && port > 0) { this.flv = new StreamURL("http", host, port, file); } if (sslPort != null && sslPort > 0) { this.https_flv = new StreamURL("https", host, sslPort, file); } } public void setWsFlv(String host, Integer port, Integer sslPort, String file) { if (port != null && port > 0) { this.ws_flv = new StreamURL("ws", host, port, file); } if (sslPort != null && sslPort > 0) { this.wss_flv = new StreamURL("wss", host, sslPort, file); } } public void setFmp4(String host, Integer port, Integer sslPort, String file) { if (port != null && port > 0) { this.fmp4 = new StreamURL("http", host, port, file); } if (sslPort != null && sslPort > 0) { this.https_fmp4 = new StreamURL("https", host, sslPort, file); } } public void setWsMp4(String host, Integer port, Integer sslPort, String file) { if (port != null && port > 0) { this.ws_fmp4 = new StreamURL("ws", host, port, file); } if (sslPort != null && sslPort > 0) { this.wss_fmp4 = new StreamURL("wss", host, sslPort, file); } } public void setHls(String host, Integer port, Integer sslPort, String app, String stream, String callIdParam) { String file = String.format("%s/%s/hls.m3u8%s", app, stream, callIdParam); if (port != null && port > 0) { this.hls = new StreamURL("http", host, port, file); } if (sslPort != null && sslPort > 0) { this.https_hls = new StreamURL("https", host, sslPort, file); } } public void setWsHls(String host, Integer port, Integer sslPort, String app, String stream, String callIdParam) { String file = String.format("%s/%s/hls.m3u8%s", app, stream, callIdParam); if (port != null && port > 0) { this.ws_hls = new StreamURL("ws", host, port, file); } if (sslPort != null && sslPort > 0) { this.wss_hls = new StreamURL("wss", host, sslPort, file); } } public void setTs(String host, Integer port, Integer sslPort, String app, String stream, String callIdParam) { String file = String.format("%s/%s.live.ts%s", app, stream, callIdParam); if (port != null && port > 0) { this.ts = new StreamURL("http", host, port, file); } if (sslPort != null && sslPort > 0) { this.https_ts = new StreamURL("https", host, sslPort, file); } } public void setWsTs(String host, Integer port, Integer sslPort, String app, String stream, String callIdParam) { String file = String.format("%s/%s.live.ts%s", app, stream, callIdParam); if (port != null && port > 0) { this.ws_ts = new StreamURL("ws", host, port, file); } if (sslPort != null && sslPort > 0) { this.wss_ts = new StreamURL("wss", host, sslPort, file); } } public void setRtc(String host, Integer port, Integer sslPort, String app, String stream, String callIdParam, boolean isPlay) { if (callIdParam != null) { callIdParam = Objects.equals(callIdParam, "") ? callIdParam : callIdParam.replace("?", "&"); } // String file = String.format("%s/%s?type=%s%s", app, stream, isPlay?"play":"push", callIdParam); String file = String.format("index/api/webrtc?app=%s&stream=%s&type=%s%s", app, stream, isPlay?"play":"push", callIdParam); if (port > 0) { this.rtc = new StreamURL("http", host, port, file); } if (sslPort > 0) { this.rtcs = new StreamURL("https", host, sslPort, file); } } public void changeStreamIp(String localAddr) { if (this.flv != null) { this.flv.setHost(localAddr); } if (this.ws_flv != null ){ this.ws_flv.setHost(localAddr); } if (this.hls != null ) { this.hls.setHost(localAddr); } if (this.ws_hls != null ) { this.ws_hls.setHost(localAddr); } if (this.ts != null ) { this.ts.setHost(localAddr); } if (this.ws_ts != null ) { this.ws_ts.setHost(localAddr); } if (this.fmp4 != null ) { this.fmp4.setHost(localAddr); } if (this.ws_fmp4 != null ) { this.ws_fmp4.setHost(localAddr); } if (this.rtc != null ) { this.rtc.setHost(localAddr); } if (this.https_flv != null) { this.https_flv.setHost(localAddr); } if (this.wss_flv != null) { this.wss_flv.setHost(localAddr); } if (this.https_hls != null) { this.https_hls.setHost(localAddr); } if (this.wss_hls != null) { this.wss_hls.setHost(localAddr); } if (this.wss_ts != null) { this.wss_ts.setHost(localAddr); } if (this.https_fmp4 != null) { this.https_fmp4.setHost(localAddr); } if (this.wss_fmp4 != null) { this.wss_fmp4.setHost(localAddr); } if (this.rtcs != null) { this.rtcs.setHost(localAddr); } if (this.rtsp != null) { this.rtsp.setHost(localAddr); } if (this.rtsps != null) { this.rtsps.setHost(localAddr); } if (this.rtmp != null) { this.rtmp.setHost(localAddr); } if (this.rtmps != null) { this.rtmps.setHost(localAddr); } } public static class TransactionInfo{ public String callId; public String localTag; public String remoteTag; public String branch; } private TransactionInfo transactionInfo; @Override public StreamInfo clone() { StreamInfo instance = null; try{ instance = (StreamInfo)super.clone(); if (this.flv != null) { instance.flv=this.flv.clone(); } if (this.ws_flv != null ){ instance.ws_flv= this.ws_flv.clone(); } if (this.hls != null ) { instance.hls= this.hls.clone(); } if (this.ws_hls != null ) { instance.ws_hls= this.ws_hls.clone(); } if (this.ts != null ) { instance.ts= this.ts.clone(); } if (this.ws_ts != null ) { instance.ws_ts= this.ws_ts.clone(); } if (this.fmp4 != null ) { instance.fmp4= this.fmp4.clone(); } if (this.ws_fmp4 != null ) { instance.ws_fmp4= this.ws_fmp4.clone(); } if (this.rtc != null ) { instance.rtc= this.rtc.clone(); } if (this.https_flv != null) { instance.https_flv= this.https_flv.clone(); } if (this.wss_flv != null) { instance.wss_flv= this.wss_flv.clone(); } if (this.https_hls != null) { instance.https_hls= this.https_hls.clone(); } if (this.wss_hls != null) { instance.wss_hls= this.wss_hls.clone(); } if (this.wss_ts != null) { instance.wss_ts= this.wss_ts.clone(); } if (this.https_fmp4 != null) { instance.https_fmp4= this.https_fmp4.clone(); } if (this.wss_fmp4 != null) { instance.wss_fmp4= this.wss_fmp4.clone(); } if (this.rtcs != null) { instance.rtcs= this.rtcs.clone(); } if (this.rtsp != null) { instance.rtsp= this.rtsp.clone(); } if (this.rtsps != null) { instance.rtsps= this.rtsps.clone(); } if (this.rtmp != null) { instance.rtmp= this.rtmp.clone(); } if (this.rtmps != null) { instance.rtmps= this.rtmps.clone(); } }catch(CloneNotSupportedException e) { e.printStackTrace(); } return instance; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/common/StreamURL.java ================================================ package com.genersoft.iot.vmp.common; import io.swagger.v3.oas.annotations.media.Schema; import java.io.Serializable; @Schema(description = "流地址信息") public class StreamURL implements Serializable,Cloneable { @Schema(description = "协议") private String protocol; @Schema(description = "主机地址") private String host; @Schema(description = "端口") private int port = -1; @Schema(description = "定位位置") private String file; @Schema(description = "拼接后的地址") private String url; public StreamURL() { } public StreamURL(String protocol, String host, int port, String file) { this.protocol = protocol; this.host = host; this.port = port; this.file = file; } public String getProtocol() { return protocol; } public void setProtocol(String protocol) { this.protocol = protocol; } public String getHost() { return host; } public void setHost(String host) { this.host = host; } public int getPort() { return port; } public void setPort(int port) { this.port = port; } public String getFile() { return file; } public void setFile(String file) { this.file = file; } public String getUrl() { return this.toString(); } @Override public String toString() { if (protocol != null && host != null && port != -1 ) { return String.format("%s://%s:%s/%s", protocol, host, port, file); }else { return null; } } @Override public StreamURL clone() throws CloneNotSupportedException { return (StreamURL) super.clone(); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/common/SubscribeCallback.java ================================================ package com.genersoft.iot.vmp.common; import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo; public interface SubscribeCallback{ public void run(String deviceId, SipTransactionInfo transactionInfo); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/common/SystemAllInfo.java ================================================ package com.genersoft.iot.vmp.common; import java.util.List; public class SystemAllInfo { private List cpu; private List mem; private List net; private long netTotal; private Object disk; public List getCpu() { return cpu; } public void setCpu(List cpu) { this.cpu = cpu; } public List getMem() { return mem; } public void setMem(List mem) { this.mem = mem; } public List getNet() { return net; } public void setNet(List net) { this.net = net; } public Object getDisk() { return disk; } public void setDisk(Object disk) { this.disk = disk; } public long getNetTotal() { return netTotal; } public void setNetTotal(long netTotal) { this.netTotal = netTotal; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/common/VersionPo.java ================================================ package com.genersoft.iot.vmp.common; import com.alibaba.fastjson2.annotation.JSONField; public class VersionPo { /** * git的全版本号 */ @JSONField(name="GIT_Revision") private String GIT_Revision; /** * maven版本 */ @JSONField(name = "Create_By") private String Create_By; /** * git的分支 */ @JSONField(name = "GIT_BRANCH") private String GIT_BRANCH; /** * git的url */ @JSONField(name = "GIT_URL") private String GIT_URL; /** * 构建日期 */ @JSONField(name = "BUILD_DATE") private String BUILD_DATE; /** * 构建日期 */ @JSONField(name = "GIT_DATE") private String GIT_DATE; /** * 项目名称 配合pom使用 */ @JSONField(name = "artifactId") private String artifactId; /** * git局部版本号 */ @JSONField(name = "GIT_Revision_SHORT") private String GIT_Revision_SHORT; /** * 项目的版本如2.0.1.0 配合pom使用 */ @JSONField(name = "version") private String version; /** * 子系统名称 */ @JSONField(name = "project") private String project; /** * jdk版本 */ @JSONField(name="Build_Jdk") private String Build_Jdk; public void setGIT_Revision(String GIT_Revision) { this.GIT_Revision = GIT_Revision; } public void setCreate_By(String create_By) { Create_By = create_By; } public void setGIT_BRANCH(String GIT_BRANCH) { this.GIT_BRANCH = GIT_BRANCH; } public void setGIT_URL(String GIT_URL) { this.GIT_URL = GIT_URL; } public void setBUILD_DATE(String BUILD_DATE) { this.BUILD_DATE = BUILD_DATE; } public void setArtifactId(String artifactId) { this.artifactId = artifactId; } public void setGIT_Revision_SHORT(String GIT_Revision_SHORT) { this.GIT_Revision_SHORT = GIT_Revision_SHORT; } public void setVersion(String version) { this.version = version; } public void setProject(String project) { this.project = project; } public void setBuild_Jdk(String build_Jdk) { Build_Jdk = build_Jdk; } public String getGIT_Revision() { return GIT_Revision; } public String getCreate_By() { return Create_By; } public String getGIT_BRANCH() { return GIT_BRANCH; } public String getGIT_URL() { return GIT_URL; } public String getBUILD_DATE() { return BUILD_DATE; } public String getArtifactId() { return artifactId; } public String getGIT_Revision_SHORT() { return GIT_Revision_SHORT; } public String getVersion() { return version; } public String getProject() { return project; } public String getBuild_Jdk() { return Build_Jdk; } public String getGIT_DATE() { return GIT_DATE; } public void setGIT_DATE(String GIT_DATE) { this.GIT_DATE = GIT_DATE; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java ================================================ package com.genersoft.iot.vmp.common; /** * @description: 定义常量 * @author: swwheihei * @date: 2019年5月30日 下午3:04:04 * */ public class VideoManagerConstants { public static final String WVP_SERVER_PREFIX = "VMP_SIGNALLING_SERVER_INFO_"; public static final String WVP_SERVER_LIST = "VMP_SERVER_LIST"; public static final String WVP_SERVER_STREAM_PREFIX = "VMP_SIGNALLING_STREAM_"; public static final String MEDIA_SERVER_PREFIX = "VMP_MEDIA_SERVER_INFO:"; public static final String ONLINE_MEDIA_SERVERS_PREFIX = "VMP_ONLINE_MEDIA_SERVERS:"; public static final String DEVICE_PREFIX = "VMP_DEVICE_INFO"; public static final String INVITE_PREFIX = "VMP_GB_INVITE_INFO"; public static final String SEND_RTP_PORT = "VM_SEND_RTP_PORT:"; public static final String SEND_RTP_INFO_CALLID = "VMP_SEND_RTP_INFO:CALL_ID:"; public static final String SEND_RTP_INFO_STREAM = "VMP_SEND_RTP_INFO:STREAM:"; public static final String SEND_RTP_INFO_CHANNEL = "VMP_SEND_RTP_INFO:CHANNEL:"; public static final String SIP_INVITE_SESSION = "VMP_SIP_INVITE_SESSION_INFO:"; public static final String SIP_INVITE_SESSION_CALL_ID = SIP_INVITE_SESSION + "CALL_ID:"; public static final String SIP_INVITE_SESSION_STREAM = SIP_INVITE_SESSION + "STREAM:"; public static final String MEDIA_STREAM_AUTHORITY = "VMP_MEDIA_STREAM_AUTHORITY"; public static final String SIP_CSEQ_PREFIX = "VMP_SIP_CSEQ_"; public static final String SIP_SUBSCRIBE_PREFIX = "VMP_SIP_SUBSCRIBE_"; public static final String SYSTEM_INFO_CPU_PREFIX = "VMP_SYSTEM_INFO_CPU_"; public static final String SYSTEM_INFO_MEM_PREFIX = "VMP_SYSTEM_INFO_MEM_"; public static final String SYSTEM_INFO_NET_PREFIX = "VMP_SYSTEM_INFO_NET_"; public static final String SYSTEM_INFO_DISK_PREFIX = "VMP_SYSTEM_INFO_DISK_"; public static final String BROADCAST_WAITE_INVITE = "task_broadcast_waite_invite_"; public static final String PUSH_STREAM_LIST = "VMP_PUSH_STREAM_LIST_"; public static final String WAITE_SEND_PUSH_STREAM = "VMP_WAITE_SEND_PUSH_STREAM:"; public static final String START_SEND_PUSH_STREAM = "VMP_START_SEND_PUSH_STREAM:"; public static final String SSE_TASK_KEY = "SSE_TASK_"; public static final String DRAW_THIN_PROCESS_PREFIX = "VMP_DRAW_THIN_PROCESS_"; //************************** redis 消息********************************* /** * 流变化的通知 */ public static final String WVP_MSG_STREAM_CHANGE_PREFIX = "WVP_MSG_STREAM_CHANGE_"; /** * 接收推流设备的GPS变化通知 */ public static final String VM_MSG_GPS = "VM_MSG_GPS"; /** * 接收推流设备的GPS变化通知 */ public static final String VM_MSG_PUSH_STREAM_STATUS_CHANGE = "VM_MSG_PUSH_STREAM_STATUS_CHANGE"; /** * 接收推流设备列表更新变化通知 */ public static final String VM_MSG_PUSH_STREAM_LIST_CHANGE = "VM_MSG_PUSH_STREAM_LIST_CHANGE"; /** * 同步三方组织结构回复 */ public static final String VM_MSG_GROUP_LIST_RESPONSE = "VM_MSG_GROUP_LIST_RESPONSE"; /** * 同步三方组织结构回复 */ public static final String VM_MSG_GROUP_LIST_CHANGE = "VM_MSG_GROUP_LIST_CHANGE"; /** * redis 消息通知设备推流到平台 */ public static final String VM_MSG_STREAM_PUSH_REQUESTED = "VM_MSG_STREAM_PUSH_REQUESTED"; /** * redis 消息通知上级平台开始观看流 */ public static final String VM_MSG_STREAM_START_PLAY_NOTIFY = "VM_MSG_STREAM_START_PLAY_NOTIFY"; /** * redis 消息通知上级平台停止观看流 */ public static final String VM_MSG_STREAM_STOP_PLAY_NOTIFY = "VM_MSG_STREAM_STOP_PLAY_NOTIFY"; /** * redis 消息接收关闭一个推流 */ public static final String VM_MSG_STREAM_PUSH_CLOSE_REQUESTED = "VM_MSG_STREAM_PUSH_CLOSE_REQUESTED"; /** * redis 消息通知平台通知设备推流结果 */ public static final String VM_MSG_STREAM_PUSH_RESPONSE = "VM_MSG_STREAM_PUSH_RESPONSE"; /** * redis 通知平台关闭推流 */ public static final String VM_MSG_STREAM_PUSH_CLOSE = "VM_MSG_STREAM_PUSH_CLOSE"; /** * redis 消息请求所有的在线通道 */ public static final String VM_MSG_GET_ALL_ONLINE_REQUESTED = "VM_MSG_GET_ALL_ONLINE_REQUESTED"; /** * 报警订阅的通知(收到报警向redis发出通知) */ public static final String VM_MSG_SUBSCRIBE_ALARM = "alarm"; /** * 报警通知的发送 (收到redis发出的通知,转发给其他平台) */ public static final String VM_MSG_SUBSCRIBE_ALARM_RECEIVE= "alarm_receive"; /** * 设备状态订阅的通知 */ public static final String VM_MSG_SUBSCRIBE_DEVICE_STATUS = "device"; //************************** 第三方 **************************************** public static final String WVP_STREAM_GB_ID_PREFIX = "memberNo_"; public static final String WVP_STREAM_GPS_MSG_PREFIX = "WVP_STREAM_GPS_MSG_"; public static final String WVP_OTHER_SEND_RTP_INFO = "VMP_OTHER_SEND_RTP_INFO_"; public static final String WVP_OTHER_SEND_PS_INFO = "VMP_OTHER_SEND_PS_INFO_"; public static final String WVP_OTHER_RECEIVE_RTP_INFO = "VMP_OTHER_RECEIVE_RTP_INFO_"; public static final String WVP_OTHER_RECEIVE_PS_INFO = "VMP_OTHER_RECEIVE_PS_INFO_"; /** * Redis Const * 设备录像信息结果前缀 */ public static final String REDIS_RECORD_INFO_RES_PRE = "GB_RECORD_INFO_RES_"; /** * Redis Const * 设备录像信息结果前缀 */ public static final String REDIS_RECORD_INFO_RES_COUNT_PRE = "GB_RECORD_INFO_RES_COUNT:"; //************************** 1078 **************************************** public static final String INVITE_INFO_1078_POSITION = "INVITE_INFO_1078_POSITION:"; public static final String INVITE_INFO_1078_PLAY = "INVITE_INFO_1078_PLAY:"; public static final String INVITE_INFO_1078_PLAYBACK = "INVITE_INFO_1078_PLAYBACK:"; public static final String INVITE_INFO_1078_TALK = "INVITE_INFO_1078_TALK:"; public static final String RECORD_LIST_1078 = "RECORD_LIST_1078:"; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/common/enums/ChannelDataType.java ================================================ package com.genersoft.iot.vmp.common.enums; /** * 支持的通道数据类型 */ public class ChannelDataType { public final static int GB28181 = 1; public final static int STREAM_PUSH = 2; public final static int STREAM_PROXY = 3; public final static int JT_1078 = 200; public final static String PLAY_SERVICE = "sourceChannelPlayService"; public final static String PLAYBACK_SERVICE = "sourceChannelPlaybackService"; public final static String DOWNLOAD_SERVICE = "sourceChannelDownloadService"; public final static String PTZ_SERVICE = "sourceChannelPTZService"; public static String getDateTypeDesc(Integer dataType) { if (dataType == null) { return "未知"; } return switch (dataType) { case ChannelDataType.GB28181 -> "国标28181"; case ChannelDataType.STREAM_PUSH -> "推流设备"; case ChannelDataType.STREAM_PROXY -> "拉流代理"; case ChannelDataType.JT_1078 -> "部标设备"; default -> "未知"; }; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/common/enums/DeviceControlType.java ================================================ package com.genersoft.iot.vmp.common.enums; import org.dom4j.Element; import org.springframework.util.ObjectUtils; /** * @author gaofuwang * @date 2023/01/18/ 10:09:00 * @since 1.0 */ public enum DeviceControlType { /** * 云台控制 * 上下左右,预置位,扫描,辅助功能,巡航 */ PTZ("PTZCmd","云台控制"), /** * 远程启动 */ TELE_BOOT("TeleBoot","远程启动"), /** * 录像控制 */ RECORD("RecordCmd","录像控制"), /** * 布防撤防 */ GUARD("GuardCmd","布防撤防"), /** * 告警控制 */ ALARM("AlarmCmd","告警控制"), /** * 强制关键帧 */ I_FRAME("IFameCmd","强制关键帧"), /** * 拉框放大 */ DRAG_ZOOM_IN("DragZoomIn","拉框放大"), /** * 拉框缩小 */ DRAG_ZOOM_OUT("DragZoomOut","拉框缩小"), /** * 看守位 */ HOME_POSITION("HomePosition","看守位"); private final String val; private final String desc; DeviceControlType(String val, String desc) { this.val = val; this.desc = desc; } public String getVal() { return val; } public String getDesc() { return desc; } public static DeviceControlType typeOf(Element rootElement) { for (DeviceControlType item : DeviceControlType.values()) { if (!ObjectUtils.isEmpty(rootElement.element(item.val)) || !ObjectUtils.isEmpty(rootElement.elements(item.val))) { return item; } } return null; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/common/enums/MediaApp.java ================================================ package com.genersoft.iot.vmp.common.enums; public class MediaApp { public final static String GB28181 = "rtp"; public final static String GB28181_TALK = "talk"; public final static String GB28181_BROADCAST = "broadcast"; public final static String JT1078 = "1078"; public static boolean isKeywords(String app) { return GB28181.equals(app) || GB28181_TALK.equals(app) || GB28181_BROADCAST.equals(app) || JT1078.equals(app); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/CivilCodeFileConf.java ================================================ package com.genersoft.iot.vmp.conf; import com.genersoft.iot.vmp.common.CivilCodePo; import com.genersoft.iot.vmp.utils.CivilCodeUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Lazy; import org.springframework.core.annotation.Order; import org.springframework.core.io.ClassPathResource; import org.springframework.util.ObjectUtils; import java.io.BufferedReader; import java.io.File; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.nio.file.Files; /** * 启动时读取行政区划表 */ @Slf4j @Configuration @Order(value=15) public class CivilCodeFileConf implements CommandLineRunner { @Autowired @Lazy private UserSetting userSetting; @Override public void run(String... args) throws Exception { if (ObjectUtils.isEmpty(userSetting.getCivilCodeFile())) { log.warn("[行政区划] 文件未设置,可能造成目录刷新结果不完整"); return; } InputStream inputStream; if (userSetting.getCivilCodeFile().startsWith("classpath:")){ String filePath = userSetting.getCivilCodeFile().substring("classpath:".length()); ClassPathResource civilCodeFile = new ClassPathResource(filePath); if (!civilCodeFile.exists()) { log.warn("[行政区划] 文件<{}>不存在,可能造成目录刷新结果不完整", userSetting.getCivilCodeFile()); return; } inputStream = civilCodeFile.getInputStream(); }else { File civilCodeFile = new File(userSetting.getCivilCodeFile()); if (!civilCodeFile.exists()) { log.warn("[行政区划] 文件<{}>不存在,可能造成目录刷新结果不完整", userSetting.getCivilCodeFile()); return; } inputStream = Files.newInputStream(civilCodeFile.toPath()); } BufferedReader inputStreamReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); int index = -1; String line; while ((line = inputStreamReader.readLine()) != null) { index ++; if (index == 0) { continue; } String[] infoArray = line.split(","); CivilCodePo civilCodePo = CivilCodePo.getInstance(infoArray); CivilCodeUtil.INSTANCE.add(civilCodePo); } inputStreamReader.close(); inputStream.close(); if (CivilCodeUtil.INSTANCE.isEmpty()) { log.warn("[行政区划] 文件内容为空,可能造成目录刷新结果不完整"); }else { log.info("[行政区划] 加载成功,共加载数据{}条", CivilCodeUtil.INSTANCE.size()); } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/CloudRecordTimer.java ================================================ package com.genersoft.iot.vmp.conf; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.service.IMediaServerService; import com.genersoft.iot.vmp.service.bean.CloudRecordItem; import com.genersoft.iot.vmp.storager.dao.CloudRecordServiceMapper; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.io.File; import java.util.Calendar; import java.util.Date; import java.util.List; /** * 录像文件定时删除 */ @Slf4j @Component public class CloudRecordTimer { @Autowired private IMediaServerService mediaServerService; @Autowired private CloudRecordServiceMapper cloudRecordServiceMapper; /** * 定时查询待删除的录像文件 */ // @Scheduled(fixedRate = 10000) //每五秒执行一次,方便测试 @Scheduled(cron = "0 0 0 * * ?") //每天的0点执行 public void execute(){ log.info("[录像文件定时清理] 开始清理过期录像文件"); // 获取配置了assist的流媒体节点 List mediaServerItemList = mediaServerService.getAllOnline(); if (mediaServerItemList.isEmpty()) { return; } long result = 0; for (MediaServer mediaServerItem : mediaServerItemList) { Calendar lastCalendar = Calendar.getInstance(); if (mediaServerItem.getRecordDay() > 0) { lastCalendar.setTime(new Date()); // 获取保存的最后截至日[期,因为每个节点都有一个日期,也就是支持每个节点设置不同的保存日期, lastCalendar.add(Calendar.DAY_OF_MONTH, -mediaServerItem.getRecordDay()); Long lastDate = lastCalendar.getTimeInMillis(); // 获取到截至日期之前的录像文件列表,文件列表满足未被收藏和保持的。这两个字段目前共能一致, // 为我自己业务系统相关的代码,大家使用的时候直接使用收藏(collect)这一个类型即可 List cloudRecordItemList = cloudRecordServiceMapper.queryRecordListForDelete(lastDate, mediaServerItem.getId()); if (cloudRecordItemList.isEmpty()) { continue; } // TODO 后续可以删除空了的过期日期文件夹 for (CloudRecordItem cloudRecordItem : cloudRecordItemList) { String date = new File(cloudRecordItem.getFilePath()).getParentFile().getName(); try { boolean deleteResult = mediaServerService.deleteRecordDirectory(mediaServerItem, cloudRecordItem.getApp(), cloudRecordItem.getStream(), date, cloudRecordItem.getFileName()); if (deleteResult) { log.warn("[录像文件定时清理] 删除磁盘文件成功: {}", cloudRecordItem.getFilePath()); } }catch (ControllerException ignored) {} } result += cloudRecordServiceMapper.deleteList(cloudRecordItemList); } } log.info("[录像文件定时清理] 共清理{}个过期录像文件", result); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/DynamicTask.java ================================================ package com.genersoft.iot.vmp.conf; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.ObjectUtils; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.stereotype.Component; import jakarta.annotation.PostConstruct; import java.time.Instant; import java.util.Date; 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 */ @Slf4j @Component public class DynamicTask { private ThreadPoolTaskScheduler threadPoolTaskScheduler; private final Map> futureMap = new ConcurrentHashMap<>(); private final Map runnableMap = new ConcurrentHashMap<>(); @PostConstruct public void DynamicTask() { threadPoolTaskScheduler = new ThreadPoolTaskScheduler(); threadPoolTaskScheduler.setPoolSize(300); threadPoolTaskScheduler.setWaitForTasksToCompleteOnShutdown(true); threadPoolTaskScheduler.setAwaitTerminationSeconds(10); threadPoolTaskScheduler.setThreadNamePrefix("dynamicTask-"); threadPoolTaskScheduler.initialize(); } /** * 循环执行的任务 * @param key 任务ID * @param task 任务 * @param cycleForCatalog 间隔 毫秒 * @return */ public void startCron(String key, Runnable task, int cycleForCatalog) { if(ObjectUtils.isEmpty(key)) { return; } ScheduledFuture future = futureMap.get(key); if (future != null) { if (future.isCancelled()) { log.debug("任务【{}】已存在但是关闭状态!!!", key); } else { log.debug("任务【{}】已存在且已启动!!!", key); return; } } // scheduleWithFixedDelay 必须等待上一个任务结束才开始计时period, cycleForCatalog表示执行的间隔 future = threadPoolTaskScheduler.scheduleAtFixedRate(task, new Date(System.currentTimeMillis() + cycleForCatalog), cycleForCatalog); if (future != null){ futureMap.put(key, future); runnableMap.put(key, task); log.debug("任务【{}】启动成功!!!", key); }else { log.debug("任务【{}】启动失败!!!", key); } } /** * 延时任务 * @param key 任务ID * @param task 任务 * @param delay 延时 /毫秒 * @return */ public void startDelay(String key, Runnable task, int delay) { if(ObjectUtils.isEmpty(key)) { return; } stop(key); // 获取执行的时刻 Instant startInstant = Instant.now().plusMillis(TimeUnit.MILLISECONDS.toMillis(delay)); ScheduledFuture future = futureMap.get(key); if (future != null) { if (future.isCancelled()) { log.debug("任务【{}】已存在但是关闭状态!!!", key); } else { log.debug("任务【{}】已存在且已启动!!!", key); return; } } // scheduleWithFixedDelay 必须等待上一个任务结束才开始计时period, cycleForCatalog表示执行的间隔 future = threadPoolTaskScheduler.schedule(task, startInstant); if (future != null){ futureMap.put(key, future); runnableMap.put(key, task); log.debug("任务【{}】启动成功!!!", key); }else { log.debug("任务【{}】启动失败!!!", key); } } public boolean stop(String key) { if(ObjectUtils.isEmpty(key)) { return false; } boolean result = false; if (!ObjectUtils.isEmpty(futureMap.get(key)) && !futureMap.get(key).isCancelled() && !futureMap.get(key).isDone()) { result = futureMap.get(key).cancel(false); futureMap.remove(key); runnableMap.remove(key); } return result; } public boolean contains(String key) { if(ObjectUtils.isEmpty(key)) { return false; } return futureMap.get(key) != null; } public Set getAllKeys() { return futureMap.keySet(); } public Runnable get(String key) { if(ObjectUtils.isEmpty(key)) { return null; } return runnableMap.get(key); } /** * 每五分钟检查失效的任务,并移除 */ @Scheduled(cron="0 0/5 * * * ?") public void execute(){ if (futureMap.size() > 0) { for (String key : futureMap.keySet()) { ScheduledFuture future = futureMap.get(key); if (future.isDone() || future.isCancelled()) { futureMap.remove(key); runnableMap.remove(key); } } } } public boolean isAlive(String key) { return futureMap.get(key) != null && !futureMap.get(key).isDone() && !futureMap.get(key).isCancelled(); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/GlobalExceptionHandler.java ================================================ package com.genersoft.iot.vmp.conf; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; /** * 全局异常处理 */ @Slf4j @RestControllerAdvice public class GlobalExceptionHandler { /** * 默认异常处理 * @param e 异常 * @return 统一返回结果 */ @ExceptionHandler(Exception.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public WVPResult exceptionHandler(Exception e) { log.error("[全局异常]: ", e); return WVPResult.fail(ErrorCode.ERROR500.getCode(), e.getMessage()); } /** * 默认异常处理 * @param e 异常 * @return 统一返回结果 */ @ExceptionHandler(IllegalStateException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public WVPResult exceptionHandler(IllegalStateException e) { return WVPResult.fail(ErrorCode.ERROR400); } /** * 默认异常处理 * @param e 异常 * @return 统一返回结果 */ @ExceptionHandler(HttpRequestMethodNotSupportedException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public WVPResult exceptionHandler(HttpRequestMethodNotSupportedException e) { return WVPResult.fail(ErrorCode.ERROR400); } /** * 断言异常处理 * @param e 异常 * @return 统一返回结果 */ @ExceptionHandler(IllegalArgumentException.class) @ResponseStatus(HttpStatus.OK) public WVPResult exceptionHandler(IllegalArgumentException e) { return WVPResult.fail(ErrorCode.ERROR100.getCode(), e.getMessage()); } /** * 自定义异常处理, 处理controller中返回的错误 * @param e 异常 * @return 统一返回结果 */ @ExceptionHandler(ControllerException.class) @ResponseStatus(HttpStatus.OK) public ResponseEntity> exceptionHandler(ControllerException e) { return new ResponseEntity<>(WVPResult.fail(e.getCode(), e.getMsg()), HttpStatus.OK); } /** * 登陆失败 * @param e 异常 * @return 统一返回结果 */ @ExceptionHandler(BadCredentialsException.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public ResponseEntity> exceptionHandler(BadCredentialsException e) { return new ResponseEntity<>(WVPResult.fail(ErrorCode.ERROR100.getCode(), e.getMessage()), HttpStatus.OK); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/GlobalResponseAdvice.java ================================================ package com.genersoft.iot.vmp.conf; import com.alibaba.fastjson2.JSON; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import org.jetbrains.annotations.NotNull; import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; import java.util.LinkedHashMap; /** * 全局统一返回结果 * @author lin */ @RestControllerAdvice public class GlobalResponseAdvice implements ResponseBodyAdvice { @Override public boolean supports(@NotNull MethodParameter returnType, @NotNull Class> converterType) { return true; } @Override public Object beforeBodyWrite(Object body, @NotNull MethodParameter returnType, @NotNull MediaType selectedContentType, @NotNull Class> selectedConverterType, @NotNull ServerHttpRequest request, @NotNull ServerHttpResponse response) { // 排除api文档的接口,这个接口不需要统一 String[] excludePath = {"/v3/api-docs","/api/v1","/index/hook","/api/video-"}; for (String path : excludePath) { if (request.getURI().getPath().startsWith(path)) { return body; } } if (selectedContentType.equals(MediaType.parseMediaType("application/x-protobuf"))) { return body; } if (body instanceof WVPResult) { return body; } if (body instanceof ErrorCode) { ErrorCode errorCode = (ErrorCode) body; return new WVPResult<>(errorCode.getCode(), errorCode.getMsg(), null); } if (body instanceof String) { return JSON.toJSONString(WVPResult.success(body)); } if (body instanceof LinkedHashMap) { LinkedHashMap bodyMap = (LinkedHashMap) body; if (bodyMap.get("status") != null && (Integer)bodyMap.get("status") != 200) { return body; } } return WVPResult.success(body); } /** * 防止返回string时出错 * @return */ /*@Bean public HttpMessageConverters custHttpMessageConverter() { return new HttpMessageConverters(new FastJsonHttpMessageConverter()); }*/ } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/MediaConfig.java ================================================ package com.genersoft.iot.vmp.conf; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.utils.DateUtil; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.util.ObjectUtils; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.regex.Pattern; @Slf4j @Configuration("mediaConfig") @Order(0) @Data public class MediaConfig{ // 修改必须配置,不再支持自动获取 @Value("${media.id}") private String id; @Value("${media.ip}") private String ip; @Value("${media.wan_ip:}") private String wanIp; @Value("${media.hook-ip:127.0.0.1}") private String hookIp; @Value("${sip.domain}") private String sipDomain; @Value("${media.sdp-ip:${media.wan_ip:}}") private String sdpIp; @Value("${media.stream-ip:${media.wan_ip:}}") private String streamIp; @Value("${media.http-port:0}") private Integer httpPort; @Value("${media.auto-config:true}") private boolean autoConfig = true; @Value("${media.secret}") private String secret; @Value("${media.rtp.enable}") private boolean rtpEnable; @Value("${media.rtp.port-range}") private String rtpPortRange; @Value("${media.rtp.send-port-range}") private String rtpSendPortRange; @Value("${media.record-assist-port:0}") private Integer recordAssistPort = 0; @Value("${media.record-day:7}") private Integer recordDay; @Value("${media.record-path:}") private String recordPath; @Value("${media.type:zlm}") private String type; public String getSdpIp() { if (ObjectUtils.isEmpty(sdpIp)){ return ip; }else { if (isValidIPAddress(sdpIp)) { return sdpIp; }else { // 按照域名解析 String hostAddress = null; try { hostAddress = InetAddress.getByName(sdpIp).getHostAddress(); } catch (UnknownHostException e) { log.error("[获取SDP IP]: 域名解析失败"); } return hostAddress; } } } public String getStreamIp() { if (ObjectUtils.isEmpty(streamIp)){ return ip; }else { return streamIp; } } public MediaServer buildMediaSer(){ MediaServer mediaServer = new MediaServer(); mediaServer.setId(id); mediaServer.setIp(ip); mediaServer.setDefaultServer(true); mediaServer.setHookIp(getHookIp()); mediaServer.setSdpIp(getSdpIp()); mediaServer.setStreamIp(getStreamIp()); mediaServer.setHttpPort(httpPort); mediaServer.setAutoConfig(autoConfig); mediaServer.setSecret(secret); mediaServer.setRtpEnable(rtpEnable); mediaServer.setRtpPortRange(rtpPortRange); mediaServer.setSendRtpPortRange(rtpSendPortRange); mediaServer.setRecordAssistPort(recordAssistPort); mediaServer.setHookAliveInterval(10f); mediaServer.setRecordDay(recordDay); mediaServer.setStatus(false); mediaServer.setType(type); if (recordPath != null) { mediaServer.setRecordPath(recordPath); } mediaServer.setCreateTime(DateUtil.getNow()); mediaServer.setUpdateTime(DateUtil.getNow()); return mediaServer; } private boolean isValidIPAddress(String ipAddress) { if ((ipAddress != null) && (!ipAddress.isEmpty())) { 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); } return false; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/MediaStatusTimerTask.java ================================================ package com.genersoft.iot.vmp.conf; import org.springframework.scheduling.annotation.Scheduled; /** * 定时向zlm同步媒体流状态 */ public class MediaStatusTimerTask { // @Scheduled(fixedRate = 2 * 1000) //每3秒执行一次 public void execute(){ } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/MybatisConfig.java ================================================ package com.genersoft.iot.vmp.conf; import org.apache.ibatis.logging.stdout.StdOutImpl; import org.apache.ibatis.mapping.DatabaseIdProvider; import org.apache.ibatis.mapping.VendorDatabaseIdProvider; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import javax.sql.DataSource; import java.util.Properties; /** * 配置mybatis */ @Configuration @Order(value=1) public class MybatisConfig { @Autowired private UserSetting userSetting; @Bean public DatabaseIdProvider databaseIdProvider() { VendorDatabaseIdProvider databaseIdProvider = new VendorDatabaseIdProvider(); Properties properties = new Properties(); properties.setProperty("Oracle", "oracle"); properties.setProperty("MySQL", "mysql"); properties.setProperty("DB2", "db2"); properties.setProperty("Derby", "derby"); properties.setProperty("H2", "h2"); properties.setProperty("HSQL", "hsql"); properties.setProperty("Informix", "informix"); properties.setProperty("MS-SQL", "ms-sql"); properties.setProperty("PostgreSQL", "postgresql"); properties.setProperty("Sybase", "sybase"); properties.setProperty("Hana", "hana"); properties.setProperty("DM", "dm"); properties.setProperty("KingbaseES", "kingbase"); properties.setProperty("KingBase8", "kingbase"); databaseIdProvider.setProperties(properties); return databaseIdProvider; } @Bean public SqlSessionFactory sqlSessionFactory(DataSource dataSource, DatabaseIdProvider databaseIdProvider) throws Exception { final SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean(); sqlSessionFactory.setDataSource(dataSource); org.apache.ibatis.session.Configuration config = new org.apache.ibatis.session.Configuration(); if (userSetting.getSqlLog()){ config.setLogImpl(StdOutImpl.class); } config.setMapUnderscoreToCamelCase(true); sqlSessionFactory.setConfiguration(config); sqlSessionFactory.setDatabaseIdProvider(databaseIdProvider); return sqlSessionFactory.getObject(); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/ScheduleConfig.java ================================================ package com.genersoft.iot.vmp.conf; import org.apache.commons.lang3.concurrent.BasicThreadFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.SchedulingConfigurer; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.scheduling.config.ScheduledTaskRegistrar; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor; import static com.genersoft.iot.vmp.conf.ThreadPoolTaskConfig.cpuNum; /** * "@Scheduled"是Spring框架提供的一种定时任务执行机制,默认情况下它是单线程的,在同时执行多个定时任务时可能会出现阻塞和性能问题。 * 为了解决这种单线程瓶颈问题,可以将定时任务的执行机制改为支持多线程 */ @Configuration public class ScheduleConfig implements SchedulingConfigurer { /** * 核心线程数(默认线程数) */ private static final int corePoolSize = Math.max(cpuNum, 20); /** * 线程池名前缀 */ private static final String threadNamePrefix = "schedule"; @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(corePoolSize, new BasicThreadFactory.Builder().namingPattern(threadNamePrefix).daemon(true).build(), new ThreadPoolExecutor.CallerRunsPolicy()); taskRegistrar.setScheduler(scheduledThreadPoolExecutor); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/ServiceInfo.java ================================================ package com.genersoft.iot.vmp.conf; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.web.context.WebServerInitializedEvent; import org.springframework.context.ApplicationListener; import org.springframework.stereotype.Component; @Slf4j @Component public class ServiceInfo implements ApplicationListener { @Getter private static int serverPort; @Override public void onApplicationEvent(WebServerInitializedEvent event) { // 项目启动获取启动的端口号 ServiceInfo.serverPort = event.getWebServer().getPort(); log.info("项目启动获取启动的端口号: {}", ServiceInfo.serverPort); } public void setServerPort(int serverPort) { ServiceInfo.serverPort = serverPort; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/SipConfig.java ================================================ package com.genersoft.iot.vmp.conf; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import java.util.List; @Component @ConfigurationProperties(prefix = "sip", ignoreInvalidFields = true) @Order(0) @Data public class SipConfig { private String ip; private String showIp; private List monitorIps; private Integer port; private String domain; private String id; private String password; Integer ptzSpeed = 50; Integer registerTimeInterval = 120; private boolean alarm = false; private long timeout = 1000; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/SpringDocConfig.java ================================================ package com.genersoft.iot.vmp.conf; import com.genersoft.iot.vmp.conf.security.JwtUtils; import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Contact; import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.info.License; import io.swagger.v3.oas.models.security.SecurityScheme; import org.springdoc.core.models.GroupedOpenApi; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; /** * @author lin */ @Configuration @Order(1) @ConditionalOnProperty(value = "user-settings.doc-enable", havingValue = "true", matchIfMissing = true) public class SpringDocConfig { @Value("${doc.enabled: true}") private boolean enable; @Bean public OpenAPI springShopOpenApi() { Contact contact = new Contact(); contact.setName("pan"); contact.setEmail("648540858@qq.com"); return new OpenAPI() .components(new Components() .addSecuritySchemes(JwtUtils.HEADER, new SecurityScheme() .type(SecurityScheme.Type.HTTP) .bearerFormat("JWT"))) .info(new Info().title("WVP-PRO 接口文档") .contact(contact) .description("开箱即用的28181协议视频平台。
    " + "1. 打开登录接口" + " 登录成功后返回AccessToken。
    " + "2. 填写到AccessToken到参数值 Token配置
    " + "后续接口就可以直接测试了") .version("v3.1.0") .license(new License().name("Apache 2.0").url("http://springdoc.org"))); } /** * 添加分组 * @return */ @Bean public GroupedOpenApi publicApi() { return GroupedOpenApi.builder() .group("1. 全部") .packagesToScan("com.genersoft.iot.vmp") .build(); } @Bean public GroupedOpenApi publicApi2() { return GroupedOpenApi.builder() .group("2. 国标28181") .packagesToScan("com.genersoft.iot.vmp.gb28181") .build(); } @Bean public GroupedOpenApi publicApi3() { return GroupedOpenApi.builder() .group("3. 拉流转发") .packagesToScan("com.genersoft.iot.vmp.streamProxy") .build(); } @Bean public GroupedOpenApi publicApi4() { return GroupedOpenApi.builder() .group("4. 推流管理") .packagesToScan("com.genersoft.iot.vmp.streamPush") .build(); } @Bean public GroupedOpenApi publicApi5() { return GroupedOpenApi.builder() .group("4. 服务管理") .packagesToScan("com.genersoft.iot.vmp.server") .build(); } @Bean public GroupedOpenApi publicApi6() { return GroupedOpenApi.builder() .group("5. 用户管理") .packagesToScan("com.genersoft.iot.vmp.user") .build(); } @Bean public GroupedOpenApi publicApi7() { return GroupedOpenApi.builder() .group("6. 部标设备") .packagesToScan("com.genersoft.iot.vmp.jt1078.controller") .build(); } @Bean public GroupedOpenApi publicApi99() { return GroupedOpenApi.builder() .group("99. 第三方接口") .packagesToScan("com.genersoft.iot.vmp.web.custom") .build(); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/SystemInfoTimerTask.java ================================================ package com.genersoft.iot.vmp.conf; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.utils.SystemInfoUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.util.List; import java.util.Map; /** * 获取系统信息写入redis */ @Slf4j @Component public class SystemInfoTimerTask { @Autowired private IRedisCatchStorage redisCatchStorage; @Scheduled(fixedRate = 2000) //每1秒执行一次 public void execute(){ try { double cpuInfo = SystemInfoUtils.getCpuInfo(); redisCatchStorage.addCpuInfo(cpuInfo); double memInfo = SystemInfoUtils.getMemInfo(); redisCatchStorage.addMemInfo(memInfo); Map networkInterfaces = SystemInfoUtils.getNetworkInterfaces(); redisCatchStorage.addNetInfo(networkInterfaces); List> diskInfo =SystemInfoUtils.getDiskInfo(); redisCatchStorage.addDiskInfo(diskInfo); } catch (InterruptedException e) { log.error("[获取系统信息失败] {}", e.getMessage()); } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/ThreadPoolTaskConfig.java ================================================ package com.genersoft.iot.vmp.conf; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import java.util.concurrent.ThreadPoolExecutor; /** * ThreadPoolTask 配置类 * @author lin */ @Configuration @Order(1) @EnableAsync(proxyTargetClass = true) public class ThreadPoolTaskConfig { public static final int cpuNum = Runtime.getRuntime().availableProcessors(); /** * 默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务, * 当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中; * 当队列满了,就继续创建线程,当线程数量大于等于maxPoolSize后,开始使用拒绝策略拒绝 */ /** * 核心线程数(默认线程数) */ private static final int corePoolSize = Math.max(cpuNum * 2, 16); /** * 最大线程数 */ private static final int maxPoolSize = corePoolSize * 10; /** * 允许线程空闲时间(单位:默认为秒) */ private static final int keepAliveTime = 30; /** * 缓冲队列大小 */ private static final int queueCapacity = 10000; /** * 线程池名前缀 */ private static final String threadNamePrefix = "async-"; @Bean("taskExecutor") // bean的名称,默认为首字母小写的方法名 public ThreadPoolTaskExecutor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(corePoolSize); executor.setMaxPoolSize(maxPoolSize); executor.setQueueCapacity(queueCapacity); executor.setKeepAliveSeconds(keepAliveTime); executor.setThreadNamePrefix(threadNamePrefix); // 线程池对拒绝任务的处理策略 // CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 初始化 executor.initialize(); return executor; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java ================================================ package com.genersoft.iot.vmp.conf; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; /** * 配置文件 user-settings 映射的配置信息 */ @Component @ConfigurationProperties(prefix = "user-settings", ignoreInvalidFields = true) @Order(0) @Data public class UserSetting { /** * 是否保存位置的历史记录(轨迹) */ private Boolean savePositionHistory = Boolean.FALSE; /** * 是否开始自动点播: 请求流为未拉起的流时,自动开启点播, 需要rtp.enable=true */ private Boolean autoApplyPlay = Boolean.FALSE; /** * [可选] 部分设备需要扩展SDP,需要打开此设置,一般设备无需打开 */ private Boolean seniorSdp = Boolean.FALSE; /** * 点播/录像回放 等待超时时间,单位:毫秒 */ private Integer playTimeout = 10000; /** * 获取设备录像数据超时时间,单位:毫秒 */ private Integer recordInfoTimeout = 15000; /** * 上级点播等待超时时间,单位:毫秒 */ private int platformPlayTimeout = 20000; /** * 是否开启接口鉴权 */ private Boolean interfaceAuthentication = Boolean.TRUE; /** * 接口鉴权例外的接口, 即不进行接口鉴权的接口,尽量详细书写,尽量不用/**,至少两级目录 */ private List interfaceAuthenticationExcludes = new ArrayList<>(); /** * 推流直播是否录制 */ private Boolean recordPushLive = Boolean.TRUE; /** * 国标是否录制 */ private Boolean recordSip = Boolean.TRUE; /** * 使用推流状态作为推流通道状态 */ private Boolean usePushingAsStatus = Boolean.FALSE; /** * 使用来源请求ip作为streamIp,当且仅当你只有zlm节点它与wvp在一起的情况下开启 */ private Boolean useSourceIpAsStreamIp = Boolean.FALSE; /** * 是否使用设备来源Ip作为回复IP, 不设置则为 false */ private Boolean sipUseSourceIpAsRemoteAddress = Boolean.FALSE; /** * 国标点播 按需拉流, true:有人观看拉流,无人观看释放, false:拉起后不自动释放 */ private Boolean streamOnDemand = Boolean.TRUE; /** * 推流鉴权, 默认开启 */ private Boolean pushAuthority = Boolean.TRUE; /** * 设备上线时是否自动同步通道 */ private Boolean syncChannelOnDeviceOnline = Boolean.FALSE; /** * 是否开启sip日志 */ private Boolean sipLog = Boolean.FALSE; /** * 是否开启mybatis-sql日志 */ private Boolean sqlLog = Boolean.FALSE; /** * 消息通道功能-缺少国标ID是否给所有上级发送消息 */ private Boolean sendToPlatformsWhenIdLost = Boolean.FALSE; /** * 保持通道状态,不接受notify通道状态变化, 兼容海康平台发送错误消息 */ private Boolean refuseChannelStatusChannelFormNotify = Boolean.FALSE; /** * 设备/通道状态变化时发送消息 */ private Boolean deviceStatusNotify = Boolean.TRUE; /** * 上级平台点播时不使用上级平台指定的ssrc,使用自定义的ssrc,参考国标文档-点播外域设备媒体流SSRC处理方式 */ private Boolean useCustomSsrcForParentInvite = Boolean.TRUE; /** * 开启接口文档页面。 默认开启,生产环境建议关闭,遇到swagger相关的漏洞时也可以关闭 */ private Boolean docEnable = Boolean.TRUE; /** * 服务ID,不写则为000000 */ private String serverId = "000000"; /** * 国标级联语音喊话发流模式 * UDP:udp传输 TCP-ACTIVE:tcp主动模式 TCP-PASSIVE:tcp被动模式 */ private String broadcastForPlatform = "UDP"; /** * 行政区划信息文件,系统启动时会加载到系统里 */ private String civilCodeFile = "classpath:civilCode.csv"; /** * 跨域配置,不配置此项则允许所有跨域请求,配置后则只允许配置的页面的地址请求, 可以配置多个 */ private List allowedOrigins = new ArrayList<>(); /** * 设置notify缓存队列最大长度,超过此长度的数据将返回486 BUSY_HERE,消息丢弃, 默认100000 */ private int maxNotifyCountQueue = 100000; /** * 国标级联离线后多久重试一次注册 */ private int registerAgainAfterTime = 60; /** * 国标续订方式,true为续订,每次注册在同一个会话里,false为重新注册,每次使用新的会话 */ private boolean registerKeepIntDialog = false; /** * # 国标设备离线后的上线策略, * # 0: 国标标准实现,设备离线后不回复心跳,直到设备重新注册上线, * # 1(默认): 对于离线设备,收到心跳就把设备设置为上线,并更新注册时间为上次这次心跳的时间。防止过期时间判断异常 */ private int gbDeviceOnline = 1; /** * 登录超时时间(分钟), */ private long loginTimeout = 60; /** * jwk文件路径,若不指定则使用resources目录下的jwk.json */ private String jwkFile = "classpath:jwk.json"; /** * wvp集群模式下如果注册向上级的wvp奔溃,则自动选择一个其他wvp继续注册到上级 */ private boolean autoRegisterPlatform = false; /** * 按需发送推流设备位置, 默认发送移动位置订阅时如果位置不变则不发送, 设置为false按照国标间隔持续发送 */ private boolean sendPositionOnDemand = true; /** * 部分设备会在短时间内发送大量注册, 导致协议栈内存溢出, 开启此项可以防止这部分设备注册, 避免服务崩溃,但是会降低系统性能, 描述如下 * 默认值为 true。 * 将此设置为 false 会使 Stack 在 Server Transaction 进入 TERMINATED 状态后关闭服务器套接字。 * 这允许服务器防止客户端发起的基于 TCP 的拒绝服务攻击(即发起数百个客户端事务)。 * 如果为 true(默认作),则堆栈将保持套接字打开,以便以牺牲线程和内存资源为代价来最大化性能 - 使自身容易受到 DOS 攻击。 */ private boolean sipCacheServerConnections = true; /** * 禁用date头,变相禁用了校时 */ private boolean disableDateHeader = false; /** * 同步业务分组时自动生成分组国标编号的模板,不配置则默认参考当前的sip域信息生成 */ private String groupSyncDeviceTemplate; /** * 与第三方进行分组同步时使用别名而不是分组ID, 如果没有设置此项为true,那么分组编号就是必须传递的。如果是设置为true则,自动为别名的分组生成新的编号 */ private boolean useAliasForGroupSync = false; /** * 设备ID严格模式,开启后设备注册时如果设备ID不符合规范则拒绝注册, 默认开启 */ private boolean deviceIdStrict = true; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/VersionConfig.java ================================================ package com.genersoft.iot.vmp.conf; import org.springframework.core.annotation.Order; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Component @ConfigurationProperties(prefix = "version") @Order(0) public class VersionConfig { private String version; private String artifactId; private String description; public void setVersion(String version) { this.version = version; } public void setArtifactId(String artifactId) { this.artifactId = artifactId; } public void setDescription(String description) { this.description = description; } public String getVersion() { return version; } public String getArtifactId() { return artifactId; } public String getDescription() { return description; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/VersionInfo.java ================================================ package com.genersoft.iot.vmp.conf; import com.genersoft.iot.vmp.common.VersionPo; import com.genersoft.iot.vmp.utils.GitUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class VersionInfo { @Autowired GitUtil gitUtil; public VersionPo getVersion() { VersionPo versionPo = new VersionPo(); versionPo.setGIT_Revision(gitUtil.getGitCommitId()); versionPo.setGIT_BRANCH(gitUtil.getBranch()); versionPo.setGIT_URL(gitUtil.getGitUrl()); versionPo.setBUILD_DATE(gitUtil.getBuildDate()); versionPo.setGIT_Revision_SHORT(gitUtil.getCommitIdShort()); versionPo.setVersion(gitUtil.getBuildVersion()); versionPo.setGIT_DATE(gitUtil.getCommitTime()); return versionPo; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/WVPTimerTask.java ================================================ package com.genersoft.iot.vmp.conf; import com.genersoft.iot.vmp.common.ServerInfo; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.util.concurrent.TimeUnit; @Component public class WVPTimerTask { @Autowired private IRedisCatchStorage redisCatchStorage; @Value("${server.port}") private int serverPort; @Autowired private SipConfig sipConfig; @Scheduled(fixedDelay = 2, timeUnit = TimeUnit.SECONDS) //每3秒执行一次 public void execute(){ redisCatchStorage.updateWVPInfo(ServerInfo.create(sipConfig.getShowIp(), serverPort), 3); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/exception/ControllerException.java ================================================ package com.genersoft.iot.vmp.conf.exception; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; /** * 自定义异常,controller出现错误时直接抛出异常由全局异常捕获并返回结果 */ public class ControllerException extends RuntimeException{ private int code; private String msg; public ControllerException(int code, String msg) { this.code = code; this.msg = msg; } public ControllerException(ErrorCode errorCode) { this.code = errorCode.getCode(); this.msg = errorCode.getMsg(); } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/exception/ServiceException.java ================================================ package com.genersoft.iot.vmp.conf.exception; /** * @author lin */ public class ServiceException extends Exception{ private String msg; public ServiceException(String msg) { this.msg = msg; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } @Override public String getMessage() { return msg; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/exception/SsrcTransactionNotFoundException.java ================================================ package com.genersoft.iot.vmp.conf.exception; /** * @author lin */ public class SsrcTransactionNotFoundException extends Exception{ private String deviceId; private String channelId; private String callId; private String stream; public SsrcTransactionNotFoundException(String deviceId, String channelId, String callId, String stream) { this.deviceId = deviceId; this.channelId = channelId; this.callId = callId; this.stream = stream; } public String getDeviceId() { return deviceId; } public String getChannelId() { return channelId; } public String getCallId() { return callId; } public String getStream() { return stream; } @Override public String getMessage() { StringBuffer msg = new StringBuffer(); msg.append(String.format("缓存事务信息未找到,device:%s channel: %s ", deviceId, channelId)); if (callId != null) { msg.append(",callId: " + callId); } if (stream != null) { msg.append(",stream: " + stream); } return msg.toString(); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/ftpServer/FileCallback.java ================================================ package com.genersoft.iot.vmp.conf.ftpServer; import java.io.OutputStream; public interface FileCallback { OutputStream run(String path); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/ftpServer/FtpAuthority.java ================================================ package com.genersoft.iot.vmp.conf.ftpServer; import org.apache.ftpserver.ftplet.Authority; import org.apache.ftpserver.ftplet.AuthorizationRequest; public class FtpAuthority implements Authority { @Override public boolean canAuthorize(AuthorizationRequest authorizationRequest) { return true; } @Override public AuthorizationRequest authorize(AuthorizationRequest authorizationRequest) { return authorizationRequest; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/ftpServer/FtpFileSystemFactory.java ================================================ package com.genersoft.iot.vmp.conf.ftpServer; import org.apache.ftpserver.ftplet.FileSystemFactory; import org.apache.ftpserver.ftplet.FileSystemView; import org.apache.ftpserver.ftplet.FtpException; import org.apache.ftpserver.ftplet.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.io.OutputStream; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @Component public class FtpFileSystemFactory implements FileSystemFactory { private final Map outputStreamMap = new ConcurrentHashMap<>(); @Override public FileSystemView createFileSystemView(User user) throws FtpException { return new FtpFileSystemView(user, path -> { return outputStreamMap.get(path); }); } public void addOutputStream(String filePath, OutputStream outputStream) { outputStreamMap.put(filePath, outputStream); } public void removeOutputStream(String filePath) { outputStreamMap.remove(filePath); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/ftpServer/FtpFileSystemView.java ================================================ package com.genersoft.iot.vmp.conf.ftpServer; import org.apache.ftpserver.ftplet.FileSystemView; import org.apache.ftpserver.ftplet.FtpException; import org.apache.ftpserver.ftplet.FtpFile; import org.apache.ftpserver.ftplet.User; import java.io.OutputStream; public class FtpFileSystemView implements FileSystemView { private User user; private FileCallback fileCallback; public FtpFileSystemView(User user, FileCallback fileCallback) { this.user = user; this.fileCallback = fileCallback; } public static String HOME_PATH = "root"; public FtpFile workDir = VirtualFtpFile.getDir(HOME_PATH); @Override public FtpFile getHomeDirectory() throws FtpException { return VirtualFtpFile.getDir(HOME_PATH); } @Override public FtpFile getWorkingDirectory() throws FtpException { return workDir; } @Override public boolean changeWorkingDirectory(String dir) throws FtpException { workDir = VirtualFtpFile.getDir(dir); return true; } @Override public FtpFile getFile(String file) throws FtpException { VirtualFtpFile ftpFile = VirtualFtpFile.getFile(file); if (fileCallback != null) { OutputStream outputStream = fileCallback.run(workDir.getName()); if (outputStream != null) { ftpFile.setOutputStream(outputStream); } } return ftpFile; } @Override public boolean isRandomAccessible() throws FtpException { return true; } @Override public void dispose() { } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/ftpServer/FtpServerConfig.java ================================================ package com.genersoft.iot.vmp.conf.ftpServer; import lombok.extern.slf4j.Slf4j; import org.apache.ftpserver.*; import org.apache.ftpserver.ftplet.FtpException; import org.apache.ftpserver.listener.Listener; import org.apache.ftpserver.listener.ListenerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.HashMap; import java.util.Map; @Configuration @ConditionalOnProperty(value = "ftp.enable", havingValue = "true") @Slf4j public class FtpServerConfig { @Autowired private UserManager userManager; @Autowired private FtpFileSystemFactory fileSystemFactory; @Autowired private Ftplet ftplet; @Autowired private FtpSetting ftpSetting; /** * ftp server init */ @Bean public FtpServer ftpServer() { FtpServerFactory serverFactory = new FtpServerFactory(); ListenerFactory listenerFactory = new ListenerFactory(); // 1、设置服务端口 listenerFactory.setPort(ftpSetting.getPort()); // 2、设置被动模式数据上传的接口范围,云服务器需要开放对应区间的端口给客户端 DataConnectionConfigurationFactory dataConnectionConfFactory = new DataConnectionConfigurationFactory(); dataConnectionConfFactory.setPassivePorts(ftpSetting.getPassivePorts()); listenerFactory.setDataConnectionConfiguration(dataConnectionConfFactory.createDataConnectionConfiguration()); // 4、替换默认的监听器 Listener listener = listenerFactory.createListener(); serverFactory.addListener("default", listener); // 5、配置自定义用户事件 Map ftpLets = new HashMap<>(); ftpLets.put("ftpService", ftplet); serverFactory.setFtplets(ftpLets); // 6、读取用户的配置信息 // 6.2、设置用信息 serverFactory.setUserManager(userManager); serverFactory.setFileSystem(fileSystemFactory); // 7、实例化FTP Server FtpServer server = serverFactory.createServer(); try { server.start(); if (!server.isStopped()) { log.info("[FTP服务] 已启动, 端口: {}", ftpSetting.getPort()); } } catch (FtpException e) { log.info("[FTP服务] 启动失败 ", e); } return server; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/ftpServer/FtpSetting.java ================================================ package com.genersoft.iot.vmp.conf.ftpServer; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** * 配置文件 user-settings 映射的配置信息 */ @Component @ConfigurationProperties(prefix = "ftp", ignoreInvalidFields = true) @Order(0) @Data public class FtpSetting { private Boolean enable = Boolean.FALSE; private int port = 21; private String passivePorts = "10000-10500"; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/ftpServer/Ftplet.java ================================================ package com.genersoft.iot.vmp.conf.ftpServer; import com.genersoft.iot.vmp.jt1078.event.FtpUploadEvent; import org.apache.ftpserver.ftplet.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Component; import java.io.IOException; @Component public class Ftplet extends DefaultFtplet { private final Logger logger = LoggerFactory.getLogger(Ftplet.class); @Autowired private ApplicationEventPublisher applicationEventPublisher; @Override public FtpletResult onUploadEnd(FtpSession session, FtpRequest request) throws FtpException, IOException { FtpFile file = session.getFileSystemView().getFile(request.getArgument()); if (file == null) { return super.onUploadEnd(session, request); } sendEvent(file.getAbsolutePath()); return super.onUploadUniqueEnd(session, request); } @Override public FtpletResult onAppendEnd(FtpSession session, FtpRequest request) throws FtpException, IOException { FtpFile file = session.getFileSystemView().getFile(request.getArgument()); if (file == null) { return super.onUploadEnd(session, request); } sendEvent(file.getAbsolutePath()); return super.onUploadUniqueEnd(session, request); } @Override public FtpletResult onUploadUniqueEnd(FtpSession session, FtpRequest request) throws FtpException, IOException { FtpFile file = session.getFileSystemView().getFile(request.getArgument()); if (file == null) { return super.onUploadEnd(session, request); } sendEvent(file.getAbsolutePath()); return super.onUploadUniqueEnd(session, request); } private void sendEvent(String filePath){ FtpUploadEvent event = new FtpUploadEvent(this); logger.info("[文件已上传]: {}", filePath); event.setFileName(filePath); applicationEventPublisher.publishEvent(event); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/ftpServer/UserManager.java ================================================ package com.genersoft.iot.vmp.conf.ftpServer; import org.apache.commons.lang3.RandomStringUtils; import org.apache.ftpserver.ftplet.*; import org.apache.ftpserver.usermanager.UsernamePasswordAuthentication; import org.apache.ftpserver.usermanager.impl.BaseUser; import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import java.io.File; import java.time.Duration; import java.util.ArrayList; import java.util.List; @Component public class UserManager implements org.apache.ftpserver.ftplet.UserManager { private static final String PREFIX = "VMP_FTP_USER_"; @Autowired private RedisTemplate redisTemplate; @Override public User getUserByName(String username) throws FtpException { return (BaseUser)redisTemplate.opsForValue().get(PREFIX + username); } @Override public String[] getAllUserNames() throws FtpException { return new String[0]; } @Override public void delete(String username) throws FtpException { } @Override public void save(User user) throws FtpException {} @Override public boolean doesExist(String username) throws FtpException { return redisTemplate.opsForValue().get(PREFIX + username) != null; } @Override public User authenticate(Authentication authentication) throws AuthenticationFailedException { UsernamePasswordAuthentication usernamePasswordAuthentication = (UsernamePasswordAuthentication) authentication; BaseUser user = (BaseUser)redisTemplate.opsForValue().get(PREFIX + usernamePasswordAuthentication.getUsername()); if (user != null && usernamePasswordAuthentication.getPassword().equals(user.getPassword())) { return user; } return null; } @Override public String getAdminName() throws FtpException { return null; } @Override public boolean isAdmin(String username) throws FtpException { return false; } public BaseUser getRandomUser(){ BaseUser use = new BaseUser(); use.setName(RandomStringUtils.randomAlphabetic(6).toLowerCase()); use.setPassword(RandomStringUtils.randomAlphabetic(6).toLowerCase()); use.setEnabled(true); use.setHomeDirectory("/"); List authorities = new ArrayList<>(); authorities.add(new FtpAuthority()); use.setAuthorities(authorities); String key = PREFIX + use.getName(); // 随机用户信息十分钟自动失效 Duration duration = Duration.ofMinutes(10); redisTemplate.opsForValue().set(key, use, duration); return use; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/ftpServer/VirtualFtpFile.java ================================================ package com.genersoft.iot.vmp.conf.ftpServer; import lombok.Setter; import org.apache.ftpserver.ftplet.FtpFile; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Collections; import java.util.List; public class VirtualFtpFile implements FtpFile { @Setter private String name; @Setter private boolean hidden = false; @Setter private boolean directory = false; @Setter private String ownerName; private Long lastModified = null; @Setter private long size = 0; @Setter private OutputStream outputStream; public static VirtualFtpFile getFile(String name) { VirtualFtpFile virtualFtpFile = new VirtualFtpFile(); virtualFtpFile.setName(name); return virtualFtpFile; } public static VirtualFtpFile getDir(String name) { if (name.endsWith("/")) { name = name.replaceAll("/", ""); } VirtualFtpFile virtualFtpFile = new VirtualFtpFile(); virtualFtpFile.setName(name); virtualFtpFile.setDirectory(true); return virtualFtpFile; } @Override public String getAbsolutePath() { return FtpFileSystemView.HOME_PATH + "/" + name; } @Override public String getName() { return name; } @Override public boolean isHidden() { return hidden; } @Override public boolean isDirectory() { return directory; } @Override public boolean isFile() { return !directory; } @Override public boolean doesExist() { return false; } @Override public boolean isReadable() { return true; } @Override public boolean isWritable() { return true; } @Override public boolean isRemovable() { return true; } @Override public String getOwnerName() { return ownerName; } @Override public String getGroupName() { return "root"; } @Override public int getLinkCount() { return 0; } @Override public long getLastModified() { if (lastModified == null) { lastModified = System.currentTimeMillis(); } return lastModified; } @Override public boolean setLastModified(long time) { lastModified = time; return true; } @Override public long getSize() { return size; } @Override public Object getPhysicalFile() { System.err.println("getPhysicalFile"); return null; } @Override public boolean mkdir() { return true; } @Override public boolean delete() { return true; } @Override public boolean move(FtpFile destination) { this.name = destination.getName(); return true; } @Override public List listFiles() { return Collections.emptyList(); } @Override public OutputStream createOutputStream(long offset) throws IOException { return outputStream; } @Override public InputStream createInputStream(long offset) throws IOException { System.out.println("createInputStream----"); return null; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/redis/RedisMsgListenConfig.java ================================================ package com.genersoft.iot.vmp.conf.redis; import com.genersoft.iot.vmp.common.VideoManagerConstants; import com.genersoft.iot.vmp.service.redisMsg.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.listener.PatternTopic; import org.springframework.data.redis.listener.RedisMessageListenerContainer; /** * @description:Redis中间件配置类,使用spring-data-redis集成,自动从application.yml中加载redis配置 * @author: swwheihei * @date: 2019年5月30日 上午10:58:25 * */ @Configuration @Order(value=1) public class RedisMsgListenConfig { @Autowired private RedisGpsMsgListener redisGPSMsgListener; @Autowired private RedisAlarmMsgListener redisAlarmMsgListener; @Autowired private RedisPushStreamStatusMsgListener redisPushStreamStatusMsgListener; @Autowired private RedisPushStreamListMsgListener pushStreamListMsgListener; @Autowired private RedisGroupMsgListener groupMsgListener; @Autowired private RedisGroupChangeListener groupChangeListener; @Autowired private RedisCloseStreamMsgListener redisCloseStreamMsgListener; @Autowired private RedisRpcConfig redisRpcConfig; @Autowired private RedisPushStreamResponseListener redisPushStreamCloseResponseListener; /** * redis消息监听器容器 可以添加多个监听不同话题的redis监听器,只需要把消息监听器和相应的消息订阅处理器绑定,该消息监听器 * 通过反射技术调用消息订阅处理器的相关方法进行一些业务处理 * * @param connectionFactory * @return */ @Bean RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) { RedisMessageListenerContainer container = new RedisMessageListenerContainer(); container.setConnectionFactory(connectionFactory); container.addMessageListener(redisGPSMsgListener, new PatternTopic(VideoManagerConstants.VM_MSG_GPS)); container.addMessageListener(redisAlarmMsgListener, new PatternTopic(VideoManagerConstants.VM_MSG_SUBSCRIBE_ALARM_RECEIVE)); container.addMessageListener(redisPushStreamStatusMsgListener, new PatternTopic(VideoManagerConstants.VM_MSG_PUSH_STREAM_STATUS_CHANGE)); container.addMessageListener(pushStreamListMsgListener, new PatternTopic(VideoManagerConstants.VM_MSG_PUSH_STREAM_LIST_CHANGE)); container.addMessageListener(redisCloseStreamMsgListener, new PatternTopic(VideoManagerConstants.VM_MSG_STREAM_PUSH_CLOSE)); container.addMessageListener(redisRpcConfig, new PatternTopic(RedisRpcConfig.REDIS_REQUEST_CHANNEL_KEY)); container.addMessageListener(redisPushStreamCloseResponseListener, new PatternTopic(VideoManagerConstants.VM_MSG_STREAM_PUSH_RESPONSE)); container.addMessageListener(groupMsgListener, new PatternTopic(VideoManagerConstants.VM_MSG_GROUP_LIST_RESPONSE)); container.addMessageListener(groupChangeListener, new PatternTopic(VideoManagerConstants.VM_MSG_GROUP_LIST_CHANGE)); return container; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/redis/RedisRpcConfig.java ================================================ package com.genersoft.iot.vmp.conf.redis; import com.alibaba.fastjson2.JSON; import com.genersoft.iot.vmp.common.CommonCallback; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcClassHandler; import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcMessage; import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcRequest; import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcResponse; import com.genersoft.iot.vmp.service.redisMsg.dto.RpcController; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.data.redis.connection.Message; import org.springframework.data.redis.connection.MessageListener; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Component; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import java.util.Random; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.TimeUnit; @Slf4j @Component public class RedisRpcConfig implements MessageListener { public final static String REDIS_REQUEST_CHANNEL_KEY = "WVP_REDIS_REQUEST_CHANNEL_KEY"; private final Random random = new Random(); @Autowired private UserSetting userSetting; @Autowired private RedisTemplate redisTemplate; private ConcurrentLinkedQueue taskQueue = new ConcurrentLinkedQueue<>(); @Qualifier("taskExecutor") @Autowired private ThreadPoolTaskExecutor taskExecutor; private final static Map protocolHash = new HashMap<>(); public void addHandler(String path, RedisRpcClassHandler handler) { protocolHash.put(path, handler); } // @Override // public void run(String... args) throws Exception { // List> classList = ClassUtil.getClassList("com.genersoft.iot.vmp.service.redisMsg.control", RedisRpcController.class); // for (Class handlerClass : classList) { // String controllerPath = handlerClass.getAnnotation(RedisRpcController.class).value(); // Object bean = ClassUtil.getBean(controllerPath, handlerClass); // // 扫描其下的方法 // Method[] methods = handlerClass.getDeclaredMethods(); // for (Method method : methods) { // RedisRpcMapping annotation = method.getAnnotation(RedisRpcMapping.class); // if (annotation != null) { // String methodPath = annotation.value(); // if (methodPath != null) { // protocolHash.put(controllerPath + "/" + methodPath, new RedisRpcClassHandler(bean, method)); // } // } // // } // // } // for (String s : protocolHash.keySet()) { // System.out.println(s); // } // if (log.isDebugEnabled()) { // log.debug("消息ID缓存表 protocolHash:{}", protocolHash); // } // } @Override public void onMessage(Message message, byte[] pattern) { boolean isEmpty = taskQueue.isEmpty(); taskQueue.offer(message); if (isEmpty) { taskExecutor.execute(() -> { while (!taskQueue.isEmpty()) { Message msg = taskQueue.poll(); try { RedisRpcMessage redisRpcMessage = JSON.parseObject(new String(msg.getBody()), RedisRpcMessage.class); if (redisRpcMessage.getRequest() != null) { handlerRequest(redisRpcMessage.getRequest()); } else if (redisRpcMessage.getResponse() != null){ handlerResponse(redisRpcMessage.getResponse()); } else { log.error("[redis-rpc]解析失败 {}", JSON.toJSONString(redisRpcMessage)); } } catch (Exception e) { log.error("[redis-rpc]解析异常 {}",new String(msg.getBody()), e); } } }); } } private void handlerResponse(RedisRpcResponse response) { if (userSetting.getServerId().equals(response.getToId())) { return; } log.info("[redis-rpc] << {}", response); response(response); } private void handlerRequest(RedisRpcRequest request) { try { if (userSetting.getServerId().equals(request.getFromId())) { return; } log.info("[redis-rpc] << {}", request); RedisRpcClassHandler redisRpcClassHandler = protocolHash.get(request.getUri()); if (redisRpcClassHandler == null) { log.error("[redis-rpc] 路径: {}不存在", request.getUri()); return; } RpcController controller = redisRpcClassHandler.getController(); Method method = redisRpcClassHandler.getMethod(); // 没有携带目标ID的可以理解为哪个wvp有结果就哪个回复,携带目标ID,但是如果是不存在的uri则直接回复404 if (userSetting.getServerId().equals(request.getToId())) { if (method == null) { // 回复404结果 RedisRpcResponse response = request.getResponse(); response.setStatusCode(ErrorCode.ERROR404.getCode()); sendResponse(response); return; } RedisRpcResponse response = (RedisRpcResponse)method.invoke(controller, request); if(response != null) { sendResponse(response); } }else { if (method == null) { // 回复404结果 RedisRpcResponse response = request.getResponse(); response.setStatusCode(ErrorCode.ERROR404.getCode()); sendResponse(response); return; } RedisRpcResponse response = (RedisRpcResponse)method.invoke(controller, request); if (response != null) { sendResponse(response); } } }catch (Exception e) { log.error("[redis-rpc ] 处理请求失败 ", e); RedisRpcResponse response = request.getResponse(); response.setStatusCode(ErrorCode.ERROR100.getCode()); sendResponse(response); } } private void sendResponse(RedisRpcResponse response){ log.info("[redis-rpc] >> {}", response); response.setToId(userSetting.getServerId()); RedisRpcMessage message = new RedisRpcMessage(); message.setResponse(response); redisTemplate.convertAndSend(REDIS_REQUEST_CHANNEL_KEY, message); } private void sendRequest(RedisRpcRequest request){ log.info("[redis-rpc] >> {}", request); RedisRpcMessage message = new RedisRpcMessage(); message.setRequest(request); redisTemplate.convertAndSend(REDIS_REQUEST_CHANNEL_KEY, message); } private final Map> topicSubscribers = new ConcurrentHashMap<>(); private final Map> callbacks = new ConcurrentHashMap<>(); public RedisRpcResponse request(RedisRpcRequest request, long timeOut) { return request(request, timeOut, TimeUnit.SECONDS); } public RedisRpcResponse request(RedisRpcRequest request, long timeOut, TimeUnit timeUnit) { request.setSn((long) random.nextInt(1000) + 1); SynchronousQueue subscribe = subscribe(request.getSn()); try { sendRequest(request); return subscribe.poll(timeOut, timeUnit); } catch (InterruptedException e) { log.warn("[redis rpc timeout] uri: {}, sn: {}", request.getUri(), request.getSn(), e); RedisRpcResponse redisRpcResponse = new RedisRpcResponse(); redisRpcResponse.setStatusCode(ErrorCode.ERROR486.getCode()); return redisRpcResponse; } finally { this.unsubscribe(request.getSn()); } } public void request(RedisRpcRequest request, CommonCallback callback) { request.setSn((long) random.nextInt(1000) + 1); setCallback(request.getSn(), callback); sendRequest(request); } public Boolean response(RedisRpcResponse response) { SynchronousQueue queue = topicSubscribers.get(response.getSn()); CommonCallback callback = callbacks.get(response.getSn()); if (queue != null) { try { return queue.offer(response, 2, TimeUnit.SECONDS); } catch (InterruptedException e) { log.error("{}", e.getMessage(), e); } }else if (callback != null) { callback.run(response); callbacks.remove(response.getSn()); } return false; } private void unsubscribe(long key) { topicSubscribers.remove(key); } private SynchronousQueue subscribe(long key) { SynchronousQueue queue = null; if (!topicSubscribers.containsKey(key)) topicSubscribers.put(key, queue = new SynchronousQueue<>()); return queue; } private void setCallback(long key, CommonCallback callback) { // TODO 如果多个上级点播同一个通道会有问题 callbacks.put(key, callback); } public void removeCallback(long key) { callbacks.remove(key); } public int getCallbackCount(){ return callbacks.size(); } // @Scheduled(fixedRate = 1000) //每1秒执行一次 // public void execute(){ // logger.info("callbacks的长度: " + callbacks.size()); // logger.info("队列的长度: " + topicSubscribers.size()); // logger.info("HOOK监听的长度: " + hookSubscribe.size()); // logger.info(""); // } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/redis/RedisTemplateConfig.java ================================================ package com.genersoft.iot.vmp.conf.redis; import com.alibaba.fastjson2.support.spring.data.redis.GenericFastJsonRedisSerializer; import com.genersoft.iot.vmp.gb28181.bean.MobilePosition; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration public class RedisTemplateConfig { @Bean public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate redisTemplate = new RedisTemplate<>(); // 使用fastJson序列化 GenericFastJsonRedisSerializer fastJsonRedisSerializer = new GenericFastJsonRedisSerializer(); // value值的序列化采用fastJsonRedisSerializer redisTemplate.setValueSerializer(fastJsonRedisSerializer); redisTemplate.setHashValueSerializer(fastJsonRedisSerializer); // key的序列化采用StringRedisSerializer redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); redisTemplate.setConnectionFactory(redisConnectionFactory); return redisTemplate; } @Bean public RedisTemplate getRedisTemplateForMobilePosition(RedisConnectionFactory redisConnectionFactory) { RedisTemplate redisTemplate = new RedisTemplate<>(); // 使用fastJson序列化 GenericFastJsonRedisSerializer fastJsonRedisSerializer = new GenericFastJsonRedisSerializer(); // value值的序列化采用fastJsonRedisSerializer redisTemplate.setValueSerializer(fastJsonRedisSerializer); redisTemplate.setHashValueSerializer(fastJsonRedisSerializer); // key的序列化采用StringRedisSerializer redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); redisTemplate.setConnectionFactory(redisConnectionFactory); return redisTemplate; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/redis/bean/RedisRpcClassHandler.java ================================================ package com.genersoft.iot.vmp.conf.redis.bean; import com.genersoft.iot.vmp.service.redisMsg.dto.RpcController; import lombok.Data; import java.lang.reflect.Method; @Data public class RedisRpcClassHandler { private RpcController controller; private Method method; public RedisRpcClassHandler(RpcController controller, Method method) { this.controller = controller; this.method = method; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/redis/bean/RedisRpcMessage.java ================================================ package com.genersoft.iot.vmp.conf.redis.bean; public class RedisRpcMessage { private RedisRpcRequest request; private RedisRpcResponse response; public RedisRpcRequest getRequest() { return request; } public void setRequest(RedisRpcRequest request) { this.request = request; } public RedisRpcResponse getResponse() { return response; } public void setResponse(RedisRpcResponse response) { this.response = response; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/redis/bean/RedisRpcRequest.java ================================================ package com.genersoft.iot.vmp.conf.redis.bean; /** * 通过redis发送请求 */ public class RedisRpcRequest { /** * 来自的WVP ID */ private String fromId; /** * 目标的WVP ID */ private String toId; /** * 序列号 */ private long sn; /** * 访问的路径 */ private String uri; /** * 参数 */ private Object param; public String getFromId() { return fromId; } public void setFromId(String fromId) { this.fromId = fromId; } public String getToId() { return toId; } public void setToId(String toId) { this.toId = toId; } public String getUri() { return uri; } public void setUri(String uri) { this.uri = uri; } public Object getParam() { return param; } public void setParam(Object param) { this.param = param; } public long getSn() { return sn; } public void setSn(long sn) { this.sn = sn; } @Override public String toString() { return "RedisRpcRequest{" + "uri='" + uri + '\'' + ", fromId='" + fromId + '\'' + ", toId='" + toId + '\'' + ", sn=" + sn + ", param=" + param + '}'; } public RedisRpcResponse getResponse() { RedisRpcResponse response = new RedisRpcResponse(); response.setFromId(fromId); response.setToId(toId); response.setSn(sn); response.setUri(uri); return response; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/redis/bean/RedisRpcResponse.java ================================================ package com.genersoft.iot.vmp.conf.redis.bean; /** * 通过redis发送回复 */ public class RedisRpcResponse { /** * 来自的WVP ID */ private String fromId; /** * 目标的WVP ID */ private String toId; /** * 序列号 */ private long sn; /** * 状态码 */ private int statusCode; /** * 访问的路径 */ private String uri; /** * 参数 */ private Object body; public String getFromId() { return fromId; } public void setFromId(String fromId) { this.fromId = fromId; } public String getToId() { return toId; } public void setToId(String toId) { this.toId = toId; } public long getSn() { return sn; } public void setSn(long sn) { this.sn = sn; } public int getStatusCode() { return statusCode; } public void setStatusCode(int statusCode) { this.statusCode = statusCode; } public String getUri() { return uri; } public void setUri(String uri) { this.uri = uri; } public Object getBody() { return body; } public void setBody(Object body) { this.body = body; } @Override public String toString() { return "RedisRpcResponse{" + "uri='" + uri + '\'' + ", fromId='" + fromId + '\'' + ", toId='" + toId + '\'' + ", sn=" + sn + ", statusCode=" + statusCode + ", body=" + body + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/security/AnonymousAuthenticationEntryPoint.java ================================================ package com.genersoft.iot.vmp.conf.security; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.conf.security.dto.JwtUser; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import org.springframework.http.MediaType; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.stereotype.Component; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.nio.charset.StandardCharsets; /** * 处理匿名用户访问逻辑 * @author lin */ @Component public class AnonymousAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) { String jwt = request.getHeader(JwtUtils.getHeader()); JwtUser jwtUser = JwtUtils.verifyToken(jwt); String username = jwtUser.getUserName(); UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, jwtUser.getPassword() ); SecurityContextHolder.getContext().setAuthentication(token); JSONObject jsonObject = new JSONObject(); jsonObject.put("code", ErrorCode.ERROR401.getCode()); jsonObject.put("msg", ErrorCode.ERROR401.getMsg()); String logUri = "api/user/login"; if (request.getRequestURI().contains(logUri)){ jsonObject.put("msg", e.getMessage()); } response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.setCharacterEncoding(StandardCharsets.UTF_8.name()); try { response.getWriter().print(jsonObject.toJSONString()); } catch (IOException ioException) { ioException.printStackTrace(); } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/security/DefaultUserDetailsServiceImpl.java ================================================ package com.genersoft.iot.vmp.conf.security; import com.alibaba.excel.util.StringUtils; import com.genersoft.iot.vmp.conf.security.dto.LoginUser; import com.genersoft.iot.vmp.service.IUserService; import com.genersoft.iot.vmp.storager.dao.dto.User; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Component; import java.time.LocalDateTime; /** * 用户登录认证逻辑 */ @Slf4j @Component public class DefaultUserDetailsServiceImpl implements UserDetailsService { @Autowired private IUserService userService; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { if (StringUtils.isBlank(username)) { log.info("登录用户:{} 不存在", username); throw new UsernameNotFoundException("登录用户:" + username + " 不存在"); } // 查出密码 User user = userService.getUserByUsername(username); if (user == null) { log.info("登录用户:{} 不存在", username); throw new UsernameNotFoundException("登录用户:" + username + " 不存在"); } String password = SecurityUtils.encryptPassword(user.getPassword()); user.setPassword(password); return new LoginUser(user, LocalDateTime.now()); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/security/JwtAuthenticationFilter.java ================================================ package com.genersoft.iot.vmp.conf.security; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.security.dto.JwtUser; import com.genersoft.iot.vmp.storager.dao.dto.Role; import com.genersoft.iot.vmp.storager.dao.dto.User; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.util.ContentCachingRequestWrapper; import java.io.IOException; import java.util.ArrayList; /** * jwt token 过滤器 */ @Slf4j @Component public class JwtAuthenticationFilter extends OncePerRequestFilter { private final static String WSHeader = "sec-websocket-protocol"; @Autowired private UserSetting userSetting; @Override protected void doFilterInternal(HttpServletRequest servletRequest, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { ContentCachingRequestWrapper request = new ContentCachingRequestWrapper(servletRequest); // 忽略登录请求的token验证 String requestURI = request.getRequestURI(); if ((requestURI.startsWith("/doc.html") || requestURI.startsWith("/swagger-ui") ) && !userSetting.getDocEnable()) { response.setStatus(HttpServletResponse.SC_NOT_FOUND); return; } if (requestURI.equalsIgnoreCase("/api/user/login")) { chain.doFilter(request, response); return; } if (!userSetting.getInterfaceAuthentication()) { UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(null, null, new ArrayList<>() ); SecurityContextHolder.getContext().setAuthentication(token); chain.doFilter(request, response); return; } String jwt = request.getHeader(JwtUtils.getHeader()); // 这里如果没有jwt,继续往后走,因为后面还有鉴权管理器等去判断是否拥有身份凭证,所以是可以放行的 // 没有jwt相当于匿名访问,若有一些接口是需要权限的,则不能访问这些接口 // websocket 鉴权信息默认存储在这里 String secWebsocketProtocolHeader = request.getHeader(WSHeader); if (StringUtils.isBlank(jwt)) { if (secWebsocketProtocolHeader != null) { jwt = secWebsocketProtocolHeader; response.setHeader(WSHeader, secWebsocketProtocolHeader); }else { jwt = request.getParameter(JwtUtils.getHeader()); } if (StringUtils.isBlank(jwt)) { jwt = request.getHeader(JwtUtils.getApiKeyHeader()); if (StringUtils.isBlank(jwt)) { chain.doFilter(request, response); return; } } } JwtUser jwtUser = JwtUtils.verifyToken(jwt); String username = jwtUser.getUserName(); // TODO 处理各个状态 switch (jwtUser.getStatus()){ case EXPIRED: response.setStatus(401); chain.doFilter(request, response); // 异常 return; case EXCEPTION: // 过期 response.setStatus(400); chain.doFilter(request, response); return; case EXPIRING_SOON: // 即将过期 // return; default: } // 构建UsernamePasswordAuthenticationToken,这里密码为null,是因为提供了正确的JWT,实现自动登录 User user = new User(); user.setId(jwtUser.getUserId()); user.setUsername(jwtUser.getUserName()); user.setPassword(jwtUser.getPassword()); Role role = new Role(); role.setId(jwtUser.getRoleId()); user.setRole(role); UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(user, jwtUser.getPassword(), new ArrayList<>() ); SecurityContextHolder.getContext().setAuthentication(token); chain.doFilter(request, response); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/security/JwtUtils.java ================================================ package com.genersoft.iot.vmp.conf.security; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.security.dto.JwtUser; import com.genersoft.iot.vmp.service.IUserApiKeyService; import com.genersoft.iot.vmp.service.IUserService; import com.genersoft.iot.vmp.storager.dao.dto.User; import com.genersoft.iot.vmp.storager.dao.dto.UserApiKey; import lombok.extern.slf4j.Slf4j; import org.jose4j.jwk.JsonWebKey; import org.jose4j.jwk.JsonWebKeySet; import org.jose4j.jwk.RsaJsonWebKey; import org.jose4j.jwk.RsaJwkGenerator; import org.jose4j.jws.AlgorithmIdentifiers; import org.jose4j.jws.JsonWebSignature; import org.jose4j.jwt.JwtClaims; import org.jose4j.jwt.NumericDate; import org.jose4j.jwt.consumer.ErrorCodes; import org.jose4j.jwt.consumer.InvalidJwtException; import org.jose4j.jwt.consumer.JwtConsumer; import org.jose4j.jwt.consumer.JwtConsumerBuilder; import org.jose4j.lang.JoseException; import org.springframework.beans.factory.InitializingBean; import org.springframework.core.io.ClassPathResource; import org.springframework.stereotype.Component; import jakarta.annotation.Resource; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.time.LocalDateTime; import java.time.ZoneOffset; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @Slf4j @Component public class JwtUtils implements InitializingBean { public static final String HEADER = "access-token"; public static final String API_KEY_HEADER = "api-key"; private static final String AUDIENCE = "Audience"; private static final String keyId = "3e79646c4dbc408383a9eed09f2b85ae"; /** * token过期时间(分钟) */ public static final long EXPIRATION_TIME = 30; private static RsaJsonWebKey rsaJsonWebKey; private static IUserService userService; private static IUserApiKeyService userApiKeyService; private static UserSetting userSetting; public static String getApiKeyHeader() { return API_KEY_HEADER; } @Resource public void setUserService(IUserService userService) { JwtUtils.userService = userService; } @Resource public void setUserApiKeyService(IUserApiKeyService userApiKeyService) { JwtUtils.userApiKeyService = userApiKeyService; } @Resource public void setUserSetting(UserSetting userSetting) { JwtUtils.userSetting = userSetting; } @Override public void afterPropertiesSet() { try { rsaJsonWebKey = generateRsaJsonWebKey(); } catch (JoseException e) { log.error("生成RsaJsonWebKey报错。", e); } } /** * 创建密钥对(修复所有bug+classpath警告+密钥持久化) */ private RsaJsonWebKey generateRsaJsonWebKey() throws JoseException { // 前置校验:避免空指针(防止userSetting未初始化或jwkFile未配置) if (userSetting == null) { log.error("[API AUTH] userSetting 未初始化!"); return createDefaultRsaKey(); } String jwkFile = userSetting.getJwkFile(); if (jwkFile == null || jwkFile.trim().isEmpty()) { log.error("[API AUTH] JWK文件路径未配置!"); return createDefaultRsaKey(); } // 尝试读取JWK文件(自动处理classpath/本地文件,用try-with-resources自动关流,无泄露) try (InputStream inputStream = getJwkInputStream(jwkFile); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { // 读取JSON(不跳过任何行,修复原bug) String jwkJson = reader.lines().collect(Collectors.joining()); JsonWebKeySet jsonWebKeySet = new JsonWebKeySet(jwkJson); List jsonWebKeys = jsonWebKeySet.getJsonWebKeys(); // 筛选:取第一个有效的RSA私钥(签名需要私钥,避免后续报错) for (JsonWebKey jsonWebKey : jsonWebKeys) { if (jsonWebKey instanceof RsaJsonWebKey) { RsaJsonWebKey rsaKey = (RsaJsonWebKey) jsonWebKey; // 校验是否包含私钥 if (rsaKey.getPrivateKey() != null) { log.info("[API AUTH] 从JWK文件读取RSA密钥成功,keyId: {}", rsaKey.getKeyId()); return rsaKey; } } } log.error("[API AUTH] JWK文件中无有效RSA私钥(仅公钥无法签名JWT)"); } catch (IOException e) { log.error("[API AUTH] 读取JWK文件失败(路径:{})", jwkFile, e); } catch (Exception e) { log.error("[API AUTH] 解析JWK文件失败(JSON格式错误或密钥无效)", e); } // 所有失败场景:生成默认密钥并持久化(避免重启失效) return createAndPersistDefaultRsaKey(jwkFile); } /** * 获取JWK文件输入流(支持classpath/本地文件,classpath读取加安全警告) */ private InputStream getJwkInputStream(String jwkFile) throws IOException { if (jwkFile.startsWith("classpath:")) { String filePath = jwkFile.substring("classpath:".length()); ClassPathResource resource = new ClassPathResource(filePath); if (resource.exists()) { // 关键:classpath读取时打印安全警告,提醒用户确认密钥来源 log.warn("[API AUTH] 从classpath读取内置JWK文件:{}!请确认该密钥是您自己签发的," + "classpath内置密钥存在泄露风险,生产环境建议改用外部文件配置", filePath); return resource.getInputStream(); } // throw new IOException("classpath下JWK文件不存在:" + filePath); } { File file = determinePersistPath(jwkFile).toFile();// 外部配置与classpath失败场景下 if (file.exists() && file.canRead()) { log.debug("[API AUTH] 从本地文件读取JWK文件:{}", file.getAbsolutePath()); return Files.newInputStream(file.toPath()); } throw new IOException("本地JWK文件不存在或无读取权限:" + file.getAbsolutePath()); } } /** * 生成默认RSA密钥(单独抽取,修复之前漏写的问题) */ private RsaJsonWebKey createDefaultRsaKey() throws JoseException { RsaJsonWebKey defaultKey = RsaJwkGenerator.generateJwk(4096); defaultKey.setKeyId(keyId); log.warn("[API AUTH] 使用默认生成的RSA密钥(未持久化,重启会失效),keyId: {}", defaultKey.getKeyId()); return defaultKey; } /** * 生成默认RSA密钥并持久化到文件(修复原重复代码,避免重启失效) */ private RsaJsonWebKey createAndPersistDefaultRsaKey(String configJwkFile) throws JoseException { // 1. 生成4096位RSA密钥(原2048位升级,更安全) RsaJsonWebKey defaultKey = RsaJwkGenerator.generateJwk(4096); defaultKey.setKeyId(keyId); // keyId配置 // 2. 确定持久化路径:优先用户配置的非classpath路径,否则用默认外部路径 Path persistPath = determinePersistPath(configJwkFile); if (persistPath == null) { log.warn("[API AUTH] 生成默认RSA密钥(keyId: {}),但配置路径是classpath(只读)!" + "服务重启后密钥会失效,请修改jwkFile为外部可写路径(如:/opt/config/jwk.json)", defaultKey.getKeyId()); return defaultKey; } // 3. 保存密钥到文件(标准JWK Set格式,下次启动可直接读取) try { // 自动创建父目录(比如./config不存在时会自动建) Files.createDirectories(persistPath.getParent()); // 构建标准JWK Set JSON(jose4j的toString()自带正确格式) JsonWebKeySet jwkSet = new JsonWebKeySet(defaultKey); String jwkJson = jwkSet.toJson(JsonWebKey.OutputControlLevel.INCLUDE_PRIVATE); // 写入文件(覆盖已有文件,避免重复) Files.writeString(persistPath, jwkJson, StandardCharsets.UTF_8); log.info("[API AUTH] 生成默认RSA密钥(keyId: {})并持久化到:{}", defaultKey.getKeyId(), persistPath.toAbsolutePath()); } catch (IOException e) { log.error("[API AUTH] 生成默认RSA密钥成功,但持久化失败(路径:{})!服务重启后密钥会失效", persistPath.toAbsolutePath(), e); } return defaultKey; } /** * 确定密钥持久化路径(兼容classpath只读场景) */ private Path determinePersistPath(String configJwkFile) { // 若配置路径不是classpath,直接用用户配置的路径(外部可写) if (!configJwkFile.startsWith("classpath:")) { return Paths.get(configJwkFile); } // 若配置是classpath,保存到默认外部路径:./config/jwk.json(项目根目录下的config文件夹) Path defaultPath = Paths.get("config", "jwk.json"); log.warn("[API AUTH] 配置的jwkFile是classpath路径(只读),默认密钥将保存到外部路径:{}", defaultPath.toAbsolutePath()); return defaultPath; } public static String createToken(String username, Long expirationTime, Map extra) { try { /* * “iss” (issuer) 发行人 * “sub” (subject) 主题 * “aud” (audience) 接收方 用户 * “exp” (expiration time) 到期时间 * “nbf” (not before) 在此之前不可用 * “iat” (issued at) jwt的签发时间 */ JwtClaims claims = new JwtClaims(); claims.setGeneratedJwtId(); claims.setIssuedAtToNow(); // 令牌将过期的时间 分钟 if (expirationTime != null) { claims.setExpirationTimeMinutesInTheFuture(expirationTime); } claims.setNotBeforeMinutesInThePast(0); claims.setSubject("login"); claims.setAudience(AUDIENCE); //添加自定义参数,必须是字符串类型 claims.setClaim("userName", username); if (extra != null) { extra.forEach(claims::setClaim); } //jws JsonWebSignature jws = new JsonWebSignature(); //签名算法RS256 jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256); jws.setKeyIdHeaderValue(keyId); jws.setPayload(claims.toJson()); jws.setKey(rsaJsonWebKey.getPrivateKey()); //get token return jws.getCompactSerialization(); } catch (JoseException e) { log.error("[Token生成失败]: {}", e.getMessage()); } return null; } public static String createToken(String username, Long expirationTime) { return createToken(username, expirationTime, null); } public static String createToken(String username) { return createToken(username, userSetting.getLoginTimeout()); } public static String getHeader() { return HEADER; } public static JwtUser verifyToken(String token) { JwtUser jwtUser = new JwtUser(); try { JwtConsumer consumer = new JwtConsumerBuilder() //.setRequireExpirationTime() //.setMaxFutureValidityInMinutes(5256000) .setAllowedClockSkewInSeconds(30) .setRequireSubject() //.setExpectedIssuer("") .setExpectedAudience(AUDIENCE) .setVerificationKey(rsaJsonWebKey.getPublicKey()) .build(); JwtClaims claims = consumer.processToClaims(token); NumericDate expirationTime = claims.getExpirationTime(); if (expirationTime != null) { // 判断是否即将过期, 默认剩余时间小于5分钟未即将过期 // 剩余时间 (秒) long timeRemaining = expirationTime.getValue() - LocalDateTime.now().toEpochSecond(ZoneOffset.ofHours(8)); if (timeRemaining < 5 * 60) { jwtUser.setStatus(JwtUser.TokenStatus.EXPIRING_SOON); } else { jwtUser.setStatus(JwtUser.TokenStatus.NORMAL); } } else { jwtUser.setStatus(JwtUser.TokenStatus.NORMAL); } Long apiKeyId = claims.getClaimValue("apiKeyId", Long.class); if (apiKeyId != null) { UserApiKey userApiKey = userApiKeyService.getUserApiKeyById(apiKeyId.intValue()); if (userApiKey == null || !userApiKey.isEnable()) { jwtUser.setStatus(JwtUser.TokenStatus.EXPIRED); } } String username = (String) claims.getClaimValue("userName"); User user = userService.getUserByUsername(username); jwtUser.setUserName(username); jwtUser.setPassword(user.getPassword()); jwtUser.setRoleId(user.getRole().getId()); jwtUser.setUserId(user.getId()); return jwtUser; } catch (InvalidJwtException e) { if (e.hasErrorCode(ErrorCodes.EXPIRED)) { jwtUser.setStatus(JwtUser.TokenStatus.EXPIRED); } else { jwtUser.setStatus(JwtUser.TokenStatus.EXCEPTION); } return jwtUser; } catch (Exception e) { log.error("[Token解析失败]: {}", e.getMessage()); jwtUser.setStatus(JwtUser.TokenStatus.EXPIRED); return jwtUser; } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/security/LogoutHandler.java ================================================ package com.genersoft.iot.vmp.conf.security; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; import org.springframework.stereotype.Component; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; /** * 退出登录成功 */ @Slf4j @Component public class LogoutHandler implements LogoutSuccessHandler { @Override public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException { String username = request.getParameter("username"); log.info("[退出登录成功] - [{}]", username); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/security/SecurityUtils.java ================================================ package com.genersoft.iot.vmp.conf.security; import com.genersoft.iot.vmp.conf.security.dto.LoginUser; import com.genersoft.iot.vmp.storager.dao.dto.User; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import javax.security.sasl.AuthenticationException; import java.time.LocalDateTime; public class SecurityUtils { /** * 描述根据账号密码进行调用security进行认证授权 主动调 * 用AuthenticationManager的authenticate方法实现 * 授权成功后将用户信息存入SecurityContext当中 * @param username 用户名 * @param password 密码 * @param authenticationManager 认证授权管理器, * @see AuthenticationManager * @return UserInfo 用户信息 */ public static LoginUser login(String username, String password, AuthenticationManager authenticationManager) throws AuthenticationException { //使用security框架自带的验证token生成器 也可以自定义。 UsernamePasswordAuthenticationToken token =new UsernamePasswordAuthenticationToken(username,password); //认证 如果失败,这里会自动异常后返回,所以这里不需要判断返回值是否为空,确定是否登录成功 Authentication authenticate = authenticationManager.authenticate(token); LoginUser user = (LoginUser) authenticate.getPrincipal(); SecurityContextHolder.getContext().setAuthentication(token); return user; } /** * 获取当前登录的所有认证信息 * @return */ public static Authentication getAuthentication(){ SecurityContext context = SecurityContextHolder.getContext(); return context.getAuthentication(); } /** * 获取当前登录用户信息 * @return */ public static LoginUser getUserInfo(){ Authentication authentication = getAuthentication(); if(authentication!=null){ Object principal = authentication.getPrincipal(); if(principal!=null && !"anonymousUser".equals(principal.toString())){ User user = (User) principal; return new LoginUser(user, LocalDateTime.now()); } } return null; } /** * 获取当前登录用户ID * @return */ public static int getUserId(){ LoginUser user = getUserInfo(); return user.getId(); } /** * 生成BCryptPasswordEncoder密码 * * @param password 密码 * @return 加密字符串 */ public static String encryptPassword(String password) { BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); return passwordEncoder.encode(password); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java ================================================ package com.genersoft.iot.vmp.conf.security; import com.genersoft.iot.vmp.conf.UserSetting; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.CorsUtils; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * 配置Spring Security * * @author lin */ @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) @Order(1) @Slf4j public class WebSecurityConfig { @Autowired private UserSetting userSetting; @Autowired private DefaultUserDetailsServiceImpl userDetailsService; /** * 登出成功的处理 */ @Autowired private LogoutHandler logoutHandler; /** * 未登录的处理 */ @Autowired private AnonymousAuthenticationEntryPoint anonymousAuthenticationEntryPoint; @Autowired private JwtAuthenticationFilter jwtAuthenticationFilter; @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { return config.getAuthenticationManager(); } @Bean public AuthenticationProvider authProvider() { DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); // 设置不隐藏 未找到用户异常 provider.setHideUserNotFoundExceptions(true); // 用户认证service - 查询数据库的逻辑 provider.setUserDetailsService(userDetailsService); // 设置密码加密算法 provider.setPasswordEncoder(passwordEncoder()); return provider; } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { List defaultExcludes = new ArrayList<>(); defaultExcludes.add("/"); defaultExcludes.add("/#/**"); defaultExcludes.add("/static/**"); defaultExcludes.add("/swagger-ui.html"); defaultExcludes.add("/swagger-ui/**"); defaultExcludes.add("/swagger-resources/**"); defaultExcludes.add("/doc.html"); defaultExcludes.add("/doc.html#/**"); defaultExcludes.add("/v3/api-docs/**"); defaultExcludes.add("/index.html"); defaultExcludes.add("/webjars/**"); defaultExcludes.add("/js/**"); defaultExcludes.add("/api/device/query/snap/**"); defaultExcludes.add("/record_proxy/*/**"); defaultExcludes.add("/api/emit"); defaultExcludes.add("/favicon.ico"); defaultExcludes.add("/api/user/login"); defaultExcludes.add("/index/hook/**"); defaultExcludes.add("/api/device/query/snap/**"); defaultExcludes.add("/index/hook/abl/**"); defaultExcludes.add("/api/jt1078/playback/download"); defaultExcludes.add("/api/jt1078/snap"); if (userSetting.getInterfaceAuthentication() && !userSetting.getInterfaceAuthenticationExcludes().isEmpty()) { defaultExcludes.addAll(userSetting.getInterfaceAuthenticationExcludes()); } http .headers(headers -> headers.contentTypeOptions(contentType -> contentType.disable())) .cors(cors -> cors.configurationSource(configurationSource())) .csrf(csrf -> csrf.disable()) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.ALWAYS)) .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) // 配置拦截规则 .authorizeHttpRequests(auth -> auth .requestMatchers(CorsUtils::isPreFlightRequest).permitAll() .requestMatchers(defaultExcludes.toArray(new String[0])).permitAll() .anyRequest().authenticated() ) // 异常处理器 .exceptionHandling(exception -> exception.authenticationEntryPoint(anonymousAuthenticationEntryPoint)) .logout(logout -> logout.logoutUrl("/api/user/logout") .permitAll() .logoutSuccessHandler(logoutHandler)); return http.build(); } CorsConfigurationSource configurationSource() { // 配置跨域 CorsConfiguration corsConfiguration = new CorsConfiguration(); corsConfiguration.setAllowedHeaders(Arrays.asList("*")); corsConfiguration.setAllowedMethods(Arrays.asList("*")); corsConfiguration.setMaxAge(3600L); if (userSetting.getAllowedOrigins() != null && !userSetting.getAllowedOrigins().isEmpty()) { corsConfiguration.setAllowCredentials(true); corsConfiguration.setAllowedOrigins(userSetting.getAllowedOrigins()); } else { // 在SpringBoot 2.4及以上版本处理跨域时,遇到错误提示:当allowCredentials为true时,allowedOrigins不能包含特殊值"*"。 // 解决方法是明确指定allowedOrigins或使用allowedOriginPatterns。 corsConfiguration.setAllowCredentials(true); corsConfiguration.addAllowedOriginPattern(CorsConfiguration.ALL); // 默认全部允许所有跨域 } corsConfiguration.setExposedHeaders(Arrays.asList(JwtUtils.getHeader())); UrlBasedCorsConfigurationSource url = new UrlBasedCorsConfigurationSource(); url.registerCorsConfiguration("/**", corsConfiguration); return url; } /** * 描述: 密码加密算法 BCrypt 推荐使用 **/ public BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/security/dto/JwtUser.java ================================================ package com.genersoft.iot.vmp.conf.security.dto; public class JwtUser { public enum TokenStatus{ /** * 正常的使用状态 */ NORMAL, /** * 过期而失效 */ EXPIRED, /** * 即将过期 */ EXPIRING_SOON, /** * 异常 */ EXCEPTION } private int userId; private String userName; private String password; private int roleId; private TokenStatus status; public int getUserId() { return userId; } public void setUserId(int userId) { this.userId = userId; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public TokenStatus getStatus() { return status; } public void setStatus(TokenStatus status) { this.status = status; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public int getRoleId() { return roleId; } public void setRoleId(int roleId) { this.roleId = roleId; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/security/dto/LoginUser.java ================================================ package com.genersoft.iot.vmp.conf.security.dto; import com.genersoft.iot.vmp.storager.dao.dto.Role; import com.genersoft.iot.vmp.storager.dao.dto.User; import lombok.Getter; import lombok.Setter; import org.springframework.security.core.CredentialsContainer; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.SpringSecurityCoreVersion; import org.springframework.security.core.userdetails.UserDetails; import java.time.LocalDateTime; import java.util.Collection; public class LoginUser implements UserDetails, CredentialsContainer { private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; /** * 用户 */ private User user; @Getter @Setter private String accessToken; @Setter @Getter private String serverId; /** * 登录时间 */ private LocalDateTime loginTime; public LoginUser(User user, LocalDateTime loginTime) { this.user = user; this.loginTime = loginTime; } @Override public Collection getAuthorities() { return null; } @Override public String getPassword() { return user.getPassword(); } @Override public String getUsername() { return user.getUsername(); } /** * 账户是否未过期,过期无法验证 */ @Override public boolean isAccountNonExpired() { return true; } /** * 指定用户是否解锁,锁定的用户无法进行身份验证 *

    * 密码锁定 *

    */ @Override public boolean isAccountNonLocked() { return true; } /** * 指示是否已过期的用户的凭据(密码),过期的凭据防止认证 */ @Override public boolean isCredentialsNonExpired() { return true; } /** * 用户是否被启用或禁用。禁用的用户无法进行身份验证。 */ @Override public boolean isEnabled() { return true; } /** * 认证完成后,擦除密码 */ @Override public void eraseCredentials() { user.setPassword(null); } public int getId() { return user.getId(); } public Role getRole() { return user.getRole(); } public String getPushKey() { return user.getPushKey(); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/webLog/LogChannel.java ================================================ package com.genersoft.iot.vmp.conf.webLog; import lombok.extern.slf4j.Slf4j; import jakarta.websocket.*; import jakarta.websocket.server.ServerEndpoint; import java.io.IOException; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @ServerEndpoint(value = "/channel/log") @Slf4j public class LogChannel { public static final ConcurrentMap CHANNELS = new ConcurrentHashMap<>(); private Session session; @OnMessage(maxMessageSize = 1) // MaxMessage 1 byte public void onMessage(String message) { try { this.session.close(new CloseReason(CloseReason.CloseCodes.TOO_BIG, "此节点不接收任何客户端信息")); } catch (IOException e) { log.error("[Web-Log] 连接关闭失败: id={}, err={}", this.session.getId(), e.getMessage()); } } @OnOpen public void onOpen(Session session, EndpointConfig endpointConfig) { this.session = session; this.session.setMaxIdleTimeout(0); CHANNELS.put(this.session.getId(), this); log.info("[Web-Log] 连接已建立: id={}", this.session.getId()); } @OnClose public void onClose(CloseReason closeReason) { log.info("[Web-Log] 连接已断开: id={}, err={}", this.session.getId(), closeReason); CHANNELS.remove(this.session.getId()); } @OnError public void onError(Throwable throwable) throws IOException { log.info("[Web-Log] 连接错误: id={}, err= {}", this.session.getId(), throwable.getMessage()); if (this.session.isOpen()) { this.session.close(new CloseReason(CloseReason.CloseCodes.UNEXPECTED_CONDITION, throwable.getMessage())); } } /** * Push messages to all clients * * @param message */ public static void push(String message) { CHANNELS.values().stream().forEach(endpoint -> { if (endpoint.session.isOpen()) { endpoint.session.getAsyncRemote().sendText(message); } }); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/webLog/WebSocketAppender.java ================================================ package com.genersoft.iot.vmp.conf.webLog; import ch.qos.logback.classic.encoder.PatternLayoutEncoder; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.AppenderBase; import lombok.Data; import lombok.EqualsAndHashCode; import java.nio.charset.StandardCharsets; @Data @EqualsAndHashCode(callSuper = true) public class WebSocketAppender extends AppenderBase { private PatternLayoutEncoder encoder; @Override protected void append(ILoggingEvent loggingEvent) { byte[] data = this.encoder.encode(loggingEvent); // Push to client. // LogChannel.push(DateUtil.timestampMsTo_yyyy_MM_dd_HH_mm_ss(loggingEvent.getTimeStamp()) + " " + loggingEvent.getFormattedMessage()); LogChannel.push(new String(data, StandardCharsets.UTF_8)); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/conf/websocket/WebSocketConfig.java ================================================ package com.genersoft.iot.vmp.conf.websocket; import com.genersoft.iot.vmp.conf.webLog.LogChannel; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.server.standard.ServerEndpointExporter; @Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter(){ ServerEndpointExporter endpointExporter = new ServerEndpointExporter(); endpointExporter.setAnnotatedEndpointClasses(LogChannel.class); return endpointExporter; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java ================================================ package com.genersoft.iot.vmp.gb28181; import com.genersoft.iot.vmp.conf.SipConfig; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.gb28181.bean.GbStringMsgParserFactory; import com.genersoft.iot.vmp.gb28181.conf.DefaultProperties; import com.genersoft.iot.vmp.gb28181.transmit.ISIPProcessorObserver; import gov.nist.javax.sip.SipProviderImpl; import gov.nist.javax.sip.SipStackImpl; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; import javax.sip.*; import java.net.Inet4Address; import java.net.InetAddress; import java.net.NetworkInterface; import java.util.*; import java.util.concurrent.ConcurrentHashMap; @Slf4j @Component @Order(value=10) public class SipLayer implements CommandLineRunner { @Autowired private SipConfig sipConfig; @Autowired private ISIPProcessorObserver sipProcessorObserver; @Autowired private UserSetting userSetting; private final Map tcpSipProviderMap = new ConcurrentHashMap<>(); private final Map udpSipProviderMap = new ConcurrentHashMap<>(); private final List monitorIps = new ArrayList<>(); @Override public void run(String... args) { if (ObjectUtils.isEmpty(sipConfig.getIp())) { try { // 获得本机的所有网络接口 Enumeration nifs = NetworkInterface.getNetworkInterfaces(); while (nifs.hasMoreElements()) { NetworkInterface nif = nifs.nextElement(); // 获得与该网络接口绑定的 IP 地址,一般只有一个 Enumeration addresses = nif.getInetAddresses(); while (addresses.hasMoreElements()) { InetAddress addr = addresses.nextElement(); if (addr instanceof Inet4Address) { if (addr.getHostAddress().equals("127.0.0.1")){ continue; } if (nif.getName().startsWith("docker")) { continue; } log.info("[自动配置SIP监听网卡] 网卡接口地址: {}", addr.getHostAddress());// 只关心 IPv4 地址 monitorIps.add(addr.getHostAddress()); } } } }catch (Exception e) { log.error("[读取网卡信息失败]", e); } if (monitorIps.isEmpty()) { log.error("[自动配置SIP监听网卡信息失败], 请手动配置SIP.IP后重新启动"); System.exit(1); } }else { // 使用逗号分割多个ip String separator = ","; if (sipConfig.getIp().indexOf(separator) > 0) { String[] split = sipConfig.getIp().split(separator); monitorIps.addAll(Arrays.asList(split)); }else { monitorIps.add(sipConfig.getIp()); } } sipConfig.setMonitorIps(monitorIps); if (ObjectUtils.isEmpty(sipConfig.getShowIp())){ sipConfig.setShowIp(String.join(",", monitorIps)); } SipFactory.getInstance().setPathName("gov.nist"); if (!monitorIps.isEmpty()) { for (String monitorIp : monitorIps) { addListeningPoint(monitorIp, sipConfig.getPort()); } if (udpSipProviderMap.size() + tcpSipProviderMap.size() == 0) { System.exit(1); } } } private void addListeningPoint(String monitorIp, int port){ SipStackImpl sipStack; try { sipStack = (SipStackImpl)SipFactory.getInstance().createSipStack(DefaultProperties.getProperties("GB28181_SIP", userSetting.getSipLog(), userSetting.isSipCacheServerConnections())); sipStack.setMessageParserFactory(new GbStringMsgParserFactory()); } catch (PeerUnavailableException e) { log.error("[SIP SERVER] SIP服务启动失败, 监听地址{}失败,请检查ip是否正确", monitorIp); return; } try { ListeningPoint tcpListeningPoint = sipStack.createListeningPoint(monitorIp, port, "TCP"); SipProviderImpl tcpSipProvider = (SipProviderImpl)sipStack.createSipProvider(tcpListeningPoint); tcpSipProvider.setDialogErrorsAutomaticallyHandled(); tcpSipProvider.addSipListener(sipProcessorObserver); tcpSipProviderMap.put(monitorIp, tcpSipProvider); log.info("[SIP SERVER] tcp://{}:{} 启动成功", monitorIp, port); } catch (TransportNotSupportedException | TooManyListenersException | ObjectInUseException | InvalidArgumentException e) { log.error("[SIP SERVER] tcp://{}:{} SIP服务启动失败,请检查端口是否被占用或者ip是否正确" , monitorIp, port); } try { ListeningPoint udpListeningPoint = sipStack.createListeningPoint(monitorIp, port, "UDP"); SipProviderImpl udpSipProvider = (SipProviderImpl)sipStack.createSipProvider(udpListeningPoint); udpSipProvider.addSipListener(sipProcessorObserver); udpSipProvider.setDialogErrorsAutomaticallyHandled(); udpSipProviderMap.put(monitorIp, udpSipProvider); log.info("[SIP SERVER] udp://{}:{} 启动成功", monitorIp, port); } catch (TransportNotSupportedException | TooManyListenersException | ObjectInUseException | InvalidArgumentException e) { log.error("[SIP SERVER] udp://{}:{} SIP服务启动失败,请检查端口是否被占用或者ip是否正确" , monitorIp, port); } } public SipProviderImpl getUdpSipProvider(String ip) { if (udpSipProviderMap.size() == 1) { return udpSipProviderMap.values().stream().findFirst().get(); } if (ObjectUtils.isEmpty(ip)) { return null; } return udpSipProviderMap.get(ip); } public SipProviderImpl getUdpSipProvider() { if (udpSipProviderMap.size() != 1) { return null; } return udpSipProviderMap.values().stream().findFirst().get(); } public SipProviderImpl getTcpSipProvider() { if (tcpSipProviderMap.size() != 1) { return null; } return tcpSipProviderMap.values().stream().findFirst().get(); } public SipProviderImpl getTcpSipProvider(String ip) { if (tcpSipProviderMap.size() == 1) { return tcpSipProviderMap.values().stream().findFirst().get(); } if (ObjectUtils.isEmpty(ip)) { return null; } return tcpSipProviderMap.get(ip); } public String getLocalIp(String deviceLocalIp) { if (monitorIps.size() == 1) { return monitorIps.get(0); } if (!ObjectUtils.isEmpty(deviceLocalIp)) { return deviceLocalIp; } return getUdpSipProvider().getListeningPoint().getIPAddress(); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/auth/DigestServerAuthenticationHelper.java ================================================ /* * Conditions Of Use * * This software was developed by employees of the National Institute of * Standards and Technology (NIST), an agency of the Federal Government. * Pursuant to title 15 Untied States Code Section 105, works of NIST * employees are not subject to copyright protection in the United States * and are considered to be in the public domain. As a result, a formal * license is not needed to use the software. * * This software is provided by NIST as a service and is expressly * provided "AS IS." NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED * OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT * AND DATA ACCURACY. NIST does not warrant or make any representations * regarding the use of the software or the results thereof, including but * not limited to the correctness, accuracy, reliability or usefulness of * the software. * * Permission to use this software is contingent upon your acceptance * of the terms of this agreement * * . * */ package com.genersoft.iot.vmp.gb28181.auth; import gov.nist.core.InternalErrorHandler; import lombok.extern.slf4j.Slf4j; import javax.sip.address.URI; import javax.sip.header.AuthorizationHeader; import javax.sip.header.HeaderFactory; import javax.sip.header.WWWAuthenticateHeader; import javax.sip.message.Request; import javax.sip.message.Response; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.time.Instant; import java.util.Random; /** * Implements the HTTP digest authentication method server side functionality. * * @author M. Ranganathan * @author Marc Bednarek */ @Slf4j public class DigestServerAuthenticationHelper { private final MessageDigest messageDigest; public static final String DEFAULT_ALGORITHM = "MD5"; public static final String DEFAULT_SCHEME = "Digest"; /** to hex converter */ private static final char[] toHex = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; /** * Default constructor. */ public DigestServerAuthenticationHelper() throws NoSuchAlgorithmException { messageDigest = MessageDigest.getInstance(DEFAULT_ALGORITHM); } public static String toHexString(byte[] b) { int pos = 0; char[] c = new char[b.length * 2]; for (byte value : b) { c[pos++] = toHex[(value >> 4) & 0x0F]; c[pos++] = toHex[value & 0x0f]; } return new String(c); } /** * Generate the challenge string. * * @return a generated nonce. */ private String generateNonce() { long time = Instant.now().toEpochMilli(); Random rand = new Random(); long pad = rand.nextLong(); String nonceString = Long.valueOf(time).toString() + Long.valueOf(pad).toString(); byte[] mdBytes = messageDigest.digest(nonceString.getBytes()); return toHexString(mdBytes); } public Response generateChallenge(HeaderFactory headerFactory, Response response, String realm) { try { WWWAuthenticateHeader proxyAuthenticate = headerFactory .createWWWAuthenticateHeader(DEFAULT_SCHEME); proxyAuthenticate.setParameter("realm", realm); proxyAuthenticate.setParameter("qop", "auth"); proxyAuthenticate.setParameter("nonce", generateNonce()); proxyAuthenticate.setParameter("algorithm", DEFAULT_ALGORITHM); response.setHeader(proxyAuthenticate); } catch (Exception ex) { InternalErrorHandler.handleException(ex); } return response; } /** * Authenticate the inbound request. * * @param request - the request to authenticate. * @param hashedPassword -- the MD5 hashed string of username:realm:plaintext password. * * @return true if authentication succeded and false otherwise. */ public boolean doAuthenticateHashedPassword(Request request, String hashedPassword) { AuthorizationHeader authHeader = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME); if ( authHeader == null ) { return false; } String realm = authHeader.getRealm(); String username = authHeader.getUsername(); if ( username == null || realm == null ) { return false; } String nonce = authHeader.getNonce(); URI uri = authHeader.getURI(); if (uri == null) { return false; } String A2 = request.getMethod().toUpperCase() + ":" + uri.toString(); String HA1 = hashedPassword; byte[] mdbytes = messageDigest.digest(A2.getBytes()); String HA2 = toHexString(mdbytes); String cnonce = authHeader.getCNonce(); String KD = HA1 + ":" + nonce; if (cnonce != null) { KD += ":" + cnonce; } KD += ":" + HA2; mdbytes = messageDigest.digest(KD.getBytes()); String mdString = toHexString(mdbytes); String response = authHeader.getResponse(); return mdString.equals(response); } /** * 鉴权 * * A1 = username + ":" + realm + ":" + password * A2 = REGISTER:URI * * HA1 = md5(A1) * HA2 = md5(A2) * * KD = HA2:HA2:qop * * response = md5(KD) * @param request - the request to authenticate. * @param pass -- the plain text password. * * @return true if authentication succeded and false otherwise. */ public boolean doAuthenticatePlainTextPassword(Request request, String pass) { AuthorizationHeader authHeader = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME); if ( authHeader == null || authHeader.getRealm() == null) { return false; } if ( authHeader.getUsername() == null || authHeader.getRealm() == null ) { return false; } String realm = authHeader.getRealm().trim(); String username = authHeader.getUsername().trim(); String nonce = authHeader.getNonce(); URI uri = authHeader.getURI(); if (uri == null) { return false; } // qop 保护质量 包含auth(默认的)和auth-int(增加了报文完整性检测)两种策略 String qop = authHeader.getQop(); // 客户端随机数,这是一个不透明的字符串值,由客户端提供,并且客户端和服务器都会使用,以避免用明文文本。 // 这使得双方都可以查验对方的身份,并对消息的完整性提供一些保护 String cnonce = authHeader.getCNonce(); // nonce计数器,是一个16进制的数值,表示同一nonce下客户端发送出请求的数量 int nc = authHeader.getNonceCount(); String ncStr = String.format("%08x", nc).toUpperCase(); String A1 = username + ":" + realm + ":" + pass; String A2 = request.getMethod().toUpperCase() + ":" + uri.toString(); byte[] mdbytes = messageDigest.digest(A1.getBytes()); String HA1 = toHexString(mdbytes); log.debug("A1: {}", A1); log.debug("A2: {}", A2); mdbytes = messageDigest.digest(A2.getBytes()); String HA2 = toHexString(mdbytes); log.debug("HA1: {}", HA1); log.debug("HA2: {}", HA2); // String cnonce = authHeader.getCNonce(); log.debug("nonce: {}", nonce); log.debug("nc: {}", ncStr); log.debug("cnonce: {}", cnonce); log.debug("qop: {}", qop); String KD = HA1 + ":" + nonce; if (qop != null && qop.equalsIgnoreCase("auth") ) { if (nc != -1) { KD += ":" + ncStr; } if (cnonce != null) { KD += ":" + cnonce; } KD += ":" + qop; } KD += ":" + HA2; log.debug("KD: {}", KD); mdbytes = messageDigest.digest(KD.getBytes()); String mdString = toHexString(mdbytes); log.debug("mdString: {}", mdString); String response = authHeader.getResponse(); log.debug("response: {}", response); return mdString.equals(response); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/AlarmChannelMessage.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import lombok.Data; /** * 通过redis分发报警消息 */ @Data public class AlarmChannelMessage { /** * 通道国标编号 */ private String gbId; /** * 报警编号 */ private int alarmSn; /** * 告警类型 */ private int alarmType; /** * 报警描述 */ private String alarmDescription; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/AudioBroadcastCatch.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import com.genersoft.iot.vmp.gb28181.controller.bean.AudioBroadcastEvent; import com.genersoft.iot.vmp.media.bean.MediaServer; import gov.nist.javax.sip.message.SIPResponse; import lombok.Data; /** * 缓存语音广播的状态 * @author lin */ @Data public class AudioBroadcastCatch { public AudioBroadcastCatch( String deviceId, Integer channelId, MediaServer mediaServerItem, String app, String stream, AudioBroadcastEvent event, AudioBroadcastCatchStatus status, boolean isFromPlatform ) { this.deviceId = deviceId; this.channelId = channelId; this.status = status; this.event = event; this.isFromPlatform = isFromPlatform; this.app = app; this.stream = stream; this.mediaServerItem = mediaServerItem; } public AudioBroadcastCatch() { } /** * 设备编号 */ private String deviceId; /** * 通道编号 */ private Integer channelId; /** * 流媒体信息 */ private MediaServer mediaServerItem; /** * 关联的流APP */ private String app; /** * 关联的流STREAM */ private String stream; /** * 是否是级联语音喊话 */ private boolean isFromPlatform; /** * 语音广播状态 */ private AudioBroadcastCatchStatus status; /** * 请求信息 */ private SipTransactionInfo sipTransactionInfo; /** * 请求结果回调 */ private AudioBroadcastEvent event; public void setSipTransactionInfoByRequest(SIPResponse sipResponse) { this.sipTransactionInfo = new SipTransactionInfo(sipResponse); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/AudioBroadcastCatchStatus.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; /** * 语音广播状态 * @author lin */ public enum AudioBroadcastCatchStatus { // 发送语音广播消息等待对方回复语音广播 Ready, // 收到回复等待invite消息 WaiteInvite, // 收到invite消息 Ok, } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/BaiduPoint.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; public class BaiduPoint { String bdLng; String bdLat; public String getBdLng() { return bdLng; } public void setBdLng(String bdLng) { this.bdLng = bdLng; } public String getBdLat() { return bdLat; } public void setBdLat(String bdLat) { this.bdLat = bdLat; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/BasicParam.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; /** * 基础配置 */ @Data @Schema(description = "基础配置") public class BasicParam { @Schema(description = "设备ID") private String deviceId; @Schema(description = "通道ID,如果时对设备配置直接设置同设备ID一样即可") private String channelId; @Schema(description = "名称") private String name; @Schema(description = "注册过期时间") private String expiration; @Schema(description = "心跳间隔时间") private Integer heartBeatInterval; @Schema(description = "心跳超时次数") private Integer heartBeatCount; @Schema(description = "定位功能支持情况。取值:0-不支持;1-支持 GPS定位;2-支持北斗定位(可选,默认取值为0)," + "用于接受配置查询结果, 基础配置时无效") private Integer positionCapability; @Schema(description = "经度(可选),用于接受配置查询结果, 基础配置时无效") private Double longitude; @Schema(description = "纬度(可选),用于接受配置查询结果, 基础配置时无效") private Double latitude; public static BasicParam getInstance(String name, String expiration, Integer heartBeatInterval, Integer heartBeatCount) { BasicParam basicParam = new BasicParam(); basicParam.setName(name); basicParam.setExpiration(expiration); basicParam.setHeartBeatInterval(heartBeatInterval); basicParam.setHeartBeatCount(heartBeatCount); return basicParam; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/CatalogChannelEvent.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.extern.slf4j.Slf4j; import org.dom4j.Element; import java.lang.reflect.InvocationTargetException; @Data @Slf4j @EqualsAndHashCode(callSuper = true) public class CatalogChannelEvent extends DeviceChannel{ private String event; private DeviceChannel channel; public static CatalogChannelEvent decode(Element element) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException { Element eventElement = element.element("Event"); CatalogChannelEvent catalogChannelEvent = new CatalogChannelEvent(); if (eventElement != null) { catalogChannelEvent.setEvent(eventElement.getText()); }else { catalogChannelEvent.setEvent(CatalogEvent.ADD); } DeviceChannel deviceChannel; if (CatalogEvent.ADD.equalsIgnoreCase(catalogChannelEvent.getEvent()) || CatalogEvent.UPDATE.equalsIgnoreCase(catalogChannelEvent.getEvent()) ){ deviceChannel = DeviceChannel.decode(element); }else { deviceChannel = DeviceChannel.decodeWithOnlyDeviceId(element); } catalogChannelEvent.setChannel(deviceChannel); return catalogChannelEvent; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/CatalogData.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import lombok.Data; import java.time.Instant; import java.util.HashSet; import java.util.Set; /** * @author lin */ @Data public class CatalogData { /** * 命令序列号 */ private int sn; private Integer total; private Instant time; private Device device; private String errorMsg; private Set redisKeysForChannel = new HashSet<>(); private Set errorChannel = new HashSet<>(); private Set redisKeysForRegion = new HashSet<>(); private Set redisKeysForGroup = new HashSet<>(); public enum CatalogDataStatus{ ready, runIng, end } private CatalogDataStatus status; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/CmdType.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; public class CmdType { public static final String CATALOG = "Catalog"; public static final String ALARM = "Alarm"; public static final String MOBILE_POSITION = "MobilePosition"; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/CommonGBChannel.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @Data @Schema(description = "国标通道") public class CommonGBChannel { @Schema(description = "国标-数据库自增ID") private int gbId; @Schema(description = "国标-编码") private String gbDeviceId; @Schema(description = "国标-名称") private String gbName; @Schema(description = "国标-设备厂商") private String gbManufacturer; @Schema(description = "国标-设备型号") private String gbModel; // 2016 @Schema(description = "国标-设备归属") private String gbOwner; @Schema(description = "国标-行政区域") private String gbCivilCode; @Schema(description = "国标-警区") private String gbBlock; @Schema(description = "国标-安装地址") private String gbAddress; @Schema(description = "国标-是否有子设备") private Integer gbParental; @Schema(description = "国标-父节点ID") private String gbParentId; // 2016 @Schema(description = "国标-信令安全模式") private Integer gbSafetyWay; @Schema(description = "国标-注册方式") private Integer gbRegisterWay; // 2016 @Schema(description = "国标-证书序列号") private String gbCertNum; // 2016 @Schema(description = "国标-证书有效标识") private Integer gbCertifiable; // 2016 @Schema(description = "国标-无效原因码(有证书且证书无效的设备必选)") private Integer gbErrCode; // 2016 @Schema(description = "国标-证书终止有效期(有证书且证书无效的设备必选)") private String gbEndTime; @Schema(description = "国标-保密属性(必选)缺省为0;0-不涉密,1-涉密") private Integer gbSecrecy; @Schema(description = "国标-设备/系统IPv4/IPv6地址") private String gbIpAddress; @Schema(description = "国标-设备/系统端口") private Integer gbPort; @Schema(description = "国标-设备口令") private String gbPassword; @Schema(description = "国标-设备状态") private String gbStatus; @Schema(description = "国标-经度 WGS-84坐标系") private Double gbLongitude; @Schema(description = "国标-纬度 WGS-84坐标系") private Double gbLatitude; private Double gpsAltitude; private Double gpsSpeed; private Double gpsDirection; private String gpsTime; @Schema(description = "国标-虚拟组织所属的业务分组ID") private String gbBusinessGroupId; @Schema(description = "国标-摄像机结构类型,标识摄像机类型: 1-球机; 2-半球; 3-固定枪机; 4-遥控枪机;5-遥控半球;6-多目设备的全景/拼接通道;" + "7-多目设备的分割通道; 99-移动设备(非标)98-会议设备(非标)") private Integer gbPtzType; // 2016 @Schema(description = "-摄像机位置类型扩展。1-省际检查站、2-党政机关、3-车站码头、4-中心广场、5-体育场馆、6-商业中心、7-宗教场所、" + "8-校园周边、9-治安复杂区域、10-交通干线。当目录项为摄像机时可选。") private Integer gbPositionType; @Schema(description = "国标-摄像机安装位置室外、室内属性。1-室外、2-室内。") private Integer gbRoomType; // 2016 @Schema(description = "国标-用途属性") private Integer gbUseType; @Schema(description = "国标-摄像机补光属性。1-无补光;2-红外补光;3-白光补光;4-激光补光;9-其他") private Integer gbSupplyLightType; @Schema(description = "国标-摄像机监视方位(光轴方向)属性。1-东(西向东)、2-西(东向西)、3-南(北向南)、4-北(南向北)、" + "5-东南(西北到东南)、6-东北(西南到东北)、7-西南(东北到西南)、8-西北(东南到西北)") private Integer gbDirectionType; @Schema(description = "国标-摄像机支持的分辨率,可多值") private String gbResolution; @Schema(description = "国标-下载倍速(可选),可多值") private String gbDownloadSpeed; @Schema(description = "国标-空域编码能力,取值0-不支持;1-1级增强(1个增强层);2-2级增强(2个增强层);3-3级增强(3个增强层)") private Integer gbSvcSpaceSupportMod; @Schema(description = "国标-时域编码能力,取值0-不支持;1-1级增强;2-2级增强;3-3级增强(可选)") private Integer gbSvcTimeSupportMode; @Schema(description = "二进制保存的录制计划, 每一位表示每个小时的前半个小时") private Long recordPLan; @Schema(description = "关联的数据类型") private Integer dataType; @Schema(description = "关联的设备ID") private Integer dataDeviceId; @Schema(description = "创建时间") private String createTime; @Schema(description = "更新时间") private String updateTime; @Schema(description = "流唯一编号,存在表示正在直播") private String streamId; @Schema(description = "是否支持对讲 1支持,0不支持") private Integer enableBroadcast; @Schema(description = "抽稀后的图层层级") private Integer mapLevel; public String encode(String serverDeviceId) { return encode(null, serverDeviceId); } public String encode(String event,String serverDeviceId) { String content; if (event == null) { return getFullContent(null, serverDeviceId); } switch (event) { case CatalogEvent.DEL: case CatalogEvent.DEFECT: case CatalogEvent.VLOST: content = "\n" + "" + this.getGbDeviceId() + "\n" + "" + event + "\n" + "\n"; break; case CatalogEvent.ON: case CatalogEvent.OFF: content = "\n" + "" + this.getGbDeviceId() + "\n" + "" + event + "\r\n" + "\n"; break; case CatalogEvent.ADD: case CatalogEvent.UPDATE: content = getFullContent(event, serverDeviceId); break; default: content = null; break; } return content; } private String getFullContent(String event, String serverDeviceId) { StringBuilder content = new StringBuilder(); // 行政区划目录项 content.append("\n") .append("" + this.getGbDeviceId() + "\n") .append("" + this.getGbName() + "\n"); if (this.getGbDeviceId().length() > 8) { String type = this.getGbDeviceId().substring(10, 13); if (type.equals("200")) { // 业务分组目录项 if (this.getGbManufacturer() != null) { content.append("" + this.getGbManufacturer() + "\n"); } if (this.getGbModel() != null) { content.append("" + this.getGbModel() + "\n"); } if (this.getGbOwner() != null) { content.append("" + this.getGbOwner() + "\n"); } if (this.getGbCivilCode() != null) { content.append("" + this.getGbCivilCode() + "\n"); } if (this.getGbAddress() != null) { content.append("
    " + this.getGbAddress() + "
    \n"); } if (this.getGbRegisterWay() != null) { content.append("" + this.getGbRegisterWay() + "\n"); } if (this.getGbSecrecy() != null) { content.append("" + this.getGbSecrecy() + "\n"); } } else if (type.equals("215")) { // 业务分组 if (this.getGbCivilCode() != null) { content.append("" + this.getGbCivilCode() + "\n"); } content.append("" + serverDeviceId + "\n"); } else if (type.equals("216")) { // 虚拟组织目录项 if (this.getGbCivilCode() != null) { content.append("" + this.getGbCivilCode() + "\n"); } if (this.getGbParentId() != null) { content.append("" + this.getGbParentId() + "\n"); } content.append("" + this.getGbBusinessGroupId() + "\n"); } else { if (this.getGbManufacturer() != null) { content.append("" + this.getGbManufacturer() + "\n"); } if (this.getGbModel() != null) { content.append("" + this.getGbModel() + "\n"); } if (this.getGbOwner() != null) { content.append("" + this.getGbOwner() + "\n"); } if (this.getGbCivilCode() != null) { content.append("" + this.getGbCivilCode() + "\n"); } if (this.getGbAddress() != null) { content.append("
    " + this.getGbAddress() + "
    \n"); } if (this.getGbRegisterWay() != null) { content.append("" + this.getGbRegisterWay() + "\n"); } if (this.getGbSecrecy() != null) { content.append("" + this.getGbSecrecy() + "\n"); } if (this.getGbParentId() != null) { content.append("" + this.getGbParentId() + "\n"); } if (this.getGbParental() != null) { content.append("" + this.getGbParental() + "\n"); } if (this.getGbSafetyWay() != null) { content.append("" + this.getGbSafetyWay() + "\n"); } if (this.getGbRegisterWay() != null) { content.append("" + this.getGbRegisterWay() + "\n"); } if (this.getGbCertNum() != null) { content.append("" + this.getGbCertNum() + "\n"); } if (this.getGbCertifiable() != null) { content.append("" + this.getGbCertifiable() + "\n"); } if (this.getGbErrCode() != null) { content.append("" + this.getGbErrCode() + "\n"); } if (this.getGbEndTime() != null) { content.append("" + this.getGbEndTime() + "\n"); } if (this.getGbSecrecy() != null) { content.append("" + this.getGbSecrecy() + "\n"); } if (this.getGbIpAddress() != null) { content.append("" + this.getGbIpAddress() + "\n"); } if (this.getGbPort() != null) { content.append("" + this.getGbPort() + "\n"); } if (this.getGbPassword() != null) { content.append("" + this.getGbPassword() + "\n"); } if (this.getGbStatus() != null) { content.append("" + this.getGbStatus() + "\n"); } if (this.getGbLongitude() != null) { content.append("" + this.getGbLongitude() + "\n"); } if (this.getGbLatitude() != null) { content.append("" + this.getGbLatitude() + "\n"); } content.append("\n"); if (this.getGbPtzType() != null) { content.append(" " + this.getGbPtzType() + "\n"); } if (this.getGbPositionType() != null) { content.append(" " + this.getGbPositionType() + "\n"); } if (this.getGbRoomType() != null) { content.append(" " + this.getGbRoomType() + "\n"); } if (this.getGbUseType() != null) { content.append(" " + this.getGbUseType() + "\n"); } if (this.getGbSupplyLightType() != null) { content.append(" " + this.getGbSupplyLightType() + "\n"); } if (this.getGbDirectionType() != null) { content.append(" " + this.getGbDirectionType() + "\n"); } if (this.getGbResolution() != null) { content.append(" " + this.getGbResolution() + "\n"); } if (this.getGbBusinessGroupId() != null) { content.append(" " + this.getGbBusinessGroupId() + "\n"); } if (this.getGbDownloadSpeed() != null) { content.append(" " + this.getGbDownloadSpeed() + "\n"); } if (this.getGbSvcSpaceSupportMod() != null) { content.append(" " + this.getGbSvcSpaceSupportMod() + "\n"); } if (this.getGbSvcTimeSupportMode() != null) { content.append(" " + this.getGbSvcTimeSupportMode() + "\n"); } if (this.getEnableBroadcast() != null) { content.append(" " + this.getEnableBroadcast() + "\n"); } content.append("\n"); } } if (event != null) { content.append("" + event + "\n"); } content.append("
    \n"); return content.toString(); } public static CommonGBChannel build(Group group) { GbCode gbCode = GbCode.decode(group.getDeviceId()); CommonGBChannel channel = new CommonGBChannel(); if (gbCode.getTypeCode().equals("215")) { // 业务分组 channel.setGbName(group.getName()); channel.setGbDeviceId(group.getDeviceId()); channel.setGbCivilCode(group.getCivilCode()); } else { // 虚拟组织 channel.setGbName(group.getName()); channel.setGbDeviceId(group.getDeviceId()); channel.setGbParentId(group.getParentDeviceId()); channel.setGbBusinessGroupId(group.getBusinessGroup()); channel.setGbCivilCode(group.getCivilCode()); } return channel; } public static CommonGBChannel build(Platform platform) { CommonGBChannel commonGBChannel = new CommonGBChannel(); commonGBChannel.setGbDeviceId(platform.getDeviceGBId()); commonGBChannel.setGbName(platform.getName()); commonGBChannel.setGbManufacturer(platform.getManufacturer()); commonGBChannel.setGbModel(platform.getModel()); commonGBChannel.setGbCivilCode(platform.getCivilCode()); commonGBChannel.setGbAddress(platform.getAddress()); commonGBChannel.setGbRegisterWay(platform.getRegisterWay()); commonGBChannel.setGbSecrecy(platform.getSecrecy()); commonGBChannel.setGbStatus(platform.isStatus() ? "ON" : "OFF"); return commonGBChannel; } public static CommonGBChannel build(Region region) { CommonGBChannel commonGBChannel = new CommonGBChannel(); commonGBChannel.setGbDeviceId(region.getDeviceId()); commonGBChannel.setGbName(region.getName()); return commonGBChannel; } @Override public String toString() { return "CommonGBChannel{" + "gbId=" + gbId + ", gbDeviceId='" + gbDeviceId + '\'' + ", gbName='" + gbName + '\'' + ", gbManufacturer='" + gbManufacturer + '\'' + ", gbModel='" + gbModel + '\'' + ", gbOwner='" + gbOwner + '\'' + ", gbCivilCode='" + gbCivilCode + '\'' + ", gbBlock='" + gbBlock + '\'' + ", gbAddress='" + gbAddress + '\'' + ", gbParental=" + gbParental + ", gbParentId='" + gbParentId + '\'' + ", gbSafetyWay=" + gbSafetyWay + ", gbRegisterWay=" + gbRegisterWay + ", gbCertNum='" + gbCertNum + '\'' + ", gbCertifiable=" + gbCertifiable + ", gbErrCode=" + gbErrCode + ", gbEndTime='" + gbEndTime + '\'' + ", gbSecrecy=" + gbSecrecy + ", gbIpAddress='" + gbIpAddress + '\'' + ", gbPort=" + gbPort + ", gbPassword='" + gbPassword + '\'' + ", gbStatus='" + gbStatus + '\'' + ", gbLongitude=" + gbLongitude + ", gbLatitude=" + gbLatitude + ", gpsAltitude=" + gpsAltitude + ", gpsSpeed=" + gpsSpeed + ", gpsDirection=" + gpsDirection + ", gpsTime='" + gpsTime + '\'' + ", gbBusinessGroupId='" + gbBusinessGroupId + '\'' + ", gbPtzType=" + gbPtzType + ", gbPositionType=" + gbPositionType + ", gbRoomType=" + gbRoomType + ", gbUseType=" + gbUseType + ", gbSupplyLightType=" + gbSupplyLightType + ", gbDirectionType=" + gbDirectionType + ", gbResolution='" + gbResolution + '\'' + ", gbDownloadSpeed='" + gbDownloadSpeed + '\'' + ", gbSvcSpaceSupportMod=" + gbSvcSpaceSupportMod + ", gbSvcTimeSupportMode=" + gbSvcTimeSupportMode + ", recordPLan=" + recordPLan + ", dataType=" + dataType + ", dataDeviceId=" + dataDeviceId + ", createTime='" + createTime + '\'' + ", updateTime='" + updateTime + '\'' + ", streamId='" + streamId + '\'' + ", enableBroadcast=" + enableBroadcast + ", mapLevel=" + mapLevel + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/CommonRecordInfo.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import lombok.Data; @Data public class CommonRecordInfo { // 开始时间 private String startTime; // 结束时间 private String endTime; // 文件大小 单位byte private String fileSize; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; /** * 国标设备/平台 * @author lin */ @Data @Schema(description = "国标设备/平台") public class Device { @Schema(description = "数据库自增ID") private int id; /** * 设备国标编号 */ @Schema(description = "设备国标编号") private String deviceId; /** * 设备名 */ @Schema(description = "名称") private String name; /** * 生产厂商 */ @Schema(description = "生产厂商") private String manufacturer; /** * 型号 */ @Schema(description = "型号") private String model; /** * 固件版本 */ @Schema(description = "固件版本") private String firmware; /** * 传输协议 * UDP/TCP */ @Schema(description = "传输协议(UDP/TCP)") private String transport; /** * 数据流传输模式 * UDP:udp传输 * TCP-ACTIVE:tcp主动模式 * TCP-PASSIVE:tcp被动模式 */ @Schema(description = "数据流传输模式") private String streamMode; /** * wan地址_ip */ @Schema(description = "IP") private String ip; /** * wan地址_port */ @Schema(description = "端口") private int port; /** * wan地址 */ @Schema(description = "wan地址") private String hostAddress; /** * 在线 */ @Schema(description = "是否在线,true为在线,false为离线") private boolean onLine; /** * 注册时间 */ @Schema(description = "注册时间") private String registerTime; /** * 心跳时间 */ @Schema(description = "心跳时间") private String keepaliveTime; /** * 心跳间隔 */ @Schema(description = "心跳间隔") private Integer heartBeatInterval; /** * 心跳超时次数 */ @Schema(description = "心跳超时次数") private Integer heartBeatCount; /** * 定位功能支持情况 */ @Schema(description = "定位功能支持情况。取值:0-不支持;1-支持 GPS定位;2-支持北斗定位(可选,默认取值为0") private Integer positionCapability; /** * 通道个数 */ @Schema(description = "通道个数") private int channelCount; /** * 注册有效期 */ @Schema(description = "注册有效期") private int expires; /** * 创建时间 */ @Schema(description = "创建时间") private String createTime; /** * 更新时间 */ @Schema(description = "更新时间") private String updateTime; /** * 设备使用的媒体id, 默认为null */ @Schema(description = "设备使用的媒体id, 默认为null") private String mediaServerId; /** * 字符集, 支持 UTF-8 与 GB2312 */ @Schema(description = "符集, 支持 UTF-8 与 GB2312") private String charset ; /** * 目录订阅周期,0为不订阅 */ @Schema(description = "目录订阅周期,o为不订阅") private int subscribeCycleForCatalog; /** * 移动设备位置订阅周期,0为不订阅 */ @Schema(description = "移动设备位置订阅周期,0为不订阅") private int subscribeCycleForMobilePosition; /** * 移动设备位置信息上报时间间隔,单位:秒,默认值5 */ @Schema(description = "移动设备位置信息上报时间间隔,单位:秒,默认值5") private int mobilePositionSubmissionInterval = 5; /** * 报警订阅周期,0为不订阅 */ @Schema(description = "报警心跳时间订阅周期,0为不订阅") private int subscribeCycleForAlarm; /** * 是否开启ssrc校验,默认关闭,开启可以防止串流 */ @Schema(description = "是否开启ssrc校验,默认关闭,开启可以防止串流") private boolean ssrcCheck = false; /** * 地理坐标系, 目前支持 WGS84,GCJ02, 此字段保留,暂无用 */ @Schema(description = "地理坐标系, 目前支持 WGS84,GCJ02") private String geoCoordSys; @Schema(description = "密码") private String password; @Schema(description = "收流IP") private String sdpIp; @Schema(description = "SIP交互IP(设备访问平台的IP)") private String localIp; @Schema(description = "是否作为消息通道") private boolean asMessageChannel; @Schema(description = "设备注册的事务信息") private SipTransactionInfo sipTransactionInfo; @Schema(description = "控制语音对讲流程,释放收到ACK后发流") private boolean broadcastPushAfterAck; @Schema(description = "所属服务Id") private String serverId; public boolean checkWgs84() { return geoCoordSys.equalsIgnoreCase("WGS84"); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceAlarm.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.util.HashSet; import java.util.Set; /** * @author lin */ @Schema(description = "报警信息") @Data public class DeviceAlarm { @Schema(description = "数据库id") private String id; @Schema(description = "设备的国标编号") private String deviceId; @Schema(description = "设备名称") private String deviceName; /** * 通道Id */ @Schema(description = "通道的国标编号") private String channelId; /** * 报警级别, 1为一级警情, 2为二级警情, 3为三级警情, 4为四级警情 */ @Schema(description = "报警级别, 1为一级警情, 2为二级警情, 3为三级警情, 4为四级警情") private String alarmPriority; @Schema(description = "报警级别, 1为一级警情, 2为二级警情, 3为三级警情, 4为四级警情") private String alarmPriorityDescription; public String getAlarmPriorityDescription() { switch (alarmPriority) { case "1": return "一级警情"; case "2": return "二级警情"; case "3": return "三级警情"; case "4": return "四级警情"; default: return alarmPriority; } } /** * 报警方式 , 1为电话报警, 2为设备报警, 3为短信报警, 4为 GPS报警, 5为视频报警, 6为设备故障报警, * 7其他报警;可以为直接组合如12为电话报警或 设备报警- */ @Schema(description = "报警方式 , 1为电话报警, 2为设备报警, 3为短信报警, 4为 GPS报警, 5为视频报警, 6为设备故障报警,\n" + "\t * 7其他报警;可以为直接组合如12为电话报警或设备报警") private String alarmMethod; private String alarmMethodDescription; public String getAlarmMethodDescription() { StringBuilder stringBuilder = new StringBuilder(); char[] charArray = alarmMethod.toCharArray(); for (char c : charArray) { switch (c) { case '1': stringBuilder.append("-电话报警"); break; case '2': stringBuilder.append("-设备报警"); break; case '3': stringBuilder.append("-短信报警"); break; case '4': stringBuilder.append("-GPS报警"); break; case '5': stringBuilder.append("-视频报警"); break; case '6': stringBuilder.append("-设备故障报警"); break; case '7': stringBuilder.append("-其他报警"); break; } } stringBuilder.delete(0, 1); return stringBuilder.toString(); } /** * 报警时间 */ @Schema(description = "报警时间") private String alarmTime; /** * 报警内容描述 */ @Schema(description = "报警内容描述") private String alarmDescription; /** * 经度 */ @Schema(description = "经度") private double longitude; /** * 纬度 */ @Schema(description = "纬度") private double latitude; /** * 报警类型, * 报警方式为2时,不携带 AlarmType为默认的报警设备报警, * 携带 AlarmType取值及对应报警类型如下: * 1-视频丢失报警; * 2-设备防拆报警; * 3-存储设备磁盘满报警; * 4-设备高温报警; * 5-设备低温报警。 * 报警方式为5时,取值如下: * 1-人工视频报警; * 2-运动目标检测报警; * 3-遗留物检测报警; * 4-物体移除检测报警; * 5-绊线检测报警; * 6-入侵检测报警; * 7-逆行检测报警; * 8-徘徊检测报警; * 9-流量统计报警; * 10-密度检测报警; * 11-视频异常检测报警; * 12-快速移动报警。 * 报警方式为6时,取值下: * 1-存储设备磁盘故障报警; * 2-存储设备风扇故障报警。 */ @Schema(description = "报警类型") private String alarmType; public String getAlarmTypeDescription() { if (alarmType == null) { return ""; } char[] charArray = alarmMethod.toCharArray(); Set alarmMethodSet = new HashSet<>(); for (char c : charArray) { alarmMethodSet.add(Character.toString(c)); } String result = alarmType; if (alarmMethodSet.contains("2")) { switch (alarmType) { case "1": result = "视频丢失报警"; break; case "2": result = "设备防拆报警"; break; case "3": result = "存储设备磁盘满报警"; break; case "4": result = "设备高温报警"; break; case "5": result = "设备低温报警"; break; } } if (alarmMethodSet.contains("5")) { switch (alarmType) { case "1": result = "人工视频报警"; break; case "2": result = "运动目标检测报警"; break; case "3": result = "遗留物检测报警"; break; case "4": result = "物体移除检测报警"; break; case "5": result = "绊线检测报警"; break; case "6": result = "入侵检测报警"; break; case "7": result = "逆行检测报警"; break; case "8": result = "徘徊检测报警"; break; case "9": result = "流量统计报警"; break; case "10": result = "密度检测报警"; break; case "11": result = "视频异常检测报警"; break; case "12": result = "快速移动报警"; break; } } if (alarmMethodSet.contains("6")) { switch (alarmType) { case "1": result = "人工视频报警"; break; case "2": result = "运动目标检测报警"; break; case "3": result = "遗留物检测报警"; break; case "4": result = "物体移除检测报警"; break; case "5": result = "绊线检测报警"; break; case "6": result = "入侵检测报警"; break; case "7": result = "逆行检测报警"; break; case "8": result = "徘徊检测报警"; break; case "9": result = "流量统计报警"; break; case "10": result = "密度检测报警"; break; case "11": result = "视频异常检测报警"; break; case "12": result = "快速移动报警"; break; } } return result; } @Schema(description = "报警类型描述") private String alarmTypeDescription; @Schema(description = "创建时间") private String createTime; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceAlarmMethod.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; /** * 报警方式 * @author lin * 1为电话报警, 2为设备报警, 3为短信报警, 4为 GPS报警, 5为视频报警, 6为设备故障报警, * 7其他报警;可以为直接组合如12为电话报警或 设备报警- */ public enum DeviceAlarmMethod { // 1为电话报警 Telephone(1), // 2为设备报警 Device(2), // 3为短信报警 SMS(3), // 4为 GPS报警 GPS(4), // 5为视频报警 Video(5), // 6为设备故障报警 DeviceFailure(6), // 7其他报警 Other(7); private final int val; DeviceAlarmMethod(int val) { this.val=val; } public int getVal() { return val; } /** * 查询是否匹配类型 * @param code * @return */ public static DeviceAlarmMethod typeOf(int code) { for (DeviceAlarmMethod item : DeviceAlarmMethod.values()) { if (code==item.getVal()) { return item; } } return null; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceChannel.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import com.genersoft.iot.vmp.common.enums.ChannelDataType; import com.genersoft.iot.vmp.gb28181.utils.MessageElementForCatalog; import com.genersoft.iot.vmp.gb28181.utils.XmlUtil; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.extern.slf4j.Slf4j; import org.dom4j.Element; import org.springframework.util.ObjectUtils; import java.lang.reflect.InvocationTargetException; @Data @Slf4j @Schema(description = "通道信息") @EqualsAndHashCode(callSuper = true) public class DeviceChannel extends CommonGBChannel { @Schema(description = "数据库自增ID") private int id; @Schema(description = "父设备编码") private String parentDeviceId; @Schema(description = "父设备名称") private String parentName; @MessageElementForCatalog("DeviceID") @Schema(description = "编码") private String deviceId; @MessageElementForCatalog("Name") @Schema(description = "名称") private String name; @MessageElementForCatalog("Manufacturer") @Schema(description = "设备厂商") private String manufacturer; @MessageElementForCatalog("Model") @Schema(description = "设备型号") private String model; // 2016 @MessageElementForCatalog("Owner") @Schema(description = "设备归属") private String owner; @MessageElementForCatalog("CivilCode") @Schema(description = "行政区域") private String civilCode; @MessageElementForCatalog("Block") @Schema(description = "警区") private String block; @MessageElementForCatalog("Address") @Schema(description = "安装地址") private String address; @MessageElementForCatalog("Parental") @Schema(description = "是否有子设备(必选)1有,0没有") private Integer parental; @MessageElementForCatalog("ParentID") @Schema(description = "父节点ID") private String parentId; // 2016 @MessageElementForCatalog("SafetyWay") @Schema(description = "信令安全模式") private Integer safetyWay; @MessageElementForCatalog("RegisterWay") @Schema(description = "注册方式") private Integer registerWay; // 2016 @MessageElementForCatalog("CertNum") @Schema(description = "证书序列号") private String certNum; // 2016 @MessageElementForCatalog("Certifiable") @Schema(description = "证书有效标识, 缺省为0;证书有效标识:0:无效 1:有效") private Integer certifiable; // 2016 @MessageElementForCatalog("ErrCode") @Schema(description = "无效原因码(有证书且证书无效的设备必选)") private Integer errCode; // 2016 @MessageElementForCatalog("EndTime") @Schema(description = "证书终止有效期(有证书且证书无效的设备必选)") private String endTime; @MessageElementForCatalog("Secrecy") @Schema(description = "保密属性(必选)缺省为0;0-不涉密,1-涉密") private Integer secrecy; @MessageElementForCatalog("IPAddress") @Schema(description = "设备/系统IPv4/IPv6地址") private String ipAddress; @MessageElementForCatalog("Port") @Schema(description = "设备/系统端口") private Integer port; @MessageElementForCatalog("Password") @Schema(description = "设备口令") private String password; @MessageElementForCatalog("Status") @Schema(description = "设备状态") private String status; @MessageElementForCatalog("Longitude") @Schema(description = "经度 WGS-84坐标系") private Double longitude; @MessageElementForCatalog("Latitude") @Schema(description = ",纬度 WGS-84坐标系") private Double latitude; @MessageElementForCatalog("Info.PTZType") @Schema(description = "摄像机结构类型,标识摄像机类型: 1-球机; 2-半球; 3-固定枪机; 4-遥控枪机;5-遥控半球;6-多目设备的全景/拼接通道;7-多目设备的分割通道") private Integer ptzType; @MessageElementForCatalog("Info.PositionType") @Schema(description = "摄像机位置类型扩展。1-省际检查站、2-党政机关、3-车站码头、4-中心广场、5-体育场馆、" + "6-商业中心、7-宗教场所、8-校园周边、9-治安复杂区域、10-交通干线") private Integer positionType; @MessageElementForCatalog("Info.RoomType") @Schema(description = "摄像机安装位置室外、室内属性。1-室外、2-室内。") private Integer roomType; @MessageElementForCatalog("Info.UseType") @Schema(description = "用途属性, 1-治安、2-交通、3-重点。") private Integer useType; @MessageElementForCatalog("Info.SupplyLightType") @Schema(description = "摄像机补光属性。1-无补光;2-红外补光;3-白光补光;4-激光补光;9-其他") private Integer supplyLightType; @MessageElementForCatalog("Info.DirectionType") @Schema(description = "摄像机监视方位(光轴方向)属性。1-东(西向东)、2-西(东向西)、3-南(北向南)、4-北(南向北)、" + "5-东南(西北到东南)、6-东北(西南到东北)、7-西南(东北到西南)、8-西北(东南到西北)") private Integer directionType; @MessageElementForCatalog("Info.Resolution") @Schema(description = "摄像机支持的分辨率,可多值") private String resolution; @MessageElementForCatalog({"BusinessGroupID","Info.BusinessGroupID"}) @Schema(description = "虚拟组织所属的业务分组ID") private String businessGroupId; @MessageElementForCatalog("Info.DownloadSpeed") @Schema(description = "下载倍速(可选),可多值") private String downloadSpeed; @MessageElementForCatalog("Info.SVCSpaceSupportMode") @Schema(description = "空域编码能力,取值0-不支持;1-1级增强(1个增强层);2-2级增强(2个增强层);3-3级增强(3个增强层)") private Integer svcSpaceSupportMod; @MessageElementForCatalog("Info.SVCTimeSupportMode") @Schema(description = "时域编码能力,取值0-不支持;1-1级增强;2-2级增强;3-3级增强(可选)") private Integer svcTimeSupportMode; @Schema(description = "云台类型描述字符串") private String ptzTypeText; @Schema(description = "子设备数") private int subCount; @Schema(description = "是否含有音频") private boolean hasAudio; @Schema(description = "GPS的更新时间") private String gpsTime; @Schema(description = "码流标识,优先级高于设备中码流标识," + "用于选择码流时组成码流标识。默认为null,不设置。可选值: stream/streamnumber/streamprofile/streamMode") private String streamIdentification; @Schema(description = "通道类型, 默认0, 0: 普通通道,1 行政区划 2 业务分组/虚拟组织") private int channelType; private Integer dataType = ChannelDataType.GB28181; public void setPtzType(int ptzType) { this.ptzType = ptzType; switch (ptzType) { case 0: this.ptzTypeText = "未知"; break; case 1: this.ptzTypeText = "球机"; break; case 2: this.ptzTypeText = "半球"; break; case 3: this.ptzTypeText = "固定枪机"; break; case 4: this.ptzTypeText = "遥控枪机"; break; case 5: this.ptzTypeText = "遥控半球"; break; case 6: this.ptzTypeText = "多目设备的全景/拼接通道"; break; case 7: this.ptzTypeText = "多目设备的分割通道"; break; } } public static DeviceChannel decode(Element element) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException { DeviceChannel deviceChannel = XmlUtil.elementDecode(element, DeviceChannel.class); if(deviceChannel.getCivilCode() != null ) { if (ObjectUtils.isEmpty(deviceChannel.getCivilCode()) || deviceChannel.getCivilCode().length() > 8 ){ deviceChannel.setCivilCode(null); } // 此处对于不在wvp缓存中的行政区划,默认直接存储.保证即使出现wvp的行政区划缓存过老,也可以通过用户自主创建的方式正常使用系统 } GbCode gbCode = GbCode.decode(deviceChannel.getDeviceId()); if (gbCode != null && "138".equals(gbCode.getTypeCode())) { deviceChannel.setHasAudio(true); if (deviceChannel.getEnableBroadcast() == null && "138".equals(gbCode.getTypeCode())) { deviceChannel.setEnableBroadcast(1); } } return deviceChannel; } public static DeviceChannel decodeWithOnlyDeviceId(Element element) { Element deviceElement = element.element("DeviceID"); DeviceChannel deviceChannel = new DeviceChannel(); deviceChannel.setDeviceId(deviceElement.getText()); deviceChannel.setDataType(ChannelDataType.GB28181); return deviceChannel; } public CommonGBChannel buildCommonGBChannelForStatus() { CommonGBChannel commonGBChannel = new CommonGBChannel(); commonGBChannel.setGbId(id); commonGBChannel.setGbDeviceId(deviceId); commonGBChannel.setGbName(name); commonGBChannel.setDataType(ChannelDataType.GB28181); commonGBChannel.setDataDeviceId(getDataDeviceId()); return commonGBChannel; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceChannelInPlatform.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; public class DeviceChannelInPlatform extends DeviceChannel{ private String platFormId; private String catalogId; public String getPlatFormId() { return platFormId; } public void setPlatFormId(String platFormId) { this.platFormId = platFormId; } public String getCatalogId() { return catalogId; } public void setCatalogId(String catalogId) { this.catalogId = catalogId; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceNotFoundEvent.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import lombok.Data; import javax.sip.Dialog; import java.util.EventObject; @Data public class DeviceNotFoundEvent { private String callId; public DeviceNotFoundEvent(String callId) { this.callId = callId; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceType.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import org.jetbrains.annotations.NotNull; public class DeviceType implements Comparable{ /** * 编号 */ private String name; /** * 名称 */ private String code; /** * 归属名称 */ private String ownerName; public static DeviceType getInstance(DeviceTypeEnum typeEnum) { DeviceType deviceType = new DeviceType(); deviceType.setName(typeEnum.getName()); deviceType.setCode(typeEnum.getCode()); deviceType.setOwnerName(typeEnum.getOwnerName()); return deviceType; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } public String getOwnerName() { return ownerName; } public void setOwnerName(String ownerName) { this.ownerName = ownerName; } @Override public int compareTo(@NotNull DeviceType deviceType) { return Integer.compare(Integer.parseInt(this.code), Integer.parseInt(deviceType.getCode())); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceTypeEnum.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; /** * 收录行业编码 */ public enum DeviceTypeEnum { DVR("111", "DVR编码", "前端主设备"), VIDEO_SERVER("112", "视频服务器编码", "前端主设备"), ENCODER("113", "编码器编码", "前端主设备"), DECODER("114", "解码器编码", "前端主设备"), VIDEO_SWITCHING_MATRIX("115", "视频切换矩阵编码", "前端主设备"), AUDIO_SWITCHING_MATRIX("116", "音频切换矩阵编码", "前端主设备"), ALARM_CONTROLLER("117", "报警控制器编码", "前端主设备"), NVR("118", "网络视频录像机(NVR)编码", "前端主设备"), RESERVE("119", "预留", "前端主设备"), ONLINE_VIDEO_IMAGE_INFORMATION_ACQUISITION_SYSTEM("120", "在线视频图像信息采集系统编码", "前端主设备"), VIDEO_CHECKPOINT("121", "视频卡口编码", "前端主设备"), MULTI_CAMERA_DEVICE("122", "多目设备编码", "前端主设备"), PARKING_LOT_ENTRANCE_AND_EXIT_CONTROL_EQUIPMENT("123", "停车场出入口控制设备编码", "前端主设备"), PERSONNEL_ACCESS_CONTROL_EQUIPMENT("124", "人员出入口控制设备编码", "前端主设备"), SECURITY_INSPECTION_EQUIPMENT("125", "安检设备编码", "前端主设备"), HVR("130", "混合硬盘录像机(HVR)编码", "前端主设备"), CAMERA("131", "摄像机编码", "前端外围设备"), IPC("132", "网络摄像机(IPC)/在线视频图像信息采集设备编码", "前端外围设备"), MONITOR("133", "显示器编码", "前端外围设备"), ALARM_INPUT_DEVICE("134", "报警输入设备编码(如红外、烟感、门禁等报警设备)", "前端外围设备"), ALARM_OUTPUT_DEVICE("135", "报警输出设备编码(如警灯、警铃等设备)", "前端外围设备"), VOICE_INPUT_DEVICE("136", "语音输入设备编码", "前端外围设备"), VOICE_OUTPUT_DEVICE("137", "语音输出设备", "前端外围设备"), MOBILE_TRANSMISSION_EQUIPMENT("138", "移动传输设备编码", "前端外围设备"), OTHER_PERIPHERAL_DEVICES("139", "其他外围设备编码", "前端外围设备"), ALARM_OUTPUT_DEVICE2("140", "报警输出设备编码(如继电器或触发器控制的设备)", "前端外围设备"), BARRIER_GATE("141", "道闸(控制车辆通行)", "前端外围设备"), SMART_DOOR("142", "智能门(控制人员通行)", "前端外围设备"), VOUCHER_RECOGNITION_UNIT("143", "凭证识别单元", "前端外围设备"), CENTRAL_SIGNALING_CONTROL_SERVER("200", "中心信令控制服务器编码", "平台设备"), WEB_APPLICATION_SERVER("201", "Web应用服务器编码", "平台设备"), PROXY_SERVER("203", "代理服务器编码", "平台设备"), SECURITY_SERVER("204", "安全服务器编码", "平台设备"), ALARM_SERVER("205", "报警服务器编码", "平台设备"), DATABASE_SERVER("206", "数据库服务器编码", "平台设备"), GIS_SERVER("207", "GIS服务器编码", "平台设备"), MANAGER_SERVER("208", "管理服务器编码", "平台设备"), ACCESS_GATEWAY("209", "接入网关编码", "平台设备"), MEDIA_STORAGE_SERVER("210", "媒体存储服务器编码", "平台设备"), SIGNALING_SECURITY_ROUTING_GATEWAY("211", "信令安全路由网关编码", "平台设备"), BUSINESS_GROUP("215", "业务分组编码", "平台设备"), VIRTUAL_ORGANIZATION("216", "虚拟组织编码", "平台设备"), CENTRAL_USER("300", "中心用户", "中心用户"), END_USER("400", "终端用户", "终端用户"), VIDEO_IMAGE_INFORMATION_SYNTHESIS("500", "视频图像信息综合应用平台", "平台外接服务器"), VIDEO_IMAGE_INFORMATION_OPERATION_AND_MAINTENANCE_MANAGEMENT("501", "视频图像信息运维管理平台", "平台外接服务器"), VIDEO_IMAGE_ANALYSIS("502", "视频图像分析系统", "平台外接服务器"), VIDEO_IMAGE_INFORMATION_DATABASE("503", "视频图像信息数据库", "平台外接服务器"), VIDEO_IMAGE_ANALYSIS_EQUIPMENT("505", "视频图像分析设备", "平台外接服务器"), ; /** * 编号 */ private final String name; /** * 名称 */ private String code; /** * 归属名称 */ private String ownerName; DeviceTypeEnum(String code, String name, String ownerName) { this.name = name; this.code = code; this.ownerName = ownerName; } public String getName() { return name; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } public String getOwnerName() { return ownerName; } public void setOwnerName(String ownerName) { this.ownerName = ownerName; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/DragZoomParam.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import com.genersoft.iot.vmp.gb28181.utils.MessageElement; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @Data @Schema(description = "拉框放大/缩小控制参数") public class DragZoomParam { @MessageElement("Length") @Schema(description = "播放窗口长度像素值(必选)") protected Integer length; @MessageElement("Width") @Schema(description = "播放窗口宽度像素值(必选)") protected Integer width; @MessageElement("MidPointX") @Schema(description = "拉框中心的横轴坐标像素值(必选)") protected Integer midPointX; @MessageElement("MidPointY") @Schema(description = "拉框中心的纵轴坐标像素值(必选)") protected Integer midPointY; @MessageElement("LengthX") @Schema(description = "拉框长度像素值(必选)") protected Integer lengthX; @MessageElement("LengthY") @Schema(description = "拉框宽度像素值(必选)") protected Integer lengthY; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/DragZoomRequest.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import com.genersoft.iot.vmp.gb28181.utils.MessageElement; import lombok.Data; /** * 设备信息查询响应 * * @author Y.G * @version 1.0 * @date 2022/6/28 14:55 */ @Data public class DragZoomRequest { /** * 序列号 */ @MessageElement("SN") private String sn; @MessageElement("DeviceID") private String deviceId; @MessageElement(value = "DragZoomIn") private DragZoomParam dragZoomIn; @MessageElement(value = "DragZoomOut") private DragZoomParam dragZoomOut; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/DrawThinProcess.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import lombok.Getter; import lombok.Setter; @Getter @Setter public class DrawThinProcess { private double process; private String msg; public DrawThinProcess(double process, String msg) { this.process = process; this.msg = msg; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndCode.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import jakarta.validation.constraints.NotNull; import lombok.Data; /** * 解析收到的前端控制指令 */ @Data public class FrontEndCode { public static String encode(IFrontEndControlCode frontEndControlCode){ return frontEndControlCode.encode(); } public static IFrontEndControlCode decode(@NotNull String cmdStr) { if (cmdStr.length() != 16) { return null; } String cmdCodeStr = cmdStr.substring(6, 8); int cmdCode = Integer.parseInt(cmdCodeStr, 16); if (cmdCode < 39) { // PTZ指令 FrontEndControlCodeForPTZ codeForPTZ = new FrontEndControlCodeForPTZ(); int zoomOut = cmdCode >> 5 & 1; if (zoomOut == 1) { codeForPTZ.setZoom(0); } int zoomIn = cmdCode >> 4 & 1; if (zoomIn == 1) { codeForPTZ.setZoom(1); } int tiltUp = cmdCode >> 3 & 1; if (tiltUp == 1) { codeForPTZ.setTilt(0); } int tiltDown = cmdCode >> 2 & 1; if (tiltDown == 1) { codeForPTZ.setTilt(1); } int panLeft = cmdCode >> 1 & 1; if (panLeft == 1) { codeForPTZ.setPan(0); } int panRight = cmdCode & 1; if (panRight == 1) { codeForPTZ.setPan(1); } String param1Str = cmdStr.substring(8, 10); codeForPTZ.setPanSpeed(Integer.parseInt(param1Str, 16)); String param2Str = cmdStr.substring(10, 12); codeForPTZ.setTiltSpeed(Integer.parseInt(param2Str, 16)); String param3Str = cmdStr.substring(12, 13); codeForPTZ.setZoomSpeed(Integer.parseInt(param3Str, 16)); return codeForPTZ; }else if (cmdCode < 74) { // FI指令 FrontEndControlCodeForFI codeForFI = new FrontEndControlCodeForFI(); int irisOut = cmdCode >> 3 & 1; if (irisOut == 1) { codeForFI.setIris(0); } int irisIn = cmdCode >> 2 & 1; if (irisIn == 1) { codeForFI.setIris(1); } int focusNear = cmdCode >> 1 & 1; if (focusNear == 1) { codeForFI.setFocus(0); } int focusFar = cmdCode & 1; if (focusFar == 1) { codeForFI.setFocus(1); } String param1Str = cmdStr.substring(8, 10); codeForFI.setFocusSpeed(Integer.parseInt(param1Str, 16)); String param2Str = cmdStr.substring(10, 12); codeForFI.setIrisSpeed(Integer.parseInt(param2Str, 16)); return codeForFI; }else if (cmdCode < 131) { // 预置位指令 FrontEndControlCodeForPreset codeForPreset = new FrontEndControlCodeForPreset(); switch (cmdCode) { case 0x81: // 设置预置位 codeForPreset.setCode(1); break; case 0x82: // 调用预置位 codeForPreset.setCode(2); break; case 0x83: // 删除预置位 codeForPreset.setCode(3); break; default: return null; } // 预置位编号 String param2Str = cmdStr.substring(10, 12); codeForPreset.setPresetId(Integer.parseInt(param2Str, 16)); return codeForPreset; }else if (cmdCode < 136) { // 巡航指令 FrontEndControlCodeForTour codeForTour = new FrontEndControlCodeForTour(); String param3Str = cmdStr.substring(12, 13); switch (cmdCode) { case 0x84: // 加入巡航点 codeForTour.setCode(1); break; case 0x85: // 删除一个巡航点 codeForTour.setCode(2); break; case 0x86: // 设置巡航速度 codeForTour.setCode(3); codeForTour.setTourSpeed(Integer.parseInt(param3Str, 16)); break; case 0x87: // 设置巡航停留时间 codeForTour.setCode(4); codeForTour.setTourTime(Integer.parseInt(param3Str, 16)); break; case 0x88: // 开始巡航 codeForTour.setCode(5); break; default: return null; } String param1Str = cmdStr.substring(8, 10); codeForTour.setTourId(Integer.parseInt(param1Str, 16)); String param2Str = cmdStr.substring(10, 12); codeForTour.setPresetId(Integer.parseInt(param2Str, 16)); return codeForTour; }else if (cmdCode < 138) { // 扫描指令 FrontEndControlCodeForScan controlCodeForScan = new FrontEndControlCodeForScan(); String param2Str = cmdStr.substring(10, 11); int param2Code = Integer.parseInt(param2Str, 16); switch (cmdCode) { case 0x89: switch (param2Code) { case 0x00: // 开始自动扫描 controlCodeForScan.setCode(1); break; case 0x01: // 设置自动扫描左边界 controlCodeForScan.setCode(2); break; case 0x02: // 设置自动扫描右边界 controlCodeForScan.setCode(3); break; } break; case 0x8A: // 删除一个巡航点 controlCodeForScan.setCode(4); String param3Str = cmdStr.substring(12, 13); controlCodeForScan.setScanSpeed(Integer.parseInt(param3Str, 16)); break; default: return null; } String param1Str = cmdStr.substring(8, 10); controlCodeForScan.setScanId(Integer.parseInt(param1Str, 16)); return controlCodeForScan; }else if (cmdCode < 141) { // 辅助开关 FrontEndControlCodeForAuxiliary codeForAuxiliary = new FrontEndControlCodeForAuxiliary(); switch (cmdCode) { case 0x8C: // 开 codeForAuxiliary.setCode(1); break; case 0x8D: // 关 codeForAuxiliary.setCode(2); break; default: return null; } // 预置位编号 String param2Str = cmdStr.substring(10, 12); codeForAuxiliary.setAuxiliaryId(Integer.parseInt(param2Str, 16)); return codeForAuxiliary; }else { return null; } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlCodeForAuxiliary.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import lombok.Getter; import lombok.Setter; public class FrontEndControlCodeForAuxiliary implements IFrontEndControlCode { private final FrontEndControlType type = FrontEndControlType.AUXILIARY; @Override public FrontEndControlType getType() { return type; } /** * 辅助开关控制指令: 1为开, 2为关 */ @Getter @Setter private Integer code; /** * 辅助开关编号 */ @Getter @Setter private Integer auxiliaryId; @Override public String encode() { return ""; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlCodeForFI.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import lombok.Getter; import lombok.Setter; public class FrontEndControlCodeForFI implements IFrontEndControlCode { private final FrontEndControlType type = FrontEndControlType.FI; @Override public FrontEndControlType getType() { return type; } /** * 光圈,0为缩小 1为放大 */ @Getter @Setter private Integer iris; /** * 聚焦 0 近, 1远 */ @Getter @Setter private Integer focus; /** * 聚焦速度 */ @Getter @Setter private Integer focusSpeed; /** * 光圈速度 */ @Getter @Setter private Integer irisSpeed; @Override public String encode() { return ""; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlCodeForPTZ.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import lombok.Getter; import lombok.Setter; public class FrontEndControlCodeForPTZ implements IFrontEndControlCode { private final FrontEndControlType type = FrontEndControlType.PTZ; @Override public FrontEndControlType getType() { return type; } /** * 镜头变倍,0为缩小 1为放大 */ @Getter @Setter private Integer zoom; /** * 云台垂直方向控制 0 为上, 1为下 */ @Getter @Setter private Integer tilt; /** * 云台水平方向控制 0 为左, 1为右 */ @Getter @Setter private Integer pan; /** * 水平控制速度相对值 */ @Getter @Setter private Integer panSpeed; /** * 垂直控制速度相对值 */ @Getter @Setter private Integer tiltSpeed; /** * 变倍控制速度相对值 */ @Getter @Setter private Integer zoomSpeed; @Override public String encode() { return ""; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlCodeForPreset.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import lombok.Getter; import lombok.Setter; public class FrontEndControlCodeForPreset implements IFrontEndControlCode { private final FrontEndControlType type = FrontEndControlType.PRESET; @Override public FrontEndControlType getType() { return type; } /** * 预置位指令: 1为设置预置位, 2为调用预置位, 3为删除预置位 */ @Getter @Setter private Integer code; /** * 预置位编号 */ @Getter @Setter private Integer presetId; /** * 预置位名称 */ @Getter @Setter private String presetName; @Override public String encode() { return ""; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlCodeForScan.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import lombok.Getter; import lombok.Setter; public class FrontEndControlCodeForScan implements IFrontEndControlCode { private final FrontEndControlType type = FrontEndControlType.SCAN; @Override public FrontEndControlType getType() { return type; } /** * 预置位指令: 1为开始自动扫描, 2为设置自动扫描左边界, 3为设置自动扫描右边界, 4为设置自动扫描速度, 5为停止自动扫描 */ @Getter @Setter private Integer code; /** * 自动扫描速度 */ @Getter @Setter private Integer scanSpeed; /** * 扫描组号 */ @Getter @Setter private Integer scanId; @Override public String encode() { return ""; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlCodeForTour.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import lombok.Getter; import lombok.Setter; public class FrontEndControlCodeForTour implements IFrontEndControlCode { private final FrontEndControlType type = FrontEndControlType.TOUR; @Override public FrontEndControlType getType() { return type; } /** * 巡航指令: 1为加入巡航点, 2为删除一个巡航点, 3为设置巡航速度, 4为设置巡航停留时间, 5为开始巡航, 6为停止巡航 */ @Getter @Setter private Integer code; /** * 巡航点 */ @Getter @Setter private Integer tourId; /** * 巡航停留时间 */ @Getter @Setter private Integer tourTime; /** * 巡航速度 */ @Getter @Setter private Integer tourSpeed; /** * 预置位编号 */ @Getter @Setter private Integer presetId; @Override public String encode() { return ""; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlCodeForWiper.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import lombok.Getter; import lombok.Setter; public class FrontEndControlCodeForWiper implements IFrontEndControlCode { private final FrontEndControlType type = FrontEndControlType.AUXILIARY; @Override public FrontEndControlType getType() { return type; } /** * 辅助开关控制指令: 1为开, 2为关 */ @Getter @Setter private Integer code; @Override public String encode() { return ""; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlType.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; public enum FrontEndControlType { PTZ,FI,PRESET,TOUR,SCAN,AUXILIARY } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/GBStringMsgParser.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import gov.nist.core.Host; import gov.nist.core.HostNameParser; import gov.nist.javax.sip.SIPConstants; import gov.nist.javax.sip.address.AddressImpl; import gov.nist.javax.sip.address.GenericURI; import gov.nist.javax.sip.address.SipUri; import gov.nist.javax.sip.address.TelephoneNumber; import gov.nist.javax.sip.header.*; import gov.nist.javax.sip.message.SIPMessage; import gov.nist.javax.sip.message.SIPRequest; import gov.nist.javax.sip.message.SIPResponse; import gov.nist.javax.sip.parser.*; import lombok.extern.slf4j.Slf4j; import java.io.UnsupportedEncodingException; import java.text.ParseException; @Slf4j public class GBStringMsgParser implements MessageParser { protected static boolean computeContentLengthFromMessage = false; /** * @since v0.9 */ public GBStringMsgParser() { super(); } /** * Parse a buffer containing a single SIP Message where the body is an array * of un-interpreted bytes. This is intended for parsing the message from a * memory buffer when the buffer. Incorporates a bug fix for a bug that was * noted by Will Sullin of Callcast * * @param msgBuffer * a byte buffer containing the messages to be parsed. This can * consist of multiple SIP Messages concatenated together. * @return a SIPMessage[] structure (request or response) containing the * parsed SIP message. * @exception ParseException * is thrown when an illegal message has been encountered * (and the rest of the buffer is discarded). * @see ParseExceptionListener */ public SIPMessage parseSIPMessage(byte[] msgBuffer, boolean readBody, boolean strict, ParseExceptionListener parseExceptionListener) throws ParseException { if (msgBuffer == null || msgBuffer.length == 0) return null; int i = 0; // Squeeze out any leading control character. try { while (msgBuffer[i] < 0x20) i++; } catch (ArrayIndexOutOfBoundsException e) { // Array contains only control char, return null. if (log.isDebugEnabled()) { log.debug("handled only control char so returning null"); } return null; } // Iterate thru the request/status line and headers. String currentLine = null; String currentHeader = null; boolean isFirstLine = true; SIPMessage message = null; do { int lineStart = i; // Find the length of the line. try { while (msgBuffer[i] != '\r' && msgBuffer[i] != '\n') i++; } catch (ArrayIndexOutOfBoundsException e) { // End of the message. break; } int lineLength = i - lineStart; // Make it a String. try { currentLine = new String(msgBuffer, lineStart, lineLength, "UTF-8"); } catch (UnsupportedEncodingException e) { throw new ParseException("Bad message encoding!", 0); } currentLine = trimEndOfLine(currentLine); if (currentLine.length() == 0) { // Last header line, process the previous buffered header. if (currentHeader != null && message != null) { processHeader(currentHeader, message, parseExceptionListener, msgBuffer); } } else { if (isFirstLine) { message = processFirstLine(currentLine, parseExceptionListener, msgBuffer); } else { char firstChar = currentLine.charAt(0); if (firstChar == '\t' || firstChar == ' ') { if (currentHeader == null) throw new ParseException("Bad header continuation.", 0); // This is a continuation, append it to the previous line. currentHeader += currentLine.substring(1); } else { if (currentHeader != null && message != null) { processHeader(currentHeader, message, parseExceptionListener, msgBuffer); } currentHeader = currentLine; } } } if (msgBuffer[i] == '\r' && msgBuffer.length > i+1 && msgBuffer[i+1] == '\n') i++; i++; isFirstLine = false; } while (currentLine.length() > 0); // End do - while if (message == null) throw new ParseException("Bad message", 0); message.setSize(i); // Check for content legth header if (readBody && message.getContentLength() != null ) { if ( message.getContentLength().getContentLength() != 0) { int bodyLength = msgBuffer.length - i; byte[] body = new byte[bodyLength]; System.arraycopy(msgBuffer, i, body, 0, bodyLength); message.setMessageContent(body,!strict,computeContentLengthFromMessage,message.getContentLength().getContentLength()); } else if (message.getCSeqHeader().getMethod().equalsIgnoreCase("MESSAGE")) { int bodyLength = msgBuffer.length - i; byte[] body = new byte[bodyLength]; System.arraycopy(msgBuffer, i, body, 0, bodyLength); message.setMessageContent(body,!strict,computeContentLengthFromMessage,bodyLength); }else if (!computeContentLengthFromMessage && strict) { String last4Chars = new String(msgBuffer, msgBuffer.length - 4, 4); if(!"\r\n\r\n".equals(last4Chars)) { throw new ParseException("Extraneous characters at the end of the message ",i); } } } return message; } protected static String trimEndOfLine(String line) { if (line == null) return line; int i = line.length() - 1; while (i >= 0 && line.charAt(i) <= 0x20) i--; if (i == line.length() - 1) return line; if (i == -1) return ""; return line.substring(0, i+1); } protected SIPMessage processFirstLine(String firstLine, ParseExceptionListener parseExceptionListener, byte[] msgBuffer) throws ParseException { SIPMessage message; if (!firstLine.startsWith(SIPConstants.SIP_VERSION_STRING)) { message = new SIPRequest(); try { RequestLine requestLine = new RequestLineParser(firstLine + "\n") .parse(); ((SIPRequest) message).setRequestLine(requestLine); } catch (ParseException ex) { if (parseExceptionListener != null) try { parseExceptionListener.handleException(ex, message, RequestLine.class, firstLine, new String(msgBuffer, "UTF-8")); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } else throw ex; } } else { message = new SIPResponse(); try { StatusLine sl = new StatusLineParser(firstLine + "\n").parse(); ((SIPResponse) message).setStatusLine(sl); } catch (ParseException ex) { if (parseExceptionListener != null) { try { parseExceptionListener.handleException(ex, message, StatusLine.class, firstLine, new String(msgBuffer, "UTF-8")); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } else throw ex; } } return message; } protected void processHeader(String header, SIPMessage message, ParseExceptionListener parseExceptionListener, byte[] rawMessage) throws ParseException { if (header == null || header.length() == 0) return; HeaderParser headerParser = null; try { headerParser = ParserFactory.createParser(header + "\n"); } catch (ParseException ex) { // https://java.net/jira/browse/JSIP-456 if (parseExceptionListener != null) { parseExceptionListener.handleException(ex, message, null, header, null); return; } else { throw ex; } } try { SIPHeader sipHeader = headerParser.parse(); message.attachHeader(sipHeader, false); } catch (ParseException ex) { if (parseExceptionListener != null) { String headerName = Lexer.getHeaderName(header); Class headerClass = NameMap.getClassFromName(headerName); if (headerClass == null) { headerClass = ExtensionHeaderImpl.class; } try { parseExceptionListener.handleException(ex, message, headerClass, header, new String(rawMessage, "UTF-8")); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } } } /** * Parse an address (nameaddr or address spec) and return and address * structure. * * @param address * is a String containing the address to be parsed. * @return a parsed address structure. * @since v1.0 * @exception ParseException * when the address is badly formatted. */ public AddressImpl parseAddress(String address) throws ParseException { AddressParser addressParser = new AddressParser(address); return addressParser.address(true); } /** * Parse a host:port and return a parsed structure. * * @param hostport * is a String containing the host:port to be parsed * @return a parsed address structure. * @since v1.0 * @exception throws * a ParseException when the address is badly formatted. * public HostPort parseHostPort(String hostport) throws ParseException { Lexer lexer = new Lexer("charLexer", hostport); return new HostNameParser(lexer).hostPort(); } */ /** * Parse a host name and return a parsed structure. * * @param host * is a String containing the host name to be parsed * @return a parsed address structure. * @since v1.0 * @exception ParseException * a ParseException when the hostname is badly formatted. */ public Host parseHost(String host) throws ParseException { Lexer lexer = new Lexer("charLexer", host); return new HostNameParser(lexer).host(); } /** * Parse a telephone number return a parsed structure. * * @param telephone_number * is a String containing the telephone # to be parsed * @return a parsed address structure. * @since v1.0 * @exception ParseException * a ParseException when the address is badly formatted. */ public TelephoneNumber parseTelephoneNumber(String telephone_number) throws ParseException { // Bug fix contributed by Will Scullin return new URLParser(telephone_number).parseTelephoneNumber(true); } /** * Parse a SIP url from a string and return a URI structure for it. * * @param url * a String containing the URI structure to be parsed. * @return A parsed URI structure * @exception ParseException * if there was an error parsing the message. */ public SipUri parseSIPUrl(String url) throws ParseException { try { return new URLParser(url).sipURL(true); } catch (ClassCastException ex) { throw new ParseException(url + " Not a SIP URL ", 0); } } /** * Parse a uri from a string and return a URI structure for it. * * @param url * a String containing the URI structure to be parsed. * @return A parsed URI structure * @exception ParseException * if there was an error parsing the message. */ public GenericURI parseUrl(String url) throws ParseException { return new URLParser(url).parse(); } /** * Parse an individual SIP message header from a string. * * @param header * String containing the SIP header. * @return a SIPHeader structure. * @exception ParseException * if there was an error parsing the message. */ public static SIPHeader parseSIPHeader(String header) throws ParseException { int start = 0; int end = header.length() - 1; try { // Squeeze out any leading control character. while (header.charAt(start) <= 0x20) start++; // Squeeze out any trailing control character. while (header.charAt(end) <= 0x20) end--; } catch (ArrayIndexOutOfBoundsException e) { // Array contains only control char. throw new ParseException("Empty header.", 0); } StringBuilder buffer = new StringBuilder(end + 1); int i = start; int lineStart = start; boolean endOfLine = false; while (i <= end) { char c = header.charAt(i); if (c == '\r' || c == '\n') { if (!endOfLine) { buffer.append(header.substring(lineStart, i)); endOfLine = true; } } else { if (endOfLine) { endOfLine = false; if (c == ' ' || c == '\t') { buffer.append(' '); lineStart = i + 1; } else { lineStart = i; } } } i++; } buffer.append(header.substring(lineStart, i)); buffer.append('\n'); HeaderParser hp = ParserFactory.createParser(buffer.toString()); if (hp == null) throw new ParseException("could not create parser", 0); return hp.parse(); } /** * Parse the SIP Request Line * * @param requestLine * a String containing the request line to be parsed. * @return a RequestLine structure that has the parsed RequestLine * @exception ParseException * if there was an error parsing the requestLine. */ public RequestLine parseSIPRequestLine(String requestLine) throws ParseException { requestLine += "\n"; return new RequestLineParser(requestLine).parse(); } /** * Parse the SIP Response message status line * * @param statusLine * a String containing the Status line to be parsed. * @return StatusLine class corresponding to message * @exception ParseException * if there was an error parsing * @see StatusLine */ public StatusLine parseSIPStatusLine(String statusLine) throws ParseException { statusLine += "\n"; return new StatusLineParser(statusLine).parse(); } public static void setComputeContentLengthFromMessage( boolean computeContentLengthFromMessage) { GBStringMsgParser.computeContentLengthFromMessage = computeContentLengthFromMessage; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/Gb28181Sdp.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import javax.sdp.SessionDescription; /** * 28181 的SDP解析器 */ public class Gb28181Sdp { private SessionDescription baseSdb; private String ssrc; private String mediaDescription; public static Gb28181Sdp getInstance(SessionDescription baseSdb, String ssrc, String mediaDescription) { Gb28181Sdp gb28181Sdp = new Gb28181Sdp(); gb28181Sdp.setBaseSdb(baseSdb); gb28181Sdp.setSsrc(ssrc); gb28181Sdp.setMediaDescription(mediaDescription); return gb28181Sdp; } public SessionDescription getBaseSdb() { return baseSdb; } public void setBaseSdb(SessionDescription baseSdb) { this.baseSdb = baseSdb; } public String getSsrc() { return ssrc; } public void setSsrc(String ssrc) { this.ssrc = ssrc; } public String getMediaDescription() { return mediaDescription; } public void setMediaDescription(String mediaDescription) { this.mediaDescription = mediaDescription; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/GbCode.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; /** * 国标编码对象 */ @Data @Schema(description = "国标编码对象") public class GbCode { @Schema(description = "中心编码,由监控中心所在地的行政区划代码确定,符合GB/T2260—2007的要求") private String centerCode; @Schema(description = "行业编码") private String industryCode; @Schema(description = "类型编码") private String typeCode; @Schema(description = "网络标识") private String netCode; @Schema(description = "序号") private String sn; /** * 解析国标编号 */ public static GbCode decode(String code){ if (code == null || code.trim().length() != 20 || !code.matches("\\d{20}")) { return null; } code = code.trim(); GbCode gbCode = new GbCode(); gbCode.setCenterCode(code.substring(0, 8)); gbCode.setIndustryCode(code.substring(8, 10)); gbCode.setTypeCode(code.substring(10, 13)); gbCode.setNetCode(code.substring(13, 14)); gbCode.setSn(code.substring(14)); return gbCode; } public String ecode(){ return centerCode + industryCode + typeCode + netCode + sn; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/GbSipDate.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import gov.nist.core.InternalErrorHandler; import gov.nist.javax.sip.header.SIPDate; import java.io.Serial; import java.util.*; /** * 重写jain sip的SIPDate解决与国标时间格式不一致的问题 */ public class GbSipDate extends SIPDate { @Serial private static final long serialVersionUID = 1L; private Calendar javaCal; public GbSipDate(long timeMillis) { this.javaCal = new GregorianCalendar(TimeZone.getDefault(), Locale.getDefault()); Date date = new Date(timeMillis); this.javaCal.setTime(date); this.wkday = this.javaCal.get(7); switch(this.wkday) { case 1: this.sipWkDay = "Sun"; break; case 2: this.sipWkDay = "Mon"; break; case 3: this.sipWkDay = "Tue"; break; case 4: this.sipWkDay = "Wed"; break; case 5: this.sipWkDay = "Thu"; break; case 6: this.sipWkDay = "Fri"; break; case 7: this.sipWkDay = "Sat"; break; default: InternalErrorHandler.handleException("No date map for wkday " + this.wkday); } this.day = this.javaCal.get(5); this.month = this.javaCal.get(2); switch(this.month) { case 0: this.sipMonth = "Jan"; break; case 1: this.sipMonth = "Feb"; break; case 2: this.sipMonth = "Mar"; break; case 3: this.sipMonth = "Apr"; break; case 4: this.sipMonth = "May"; break; case 5: this.sipMonth = "Jun"; break; case 6: this.sipMonth = "Jul"; break; case 7: this.sipMonth = "Aug"; break; case 8: this.sipMonth = "Sep"; break; case 9: this.sipMonth = "Oct"; break; case 10: this.sipMonth = "Nov"; break; case 11: this.sipMonth = "Dec"; break; default: InternalErrorHandler.handleException("No date map for month " + this.month); } this.year = this.javaCal.get(1); this.hour = this.javaCal.get(11); this.minute = this.javaCal.get(12); this.second = this.javaCal.get(13); } @Override public StringBuilder encode(StringBuilder var1) { String var2; if (this.month < 9) { var2 = "0" + (this.month + 1); } else { var2 = "" + (this.month + 1); } String var3; if (this.day < 10) { var3 = "0" + this.day; } else { var3 = "" + this.day; } String var4; if (this.hour < 10) { var4 = "0" + this.hour; } else { var4 = "" + this.hour; } String var5; if (this.minute < 10) { var5 = "0" + this.minute; } else { var5 = "" + this.minute; } String var6; if (this.second < 10) { var6 = "0" + this.second; } else { var6 = "" + this.second; } int var8 = this.javaCal.get(14); String var7; if (var8 < 10) { var7 = "00" + var8; } else if (var8 < 100) { var7 = "0" + var8; } else { var7 = "" + var8; } return var1.append(this.year).append("-").append(var2).append("-").append(var3).append("T").append(var4).append(":").append(var5).append(":").append(var6).append(".").append(var7); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/GbSteamIdentification.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; /** * 码流索引标识 */ public enum GbSteamIdentification { /** * 主码流 stream:0 * 子码流 stream:1s */ streamMain("stream", new String[]{"0","1"}), /** * 国标28181-2022定义的方式 * 主码流 streamnumber:0 * 子码流 streamnumber:1 */ streamnumber("streamnumber", new String[]{"0","1"}), /** * 主码流 streamprofile:0 * 子码流 streamprofile:1 */ streamprofile("streamprofile", new String[]{"0","1"}), /** * 适用的品牌: TP-LINK */ streamMode("streamMode", new String[]{"main","sub"}), ; GbSteamIdentification(String value, String[] indexArray) { this.value = value; this.indexArray = indexArray; } private String value; private String[] indexArray; public String getValue() { return value; } public String[] getIndexArray() { return indexArray; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/GbStream.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import io.swagger.v3.oas.annotations.media.Schema; /** * 直播流关联国标上级平台 * @author lin */ @Schema(description = "直播流关联国标上级平台") public class GbStream extends PlatformGbStream{ @Schema(description = "ID") private int gbStreamId; @Schema(description = "应用名") private String app; @Schema(description = "流ID") private String stream; @Schema(description = "国标ID") private String gbId; @Schema(description = "名称") private String name; @Schema(description = "流媒体ID") private String mediaServerId; @Schema(description = "经度") private double longitude; @Schema(description = "纬度") private double latitude; @Schema(description = "流类型(拉流/推流)") private String streamType; @Schema(description = "状态") private boolean status; @Schema(description = "创建时间") public String createTime; @Override public Integer getGbStreamId() { return gbStreamId; } @Override public void setGbStreamId(Integer gbStreamId) { this.gbStreamId = gbStreamId; } public String getApp() { return app; } public void setApp(String app) { this.app = app; } public String getStream() { return stream; } public void setStream(String stream) { this.stream = stream; } public String getGbId() { return gbId; } public void setGbId(String gbId) { this.gbId = gbId; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getLongitude() { return longitude; } public void setLongitude(double longitude) { this.longitude = longitude; } public double getLatitude() { return latitude; } public void setLatitude(double latitude) { this.latitude = latitude; } public String getStreamType() { return streamType; } public void setStreamType(String streamType) { this.streamType = streamType; } public boolean isStatus() { return status; } public void setStatus(boolean status) { this.status = status; } public String getMediaServerId() { return mediaServerId; } public void setMediaServerId(String mediaServerId) { this.mediaServerId = mediaServerId; } public String getCreateTime() { return createTime; } public void setCreateTime(String createTime) { this.createTime = createTime; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/GbStringMsgParserFactory.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import gov.nist.javax.sip.parser.MessageParser; import gov.nist.javax.sip.parser.MessageParserFactory; import gov.nist.javax.sip.stack.SIPTransactionStack; public class GbStringMsgParserFactory implements MessageParserFactory { /** * msg parser is completely stateless, reuse isntance for the whole stack * fixes https://github.com/RestComm/jain-sip/issues/92 */ private static GBStringMsgParser msgParser = new GBStringMsgParser(); /* * (non-Javadoc) * @see gov.nist.javax.sip.parser.MessageParserFactory#createMessageParser(gov.nist.javax.sip.stack.SIPTransactionStack) */ public MessageParser createMessageParser(SIPTransactionStack stack) { return msgParser; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/Group.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import com.genersoft.iot.vmp.utils.DateUtil; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import org.jetbrains.annotations.NotNull; /** * 业务分组 */ @Data @Schema(description = "业务分组") public class Group implements Comparable{ /** * 数据库自增ID */ @Schema(description = "数据库自增ID") private int id; /** * 区域国标编号 */ @Schema(description = "区域国标编号") private String deviceId; /** * 区域名称 */ @Schema(description = "区域名称") private String name; /** * 父分组ID */ @Schema(description = "父分组ID") private Integer parentId; /** * 父区域国标ID */ @Schema(description = "父区域国标ID") private String parentDeviceId; /** * 所属的业务分组国标编号 */ @Schema(description = "所属的业务分组国标编号") private String businessGroup; /** * 创建时间 */ @Schema(description = "创建时间") private String createTime; /** * 更新时间 */ @Schema(description = "更新时间") private String updateTime; /** * 行政区划 */ @Schema(description = "行政区划") private String civilCode; /** * 别名 */ @Schema(description = "别名, 此别名为唯一值,可以对接第三方是存储对方的ID") private String alias; public static Group getInstance(DeviceChannel channel) { GbCode gbCode = GbCode.decode(channel.getDeviceId()); if (gbCode == null || (!gbCode.getTypeCode().equals("215") && !gbCode.getTypeCode().equals("216"))) { return null; } Group group = new Group(); group.setName(channel.getName()); group.setDeviceId(channel.getDeviceId()); group.setCreateTime(DateUtil.getNow()); group.setUpdateTime(DateUtil.getNow()); if (gbCode.getTypeCode().equals("215")) { group.setBusinessGroup(channel.getDeviceId()); }else if (gbCode.getTypeCode().equals("216")) { group.setBusinessGroup(channel.getBusinessGroupId()); group.setParentDeviceId(channel.getParentId()); } if (group.getBusinessGroup() == null) { return null; } return group; } @Override public int compareTo(@NotNull Group region) { return Integer.compare(Integer.parseInt(this.deviceId), Integer.parseInt(region.getDeviceId())); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/GroupTree.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; /** * 业务分组 */ @EqualsAndHashCode(callSuper = true) @Data @Schema(description = "业务分组树") public class GroupTree extends Group{ @Schema(description = "树节点ID") private String treeId; @Schema(description = "是否有子节点") private boolean isLeaf; @Schema(description = "类型, 行政区划:0 摄像头: 1") private int type; @Schema(description = "在线状态") private String status; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/HandlerCatchData.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import org.dom4j.Element; import javax.sip.RequestEvent; /** * @author lin */ public class HandlerCatchData { private RequestEvent evt; private Device device; private Element rootElement; public HandlerCatchData(RequestEvent evt, Device device, Element rootElement) { this.evt = evt; this.device = device; this.rootElement = rootElement; } public RequestEvent getEvt() { return evt; } public void setEvt(RequestEvent evt) { this.evt = evt; } public Device getDevice() { return device; } public void setDevice(Device device) { this.device = device; } public Element getRootElement() { return rootElement; } public void setRootElement(Element rootElement) { this.rootElement = rootElement; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/HomePositionRequest.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import com.genersoft.iot.vmp.gb28181.utils.MessageElement; /** * 设备信息查询响应 * * @author Y.G * @version 1.0 * @date 2022/6/28 14:55 */ public class HomePositionRequest { /** * 序列号 */ @MessageElement("SN") private String sn; @MessageElement("DeviceID") private String deviceId; @MessageElement(value = "HomePosition") private HomePosition homePosition; /** * 基本参数 */ public static class HomePosition { /** * 播放窗口长度像素值 */ @MessageElement("Enabled") protected String enabled; /** * 播放窗口宽度像素值 */ @MessageElement("ResetTime") protected String resetTime; /** * 拉框中心的横轴坐标像素值 */ @MessageElement("PresetIndex") protected String presetIndex; public String getEnabled() { return enabled; } public void setEnabled(String enabled) { this.enabled = enabled; } public String getResetTime() { return resetTime; } public void setResetTime(String resetTime) { this.resetTime = resetTime; } public String getPresetIndex() { return presetIndex; } public void setPresetIndex(String presetIndex) { this.presetIndex = presetIndex; } } public String getSn() { return sn; } public void setSn(String sn) { this.sn = sn; } public String getDeviceId() { return deviceId; } public void setDeviceId(String deviceId) { this.deviceId = deviceId; } public HomePosition getHomePosition() { return homePosition; } public void setHomePosition(HomePosition homePosition) { this.homePosition = homePosition; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/Host.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; public class Host { private String ip; private int port; private String address; public String getIp() { return ip; } public void setIp(String ip) { this.ip = ip; } public int getPort() { return port; } public void setPort(int port) { this.port = port; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/IFrontEndControlCode.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; public interface IFrontEndControlCode { FrontEndControlType getType(); String encode(); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/IndustryCodeType.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import org.jetbrains.annotations.NotNull; public class IndustryCodeType implements Comparable{ /** * 接入类型码 */ private String name; /** * 名称 */ private String code; /** * 备注 */ private String notes; public static IndustryCodeType getInstance(IndustryCodeTypeEnum typeEnum) { IndustryCodeType industryCodeType = new IndustryCodeType(); industryCodeType.setName(typeEnum.getName()); industryCodeType.setCode(typeEnum.getCode()); industryCodeType.setNotes(typeEnum.getNotes()); return industryCodeType; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } public String getNotes() { return notes; } public void setNotes(String notes) { this.notes = notes; } @Override public int compareTo(@NotNull IndustryCodeType industryCodeType) { return Integer.compare(Integer.parseInt(this.code), Integer.parseInt(industryCodeType.getCode())); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/IndustryCodeTypeEnum.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import lombok.Getter; /** * 收录行业编码 */ public enum IndustryCodeTypeEnum { SOCIAL_SECURITY_ROAD("00", "社会治安路面接入", "包括城市路面、商业街、公共区域、重点区域"), SOCIAL_SECURITY_COMMUNITY("01", "社会治安社区接入", "包括社区、楼宇、网吧等"), SOCIAL_SECURITY__INTERNAL("02", "社会治安内部接入 ", "包括公安办公楼、留置室等"), SOCIAL_SECURITY_OTHER("03", "社会治安其他接入", ""), TRAFFIC_ROAD("04", "交通路面接入 ", "包括城市主要干道、国道、高速交通状况监视"), TRAFFIC_BAYONET("05", "交通卡口接入", "包括交叉路口、“电子警察”、关口、收费站等"), TRAFFIC_INTERNAL("06", "交通内部接入", "包括交管办公楼等"), TRAFFIC_OTHER("07", "交通其他接入", ""), CITY_MANAGEMENT("08", "城市管理接入", ""), HEALTH_ENVIRONMENTAL_PROTECTION("09", "卫生环保接入", ""), COMMODITY_INSPECTION_CUSTOMHOUSE("10", "商检海关接入", ""), EDUCATION_SECTOR("11", "教育部门接入", ""), CIVIL_AVIATION("12", "民航接入", ""), RAILWAY("13", "铁路接入", ""), SHIPPING("14", "航运接入", ""), AGRICULTURE_FORESTRY_ANIMAL_HUSBANDRY_FISHING("40", "农、林、牧、渔业接入", ""), MINING("41", "采矿业接入", ""), MANUFACTURING_INDUSTRY("42", "制造业接入", ""), ELECTRICITY_HEAT_GAS_AND_WATER_PRODUCTION_AND_SUPPLY("43", "电力、热力、燃气及水生产和供应业接入", ""), CONSTRUCTION("44", "建筑业接入", ""), WHOLESALE_AND_RETAIL("45", "批发和零售业接入", ""), ; /** * 接入类型码 */ @Getter private String name; /** * 名称 */ @Getter private String code; /** * 备注 */ @Getter private String notes; IndustryCodeTypeEnum(String code, String name, String notes) { this.name = name; this.code = code; this.notes = notes; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/InviteDecodeException.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import lombok.Data; @Data public class InviteDecodeException extends RuntimeException{ private int code; private String msg; public InviteDecodeException(int code, String msg) { this.code = code; this.msg = msg; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/InviteMessageInfo.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import lombok.Data; // 从INVITE消息中解析需要的信息 @Data public class InviteMessageInfo { private String requesterId; private String targetChannelId; private String sourceChannelId; private String sessionName; private String ssrc; private boolean tcp; private boolean tcpActive; private String callId; private Long startTime; private Long stopTime; private String downloadSpeed; private String ip; private int port; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/InviteStreamCallback.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; public interface InviteStreamCallback { void call(InviteStreamInfo inviteStreamInfo); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/InviteStreamInfo.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.media.bean.MediaServer; public class InviteStreamInfo { public InviteStreamInfo(MediaServer mediaServerItem, JSONObject response, String callId, String app, String stream) { this.mediaServerItem = mediaServerItem; this.response = response; this.callId = callId; this.app = app; this.stream = stream; } private MediaServer mediaServerItem; private JSONObject response; private String callId; private String app; private String stream; public MediaServer getMediaServerItem() { return mediaServerItem; } public void setMediaServerItem(MediaServer mediaServerItem) { this.mediaServerItem = mediaServerItem; } public JSONObject getResponse() { return response; } public void setResponse(JSONObject response) { this.response = response; } public String getCallId() { return callId; } public void setCallId(String callId) { this.callId = callId; } public String getApp() { return app; } public void setApp(String app) { this.app = app; } public String getStream() { return stream; } public void setStream(String stream) { this.stream = stream; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/InviteStreamType.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; public enum InviteStreamType { PLAY,PLAYBACK,DOWNLOAD,PUSH,PROXY,CLOUD_RECORD_PUSH,CLOUD_RECORD_PROXY,BROADCAST,TALK } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/MessageResponseTask.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import lombok.Getter; import lombok.Setter; import org.dom4j.Element; import org.jetbrains.annotations.NotNull; import java.util.List; import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit; public class MessageResponseTask implements Delayed { @Getter @Setter private Element element; @Getter @Setter private List data; @Getter @Setter private String key; /** * 超时时间(单位: 毫秒) */ @Getter @Setter private long delayTime; @Override public long getDelay(@NotNull TimeUnit unit) { return unit.convert(delayTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS); } @Override public int compareTo(@NotNull Delayed o) { return (int) (this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS)); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/MobilePosition.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import lombok.Data; /** * @description: 移动位置bean * @author: lawrencehj * @date: 2021年1月23日 */ @Data public class MobilePosition { /** * 设备Id */ private String deviceId; /** * 通道Id */ private Integer channelId; /** * 通道国标编号 */ private String channelDeviceId; /** * 设备名称 */ private String deviceName; /** * 通知时间 */ private String time; /** * 经度 */ private double longitude; /** * 纬度 */ private double latitude; /** * 海拔高度 */ private double altitude; /** * 速度 */ private double speed; /** * 方向 */ private double direction; /** * 位置信息上报来源(Mobile Position、GPS Alarm) */ private String reportSource; /** * 创建时间 */ private String createTime; @Override public String toString() { return "MobilePosition{" + "deviceId='" + deviceId + '\'' + ", channelId=" + channelId + ", channelDeviceId='" + channelDeviceId + '\'' + ", deviceName='" + deviceName + '\'' + ", time='" + time + '\'' + ", longitude=" + longitude + ", latitude=" + latitude + ", altitude=" + altitude + ", speed=" + speed + ", direction=" + direction + ", reportSource='" + reportSource + '\'' + ", createTime='" + createTime + '\'' + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/NetworkIdentificationType.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import org.jetbrains.annotations.NotNull; public class NetworkIdentificationType implements Comparable{ /** * 接入类型码 */ private String name; /** * 名称 */ private String code; public static NetworkIdentificationType getInstance(NetworkIdentificationTypeEnum typeEnum) { NetworkIdentificationType industryCodeType = new NetworkIdentificationType(); industryCodeType.setName(typeEnum.getName()); industryCodeType.setCode(typeEnum.getCode()); return industryCodeType; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } @Override public int compareTo(@NotNull NetworkIdentificationType networkIdentificationType) { return Integer.compare(Integer.parseInt(this.code), Integer.parseInt(networkIdentificationType.getCode())); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/NetworkIdentificationTypeEnum.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; /** * 收录行业编码 */ public enum NetworkIdentificationTypeEnum { PUBLIC_SECURITY_VIDEO_TRANSMISSION_NETWORK("0", "公安视频传输网"), PUBLIC_SECURITY_VIDEO_TRANSMISSION_NETWORK2("1", "公安视频传输网"), INDUSTRY_SPECIFIC_NETWORK("2", "行业专网"), POLITICAL_AND_LEGAL_INFORMATION_NETWORK("3", "政法信息网"), PUBLIC_SECURITY_MOBILE_INFORMATION_NETWORK("4", "公安移动信息网"), PUBLIC_SECURITY_INFORMATION_NETWORK("5", "公安信息网"), ELECTRONIC_GOVERNMENT_EXTRANET("6", "电子政务外网"), PUBLIC_NETWORKS_SUCH_AS_THE_INTERNET("7", "互联网等公共网络"), Dedicated_Line("8", "专线"), RESERVE("9", "预留"), ; /** * 接入类型码 */ private String name; /** * 名称 */ private String code; NetworkIdentificationTypeEnum(String code, String name) { this.name = name; this.code = code; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/NotifyCatalogChannel.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; public class NotifyCatalogChannel { private Type type; private DeviceChannel channel; public enum Type { ADD, DELETE, UPDATE, STATUS_CHANGED } public static NotifyCatalogChannel getInstance(Type type, DeviceChannel channel) { NotifyCatalogChannel notifyCatalogChannel = new NotifyCatalogChannel(); notifyCatalogChannel.setType(type); notifyCatalogChannel.setChannel(channel); return notifyCatalogChannel; } public Type getType() { return type; } public void setType(Type type) { this.type = type; } public DeviceChannel getChannel() { return channel; } public void setChannel(DeviceChannel channel) { this.channel = channel; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/OpenRTPServerResult.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import com.genersoft.iot.vmp.media.event.hook.HookData; import com.genersoft.iot.vmp.service.bean.SSRCInfo; import lombok.Data; @Data public class OpenRTPServerResult { private SSRCInfo ssrcInfo; private HookData hookData; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/Platform.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; /** * @author lin */ @Data @Schema(description = "平台信息") public class Platform { @Schema(description = "ID(数据库中)") private Integer id; @Schema(description = "是否启用") private boolean enable; @Schema(description = "名称") private String name; @Schema(description = "SIP服务国标编码") private String serverGBId; @Schema(description = "SIP服务国标域") private String serverGBDomain; @Schema(description = "SIP服务IP") private String serverIp; @Schema(description = "SIP服务端口") private int serverPort; @Schema(description = "设备国标编号") private String deviceGBId; @Schema(description = "设备ip") private String deviceIp; @Schema(description = "设备端口") private int devicePort; @Schema(description = "SIP认证用户名(默认使用设备国标编号)") private String username; @Schema(description = "SIP认证密码") private String password; @Schema(description = "注册周期 (秒)") private int expires; @Schema(description = "心跳周期(秒)") private int keepTimeout; @Schema(description = "传输协议") private String transport; @Schema(description = "字符集") private String characterSet; @Schema(description = "允许云台控制") private boolean ptz; @Schema(description = "RTCP流保活") private boolean rtcp; @Schema(description = "在线状态") private boolean status; @Schema(description = "通道数量") private int channelCount; @Schema(description = "已被订阅目录信息") private boolean catalogSubscribe; @Schema(description = "已被订阅报警信息") private boolean alarmSubscribe; @Schema(description = "已被订阅移动位置信息") private boolean mobilePositionSubscribe; @Schema(description = "目录分组-每次向上级发送通道信息时单个包携带的通道数量,取值1,2,4,8") private int catalogGroup; @Schema(description = "更新时间") private String updateTime; @Schema(description = "创建时间") private String createTime; @Schema(description = "是否作为消息通道") private boolean asMessageChannel; @Schema(description = "点播回复200OK使用的IP") private String sendStreamIp; @Schema(description = "是否自动推送通道变化") private Boolean autoPushChannel; @Schema(description = "目录信息包含平台信息, 0:关闭,1:打开") private int catalogWithPlatform; @Schema(description = "目录信息包含分组信息, 0:关闭,1:打开") private int catalogWithGroup; @Schema(description = "目录信息包含行政区划, 0:关闭,1:打开") private int catalogWithRegion; @Schema(description = "行政区划") private String civilCode; @Schema(description = "平台厂商") private String manufacturer; @Schema(description = "平台型号") private String model; @Schema(description = "平台安装地址") private String address; @Schema(description = "注册方式(必选)缺省为1; " + "1-符合IETF RFC 3261标准的认证注册模式;" + "2-基于口令的双向认证注册模式;" + "3-基于数字证书的双向认证注册模式(高安全级别要求);" + "4-基于数字证书的单向认证注册模式(高安全级别要求)") private int registerWay = 1; @Schema(description = "保密属性(必选)缺省为0;0-不涉密,1-涉密") private int secrecy = 0; @Schema(description = "执行注册的服务ID") private String serverId; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/PlatformCatalog.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import io.swagger.v3.oas.annotations.media.Schema; /** * 国标级联-目录 * @author lin */ @Schema(description = "目录信息") public class PlatformCatalog { @Schema(description = "ID") private String id; @Schema(description = "名称") private String name; @Schema(description = "平台ID") private String platformId; @Schema(description = "父级目录ID") private String parentId; @Schema(description = "行政区划") private String civilCode; @Schema(description = "目录分组") private String businessGroupId; /** * 子节点数 */ @Schema(description = "子节点数") private int childrenCount; /** * 0 目录, 1 国标通道, 2 直播流 */ @Schema(description = "类型:0 目录, 1 国标通道, 2 直播流") private int type; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPlatformId() { return platformId; } public void setPlatformId(String platformId) { this.platformId = platformId; } public String getParentId() { return parentId; } public void setParentId(String parentId) { this.parentId = parentId; } public int getChildrenCount() { return childrenCount; } public void setChildrenCount(int childrenCount) { this.childrenCount = childrenCount; } public int getType() { return type; } public void setType(int type) { this.type = type; } public void setTypeForCatalog() { this.type = 0; } public void setTypeForGb() { this.type = 1; } public void setTypeForStream() { this.type = 2; } public String getCivilCode() { return civilCode; } public void setCivilCode(String civilCode) { this.civilCode = civilCode; } public String getBusinessGroupId() { return businessGroupId; } public void setBusinessGroupId(String businessGroupId) { this.businessGroupId = businessGroupId; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/PlatformCatch.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import lombok.Data; @Data public class PlatformCatch { private String id; /** * 心跳未回复次数 */ private int keepAliveReply; // 注册未回复次数 private int registerAliveReply; private String callId; private Platform platform; private SipTransactionInfo sipTransactionInfo; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/PlatformChannel.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; @EqualsAndHashCode(callSuper = true) @Data public class PlatformChannel extends CommonGBChannel { @Schema(description = "Id") private int id; @Schema(description = "平台ID") private int platformId; @Schema(description = "国标-编码") private String customDeviceId; @Schema(description = "国标-名称") private String customName; @Schema(description = "国标-设备厂商") private String customManufacturer; @Schema(description = "国标-设备型号") private String customModel; // 2016 @Schema(description = "国标-设备归属") private String customOwner; @Schema(description = "国标-行政区域") private String customCivilCode; @Schema(description = "国标-警区") private String customBlock; @Schema(description = "国标-安装地址") private String customAddress; @Schema(description = "国标-是否有子设备") private Integer customParental; @Schema(description = "国标-父节点ID") private String customParentId; // 2016 @Schema(description = "国标-信令安全模式") private Integer customSafetyWay; @Schema(description = "国标-注册方式") private Integer customRegisterWay; // 2016 @Schema(description = "国标-证书序列号") private Integer customCertNum; // 2016 @Schema(description = "国标-证书有效标识") private Integer customCertifiable; // 2016 @Schema(description = "国标-无效原因码(有证书且证书无效的设备必选)") private Integer customErrCode; // 2016 @Schema(description = "国标-证书终止有效期(有证书且证书无效的设备必选)") private Integer customEndTime; // 2022 @Schema(description = "国标-摄像机安全能力等级代码") private String customSecurityLevelCode; @Schema(description = "国标-保密属性(必选)缺省为0;0-不涉密,1-涉密") private Integer customSecrecy; @Schema(description = "国标-设备/系统IPv4/IPv6地址") private String customIpAddress; @Schema(description = "国标-设备/系统端口") private Integer customPort; @Schema(description = "国标-设备口令") private String customPassword; @Schema(description = "国标-设备状态") private String customStatus; @Schema(description = "国标-经度 WGS-84坐标系") private Double customLongitude; @Schema(description = "国标-纬度 WGS-84坐标系") private Double customLatitude; @Schema(description = "国标-虚拟组织所属的业务分组ID") private String customBusinessGroupId; @Schema(description = "国标-摄像机结构类型,标识摄像机类型: 1-球机; 2-半球; 3-固定枪机; 4-遥控枪机;5-遥控半球;6-多目设备的全景/拼接通道;7-多目设备的分割通道") private Integer customPtzType; // 2016 @Schema(description = "-摄像机位置类型扩展。1-省际检查站、2-党政机关、3-车站码头、4-中心广场、5-体育场馆、6-商业中心、7-宗教场所、" + "8-校园周边、9-治安复杂区域、10-交通干线。当目录项为摄像机时可选。") private Integer customPositionType; @Schema(description = "国标-摄像机光电成像类型。1-可见光成像;2-热成像;3-雷达成像;4-X光成像;5-深度光场成像;9-其他。可多值,") private String customPhotoelectricImagingTyp; @Schema(description = "国标-摄像机采集部位类型") private String customCapturePositionType; @Schema(description = "国标-摄像机安装位置室外、室内属性。1-室外、2-室内。") private Integer customRoomType; // 2016 @Schema(description = "国标-用途属性") private Integer customUseType; @Schema(description = "国标-摄像机补光属性。1-无补光;2-红外补光;3-白光补光;4-激光补光;9-其他") private Integer customSupplyLightType; @Schema(description = "国标-摄像机监视方位(光轴方向)属性。1-东(西向东)、2-西(东向西)、3-南(北向南)、4-北(南向北)、" + "5-东南(西北到东南)、6-东北(西南到东北)、7-西南(东北到西南)、8-西北(东南到西北)") private Integer customDirectionType; @Schema(description = "国标-摄像机支持的分辨率,可多值") private String customResolution; // 2022 @Schema(description = "国标-摄像机支持的码流编号列表,用于实时点播时指定码流编号(可选)") private String customStreamNumberList; @Schema(description = "国标-下载倍速(可选),可多值") private String customDownloadSpeed; @Schema(description = "国标-空域编码能力,取值0-不支持;1-1级增强(1个增强层);2-2级增强(2个增强层);3-3级增强(3个增强层)") private Integer customSvcSpaceSupportMod; @Schema(description = "国标-时域编码能力,取值0-不支持;1-1级增强;2-2级增强;3-3级增强(可选)") private Integer customSvcTimeSupportMode; // 2022 @Schema(description = "国标- SSVC增强层与基本层比例能力 ") private String customSsvcRatioSupportList; // 2022 @Schema(description = "国标-移动采集设备类型(仅移动采集设备适用,必选);1-移动机器人载摄像机;2-执法记录仪;3-移动单兵设备;" + "4-车载视频记录设备;5-无人机载摄像机;9-其他") private Integer customMobileDeviceType; // 2022 @Schema(description = "国标-摄像机水平视场角(可选),取值范围大于0度小于等于360度") private Double customHorizontalFieldAngle; // 2022 @Schema(description = "国标-摄像机竖直视场角(可选),取值范围大于0度小于等于360度 ") private Double customVerticalFieldAngle; // 2022 @Schema(description = "国标-摄像机可视距离(可选),单位:米") private Double customMaxViewDistance; // 2022 @Schema(description = "国标-基层组织编码(必选,非基层建设时为“000000”)") private String customGrassrootsCode; // 2022 @Schema(description = "国标-监控点位类型(当为摄像机时必选),1-一类视频监控点;2-二类视频监控点;3-三类视频监控点;9-其他点位。") private Integer customPoType; // 2022 @Schema(description = "国标-点位俗称") private String customPoCommonName; // 2022 @Schema(description = "国标-设备MAC地址(可选),用“XX-XX-XX-XX-XX-XX”格式表达") private String customMac; // 2022 @Schema(description = "国标-摄像机卡口功能类型,01-人脸卡口;02-人员卡口;03-机动车卡口;04-非机动车卡口;05-物品卡口;99-其他") private String customFunctionType; // 2022 @Schema(description = "国标-摄像机视频编码格式") private String customEncodeType; // 2022 @Schema(description = "国标-摄像机安装使用时间") private String customInstallTime; // 2022 @Schema(description = "国标-摄像机所属管理单位名称") private String customManagementUnit; // 2022 @Schema(description = "国标-摄像机所属管理单位联系人的联系方式(电话号码,可多值,用英文半角“/”分割)") private String customContactInfo; // 2022 @Schema(description = "国标-录像保存天数(可选)") private Integer customRecordSaveDays; // 2022 @Schema(description = "国标-国民经济行业分类代码(可选)") private String customIndustrialClassification; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/PlatformGbStream.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import io.swagger.v3.oas.annotations.media.Schema; public class PlatformGbStream { @Schema(description = "ID") private int gbStreamId; @Schema(description = "平台ID") private String platformId; @Schema(description = "目录ID") private String catalogId; public Integer getGbStreamId() { return gbStreamId; } public void setGbStreamId(Integer gbStreamId) { this.gbStreamId = gbStreamId; } public String getPlatformId() { return platformId; } public void setPlatformId(String platformId) { this.platformId = platformId; } public String getCatalogId() { return catalogId; } public void setCatalogId(String catalogId) { this.catalogId = catalogId; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/PlatformKeepaliveCallback.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; public interface PlatformKeepaliveCallback { public void run(String platformServerGbId, int failCount); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/PlatformRegister.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; public class PlatformRegister { // 未回复次数 private int reply; public int getReply() { return reply; } public void setReply(int reply) { this.reply = reply; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/PlayException.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import lombok.Data; @Data public class PlayException extends RuntimeException{ private int code; private String msg; public PlayException(int code, String msg) { this.code = code; this.msg = msg; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/Preset.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import lombok.Data; @Data public class Preset { private String presetId; private String presetName; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordInfo.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.Setter; import java.time.Instant; import java.util.List; /** * @description:设备录像信息bean * @author: swwheihei * @date: 2020年5月8日 下午2:05:56 */ @Setter @Getter @Schema(description = "设备录像查询结果信息") public class RecordInfo { @Schema(description = "设备编号") private String deviceId; @Schema(description = "通道编号") private String channelId; @Schema(description = "命令序列号") private String sn; @Schema(description = "设备名称") private String name; @Schema(description = "列表总数") private int sumNum; private int count; private Instant lastTime; @Schema(description = "") private List recordList; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordItem.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import com.genersoft.iot.vmp.utils.DateUtil; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.Setter; import org.jetbrains.annotations.NotNull; import java.time.Instant; import java.time.temporal.TemporalAccessor; /** * @description:设备录像bean * @author: swwheihei * @date: 2020年5月8日 下午2:06:54 */ @Setter @Getter @Schema(description = "设备录像详情") public class RecordItem implements Comparable{ @Schema(description = "设备编号") private String deviceId; @Schema(description = "名称") private String name; @Schema(description = "文件路径名 (可选)") private String filePath; @Schema(description = "录像文件大小,单位:Byte(可选)") private String fileSize; @Schema(description = "录像地址(可选)") private String address; @Schema(description = "录像开始时间(可选)") private String startTime; @Schema(description = "录像结束时间(可选)") private String endTime; @Schema(description = "保密属性(必选)缺省为0;0:不涉密,1:涉密") private int secrecy; @Schema(description = "录像产生类型(可选)time或alarm 或 manual") private String type; @Schema(description = "录像触发者ID(可选)") private String recorderId; @Override public int compareTo(@NotNull RecordItem recordItem) { 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; } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/RedisGroupMessage.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import lombok.Data; @Data public class RedisGroupMessage { /** * 分组别名 */ private String groupAlias; /** * 分组名称 */ private String groupName; /** * 分组所属父分组别名 */ private String parentGAlias; /** * 分组所属业务分组别名 */ private String topGroupGAlias; /** * 分组变化消息中的消息类型,取值为 add update delete */ private String messageType; @Override public String toString() { return "RedisGroupMessage{" + ", groupAlias='" + groupAlias + '\'' + ", groupName='" + groupName + '\'' + ", parentGAlias='" + parentGAlias + '\'' + ", topGroupGAlias='" + topGroupGAlias + '\'' + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/Region.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import com.genersoft.iot.vmp.common.CivilCodePo; import com.genersoft.iot.vmp.utils.CivilCodeUtil; import com.genersoft.iot.vmp.utils.DateUtil; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import org.jetbrains.annotations.NotNull; /** * 区域 */ @Data @Schema(description = "区域") public class Region implements Comparable{ /** * 数据库自增ID */ @Schema(description = "数据库自增ID") private int id; /** * 区域国标编号 */ @Schema(description = "区域国标编号") private String deviceId; /** * 区域名称 */ @Schema(description = "区域名称") private String name; /** * 父区域国标ID */ @Schema(description = "父区域ID") private Integer parentId; /** * 父区域国标ID */ @Schema(description = "父区域国标ID") private String parentDeviceId; /** * 创建时间 */ @Schema(description = "创建时间") private String createTime; /** * 更新时间 */ @Schema(description = "更新时间") private String updateTime; public static Region getInstance(String commonRegionDeviceId, String commonRegionName, String commonRegionParentId) { Region region = new Region(); region.setDeviceId(commonRegionDeviceId); region.setName(commonRegionName); region.setParentDeviceId(commonRegionParentId); region.setCreateTime(DateUtil.getNow()); region.setUpdateTime(DateUtil.getNow()); return region; } public static Region getInstance(CivilCodePo civilCodePo) { Region region = new Region(); region.setName(civilCodePo.getName()); region.setDeviceId(civilCodePo.getCode()); if (civilCodePo.getCode().length() > 2) { region.setParentDeviceId(civilCodePo.getParentCode()); } region.setCreateTime(DateUtil.getNow()); region.setUpdateTime(DateUtil.getNow()); return region; } public static Region getInstance(DeviceChannel channel) { Region region = new Region(); region.setName(channel.getName()); region.setDeviceId(channel.getDeviceId()); CivilCodePo parentCode = CivilCodeUtil.INSTANCE.getParentCode(channel.getDeviceId()); if (parentCode != null) { region.setParentDeviceId(parentCode.getCode()); } region.setCreateTime(DateUtil.getNow()); region.setUpdateTime(DateUtil.getNow()); return region; } @Override public int compareTo(@NotNull Region region) { return Integer.compare(Integer.parseInt(this.deviceId), Integer.parseInt(region.getDeviceId())); } @Override public boolean equals(Object obj) { if (obj == null) return false; if (this == obj) return true; if (obj instanceof Region) { Region region = (Region) obj; // 比较每个属性的值一致时才返回true if (region.getId() == this.id) { return true; } } return false; } /** * 重写hashcode方法,返回的hashCode一样才再去比较每个属性的值 */ @Override public int hashCode() { return id; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/RegionTree.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; /** * 区域 */ @EqualsAndHashCode(callSuper = true) @Data @Schema(description = "区域树") public class RegionTree extends Region { @Schema(description = "树节点ID") private String treeId; @Schema(description = "是否有子节点") private boolean isLeaf; @Schema(description = "类型, 行政区划:0 摄像头: 1") private int type; @Schema(description = "在线状态") private String status; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/SDPInfo.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import javax.sdp.SessionDescription; public class SDPInfo { private byte[] source; private SessionDescription sdpSource; private String sessionName; private Long startTime; private Long stopTime; private String username; private String address; private String ssrc; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/SendRtpInfo.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import com.genersoft.iot.vmp.common.enums.ChannelDataType; import com.genersoft.iot.vmp.common.enums.MediaApp; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.service.bean.RequestPushStreamMsg; import lombok.Data; @Data public class SendRtpInfo { /** * 推流ip */ private String ip; /** * 推流端口 */ private int port; /** * 推流标识 */ private String ssrc; /** * 目标平台或设备的编号 */ private String targetId; /** * 目标平台或设备的名称 */ private String targetName; /** * 是否是发送给上级平台 */ private boolean sendToPlatform; /** * 直播流的应用名 */ private String app; /** * 通道id */ private Integer channelId; /** * 推流状态 * 0 等待设备推流上来 * 1 等待上级平台回复ack * 2 推流中 */ private int status = 0; /** * 设备推流的streamId */ private String stream; /** * 是否为tcp */ private boolean tcp; /** * 是否为tcp主动模式 */ private boolean tcpActive; /** * 自己推流使用的IP */ private String localIp; /** * 自己推流使用的端口 */ private int localPort; /** * 使用的流媒体 */ private String mediaServerId; /** * 使用的服务的ID */ private String serverId; /** * invite 的 callId */ private String callId; /** * invite 的 fromTag */ private String fromTag; /** * invite 的 toTag */ private String toTag; /** * 发送时,rtp的pt(uint8_t),不传时默认为96 */ private int pt = 96; /** * 发送时,rtp的负载类型。为true时,负载为ps;为false时,为es; */ private boolean usePs = true; /** * 当usePs 为false时,有效。为1时,发送音频;为0时,发送视频;不传时默认为0 */ private boolean onlyAudio = false; /** * 是否开启rtcp保活 */ private boolean rtcp = false; /** * 播放类型 */ private InviteStreamType playType; /** * 发流的同时收流 */ private String receiveStream; /** * 上级的点播类型 */ private String sessionName; public static SendRtpInfo getInstance(RequestPushStreamMsg requestPushStreamMsg) { SendRtpInfo sendRtpItem = new SendRtpInfo(); sendRtpItem.setMediaServerId(requestPushStreamMsg.getMediaServerId()); sendRtpItem.setApp(requestPushStreamMsg.getApp()); sendRtpItem.setStream(requestPushStreamMsg.getStream()); sendRtpItem.setIp(requestPushStreamMsg.getIp()); sendRtpItem.setPort(requestPushStreamMsg.getPort()); sendRtpItem.setSsrc(requestPushStreamMsg.getSsrc()); sendRtpItem.setTcp(requestPushStreamMsg.isTcp()); sendRtpItem.setLocalPort(requestPushStreamMsg.getSrcPort()); sendRtpItem.setPt(requestPushStreamMsg.getPt()); sendRtpItem.setUsePs(requestPushStreamMsg.isPs()); sendRtpItem.setOnlyAudio(requestPushStreamMsg.isOnlyAudio()); return sendRtpItem; } public static SendRtpInfo getInstance(String app, String stream, String ssrc, String dstIp, Integer dstPort, boolean tcp, int sendLocalPort, Integer pt) { SendRtpInfo sendRtpItem = new SendRtpInfo(); sendRtpItem.setApp(app); sendRtpItem.setStream(stream); sendRtpItem.setSsrc(ssrc); sendRtpItem.setTcp(tcp); sendRtpItem.setLocalPort(sendLocalPort); sendRtpItem.setIp(dstIp); sendRtpItem.setPort(dstPort); if (pt != null) { sendRtpItem.setPt(pt); } return sendRtpItem; } public static SendRtpInfo getInstance(Integer localPort, MediaServer mediaServer, String ip, Integer port, String ssrc, String deviceId, String platformId, Integer channelId, Boolean isTcp, Boolean rtcp, String serverId) { if (localPort == 0) { return null; } SendRtpInfo sendRtpItem = new SendRtpInfo(); sendRtpItem.setIp(ip); if(port != null) { sendRtpItem.setPort(port); } sendRtpItem.setSsrc(ssrc); if (deviceId != null) { sendRtpItem.setTargetId(deviceId); sendRtpItem.setSendToPlatform(false); }else { sendRtpItem.setTargetId(platformId); sendRtpItem.setSendToPlatform(true); } sendRtpItem.setChannelId(channelId); sendRtpItem.setTcp(isTcp); sendRtpItem.setRtcp(rtcp); sendRtpItem.setApp(MediaApp.GB28181); sendRtpItem.setLocalPort(localPort); sendRtpItem.setServerId(serverId); sendRtpItem.setMediaServerId(mediaServer.getId()); return sendRtpItem; } @Override public String toString() { return "SendRtpItem{" + "ip='" + ip + '\'' + ", port=" + port + ", ssrc='" + ssrc + '\'' + ", targetId='" + targetId + '\'' + ", app='" + app + '\'' + ", channelId='" + channelId + '\'' + ", status=" + status + ", stream='" + stream + '\'' + ", tcp=" + tcp + ", tcpActive=" + tcpActive + ", localIp='" + localIp + '\'' + ", localPort=" + localPort + ", mediaServerId='" + mediaServerId + '\'' + ", serverId='" + serverId + '\'' + ", CallId='" + callId + '\'' + ", fromTag='" + fromTag + '\'' + ", toTag='" + toTag + '\'' + ", pt=" + pt + ", usePs=" + usePs + ", onlyAudio=" + onlyAudio + ", rtcp=" + rtcp + ", playType=" + playType + ", receiveStream='" + receiveStream + '\'' + ", sessionName='" + sessionName + '\'' + '}'; } public void setPlayTypeByChannelDataType(Integer dataType, String sessionName) { if (dataType == ChannelDataType.STREAM_PUSH) { this.setPlayType(InviteStreamType.PUSH); }else if (dataType == ChannelDataType.STREAM_PROXY){ this.setPlayType(InviteStreamType.PROXY); }else { this.setPlayType("Play".equalsIgnoreCase(sessionName) ? InviteStreamType.PLAY : InviteStreamType.PLAYBACK); } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/SipMsgInfo.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import org.dom4j.Element; import javax.sip.RequestEvent; public class SipMsgInfo { private RequestEvent evt; private Device device; private Platform platform; private Element rootElement; public SipMsgInfo(RequestEvent evt, Device device, Element rootElement) { this.evt = evt; this.device = device; this.rootElement = rootElement; } public SipMsgInfo(RequestEvent evt, Platform platform, Element rootElement) { this.evt = evt; this.platform = platform; this.rootElement = rootElement; } public RequestEvent getEvt() { return evt; } public void setEvt(RequestEvent evt) { this.evt = evt; } public Device getDevice() { return device; } public void setDevice(Device device) { this.device = device; } public Platform getPlatform() { return platform; } public void setPlatform(Platform platform) { this.platform = platform; } public Element getRootElement() { return rootElement; } public void setRootElement(Element rootElement) { this.rootElement = rootElement; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/SipSendFailEvent.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; import lombok.Data; @Data public class SipSendFailEvent extends SipSubscribe.EventResult { private String callId; private String msg; public static SipSendFailEvent getInstance(String callId, String msg){ SipSendFailEvent sipSendFailEvent = new SipSendFailEvent(); sipSendFailEvent.setMsg(msg); sipSendFailEvent.setCallId(callId); return sipSendFailEvent; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/SipTransactionInfo.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import gov.nist.javax.sip.message.SIPResponse; import lombok.Data; import javax.sip.header.EventHeader; @Data public class SipTransactionInfo { private String callId; private String fromTag; private String toTag; private String viaBranch; private int expires; private String user; private String eventId; // 自己是否媒体流发送者 private boolean asSender; public SipTransactionInfo(SIPResponse response, boolean asSender) { this.callId = response.getCallIdHeader().getCallId(); this.fromTag = response.getFromTag(); this.toTag = response.getToTag(); this.viaBranch = response.getTopmostViaHeader().getBranch(); this.asSender = asSender; EventHeader header = (EventHeader)response.getHeader(EventHeader.NAME); if (header != null) { this.eventId = header.getEventId(); } } public SipTransactionInfo(SIPResponse response) { this.callId = response.getCallIdHeader().getCallId(); this.fromTag = response.getFromTag(); this.toTag = response.getToTag(); this.viaBranch = response.getTopmostViaHeader().getBranch(); this.asSender = false; EventHeader header = (EventHeader)response.getHeader(EventHeader.NAME); if (header != null) { this.eventId = header.getEventId(); } } public SipTransactionInfo() { } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/SsrcTransaction.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import com.genersoft.iot.vmp.common.InviteSessionType; import gov.nist.javax.sip.message.SIPResponse; import lombok.Data; @Data public class SsrcTransaction { /** * 设备编号 */ private String deviceId; /** * 上级平台的编号 */ private String platformId; /** * 通道的数据库ID */ private Integer channelId; /** * 会话的CALL ID */ private String callId; /** * 关联的流应用名 */ private String app; /** * 关联的流ID */ private String stream; /** * 使用的流媒体 */ private String mediaServerId; /** * 使用的SSRC */ private String ssrc; /** * 事务信息 */ private SipTransactionInfo sipTransactionInfo; /** * 类型 */ private InviteSessionType type; public static SsrcTransaction buildForDevice(String deviceId, Integer channelId, String callId, String app, String stream, String ssrc, String mediaServerId, SIPResponse response, InviteSessionType type) { SsrcTransaction ssrcTransaction = new SsrcTransaction(); ssrcTransaction.setDeviceId(deviceId); ssrcTransaction.setChannelId(channelId); ssrcTransaction.setCallId(callId); ssrcTransaction.setApp(app); ssrcTransaction.setStream(stream); ssrcTransaction.setMediaServerId(mediaServerId); ssrcTransaction.setSsrc(ssrc); ssrcTransaction.setSipTransactionInfo(new SipTransactionInfo(response)); ssrcTransaction.setType(type); return ssrcTransaction; } public static SsrcTransaction buildForPlatform(String platformId, Integer channelId, String callId, String app,String stream, String ssrc, String mediaServerId, SIPResponse response, InviteSessionType type) { SsrcTransaction ssrcTransaction = new SsrcTransaction(); ssrcTransaction.setPlatformId(platformId); ssrcTransaction.setChannelId(channelId); ssrcTransaction.setCallId(callId); ssrcTransaction.setStream(stream); ssrcTransaction.setApp(app); ssrcTransaction.setMediaServerId(mediaServerId); ssrcTransaction.setSsrc(ssrc); ssrcTransaction.setSipTransactionInfo(new SipTransactionInfo(response)); ssrcTransaction.setType(type); return ssrcTransaction; } public SsrcTransaction() { } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/SubscribeHolder.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import com.genersoft.iot.vmp.conf.DynamicTask; import com.genersoft.iot.vmp.conf.UserSetting; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import java.time.Duration; import java.util.ArrayList; import java.util.List; /** * @author lin */ @Slf4j @Component public class SubscribeHolder { @Autowired private DynamicTask dynamicTask; @Autowired private UserSetting userSetting; @Autowired private RedisTemplate redisTemplate; private final String prefix = "VMP_SUBSCRIBE_OVERDUE"; public void putCatalogSubscribe(String platformId, SubscribeInfo subscribeInfo) { log.info("[国标级联] 添加目录订阅,平台: {}, 有效期: {}", platformId, subscribeInfo.getExpires()); subscribeInfo.setServerId(userSetting.getServerId()); String key = String.format("%s:%s:%s", prefix, "catalog", platformId); if (subscribeInfo.getExpires() > 0) { Duration duration = Duration.ofSeconds(subscribeInfo.getExpires()); redisTemplate.opsForValue().set(key, subscribeInfo, duration); }else { redisTemplate.opsForValue().set(key, subscribeInfo); } } public SubscribeInfo getCatalogSubscribe(String platformId) { String key = String.format("%s:%s:%s", prefix, "catalog", platformId); return (SubscribeInfo)redisTemplate.opsForValue().get(key); } public void removeCatalogSubscribe(String platformId) { String key = String.format("%s:%s:%s", prefix, "catalog", platformId); redisTemplate.delete(key); } public void putMobilePositionSubscribe(String platformId, SubscribeInfo subscribeInfo, Runnable gpsTask) { log.info("[国标级联] 添加移动位置订阅,平台: {}, 有效期: {}s", platformId, subscribeInfo.getExpires()); subscribeInfo.setServerId(userSetting.getServerId()); String key = String.format("%s:%s:%s", prefix, "mobilePosition", platformId); if (subscribeInfo.getExpires() > 0) { Duration duration = Duration.ofSeconds(subscribeInfo.getExpires()); redisTemplate.opsForValue().set(key, subscribeInfo, duration); }else { redisTemplate.opsForValue().set(key, subscribeInfo); } int cycleForCatalog; if (subscribeInfo.getGpsInterval() <= 0) { cycleForCatalog = 5; }else { cycleForCatalog = subscribeInfo.getGpsInterval(); } dynamicTask.startCron( key, () -> { SubscribeInfo subscribe = getMobilePositionSubscribe(platformId); if (subscribe != null) { gpsTask.run(); }else { dynamicTask.stop(key); } }, cycleForCatalog * 1000); } public SubscribeInfo getMobilePositionSubscribe(String platformId) { String key = String.format("%s:%s:%s", prefix, "mobilePosition", platformId); return (SubscribeInfo)redisTemplate.opsForValue().get(key); } public void removeMobilePositionSubscribe(String platformId) { String key = String.format("%s:%s:%s", prefix, "mobilePosition", platformId); redisTemplate.delete(key); } public List getAllCatalogSubscribePlatform(List platformList) { if (platformList == null || platformList.isEmpty()) { return new ArrayList<>(); } List result = new ArrayList<>(); for (Platform platform : platformList) { String key = String.format("%s:%s:%s", prefix, "catalog", platform.getServerGBId()); if (redisTemplate.hasKey(key)) { result.add(platform.getServerGBId()); } } return result; } public List getAllMobilePositionSubscribePlatform(List platformList) { if (platformList == null || platformList.isEmpty()) { return new ArrayList<>(); } List result = new ArrayList<>(); for (Platform platform : platformList) { String key = String.format("%s:%s:%s", prefix, "mobilePosition", platform.getServerGBId()); if (redisTemplate.hasKey(key)) { result.add(platform.getServerGBId()); } } return result; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/SubscribeInfo.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import gov.nist.javax.sip.message.SIPResponse; import lombok.Data; import javax.sip.header.EventHeader; import java.util.UUID; @Data public class SubscribeInfo { private String id; private int expires; private String eventId; private String eventType; private SipTransactionInfo transactionInfo; /** * 以下为可选字段 */ private String sn; private int gpsInterval; /** * 模拟的FromTag */ private String simulatedFromTag; /** * 模拟的ToTag */ private String simulatedToTag; /** * 模拟的CallID */ private String simulatedCallId; /** * 来源serverId */ private String serverId; public static SubscribeInfo getInstance(SIPResponse response, String id, int expires, EventHeader eventHeader){ SubscribeInfo subscribeInfo = new SubscribeInfo(); subscribeInfo.id = id; subscribeInfo.transactionInfo = new SipTransactionInfo(response); subscribeInfo.expires = expires; subscribeInfo.eventId = eventHeader.getEventId(); subscribeInfo.eventType = eventHeader.getEventType(); return subscribeInfo; } public static SubscribeInfo buildSimulated(String platFormServerId, String platFormServerIp){ SubscribeInfo subscribeInfo = new SubscribeInfo(); subscribeInfo.setId(platFormServerId); subscribeInfo.setExpires(-1); subscribeInfo.setEventType("Catalog"); int random = (int) Math.floor(Math.random() * 10000); subscribeInfo.setEventId(random + ""); subscribeInfo.setSimulatedCallId(UUID.randomUUID().toString().replace("-", "") + "@" + platFormServerIp); subscribeInfo.setSimulatedFromTag(UUID.randomUUID().toString().replace("-", "")); subscribeInfo.setSimulatedToTag(UUID.randomUUID().toString().replace("-", "")); return subscribeInfo; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/SyncStatus.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.time.Instant; /** * 摄像机同步状态 * @author lin */ @Data @Schema(description = "摄像机同步状态") public class SyncStatus { @Schema(description = "总数") private Integer total; @Schema(description = "当前更新多少") private Integer current; @Schema(description = "错误描述") private String errorMsg; @Schema(description = "是否同步中") private Boolean syncIng; @Schema(description = "时间") private Instant time; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/TalkRtpInfo.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import lombok.Data; @Data public class TalkRtpInfo { /** * 应用名, 待推送给设备的流应用名 */ private String app; /** * 流id, 待推送给设备的流id */ private String stream; /** * rtp推流出去的ssrc */ private String ssrc; /** * 对方rtp推流上来的流id */ private String receiveStreamId; /** * 是否推送本地MP4录像,该参数非必选参数 */ private Integer fromMp4; /** * 类型: 0(ES流)、1(PS流)、2(TS流),默认1(PS流);该参数非必选参数 */ private Integer type; /** * rtp payload type,默认96;该参数非必选参数 */ private Integer pt; /** * rtp es方式打包时,是否只打包音频;该参数非必选参数 */ private Integer onlyAudio; /** * 转发rtp(tcp模式)时,如果发送不出去,是否限制源端收流速度,此参数在多倍速rtp转发时作用较大 */ private Integer enableOriginReceiveLimit; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/bean/VectorTileSource.java ================================================ package com.genersoft.iot.vmp.gb28181.bean; import lombok.Getter; import lombok.Setter; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit; @Getter @Setter public class VectorTileSource implements Delayed { /** * 抽稀的图层数据 */ private Map vectorTileMap = new ConcurrentHashMap<>(); /** * 抽稀的原始数据 */ private List channelList = new ArrayList<>(); private String id; /** * 创建时间, 大于6小时后删除 */ private long time; public VectorTileSource() { this.time = System.currentTimeMillis(); } @Override public long getDelay(@NotNull TimeUnit unit) { return unit.convert(time + 6 * 60 * 60 * 1000 - System.currentTimeMillis(), TimeUnit.MILLISECONDS); } @Override public int compareTo(@NotNull Delayed o) { return (int) (this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS)); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/conf/DefaultProperties.java ================================================ package com.genersoft.iot.vmp.gb28181.conf; import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.cmd.AlarmNotifyMessageHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Properties; /** * 获取sip默认配置 * @author lin */ public class DefaultProperties { public static Properties getProperties(String name, boolean sipLog, boolean sipCacheServerConnections) { Properties properties = new Properties(); properties.setProperty("javax.sip.STACK_NAME", name); // properties.setProperty("javax.sip.IP_ADDRESS", ip); // 关闭自动会话 properties.setProperty("javax.sip.AUTOMATIC_DIALOG_SUPPORT", "off"); /** * 完整配置参考 gov.nist.javax.sip.SipStackImpl,需要下载源码 * gov/nist/javax/sip/SipStackImpl.class * sip消息的解析在 gov.nist.javax.sip.stack.UDPMessageChannel的processIncomingDataPacket方法 */ // 接收所有notify请求,即使没有订阅 properties.setProperty("gov.nist.javax.sip.DELIVER_UNSOLICITED_NOTIFY", "true"); properties.setProperty("gov.nist.javax.sip.AUTOMATIC_DIALOG_ERROR_HANDLING", "false"); properties.setProperty("gov.nist.javax.sip.CANCEL_CLIENT_TRANSACTION_CHECKED", "true"); // 为_NULL _对话框传递_终止的_事件 properties.setProperty("gov.nist.javax.sip.DELIVER_TERMINATED_EVENT_FOR_NULL_DIALOG", "true"); // 是否自动计算content length的实际长度,默认不计算 properties.setProperty("gov.nist.javax.sip.COMPUTE_CONTENT_LENGTH_FROM_MESSAGE_BODY", "true"); // 会话清理策略 properties.setProperty("gov.nist.javax.sip.RELEASE_REFERENCES_STRATEGY", "Normal"); // 处理由该服务器处理的基于底层TCP的保持生存超时 properties.setProperty("gov.nist.javax.sip.RELIABLE_CONNECTION_KEEP_ALIVE_TIMEOUT", "60"); // 获取实际内容长度,不使用header中的长度信息 properties.setProperty("gov.nist.javax.sip.COMPUTE_CONTENT_LENGTH_FROM_MESSAGE_BODY", "true"); // 线程可重入 properties.setProperty("gov.nist.javax.sip.REENTRANT_LISTENER", "true"); // 定义应用程序打算多久审计一次 SIP 堆栈,了解其内部线程的健康状况(该属性指定连续审计之间的时间(以毫秒为单位)) properties.setProperty("gov.nist.javax.sip.THREAD_AUDIT_INTERVAL_IN_MILLISECS", "30000"); // 部分设备会在短时间内发送大量注册, 导致协议栈内存溢出, 开启此项可以防止这部分设备注册, 避免服务崩溃,但是会降低系统性能, 描述如下 // 默认值为 true。 // 将此设置为 false 会使 Stack 在 Server Transaction 进入 TERMINATED 状态后关闭服务器套接字。 // 这允许服务器防止客户端发起的基于 TCP 的拒绝服务攻击(即发起数百个客户端事务)。 // 如果为 true(默认作),则堆栈将保持套接字打开,以便以牺牲线程和内存资源为代价来最大化性能 - 使自身容易受到 DOS 攻击。 properties.setProperty("gov.nist.javax.sip.CACHE_SERVER_CONNECTIONS", String.valueOf(sipCacheServerConnections)); properties.setProperty("gov.nist.javax.sip.MESSAGE_PROCESSOR_FACTORY", "gov.nist.javax.sip.stack.NioMessageProcessorFactory"); /** * sip_server_log.log 和 sip_debug_log.log ERROR, INFO, WARNING, OFF, DEBUG, TRACE */ Logger log = LoggerFactory.getLogger(AlarmNotifyMessageHandler.class); if (sipLog) { properties.setProperty("gov.nist.javax.sip.STACK_LOGGER", "com.genersoft.iot.vmp.gb28181.conf.StackLoggerImpl"); properties.setProperty("gov.nist.javax.sip.SERVER_LOGGER", "com.genersoft.iot.vmp.gb28181.conf.ServerLoggerImpl"); properties.setProperty("gov.nist.javax.sip.LOG_MESSAGE_CONTENT", "true"); log.info("[SIP日志]已开启"); }else { log.info("[SIP日志]已关闭"); } return properties; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/conf/ServerLoggerImpl.java ================================================ package com.genersoft.iot.vmp.gb28181.conf; import gov.nist.core.CommonLogger; import gov.nist.core.ServerLogger; import gov.nist.core.StackLogger; import gov.nist.javax.sip.message.SIPMessage; import gov.nist.javax.sip.stack.SIPTransactionStack; import javax.sip.SipStack; import java.util.Properties; public class ServerLoggerImpl implements ServerLogger { private boolean showLog = true; private SIPTransactionStack sipStack; protected StackLogger stackLogger; @Override public void closeLogFile() { } @Override public void logMessage(SIPMessage message, String from, String to, boolean sender, long time) { if (!showLog) { return; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(sender? "发送:目标--->" + from:"接收:来自--->" + to) .append("\r\n") .append(message); this.stackLogger.logInfo(stringBuilder.toString()); } @Override public void logMessage(SIPMessage message, String from, String to, String status, boolean sender, long time) { if (!showLog) { return; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(sender? "发送: 目标->" + from :"接收:来自->" + to) .append("\r\n") .append(message); this.stackLogger.logInfo(stringBuilder.toString()); } @Override public void logMessage(SIPMessage message, String from, String to, String status, boolean sender) { if (!showLog) { return; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(sender? "发送: 目标->" + from :"接收:来自->" + to) .append("\r\n") .append(message); this.stackLogger.logInfo(stringBuilder.toString()); } @Override public void logException(Exception ex) { if (!showLog) { return; } this.stackLogger.logException(ex); } @Override public void setStackProperties(Properties stackProperties) { if (!showLog) { return; } String TRACE_LEVEL = stackProperties.getProperty("gov.nist.javax.sip.TRACE_LEVEL"); if (TRACE_LEVEL != null) { showLog = true; } } @Override public void setSipStack(SipStack sipStack) { if (!showLog) { return; } if(sipStack instanceof SIPTransactionStack) { this.sipStack = (SIPTransactionStack)sipStack; this.stackLogger = CommonLogger.getLogger(SIPTransactionStack.class); } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/conf/StackLoggerImpl.java ================================================ package com.genersoft.iot.vmp.gb28181.conf; import gov.nist.core.StackLogger; import org.slf4j.LoggerFactory; import org.slf4j.spi.LocationAwareLogger; import org.springframework.stereotype.Component; import java.util.Properties; @Component public class StackLoggerImpl implements StackLogger { /** * 完全限定类名(Fully Qualified Class Name),用于定位日志位置 */ private static final String FQCN = StackLoggerImpl.class.getName(); /** * 获取栈中类信息(以便底层日志记录系统能够提取正确的位置信息(方法名、行号)) * @return LocationAwareLogger */ private static LocationAwareLogger getLocationAwareLogger() { return (LocationAwareLogger) LoggerFactory.getLogger(new Throwable().getStackTrace()[4].getClassName()); } /** * 封装打印日志的位置信息 * @param level 日志级别 * @param message 日志事件的消息 */ private static void log(int level, String message) { LocationAwareLogger locationAwareLogger = getLocationAwareLogger(); locationAwareLogger.log(null, FQCN, level, message, null, null); } /** * 封装打印日志的位置信息 * @param level 日志级别 * @param message 日志事件的消息 */ private static void log(int level, String message, Throwable throwable) { LocationAwareLogger locationAwareLogger = getLocationAwareLogger(); locationAwareLogger.log(null, FQCN, level, message, null, throwable); } @Override public void logStackTrace() { } @Override public void logStackTrace(int traceLevel) { System.out.println("traceLevel: " + traceLevel); } @Override public int getLineCount() { return 0; } @Override public void logException(Throwable ex) { } @Override public void logDebug(String message) { log(LocationAwareLogger.INFO_INT, message); } @Override public void logDebug(String message, Exception ex) { log(LocationAwareLogger.INFO_INT, message, ex); } @Override public void logTrace(String message) { log(LocationAwareLogger.INFO_INT, message); } @Override public void logFatalError(String message) { log(LocationAwareLogger.INFO_INT, message); } @Override public void logError(String message) { log(LocationAwareLogger.INFO_INT, message); } @Override public boolean isLoggingEnabled() { return true; } @Override public boolean isLoggingEnabled(int logLevel) { return true; } @Override public void logError(String message, Exception ex) { log(LocationAwareLogger.INFO_INT, message, ex); } @Override public void logWarning(String message) { log(LocationAwareLogger.INFO_INT, message); } @Override public void logInfo(String message) { log(LocationAwareLogger.INFO_INT, message); } @Override public void disableLogging() { } @Override public void enableLogging() { } @Override public void setBuildTimeStamp(String buildTimeStamp) { } @Override public void setStackProperties(Properties stackProperties) { } @Override public String getLoggerName() { return null; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/controller/AlarmController.java ================================================ package com.genersoft.iot.vmp.gb28181.controller; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.conf.security.JwtUtils; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm; import com.genersoft.iot.vmp.gb28181.bean.Platform; import com.genersoft.iot.vmp.gb28181.service.IDeviceAlarmService; import com.genersoft.iot.vmp.gb28181.service.IDeviceService; import com.genersoft.iot.vmp.gb28181.service.IPlatformService; import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander; import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.github.pagehelper.PageInfo; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.ObjectUtils; import org.springframework.web.bind.annotation.*; import javax.sip.InvalidArgumentException; import javax.sip.SipException; import java.text.ParseException; import java.util.Arrays; import java.util.List; @Tag(name = "报警信息管理") @Slf4j @RestController @RequestMapping("/api/alarm") public class AlarmController { @Autowired private IDeviceAlarmService deviceAlarmService; @Autowired private ISIPCommander commander; @Autowired private ISIPCommanderForPlatform commanderForPlatform; @Autowired private IPlatformService platformService; @Autowired private IDeviceService deviceService; /** * 删除报警 * * @param id 报警id * @param deviceIds 多个设备id,逗号分隔 * @param time 结束时间(这个时间之前的报警会被删除) * @return */ @DeleteMapping("/delete") @Operation(summary = "删除报警", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "id", description = "ID") @Parameter(name = "deviceIds", description = "多个设备id,逗号分隔") @Parameter(name = "time", description = "结束时间") public Integer delete( @RequestParam(required = false) Integer id, @RequestParam(required = false) String deviceIds, @RequestParam(required = false) String time ) { if (ObjectUtils.isEmpty(id)) { id = null; } if (ObjectUtils.isEmpty(deviceIds)) { deviceIds = null; } if (ObjectUtils.isEmpty(time)) { time = null; }else if (!DateUtil.verification(time, DateUtil.formatter) ){ throw new ControllerException(ErrorCode.ERROR400.getCode(), "time格式为" + DateUtil.PATTERN); } List deviceIdList = null; if (deviceIds != null) { String[] deviceIdArray = deviceIds.split(","); deviceIdList = Arrays.asList(deviceIdArray); } return deviceAlarmService.clearAlarmBeforeTime(id, deviceIdList, time); } /** * 测试向上级/设备发送模拟报警通知 * * @param deviceId 报警id * @return */ @GetMapping("/test/notify/alarm") @Operation(summary = "测试向上级/设备发送模拟报警通知", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号") public void delete(@RequestParam String deviceId) { Device device = deviceService.getDeviceByDeviceId(deviceId); Platform platform = platformService.queryPlatformByServerGBId(deviceId); DeviceAlarm deviceAlarm = new DeviceAlarm(); deviceAlarm.setChannelId(deviceId); deviceAlarm.setAlarmDescription("test"); deviceAlarm.setAlarmMethod("1"); deviceAlarm.setAlarmPriority("1"); deviceAlarm.setAlarmTime(DateUtil.getNow()); deviceAlarm.setAlarmType("1"); deviceAlarm.setLongitude(115.33333); deviceAlarm.setLatitude(39.33333); if (device != null && platform == null) { try { commander.sendAlarmMessage(device, deviceAlarm); } catch (InvalidArgumentException | SipException | ParseException e) { } }else if (device == null && platform != null){ try { commanderForPlatform.sendAlarmMessage(platform, deviceAlarm); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage()); throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage()); } }else { throw new ControllerException(ErrorCode.ERROR100.getCode(),"无法确定" + deviceId + "是平台还是设备"); } } /** * 分页查询报警 * * @param deviceId 设备id * @param page 当前页 * @param count 每页查询数量 * @param alarmPriority 报警级别 * @param alarmMethod 报警方式 * @param alarmType 报警类型 * @param startTime 开始时间 * @param endTime 结束时间 * @return */ @Operation(summary = "分页查询报警", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "page",description = "当前页",required = true) @Parameter(name = "count",description = "每页查询数量",required = true) @Parameter(name = "deviceId",description = "设备id") @Parameter(name = "channelId",description = "通道id") @Parameter(name = "alarmPriority",description = "查询内容") @Parameter(name = "alarmMethod",description = "查询内容") @Parameter(name = "alarmType",description = "每页查询数量") @Parameter(name = "startTime",description = "开始时间") @Parameter(name = "endTime",description = "结束时间") @GetMapping("/all") public PageInfo getAll( @RequestParam int page, @RequestParam int count, @RequestParam(required = false) String deviceId, @RequestParam(required = false) String channelId, @RequestParam(required = false) String alarmPriority, @RequestParam(required = false) String alarmMethod, @RequestParam(required = false) String alarmType, @RequestParam(required = false) String startTime, @RequestParam(required = false) String endTime ) { if (ObjectUtils.isEmpty(alarmPriority)) { alarmPriority = null; } if (ObjectUtils.isEmpty(alarmMethod)) { alarmMethod = null; } if (ObjectUtils.isEmpty(alarmType)) { alarmType = null; } if (ObjectUtils.isEmpty(startTime)) { startTime = null; }else if (!DateUtil.verification(startTime, DateUtil.formatter) ){ throw new ControllerException(ErrorCode.ERROR400.getCode(), "startTime格式为" + DateUtil.PATTERN); } if (ObjectUtils.isEmpty(endTime)) { endTime = null; }else if (!DateUtil.verification(endTime, DateUtil.formatter) ){ throw new ControllerException(ErrorCode.ERROR400.getCode(), "endTime格式为" + DateUtil.PATTERN); } return deviceAlarmService.getAllAlarm(page, count, deviceId, channelId, alarmPriority, alarmMethod, alarmType, startTime, endTime); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/controller/ChannelController.java ================================================ package com.genersoft.iot.vmp.gb28181.controller; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.conf.security.JwtUtils; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.controller.bean.*; import com.genersoft.iot.vmp.gb28181.service.IGbChannelPlayService; import com.genersoft.iot.vmp.gb28181.service.IGbChannelService; import com.genersoft.iot.vmp.gb28181.utils.VectorTileCatch; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.service.bean.InviteErrorCode; import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.StreamContent; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import com.github.pagehelper.PageInfo; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanWrapperImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.web.bind.annotation.*; import org.springframework.web.context.request.async.DeferredResult; import java.beans.PropertyDescriptor; import java.net.MalformedURLException; import java.net.URL; import java.util.List; import java.util.concurrent.TimeUnit; @Tag(name = "全局通道管理") @RestController @Slf4j @RequestMapping(value = "/api/common/channel") public class ChannelController { @Autowired private RedisTemplate redisTemplate; @Autowired private IGbChannelService channelService; @Autowired private IGbChannelPlayService channelPlayService; @Autowired private UserSetting userSetting; @Autowired private VectorTileCatch vectorTileCatch; @Operation(summary = "查询通道信息", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "id", description = "通道的数据库自增Id", required = true) @GetMapping(value = "/one") public CommonGBChannel getOne(int id){ return channelService.getOne(id); } @Operation(summary = "获取行业编码列表", security = @SecurityRequirement(name = JwtUtils.HEADER)) @GetMapping("/industry/list") public List getIndustryCodeList(){ return channelService.getIndustryCodeList(); } @Operation(summary = "获取编码列表", security = @SecurityRequirement(name = JwtUtils.HEADER)) @GetMapping("/type/list") public List getDeviceTypeList(){ return channelService.getDeviceTypeList(); } @Operation(summary = "获取编码列表", security = @SecurityRequirement(name = JwtUtils.HEADER)) @GetMapping("/network/identification/list") public List getNetworkIdentificationTypeList(){ return channelService.getNetworkIdentificationTypeList(); } @Operation(summary = "更新通道", security = @SecurityRequirement(name = JwtUtils.HEADER)) @PostMapping("/update") public void update(@RequestBody CommonGBChannel channel){ BeanWrapperImpl wrapper = new BeanWrapperImpl(channel); int count = 0; for (PropertyDescriptor pd : wrapper.getPropertyDescriptors()) { String name = pd.getName(); if ("class".equals(name)) continue; if (pd.getReadMethod() == null) continue; Object val = wrapper.getPropertyValue(name); if (val != null) count++; } Assert.isTrue(count > 1, "未进行任何修改"); channelService.update(channel); } @Operation(summary = "重置国标通道", security = @SecurityRequirement(name = JwtUtils.HEADER)) @PostMapping("/reset") public void reset(@RequestBody ResetParam param){ Assert.notNull(param.getId(), "通道ID不能为空"); Assert.notEmpty(param.getChanelFields(), "待重置字段不可以空"); channelService.reset(param.getId(), param.getChanelFields()); } @Operation(summary = "增加通道", security = @SecurityRequirement(name = JwtUtils.HEADER)) @PostMapping("/add") public CommonGBChannel add(@RequestBody CommonGBChannel channel){ channelService.add(channel); return channel; } @Operation(summary = "获取通道列表", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "page", description = "当前页", required = true) @Parameter(name = "count", description = "每页查询数量", required = true) @Parameter(name = "query", description = "查询内容") @Parameter(name = "online", description = "是否在线") @Parameter(name = "hasRecordPlan", description = "是否已设置录制计划") @Parameter(name = "channelType", description = "通道类型, 0:国标设备,1:推流设备,2:拉流代理") @Parameter(name = "civilCode", description = "行政区划") @Parameter(name = "parentDeviceId", description = "父节点编码") @GetMapping("/list") public PageInfo queryList(int page, int count, @RequestParam(required = false) String query, @RequestParam(required = false) Boolean online, @RequestParam(required = false) Boolean hasRecordPlan, @RequestParam(required = false) Integer channelType, @RequestParam(required = false) String civilCode, @RequestParam(required = false) String parentDeviceId){ if (ObjectUtils.isEmpty(query)){ query = null; } if (ObjectUtils.isEmpty(civilCode)){ civilCode = null; } if (ObjectUtils.isEmpty(parentDeviceId)){ parentDeviceId = null; } return channelService.queryList(page, count, query, online, hasRecordPlan, channelType, civilCode, parentDeviceId); } @Operation(summary = "获取关联行政区划通道列表", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "page", description = "当前页", required = true) @Parameter(name = "count", description = "每页查询数量", required = true) @Parameter(name = "query", description = "查询内容") @Parameter(name = "online", description = "是否在线") @Parameter(name = "channelType", description = "通道类型, 0:国标设备,1:推流设备,2:拉流代理") @Parameter(name = "civilCode", description = "行政区划") @GetMapping("/civilcode/list") public PageInfo queryListByCivilCode(int page, int count, @RequestParam(required = false) String query, @RequestParam(required = false) Boolean online, @RequestParam(required = false) Integer channelType, @RequestParam(required = false) String civilCode){ if (ObjectUtils.isEmpty(query)){ query = null; } return channelService.queryListByCivilCode(page, count, query, online, channelType, civilCode); } @Operation(summary = "存在行政区划但无法挂载的通道列表", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "page", description = "当前页", required = true) @Parameter(name = "count", description = "每页查询数量", required = true) @Parameter(name = "query", description = "查询内容") @Parameter(name = "online", description = "是否在线") @Parameter(name = "channelType", description = "通道类型, 0:国标设备,1:推流设备,2:拉流代理") @GetMapping("/civilCode/unusual/list") public PageInfo queryListByCivilCodeForUnusual(int page, int count, @RequestParam(required = false) String query, @RequestParam(required = false) Boolean online, @RequestParam(required = false) Integer channelType){ if (ObjectUtils.isEmpty(query)){ query = null; } return channelService.queryListByCivilCodeForUnusual(page, count, query, online, channelType); } @Operation(summary = "存在父节点编号但无法挂载的通道列表", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "page", description = "当前页", required = true) @Parameter(name = "count", description = "每页查询数量", required = true) @Parameter(name = "query", description = "查询内容") @Parameter(name = "online", description = "是否在线") @Parameter(name = "channelType", description = "通道类型, 0:国标设备,1:推流设备,2:拉流代理") @GetMapping("/parent/unusual/list") public PageInfo queryListByParentForUnusual(int page, int count, @RequestParam(required = false) String query, @RequestParam(required = false) Boolean online, @RequestParam(required = false) Integer channelType){ if (ObjectUtils.isEmpty(query)){ query = null; } return channelService.queryListByParentForUnusual(page, count, query, online, channelType); } @Operation(summary = "清除存在行政区划但无法挂载的通道列表", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "param", description = "清理参数, all为true清理所有异常数据。 否则按照传入的设备Id清理", required = true) @PostMapping("/civilCode/unusual/clear") public void clearChannelCivilCode(@RequestBody ChannelToRegionParam param){ channelService.clearChannelCivilCode(param.getAll(), param.getChannelIds()); } @Operation(summary = "清除存在分组节点但无法挂载的通道列表", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "param", description = "清理参数, all为true清理所有异常数据。 否则按照传入的设备Id清理", required = true) @PostMapping("/parent/unusual/clear") public void clearChannelParent(@RequestBody ChannelToRegionParam param){ channelService.clearChannelParent(param.getAll(), param.getChannelIds()); } @Operation(summary = "获取关联业务分组通道列表", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "page", description = "当前页", required = true) @Parameter(name = "count", description = "每页查询数量", required = true) @Parameter(name = "query", description = "查询内容") @Parameter(name = "online", description = "是否在线") @Parameter(name = "channelType", description = "通道类型, 0:国标设备,1:推流设备,2:拉流代理") @Parameter(name = "groupDeviceId", description = "业务分组下的父节点ID") @GetMapping("/parent/list") public PageInfo queryListByParentId(int page, int count, @RequestParam(required = false) String query, @RequestParam(required = false) Boolean online, @RequestParam(required = false) Integer channelType, @RequestParam(required = false) String groupDeviceId){ if (ObjectUtils.isEmpty(query)){ query = null; } return channelService.queryListByParentId(page, count, query, online, channelType, groupDeviceId); } @Operation(summary = "通道设置行政区划", security = @SecurityRequirement(name = JwtUtils.HEADER)) @PostMapping("/region/add") public void addChannelToRegion(@RequestBody ChannelToRegionParam param){ Assert.notEmpty(param.getChannelIds(),"通道ID不可为空"); Assert.hasLength(param.getCivilCode(),"未添加行政区划"); channelService.addChannelToRegion(param.getCivilCode(), param.getChannelIds()); } @Operation(summary = "通道删除行政区划", security = @SecurityRequirement(name = JwtUtils.HEADER)) @PostMapping("/region/delete") public void deleteChannelToRegion(@RequestBody ChannelToRegionParam param){ Assert.isTrue(!param.getChannelIds().isEmpty() || !ObjectUtils.isEmpty(param.getCivilCode()),"参数异常"); channelService.deleteChannelToRegion(param.getCivilCode(), param.getChannelIds()); } @Operation(summary = "通道设置行政区划-根据国标设备", security = @SecurityRequirement(name = JwtUtils.HEADER)) @PostMapping("/region/device/add") public void addChannelToRegionByGbDevice(@RequestBody ChannelToRegionByGbDeviceParam param){ Assert.notEmpty(param.getDeviceIds(),"参数异常"); Assert.hasLength(param.getCivilCode(),"未添加行政区划"); channelService.addChannelToRegionByGbDevice(param.getCivilCode(), param.getDeviceIds()); } @Operation(summary = "通道删除行政区划-根据国标设备", security = @SecurityRequirement(name = JwtUtils.HEADER)) @PostMapping("/region/device/delete") public void deleteChannelToRegionByGbDevice(@RequestBody ChannelToRegionByGbDeviceParam param){ Assert.notEmpty(param.getDeviceIds(),"参数异常"); channelService.deleteChannelToRegionByGbDevice(param.getDeviceIds()); } @Operation(summary = "通道设置业务分组", security = @SecurityRequirement(name = JwtUtils.HEADER)) @PostMapping("/group/add") public void addChannelToGroup(@RequestBody ChannelToGroupParam param){ Assert.notEmpty(param.getChannelIds(),"通道ID不可为空"); Assert.hasLength(param.getParentId(),"未添加上级分组编号"); Assert.hasLength(param.getBusinessGroup(),"未添加业务分组"); channelService.addChannelToGroup(param.getParentId(), param.getBusinessGroup(), param.getChannelIds()); } @Operation(summary = "通道删除业务分组", security = @SecurityRequirement(name = JwtUtils.HEADER)) @PostMapping("/group/delete") public void deleteChannelToGroup(@RequestBody ChannelToGroupParam param){ Assert.isTrue(!param.getChannelIds().isEmpty() || (!ObjectUtils.isEmpty(param.getParentId()) && !ObjectUtils.isEmpty(param.getBusinessGroup())), "参数异常"); channelService.deleteChannelToGroup(param.getParentId(), param.getBusinessGroup(), param.getChannelIds()); } @Operation(summary = "通道设置业务分组-根据国标设备", security = @SecurityRequirement(name = JwtUtils.HEADER)) @PostMapping("/group/device/add") public void addChannelToGroupByGbDevice(@RequestBody ChannelToGroupByGbDeviceParam param){ Assert.notEmpty(param.getDeviceIds(),"参数异常"); Assert.hasLength(param.getParentId(),"未添加上级分组编号"); Assert.hasLength(param.getBusinessGroup(),"未添加业务分组"); channelService.addChannelToGroupByGbDevice(param.getParentId(), param.getBusinessGroup(), param.getDeviceIds()); } @Operation(summary = "通道删除业务分组-根据国标设备", security = @SecurityRequirement(name = JwtUtils.HEADER)) @PostMapping("/group/device/delete") public void deleteChannelToGroupByGbDevice(@RequestBody ChannelToGroupByGbDeviceParam param){ Assert.notEmpty(param.getDeviceIds(),"参数异常"); channelService.deleteChannelToGroupByGbDevice(param.getDeviceIds()); } @Operation(summary = "播放通道", security = @SecurityRequirement(name = JwtUtils.HEADER)) @GetMapping("/play") public DeferredResult> play(HttpServletRequest request, Integer channelId){ Assert.notNull(channelId,"参数异常"); CommonGBChannel channel = channelService.getOne(channelId); Assert.notNull(channel, "通道不存在"); DeferredResult> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue()); ErrorCallback callback = (code, msg, streamInfo) -> { if (code == InviteErrorCode.SUCCESS.getCode()) { WVPResult wvpResult = WVPResult.success(); if (streamInfo != null) { if (userSetting.getUseSourceIpAsStreamIp()) { streamInfo=streamInfo.clone();//深拷贝 String host; try { URL url=new URL(request.getRequestURL().toString()); host=url.getHost(); } catch (MalformedURLException e) { host=request.getLocalAddr(); } streamInfo.changeStreamIp(host); } if (!ObjectUtils.isEmpty(streamInfo.getMediaServer().getTranscodeSuffix()) && !"null".equalsIgnoreCase(streamInfo.getMediaServer().getTranscodeSuffix())) { streamInfo.setStream(streamInfo.getStream() + "_" + streamInfo.getMediaServer().getTranscodeSuffix()); } wvpResult.setData(new StreamContent(streamInfo)); }else { wvpResult.setCode(code); wvpResult.setMsg(msg); } result.setResult(wvpResult); }else { result.setResult(WVPResult.fail(code, msg)); } }; channelPlayService.play(channel, null, userSetting.getRecordSip(), callback); return result; } @Operation(summary = "停止播放通道", security = @SecurityRequirement(name = JwtUtils.HEADER)) @GetMapping("/play/stop") public void stopPlay(Integer channelId){ Assert.notNull(channelId,"参数异常"); CommonGBChannel channel = channelService.getOne(channelId); Assert.notNull(channel, "通道不存在"); channelPlayService.stopPlay(channel); } @Operation(summary = "录像查询", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "channelId", description = "通道ID", required = true) @Parameter(name = "startTime", description = "开始时间", required = true) @Parameter(name = "endTime", description = "结束时间", required = true) @GetMapping("/playback/query") public DeferredResult>> queryRecord(Integer channelId, String startTime, String endTime){ DeferredResult>> result = new DeferredResult<>(Long.valueOf(userSetting.getRecordInfoTimeout()), TimeUnit.MILLISECONDS); if (!DateUtil.verification(startTime, DateUtil.formatter)){ throw new ControllerException(ErrorCode.ERROR100.getCode(), "startTime格式为" + DateUtil.PATTERN); } if (!DateUtil.verification(endTime, DateUtil.formatter)){ throw new ControllerException(ErrorCode.ERROR100.getCode(), "endTime格式为" + DateUtil.PATTERN); } CommonGBChannel channel = channelService.getOne(channelId); Assert.notNull(channel, "通道不存在"); channelPlayService.queryRecord(channel, startTime, endTime, (code, msg, data) -> { WVPResult> wvpResult = new WVPResult<>(); wvpResult.setCode(code); wvpResult.setMsg(msg); wvpResult.setData(data); result.setResult(wvpResult); }); result.onTimeout(()->{ WVPResult> wvpResult = new WVPResult<>(); wvpResult.setCode(ErrorCode.ERROR100.getCode()); wvpResult.setMsg("timeout"); result.setResult(wvpResult); }); return result; } @Operation(summary = "录像回放", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "channelId", description = "通道ID", required = true) @Parameter(name = "startTime", description = "开始时间", required = true) @Parameter(name = "endTime", description = "结束时间", required = true) @GetMapping("/playback") public DeferredResult> playback(HttpServletRequest request, Integer channelId, String startTime, String endTime){ Assert.notNull(channelId,"参数异常"); CommonGBChannel channel = channelService.getOne(channelId); Assert.notNull(channel, "通道不存在"); DeferredResult> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue()); ErrorCallback callback = (code, msg, streamInfo) -> { if (code == InviteErrorCode.SUCCESS.getCode()) { WVPResult wvpResult = WVPResult.success(); if (streamInfo != null) { if (userSetting.getUseSourceIpAsStreamIp()) { streamInfo=streamInfo.clone();//深拷贝 String host; try { URL url=new URL(request.getRequestURL().toString()); host=url.getHost(); } catch (MalformedURLException e) { host=request.getLocalAddr(); } streamInfo.changeStreamIp(host); } if (!ObjectUtils.isEmpty(streamInfo.getMediaServer().getTranscodeSuffix()) && !"null".equalsIgnoreCase(streamInfo.getMediaServer().getTranscodeSuffix())) { streamInfo.setStream(streamInfo.getStream() + "_" + streamInfo.getMediaServer().getTranscodeSuffix()); } wvpResult.setData(new StreamContent(streamInfo)); }else { wvpResult.setCode(code); wvpResult.setMsg(msg); } result.setResult(wvpResult); }else { result.setResult(WVPResult.fail(code, msg)); } }; channelPlayService.playback(channel, DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime), DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime), callback); return result; } @Operation(summary = "停止录像回放", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "channelId", description = "通道ID", required = true) @Parameter(name = "stream", description = "流ID", required = true) @GetMapping("/playback/stop") public void stopPlayback(Integer channelId, String stream){ Assert.notNull(channelId,"参数异常"); CommonGBChannel channel = channelService.getOne(channelId); Assert.notNull(channel, "通道不存在"); channelPlayService.stopPlayback(channel, stream); } @Operation(summary = "暂停录像回放", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "channelId", description = "通道ID", required = true) @Parameter(name = "stream", description = "流ID", required = true) @GetMapping("/playback/pause") public void pausePlayback(Integer channelId, String stream){ Assert.notNull(channelId,"参数异常"); CommonGBChannel channel = channelService.getOne(channelId); Assert.notNull(channel, "通道不存在"); channelPlayService.playbackPause(channel, stream); } @Operation(summary = "恢复录像回放", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "channelId", description = "通道ID", required = true) @Parameter(name = "stream", description = "流ID", required = true) @GetMapping("/playback/resume") public void resumePlayback(Integer channelId, String stream){ Assert.notNull(channelId,"参数异常"); CommonGBChannel channel = channelService.getOne(channelId); Assert.notNull(channel, "通道不存在"); channelPlayService.playbackResume(channel, stream); } @Operation(summary = "拖动录像回放", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "channelId", description = "通道ID", required = true) @Parameter(name = "stream", description = "流ID", required = true) @Parameter(name = "seekTime", description = "将要播放的时间", required = true) @GetMapping("/playback/seek") public void seekPlayback(Integer channelId, String stream, Long seekTime){ Assert.notNull(channelId,"参数异常"); Assert.notNull(seekTime,"参数异常"); CommonGBChannel channel = channelService.getOne(channelId); Assert.notNull(channel, "通道不存在"); channelPlayService.playbackSeek(channel, stream, seekTime); } @Operation(summary = "拖动录像回放", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "channelId", description = "通道ID", required = true) @Parameter(name = "stream", description = "流ID", required = true) @Parameter(name = "speed", description = "倍速", required = true) @GetMapping("/playback/speed") public void seekPlayback(Integer channelId, String stream, Double speed){ Assert.notNull(channelId,"参数异常"); Assert.notNull(speed,"参数异常"); CommonGBChannel channel = channelService.getOne(channelId); Assert.notNull(channel, "通道不存在"); channelPlayService.playbackSpeed(channel, stream, speed); } @Operation(summary = "为地图获取通道列表", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "query", description = "查询内容") @Parameter(name = "online", description = "是否在线") @Parameter(name = "hasRecordPlan", description = "是否已设置录制计划") @Parameter(name = "channelType", description = "通道类型, 0:国标设备,1:推流设备,2:拉流代理") @Parameter(name = "geoCoordSys", description = "地理坐标系, WGS84/GCJ02") @GetMapping("/map/list") public List queryListForMap( @RequestParam(required = false) String query, @RequestParam(required = false) Boolean online, @RequestParam(required = false) Boolean hasRecordPlan, @RequestParam(required = false) Integer channelType){ if (ObjectUtils.isEmpty(query)){ query = null; } return channelService.queryListForMap(query, online, hasRecordPlan, channelType); } @Operation(summary = "为地图去除抽稀结果", security = @SecurityRequirement(name = JwtUtils.HEADER)) @PostMapping("/map/reset-level") public void resetLevel(){ channelService.resetLevel(); } @Operation(summary = "执行抽稀", security = @SecurityRequirement(name = JwtUtils.HEADER)) @PostMapping("/map/thin/draw") public String drawThin(@RequestBody DrawThinParam param){ if(param == null || param.getZoomParam() == null || param.getZoomParam().isEmpty()) { throw new ControllerException(ErrorCode.ERROR400); } return channelService.drawThin(param.getZoomParam(), param.getExtent(), param.getGeoCoordSys()); } @Operation(summary = "清除未保存的抽稀结果", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "id", description = "抽稀ID", required = true) @GetMapping("/map/thin/clear") public void clearThin(String id){ vectorTileCatch.remove(id); } @Operation(summary = "保存的抽稀结果", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "id", description = "抽稀ID", required = true) @GetMapping("/map/thin/save") public void saveThin(String id){ channelService.saveThin(id); } @Operation(summary = "获取抽稀执行的进度", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "id", description = "抽稀ID", required = true) @GetMapping("/map/thin/progress") public DrawThinProcess thinProgress(String id){ return channelService.thinProgress(id); } @Operation(summary = "为地图提供标准mvt图层", security = @SecurityRequirement(name = JwtUtils.HEADER)) @GetMapping(value = "/map/tile/{z}/{x}/{y}", produces = "application/x-protobuf") @Parameter(name = "geoCoordSys", description = "地理坐标系, WGS84/GCJ02") public ResponseEntity getTile(@PathVariable int z, @PathVariable int x, @PathVariable int y, String geoCoordSys){ try { byte[] mvt = channelService.getTile(z, x, y, geoCoordSys); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.parseMediaType("application/x-protobuf")); if (mvt == null) { headers.setContentLength(0); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null); } headers.setContentLength(mvt.length); return new ResponseEntity<>(mvt, headers, HttpStatus.OK); } catch (Exception e) { log.error("构建矢量瓦片失败: z: {}, x: {}, y:{}", z, x, y, e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null); } } @Operation(summary = "为地图提供经过抽稀的标准mvt图层", security = @SecurityRequirement(name = JwtUtils.HEADER)) @GetMapping(value = "/map/thin/tile/{z}/{x}/{y}", produces = "application/x-protobuf") @Parameter(name = "geoCoordSys", description = "地理坐标系, WGS84/GCJ02") @Parameter(name = "thinId", description = "抽稀结果ID") public ResponseEntity getThinTile(@PathVariable int z, @PathVariable int x, @PathVariable int y, String geoCoordSys, @RequestParam(required = false) String thinId){ if (ObjectUtils.isEmpty(thinId)) { thinId = "DEFAULT"; } String catchKey = z + "_" + x + "_" + y + "_" + geoCoordSys.toUpperCase(); byte[] mvt = vectorTileCatch.getVectorTile(thinId, catchKey); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.parseMediaType("application/x-protobuf")); if (mvt == null) { headers.setContentLength(0); return ResponseEntity.status(HttpStatus.OK).body(null); } headers.setContentLength(mvt.length); return new ResponseEntity<>(mvt, headers, HttpStatus.OK); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/controller/ChannelFrontEndController.java ================================================ package com.genersoft.iot.vmp.gb28181.controller; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.conf.security.JwtUtils; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.service.IGbChannelControlService; import com.genersoft.iot.vmp.gb28181.service.IGbChannelService; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.Assert; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.async.DeferredResult; import java.util.List; @Tag(name = "全局通道前端控制") @RestController @Slf4j @RequestMapping(value = "/api/common/channel/front-end") public class ChannelFrontEndController { @Autowired private IGbChannelService channelService; @Autowired private IGbChannelControlService channelControlService; @Operation(summary = "云台控制", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "channelId", description = "通道ID", required = true) @Parameter(name = "command", description = "控制指令,允许值: left, right, up, down, upleft, upright, downleft, downright, zoomin, zoomout, stop", required = true) @Parameter(name = "panSpeed", description = "水平速度(0-100)", required = true) @Parameter(name = "tiltSpeed", description = "垂直速度(0-100)", required = true) @Parameter(name = "zoomSpeed", description = "缩放速度(0-100)", required = true) @GetMapping("/ptz") public DeferredResult> ptz(Integer channelId, String command, Integer panSpeed, Integer tiltSpeed, Integer zoomSpeed){ if (log.isDebugEnabled()) { log.debug("[通用通道]云台控制 API调用,channelId:{} ,command:{} ,panSpeed:{} ,tiltSpeed:{} ,zoomSpeed:{}",channelId, command, panSpeed, tiltSpeed, zoomSpeed); } CommonGBChannel channel = channelService.getOne(channelId); Assert.notNull(channel, "通道不存在"); if (panSpeed == null) { panSpeed = 50; }else if (panSpeed < 0 || panSpeed > 100) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "panSpeed 为 0-100的数字"); } if (tiltSpeed == null) { tiltSpeed = 50; }else if (tiltSpeed < 0 || tiltSpeed > 100) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "tiltSpeed 为 0-100的数字"); } if (zoomSpeed == null) { zoomSpeed = 50; }else if (zoomSpeed < 0 || zoomSpeed > 100) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "zoomSpeed 为 0-100的数字"); } FrontEndControlCodeForPTZ controlCode = new FrontEndControlCodeForPTZ(); controlCode.setPanSpeed(panSpeed); controlCode.setTiltSpeed(tiltSpeed); controlCode.setZoomSpeed(zoomSpeed); switch (command){ case "left": controlCode.setPan(0); break; case "right": controlCode.setPan(1); break; case "up": controlCode.setTilt(0); break; case "down": controlCode.setTilt(1); break; case "upleft": controlCode.setPan(0); controlCode.setTilt(0); break; case "upright": controlCode.setTilt(0); controlCode.setPan(1); break; case "downleft": controlCode.setPan(0); controlCode.setTilt(1); break; case "downright": controlCode.setTilt(1); controlCode.setPan(1); break; case "zoomin": controlCode.setZoom(1); break; case "zoomout": controlCode.setZoom(0); break; default: break; } DeferredResult> result = new DeferredResult<>(); result.onTimeout(()->{ WVPResult wvpResult = WVPResult.fail(ErrorCode.ERROR100.getCode(), "请求超时"); result.setResult(wvpResult); }); channelControlService.ptz(channel, controlCode, (code, msg, data) -> { WVPResult wvpResult = new WVPResult<>(); wvpResult.setCode(code); wvpResult.setMsg(msg); wvpResult.setData(data); result.setResult(wvpResult); }); return result; } @Operation(summary = "光圈控制", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "command", description = "控制指令,允许值: in, out, stop", required = true) @Parameter(name = "speed", description = "光圈速度(0-100)", required = true) @GetMapping("/fi/iris") public DeferredResult> iris(Integer channelId, String command, Integer speed){ if (log.isDebugEnabled()) { log.debug("[通用通道]光圈控制 API调用,channelId:{} ,command:{} ,speed:{} ",channelId, command, speed); } CommonGBChannel channel = channelService.getOne(channelId); Assert.notNull(channel, "通道不存在"); if (speed == null) { speed = 50; }else if (speed < 0 || speed > 100) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "speed 为 0-100的数字"); } FrontEndControlCodeForFI controlCode = new FrontEndControlCodeForFI(); controlCode.setIrisSpeed(speed); switch (command){ case "in": controlCode.setIris(1); break; case "out": controlCode.setIris(0); break; default: break; } DeferredResult> result = new DeferredResult<>(); result.onTimeout(()->{ WVPResult wvpResult = WVPResult.fail(ErrorCode.ERROR100.getCode(), "请求超时"); result.setResult(wvpResult); }); ErrorCallback callback = (code, msg, data) -> { WVPResult wvpResult = new WVPResult<>(); wvpResult.setCode(code); wvpResult.setMsg(msg); wvpResult.setData(data); result.setResult(wvpResult); }; channelControlService.fi(channel, controlCode, callback); return result; } @Operation(summary = "聚焦控制", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "command", description = "控制指令,允许值: near, far, stop", required = true) @Parameter(name = "speed", description = "聚焦速度(0-100)", required = true) @GetMapping("/fi/focus") public DeferredResult> focus(Integer channelId, String command, Integer speed){ if (log.isDebugEnabled()) { log.debug("[通用通道]聚焦控制 API调用,channelId:{} ,command:{} ,speed:{} ", channelId, command, speed); } if (speed == null) { speed = 50; }else if (speed < 0 || speed > 100) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "speed 为 0-100的数字"); } CommonGBChannel channel = channelService.getOne(channelId); Assert.notNull(channel, "通道不存在"); FrontEndControlCodeForFI controlCode = new FrontEndControlCodeForFI(); controlCode.setFocusSpeed(speed); switch (command){ case "near": controlCode.setFocus(0); break; case "far": controlCode.setFocus(1); break; default: break; } DeferredResult> result = new DeferredResult<>(); result.onTimeout(()->{ WVPResult wvpResult = WVPResult.fail(ErrorCode.ERROR100.getCode(), "请求超时"); result.setResult(wvpResult); }); ErrorCallback callback = (code, msg, data) -> { WVPResult wvpResult = new WVPResult<>(); wvpResult.setCode(code); wvpResult.setMsg(msg); wvpResult.setData(data); result.setResult(wvpResult); }; channelControlService.fi(channel, controlCode, callback); return result; } @Operation(summary = "查询预置位", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "channelId", description = "通道国标编号", required = true) @GetMapping("/preset/query") public DeferredResult>> queryPreset(Integer channelId) { if (log.isDebugEnabled()) { log.debug("[通用通道] 预置位查询API调用, {}", channelId); } CommonGBChannel channel = channelService.getOne(channelId); Assert.notNull(channel, "通道不存在"); DeferredResult>> result = new DeferredResult<>(); result.onTimeout(()->{ WVPResult> wvpResult = WVPResult.fail(ErrorCode.ERROR100.getCode(), "请求超时"); result.setResult(wvpResult); }); ErrorCallback> callback = (code, msg, data) -> { WVPResult> wvpResult = new WVPResult<>(); wvpResult.setCode(code); wvpResult.setMsg(msg); wvpResult.setData(data); result.setResult(wvpResult); }; channelControlService.queryPreset(channel, callback); return result; } private DeferredResult> controlPreset(Integer channelId, FrontEndControlCodeForPreset controlCode) { CommonGBChannel channel = channelService.getOne(channelId); Assert.notNull(channel, "通道不存在"); DeferredResult> result = new DeferredResult<>(); result.onTimeout(()->{ WVPResult wvpResult = WVPResult.fail(ErrorCode.ERROR100.getCode(), "请求超时"); result.setResult(wvpResult); }); ErrorCallback callback = (code, msg, data) -> { WVPResult wvpResult = new WVPResult<>(); wvpResult.setCode(code); wvpResult.setMsg(msg); wvpResult.setData(data); result.setResult(wvpResult); }; channelControlService.preset(channel, controlCode, callback); return result; } @Operation(summary = "预置位指令-设置预置位", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "presetId", description = "预置位编号", required = true) @Parameter(name = "presetName", description = "预置位名称", required = true) @GetMapping("/preset/add") public DeferredResult> addPreset(Integer channelId, Integer presetId, String presetName) { FrontEndControlCodeForPreset controlCode = new FrontEndControlCodeForPreset(); controlCode.setCode(1); controlCode.setPresetId(presetId); controlCode.setPresetName(presetName); return controlPreset(channelId, controlCode); } @Operation(summary = "预置位指令-调用预置位", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "presetId", description = "预置位编号(1-100)", required = true) @GetMapping("/preset/call") public DeferredResult> callPreset(Integer channelId, Integer presetId) { FrontEndControlCodeForPreset controlCode = new FrontEndControlCodeForPreset(); controlCode.setCode(2); controlCode.setPresetId(presetId); return controlPreset(channelId, controlCode); } @Operation(summary = "预置位指令-删除预置位", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "presetId", description = "预置位编号(1-100)", required = true) @GetMapping("/preset/delete") public DeferredResult> deletePreset(Integer channelId, Integer presetId) { FrontEndControlCodeForPreset controlCode = new FrontEndControlCodeForPreset(); controlCode.setCode(3); controlCode.setPresetId(presetId); return controlPreset(channelId, controlCode); } private DeferredResult> tourControl(Integer channelId, FrontEndControlCodeForTour controlCode) { CommonGBChannel channel = channelService.getOne(channelId); Assert.notNull(channel, "通道不存在"); DeferredResult> result = new DeferredResult<>(); result.onTimeout(()->{ WVPResult wvpResult = WVPResult.fail(ErrorCode.ERROR100.getCode(), "请求超时"); result.setResult(wvpResult); }); ErrorCallback callback = (code, msg, data) -> { WVPResult wvpResult = new WVPResult<>(); wvpResult.setCode(code); wvpResult.setMsg(msg); wvpResult.setData(data); result.setResult(wvpResult); }; channelControlService.tour(channel, controlCode, callback); return result; } @Operation(summary = "巡航指令-加入巡航点", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "tourId", description = "巡航组号", required = true) @Parameter(name = "presetId", description = "预置位编号", required = true) @GetMapping("/tour/point/add") public DeferredResult> addTourPoint(Integer channelId, Integer tourId, Integer presetId) { FrontEndControlCodeForTour controlCode = new FrontEndControlCodeForTour(); controlCode.setCode(1); controlCode.setPresetId(presetId); controlCode.setTourId(tourId); return tourControl(channelId, controlCode); } @Operation(summary = "巡航指令-删除一个巡航点", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "tourId", description = "巡航组号(1-100)", required = true) @Parameter(name = "presetId", description = "预置位编号(0-100, 为0时删除整个巡航)", required = true) @GetMapping("/tour/point/delete") public DeferredResult> deleteCruisePoint(Integer channelId, Integer tourId, Integer presetId) { FrontEndControlCodeForTour controlCode = new FrontEndControlCodeForTour(); controlCode.setCode(2); controlCode.setPresetId(presetId); controlCode.setTourId(tourId); return tourControl(channelId, controlCode); } @Operation(summary = "巡航指令-设置巡航速度", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "tourId", description = "巡航组号(0-100)", required = true) @Parameter(name = "speed", description = "巡航速度(1-4095)", required = true) @Parameter(name = "presetId", description = "预置位编号", required = true) @GetMapping("/tour/speed") public DeferredResult> setCruiseSpeed(Integer channelId, Integer tourId, Integer speed, Integer presetId) { FrontEndControlCodeForTour controlCode = new FrontEndControlCodeForTour(); controlCode.setCode(3); controlCode.setTourSpeed(speed); controlCode.setTourId(tourId); controlCode.setPresetId(presetId); return tourControl(channelId, controlCode); } @Operation(summary = "巡航指令-设置巡航停留时间", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "tourId", description = "巡航组号", required = true) @Parameter(name = "time", description = "巡航停留时间(1-4095)", required = true) @Parameter(name = "presetId", description = "预置位编号", required = true) @GetMapping("/tour/time") public DeferredResult> setCruiseTime(Integer channelId, Integer tourId, Integer time, Integer presetId) { FrontEndControlCodeForTour controlCode = new FrontEndControlCodeForTour(); controlCode.setCode(4); controlCode.setTourTime(time); controlCode.setTourId(tourId); controlCode.setPresetId(presetId); return tourControl(channelId, controlCode); } @Operation(summary = "巡航指令-开始巡航", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "tourId", description = "巡航组号)", required = true) @GetMapping("/tour/start") public DeferredResult> startCruise(Integer channelId, Integer tourId) { FrontEndControlCodeForTour controlCode = new FrontEndControlCodeForTour(); controlCode.setCode(5); controlCode.setTourId(tourId); return tourControl(channelId, controlCode); } @Operation(summary = "巡航指令-停止巡航", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "tourId", description = "巡航组号", required = true) @GetMapping("/tour/stop") public DeferredResult> stopCruise(Integer channelId, Integer tourId) { FrontEndControlCodeForTour controlCode = new FrontEndControlCodeForTour(); controlCode.setCode(6); controlCode.setTourId(tourId); return tourControl(channelId, controlCode); } private DeferredResult> scanControl(Integer channelId, FrontEndControlCodeForScan controlCode) { CommonGBChannel channel = channelService.getOne(channelId); Assert.notNull(channel, "通道不存在"); DeferredResult> result = new DeferredResult<>(); result.onTimeout(()->{ WVPResult wvpResult = WVPResult.fail(ErrorCode.ERROR100.getCode(), "请求超时"); result.setResult(wvpResult); }); ErrorCallback callback = (code, msg, data) -> { WVPResult wvpResult = new WVPResult<>(); wvpResult.setCode(code); wvpResult.setMsg(msg); wvpResult.setData(data); result.setResult(wvpResult); }; channelControlService.scan(channel, controlCode, callback); return result; } @Operation(summary = "扫描指令-开始自动扫描", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "scanId", description = "扫描组号(0-100)", required = true) @GetMapping("/scan/start") public DeferredResult> startScan(Integer channelId, Integer scanId) { FrontEndControlCodeForScan controlCode = new FrontEndControlCodeForScan(); controlCode.setCode(1); controlCode.setScanId(scanId); return scanControl(channelId, controlCode); } @Operation(summary = "扫描指令-停止自动扫描", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "scanId", description = "扫描组号(0-100)", required = true) @GetMapping("/scan/stop") public DeferredResult> stopScan(Integer channelId, Integer scanId) { FrontEndControlCodeForScan controlCode = new FrontEndControlCodeForScan(); controlCode.setCode(5); controlCode.setScanId(scanId); return scanControl(channelId, controlCode); } @Operation(summary = "扫描指令-设置自动扫描左边界", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "scanId", description = "扫描组号(0-100)", required = true) @GetMapping("/scan/set/left") public DeferredResult> setScanLeft(Integer channelId, Integer scanId) { FrontEndControlCodeForScan controlCode = new FrontEndControlCodeForScan(); controlCode.setCode(2); controlCode.setScanId(scanId); return scanControl(channelId, controlCode); } @Operation(summary = "扫描指令-设置自动扫描右边界", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "scanId", description = "扫描组号(0-100)", required = true) @GetMapping("/scan/set/right") public DeferredResult> setScanRight(Integer channelId, Integer scanId) { FrontEndControlCodeForScan controlCode = new FrontEndControlCodeForScan(); controlCode.setCode(3); controlCode.setScanId(scanId); return scanControl(channelId, controlCode); } @Operation(summary = "扫描指令-设置自动扫描速度", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "scanId", description = "扫描组号(0-100)", required = true) @Parameter(name = "speed", description = "自动扫描速度(1-4095)", required = true) @GetMapping("/scan/set/speed") public DeferredResult> setScanSpeed(Integer channelId, Integer scanId, Integer speed) { FrontEndControlCodeForScan controlCode = new FrontEndControlCodeForScan(); controlCode.setCode(4); controlCode.setScanId(scanId); controlCode.setScanSpeed(speed); return scanControl(channelId, controlCode); } @Operation(summary = "辅助开关控制指令-雨刷控制", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "command", description = "控制指令,允许值: on, off", required = true) @GetMapping("/wiper") public DeferredResult> wiper(Integer channelId, String command){ CommonGBChannel channel = channelService.getOne(channelId); Assert.notNull(channel, "通道不存在"); FrontEndControlCodeForWiper controlCode = new FrontEndControlCodeForWiper(); switch (command){ case "on": controlCode.setCode(1); break; case "off": controlCode.setCode(2); break; default: break; } DeferredResult> result = new DeferredResult<>(); result.onTimeout(()->{ WVPResult wvpResult = WVPResult.fail(ErrorCode.ERROR100.getCode(), "请求超时"); result.setResult(wvpResult); }); ErrorCallback callback = (code, msg, data) -> { WVPResult wvpResult = new WVPResult<>(); wvpResult.setCode(code); wvpResult.setMsg(msg); wvpResult.setData(data); result.setResult(wvpResult); }; channelControlService.wiper(channel, controlCode, callback); return result; } @Operation(summary = "辅助开关控制指令", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "command", description = "控制指令,允许值: on, off", required = true) @Parameter(name = "auxiliaryId", description = "开关编号", required = true) @GetMapping("/auxiliary") public DeferredResult> auxiliarySwitch(Integer channelId, String command, Integer auxiliaryId){ CommonGBChannel channel = channelService.getOne(channelId); Assert.notNull(channel, "通道不存在"); FrontEndControlCodeForAuxiliary controlCode = new FrontEndControlCodeForAuxiliary(); controlCode.setAuxiliaryId(auxiliaryId); switch (command){ case "on": controlCode.setCode(1); break; case "off": controlCode.setCode(2); break; default: break; } DeferredResult> result = new DeferredResult<>(); result.onTimeout(()->{ WVPResult wvpResult = WVPResult.fail(ErrorCode.ERROR100.getCode(), "请求超时"); result.setResult(wvpResult); }); ErrorCallback callback = (code, msg, data) -> { WVPResult wvpResult = new WVPResult<>(); wvpResult.setCode(code); wvpResult.setMsg(msg); wvpResult.setData(data); result.setResult(wvpResult); }; channelControlService.auxiliary(channel, controlCode, callback); return result; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/controller/DeviceConfig.java ================================================ /** * 设备设置命令API接口 * * @author lawrencehj * @date 2021年2月2日 */ package com.genersoft.iot.vmp.gb28181.controller; import com.genersoft.iot.vmp.conf.security.JwtUtils; import com.genersoft.iot.vmp.gb28181.bean.BasicParam; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.service.IDeviceService; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.Assert; import org.springframework.web.bind.annotation.*; import org.springframework.web.context.request.async.DeferredResult; @Slf4j @Tag(name = "国标设备配置") @RestController @RequestMapping("/api/device/config") public class DeviceConfig { @Autowired private IDeviceService deviceService; @GetMapping("/basicParam") @Operation(summary = "基本配置设置命令", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "basicParam", description = "基础配置参数", required = true) public DeferredResult> homePositionApi(BasicParam basicParam) { if (log.isDebugEnabled()) { log.debug("基本配置设置命令API调用"); } Assert.notNull(basicParam.getDeviceId(), "设备ID必须存在"); Device device = deviceService.getDeviceByDeviceId(basicParam.getDeviceId()); Assert.notNull(device, "设备不存在"); DeferredResult> deferredResult = new DeferredResult<>(); deviceService.deviceBasicConfig(device, basicParam, (code, msg, data) -> { deferredResult.setResult(new WVPResult<>(code, msg, data)); }); deferredResult.onTimeout(() -> { log.warn("[设备配置] 超时, {}", device.getDeviceId()); deferredResult.setResult(WVPResult.fail(ErrorCode.ERROR100.getCode(), "超时")); }); return deferredResult; } @Operation(summary = "设备配置查询", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "configType", description = "配置类型, 可选值," + "基本参数配置:BasicParam," + "视频参数范围:VideoParamOpt, " + "SVAC编码配置:SVACEncodeConfig, " + "SVAC解码配置:SVACDecodeConfig。" + "可同时查询多个配置类型,各类型以“/”分隔,") @GetMapping("/query") public DeferredResult> configDownloadApi(String deviceId,String configType, @RequestParam(required = false) String channelId) { if (log.isDebugEnabled()) { log.debug("设备配置查询请求API调用"); } Device device = deviceService.getDeviceByDeviceId(deviceId); Assert.notNull(device, "设备不存在"); DeferredResult> deferredResult = new DeferredResult<>(); deviceService.deviceConfigQuery(device, channelId, configType, (code, msg, data) -> { deferredResult.setResult(new WVPResult<>(code, msg, data)); }); deferredResult.onTimeout(() -> { log.warn("[获取设备配置] 超时, {}", device.getDeviceId()); deferredResult.setResult(WVPResult.fail(ErrorCode.ERROR100.getCode(), "超时")); }); return deferredResult; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/controller/DeviceControl.java ================================================ /** * 设备控制命令API接口 * * @author lawrencehj * @date 2021年2月1日 */ package com.genersoft.iot.vmp.gb28181.controller; import com.genersoft.iot.vmp.conf.security.JwtUtils; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.service.IDeviceService; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.Assert; import org.springframework.web.bind.annotation.*; import org.springframework.web.context.request.async.DeferredResult; @Tag(name = "国标设备控制") @Slf4j @RestController @RequestMapping("/api/device/control") public class DeviceControl { @Autowired private IDeviceService deviceService; @Operation(summary = "远程启动", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @GetMapping("/teleboot/{deviceId}") public void teleBootApi(@PathVariable String deviceId) { if (log.isDebugEnabled()) { log.debug("设备远程启动API调用"); } Device device = deviceService.getDeviceByDeviceId(deviceId); Assert.notNull(device, "设备不存在"); deviceService.teleboot(device); } @Operation(summary = "录像控制", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "recordCmdStr", description = "命令, 可选值:Record(手动录像),StopRecord(停止手动录像)", required = true) @GetMapping("/record") public DeferredResult> recordApi(String deviceId, String recordCmdStr, String channelId) { if (log.isDebugEnabled()) { log.debug("开始/停止录像API调用"); } Device device = deviceService.getDeviceByDeviceId(deviceId); Assert.notNull(device, "设备不存在"); DeferredResult> deferredResult = new DeferredResult<>(); deviceService.record(device, channelId, recordCmdStr, (code, msg, data) -> { deferredResult.setResult(new WVPResult<>(code, msg, data)); }); deferredResult.onTimeout(() -> { log.warn("[开始/停止录像] 操作超时, 设备未返回应答指令, {}", deviceId); deferredResult.setResult(WVPResult.fail(ErrorCode.ERROR100.getCode(), "操作超时, 设备未应答")); }); return deferredResult; } @Operation(summary = "布防/撤防", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "guardCmd", description = "命令, 可选值:SetGuard(布防),ResetGuard(撤防)", required = true) @GetMapping("/guard") public DeferredResult> guardApi(String deviceId, String guardCmd) { if (log.isDebugEnabled()) { log.debug("布防/撤防API调用"); } Device device = deviceService.getDeviceByDeviceId(deviceId); Assert.notNull(device, "设备不存在"); DeferredResult> result = new DeferredResult<>(); deviceService.guard(device, guardCmd, (code, msg, data) -> { result.setResult(new WVPResult<>(code, msg, data)); }); result.onTimeout(() -> { log.warn("[布防/撤防] 操作超时, 设备未返回应答指令, {}", deviceId); result.setResult(WVPResult.fail(ErrorCode.ERROR100.getCode(), "操作超时, 设备未应答")); }); return result; } @Operation(summary = "报警复位", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "alarmMethod", description = "报警方式, 报警方式条件(可选),取值0为全部,1为电话报警,2为设备报警,3为短信报警,4为\n" + "GPS报警,5为视频报警,6为设备故障报警,7其他报警;可以为直接组合如12为电话报警或设备报警") @Parameter(name = "alarmType", description = "报警类型, " + "报警类型。" + "报警方式为2时,不携带 AlarmType为默认的报警设备报警," + "携带 AlarmType取值及对应报警类型如下:" + "1-视频丢失报警;2-设备防拆报警;3-存储设备磁盘满报警;4-设备高温报警;5-设备低温报警。" + "报警方式为5时,取值如下:" + "1-人工视频报警;2-运动目标检测报警;3-遗留物检测报警;4-物体移除检测报警;5-绊线检测报警;" + "6-入侵检测报警;7-逆行检测报警;8-徘徊检测报警;9-流量统计报警;10-密度检测报警;" + "11-视频异常检测报警;12-快速移动报警。" + "报警方式为6时,取值如下:" + "1-存储设备磁盘故障报警;2-存储设备风扇故障报警") @GetMapping("/reset_alarm") public DeferredResult> resetAlarm(String deviceId, String channelId, @RequestParam(required = false) String alarmMethod, @RequestParam(required = false) String alarmType) { if (log.isDebugEnabled()) { log.debug("报警复位API调用"); } Device device = deviceService.getDeviceByDeviceId(deviceId); Assert.notNull(device, "设备不存在"); DeferredResult> result = new DeferredResult<>(); deviceService.resetAlarm(device, channelId, alarmMethod, alarmType, (code, msg, data) -> { result.setResult(new WVPResult<>(code, msg, data)); }); result.onTimeout(() -> { log.warn("[布防/撤防] 操作超时, 设备未返回应答指令, {}", deviceId); result.setResult(WVPResult.fail(ErrorCode.ERROR100.getCode(), "操作超时, 设备未应答")); }); return result; } @Operation(summary = "强制关键帧", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "channelId", description = "通道国标编号") @GetMapping("/i_frame") public void iFrame(String deviceId, @RequestParam(required = false) String channelId) { if (log.isDebugEnabled()) { log.debug("强制关键帧API调用"); } Device device = deviceService.getDeviceByDeviceId(deviceId); Assert.notNull(device, "设备不存在"); deviceService.iFrame(device, channelId); } @Operation(summary = "看守位控制", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "enabled", description = "是否开启看守位", required = true) @Parameter(name = "presetIndex", description = "调用预置位编号") @Parameter(name = "resetTime", description = "自动归位时间间隔 单位:秒") @GetMapping("/home_position") public DeferredResult> homePositionApi(String deviceId, String channelId, Boolean enabled, @RequestParam(required = false) Integer resetTime, @RequestParam(required = false) Integer presetIndex) { if (log.isDebugEnabled()) { log.debug("看守位控制API调用"); } Device device = deviceService.getDeviceByDeviceId(deviceId); Assert.notNull(device, "设备不存在"); DeferredResult> result = new DeferredResult<>(); deviceService.homePosition(device, channelId, enabled, resetTime, presetIndex, (code, msg, data) -> { result.setResult(new WVPResult<>(code, msg, data)); }); result.onTimeout(() -> { log.warn("[看守位控制] 操作超时, 设备未返回应答指令, {}", deviceId); result.setResult(WVPResult.fail(ErrorCode.ERROR100.getCode(), "操作超时, 设备未应答")); }); return result; } @Operation(summary = "拉框放大", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "length", description = "播放窗口长度像素值", required = true) @Parameter(name = "width", description = "播放窗口宽度像素值", required = true) @Parameter(name = "midpointx", description = "拉框中心的横轴坐标像素值", required = true) @Parameter(name = "midpointy", description = "拉框中心的纵轴坐标像素值", required = true) @Parameter(name = "lengthx", description = "拉框长度像素值", required = true) @Parameter(name = "lengthy", description = "拉框宽度像素值", required = true) @GetMapping("drag_zoom/zoom_in") public DeferredResult> dragZoomIn(@RequestParam String deviceId, String channelId, @RequestParam int length, @RequestParam int width, @RequestParam int midpointx, @RequestParam int midpointy, @RequestParam int lengthx, @RequestParam int lengthy) { if (log.isDebugEnabled()) { log.debug(String.format("设备拉框放大 API调用,deviceId:%s ,channelId:%s ,length:%d ,width:%d ,midpointx:%d ,midpointy:%d ,lengthx:%d ,lengthy:%d",deviceId, channelId, length, width, midpointx, midpointy,lengthx, lengthy)); } Device device = deviceService.getDeviceByDeviceId(deviceId); Assert.notNull(device, "设备不存在"); DeferredResult> result = new DeferredResult<>(); deviceService.dragZoomIn(device, channelId, length, width, midpointx, midpointy, lengthx,lengthy, (code, msg, data) -> { result.setResult(new WVPResult<>(code, msg, data)); }); result.onTimeout(() -> { log.warn("[设备拉框放大] 操作超时, 设备未返回应答指令, {}", deviceId); result.setResult(WVPResult.fail(ErrorCode.ERROR100.getCode(), "操作超时, 设备未应答")); }); return result; } @Operation(summary = "拉框缩小", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "channelId", description = "通道国标编号") @Parameter(name = "length", description = "播放窗口长度像素值", required = true) @Parameter(name = "width", description = "拉框中心的横轴坐标像素值", required = true) @Parameter(name = "midpointx", description = "拉框中心的横轴坐标像素值", required = true) @Parameter(name = "midpointy", description = "拉框中心的纵轴坐标像素值", required = true) @Parameter(name = "lengthx", description = "拉框长度像素值", required = true) @Parameter(name = "lengthy", description = "拉框宽度像素值", required = true) @GetMapping("/drag_zoom/zoom_out") public DeferredResult> dragZoomOut(@RequestParam String deviceId, @RequestParam(required = false) String channelId, @RequestParam int length, @RequestParam int width, @RequestParam int midpointx, @RequestParam int midpointy, @RequestParam int lengthx, @RequestParam int lengthy){ if (log.isDebugEnabled()) { log.debug(String.format("设备拉框缩小 API调用,deviceId:%s ,channelId:%s ,length:%d ,width:%d ,midpointx:%d ,midpointy:%d ,lengthx:%d ,lengthy:%d",deviceId, channelId, length, width, midpointx, midpointy,lengthx, lengthy)); } Device device = deviceService.getDeviceByDeviceId(deviceId); Assert.notNull(device, "设备不存在"); DeferredResult> result = new DeferredResult<>(); deviceService.dragZoomOut(device, channelId, length, width, midpointx, midpointy, lengthx,lengthy, (code, msg, data) -> { result.setResult(new WVPResult<>(code, msg, data)); }); result.onTimeout(() -> { log.warn("[设备拉框放大] 操作超时, 设备未返回应答指令, {}", deviceId); result.setResult(WVPResult.fail(ErrorCode.ERROR100.getCode(), "操作超时, 设备未应答")); }); return result; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/controller/DeviceQuery.java ================================================ package com.genersoft.iot.vmp.gb28181.controller; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.conf.DynamicTask; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.conf.security.JwtUtils; 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.service.IDeviceChannelService; import com.genersoft.iot.vmp.gb28181.service.IDeviceService; import com.genersoft.iot.vmp.gb28181.service.IInviteStreamService; import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander; import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcService; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import com.github.pagehelper.PageInfo; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; import org.apache.commons.compress.utils.IOUtils; import org.apache.ibatis.annotations.Options; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.web.bind.annotation.*; import org.springframework.web.context.request.async.DeferredResult; import jakarta.servlet.ServletOutputStream; import jakarta.servlet.http.HttpServletResponse; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; @Tag(name = "国标设备查询", description = "国标设备查询") @SuppressWarnings("rawtypes") @Slf4j @RestController @RequestMapping("/api/device/query") public class DeviceQuery { @Autowired private IDeviceChannelService deviceChannelService; @Autowired private IInviteStreamService inviteStreamService; @Autowired private IDeviceService deviceService; @Autowired private ISIPCommander cmder; @Autowired private DeferredResultHolder resultHolder; @Autowired private UserSetting userSetting; @Autowired private DynamicTask dynamicTask; @Autowired private IRedisRpcService redisRpcService; @Operation(summary = "查询国标设备", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @GetMapping("/devices/{deviceId}") public Device devices(@PathVariable String deviceId){ return deviceService.getDeviceByDeviceId(deviceId); } @Operation(summary = "分页查询国标设备", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "page", description = "当前页", required = true) @Parameter(name = "count", description = "每页查询数量", required = true) @Parameter(name = "query", description = "搜索", required = false) @Parameter(name = "status", description = "状态", required = false) @GetMapping("/devices") @Options() public PageInfo devices(int page, int count, String query, Boolean status){ if (ObjectUtils.isEmpty(query)){ query = null; } return deviceService.getAll(page, count, query, status); } @GetMapping("/devices/{deviceId}/channels") @Operation(summary = "分页查询通道", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "page", description = "当前页", required = true) @Parameter(name = "count", description = "每页查询数量", required = true) @Parameter(name = "query", description = "查询内容") @Parameter(name = "online", description = "是否在线") @Parameter(name = "channelType", description = "设备/子目录-> false/true") public PageInfo channels(@PathVariable String deviceId, int page, int count, @RequestParam(required = false) String query, @RequestParam(required = false) Boolean online, @RequestParam(required = false) Boolean channelType) { if (ObjectUtils.isEmpty(query)) { query = null; } return deviceChannelService.queryChannelsByDeviceId(deviceId, query, channelType, online, page, count); } @GetMapping("/streams") @Operation(summary = "分页查询存在流的通道", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "page", description = "当前页", required = true) @Parameter(name = "count", description = "每页查询数量", required = true) @Parameter(name = "query", description = "查询内容") public PageInfo streamChannels(int page, int count, @RequestParam(required = false) String query) { if (ObjectUtils.isEmpty(query)) { query = null; } return deviceChannelService.queryChannels(query, true, null, null, true, page, count); } @Operation(summary = "同步设备通道", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @GetMapping("/devices/{deviceId}/sync") public WVPResult devicesSync(@PathVariable String deviceId){ if (log.isDebugEnabled()) { log.debug("设备通道信息同步API调用,deviceId:" + deviceId); } Device device = deviceService.getDeviceByDeviceId(deviceId); if (device.getRegisterTime() == null) { WVPResult wvpResult = new WVPResult<>(); wvpResult.setCode(ErrorCode.ERROR100.getCode()); wvpResult.setMsg("设备尚未注册过"); return wvpResult; } return deviceService.devicesSync(device); } @Operation(summary = "移除设备", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @DeleteMapping("/devices/{deviceId}/delete") public String delete(@PathVariable String deviceId){ if (log.isDebugEnabled()) { log.debug("设备信息删除API调用,deviceId:" + deviceId); } // 清除redis记录 deviceService.delete(deviceId); JSONObject json = new JSONObject(); json.put("deviceId", deviceId); return json.toString(); } @Operation(summary = "分页查询子目录通道", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "page", description = "当前页", required = true) @Parameter(name = "count", description = "每页查询数量", required = true) @Parameter(name = "query", description = "查询内容") @Parameter(name = "online", description = "是否在线") @Parameter(name = "channelType", description = "设备/子目录-> false/true") @GetMapping("/sub_channels/{deviceId}/{channelId}/channels") public PageInfo subChannels(@PathVariable String deviceId, @PathVariable String channelId, int page, int count, @RequestParam(required = false) String query, @RequestParam(required = false) Boolean online, @RequestParam(required = false) Boolean channelType){ DeviceChannel deviceChannel = deviceChannelService.getOne(deviceId,channelId); if (deviceChannel == null) { PageInfo deviceChannelPageResult = new PageInfo<>(); return deviceChannelPageResult; } return deviceChannelService.getSubChannels(deviceChannel.getDataDeviceId(), channelId, query, channelType, online, page, count); } @Operation(summary = "开启/关闭通道的音频", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "channelId", description = "通道的数据库ID", required = true) @Parameter(name = "audio", description = "开启/关闭音频", required = true) @PostMapping("/channel/audio") public void changeAudio(Integer channelId, Boolean audio){ Assert.notNull(channelId, "通道的数据库ID不可为NULL"); Assert.notNull(audio, "开启/关闭音频不可为NULL"); deviceChannelService.changeAudio(channelId, audio); } @Operation(summary = "修改通道的码流类型", security = @SecurityRequirement(name = JwtUtils.HEADER)) @PostMapping("/channel/stream/identification/update/") public void updateChannelStreamIdentification(DeviceChannel channel){ deviceChannelService.updateChannelStreamIdentification(channel); } @Operation(summary = "获取单个通道详情", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备的国标编码", required = true) @Parameter(name = "channelDeviceId", description = "通道的国标编码", required = true) @GetMapping("/channel/one") public DeviceChannel getChannel(String deviceId, String channelDeviceId){ return deviceChannelService.getOne(deviceId, channelDeviceId); } @Operation(summary = "修改数据流传输模式", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "streamMode", description = "数据流传输模式, 取值:" + "UDP(udp传输),TCP-ACTIVE(tcp主动模式),TCP-PASSIVE(tcp被动模式)", required = true) @PostMapping("/transport/{deviceId}/{streamMode}") public void updateTransport(@PathVariable String deviceId, @PathVariable String streamMode){ Assert.isTrue(streamMode.equalsIgnoreCase("UDP") || streamMode.equalsIgnoreCase("TCP-ACTIVE") || streamMode.equalsIgnoreCase("TCP-PASSIVE"), "数据流传输模式, 取值:UDP/TCP-ACTIVE/TCP-PASSIVE"); Device device = deviceService.getDeviceByDeviceId(deviceId); device.setStreamMode(streamMode.toUpperCase()); deviceService.updateCustomDevice(device); } @Operation(summary = "添加设备信息", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "device", description = "设备", required = true) @PostMapping("/device/add") public void addDevice(@RequestBody Device device){ if (device == null || device.getDeviceId() == null) { throw new ControllerException(ErrorCode.ERROR400); } // 查看deviceId是否存在 boolean exist = deviceService.isExist(device.getDeviceId()); if (exist) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "设备编号已存在"); } deviceService.addCustomDevice(device); } @Operation(summary = "更新设备信息", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "device", description = "设备", required = true) @PostMapping("/device/update") public void updateDevice(@RequestBody Device device){ if (device == null || device.getDeviceId() == null || device.getId() <= 0) { throw new ControllerException(ErrorCode.ERROR400); } deviceService.updateCustomDevice(device); } @Operation(summary = "设备状态查询", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @GetMapping("/devices/{deviceId}/status") public DeferredResult> deviceStatusApi(@PathVariable String deviceId) { if (log.isDebugEnabled()) { log.debug("设备状态查询API调用"); } Device device = deviceService.getDeviceByDeviceId(deviceId); Assert.notNull(device, "设备不存在"); DeferredResult> result = new DeferredResult<>(); deviceService.deviceStatus(device, (code, msg, data) -> { result.setResult(new WVPResult<>(code, msg, data)); }); result.onTimeout(() -> { log.warn("[设备状态查询] 操作超时, 设备未返回应答指令, {}", deviceId); result.setResult(WVPResult.fail(ErrorCode.ERROR100.getCode(), "操作超时, 设备未应答")); }); return result; } @Operation(summary = "设备报警查询", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "startPriority", description = "报警起始级别, 0为全部,1为一级警情,2为二级警情,3为三级警情,4为四级警情") @Parameter(name = "endPriority", description = "报警终止级别, ,0为全部,1为一级警情,2为二级警情,3为三级警情,4为四级警情") @Parameter(name = "alarmMethod", description = "报警方式条件,取值0为全部,1为电话报警,2为设备报警,3为短信报警,4为GPS报警," + "5为视频报警,6为设备故障报警,7其他报警;可以为直接组合如12为电话报警或设备报警") @Parameter(name = "alarmType", description = "报警类型") @Parameter(name = "startTime", description = "报警发生起始时间") @Parameter(name = "endTime", description = "报警发生终止时间") @GetMapping("/alarm") public DeferredResult> alarmApi(String deviceId, @RequestParam(required = false) String startPriority, @RequestParam(required = false) String endPriority, @RequestParam(required = false) String alarmMethod, @RequestParam(required = false) String alarmType, @RequestParam(required = false) String startTime, @RequestParam(required = false) String endTime) { if (log.isDebugEnabled()) { log.debug("设备报警查询API调用"); } Device device = deviceService.getDeviceByDeviceId(deviceId); Assert.notNull(device, "设备不存在"); DeferredResult> result = new DeferredResult<>(); deviceService.alarm(device, startPriority,endPriority ,alarmMethod ,alarmType ,startTime ,endTime, (code, msg, data) -> { result.setResult(new WVPResult<>(code, msg, data)); }); result.onTimeout(() -> { log.warn("[设备报警查询] 操作超时, 设备未返回应答指令, {}", deviceId); result.setResult(WVPResult.fail(ErrorCode.ERROR100.getCode(), "操作超时, 设备未应答")); }); return result; } @Operation(summary = "设备信息查询", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @GetMapping("/info") public DeferredResult> deviceInfo(String deviceId) { if (log.isDebugEnabled()) { log.debug("设备信息查询API调用"); } Device device = deviceService.getDeviceByDeviceId(deviceId); Assert.notNull(device, "设备不存在"); DeferredResult> result = new DeferredResult<>(); deviceService.deviceInfo(device, (code, msg, data) -> { result.setResult(new WVPResult<>(code, msg, data)); }); result.onTimeout(() -> { log.warn("[设备信息查询] 操作超时, 设备未返回应答指令, {}", deviceId); result.setResult(WVPResult.fail(ErrorCode.ERROR100.getCode(), "操作超时, 设备未应答")); }); return result; } /** * 此接口保留仅作为兼容,后续将移除,请迁移至 */ @GetMapping("/{deviceId}/sync_status") @Operation(summary = "获取通道同步进度(此接口保留仅作为兼容,后续将移除,请迁移至 /sync_status?deviceId=)", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) public WVPResult getSyncStatusInPath(@PathVariable String deviceId) { SyncStatus channelSyncStatus = deviceService.getChannelSyncStatus(deviceId); WVPResult wvpResult = new WVPResult<>(); if (channelSyncStatus == null) { wvpResult.setCode(ErrorCode.ERROR100.getCode()); wvpResult.setMsg("同步不存在"); }else if (channelSyncStatus.getErrorMsg() != null) { wvpResult.setCode(ErrorCode.ERROR100.getCode()); wvpResult.setMsg(channelSyncStatus.getErrorMsg()); }else if (channelSyncStatus.getTotal() == null){ wvpResult.setCode(ErrorCode.SUCCESS.getCode()); wvpResult.setMsg("等待通道信息..."); }else if (channelSyncStatus.getTotal() == 0){ wvpResult.setCode(ErrorCode.SUCCESS.getCode()); wvpResult.setMsg(ErrorCode.SUCCESS.getMsg()); wvpResult.setData(channelSyncStatus); }else { wvpResult.setCode(ErrorCode.SUCCESS.getCode()); wvpResult.setMsg(ErrorCode.SUCCESS.getMsg()); wvpResult.setData(channelSyncStatus); } return wvpResult; } /** * 此接口保留仅作为兼容,后续将移除,请迁移至 */ @GetMapping("/sync_status") @Operation(summary = "获取通道同步进度", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) public WVPResult getSyncStatus(String deviceId) { SyncStatus channelSyncStatus = deviceService.getChannelSyncStatus(deviceId); WVPResult wvpResult = new WVPResult<>(); if (channelSyncStatus == null) { wvpResult.setCode(ErrorCode.ERROR100.getCode()); wvpResult.setMsg("同步不存在"); }else if (channelSyncStatus.getErrorMsg() != null) { wvpResult.setCode(ErrorCode.ERROR100.getCode()); wvpResult.setMsg(channelSyncStatus.getErrorMsg()); }else if (channelSyncStatus.getTotal() == null){ wvpResult.setCode(ErrorCode.SUCCESS.getCode()); wvpResult.setMsg("等待通道信息..."); }else if (channelSyncStatus.getTotal() == 0){ wvpResult.setCode(ErrorCode.SUCCESS.getCode()); wvpResult.setMsg(ErrorCode.SUCCESS.getMsg()); wvpResult.setData(channelSyncStatus); }else { wvpResult.setCode(ErrorCode.SUCCESS.getCode()); wvpResult.setMsg(ErrorCode.SUCCESS.getMsg()); wvpResult.setData(channelSyncStatus); } return wvpResult; } @GetMapping("/snap/{deviceId}/{channelId}") @Operation(summary = "请求截图") @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "mark", description = "标识", required = false) public void getSnap(HttpServletResponse resp, @PathVariable String deviceId, @PathVariable String channelId, @RequestParam(required = false) String mark) { try { final InputStream in = Files.newInputStream(new File("snap" + File.separator + deviceId + "_" + channelId + (mark == null? ".jpg": ("_" + mark + ".jpg"))).toPath()); resp.setContentType(MediaType.IMAGE_PNG_VALUE); ServletOutputStream outputStream = resp.getOutputStream(); IOUtils.copy(in, resp.getOutputStream()); in.close(); outputStream.close(); } catch (IOException e) { resp.setStatus(HttpServletResponse.SC_NO_CONTENT); } } @GetMapping("/channel/raw") @Operation(summary = "国标通道编辑时的数据回显") @Parameter(name = "id", description = "通道的Id", required = true) public DeviceChannel getRawChannel(int id) { return deviceChannelService.getRawChannel(id); } @GetMapping("/subscribe/catalog") @Operation(summary = "开启/关闭目录订阅") @Parameter(name = "id", description = "通道的Id", required = true) @Parameter(name = "cycle", description = "订阅周期", required = true) public void subscribeCatalog(int id, int cycle) { deviceService.subscribeCatalog(id, cycle); } @GetMapping("/subscribe/mobile-position") @Operation(summary = "开启/关闭移动位置订阅") @Parameter(name = "id", description = "通道的Id", required = true) @Parameter(name = "cycle", description = "订阅周期", required = true) @Parameter(name = "interval", description = "报送间隔", required = true) public void subscribeMobilePosition(int id, int cycle, int interval) { deviceService.subscribeMobilePosition(id, cycle, interval); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/controller/GBRecordController.java ================================================ package com.genersoft.iot.vmp.gb28181.controller; import com.genersoft.iot.vmp.common.InviteSessionType; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.conf.security.JwtUtils; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; import com.genersoft.iot.vmp.gb28181.bean.RecordInfo; import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService; import com.genersoft.iot.vmp.gb28181.service.IDeviceService; import com.genersoft.iot.vmp.gb28181.service.IPlayService; 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.cmd.impl.SIPCommander; import com.genersoft.iot.vmp.service.bean.InviteErrorCode; import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.StreamContent; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.async.DeferredResult; import jakarta.servlet.http.HttpServletRequest; import java.util.UUID; import java.util.concurrent.TimeUnit; @Tag(name = "国标录像") @Slf4j @RestController @RequestMapping("/api/gb_record") public class GBRecordController { @Autowired private SIPCommander cmder; @Autowired private DeferredResultHolder resultHolder; @Autowired private IPlayService playService; @Autowired private IDeviceChannelService channelService; @Autowired private IDeviceService deviceService; @Autowired private UserSetting userSetting; @Operation(summary = "录像查询", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "startTime", description = "开始时间", required = true) @Parameter(name = "endTime", description = "结束时间", required = true) @GetMapping("/query/{deviceId}/{channelId}") public DeferredResult> recordinfo(@PathVariable String deviceId, @PathVariable String channelId, String startTime, String endTime){ if (log.isDebugEnabled()) { log.debug(String.format("录像信息查询 API调用,deviceId:%s ,startTime:%s, endTime:%s",deviceId, startTime, endTime)); } DeferredResult> result = new DeferredResult<>(Long.valueOf(userSetting.getRecordInfoTimeout()), TimeUnit.MILLISECONDS); if (!DateUtil.verification(startTime, DateUtil.formatter)){ throw new ControllerException(ErrorCode.ERROR100.getCode(), "startTime格式为" + DateUtil.PATTERN); } if (!DateUtil.verification(endTime, DateUtil.formatter)){ throw new ControllerException(ErrorCode.ERROR100.getCode(), "endTime格式为" + DateUtil.PATTERN); } Device device = deviceService.getDeviceByDeviceId(deviceId); if (device == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), deviceId + " 不存在"); } DeviceChannel channel = channelService.getOneForSource(device.getId(), channelId); if (channel == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), channelId + " 不存在"); } channelService.queryRecordInfo(device, channel, startTime, endTime, (code, msg, data)->{ WVPResult wvpResult = new WVPResult<>(); wvpResult.setCode(code); wvpResult.setMsg(msg); wvpResult.setData(data); result.setResult(wvpResult); }); result.onTimeout(()->{ WVPResult wvpResult = new WVPResult<>(); wvpResult.setCode(ErrorCode.ERROR100.getCode()); wvpResult.setMsg("timeout"); result.setResult(wvpResult); }); return result; } @Operation(summary = "开始历史媒体下载", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "startTime", description = "开始时间", required = true) @Parameter(name = "endTime", description = "结束时间", required = true) @Parameter(name = "downloadSpeed", description = "下载倍速", required = true) @GetMapping("/download/start/{deviceId}/{channelId}") public DeferredResult> download(HttpServletRequest request, @PathVariable String deviceId, @PathVariable String channelId, String startTime, String endTime, String downloadSpeed) { if (log.isDebugEnabled()) { log.debug(String.format("历史媒体下载 API调用,deviceId:%s,channelId:%s,downloadSpeed:%s", deviceId, channelId, downloadSpeed)); } String uuid = UUID.randomUUID().toString(); String key = DeferredResultHolder.CALLBACK_CMD_DOWNLOAD + deviceId + channelId; DeferredResult> result = new DeferredResult<>(30000L); resultHolder.put(key, uuid, result); RequestMessage requestMessage = new RequestMessage(); requestMessage.setId(uuid); requestMessage.setKey(key); Device device = deviceService.getDeviceByDeviceId(deviceId); if (device == null) { log.warn("[开始历史媒体下载] 未找到设备 deviceId: {},channelId:{}", deviceId, channelId); throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到设备:" + deviceId); } DeviceChannel channel = channelService.getOne(deviceId, channelId); if (channel == null) { log.warn("[开始历史媒体下载] 未找到通道 deviceId: {},channelId:{}", deviceId, channelId); throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到通道:" + channelId); } playService.download(device, channel, startTime, endTime, Integer.parseInt(downloadSpeed), (code, msg, data)->{ WVPResult wvpResult = new WVPResult<>(); if (code == InviteErrorCode.SUCCESS.getCode()) { wvpResult.setCode(ErrorCode.SUCCESS.getCode()); wvpResult.setMsg(ErrorCode.SUCCESS.getMsg()); if (data != null) { StreamInfo streamInfo = (StreamInfo)data; if (userSetting.getUseSourceIpAsStreamIp()) { streamInfo.changeStreamIp(request.getLocalAddr()); } wvpResult.setData(new StreamContent(streamInfo)); } }else { wvpResult.setCode(code); wvpResult.setMsg(msg); } requestMessage.setData(wvpResult); resultHolder.invokeResult(requestMessage); }); return result; } @Operation(summary = "停止历史媒体下载", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "stream", description = "流ID", required = true) @GetMapping("/download/stop/{deviceId}/{channelId}/{stream}") public void downloadStop(@PathVariable String deviceId, @PathVariable String channelId, @PathVariable String stream) { if (log.isDebugEnabled()) { log.debug(String.format("设备历史媒体下载停止 API调用,deviceId/channelId:%s_%s", deviceId, channelId)); } if (deviceId == null || channelId == null) { throw new ControllerException(ErrorCode.ERROR400); } Device device = deviceService.getDeviceByDeviceId(deviceId); if (device == null) { throw new ControllerException(ErrorCode.ERROR400.getCode(), "设备:" + deviceId + " 未找到"); } DeviceChannel deviceChannel = channelService.getOneForSource(deviceId, channelId); if (deviceChannel == null) { throw new ControllerException(ErrorCode.ERROR400.getCode(), "通道:" + channelId + " 未找到"); } playService.stop(InviteSessionType.DOWNLOAD, device, deviceChannel, stream); } @Operation(summary = "获取历史媒体下载进度", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "stream", description = "流ID", required = true) @GetMapping("/download/progress/{deviceId}/{channelId}/{stream}") public StreamContent getProgress(@PathVariable String deviceId, @PathVariable String channelId, @PathVariable String stream) { Device device = deviceService.getDeviceByDeviceId(deviceId); if (device == null) { log.warn("[获取历史媒体下载进度] 未找到设备 deviceId: {},channelId:{}", deviceId, channelId); throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到设备:" + deviceId); } DeviceChannel channel = channelService.getOne(deviceId, channelId); if (channel == null) { log.warn("[获取历史媒体下载进度] 未找到通道 deviceId: {},channelId:{}", deviceId, channelId); throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到通道:" + channelId); } StreamInfo downLoadInfo = playService.getDownLoadInfo(device, channel, stream); if (downLoadInfo == null) { throw new ControllerException(ErrorCode.ERROR404); } return new StreamContent(downLoadInfo); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/controller/GroupController.java ================================================ package com.genersoft.iot.vmp.gb28181.controller; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.gb28181.bean.Group; import com.genersoft.iot.vmp.gb28181.bean.GroupTree; import com.genersoft.iot.vmp.gb28181.service.IGroupService; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.github.pagehelper.PageInfo; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.web.bind.annotation.*; import java.util.List; @Slf4j @Tag(name = "分组管理") @RestController @RequestMapping("/api/group") public class GroupController { @Autowired private IGroupService groupService; @Operation(summary = "添加分组") @Parameter(name = "group", description = "group", required = true) @ResponseBody @PostMapping("/add") public void add(@RequestBody Group group){ groupService.add(group); } @Operation(summary = "查询分组节点") @Parameter(name = "query", description = "要搜索的内容", required = true) @Parameter(name = "parent", description = "所属分组编号", required = true) @ResponseBody @GetMapping("/tree/list") public List queryForTree( @RequestParam(required = false) String query, @RequestParam(required = false) Integer parent, @RequestParam(required = false) Boolean hasChannel ){ if (ObjectUtils.isEmpty(query)) { query = null; } return groupService.queryForTree(query, parent, hasChannel); } @Operation(summary = "查询分组") @Parameter(name = "query", description = "要搜索的内容", required = true) @Parameter(name = "channel", description = "true为查询通道,false为查询节点", required = true) @ResponseBody @GetMapping("/tree/query") public PageInfo queryTree(Integer page, Integer count, @RequestParam(required = true) String query ){ return groupService.queryList(page, count, query); } @Operation(summary = "更新分组") @Parameter(name = "group", description = "Group", required = true) @ResponseBody @PostMapping("/update") public void update(@RequestBody Group group){ groupService.update(group); } @Operation(summary = "删除分组") @Parameter(name = "id", description = "分组id", required = true) @ResponseBody @DeleteMapping("/delete") public void delete(Integer id){ Assert.notNull(id, "分组id(deviceId)不需要存在"); boolean result = groupService.delete(id); if (!result) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "移除失败"); } } @Operation(summary = "获取所属的行政区划下的行政区划") @Parameter(name = "deviceId", description = "当前的行政区划", required = false) @ResponseBody @GetMapping("/path") public List getPath(String deviceId, String businessGroup){ return groupService.getPath(deviceId, businessGroup); } // @Operation(summary = "根据分组Id查询分组") // @Parameter(name = "groupDeviceId", description = "分组节点编号", required = true) // @ResponseBody // @GetMapping("/one") // public Group queryGroupByDeviceId( // @RequestParam(required = true) String deviceId // ){ // Assert.hasLength(deviceId, ""); // return groupService.queryGroupByDeviceId(deviceId); // } // @Operation(summary = "从通道中同步分组") // @ResponseBody // @GetMapping("/sync") // public void sync(){ // groupService.syncFromChannel(); // } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/controller/MediaController.java ================================================ package com.genersoft.iot.vmp.gb28181.controller; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.conf.security.JwtUtils; import com.genersoft.iot.vmp.conf.security.SecurityUtils; import com.genersoft.iot.vmp.conf.security.dto.LoginUser; import com.genersoft.iot.vmp.media.service.IMediaServerService; import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.service.bean.InviteErrorCode; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.streamProxy.service.IStreamProxyService; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.StreamContent; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.ObjectUtils; import org.springframework.web.bind.annotation.*; import org.springframework.web.context.request.async.DeferredResult; import java.net.MalformedURLException; import java.net.URL; @Tag(name = "媒体流相关") @RestController @Slf4j @RequestMapping(value = "/api/media") public class MediaController { @Autowired private IRedisCatchStorage redisCatchStorage; @Autowired private IStreamProxyService streamProxyService; @Autowired private IMediaServerService mediaServerService; /** * 根据应用名和流id获取播放地址 * @param app 应用名 * @param stream 流id * @return */ @Operation(summary = "根据应用名和流id获取播放地址", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "app", description = "应用名", required = true) @Parameter(name = "stream", description = "流id", required = true) @Parameter(name = "mediaServerId", description = "媒体服务器id") @Parameter(name = "callId", description = "推流时携带的自定义鉴权ID") @Parameter(name = "useSourceIpAsStreamIp", description = "是否使用请求IP作为返回的地址IP") @GetMapping(value = "/stream_info_by_app_and_stream") @ResponseBody public DeferredResult> getStreamInfoByAppAndStream(HttpServletRequest request, @RequestParam String app, @RequestParam String stream, @RequestParam(required = false) String mediaServerId, @RequestParam(required = false) String callId, @RequestParam(required = false) Boolean useSourceIpAsStreamIp){ DeferredResult> result = new DeferredResult<>(); boolean authority = false; if (callId != null) { // 权限校验 StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(app, stream); if (streamAuthorityInfo != null && streamAuthorityInfo.getCallId() != null && streamAuthorityInfo.getCallId().equals(callId)) { authority = true; }else { throw new ControllerException(ErrorCode.ERROR400.getCode(), "获取播放地址鉴权失败"); } }else { // 是否登陆用户, 登陆用户返回完整信息 LoginUser userInfo = SecurityUtils.getUserInfo(); if (userInfo!= null) { authority = true; } } StreamInfo streamInfo; if (useSourceIpAsStreamIp != null && useSourceIpAsStreamIp) { String host = request.getHeader("Host"); String localAddr = host.split(":")[0]; log.info("使用{}作为返回流的ip", localAddr); streamInfo = mediaServerService.getStreamInfoByAppAndStreamWithCheck(app, stream, mediaServerId, localAddr, authority); }else { streamInfo = mediaServerService.getStreamInfoByAppAndStreamWithCheck(app, stream, mediaServerId, authority); } if (streamInfo != null){ WVPResult wvpResult = WVPResult.success(); wvpResult.setData(new StreamContent(streamInfo)); result.setResult(wvpResult); }else { ErrorCallback callback = (code, msg, streamInfoStoStart) -> { if (code == InviteErrorCode.SUCCESS.getCode()) { WVPResult wvpResult = WVPResult.success(); if (useSourceIpAsStreamIp != null && useSourceIpAsStreamIp) { String host; try { URL url=new URL(request.getRequestURL().toString()); host=url.getHost(); } catch (MalformedURLException e) { host=request.getLocalAddr(); } streamInfoStoStart.changeStreamIp(host); } if (!ObjectUtils.isEmpty(streamInfoStoStart.getMediaServer().getTranscodeSuffix()) && !"null".equalsIgnoreCase(streamInfoStoStart.getMediaServer().getTranscodeSuffix())) { streamInfoStoStart.setStream(streamInfoStoStart.getStream() + "_" + streamInfoStoStart.getMediaServer().getTranscodeSuffix()); } wvpResult.setData(new StreamContent(streamInfoStoStart)); result.setResult(wvpResult); }else { result.setResult(WVPResult.fail(code, msg)); } }; //获取流失败,重启拉流后重试一次 streamProxyService.startByAppAndStream(app, stream, callback); } return result; } /** * 获取推流播放地址 * @param app 应用名 * @param stream 流id * @return */ @GetMapping(value = "/getPlayUrl") @ResponseBody @Operation(summary = "获取推流播放地址", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "app", description = "应用名", required = true) @Parameter(name = "stream", description = "流id", required = true) @Parameter(name = "mediaServerId", description = "媒体服务器id") public StreamContent getPlayUrl(@RequestParam String app, @RequestParam String stream, @RequestParam(required = false) String mediaServerId){ boolean authority = false; // 是否登陆用户, 登陆用户返回完整信息 LoginUser userInfo = SecurityUtils.getUserInfo(); if (userInfo!= null) { authority = true; } StreamInfo streamInfo = mediaServerService.getStreamInfoByAppAndStreamWithCheck(app, stream, mediaServerId, authority); if (streamInfo == null){ throw new ControllerException(ErrorCode.ERROR100.getCode(), "获取播放地址失败"); } return new StreamContent(streamInfo); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/controller/MobilePositionController.java ================================================ package com.genersoft.iot.vmp.gb28181.controller; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.conf.security.JwtUtils; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.MobilePosition; import com.genersoft.iot.vmp.gb28181.service.IDeviceService; 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.cmd.ISIPCommander; import com.genersoft.iot.vmp.service.IMobilePositionService; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.github.pagehelper.util.StringUtil; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import org.springframework.web.context.request.async.DeferredResult; import javax.sip.InvalidArgumentException; import javax.sip.SipException; import java.text.ParseException; import java.util.List; import java.util.UUID; /** * 位置信息管理 */ @Tag(name = "位置信息管理") @Slf4j @RestController @RequestMapping("/api/position") public class MobilePositionController { @Autowired private IMobilePositionService mobilePositionService; @Autowired private ISIPCommander cmder; @Autowired private DeferredResultHolder resultHolder; @Autowired private IDeviceService deviceService; /** * 查询历史轨迹 * @param deviceId 设备ID * @param start 开始时间 * @param end 结束时间 * @return */ @Operation(summary = "查询历史轨迹", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "channelId", description = "通道国标编号") @Parameter(name = "start", description = "开始时间") @Parameter(name = "end", description = "结束时间") @GetMapping("/history/{deviceId}") public List positions(@PathVariable String deviceId, @RequestParam(required = false) String channelId, @RequestParam(required = false) String start, @RequestParam(required = false) String end) { if (StringUtil.isEmpty(start)) { start = null; } if (StringUtil.isEmpty(end)) { end = null; } return mobilePositionService.queryMobilePositions(deviceId, channelId, start, end); } /** * 查询设备最新位置 * @param deviceId 设备ID * @return */ @Operation(summary = "查询设备最新位置", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @GetMapping("/latest/{deviceId}") public MobilePosition latestPosition(@PathVariable String deviceId) { return mobilePositionService.queryLatestPosition(deviceId); } /** * 获取移动位置信息 * @param deviceId 设备ID * @return */ @Operation(summary = "获取移动位置信息", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @GetMapping("/realtime/{deviceId}") public DeferredResult realTimePosition(@PathVariable String deviceId) { Device device = deviceService.getDeviceByDeviceId(deviceId); String uuid = UUID.randomUUID().toString(); String key = DeferredResultHolder.CALLBACK_CMD_MOBILE_POSITION + deviceId; try { cmder.mobilePositionQuery(device, event -> { RequestMessage msg = new RequestMessage(); msg.setId(uuid); msg.setKey(key); msg.setData(String.format("获取移动位置信息失败,错误码: %s, %s", event.statusCode, event.msg)); resultHolder.invokeResult(msg); }); } catch (InvalidArgumentException | SipException | ParseException e) { log.error("[命令发送失败] 获取移动位置信息: {}", e.getMessage()); throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage()); } DeferredResult result = new DeferredResult(5*1000L); result.onTimeout(()->{ log.warn(String.format("获取移动位置信息超时")); // 释放rtpserver RequestMessage msg = new RequestMessage(); msg.setId(uuid); msg.setKey(key); msg.setData("Timeout"); resultHolder.invokeResult(msg); }); resultHolder.put(key, uuid, result); return result; } /** * 订阅位置信息 * @param deviceId 设备ID * @param expires 订阅超时时间 * @param interval 上报时间间隔 * @return true = 命令发送成功 */ @Operation(summary = "订阅位置信息", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "expires", description = "订阅超时时间", required = true) @Parameter(name = "interval", description = "上报时间间隔", required = true) @GetMapping("/subscribe/{deviceId}") public void positionSubscribe(@PathVariable String deviceId, @RequestParam String expires, @RequestParam String interval) { if (StringUtil.isEmpty(interval)) { interval = "5"; } Device device = deviceService.getDeviceByDeviceId(deviceId); device.setSubscribeCycleForMobilePosition(Integer.parseInt(expires)); device.setMobilePositionSubmissionInterval(Integer.parseInt(interval)); deviceService.updateCustomDevice(device); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/controller/PlatformController.java ================================================ package com.genersoft.iot.vmp.gb28181.controller; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.conf.SipConfig; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.conf.security.JwtUtils; import com.genersoft.iot.vmp.gb28181.bean.Platform; import com.genersoft.iot.vmp.gb28181.bean.PlatformChannel; import com.genersoft.iot.vmp.gb28181.bean.SubscribeHolder; import com.genersoft.iot.vmp.gb28181.controller.bean.UpdateChannelParam; import com.genersoft.iot.vmp.gb28181.service.IPlatformChannelService; import com.genersoft.iot.vmp.gb28181.service.IPlatformService; import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import com.github.pagehelper.PageInfo; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.web.bind.annotation.*; /** * 级联平台管理 */ @Tag(name = "级联平台管理") @Slf4j @RestController @RequestMapping("/api/platform") public class PlatformController { @Autowired private IPlatformChannelService platformChannelService; @Autowired private SubscribeHolder subscribeHolder; @Autowired private SipConfig sipConfig; @Autowired private IPlatformService platformService; @Operation(summary = "获取国标服务的配置", security = @SecurityRequirement(name = JwtUtils.HEADER)) @GetMapping("/server_config") public JSONObject serverConfig() { JSONObject result = new JSONObject(); result.put("deviceIp", sipConfig.getShowIp()); result.put("devicePort", sipConfig.getPort()); result.put("username", sipConfig.getId()); result.put("password", sipConfig.getPassword()); return result; } @Operation(summary = "获取级联服务器信息", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "id", description = "平台国标编号", required = true) @GetMapping("/info/{id}") public Platform getPlatform(@PathVariable String id) { Platform parentPlatform = platformService.queryPlatformByServerGBId(id); if (parentPlatform != null) { return parentPlatform; } else { throw new ControllerException(ErrorCode.ERROR100.getCode(), "未查询到此平台"); } } @GetMapping("/query") @Operation(summary = "分页查询级联平台", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "page", description = "当前页") @Parameter(name = "count", description = "每页查询数量") @Parameter(name = "query", description = "查询内容") public PageInfo platforms(int page, int count, @RequestParam(required = false) String query) { PageInfo parentPlatformPageInfo = platformService.queryPlatformList(page, count, query); if (parentPlatformPageInfo != null && !parentPlatformPageInfo.getList().isEmpty()) { for (Platform platform : parentPlatformPageInfo.getList()) { platform.setMobilePositionSubscribe(subscribeHolder.getMobilePositionSubscribe(platform.getServerGBId()) != null); platform.setCatalogSubscribe(subscribeHolder.getCatalogSubscribe(platform.getServerGBId()) != null); } } return parentPlatformPageInfo; } @Operation(summary = "添加上级平台信息", security = @SecurityRequirement(name = JwtUtils.HEADER)) @PostMapping("/add") @ResponseBody public void add(@RequestBody Platform platform) { Assert.notNull(platform.getName(), "平台名称不可为空"); Assert.notNull(platform.getServerGBId(), "上级平台国标编号不可为空"); Assert.notNull(platform.getServerIp(), "上级平台IP不可为空"); Assert.isTrue(platform.getServerPort() > 0 && platform.getServerPort() < 65535, "上级平台端口异常"); Assert.notNull(platform.getDeviceGBId(), "本平台国标编号不可为空"); if (ObjectUtils.isEmpty(platform.getServerGBDomain())) { platform.setServerGBDomain(platform.getServerGBId().substring(0, 6)); } if (platform.getExpires() <= 0) { platform.setExpires(3600); } if (platform.getKeepTimeout() <= 0) { platform.setKeepTimeout(60); } if (ObjectUtils.isEmpty(platform.getTransport())) { platform.setTransport("UDP"); } if (ObjectUtils.isEmpty(platform.getCharacterSet())) { platform.setCharacterSet("GB2312"); } Platform parentPlatformOld = platformService.queryPlatformByServerGBId(platform.getServerGBId()); if (parentPlatformOld != null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "平台 " + platform.getServerGBId() + " 已存在"); } platform.setCreateTime(DateUtil.getNow()); platform.setUpdateTime(DateUtil.getNow()); boolean updateResult = platformService.add(platform); if (!updateResult) { throw new ControllerException(ErrorCode.ERROR100); } } @Operation(summary = "更新上级平台信息", security = @SecurityRequirement(name = JwtUtils.HEADER)) @PostMapping("/update") @ResponseBody public void updatePlatform(@RequestBody Platform parentPlatform) { if (ObjectUtils.isEmpty(parentPlatform.getName()) || ObjectUtils.isEmpty(parentPlatform.getServerGBId()) || ObjectUtils.isEmpty(parentPlatform.getServerGBDomain()) || ObjectUtils.isEmpty(parentPlatform.getServerIp()) || ObjectUtils.isEmpty(parentPlatform.getServerPort()) || ObjectUtils.isEmpty(parentPlatform.getDeviceGBId()) || ObjectUtils.isEmpty(parentPlatform.getExpires()) || ObjectUtils.isEmpty(parentPlatform.getKeepTimeout()) || ObjectUtils.isEmpty(parentPlatform.getTransport()) || ObjectUtils.isEmpty(parentPlatform.getCharacterSet()) ) { throw new ControllerException(ErrorCode.ERROR400); } platformService.update(parentPlatform); } @Operation(summary = "删除上级平台", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "id", description = "上级平台ID") @DeleteMapping("/delete") @ResponseBody public WVPResult deletePlatform(Integer id) { if (log.isDebugEnabled()) { log.debug("删除上级平台API调用"); } boolean result = platformService.delete(id); if (result) { return WVPResult.success(); }else { return WVPResult.fail(ErrorCode.ERROR100); } } @Operation(summary = "查询上级平台是否存在", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "serverGBId", description = "上级平台的国标编号") @GetMapping("/exit/{serverGBId}") @ResponseBody public Boolean exitPlatform(@PathVariable String serverGBId) { Platform platform = platformService.queryPlatformByServerGBId(serverGBId); return platform != null; } @Operation(summary = "分页查询级联平台的所有所有通道", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "page", description = "当前页", required = true) @Parameter(name = "count", description = "每页条数", required = true) @Parameter(name = "platformId", description = "上级平台的数据ID") @Parameter(name = "channelType", description = "通道类型, 0:国标设备,1:推流设备,2:拉流代理") @Parameter(name = "query", description = "查询内容") @Parameter(name = "online", description = "是否在线") @Parameter(name = "hasShare", description = "是否已经共享") @GetMapping("/channel/list") @ResponseBody public PageInfo queryChannelList(int page, int count, @RequestParam(required = false) Integer platformId, @RequestParam(required = false) String query, @RequestParam(required = false) Integer channelType, @RequestParam(required = false) Boolean online, @RequestParam(required = false) Boolean hasShare) { Assert.notNull(platformId, "上级平台的数据ID不可为NULL"); if (ObjectUtils.isEmpty(query)) { query = null; } return platformChannelService.queryChannelList(page, count, query, channelType, online, platformId, hasShare); } @Operation(summary = "向上级平台添加国标通道", security = @SecurityRequirement(name = JwtUtils.HEADER)) @PostMapping("/channel/add") @ResponseBody public void addChannel(@RequestBody UpdateChannelParam param) { if (log.isDebugEnabled()) { log.debug("给上级平台添加国标通道API调用"); } int result = 0; if (param.getChannelIds() == null || param.getChannelIds().isEmpty()) { if (param.isAll()) { log.info("[国标级联]添加所有通道到上级平台, {}", param.getPlatformId()); result = platformChannelService.addAllChannel(param.getPlatformId()); } }else { result = platformChannelService.addChannels(param.getPlatformId(), param.getChannelIds()); } if (result <= 0) { throw new ControllerException(ErrorCode.ERROR100); } } @Operation(summary = "从上级平台移除国标通道", security = @SecurityRequirement(name = JwtUtils.HEADER)) @DeleteMapping("/channel/remove") @ResponseBody public void delChannelForGB(@RequestBody UpdateChannelParam param) { if (log.isDebugEnabled()) { log.debug("给上级平台删除国标通道API调用"); } int result = 0; if (param.getChannelIds() == null || param.getChannelIds().isEmpty()) { if (param.isAll()) { log.info("[国标级联]移除所有通道,上级平台, {}", param.getPlatformId()); result = platformChannelService.removeAllChannel(param.getPlatformId()); } }else { result = platformChannelService.removeChannels(param.getPlatformId(), param.getChannelIds()); } if (result <= 0) { throw new ControllerException(ErrorCode.ERROR100); } } @Operation(summary = "推送通道", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "id", description = "平台ID", required = true) @GetMapping("/channel/push") @ResponseBody public void pushChannel(Integer id) { Assert.notNull(id, "平台ID不可为空"); platformChannelService.pushChannel(id); } @Operation(summary = "添加通道-通过设备", security = @SecurityRequirement(name = JwtUtils.HEADER)) @PostMapping("/channel/device/add") @ResponseBody public void addChannelByDevice(@RequestBody UpdateChannelParam param) { Assert.notNull(param.getPlatformId(), "平台ID不可为空"); Assert.notNull(param.getDeviceIds(), "设备ID不可为空"); Assert.notEmpty(param.getDeviceIds(), "设备ID不可为空"); platformChannelService.addChannelByDevice(param.getPlatformId(), param.getDeviceIds()); } @Operation(summary = "移除通道-通过设备", security = @SecurityRequirement(name = JwtUtils.HEADER)) @PostMapping("/channel/device/remove") @ResponseBody public void removeChannelByDevice(@RequestBody UpdateChannelParam param) { Assert.notNull(param.getPlatformId(), "平台ID不可为空"); Assert.notNull(param.getDeviceIds(), "设备ID不可为空"); Assert.notEmpty(param.getDeviceIds(), "设备ID不可为空"); platformChannelService.removeChannelByDevice(param.getPlatformId(), param.getDeviceIds()); } @Operation(summary = "自定义共享通道信息", security = @SecurityRequirement(name = JwtUtils.HEADER)) @PostMapping("/channel/custom/update") @ResponseBody public void updateCustomChannel(@RequestBody PlatformChannel channel) { Assert.isTrue(channel.getId() > 0, "共享通道ID必须存在"); platformChannelService.updateCustomChannel(channel); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/controller/PlayController.java ================================================ package com.genersoft.iot.vmp.gb28181.controller; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.common.InviteSessionType; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.conf.security.JwtUtils; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction; import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService; import com.genersoft.iot.vmp.gb28181.service.IDeviceService; import com.genersoft.iot.vmp.gb28181.service.IInviteStreamService; import com.genersoft.iot.vmp.gb28181.service.IPlayService; import com.genersoft.iot.vmp.gb28181.session.SipInviteSessionManager; import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.service.IMediaServerService; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.service.bean.InviteErrorCode; import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.vmanager.bean.AudioBroadcastResult; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.StreamContent; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.web.bind.annotation.*; import org.springframework.web.context.request.async.DeferredResult; import jakarta.servlet.http.HttpServletRequest; import java.net.MalformedURLException; import java.net.URL; import java.util.List; import java.util.UUID; /** * @author lin */ @Tag(name = "国标设备点播") @Slf4j @RestController @RequestMapping("/api/play") public class PlayController { @Autowired private SipInviteSessionManager sessionManager; @Autowired private IInviteStreamService inviteStreamService; @Autowired private DeferredResultHolder resultHolder; @Autowired private IPlayService playService; @Autowired private IMediaServerService mediaServerService; @Autowired private UserSetting userSetting; @Autowired private IDeviceService deviceService; @Autowired private IDeviceChannelService deviceChannelService; @Operation(summary = "开始点播", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "channelId", description = "通道国标编号", required = true) @GetMapping("/start/{deviceId}/{channelId}") public DeferredResult> play(HttpServletRequest request, @PathVariable String deviceId, @PathVariable String channelId) { log.info("[开始点播] deviceId:{}, channelId:{}, ", deviceId, channelId); Assert.notNull(deviceId, "设备国标编号不可为NULL"); Assert.notNull(channelId, "通道国标编号不可为NULL"); // 获取可用的zlm Device device = deviceService.getDeviceByDeviceId(deviceId); Assert.notNull(device, "设备不存在"); DeviceChannel channel = deviceChannelService.getOne(deviceId, channelId); Assert.notNull(channel, "通道不存在"); DeferredResult> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue()); result.onTimeout(()->{ log.info("[点播等待超时] deviceId:{}, channelId:{}, ", deviceId, channelId); // 释放rtpserver WVPResult wvpResult = new WVPResult<>(); wvpResult.setCode(ErrorCode.ERROR100.getCode()); wvpResult.setMsg("点播超时"); result.setResult(wvpResult); inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, channel.getId()); deviceChannelService.stopPlay(channel.getId()); }); ErrorCallback callback = (code, msg, streamInfo) -> { WVPResult wvpResult = new WVPResult<>(); if (code == InviteErrorCode.SUCCESS.getCode()) { wvpResult.setCode(ErrorCode.SUCCESS.getCode()); wvpResult.setMsg(ErrorCode.SUCCESS.getMsg()); if (streamInfo != null) { if (userSetting.getUseSourceIpAsStreamIp()) { streamInfo=streamInfo.clone();//深拷贝 String host; try { URL url=new URL(request.getRequestURL().toString()); host=url.getHost(); } catch (MalformedURLException e) { host=request.getLocalAddr(); } streamInfo.changeStreamIp(host); } if (!ObjectUtils.isEmpty(streamInfo.getMediaServer().getTranscodeSuffix()) && !"null".equalsIgnoreCase(streamInfo.getMediaServer().getTranscodeSuffix())) { streamInfo.setStream(streamInfo.getStream() + "_" + streamInfo.getMediaServer().getTranscodeSuffix()); } wvpResult.setData(new StreamContent(streamInfo)); }else { wvpResult.setCode(code); wvpResult.setMsg(msg); } }else { wvpResult.setCode(code); wvpResult.setMsg(msg); } result.setResult(wvpResult); }; playService.play(device, channel, callback); return result; } @Operation(summary = "停止点播", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "channelId", description = "通道国标编号", required = true) @GetMapping("/stop/{deviceId}/{channelId}") public JSONObject playStop(@PathVariable String deviceId, @PathVariable String channelId) { log.debug(String.format("设备预览/回放停止API调用,streamId:%s_%s", deviceId, channelId )); if (deviceId == null || channelId == null) { throw new ControllerException(ErrorCode.ERROR400); } Device device = deviceService.getDeviceByDeviceId(deviceId); DeviceChannel channel = deviceChannelService.getOneForSource(deviceId, channelId); Assert.notNull(device, "设备不存在"); Assert.notNull(channel, "通道不存在"); String streamId = String.format("%s_%s", device.getDeviceId(), channel.getDeviceId()); playService.stop(InviteSessionType.PLAY, device, channel, streamId); JSONObject json = new JSONObject(); json.put("deviceId", deviceId); json.put("channelId", channelId); return json; } /** * 结束转码 */ @Operation(summary = "结束转码", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "key", description = "视频流key", required = true) @Parameter(name = "mediaServerId", description = "流媒体服务ID", required = true) @PostMapping("/convertStop/{key}") public void playConvertStop(@PathVariable String key, String mediaServerId) { if (mediaServerId == null) { throw new ControllerException(ErrorCode.ERROR400.getCode(), "流媒体:" + mediaServerId + "不存在" ); } MediaServer mediaInfo = mediaServerService.getOne(mediaServerId); if (mediaInfo == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "使用的流媒体已经停止运行" ); }else { Boolean deleted = mediaServerService.delFFmpegSource(mediaInfo, key); if (!deleted) { throw new ControllerException(ErrorCode.ERROR100 ); } } } @Operation(summary = "语音广播命令", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "deviceId", description = "通道国标编号", required = true) @Parameter(name = "timeout", description = "推流超时时间(秒)", required = true) @GetMapping("/broadcast/{deviceId}/{channelId}") @PostMapping("/broadcast/{deviceId}/{channelId}") public AudioBroadcastResult broadcastApi(@PathVariable String deviceId, @PathVariable String channelId, Integer timeout, Boolean broadcastMode) { if (log.isDebugEnabled()) { log.debug("语音广播API调用"); } return playService.audioBroadcast(deviceId, channelId, broadcastMode); } @Operation(summary = "停止语音广播") @Parameter(name = "deviceId", description = "设备Id", required = true) @Parameter(name = "channelId", description = "通道Id", required = true) @GetMapping("/broadcast/stop/{deviceId}/{channelId}") @PostMapping("/broadcast/stop/{deviceId}/{channelId}") public void stopBroadcast(@PathVariable String deviceId, @PathVariable String channelId) { if (log.isDebugEnabled()) { log.debug("停止语音广播API调用"); } Device device = deviceService.getDeviceByDeviceId(deviceId); Assert.notNull(device, "设备不存在"); DeviceChannel channel = deviceChannelService.getOne(deviceId, channelId); Assert.notNull(channel, "通道不存在"); playService.stopAudioBroadcast(device, channel); } @Operation(summary = "获取所有的ssrc", security = @SecurityRequirement(name = JwtUtils.HEADER)) @GetMapping("/ssrc") public JSONObject getSSRC() { if (log.isDebugEnabled()) { log.debug("获取所有的ssrc"); } JSONArray objects = new JSONArray(); List allSsrc = sessionManager.getAll(); for (SsrcTransaction transaction : allSsrc) { JSONObject jsonObject = new JSONObject(); jsonObject.put("deviceId", transaction.getDeviceId()); jsonObject.put("channelId", transaction.getChannelId()); jsonObject.put("ssrc", transaction.getSsrc()); jsonObject.put("streamId", transaction.getStream()); objects.add(jsonObject); } JSONObject jsonObject = new JSONObject(); jsonObject.put("data", objects); jsonObject.put("count", objects.size()); return jsonObject; } @Operation(summary = "获取截图", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "channelId", description = "通道国标编号", required = true) @GetMapping("/snap") public DeferredResult getSnap(String deviceId, String channelId) { if (log.isDebugEnabled()) { log.debug("获取截图: {}/{}", deviceId, channelId); } DeferredResult result = new DeferredResult<>(3 * 1000L); String key = DeferredResultHolder.CALLBACK_CMD_SNAP + deviceId; String uuid = UUID.randomUUID().toString(); resultHolder.put(key, uuid, result); RequestMessage message = new RequestMessage(); message.setKey(key); message.setId(uuid); String fileName = deviceId + "_" + channelId + "_" + DateUtil.getNowForUrl() + ".jpg"; playService.getSnap(deviceId, channelId, fileName, (code, msg, data) -> { if (code == InviteErrorCode.SUCCESS.getCode()) { message.setData(data); }else { message.setData(WVPResult.fail(code, msg)); } resultHolder.invokeResult(message); }); return result; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/controller/PlaybackController.java ================================================ package com.genersoft.iot.vmp.gb28181.controller; import com.genersoft.iot.vmp.common.InviteSessionType; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.conf.exception.ServiceException; import com.genersoft.iot.vmp.conf.security.JwtUtils; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService; import com.genersoft.iot.vmp.gb28181.service.IDeviceService; import com.genersoft.iot.vmp.gb28181.service.IInviteStreamService; import com.genersoft.iot.vmp.gb28181.service.IPlayService; 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.cmd.impl.SIPCommander; import com.genersoft.iot.vmp.service.bean.InviteErrorCode; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.StreamContent; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.async.DeferredResult; import jakarta.servlet.http.HttpServletRequest; import javax.sip.InvalidArgumentException; import javax.sip.SipException; import java.net.MalformedURLException; import java.net.URL; import java.text.ParseException; import java.util.UUID; /** * @author lin */ @Tag(name = "视频回放") @Slf4j @RestController @RequestMapping("/api/playback") public class PlaybackController { @Autowired private SIPCommander cmder; @Autowired private IInviteStreamService inviteStreamService; @Autowired private IPlayService playService; @Autowired private DeferredResultHolder resultHolder; @Autowired private UserSetting userSetting; @Autowired private IDeviceService deviceService; @Autowired private IDeviceChannelService channelService; @Operation(summary = "开始视频回放", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "startTime", description = "开始时间", required = true) @Parameter(name = "endTime", description = "结束时间", required = true) @GetMapping("/start/{deviceId}/{channelId}") public DeferredResult> start(HttpServletRequest request, @PathVariable String deviceId, @PathVariable String channelId, String startTime, String endTime) { if (log.isDebugEnabled()) { log.debug(String.format("设备回放 API调用,deviceId:%s ,channelId:%s", deviceId, channelId)); } String uuid = UUID.randomUUID().toString(); String key = DeferredResultHolder.CALLBACK_CMD_PLAYBACK + deviceId + channelId; DeferredResult> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue()); resultHolder.put(key, uuid, result); RequestMessage requestMessage = new RequestMessage(); requestMessage.setKey(key); requestMessage.setId(uuid); Device device = deviceService.getDeviceByDeviceId(deviceId); if (device == null) { log.warn("[录像回放] 未找到设备 deviceId: {},channelId:{}", deviceId, channelId); throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到设备:" + deviceId); } DeviceChannel channel = channelService.getOne(deviceId, channelId); if (channel == null) { log.warn("[录像回放] 未找到通道 deviceId: {},channelId:{}", deviceId, channelId); throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到通道:" + channelId); } playService.playBack(device, channel, startTime, endTime, (code, msg, data)->{ WVPResult wvpResult = new WVPResult<>(); if (code == InviteErrorCode.SUCCESS.getCode()) { wvpResult.setCode(ErrorCode.SUCCESS.getCode()); wvpResult.setMsg(ErrorCode.SUCCESS.getMsg()); if (data != null) { StreamInfo streamInfo = (StreamInfo)data; if (userSetting.getUseSourceIpAsStreamIp()) { streamInfo=streamInfo.clone();//深拷贝 String host; try { URL url=new URL(request.getRequestURL().toString()); host=url.getHost(); } catch (MalformedURLException e) { host=request.getLocalAddr(); } streamInfo.changeStreamIp(host); } wvpResult.setData(new StreamContent(streamInfo)); } }else { wvpResult.setCode(code); wvpResult.setMsg(msg); } requestMessage.setData(wvpResult); resultHolder.invokeResult(requestMessage); }); return result; } @Operation(summary = "停止视频回放", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "stream", description = "流ID", required = true) @GetMapping("/stop/{deviceId}/{channelId}/{stream}") public void playStop( @PathVariable String deviceId, @PathVariable String channelId, @PathVariable String stream) { if (ObjectUtils.isEmpty(deviceId) || ObjectUtils.isEmpty(channelId) || ObjectUtils.isEmpty(stream)) { throw new ControllerException(ErrorCode.ERROR400); } Device device = deviceService.getDeviceByDeviceId(deviceId); if (device == null) { throw new ControllerException(ErrorCode.ERROR400.getCode(), "设备:" + deviceId + " 未找到"); } DeviceChannel deviceChannel = channelService.getOneForSource(deviceId, channelId); if (deviceChannel == null) { throw new ControllerException(ErrorCode.ERROR400.getCode(), "通道:" + channelId + " 未找到"); } playService.stop(InviteSessionType.PLAYBACK, device, deviceChannel, stream); } @Operation(summary = "回放暂停", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "streamId", description = "回放流ID", required = true) @GetMapping("/pause/{streamId}") public void playbackPause(@PathVariable String streamId) { log.info("[回放暂停] streamId: {}", streamId); try { playService.playbackPause(streamId); } catch (ServiceException e) { throw new ControllerException(ErrorCode.ERROR400.getCode(), e.getMessage()); } catch (InvalidArgumentException | ParseException | SipException e) { throw new ControllerException(ErrorCode.ERROR100.getCode(), e.getMessage()); } } @Operation(summary = "回放恢复", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "streamId", description = "回放流ID", required = true) @GetMapping("/resume/{streamId}") public void playResume(@PathVariable String streamId) { log.info("playResume: "+streamId); try { playService.playbackResume(streamId); } catch (ServiceException e) { throw new ControllerException(ErrorCode.ERROR400.getCode(), e.getMessage()); } catch (InvalidArgumentException | ParseException | SipException e) { throw new ControllerException(ErrorCode.ERROR100.getCode(), e.getMessage()); } } @Operation(summary = "回放拖动播放", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "streamId", description = "回放流ID", required = true) @Parameter(name = "seekTime", description = "拖动偏移量,单位s", required = true) @GetMapping("/seek/{streamId}/{seekTime}") public void playbackSeek(@PathVariable String streamId, @PathVariable long seekTime) { log.info("playSeek: "+streamId+", "+seekTime); try { playService.playbackSeek(streamId, seekTime); } catch (InvalidArgumentException | ParseException | SipException e) { throw new ControllerException(ErrorCode.ERROR100.getCode(), e.getMessage()); } } @Operation(summary = "回放倍速播放", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "streamId", description = "回放流ID", required = true) @Parameter(name = "speed", description = "倍速0.25 0.5 1、2、4、8", required = true) @GetMapping("/speed/{streamId}/{speed}") public void playSpeed(@PathVariable String streamId, @PathVariable Double speed) { Assert.notNull(speed, "倍速不存在"); log.info("playSpeed: "+streamId+", "+speed); try { playService.playbackSpeed(streamId, speed); } catch (InvalidArgumentException | ParseException | SipException e) { throw new ControllerException(ErrorCode.ERROR100.getCode(), e.getMessage()); } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/controller/PtzController.java ================================================ package com.genersoft.iot.vmp.gb28181.controller; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.conf.security.JwtUtils; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.service.IDeviceService; import com.genersoft.iot.vmp.gb28181.service.IPTZService; import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.Assert; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.async.DeferredResult; @Tag(name = "前端设备控制") @Slf4j @RestController @RequestMapping("/api/front-end") public class PtzController { @Autowired private SIPCommander cmder; @Autowired private IDeviceService deviceService; @Autowired private IPTZService ptzService; @Autowired private DeferredResultHolder resultHolder; @Operation(summary = "通用前端控制命令(参考国标文档A.3.1指令格式)", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "cmdCode", description = "指令码(对应国标文档指令格式中的字节4)", required = true) @Parameter(name = "parameter1", description = "数据一(对应国标文档指令格式中的字节5, 范围0-255)", required = true) @Parameter(name = "parameter2", description = "数据二(对应国标文档指令格式中的字节6, 范围0-255)", required = true) @Parameter(name = "combindCode2", description = "组合码二(对应国标文档指令格式中的字节7, 范围0-15)", required = true) @GetMapping("/common/{deviceId}/{channelId}") public void frontEndCommand(@PathVariable String deviceId,@PathVariable String channelId,Integer cmdCode, Integer parameter1, Integer parameter2, Integer combindCode2){ if (log.isDebugEnabled()) { log.debug(String.format("设备云台控制 API调用,deviceId:%s ,channelId:%s ,cmdCode:%d parameter1:%d parameter2:%d",deviceId, channelId, cmdCode, parameter1, parameter2)); } if (parameter1 == null || parameter1 < 0 || parameter1 > 255) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "parameter1 为 0-255的数字"); } if (parameter2 == null || parameter2 < 0 || parameter2 > 255) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "parameter2 为 0-255的数字"); } if (combindCode2 == null || combindCode2 < 0 || combindCode2 > 15) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "combindCode2 为 0-15的数字"); } Device device = deviceService.getDeviceByDeviceId(deviceId); Assert.notNull(device, "设备[" + deviceId + "]不存在"); ptzService.frontEndCommand(device, channelId, cmdCode, parameter1, parameter2, combindCode2); } @Operation(summary = "云台控制", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "command", description = "控制指令,允许值: left, right, up, down, upleft, upright, downleft, downright, zoomin, zoomout, stop", required = true) @Parameter(name = "horizonSpeed", description = "水平速度(0-255)", required = true) @Parameter(name = "verticalSpeed", description = "垂直速度(0-255)", required = true) @Parameter(name = "zoomSpeed", description = "缩放速度(0-15)", required = true) @GetMapping("/ptz/{deviceId}/{channelId}") public void ptz(@PathVariable String deviceId,@PathVariable String channelId, String command, Integer horizonSpeed, Integer verticalSpeed, Integer zoomSpeed){ if (log.isDebugEnabled()) { log.debug(String.format("设备云台控制 API调用,deviceId:%s ,channelId:%s ,command:%s ,horizonSpeed:%d ,verticalSpeed:%d ,zoomSpeed:%d",deviceId, channelId, command, horizonSpeed, verticalSpeed, zoomSpeed)); } if (horizonSpeed == null) { horizonSpeed = 100; }else if (horizonSpeed < 0 || horizonSpeed > 255) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "horizonSpeed 为 0-255的数字"); } if (verticalSpeed == null) { verticalSpeed = 100; }else if (verticalSpeed < 0 || verticalSpeed > 255) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "verticalSpeed 为 0-255的数字"); } if (zoomSpeed == null) { zoomSpeed = 16; }else if (zoomSpeed < 0 || zoomSpeed > 15) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "zoomSpeed 为 0-15的数字"); } int cmdCode = 0; switch (command){ case "left": cmdCode = 2; break; case "right": cmdCode = 1; break; case "up": cmdCode = 8; break; case "down": cmdCode = 4; break; case "upleft": cmdCode = 10; break; case "upright": cmdCode = 9; break; case "downleft": cmdCode = 6; break; case "downright": cmdCode = 5; break; case "zoomin": cmdCode = 16; break; case "zoomout": cmdCode = 32; break; case "stop": horizonSpeed = 0; verticalSpeed = 0; zoomSpeed = 0; break; default: break; } frontEndCommand(deviceId, channelId, cmdCode, horizonSpeed, verticalSpeed, zoomSpeed); } @Operation(summary = "光圈控制", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "command", description = "控制指令,允许值: in, out, stop", required = true) @Parameter(name = "speed", description = "光圈速度(0-255)", required = true) @GetMapping("/fi/iris/{deviceId}/{channelId}") public void iris(@PathVariable String deviceId,@PathVariable String channelId, String command, Integer speed){ if (log.isDebugEnabled()) { log.debug("设备光圈控制 API调用,deviceId:{} ,channelId:{} ,command:{} ,speed:{} ",deviceId, channelId, command, speed); } if (speed == null) { speed = 100; }else if (speed < 0 || speed > 255) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "speed 为 0-255的数字"); } int cmdCode = 0x40; switch (command){ case "in": cmdCode = 0x44; break; case "out": cmdCode = 0x48; break; case "stop": speed = 0; break; default: break; } frontEndCommand(deviceId, channelId, cmdCode, 0, speed, 0); } @Operation(summary = "聚焦控制", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "command", description = "控制指令,允许值: near, far, stop", required = true) @Parameter(name = "speed", description = "聚焦速度(0-255)", required = true) @GetMapping("/fi/focus/{deviceId}/{channelId}") public void focus(@PathVariable String deviceId,@PathVariable String channelId, String command, Integer speed){ if (log.isDebugEnabled()) { log.debug("设备聚焦控制 API调用,deviceId:{} ,channelId:{} ,command:{} ,speed:{} ",deviceId, channelId, command, speed); } if (speed == null) { speed = 100; }else if (speed < 0 || speed > 255) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "speed 为 0-255的数字"); } int cmdCode = 0x40; switch (command){ case "near": cmdCode = 0x42; break; case "far": cmdCode = 0x41; break; case "stop": speed = 0; break; default: break; } frontEndCommand(deviceId, channelId, cmdCode, speed, 0, 0); } @Operation(summary = "查询预置位", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "channelId", description = "通道国标编号", required = true) @GetMapping("/preset/query/{deviceId}/{channelId}") public DeferredResult> queryPreset(@PathVariable String deviceId, @PathVariable String channelId) { if (log.isDebugEnabled()) { log.debug("设备预置位查询API调用"); } Device device = deviceService.getDeviceByDeviceId(deviceId); Assert.notNull(device, "设备不存在"); DeferredResult> deferredResult = new DeferredResult<> (3 * 1000L); deviceService.queryPreset(device, channelId, (code, msg, data) -> { deferredResult.setResult(new WVPResult<>(code, msg, data)); }); deferredResult.onTimeout(()->{ log.warn("[获取设备预置位] 超时, {}", device.getDeviceId()); deferredResult.setResult(WVPResult.fail(ErrorCode.ERROR100.getCode(), "超时")); }); return deferredResult; } @Operation(summary = "预置位指令-设置预置位", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "presetId", description = "预置位编号(1-255)", required = true) @GetMapping("/preset/add/{deviceId}/{channelId}") public void addPreset(@PathVariable String deviceId, @PathVariable String channelId, Integer presetId) { if (presetId == null || presetId < 1 || presetId > 255) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "预置位编号必须为1-255之间的数字"); } frontEndCommand(deviceId, channelId, 0x81, 1, presetId, 0); } @Operation(summary = "预置位指令-调用预置位", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "presetId", description = "预置位编号(1-255)", required = true) @GetMapping("/preset/call/{deviceId}/{channelId}") public void callPreset(@PathVariable String deviceId, @PathVariable String channelId, Integer presetId) { if (presetId == null || presetId < 1 || presetId > 255) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "预置位编号必须为1-255之间的数字"); } frontEndCommand(deviceId, channelId, 0x82, 1, presetId, 0); } @Operation(summary = "预置位指令-删除预置位", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "presetId", description = "预置位编号(1-255)", required = true) @GetMapping("/preset/delete/{deviceId}/{channelId}") public void deletePreset(@PathVariable String deviceId, @PathVariable String channelId, Integer presetId) { if (presetId == null || presetId < 1 || presetId > 255) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "预置位编号必须为1-255之间的数字"); } frontEndCommand(deviceId, channelId, 0x83, 1, presetId, 0); } @Operation(summary = "巡航指令-加入巡航点", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "cruiseId", description = "巡航组号(0-255)", required = true) @Parameter(name = "presetId", description = "预置位编号(1-255)", required = true) @GetMapping("/cruise/point/add/{deviceId}/{channelId}") public void addCruisePoint(@PathVariable String deviceId, @PathVariable String channelId, Integer cruiseId, Integer presetId) { if (presetId == null || cruiseId == null || presetId < 1 || presetId > 255 || cruiseId < 0 || cruiseId > 255) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "编号必须为1-255之间的数字"); } frontEndCommand(deviceId, channelId, 0x84, cruiseId, presetId, 0); } @Operation(summary = "巡航指令-删除一个巡航点", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "cruiseId", description = "巡航组号(1-255)", required = true) @Parameter(name = "presetId", description = "预置位编号(0-255, 为0时删除整个巡航)", required = true) @GetMapping("/cruise/point/delete/{deviceId}/{channelId}") public void deleteCruisePoint(@PathVariable String deviceId, @PathVariable String channelId, Integer cruiseId, Integer presetId) { if (presetId == null || presetId < 0 || presetId > 255) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "预置位编号必须为0-255之间的数字, 为0时删除整个巡航"); } if (cruiseId == null || cruiseId < 0 || cruiseId > 255) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "巡航组号必须为0-255之间的数字"); } frontEndCommand(deviceId, channelId, 0x85, cruiseId, presetId, 0); } @Operation(summary = "巡航指令-设置巡航速度", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "cruiseId", description = "巡航组号(0-255)", required = true) @Parameter(name = "speed", description = "巡航速度(1-4095)", required = true) @GetMapping("/cruise/speed/{deviceId}/{channelId}") public void setCruiseSpeed(@PathVariable String deviceId, @PathVariable String channelId, Integer cruiseId, Integer speed) { if (cruiseId == null || cruiseId < 0 || cruiseId > 255) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "巡航组号必须为0-255之间的数字"); } if (speed == null || speed < 1 || speed > 4095) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "巡航速度必须为1-4095之间的数字"); } int parameter2 = speed & 0xFF; int combindCode2 = speed >> 8; frontEndCommand(deviceId, channelId, 0x86, cruiseId, parameter2, combindCode2); } @Operation(summary = "巡航指令-设置巡航停留时间", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "cruiseId", description = "巡航组号", required = true) @Parameter(name = "time", description = "巡航停留时间(1-4095)", required = true) @GetMapping("/cruise/time/{deviceId}/{channelId}") public void setCruiseTime(@PathVariable String deviceId, @PathVariable String channelId, Integer cruiseId, Integer time) { if (cruiseId == null || cruiseId < 0 || cruiseId > 255) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "巡航组号必须为0-255之间的数字"); } if (time == null || time < 1 || time > 4095) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "巡航停留时间必须为1-4095之间的数字"); } int parameter2 = time & 0xFF; int combindCode2 = time >> 8; frontEndCommand(deviceId, channelId, 0x87, cruiseId, parameter2, combindCode2); } @Operation(summary = "巡航指令-开始巡航", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "cruiseId", description = "巡航组号)", required = true) @GetMapping("/cruise/start/{deviceId}/{channelId}") public void startCruise(@PathVariable String deviceId, @PathVariable String channelId, Integer cruiseId) { if (cruiseId == null || cruiseId < 0 || cruiseId > 255) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "巡航组号必须为0-255之间的数字"); } frontEndCommand(deviceId, channelId, 0x88, cruiseId, 0, 0); } @Operation(summary = "巡航指令-停止巡航", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "cruiseId", description = "巡航组号", required = true) @GetMapping("/cruise/stop/{deviceId}/{channelId}") public void stopCruise(@PathVariable String deviceId, @PathVariable String channelId, Integer cruiseId) { if (cruiseId == null || cruiseId < 0 || cruiseId > 255) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "巡航组号必须为0-255之间的数字"); } frontEndCommand(deviceId, channelId, 0, 0, 0, 0); } @Operation(summary = "扫描指令-开始自动扫描", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "scanId", description = "扫描组号(0-255)", required = true) @GetMapping("/scan/start/{deviceId}/{channelId}") public void startScan(@PathVariable String deviceId, @PathVariable String channelId, Integer scanId) { if (scanId == null || scanId < 0 || scanId > 255 ) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "扫描组号必须为0-255之间的数字"); } frontEndCommand(deviceId, channelId, 0x89, scanId, 0, 0); } @Operation(summary = "扫描指令-停止自动扫描", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "scanId", description = "扫描组号(0-255)", required = true) @GetMapping("/scan/stop/{deviceId}/{channelId}") public void stopScan(@PathVariable String deviceId, @PathVariable String channelId, Integer scanId) { if (scanId == null || scanId < 0 || scanId > 255 ) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "扫描组号必须为0-255之间的数字"); } frontEndCommand(deviceId, channelId, 0, 0, 0, 0); } @Operation(summary = "扫描指令-设置自动扫描左边界", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "scanId", description = "扫描组号(0-255)", required = true) @GetMapping("/scan/set/left/{deviceId}/{channelId}") public void setScanLeft(@PathVariable String deviceId, @PathVariable String channelId, Integer scanId) { if (scanId == null || scanId < 0 || scanId > 255 ) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "扫描组号必须为0-255之间的数字"); } frontEndCommand(deviceId, channelId, 0x89, scanId, 1, 0); } @Operation(summary = "扫描指令-设置自动扫描右边界", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "scanId", description = "扫描组号(0-255)", required = true) @GetMapping("/scan/set/right/{deviceId}/{channelId}") public void setScanRight(@PathVariable String deviceId, @PathVariable String channelId, Integer scanId) { if (scanId == null || scanId < 0 || scanId > 255 ) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "扫描组号必须为0-255之间的数字"); } frontEndCommand(deviceId, channelId, 0x89, scanId, 2, 0); } @Operation(summary = "扫描指令-设置自动扫描速度", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "scanId", description = "扫描组号(0-255)", required = true) @Parameter(name = "speed", description = "自动扫描速度(1-4095)", required = true) @GetMapping("/scan/set/speed/{deviceId}/{channelId}") public void setScanSpeed(@PathVariable String deviceId, @PathVariable String channelId, Integer scanId, Integer speed) { if (scanId == null || scanId < 0 || scanId > 255 ) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "扫描组号必须为0-255之间的数字"); } if (speed == null || speed < 1 || speed > 4095) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "自动扫描速度必须为1-4095之间的数字"); } int parameter2 = speed & 0xFF; int combindCode2 = speed >> 8; frontEndCommand(deviceId, channelId, 0x8A, scanId, parameter2, combindCode2); } @Operation(summary = "辅助开关控制指令-雨刷控制", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "command", description = "控制指令,允许值: on, off", required = true) @GetMapping("/wiper/{deviceId}/{channelId}") public void wiper(@PathVariable String deviceId,@PathVariable String channelId, String command){ if (log.isDebugEnabled()) { log.debug("辅助开关控制指令-雨刷控制 API调用,deviceId:{} ,channelId:{} ,command:{}",deviceId, channelId, command); } int cmdCode = 0; switch (command){ case "on": cmdCode = 0x8c; break; case "off": cmdCode = 0x8d; break; default: break; } frontEndCommand(deviceId, channelId, cmdCode, 1, 0, 0); } @Operation(summary = "辅助开关控制指令", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "command", description = "控制指令,允许值: on, off", required = true) @Parameter(name = "switchId", description = "开关编号", required = true) @GetMapping("/auxiliary/{deviceId}/{channelId}") public void auxiliarySwitch(@PathVariable String deviceId,@PathVariable String channelId, String command, Integer switchId){ if (log.isDebugEnabled()) { log.debug("辅助开关控制指令-雨刷控制 API调用,deviceId:{} ,channelId:{} ,command:{}, switchId: {}",deviceId, channelId, command, switchId); } int cmdCode = 0; switch (command){ case "on": cmdCode = 0x8c; break; case "off": cmdCode = 0x8d; break; default: break; } frontEndCommand(deviceId, channelId, cmdCode, switchId, 0, 0); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/controller/RegionController.java ================================================ package com.genersoft.iot.vmp.gb28181.controller; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.gb28181.bean.Region; import com.genersoft.iot.vmp.gb28181.bean.RegionTree; import com.genersoft.iot.vmp.gb28181.service.IRegionService; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.github.pagehelper.PageInfo; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.web.bind.annotation.*; import java.util.List; @Tag(name = "区域管理") @RestController @RequestMapping("/api/region") public class RegionController { private final static Logger logger = LoggerFactory.getLogger(RegionController.class); @Autowired private IRegionService regionService; @Operation(summary = "添加区域") @Parameter(name = "region", description = "Region", required = true) @ResponseBody @PostMapping("/add") public void add(@RequestBody Region region){ regionService.add(region); } @Operation(summary = "查询区域") @Parameter(name = "query", description = "要搜索的内容", required = true) @Parameter(name = "page", description = "当前页", required = true) @Parameter(name = "count", description = "每页查询数量", required = true) @ResponseBody @GetMapping("/page/list") public PageInfo query( @RequestParam(required = false) String query, @RequestParam(required = true) int page, @RequestParam(required = true) int count ){ return regionService.query(query, page, count); } @Operation(summary = "查询区域节点") @Parameter(name = "query", description = "要搜索的内容", required = true) @Parameter(name = "parent", description = "所属行政区划编号", required = true) @Parameter(name = "hasChannel", description = "是否查询通道", required = true) @ResponseBody @GetMapping("/tree/list") public List queryForTree( @RequestParam(required = false) Integer parent, @RequestParam(required = false) Boolean hasChannel ){ return regionService.queryForTree(parent, hasChannel); } @Operation(summary = "查询区域") @Parameter(name = "query", description = "要搜索的内容", required = true) @Parameter(name = "channel", description = "true为查询通道,false为查询节点", required = true) @ResponseBody @GetMapping("/tree/query") public PageInfo queryTree(Integer page, Integer count, @RequestParam(required = true) String query ){ return regionService.queryList(page, count, query); } @Operation(summary = "更新区域") @Parameter(name = "region", description = "Region", required = true) @ResponseBody @PostMapping("/update") public void update(@RequestBody Region region){ regionService.update(region); } @Operation(summary = "删除区域") @Parameter(name = "id", description = "区域ID", required = true) @ResponseBody @DeleteMapping("/delete") public void delete(Integer id){ Assert.notNull(id, "区域ID需要存在"); boolean result = regionService.deleteByDeviceId(id); if (!result) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "移除失败"); } } @Operation(summary = "根据区域Id查询区域") @Parameter(name = "regionDeviceId", description = "行政区划节点编号", required = true) @ResponseBody @GetMapping("/one") public Region queryRegionByDeviceId( @RequestParam(required = true) String regionDeviceId ){ if (ObjectUtils.isEmpty(regionDeviceId.trim())) { throw new ControllerException(ErrorCode.ERROR400); } return regionService.queryRegionByDeviceId(regionDeviceId); } @Operation(summary = "获取所属的行政区划下的行政区划") @Parameter(name = "parent", description = "所属的行政区划", required = false) @ResponseBody @GetMapping("/base/child/list") public List getAllChild(@RequestParam(required = false) String parent){ if (ObjectUtils.isEmpty(parent)) { parent = null; } return regionService.getAllChild(parent); } @Operation(summary = "获取所属的行政区划下的行政区划") @Parameter(name = "deviceId", description = "当前的行政区划", required = false) @ResponseBody @GetMapping("/path") public List getPath(String deviceId){ return regionService.getPath(deviceId); } @Operation(summary = "从通道中同步行政区划") @ResponseBody @GetMapping("/sync") public void sync(){ regionService.syncFromChannel(); } @Operation(summary = "根据行政区划编号从文件中查询层级和描述") @ResponseBody @GetMapping("/description") public String getDescription(String civilCode){ return regionService.getDescription(civilCode); } @Operation(summary = "根据行政区划编号从文件中查询层级并添加") @ResponseBody @GetMapping("/addByCivilCode") public void addByCivilCode(String civilCode){ regionService.addByCivilCode(civilCode); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/controller/SseController.java ================================================ package com.genersoft.iot.vmp.gb28181.controller; import com.genersoft.iot.vmp.gb28181.session.SseSessionManager; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import jakarta.annotation.Resource; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; /** * SSE 推送. * * @author lawrencehj * @author xiaoQQya * @since 2021/01/20 */ @Tag(name = "SSE 推送") @RestController @RequestMapping("/api") public class SseController { @Resource private SseSessionManager sseSessionManager; /** * SSE 推送. * * @param browserId 浏览器ID */ @GetMapping("/emit") public SseEmitter emit(HttpServletResponse response, @RequestParam String browserId) throws IOException, InterruptedException { // response.setContentType("text/event-stream"); // response.setCharacterEncoding("utf-8"); return sseSessionManager.conect(browserId); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/controller/bean/AudioBroadcastEvent.java ================================================ package com.genersoft.iot.vmp.gb28181.controller.bean; /** * @author lin */ public interface AudioBroadcastEvent { void call(String msg); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/controller/bean/ChannelForThin.java ================================================ package com.genersoft.iot.vmp.gb28181.controller.bean; import lombok.Data; @Data public class ChannelForThin { private Integer gbId; private Integer mapLevel; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/controller/bean/ChannelListForRpcParam.java ================================================ package com.genersoft.iot.vmp.gb28181.controller.bean; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import java.util.List; @Getter @Setter @NoArgsConstructor @AllArgsConstructor public class ChannelListForRpcParam { private List channelIds; private Integer platformId; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/controller/bean/ChannelReduce.java ================================================ package com.genersoft.iot.vmp.gb28181.controller.bean; import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; import io.swagger.v3.oas.annotations.media.Schema; /** * 精简的channel信息展示,主要是选择通道的时候展示列表使用 */ @Schema(description = "精简的channel信息展示") public class ChannelReduce { /** * deviceChannel的数据库自增ID */ @Schema(description = "deviceChannel的数据库自增ID") private int id; /** * 通道id */ @Schema(description = "通道国标编号") private String channelId; /** * 设备id */ @Schema(description = "设备国标编号") private String deviceId; /** * 通道名 */ @Schema(description = "通道名") private String name; /** * 生产厂商 */ @Schema(description = "生产厂商") private String manufacturer; /** * wan地址 */ @Schema(description = "wan地址") private String hostAddress; /** * 子节点数 */ @Schema(description = "子节点数") private int subCount; /** * 平台Id */ @Schema(description = "平台上级国标编号") private String platformId; /** * 目录Id */ @Schema(description = "目录国标编号") private String catalogId; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getChannelId() { return channelId; } public void setChannelId(String channelId) { this.channelId = channelId; } public String getDeviceId() { return deviceId; } public void setDeviceId(String deviceId) { this.deviceId = deviceId; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getManufacturer() { return manufacturer; } public void setManufacturer(String manufacturer) { this.manufacturer = manufacturer; } public String getHostAddress() { return hostAddress; } public void setHostAddress(String hostAddress) { this.hostAddress = hostAddress; } public int getSubCount() { return subCount; } public void setSubCount(int subCount) { this.subCount = subCount; } public String getPlatformId() { return platformId; } public void setPlatformId(String platformId) { this.platformId = platformId; } public String getCatalogId() { return catalogId; } public void setCatalogId(String catalogId) { this.catalogId = catalogId; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/controller/bean/ChannelToGroupByGbDeviceParam.java ================================================ package com.genersoft.iot.vmp.gb28181.controller.bean; import lombok.Data; import java.util.List; @Data public class ChannelToGroupByGbDeviceParam { private List deviceIds; private String parentId; private String businessGroup; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/controller/bean/ChannelToGroupParam.java ================================================ package com.genersoft.iot.vmp.gb28181.controller.bean; import lombok.Data; import java.util.List; @Data public class ChannelToGroupParam { private String parentId; private String businessGroup; private List channelIds; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/controller/bean/ChannelToRegionByGbDeviceParam.java ================================================ package com.genersoft.iot.vmp.gb28181.controller.bean; import lombok.Data; import java.util.List; @Data public class ChannelToRegionByGbDeviceParam { private List deviceIds; private String civilCode; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/controller/bean/ChannelToRegionParam.java ================================================ package com.genersoft.iot.vmp.gb28181.controller.bean; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.util.List; @Data @Schema(description="提交行政区划关联多个通道的参数") public class ChannelToRegionParam { @Schema(description = "行政区划编号") private String civilCode; @Schema(description = "选择的通道, 和all参数二选一") private List channelIds; @Schema(description = "所有通道, 和channelIds参数二选一") private Boolean all; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/controller/bean/DrawThinParam.java ================================================ package com.genersoft.iot.vmp.gb28181.controller.bean; import lombok.Getter; import lombok.Setter; import java.util.Map; @Getter @Setter public class DrawThinParam { private Map zoomParam; private Extent extent; /** * 地理坐标系, WGS84/GCJ02, 用来标识 extent 参数的坐标系 */ private String geoCoordSys; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/controller/bean/Extent.java ================================================ package com.genersoft.iot.vmp.gb28181.controller.bean; import lombok.Getter; import lombok.Setter; @Getter @Setter public class Extent { private Double minLng; private Double maxLng; private Double minLat; private Double maxLat; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/controller/bean/PlayResult.java ================================================ package com.genersoft.iot.vmp.gb28181.controller.bean; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import org.springframework.web.context.request.async.DeferredResult; public class PlayResult { private DeferredResult> result; private String uuid; private Device device; public DeferredResult> getResult() { return result; } public void setResult(DeferredResult> result) { this.result = result; } public String getUuid() { return uuid; } public void setUuid(String uuid) { this.uuid = uuid; } public Device getDevice() { return device; } public void setDevice(Device device) { this.device = device; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/controller/bean/ResetParam.java ================================================ package com.genersoft.iot.vmp.gb28181.controller.bean; import lombok.Getter; import lombok.Setter; import java.util.List; @Getter @Setter public class ResetParam { private Integer id; private List chanelFields; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/controller/bean/UpdateChannelParam.java ================================================ package com.genersoft.iot.vmp.gb28181.controller.bean; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.util.List; @Data @Schema(description = "通道关联参数") public class UpdateChannelParam { @Schema(description = "上级平台的数据库ID") private Integer platformId; @Schema(description = "关联所有通道") private boolean all; @Schema(description = "待关联的通道ID") List channelIds; @Schema(description = "待关联的设备ID") List deviceIds; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/dao/CommonGBChannelMapper.java ================================================ package com.genersoft.iot.vmp.gb28181.dao; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.controller.bean.Extent; import com.genersoft.iot.vmp.gb28181.dao.provider.ChannelProvider; import com.genersoft.iot.vmp.service.bean.GPSMsgInfo; import com.genersoft.iot.vmp.streamPush.bean.StreamPush; import com.genersoft.iot.vmp.web.custom.bean.CameraChannel; import com.genersoft.iot.vmp.web.custom.bean.CameraGroup; import com.genersoft.iot.vmp.web.custom.bean.Point; import org.apache.ibatis.annotations.*; import org.springframework.stereotype.Repository; import java.util.Collection; import java.util.List; @Mapper @Repository public interface CommonGBChannelMapper { @SelectProvider(type = ChannelProvider.class, method = "queryByDeviceId") List queryByDeviceId(@Param("gbDeviceId") String gbDeviceId); @Insert(" ") @Options(useGeneratedKeys = true, keyProperty = "gbId", keyColumn = "id") int insert(CommonGBChannel commonGBChannel); @SelectProvider(type = ChannelProvider.class, method = "queryById") CommonGBChannel queryById(@Param("gbId") int gbId); @Delete(value = {"delete from wvp_device_channel where id = #{gbId} "}) void delete(int gbId); @Update(value = {" "}) int update(CommonGBChannel commonGBChannel); @Update(value = {" "}) int updateStatusById(@Param("gbId") int gbId, @Param("status") String status); @Update("") int updateStatusForListById(List commonGBChannels, @Param("status") String status); @SelectProvider(type = ChannelProvider.class, method = "queryInListByStatus") List queryInListByStatus(List commonGBChannelList, @Param("status") String status); @Insert(" ") int batchAdd(List commonGBChannels); @Update("") int updateStatus(List commonGBChannels); @Update(value = {" "}) void reset(@Param("id") int id, List fields, @Param("updateTime") String updateTime); @SelectProvider(type = ChannelProvider.class, method = "queryByIds") List queryByIds(Collection ids); @Delete(value = {" "}) void batchDelete(List channelListInDb); @SelectProvider(type = ChannelProvider.class, method = "queryListByCivilCode") List queryListByCivilCode(@Param("query") String query, @Param("online") Boolean online, @Param("dataType") Integer dataType, @Param("civilCode") String civilCode); @SelectProvider(type = ChannelProvider.class, method = "queryListByParentId") List queryListByParentId(@Param("query") String query, @Param("online") Boolean online, @Param("dataType") Integer dataType, @Param("groupDeviceId") String groupDeviceId); @Select("") List queryForRegionTreeByCivilCode(@Param("parentDeviceId") String parentDeviceId); @Update(value = {" "}) int removeCivilCode(List allChildren); @Update(value = {" "}) int updateRegion(@Param("civilCode") String civilCode, @Param("channelList") List channelList); @SelectProvider(type = ChannelProvider.class, method = "queryByIdsOrCivilCode") List queryByIdsOrCivilCode(@Param("civilCode") String civilCode, @Param("ids") List ids); @Update(value = {" "}) int removeCivilCodeByChannels(List channelList); @Update(value = {" "}) int removeCivilCodeByChannelIds(List channelIdList); @SelectProvider(type = ChannelProvider.class, method = "queryByCivilCode") List queryByCivilCode(@Param("civilCode") String civilCode); @SelectProvider(type = ChannelProvider.class, method = "queryByDataTypeAndDeviceIds") List queryByDataTypeAndDeviceIds(@Param("dataType") Integer dataType, List deviceIds); @SelectProvider(type = ChannelProvider.class, method = "queryByGbDeviceIds") List queryByGbDeviceIds(List deviceIds); @Select(value = {" "}) List queryByGbDeviceIdsForIds(@Param("dataType") Integer dataType, List deviceIds); @SelectProvider(type = ChannelProvider.class, method = "queryByGroupList") List queryByGroupList(List groupList); @Update(value = {" "}) int removeParentIdByChannels(List channelList); @SelectProvider(type = ChannelProvider.class, method = "queryByBusinessGroup") List queryByBusinessGroup(@Param("businessGroup") String businessGroup); @SelectProvider(type = ChannelProvider.class, method = "queryByParentId") List queryByParentId(@Param("parentId") String parentId); @Update(value = {" "}) int updateBusinessGroupByChannelList(@Param("businessGroup") String businessGroup, List channelList); @Update(value = {" "}) int updateParentIdByChannelList(@Param("parentId") String parentId, List channelList); @Select("") List queryForGroupTreeByParentId(@Param("query") String query, @Param("parent") String parent); @Update(value = {" "}) int updateGroup(@Param("parentId") String parentId, @Param("businessGroup") String businessGroup, List channelList); @Update({""}) int batchUpdate(List commonGBChannels); @SelectProvider(type = ChannelProvider.class, method = "queryWithPlatform") List queryWithPlatform(@Param("platformId") Integer platformId); @SelectProvider(type = ChannelProvider.class, method = "queryShareChannelByParentId") List queryShareChannelByParentId(@Param("parentId") String parentId, @Param("platformId") Integer platformId); @SelectProvider(type = ChannelProvider.class, method = "queryShareChannelByCivilCode") List queryShareChannelByCivilCode(@Param("civilCode") String civilCode, @Param("platformId") Integer platformId); @Update(value = {" "}) int updateCivilCodeByChannelList(@Param("civilCode") String civilCode, List channelList); @SelectProvider(type = ChannelProvider.class, method = "queryListByStreamPushList") List queryListByStreamPushList(@Param("dataType") Integer dataType, List streamPushList); @Update(value = {" "}) void updateGpsByDeviceId(List gpsMsgInfoList); @SelectProvider(type = ChannelProvider.class, method = "queryList") List queryList(@Param("query") String query, @Param("online") Boolean online, @Param("hasRecordPlan") Boolean hasRecordPlan, @Param("dataType") Integer dataType, @Param("civilCode") String civilCode, @Param("parentDeviceId") String parentDeviceId); @Update(value = {" "}) void removeRecordPlan(List channelIds); @Update(value = {" "}) void addRecordPlan(List channelIds, @Param("planId") Integer planId); @Update(value = {" "}) void addRecordPlanForAll(@Param("planId") Integer planId); @Update(value = {" "}) void removeRecordPlanByPlanId( @Param("planId") Integer planId); @Select("") List queryForRecordPlanForWebList(@Param("planId") Integer planId, @Param("query") String query, @Param("dataType") Integer dataType, @Param("online") Boolean online, @Param("hasLink") Boolean hasLink); @SelectProvider(type = ChannelProvider.class, method = "queryByDataId") CommonGBChannel queryByDataId(@Param("dataType") Integer dataType, @Param("dataDeviceId") Integer dataDeviceId); @SelectProvider(type = ChannelProvider.class, method = "queryListByCivilCodeForUnusual") List queryListByCivilCodeForUnusual(@Param("query") String query, @Param("online") Boolean online, @Param("dataType")Integer dataType); @SelectProvider(type = ChannelProvider.class, method = "queryAllForUnusualCivilCode") List queryAllForUnusualCivilCode(); @SelectProvider(type = ChannelProvider.class, method = "queryListByParentForUnusual") List queryListByParentForUnusual(@Param("query") String query, @Param("online") Boolean online, @Param("dataType")Integer dataType); @SelectProvider(type = ChannelProvider.class, method = "queryAllForUnusualParent") List queryAllForUnusualParent(); @Update(value = {" "}) void removeParentIdByChannelIds(List channelIdsForClear); @SelectProvider(type = ChannelProvider.class, method = "queryOnlineListsByGbDeviceId") List queryOnlineListsByGbDeviceId(@Param("deviceId") int deviceId); @SelectProvider(type = ChannelProvider.class, method = "queryCommonChannelByDeviceChannel") CommonGBChannel queryCommonChannelByDeviceChannel(@Param("dataType") Integer dataType, @Param("dataDeviceId") Integer dataDeviceId, @Param("deviceId") String deviceId); @Update("UPDATE wvp_device_channel SET stream_id = #{stream} where id = #{gbId}") void updateStream(int gbId, String stream); @Update("") void updateGps(List commonGBChannels); @SelectProvider(type = ChannelProvider.class, method = "queryListForSy") List queryListForSy(@Param("groupDeviceId") String groupDeviceId, @Param("online") Boolean online); @SelectProvider(type = ChannelProvider.class, method = "queryGbChannelByChannelDeviceIdAndGbDeviceId") List queryGbChannelByChannelDeviceIdAndGbDeviceId(@Param("channelDeviceId") String channelDeviceId, @Param("gbDeviceId") String gbDeviceId); @SelectProvider(type = ChannelProvider.class, method = "queryListByDeviceIds") List queryListByDeviceIds(List deviceIds); @SelectProvider(type = ChannelProvider.class, method = "queryListWithChildForSy") List queryListWithChildForSy(@Param("query") String query, @Param("sortName") String sortName, @Param("order") Boolean order, @Param("groupList") List groupList, @Param("online") Boolean online); @SelectProvider(type = ChannelProvider.class, method = "queryListByAddressAndDirectionType") List queryListByAddressAndDirectionType(@Param("address") String address, @Param("directionType") Integer directionType); @SelectProvider(type = ChannelProvider.class, method = "queryListInBox") List queryListInBox(@Param("minLongitude") Double minLongitude, @Param("maxLongitude") Double maxLongitude, @Param("minLatitude") Double minLatitude, @Param("maxLatitude") Double maxLatitude, @Param("level") Integer level, @Param("groupList") List groupList); @SelectProvider(type = ChannelProvider.class, method = "queryListInCircleForMysql", databaseId = "mysql") @SelectProvider(type = ChannelProvider.class, method = "queryListInCircleForMysql", databaseId = "h2") @SelectProvider(type = ChannelProvider.class, method = "queryListInCircleForKingBase", databaseId = "kingbase") @SelectProvider(type = ChannelProvider.class, method = "queryListInCircleForKingBase", databaseId = "postgresql") List queryListInCircle(@Param("centerLongitude") Double centerLongitude, @Param("centerLatitude") Double centerLatitude, @Param("radius") Double radius, @Param("level") Integer level, @Param("groupList") List groupList); @SelectProvider(type = ChannelProvider.class, method = "queryListInPolygonForMysql", databaseId = "mysql") @SelectProvider(type = ChannelProvider.class, method = "queryListInPolygonForMysql", databaseId = "h2") @SelectProvider(type = ChannelProvider.class, method = "queryListInPolygonForKingBase", databaseId = "kingbase") @SelectProvider(type = ChannelProvider.class, method = "queryListInPolygonForKingBase", databaseId = "postgresql") List queryListInPolygon(@Param("pointList") List pointList, @Param("level") Integer level, @Param("groupList") List groupList); @SelectProvider(type = ChannelProvider.class, method = "queryListForSyMobile") List queryListForSyMobile(@Param("business") String business); @SelectProvider(type = ChannelProvider.class, method = "queryCameraChannelById") CameraChannel queryCameraChannelById(@Param("gbId") Integer id); @Update("") void saveLevel(List channels); @SelectProvider(type = ChannelProvider.class, method = "queryCameraChannelByIds") List queryCameraChannelByIds(List channelList); @SelectProvider(type = ChannelProvider.class, method = "queryOldChanelListByChannels") List queryOldChanelListByChannels(List channelList); @SelectProvider(type = ChannelProvider.class, method = "queryMeetingChannelList") List queryMeetingChannelList(@Param("business") String business); @Update("UPDATE wvp_device_channel SET map_level=null") int resetLevel(); @SelectProvider(type = ChannelProvider.class, method = "queryCameraChannelInBox") List queryCameraChannelInBox(@Param("minLon") double minLon, @Param("maxLon") double maxLon, @Param("minLat") double minLat, @Param("maxLat") double maxLat); @Select("select " + "MAX(coalesce(gb_longitude, longitude)) as maxLng, " + "MIN(coalesce(gb_longitude, longitude)) as minLng, " + "MAX(coalesce(gb_latitude, latitude)) as maxLat, " + "MIN(coalesce(gb_latitude, latitude)) as minLat " + " from wvp_device_channel " + " where channel_type = 0") Extent queryExtent(); @SelectProvider(type = ChannelProvider.class, method = "queryAllWithPosition") List queryAllWithPosition(); @SelectProvider(type = ChannelProvider.class, method = "queryListInExtent") List queryListInExtent(@Param("minLng") double minLng, @Param("maxLng") double maxLng, @Param("minLat") double minLat, @Param("maxLat") double maxLat); @SelectProvider(type = ChannelProvider.class, method = "queryListOutExtent") List queryListOutExtent(@Param("minLng") double minLng, @Param("maxLng") double maxLng, @Param("minLat") double minLat, @Param("maxLat") double maxLat); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/dao/DeviceAlarmMapper.java ================================================ package com.genersoft.iot.vmp.gb28181.dao; import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm; import org.apache.ibatis.annotations.Delete; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import org.springframework.stereotype.Repository; import java.util.List; /** * 用于存储设备的报警信息 */ @Mapper @Repository public interface DeviceAlarmMapper { @Insert("INSERT INTO wvp_device_alarm (device_id, channel_id, alarm_priority, alarm_method, alarm_time, alarm_description, longitude, latitude, alarm_type , create_time ) " + "VALUES (#{deviceId}, #{channelId}, #{alarmPriority}, #{alarmMethod}, #{alarmTime}, #{alarmDescription}, #{longitude}, #{latitude}, #{alarmType}, #{createTime})") int add(DeviceAlarm alarm); @Select( value = {" "}) List query(@Param("deviceId") String deviceId, @Param("channelId") String channelId, @Param("alarmPriority") String alarmPriority, @Param("alarmMethod") String alarmMethod, @Param("alarmType") String alarmType, @Param("startTime") String startTime, @Param("endTime") String endTime); @Delete(" " ) int clearAlarmBeforeTime(@Param("id") Integer id, @Param("deviceIdList") List deviceIdList, @Param("time") String time); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/dao/DeviceChannelMapper.java ================================================ package com.genersoft.iot.vmp.gb28181.dao; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; import com.genersoft.iot.vmp.gb28181.controller.bean.ChannelReduce; import com.genersoft.iot.vmp.gb28181.dao.provider.DeviceChannelProvider; import com.genersoft.iot.vmp.web.gb28181.dto.DeviceChannelExtend; import org.apache.ibatis.annotations.*; import org.springframework.stereotype.Repository; import java.util.List; /** * 用于存储设备通道信息 */ @Mapper @Repository public interface DeviceChannelMapper { @Insert("") @Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id") int add(DeviceChannel channel); @Update(value = {" "}) int update(DeviceChannel channel); @SelectProvider(type = DeviceChannelProvider.class, method = "queryChannels") List queryChannels(@Param("dataDeviceId") Integer dataDeviceId, @Param("civilCode") String civilCode, @Param("businessGroupId") String businessGroupId, @Param("parentChannelId") String parentChannelId, @Param("query") String query, @Param("queryParent") Boolean queryParent, @Param("hasSubChannel") Boolean hasSubChannel, @Param("online") Boolean online, @Param("channelIds") List channelIds, @Param("hasStream") Boolean hasStream); @SelectProvider(type = DeviceChannelProvider.class, method = "queryChannelsByDeviceDbId") List queryChannelsByDeviceDbId(@Param("dataDeviceId") int dataDeviceId); @Select("") List queryChaneIdListByDeviceDbIds(List deviceDbIds); @Delete("DELETE FROM wvp_device_channel WHERE data_type =1 and data_device_id=#{dataDeviceId}") int cleanChannelsByDeviceId(@Param("dataDeviceId") int dataDeviceId); @Delete("DELETE FROM wvp_device_channel WHERE data_type=#{dataType} and data_device_id=#{dataDeviceId} AND device_id=#{deviceId}") int deleteForNotify(DeviceChannel channel); @Select(value = {" "}) List queryChannelsWithDeviceInfo( @Param("deviceId") String deviceId, @Param("parentChannelId") String parentChannelId, @Param("query") String query, @Param("hasSubChannel") Boolean hasSubChannel, @Param("online") Boolean online, @Param("channelIds") List channelIds); @Update(value = {"UPDATE wvp_device_channel SET stream_id=#{streamId} WHERE id=#{channelId}"}) void startPlay(@Param("channelId") Integer channelId, @Param("streamId") String streamId); @Select(value = {" "}) List queryChannelListInAll(@Param("query") String query, @Param("online") Boolean online, @Param("hasSubChannel") Boolean hasSubChannel, @Param("platformId") String platformId, @Param("catalogId") String catalogId); @Update(value = {"UPDATE wvp_device_channel SET status='OFF' WHERE id=#{id}"}) void offline(@Param("id") int id); @Insert("") int batchAdd(@Param("addChannels") List addChannels); @Update(value = {"UPDATE wvp_device_channel SET status='ON' WHERE id=#{id}"}) void online(@Param("id") int id); @Update({""}) int batchUpdate(List updateChannels); @Update(value = {" "}) int updatePosition(DeviceChannel deviceChannel); @Select("select " + " id,\n" + " data_device_id,\n" + " create_time,\n" + " update_time,\n" + " sub_count,\n" + " stream_id,\n" + " has_audio,\n" + " gps_time,\n" + " stream_identification,\n" + " channel_type,\n" + " device_id,\n" + " name,\n" + " manufacturer,\n" + " model,\n" + " owner,\n" + " civil_code,\n" + " block,\n" + " address,\n" + " parental,\n" + " parent_id,\n" + " safety_way,\n" + " register_way,\n" + " cert_num,\n" + " certifiable,\n" + " err_code,\n" + " end_time,\n" + " secrecy,\n" + " ip_address,\n" + " port,\n" + " password,\n" + " status,\n" + " longitude,\n" + " latitude,\n" + " gb_longitude,\n" + " gb_latitude,\n" + " ptz_type,\n" + " position_type,\n" + " room_type,\n" + " use_type,\n" + " supply_light_type,\n" + " direction_type,\n" + " resolution,\n" + " business_group_id,\n" + " download_speed,\n" + " svc_space_support_mod,\n" + " svc_time_support_mode\n" + " from wvp_device_channel where data_type = 1 and data_device_id = #{dataDeviceId}") List queryAllChannelsForRefresh(@Param("dataDeviceId") int dataDeviceId); @Select("select de.* from wvp_device de left join wvp_device_channel dc on de.device_id = dc.device_id where dc.data_type = 1 and dc.device_id=#{channelId}") List getDeviceByChannelDeviceId(@Param("channelId") String channelId); @Delete({""}) int batchDel(List deleteChannelList); @Update({""}) int batchUpdateStatus(List channels); @Select("select count(1) from wvp_device_channel where status = 'ON'") int getOnlineCount(); @Select("select count(1) from wvp_device_channel") int getAllChannelCount(); @Update("") void updateChannelStreamIdentification(DeviceChannel channel); @Update("") void updateAllChannelStreamIdentification(@Param("streamIdentification") String streamIdentification); @Update({""}) void batchUpdatePosition(List channelList); @SelectProvider(type = DeviceChannelProvider.class, method = "getOne") DeviceChannel getOne(@Param("id") int id); @Select(value = {" "}) DeviceChannel getOneForSource(@Param("id") int id); @SelectProvider(type = DeviceChannelProvider.class, method = "getOneByDeviceId") DeviceChannel getOneByDeviceId(@Param("dataDeviceId") int dataDeviceId, @Param("channelId") String channelId); @Select(value = {" "}) DeviceChannel getOneByDeviceIdForSource(@Param("dataDeviceId") int dataDeviceId, @Param("channelId") String channelId); @Update(value = {"UPDATE wvp_device_channel SET stream_id=null WHERE id=#{channelId}"}) void stopPlayById(@Param("channelId") Integer channelId); @Update(value = {" "}) void changeAudio(@Param("channelId") int channelId, @Param("audio") boolean audio); @Update("UPDATE wvp_device_channel SET status=#{status} WHERE data_type=#{dataType} and data_device_id=#{dataDeviceId} AND device_id=#{deviceId}") @Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id") void updateStatus(DeviceChannel channel); @Update({""}) void updateChannelForNotify(DeviceChannel channel); @Select(value = {" "}) DeviceChannel getOneBySourceChannelId(@Param("dataDeviceId") int dataDeviceId, @Param("channelId") String channelId); @Update(value = {"UPDATE wvp_device_channel SET status = 'OFF' WHERE data_type = 1 and data_device_id=#{deviceId}"}) void offlineByDeviceId(@Param("deviceId") int deviceId); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/dao/DeviceMapper.java ================================================ package com.genersoft.iot.vmp.gb28181.dao; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; import org.apache.ibatis.annotations.*; import org.springframework.stereotype.Repository; import java.util.List; /** * 用于存储设备信息 */ @Mapper @Repository public interface DeviceMapper { @Select("SELECT " + "id, " + "device_id, " + "coalesce(custom_name, name) as name, " + "password, " + "manufacturer, " + "model, " + "firmware, " + "transport," + "stream_mode," + "ip," + "sdp_ip," + "local_ip," + "port," + "host_address," + "expires," + "register_time," + "keepalive_time," + "create_time," + "update_time," + "charset," + "subscribe_cycle_for_catalog," + "subscribe_cycle_for_mobile_position," + "mobile_position_submission_interval," + "subscribe_cycle_for_alarm," + "ssrc_check," + "as_message_channel," + "geo_coord_sys," + "on_line," + "server_id,"+ "media_server_id," + "broadcast_push_after_ack," + "(SELECT count(0) FROM wvp_device_channel dc WHERE dc.data_type = 1 and dc.data_device_id= de.id) as channel_count "+ " FROM wvp_device de WHERE de.device_id = #{deviceId}") Device getDeviceByDeviceId( @Param("deviceId") String deviceId); @Insert("INSERT INTO wvp_device (" + "device_id, " + "name, " + "manufacturer, " + "model, " + "firmware, " + "transport," + "stream_mode," + "media_server_id," + "ip," + "sdp_ip," + "local_ip," + "port," + "host_address," + "expires," + "register_time," + "keepalive_time," + "heart_beat_interval," + "heart_beat_count," + "position_capability," + "create_time," + "update_time," + "charset," + "subscribe_cycle_for_catalog," + "subscribe_cycle_for_mobile_position,"+ "mobile_position_submission_interval,"+ "subscribe_cycle_for_alarm,"+ "ssrc_check,"+ "as_message_channel,"+ "broadcast_push_after_ack,"+ "geo_coord_sys,"+ "server_id,"+ "on_line"+ ") VALUES (" + "#{deviceId}," + "#{name}," + "#{manufacturer}," + "#{model}," + "#{firmware}," + "#{transport}," + "#{streamMode}," + "#{mediaServerId}," + "#{ip}," + "#{sdpIp}," + "#{localIp}," + "#{port}," + "#{hostAddress}," + "#{expires}," + "#{registerTime}," + "#{keepaliveTime}," + "#{heartBeatInterval}," + "#{heartBeatCount}," + "#{positionCapability}," + "#{createTime}," + "#{updateTime}," + "#{charset}," + "#{subscribeCycleForCatalog}," + "#{subscribeCycleForMobilePosition}," + "#{mobilePositionSubmissionInterval}," + "#{subscribeCycleForAlarm}," + "#{ssrcCheck}," + "#{asMessageChannel}," + "#{broadcastPushAfterAck}," + "#{geoCoordSys}," + "#{serverId}," + "#{onLine}" + ")") @Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id") int add(Device device); @Update(value = {" "}) int update(Device device); @Select( " " ) List getDevices(@Param("dataType") Integer dataType, @Param("online") Boolean online); @Delete("DELETE FROM wvp_device WHERE device_id=#{deviceId}") int del(String deviceId); @Select("SELECT " + "id, " + "device_id, " + "coalesce(custom_name, name) as name, " + "password, " + "manufacturer, " + "model, " + "firmware, " + "transport," + "stream_mode," + "ip," + "sdp_ip,"+ "local_ip,"+ "port,"+ "host_address,"+ "expires,"+ "register_time,"+ "keepalive_time,"+ "create_time,"+ "update_time,"+ "charset,"+ "subscribe_cycle_for_catalog,"+ "subscribe_cycle_for_mobile_position,"+ "mobile_position_submission_interval,"+ "subscribe_cycle_for_alarm,"+ "ssrc_check,"+ "as_message_channel,"+ "broadcast_push_after_ack,"+ "geo_coord_sys,"+ "server_id,"+ "on_line"+ " FROM wvp_device WHERE on_line = true") List getOnlineDevices(); @Select("SELECT " + "id, " + "device_id, " + "coalesce(custom_name, name) as name, " + "password, " + "manufacturer, " + "model, " + "firmware, " + "transport," + "stream_mode," + "ip," + "sdp_ip,"+ "local_ip,"+ "port,"+ "host_address,"+ "expires,"+ "register_time,"+ "keepalive_time,"+ "create_time,"+ "update_time,"+ "charset,"+ "subscribe_cycle_for_catalog,"+ "subscribe_cycle_for_mobile_position,"+ "mobile_position_submission_interval,"+ "subscribe_cycle_for_alarm,"+ "ssrc_check,"+ "media_server_id,"+ "as_message_channel,"+ "broadcast_push_after_ack,"+ "geo_coord_sys,"+ "server_id,"+ "on_line"+ " FROM wvp_device WHERE on_line = true and server_id = #{serverId}") List getOnlineDevicesByServerId(@Param("serverId") String serverId); @Select("SELECT " + "id,"+ "device_id,"+ "coalesce(custom_name,name)as name,"+ "password,"+ "manufacturer,"+ "model,"+ "firmware,"+ "transport,"+ "stream_mode,"+ "ip,"+ "sdp_ip,"+ "local_ip,"+ "port,"+ "host_address,"+ "expires,"+ "register_time,"+ "keepalive_time,"+ "create_time,"+ "update_time,"+ "charset,"+ "subscribe_cycle_for_catalog,"+ "subscribe_cycle_for_mobile_position,"+ "mobile_position_submission_interval,"+ "subscribe_cycle_for_alarm,"+ "ssrc_check,"+ "as_message_channel,"+ "broadcast_push_after_ack,"+ "geo_coord_sys,"+ "on_line"+ " FROM wvp_device WHERE ip = #{host} AND port=#{port}") Device getDeviceByHostAndPort(@Param("host") String host, @Param("port") int port); @Update(value = {" "}) void updateCustom(Device device); @Insert("INSERT INTO wvp_device (" + "device_id,"+ "custom_name,"+ "password,"+ "sdp_ip,"+ "create_time,"+ "update_time,"+ "charset,"+ "ssrc_check,"+ "as_message_channel,"+ "broadcast_push_after_ack,"+ "geo_coord_sys,"+ "on_line,"+ "stream_mode," + "server_id," + "media_server_id"+ ") VALUES (" + "#{deviceId}," + "#{name}," + "#{password}," + "#{sdpIp}," + "#{createTime}," + "#{updateTime}," + "#{charset}," + "#{ssrcCheck}," + "#{asMessageChannel}," + "#{broadcastPushAfterAck}," + "#{geoCoordSys}," + "#{onLine}," + "#{streamMode}," + "#{serverId}," + "#{mediaServerId}" + ")") void addCustomDevice(Device device); @Select("select * FROM wvp_device") List getAll(); @Select("select * FROM wvp_device where as_message_channel = true") List queryDeviceWithAsMessageChannel(); @Select(" ") List getDeviceList(@Param("dataType") Integer dataType, @Param("query") String query, @Param("status") Boolean status); @Select("select * from wvp_device_channel where id = #{id}") DeviceChannel getRawChannel(@Param("id") int id); @Select("select * from wvp_device where id = #{id}") Device query(@Param("id") Integer id); @Select("select wd.* from wvp_device wd left join wvp_device_channel wdc on wdc.data_type = #{dataType} and wd.id = wdc.data_device_id where wdc.id = #{channelId}") Device queryByChannelId(@Param("dataType") Integer dataType, @Param("channelId") Integer channelId); @Select("select wd.* from wvp_device wd left join wvp_device_channel wdc on wdc.data_type = #{dataType} and wd.id = wdc.data_device_id where wdc.device_id = #{channelDeviceId}") Device getDeviceBySourceChannelDeviceId(@Param("dataType") Integer dataType, @Param("channelDeviceId") String channelDeviceId); @Update(value = {" "}) void updateSubscribeCatalog(Device device); @Update(value = {" "}) void updateSubscribeMobilePosition(Device device); @Update(value = {" "}) void offlineByList(List offlineDevices); @Update({""}) void batchUpdate(List devices); @Select(value = {" "}) List queryByDeviceIds(List deviceIds); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/dao/DeviceMobilePositionMapper.java ================================================ package com.genersoft.iot.vmp.gb28181.dao; import com.genersoft.iot.vmp.gb28181.bean.MobilePosition; import org.apache.ibatis.annotations.Delete; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import java.util.List; @Mapper public interface DeviceMobilePositionMapper { @Insert("INSERT INTO wvp_device_mobile_position (device_id,channel_id, device_name,time,longitude,latitude,altitude,speed,direction,report_source,create_time)"+ "VALUES (#{deviceId}, #{channelId}, #{deviceName}, #{time}, #{longitude}, #{latitude}, #{altitude}, #{speed}, #{direction}, #{reportSource}, #{createTime})") int insertNewPosition(MobilePosition mobilePosition); @Select(value = {" "}) List queryPositionByDeviceIdAndTime(@Param("deviceId") String deviceId, @Param("channelId") String channelId, @Param("startTime") String startTime, @Param("endTime") String endTime); @Select("SELECT * FROM wvp_device_mobile_position WHERE device_id = #{deviceId}" + " ORDER BY time DESC LIMIT 1") MobilePosition queryLatestPositionByDevice(String deviceId); @Delete("DELETE FROM wvp_device_mobile_position WHERE device_id = #{deviceId}") int clearMobilePositionsByDeviceId(String deviceId); @Insert("") void batchadd(List mobilePositions); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/dao/GroupMapper.java ================================================ package com.genersoft.iot.vmp.gb28181.dao; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import com.genersoft.iot.vmp.gb28181.bean.Group; import com.genersoft.iot.vmp.gb28181.bean.GroupTree; import com.genersoft.iot.vmp.gb28181.bean.Platform; import com.genersoft.iot.vmp.web.custom.bean.CameraCount; import com.genersoft.iot.vmp.web.custom.bean.CameraGroup; import org.apache.ibatis.annotations.*; import java.util.List; import java.util.Map; import java.util.Set; @Mapper public interface GroupMapper { @Insert("INSERT INTO wvp_common_group (device_id, name, parent_id, parent_device_id, business_group, create_time, update_time, civil_code, alias) " + "VALUES (#{deviceId}, #{name}, #{parentId}, #{parentDeviceId}, #{businessGroup}, #{createTime}, #{updateTime}, #{civilCode}, #{alias})") @Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id") int add(Group group); @Insert("INSERT INTO wvp_common_group (device_id, name, business_group, create_time, update_time, civil_code, alias) " + "VALUES (#{deviceId}, #{name}, #{businessGroup}, #{createTime}, #{updateTime}, #{civilCode}, #{alias})") @Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id") int addBusinessGroup(Group group); @Delete("DELETE FROM wvp_common_group WHERE id=#{id}") int delete(@Param("id") int id); @Update(" UPDATE wvp_common_group " + " SET update_time=#{updateTime}, device_id=#{deviceId}, name=#{name}, parent_id=#{parentId}, " + " parent_device_id=#{parentDeviceId}, business_group=#{businessGroup}, civil_code=#{civilCode}, " + " alias=#{alias}" + " WHERE id = #{id}") int update(Group group); @Select(value = {" "}) List query(@Param("query") String query, @Param("parentId") String parentId, @Param("businessGroup") String businessGroup); @Select("SELECT * from wvp_common_group WHERE parent_id = #{parentId} ") List getChildren(@Param("parentId") int parentId); @Select("SELECT * from wvp_common_group WHERE id = #{id} ") Group queryOne(@Param("id") int id); @Insert(" ") @Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id") int batchAdd(List groupList); @Select(" ") List queryForTree(@Param("query") String query, @Param("parentId") Integer parentId); @Select(" ") List queryForTreeByBusinessGroup(@Param("query") String query, @Param("businessGroup") String businessGroup); @Select(" ") List queryBusinessGroupForTree(@Param("query") String query); @Select("SELECT * from wvp_common_group WHERE device_id = #{deviceId} and business_group = #{businessGroup}") Group queryOneByDeviceId(@Param("deviceId") String deviceId, @Param("businessGroup") String businessGroup); @Select("SELECT * from wvp_common_group WHERE device_id = #{deviceId}") Group queryOneByOnlyDeviceId(@Param("deviceId") String deviceId); @Delete("") int batchDelete(List allChildren); @Select("SELECT * from wvp_common_group WHERE device_id = #{businessGroup} and business_group = #{businessGroup} ") Group queryBusinessGroup(@Param("businessGroup") String businessGroup); @Select("SELECT * from wvp_common_group WHERE business_group = #{businessGroup} ") List queryByBusinessGroup(@Param("businessGroup") String businessGroup); @Select("SELECT * from wvp_common_group WHERE business_group = #{businessGroup}") @MapKey("id") Map queryByBusinessGroupForMap(@Param("businessGroup") String businessGroup); @Delete("DELETE FROM wvp_common_group WHERE business_group = #{businessGroup}") int deleteByBusinessGroup(@Param("businessGroup") String businessGroup); @Update(" UPDATE wvp_common_group " + " SET parent_device_id=#{group.deviceId}, business_group = #{group.businessGroup}" + " WHERE parent_id = #{parentId}") int updateChild(@Param("parentId") Integer parentId, Group group); @Select(" ") List queryInGroupListByDeviceId(List groupList); @Select(" ") Set queryInChannelList(List channelList); @Select(" ") Set queryParentInChannelList(Set groupSet); @Select(" ") List queryForPlatform(@Param("platformId") Integer platformId); @Select(" ") Set queryNotShareGroupForPlatformByChannelList(List channelList, @Param("platformId") Integer platformId); @Select(" ") Set queryNotShareGroupForPlatformByGroupList(Set allGroup, @Param("platformId") Integer platformId); @Select(" ") Set queryByChannelList(List channelList); @Update(value = " ", databaseId = "mysql") @Update(value = " ", databaseId = "h2") @Update( value = " ", databaseId = "postgresql") @Update( value = " ", databaseId = "kingbase") void updateParentId(List groupListForAdd); @Update(value = " ", databaseId = "mysql") @Update(value = " ", databaseId = "h2") @Update( value = " ", databaseId = "kingbase") @Update( value = " ", databaseId = "postgresql") void updateParentIdWithBusinessGroup(List groupListForAdd); @Select(" ") List queryForPlatformByGroupId(@Param("groupId") int groupId); @Delete("DELETE FROM wvp_platform_group WHERE group_id = #{groupId}") void deletePlatformGroup(@Param("groupId") int groupId); @Select("SELECT * from wvp_common_group WHERE alias = #{alias} ") CameraGroup queryGroupByAlias(@Param("alias") String alias); @Select("SELECT * from wvp_common_group WHERE alias = #{alias} and business_group = #{businessGroup}") Group queryGroupByAliasAndBusinessGroup(@Param("alias") String alias, @Param("deviceId") String businessGroup); @Select("") List queryCountWithChild(List groupList); @Select("SELECT * from wvp_common_group where alias is not null") @MapKey("alias") Map queryGroupByAliasMap(); @Delete("DELETE FROM wvp_common_group where alias is not null") void deleteHasAlias(); @Update(value = " UPDATE wvp_common_group g1" + " JOIN wvp_common_group g2" + " ON g1.parent_device_id = g2.device_id" + " SET g1.parent_id = g2.id" + " WHERE g1.alias IS NOT NULL;", databaseId = "mysql") @Update(value = " UPDATE wvp_common_group g1" + " JOIN wvp_common_group g2" + " ON g1.parent_device_id = g2.device_id" + " SET g1.parent_id = g2.id" + " WHERE g1.alias IS NOT NULL;", databaseId = "h2") @Update(value = " UPDATE wvp_common_group AS g1" + " SET parent_id = g2.id" + " FROM wvp_common_group AS g2" + " WHERE g1.parent_device_id = g2.device_id" + " AND g1.alias IS NOT NULL;", databaseId = "kingbase") @Update(value = " UPDATE wvp_common_group AS g1" + " SET parent_id = g2.id" + " FROM wvp_common_group AS g2" + " WHERE g1.parent_device_id = g2.device_id" + " AND g1.alias IS NOT NULL;", databaseId = "postgresql") void fixParentId(); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/dao/PlatformChannelMapper.java ================================================ package com.genersoft.iot.vmp.gb28181.dao; import com.genersoft.iot.vmp.gb28181.bean.*; import org.apache.ibatis.annotations.*; import org.springframework.stereotype.Repository; import java.util.Collection; import java.util.List; import java.util.Set; @Mapper @Repository public interface PlatformChannelMapper { @Insert("") int addChannels(@Param("platformId") Integer platformId, @Param("channelList") List channelList); @Delete("") int delChannelForDeviceId(String deviceId); @Select("select d.*\n" + "from wvp_platform_channel pgc\n" + " left join wvp_device_channel dc on dc.id = pgc.device_channel_id\n" + " left join wvp_device d on dc.device_id = d.device_id\n" + "where dc.channel_type = 0 and dc.channel_id = #{channelId} and pgc.platform_id=#{platformId}") List queryDeviceByPlatformIdAndChannelId(@Param("platformId") String platformId, @Param("channelId") String channelId); @Select(" ") List queryPlatFormListForGBWithGBId(@Param("channelId") Integer channelId, List platforms); @Select("select dc.channel_id, dc.device_id,dc.name,d.manufacturer,d.model,d.firmware\n" + "from wvp_platform_channel pgc\n" + " left join wvp_device_channel dc on dc.id = pgc.device_channel_id\n" + " left join wvp_device d on dc.device_id = d.device_id\n" + "where dc.channel_type = 0 and dc.channel_id = #{channelId} and pgc.platform_id=#{platformId}") List queryDeviceInfoByPlatformIdAndChannelId(@Param("platformId") String platformId, @Param("channelId") String channelId); @Select(" SELECT wp.* from wvp_platform_channel pgc " + " left join wvp_device_channel dc on dc.id = pgc.device_channel_id " + " left join wvp_platform wp on wp.id = pgc.platform_id" + " WHERE dc.channel_type = 0 and dc.device_id=#{channelId}") List queryParentPlatformByChannelId(@Param("channelId") String channelId); @Select("") List queryForPlatformForWebList(@Param("platformId") Integer platformId, @Param("query") String query, @Param("dataType") Integer dataType, @Param("online") Boolean online, @Param("hasShare") Boolean hasShare); @Select("select\n" + " wdc.id as gb_id,\n" + " wdc.data_type,\n" + " wdc.data_device_id,\n" + " wdc.create_time,\n" + " wdc.update_time,\n" + " coalesce(wpgc.custom_device_id, wdc.gb_device_id, wdc.device_id) as gb_device_id,\n" + " coalesce(wpgc.custom_name, wdc.gb_name, wdc.name) as gb_name,\n" + " coalesce(wpgc.custom_manufacturer, wdc.gb_manufacturer, wdc.manufacturer) as gb_manufacturer,\n" + " coalesce(wpgc.custom_model, wdc.gb_model, wdc.model) as gb_model,\n" + " coalesce(wpgc.custom_owner, wdc.gb_owner, wdc.owner) as gb_owner,\n" + " coalesce(wpgc.custom_civil_code, wdc.gb_civil_code, wdc.civil_code) as gb_civil_code,\n" + " coalesce(wpgc.custom_block, wdc.gb_block, wdc.block) as gb_block,\n" + " coalesce(wpgc.custom_address, wdc.gb_address, wdc.address) as gb_address,\n" + " coalesce(wpgc.custom_parental, wdc.gb_parental, wdc.parental) as gb_parental,\n" + " coalesce(wpgc.custom_parent_id, wdc.gb_parent_id, wdc.parent_id) as gb_parent_id,\n" + " coalesce(wpgc.custom_safety_way, wdc.gb_safety_way, wdc.safety_way) as gb_safety_way,\n" + " coalesce(wpgc.custom_register_way, wdc.gb_register_way, wdc.register_way) as gb_register_way,\n" + " coalesce(wpgc.custom_cert_num, wdc.gb_cert_num, wdc.cert_num) as gb_cert_num,\n" + " coalesce(wpgc.custom_certifiable, wdc.gb_certifiable, wdc.certifiable) as gb_certifiable,\n" + " coalesce(wpgc.custom_err_code, wdc.gb_err_code, wdc.err_code) as gb_err_code,\n" + " coalesce(wpgc.custom_end_time, wdc.gb_end_time, wdc.end_time) as gb_end_time,\n" + " coalesce(wpgc.custom_secrecy, wdc.gb_secrecy, wdc.secrecy) as gb_secrecy,\n" + " coalesce(wpgc.custom_ip_address, wdc.gb_ip_address, wdc.ip_address) as gb_ip_address,\n" + " coalesce(wpgc.custom_port, wdc.gb_port, wdc.port) as gb_port,\n" + " coalesce(wpgc.custom_password, wdc.gb_password, wdc.password) as gb_password,\n" + " coalesce(wpgc.custom_status, wdc.gb_status, wdc.status) as gb_status,\n" + " coalesce(wpgc.custom_longitude, wdc.gb_longitude, wdc.longitude) as gb_longitude,\n" + " coalesce(wpgc.custom_latitude, wdc.gb_latitude, wdc.latitude) as gb_latitude,\n" + " coalesce(wpgc.custom_ptz_type, wdc.gb_ptz_type, wdc.ptz_type) as gb_ptz_type,\n" + " coalesce(wpgc.custom_position_type, wdc.gb_position_type, wdc.position_type) as gb_position_type,\n" + " coalesce(wpgc.custom_room_type, wdc.gb_room_type, wdc.room_type) as gb_room_type,\n" + " coalesce(wpgc.custom_use_type, wdc.gb_use_type, wdc.use_type) as gb_use_type,\n" + " coalesce(wpgc.custom_supply_light_type, wdc.gb_supply_light_type, wdc.supply_light_type) as gb_supply_light_type,\n" + " coalesce(wpgc.custom_direction_type, wdc.gb_direction_type, wdc.direction_type) as gb_direction_type,\n" + " coalesce(wpgc.custom_resolution, wdc.gb_resolution, wdc.resolution) as gb_resolution,\n" + " coalesce(wpgc.custom_business_group_id, wdc.gb_business_group_id, wdc.business_group_id) as gb_business_group_id,\n" + " coalesce(wpgc.custom_download_speed, wdc.gb_download_speed, wdc.download_speed) as gb_download_speed,\n" + " coalesce(wpgc.custom_svc_space_support_mod, wdc.gb_svc_space_support_mod, wdc.svc_space_support_mod) as gb_svc_space_support_mod,\n" + " coalesce(wpgc.custom_svc_time_support_mode, wdc.gb_svc_time_support_mode, wdc.svc_time_support_mode) as gb_svc_time_support_mode\n" + " from wvp_device_channel wdc" + " left join wvp_platform_channel wpgc on wdc.id = wpgc.device_channel_id" + " where wdc.channel_type = 0 and wpgc.platform_id = #{platformId} and coalesce(wpgc.custom_device_id, wdc.gb_device_id, wdc.device_id) = #{channelDeviceId} order by wdc.id " ) List queryOneWithPlatform(@Param("platformId") Integer platformId, @Param("channelDeviceId") String channelDeviceId); @Select("") List queryNotShare(@Param("platformId") Integer platformId, List channelIds); @Select("") List queryShare(@Param("platformId") Integer platformId, List channelIds); @Delete("") int removeChannelsWithPlatform(@Param("platformId") Integer platformId, List channelList); @Delete("") int removeChannels(List channelList); @Insert("") int addPlatformGroup(Collection groupListNotShare, @Param("platformId") Integer platformId); @Insert("") int addPlatformRegion(List regionListNotShare, @Param("platformId") Integer platformId); @Delete("") int removePlatformGroup(List groupList, @Param("platformId") Integer platformId); @Delete("") void removePlatformGroupById(@Param("id") int id, @Param("platformId") Integer platformId); @Delete("") void removePlatformRegionById(@Param("id") int id, @Param("platformId") Integer platformId); @Select(" ") Set queryShareChildrenGroup(@Param("parentId") Integer parentId, @Param("platformId") Integer platformId); @Select(" ") Set queryShareChildrenRegion(@Param("parentId") String parentId, @Param("platformId") Integer platformId); @Select(" ") Set queryShareParentGroupByGroupSet(Set groupSet, @Param("platformId") Integer platformId); @Select(" ") Set queryShareParentRegionByRegionSet(Set regionSet, @Param("platformId") Integer platformId); @Select(" ") List queryPlatFormListByChannelList(Collection ids); @Select(" ") List queryPlatFormListByChannelId(@Param("channelId") int channelId); @Delete("") void removeChannelsByPlatformId(@Param("platformId") Integer platformId); @Delete("") void removePlatformGroupsByPlatformId(@Param("platformId") Integer platformId); @Delete("") void removePlatformRegionByPlatformId(@Param("platformId") Integer platformId); @Update(value = {" "}) void updateCustomChannel(PlatformChannel channel); @Select("") CommonGBChannel queryShareChannel(@Param("platformId") int platformId, @Param("gbId") int gbId); @Select(" ") Set queryShareGroup(@Param("platformId") Integer platformId); @Select(" ") Set queryShareRegion(Integer id); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/dao/PlatformMapper.java ================================================ package com.genersoft.iot.vmp.gb28181.dao; import com.genersoft.iot.vmp.gb28181.bean.Platform; import org.apache.ibatis.annotations.*; import org.springframework.stereotype.Repository; import java.util.List; /** * 用于存储上级平台 */ @Mapper @Repository public interface PlatformMapper { @Insert("INSERT INTO wvp_platform (enable, name, server_gb_id, server_gb_domain, server_ip, server_port,device_gb_id,device_ip,"+ " device_port,username,password,expires,keep_timeout,transport,character_set,ptz,rtcp,status,catalog_group, update_time," + " create_time, as_message_channel, send_stream_ip, auto_push_channel, catalog_with_platform,catalog_with_group,catalog_with_region, "+ " civil_code,manufacturer,model,address,register_way,secrecy,server_id) " + " VALUES (#{enable}, #{name}, #{serverGBId}, #{serverGBDomain}, #{serverIp}, #{serverPort}, #{deviceGBId}, #{deviceIp}, " + " #{devicePort}, #{username}, #{password}, #{expires}, #{keepTimeout}, #{transport}, #{characterSet}, #{ptz}, #{rtcp}, #{status}, #{catalogGroup},#{updateTime}," + " #{createTime}, #{asMessageChannel}, #{sendStreamIp}, #{autoPushChannel}, #{catalogWithPlatform}, #{catalogWithGroup},#{catalogWithRegion}, " + " #{civilCode}, #{manufacturer}, #{model}, #{address}, #{registerWay}, #{secrecy}, #{serverId})") int add(Platform parentPlatform); @Update("UPDATE wvp_platform " + "SET update_time = #{updateTime}," + " enable=#{enable}, " + " name=#{name}," + " server_gb_id=#{serverGBId}, " + " server_gb_domain=#{serverGBDomain}, " + " server_ip=#{serverIp}," + " server_port=#{serverPort}, " + " device_gb_id=#{deviceGBId}," + " device_ip=#{deviceIp}, " + " device_port=#{devicePort}, " + " username=#{username}, " + " password=#{password}, " + " expires=#{expires}, " + " keep_timeout=#{keepTimeout}, " + " transport=#{transport}, " + " character_set=#{characterSet}, " + " ptz=#{ptz}, " + " rtcp=#{rtcp}, " + " status=#{status}, " + " catalog_group=#{catalogGroup}, " + " as_message_channel=#{asMessageChannel}, " + " send_stream_ip=#{sendStreamIp}, " + " auto_push_channel=#{autoPushChannel}, " + " catalog_with_platform=#{catalogWithPlatform}, " + " catalog_with_group=#{catalogWithGroup}, " + " catalog_with_region=#{catalogWithRegion}, " + " civil_code=#{civilCode}, " + " manufacturer=#{manufacturer}, " + " model=#{model}, " + " address=#{address}, " + " register_way=#{registerWay}, " + " server_id=#{serverId}, " + " secrecy=#{secrecy} " + "WHERE id=#{id}") int update(Platform parentPlatform); @Delete("DELETE FROM wvp_platform WHERE id=#{id}") int delete(@Param("id") Integer id); @Select(" ") List queryList(@Param("query") String query); @Select("SELECT * FROM wvp_platform WHERE server_id=#{serverId} and enable=#{enable} ") List queryEnableParentPlatformListByServerId(@Param("serverId") String serverId, @Param("enable") boolean enable); @Select("SELECT * FROM wvp_platform WHERE enable=true and as_message_channel=true") List queryEnablePlatformListWithAsMessageChannel(); @Select("SELECT * FROM wvp_platform WHERE server_gb_id=#{platformGbId}") Platform getParentPlatByServerGBId(String platformGbId); @Select("SELECT * FROM wvp_platform WHERE id=#{id}") Platform query(int id); @Update("UPDATE wvp_platform SET status=#{online}, server_id = #{serverId} WHERE id=#{id}" ) int updateStatus(@Param("id") int id, @Param("online") boolean online, @Param("serverId") String serverId); @Select("SELECT server_id FROM wvp_platform WHERE enable=true and server_id != #{serverId} group by server_id") List queryServerIdsWithEnableAndNotInServer(@Param("serverId") String serverId); @Select("SELECT * FROM wvp_platform WHERE server_id = #{serverId}") List queryByServerId(@Param("serverId") String serverId); @Select("SELECT * FROM wvp_platform ") List queryAll(); @Select("SELECT * FROM wvp_platform WHERE enable=true and server_id = #{serverId}") List queryServerIdsWithEnableAndServer(@Param("serverId") String serverId); @Update("UPDATE wvp_platform SET status=false where server_id = #{serverId}" ) void offlineAll(@Param("serverId") String serverId); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/dao/RegionMapper.java ================================================ package com.genersoft.iot.vmp.gb28181.dao; import com.genersoft.iot.vmp.common.CivilCodePo; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import com.genersoft.iot.vmp.gb28181.bean.Region; import com.genersoft.iot.vmp.gb28181.bean.RegionTree; import org.apache.ibatis.annotations.*; import java.util.List; import java.util.Set; @Mapper public interface RegionMapper { @Insert("INSERT INTO wvp_common_region (device_id, name, parent_id, parent_device_id, create_time, update_time) " + "VALUES (#{deviceId}, #{name}, #{parentId}, #{parentDeviceId}, #{createTime}, #{updateTime})") @Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id") void add(Region region); @Delete("DELETE FROM wvp_common_region WHERE id=#{id}") int delete(@Param("id") int id); @Update(" UPDATE wvp_common_region " + " SET update_time=#{updateTime}, device_id=#{deviceId}, name=#{name}, parent_id=#{parentId}, parent_device_id=#{parentDeviceId}" + " WHERE id = #{id}") int update(Region region); @Select(value = {" "}) List query(@Param("query") String query, @Param("parentId") String parentId); @Select("SELECT * from wvp_common_region WHERE parent_id = #{parentId} ORDER BY id ") List getChildren(@Param("parentId") Integer parentId); @Select("SELECT * from wvp_common_region WHERE id = #{id} ") Region queryOne(@Param("id") int id); @Select(" select dc.civil_code as civil_code " + " from wvp_device_channel dc " + " where dc.civil_code not in " + " (select device_id from wvp_common_region)") List getUninitializedCivilCode(); @Select(" ") List queryInList(Set codes); @Insert(" ") @Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id") int batchAdd(List regionList); @Select(" ") List queryForTree(@Param("parentId") Integer parentId); @Delete("") void batchDelete(List allChildren); @Select(" ") List queryInRegionListByDeviceId(List regionList); @Select(" ") List queryByPlatform(@Param("platformId") Integer platformId); @Update(value = " ", databaseId = "mysql") @Update(value = " ", databaseId = "h2") @Update( value = " ", databaseId = "kingbase") @Update( value = " ", databaseId = "postgresql") void updateParentId(List regionListForAdd); @Update(" ") void updateChild(@Param("parentId") int parentId, @Param("parentDeviceId") String parentDeviceId); @Select("SELECT * from wvp_common_region WHERE device_id = #{deviceId} ") Region queryByDeviceId(@Param("deviceId") String deviceId); @Select(" ") Set queryParentInChannelList(Set regionSet); @Select(" ") Set queryByChannelList(List channelList); @Select(" ") Set queryNotShareRegionForPlatformByChannelList(List channelList, @Param("platformId") Integer platformId); @Select(" ") Set queryNotShareRegionForPlatformByRegionList(Set allRegion, @Param("platformId") Integer platformId); @Select(" ") Set queryInCivilCodePoList(List civilCodePoList); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/dao/provider/ChannelProvider.java ================================================ package com.genersoft.iot.vmp.gb28181.dao.provider; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import com.genersoft.iot.vmp.gb28181.bean.Group; import com.genersoft.iot.vmp.streamPush.bean.StreamPush; import com.genersoft.iot.vmp.web.custom.bean.CameraGroup; import com.genersoft.iot.vmp.web.custom.bean.Point; import java.util.Collection; import java.util.List; import java.util.Map; public class ChannelProvider { public final static String BASE_SQL = "select\n" + " id as gb_id,\n" + " data_type,\n" + " data_device_id,\n" + " create_time,\n" + " update_time,\n" + " stream_id,\n" + " record_plan_id,\n" + " enable_broadcast,\n" + " map_level,\n" + " coalesce(gb_device_id, device_id) as gb_device_id,\n" + " coalesce(gb_name, name) as gb_name,\n" + " coalesce(gb_manufacturer, manufacturer) as gb_manufacturer,\n" + " coalesce(gb_model, model) as gb_model,\n" + " coalesce(gb_owner, owner) as gb_owner,\n" + " coalesce(gb_civil_code, civil_code) as gb_civil_code,\n" + " coalesce(gb_block, block) as gb_block,\n" + " coalesce(gb_address, address) as gb_address,\n" + " coalesce(gb_parental, parental) as gb_parental,\n" + " coalesce(gb_parent_id, parent_id) as gb_parent_id,\n" + " coalesce(gb_safety_way, safety_way) as gb_safety_way,\n" + " coalesce(gb_register_way, register_way) as gb_register_way,\n" + " coalesce(gb_cert_num, cert_num) as gb_cert_num,\n" + " coalesce(gb_certifiable, certifiable) as gb_certifiable,\n" + " coalesce(gb_err_code, err_code) as gb_err_code,\n" + " coalesce(gb_end_time, end_time) as gb_end_time,\n" + " coalesce(gb_secrecy, secrecy) as gb_secrecy,\n" + " coalesce(gb_ip_address, ip_address) as gb_ip_address,\n" + " coalesce(gb_port, port) as gb_port,\n" + " coalesce(gb_password, password) as gb_password,\n" + " coalesce(gb_status, status) as gb_status,\n" + " coalesce(gb_longitude, longitude) as gb_longitude,\n" + " coalesce(gb_latitude, latitude) as gb_latitude,\n" + " coalesce(gb_ptz_type, ptz_type) as gb_ptz_type,\n" + " coalesce(gb_position_type, position_type) as gb_position_type,\n" + " coalesce(gb_room_type, room_type) as gb_room_type,\n" + " coalesce(gb_use_type, use_type) as gb_use_type,\n" + " coalesce(gb_supply_light_type, supply_light_type) as gb_supply_light_type,\n" + " coalesce(gb_direction_type, direction_type) as gb_direction_type,\n" + " coalesce(gb_resolution, resolution) as gb_resolution,\n" + " coalesce(gb_business_group_id, business_group_id) as gb_business_group_id,\n" + " coalesce(gb_download_speed, download_speed) as gb_download_speed,\n" + " coalesce(gb_svc_space_support_mod, svc_space_support_mod) as gb_svc_space_support_mod,\n" + " coalesce(gb_svc_time_support_mode,svc_time_support_mode) as gb_svc_time_support_mode\n" + " from wvp_device_channel\n" ; public final static String BASE_SQL_TABLE_NAME = "select\n" + " wdc.id as gb_id,\n" + " wdc.data_type,\n" + " wdc.data_device_id,\n" + " wdc.create_time,\n" + " wdc.update_time,\n" + " wdc.stream_id,\n" + " wdc.record_plan_id,\n" + " wdc.enable_broadcast,\n" + " coalesce(wdc.gb_device_id, wdc.device_id) as gb_device_id,\n" + " coalesce(wdc.gb_name, wdc.name) as gb_name,\n" + " coalesce(wdc.gb_manufacturer, wdc.manufacturer) as gb_manufacturer,\n" + " coalesce(wdc.gb_model, wdc.model) as gb_model,\n" + " coalesce(wdc.gb_owner, wdc.owner) as gb_owner,\n" + " coalesce(wdc.gb_civil_code, wdc.civil_code) as gb_civil_code,\n" + " coalesce(wdc.gb_block, wdc.block) as gb_block,\n" + " coalesce(wdc.gb_address, wdc.address) as gb_address,\n" + " coalesce(wdc.gb_parental, wdc.parental) as gb_parental,\n" + " coalesce(wdc.gb_parent_id, wdc.parent_id) as gb_parent_id,\n" + " coalesce(wdc.gb_safety_way, wdc.safety_way) as gb_safety_way,\n" + " coalesce(wdc.gb_register_way, wdc.register_way) as gb_register_way,\n" + " coalesce(wdc.gb_cert_num, wdc.cert_num) as gb_cert_num,\n" + " coalesce(wdc.gb_certifiable, wdc.certifiable) as gb_certifiable,\n" + " coalesce(wdc.gb_err_code, wdc.err_code) as gb_err_code,\n" + " coalesce(wdc.gb_end_time, wdc.end_time) as gb_end_time,\n" + " coalesce(wdc.gb_secrecy, wdc.secrecy) as gb_secrecy,\n" + " coalesce(wdc.gb_ip_address, wdc.ip_address) as gb_ip_address,\n" + " coalesce(wdc.gb_port, wdc.port) as gb_port,\n" + " coalesce(wdc.gb_password, wdc.password) as gb_password,\n" + " coalesce(wdc.gb_status, wdc.status) as gb_status,\n" + " coalesce(wdc.gb_longitude, wdc.longitude) as gb_longitude,\n" + " coalesce(wdc.gb_latitude, wdc.latitude) as gb_latitude,\n" + " coalesce(wdc.gb_ptz_type, wdc.ptz_type) as gb_ptz_type,\n" + " coalesce(wdc.gb_position_type, wdc.position_type) as gb_position_type,\n" + " coalesce(wdc.gb_room_type, wdc.room_type) as gb_room_type,\n" + " coalesce(wdc.gb_use_type, wdc.use_type) as gb_use_type,\n" + " coalesce(wdc.gb_supply_light_type, wdc.supply_light_type) as gb_supply_light_type,\n" + " coalesce(wdc.gb_direction_type, wdc.direction_type) as gb_direction_type,\n" + " coalesce(wdc.gb_resolution, wdc.resolution) as gb_resolution,\n" + " coalesce(wdc.gb_business_group_id, wdc.business_group_id) as gb_business_group_id,\n" + " coalesce(wdc.gb_download_speed, wdc.download_speed) as gb_download_speed,\n" + " coalesce(wdc.gb_svc_space_support_mod, wdc.svc_space_support_mod) as gb_svc_space_support_mod,\n" + " coalesce(wdc.gb_svc_time_support_mode, wdc.svc_time_support_mode) as gb_svc_time_support_mode\n" + " from wvp_device_channel wdc\n" ; private final static String BASE_SQL_FOR_PLATFORM = "select\n" + " wdc.id as gb_id,\n" + " wdc.data_type,\n" + " wdc.data_device_id,\n" + " wdc.create_time,\n" + " wdc.update_time,\n" + " wdc.enable_broadcast,\n" + " coalesce(wpgc.custom_device_id, wdc.gb_device_id, wdc.device_id) as gb_device_id,\n" + " coalesce(wpgc.custom_name, wdc.gb_name, wdc.name) as gb_name,\n" + " coalesce(wpgc.custom_manufacturer, wdc.gb_manufacturer, wdc.manufacturer) as gb_manufacturer,\n" + " coalesce(wpgc.custom_model, wdc.gb_model, wdc.model) as gb_model,\n" + " coalesce(wpgc.custom_owner, wdc.gb_owner, wdc.owner) as gb_owner,\n" + " coalesce(wpgc.custom_civil_code, wdc.gb_civil_code, wdc.civil_code) as gb_civil_code,\n" + " coalesce(wpgc.custom_block, wdc.gb_block, wdc.block) as gb_block,\n" + " coalesce(wpgc.custom_address, wdc.gb_address, wdc.address) as gb_address,\n" + " coalesce(wpgc.custom_parental, wdc.gb_parental, wdc.parental) as gb_parental,\n" + " coalesce(wpgc.custom_parent_id, wdc.gb_parent_id, wdc.parent_id) as gb_parent_id,\n" + " coalesce(wpgc.custom_safety_way, wdc.gb_safety_way, wdc.safety_way) as gb_safety_way,\n" + " coalesce(wpgc.custom_register_way, wdc.gb_register_way, wdc.register_way) as gb_register_way,\n" + " coalesce(wpgc.custom_cert_num, wdc.gb_cert_num, wdc.cert_num) as gb_cert_num,\n" + " coalesce(wpgc.custom_certifiable, wdc.gb_certifiable, wdc.certifiable) as gb_certifiable,\n" + " coalesce(wpgc.custom_err_code, wdc.gb_err_code, wdc.err_code) as gb_err_code,\n" + " coalesce(wpgc.custom_end_time, wdc.gb_end_time, wdc.end_time) as gb_end_time,\n" + " coalesce(wpgc.custom_secrecy, wdc.gb_secrecy, wdc.secrecy) as gb_secrecy,\n" + " coalesce(wpgc.custom_ip_address, wdc.gb_ip_address, wdc.ip_address) as gb_ip_address,\n" + " coalesce(wpgc.custom_port, wdc.gb_port, wdc.port) as gb_port,\n" + " coalesce(wpgc.custom_password, wdc.gb_password, wdc.password) as gb_password,\n" + " coalesce(wpgc.custom_status, wdc.gb_status, wdc.status) as gb_status,\n" + " coalesce(wpgc.custom_longitude, wdc.gb_longitude, wdc.longitude) as gb_longitude,\n" + " coalesce(wpgc.custom_latitude, wdc.gb_latitude, wdc.latitude) as gb_latitude,\n" + " coalesce(wpgc.custom_ptz_type, wdc.gb_ptz_type, wdc.ptz_type) as gb_ptz_type,\n" + " coalesce(wpgc.custom_position_type, wdc.gb_position_type, wdc.position_type) as gb_position_type,\n" + " coalesce(wpgc.custom_room_type, wdc.gb_room_type, wdc.room_type) as gb_room_type,\n" + " coalesce(wpgc.custom_use_type, wdc.gb_use_type, wdc.use_type) as gb_use_type,\n" + " coalesce(wpgc.custom_supply_light_type, wdc.gb_supply_light_type, wdc.supply_light_type) as gb_supply_light_type,\n" + " coalesce(wpgc.custom_direction_type, wdc.gb_direction_type, wdc.direction_type) as gb_direction_type,\n" + " coalesce(wpgc.custom_resolution, wdc.gb_resolution, wdc.resolution) as gb_resolution,\n" + " coalesce(wpgc.custom_business_group_id, wdc.gb_business_group_id, wdc.business_group_id) as gb_business_group_id,\n" + " coalesce(wpgc.custom_download_speed, wdc.gb_download_speed, wdc.download_speed) as gb_download_speed,\n" + " coalesce(wpgc.custom_svc_space_support_mod, wdc.gb_svc_space_support_mod, wdc.svc_space_support_mod) as gb_svc_space_support_mod,\n" + " coalesce(wpgc.custom_svc_time_support_mode, wdc.gb_svc_time_support_mode, wdc.svc_time_support_mode) as gb_svc_time_support_mode\n" + " from wvp_device_channel wdc" + " left join wvp_platform_channel wpgc on wdc.id = wpgc.device_channel_id" ; private final static String BASE_SQL_FOR_CAMERA_DEVICE = "select\n" + " wdc.id as gb_id,\n" + " wdc.data_type,\n" + " wdc.data_device_id,\n" + " wdc.create_time,\n" + " wdc.update_time,\n" + " wdc.stream_id,\n" + " wdc.record_plan_id,\n" + " wdc.enable_broadcast,\n" + " wd.device_id as deviceCode,\n" + " wcg.alias as groupAlias,\n" + " wcg2.alias as topGroupGAlias,\n" + " coalesce(wdc.gb_device_id, wdc.device_id) as gb_device_id,\n" + " coalesce(wdc.gb_name, wdc.name) as gb_name,\n" + " coalesce(wdc.gb_manufacturer, wdc.manufacturer) as gb_manufacturer,\n" + " coalesce(wdc.gb_model, wdc.model) as gb_model,\n" + " coalesce(wdc.gb_owner, wdc.owner) as gb_owner,\n" + " coalesce(wdc.gb_civil_code, wdc.civil_code) as gb_civil_code,\n" + " coalesce(wdc.gb_block, wdc.block) as gb_block,\n" + " coalesce(wdc.gb_address, wdc.address) as gb_address,\n" + " coalesce(wdc.gb_parental, wdc.parental) as gb_parental,\n" + " coalesce(wdc.gb_parent_id, wdc.parent_id) as gb_parent_id,\n" + " coalesce(wdc.gb_safety_way, wdc.safety_way) as gb_safety_way,\n" + " coalesce(wdc.gb_register_way, wdc.register_way) as gb_register_way,\n" + " coalesce(wdc.gb_cert_num, wdc.cert_num) as gb_cert_num,\n" + " coalesce(wdc.gb_certifiable, wdc.certifiable) as gb_certifiable,\n" + " coalesce(wdc.gb_err_code, wdc.err_code) as gb_err_code,\n" + " coalesce(wdc.gb_end_time, wdc.end_time) as gb_end_time,\n" + " coalesce(wdc.gb_secrecy, wdc.secrecy) as gb_secrecy,\n" + " coalesce(wdc.gb_ip_address, wdc.ip_address) as gb_ip_address,\n" + " coalesce(wdc.gb_port, wdc.port) as gb_port,\n" + " coalesce(wdc.gb_password, wdc.password) as gb_password,\n" + " coalesce(wdc.gb_status, wdc.status) as gb_status,\n" + " coalesce(wdc.gb_longitude, wdc.longitude) as gb_longitude,\n" + " coalesce(wdc.gb_latitude, wdc.latitude) as gb_latitude,\n" + " coalesce(wdc.gb_ptz_type, wdc.ptz_type) as gb_ptz_type,\n" + " coalesce(wdc.gb_position_type, wdc.position_type) as gb_position_type,\n" + " coalesce(wdc.gb_room_type, wdc.room_type) as gb_room_type,\n" + " coalesce(wdc.gb_use_type, wdc.use_type) as gb_use_type,\n" + " coalesce(wdc.gb_supply_light_type, wdc.supply_light_type) as gb_supply_light_type,\n" + " coalesce(wdc.gb_direction_type, wdc.direction_type) as gb_direction_type,\n" + " coalesce(wdc.gb_resolution, wdc.resolution) as gb_resolution,\n" + " coalesce(wdc.gb_business_group_id, wdc.business_group_id) as gb_business_group_id,\n" + " coalesce(wdc.gb_download_speed, wdc.download_speed) as gb_download_speed,\n" + " coalesce(wdc.gb_svc_space_support_mod, wdc.svc_space_support_mod) as gb_svc_space_support_mod,\n" + " coalesce(wdc.gb_svc_time_support_mode, wdc.svc_time_support_mode) as gb_svc_time_support_mode\n" + " from wvp_device_channel wdc\n" + " left join wvp_device wd on wdc.data_type = 1 AND wd.id = wdc.data_device_id" + " left join wvp_common_group wcg on wcg.device_id = coalesce(wdc.gb_parent_id, wdc.parent_id)" + " left join wvp_common_group wcg2 on wcg2.device_id = wcg.business_group" ; public String queryByDeviceId(Map params ){ return BASE_SQL + " where channel_type = 0 and coalesce(gb_device_id, device_id) = #{gbDeviceId}"; } public String queryById(Map params ){ return BASE_SQL + " where channel_type = 0 and id = #{gbId}"; } public String queryByDataId(Map params ){ return BASE_SQL + " where channel_type = 0 and data_type = #{dataType} and data_device_id = #{dataDeviceId}"; } public String queryListByCivilCode(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(BASE_SQL); sqlBuild.append(" where channel_type = 0 "); if (params.get("query") != null) { sqlBuild.append(" AND (coalesce(gb_device_id, device_id) LIKE concat('%',#{query},'%') escape '/'" + " OR coalesce(gb_name, name) LIKE concat('%',#{query},'%') escape '/' )") ; } if (params.get("online") != null && (Boolean)params.get("online")) { sqlBuild.append(" AND coalesce(gb_status, status) = 'ON'"); } if (params.get("online") != null && !(Boolean)params.get("online")) { sqlBuild.append(" AND coalesce(gb_status, status) = 'OFF'"); } if (params.get("civilCode") != null) { sqlBuild.append(" AND coalesce(gb_civil_code, civil_code) = #{civilCode}"); }else { sqlBuild.append(" AND coalesce(gb_civil_code, civil_code) is null"); } if (params.get("dataType") != null) { sqlBuild.append(" AND data_type = #{dataType}"); } return sqlBuild.toString(); } public String queryListByParentId(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(BASE_SQL); sqlBuild.append(" where channel_type = 0 "); if (params.get("query") != null) { sqlBuild.append(" AND (coalesce(gb_device_id, device_id) LIKE concat('%',#{query},'%') escape '/'" + " OR coalesce(gb_name, name) LIKE concat('%',#{query},'%') escape '/' )") ; } if (params.get("online") != null && (Boolean)params.get("online")) { sqlBuild.append(" AND coalesce(gb_status, status) = 'ON'"); } if (params.get("online") != null && !(Boolean)params.get("online")) { sqlBuild.append(" AND coalesce(gb_status, status) = 'OFF'"); } if (params.get("groupDeviceId") != null) { sqlBuild.append(" AND coalesce(gb_parent_id, parent_id) = #{groupDeviceId}"); }else { sqlBuild.append(" AND coalesce(gb_parent_id, parent_id) is null"); } if (params.get("dataType") != null) { sqlBuild.append(" AND data_type = #{dataType}"); } return sqlBuild.toString(); } public String queryList(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(BASE_SQL); sqlBuild.append(" where channel_type = 0 "); if (params.get("query") != null) { sqlBuild.append(" AND (coalesce(gb_device_id, device_id) LIKE concat('%',#{query},'%') escape '/'" + " OR coalesce(gb_name, name) LIKE concat('%',#{query},'%') escape '/' )") ; } if (params.get("online") != null && (Boolean)params.get("online")) { sqlBuild.append(" AND coalesce(gb_status, status) = 'ON'"); } if (params.get("online") != null && !(Boolean)params.get("online")) { sqlBuild.append(" AND coalesce(gb_status, status) = 'OFF'"); } if (params.get("hasRecordPlan") != null && (Boolean)params.get("hasRecordPlan")) { sqlBuild.append(" AND record_plan_id > 0"); } if (params.get("dataType") != null) { sqlBuild.append(" AND data_type = #{dataType}"); } if (params.get("civilCode") != null) { sqlBuild.append(" AND coalesce(gb_civil_code, civil_code) = #{civilCode}"); } if (params.get("parentDeviceId") != null) { sqlBuild.append(" AND coalesce(gb_parent_id, parent_id) = #{parentDeviceId}"); } sqlBuild.append(" order by create_time desc"); return sqlBuild.toString(); } public String queryInListByStatus(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(BASE_SQL); sqlBuild.append("where channel_type = 0 and gb_status=#{status} and id in ( "); List commonGBChannelList = (List)params.get("commonGBChannelList"); boolean first = true; for (CommonGBChannel channel : commonGBChannelList) { if (!first) { sqlBuild.append(","); } sqlBuild.append(channel.getGbId()); first = false; } sqlBuild.append(" )"); return sqlBuild.toString() ; } public String queryByIds(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(BASE_SQL); sqlBuild.append("where channel_type = 0 and id in ( "); Collection ids = (Collection)params.get("ids"); boolean first = true; for (Integer id : ids) { if (!first) { sqlBuild.append(","); } sqlBuild.append(id); first = false; } sqlBuild.append(" )"); return sqlBuild.toString() ; } public String queryByDataTypeAndDeviceIds(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(BASE_SQL); sqlBuild.append("where channel_type = 0 and data_type = #{dataType} and data_device_id in ( "); Collection ids = (Collection)params.get("deviceIds"); boolean first = true; for (Integer id : ids) { if (!first) { sqlBuild.append(","); } sqlBuild.append(id); first = false; } sqlBuild.append(" )"); return sqlBuild.toString() ; } public String queryByGbDeviceIds(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(BASE_SQL); sqlBuild.append("where channel_type = 0 and coalesce(gb_device_id, device_id) in ( "); Collection ids = (Collection)params.get("deviceIds"); boolean first = true; for (String id : ids) { if (!first) { sqlBuild.append(","); } sqlBuild.append("'"); sqlBuild.append(id); sqlBuild.append("'"); first = false; } sqlBuild.append(" )"); return sqlBuild.toString() ; } public String queryByDeviceIds(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(BASE_SQL); sqlBuild.append("where channel_type = 0 and id in ( "); Collection ids = (Collection)params.get("deviceIds"); boolean first = true; for (Integer id : ids) { if (!first) { sqlBuild.append(","); } sqlBuild.append(id); first = false; } sqlBuild.append(" )"); return sqlBuild.toString() ; } public String queryByIdsOrCivilCode(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(BASE_SQL); sqlBuild.append("where channel_type = 0 and "); if (params.get("civilCode") != null) { sqlBuild.append(" coalesce(gb_civil_code, civil_code) = #{civilCode} "); if (params.get("ids") != null) { sqlBuild.append(" OR "); } } if (params.get("ids") != null) { sqlBuild.append(" id in ( "); Collection ids = (Collection)params.get("ids"); boolean first = true; for (Integer id : ids) { if (!first) { sqlBuild.append(","); } sqlBuild.append(id); first = false; } sqlBuild.append(" )"); } return sqlBuild.toString() ; } public String queryByCivilCode(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(BASE_SQL); sqlBuild.append("where channel_type = 0 and coalesce(gb_civil_code, civil_code) = #{civilCode} "); return sqlBuild.toString(); } public String queryByBusinessGroup(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(BASE_SQL); sqlBuild.append("where channel_type = 0 and coalesce(gb_business_group_id, business_group_id) = #{businessGroup} "); return sqlBuild.toString() ; } public String queryByParentId(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(BASE_SQL); sqlBuild.append("where channel_type = 0 and gb_parent_id = #{parentId} "); return sqlBuild.toString() ; } public String queryByGroupList(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(BASE_SQL); sqlBuild.append(" where channel_type = 0 and gb_parent_id in ( "); Collection ids = (Collection)params.get("groupList"); boolean first = true; for (Group group : ids) { if (!first) { sqlBuild.append(","); } sqlBuild.append(group.getDeviceId()); first = false; } sqlBuild.append(" )"); return sqlBuild.toString() ; } public String queryListByStreamPushList(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(BASE_SQL); sqlBuild.append(" where channel_type = 0 and data_type = #{dataType} and data_device_id in ( "); Collection ids = (Collection)params.get("streamPushList"); boolean first = true; for (StreamPush streamPush : ids) { if (!first) { sqlBuild.append(","); } sqlBuild.append(streamPush.getId()); first = false; } sqlBuild.append(" )"); return sqlBuild.toString() ; } public String queryWithPlatform(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(BASE_SQL_FOR_PLATFORM); sqlBuild.append(" where wpgc.platform_id = #{platformId}"); return sqlBuild.toString() ; } public String queryShareChannelByParentId(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(BASE_SQL_FOR_PLATFORM); sqlBuild.append(" where wpgc.platform_id = #{platformId} and coalesce(wpgc.custom_parent_id, wdc.gb_parent_id, wdc.parent_id) = #{parentId}"); return sqlBuild.toString() ; } public String queryShareChannelByCivilCode(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(BASE_SQL_FOR_PLATFORM); sqlBuild.append(" where wpgc.platform_id = #{platformId} and coalesce(wpgc.custom_civil_code, wdc.gb_civil_code, wdc.civil_code) = #{civilCode}"); return sqlBuild.toString() ; } public String queryListByCivilCodeForUnusual(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(BASE_SQL_TABLE_NAME); sqlBuild.append(" left join (select wcr.device_id from wvp_common_region wcr) temp on temp.device_id = coalesce(wdc.gb_civil_code, wdc.civil_code)" + " where coalesce(wdc.gb_civil_code, wdc.civil_code) is not null and temp.device_id is null "); sqlBuild.append(" AND wdc.channel_type = 0 "); if (params.get("query") != null) { sqlBuild.append(" AND (coalesce(wdc.gb_device_id, wdc.device_id) LIKE concat('%',#{query},'%') escape '/'" + " OR coalesce(wdc.gb_name, wdc.name) LIKE concat('%',#{query},'%') escape '/' )") ; } if (params.get("online") != null && (Boolean)params.get("online")) { sqlBuild.append(" AND coalesce(wdc.gb_status, wdc.status) = 'ON'"); } if (params.get("online") != null && !(Boolean)params.get("online")) { sqlBuild.append(" AND coalesce(wdc.gb_status, wdc.status) = 'OFF'"); } if (params.get("dataType") != null) { sqlBuild.append(" AND wdc.data_type = #{dataType}"); } return sqlBuild.toString(); } public String queryListByParentForUnusual(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(BASE_SQL_TABLE_NAME); sqlBuild.append(" left join (select wcg.device_id from wvp_common_group wcg) temp on temp.device_id = coalesce(wdc.gb_parent_id, wdc.parent_id)" + " where coalesce(wdc.gb_parent_id, wdc.parent_id) is not null and temp.device_id is null "); sqlBuild.append(" AND wdc.channel_type = 0 "); if (params.get("query") != null) { sqlBuild.append(" AND (coalesce(wdc.gb_device_id, wdc.device_id) LIKE concat('%',#{query},'%') escape '/'" + " OR coalesce(wdc.gb_name, wdc.name) LIKE concat('%',#{query},'%') escape '/' )") ; } if (params.get("online") != null && (Boolean)params.get("online")) { sqlBuild.append(" AND coalesce(wdc.gb_status, wdc.status) = 'ON'"); } if (params.get("online") != null && !(Boolean)params.get("online")) { sqlBuild.append(" AND coalesce(wdc.gb_status, wdc.status) = 'OFF'"); } if (params.get("dataType") != null) { sqlBuild.append(" AND wdc.data_type = #{dataType}"); } return sqlBuild.toString(); } public String queryCommonChannelByDeviceChannel(Map params ){ return BASE_SQL + " where data_type=#{dataType} and data_device_id=#{dataDeviceId} AND device_id=#{deviceId}"; } public String queryCameraChannelInBox(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(BASE_SQL_TABLE_NAME); sqlBuild.append(" where wdc.channel_type = 0 AND coalesce(wdc.gb_longitude, wdc.longitude) > #{minLon} " + "AND coalesce(wdc.gb_longitude, wdc.longitude) <= #{maxLon} " + "AND coalesce(wdc.gb_latitude, wdc.latitude) > #{minLat} " + "AND coalesce(wdc.gb_latitude, wdc.latitude) <= #{maxLat}"); return sqlBuild.toString(); } public String queryOldChanelListByChannels(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(BASE_SQL); sqlBuild.append(" where id in ( "); List channelList = (List)params.get("channelList"); boolean first = true; for (CommonGBChannel channel : channelList) { if (!first) { sqlBuild.append(","); } sqlBuild.append(channel.getGbId()); first = false; } sqlBuild.append(" )"); return sqlBuild.toString() ; } public String queryAllForUnusualCivilCode(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append("select wdc.id from wvp_device_channel wdc "); sqlBuild.append(" left join (select wcr.device_id from wvp_common_region wcr) temp on temp.device_id = coalesce(wdc.gb_civil_code, wdc.civil_code)" + " where coalesce(wdc.gb_civil_code, wdc.civil_code) is not null and temp.device_id is null "); sqlBuild.append(" AND wdc.channel_type = 0 "); return sqlBuild.toString(); } public String queryAllForUnusualParent(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append("select wdc.id from wvp_device_channel wdc "); sqlBuild.append(" left join (select wcg.device_id from wvp_common_group wcg) temp on temp.device_id = coalesce(wdc.gb_parent_id, wdc.parent_id)" + " where coalesce(wdc.gb_parent_id, wdc.parent_id) is not null and temp.device_id is null "); sqlBuild.append(" AND wdc.channel_type = 0 "); return sqlBuild.toString(); } public String queryOnlineListsByGbDeviceId(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(BASE_SQL_TABLE_NAME); sqlBuild.append(" where wdc.channel_type = 0 AND coalesce(wdc.gb_status, wdc.status) = 'ON' AND wdc.data_type = 1 AND data_device_id = #{deviceId}"); return sqlBuild.toString(); } public String queryListForSy(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(BASE_SQL_FOR_CAMERA_DEVICE); sqlBuild.append(" where wdc.channel_type = 0 AND wdc.data_type != 2 AND (wdc.gb_ptz_type is null or ( wdc.gb_ptz_type != 98 AND wdc.gb_ptz_type != 99)) AND coalesce(wdc.gb_parent_id, wdc.parent_id) = #{groupDeviceId}"); if (params.get("online") != null && (Boolean)params.get("online")) { sqlBuild.append(" AND coalesce(wdc.gb_status, wdc.status) = 'ON'"); } if (params.get("online") != null && !(Boolean)params.get("online")) { sqlBuild.append(" AND coalesce(wdc.gb_status, wdc.status) = 'OFF'"); } sqlBuild.append(" order by coalesce(wdc.gb_status, wdc.status) desc"); return sqlBuild.toString(); } public String queryListWithChildForSy(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(BASE_SQL_FOR_CAMERA_DEVICE); sqlBuild.append(" where wdc.channel_type = 0 AND wdc.data_type != 2 AND (wdc.gb_ptz_type is null or ( wdc.gb_ptz_type != 98 AND wdc.gb_ptz_type != 99)) "); List groupList = (List)params.get("groupList"); if (groupList != null && !groupList.isEmpty()) { sqlBuild.append(" AND coalesce(wdc.gb_parent_id, wdc.parent_id) in ("); boolean first = true; for (CameraGroup group : groupList) { if (!first) { sqlBuild.append(","); } sqlBuild.append("'" + group.getDeviceId() + "'"); first = false; } sqlBuild.append(" )"); } if (params.get("query") != null) { sqlBuild.append(" AND (coalesce(wdc.gb_device_id, wdc.device_id) LIKE concat('%',#{query},'%') escape '/'" + " OR coalesce(wdc.gb_name, wdc.name) LIKE concat('%',#{query},'%') escape '/' )") ; } if (params.get("online") != null && (Boolean)params.get("online")) { sqlBuild.append(" AND coalesce(wdc.gb_status, status) = 'ON'"); } if (params.get("online") != null && !(Boolean)params.get("online")) { sqlBuild.append(" AND coalesce(wdc.gb_status, status) = 'OFF'"); } if (params.get("sortName") != null) { StringBuilder sqlBuildForSort = new StringBuilder(); sqlBuildForSort.append("select * from ( "); sqlBuildForSort.append(sqlBuild); sqlBuildForSort.append(" ) as temp"); String sortName = (String)params.get("sortName"); switch (sortName) { case "gbId": sqlBuildForSort.append(" order by gb_id "); break; case "gbDeviceId": sqlBuildForSort.append(" order by gb_device_id "); break; case "gbName": sqlBuildForSort.append(" order by gb_name "); break; case "gbStatus": sqlBuildForSort.append(" order by gb_status "); break; case "createTime": sqlBuildForSort.append(" order by create_time "); break; case "updateTime": sqlBuildForSort.append(" order by update_time "); break; case "deviceCode": sqlBuildForSort.append(" order by deviceCode "); break; } if (params.get("order") != null && (Boolean)params.get("order")) { sqlBuildForSort.append(" ASC"); } if (params.get("order") != null && !(Boolean)params.get("order")) { sqlBuildForSort.append(" DESC"); } return sqlBuildForSort.toString(); }else { return sqlBuild.toString(); } } public String queryListInBox(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(BASE_SQL_FOR_CAMERA_DEVICE); sqlBuild.append(" where wdc.channel_type = 0 AND wdc.data_type != 2 AND (wdc.gb_ptz_type is null or ( wdc.gb_ptz_type != 98 AND wdc.gb_ptz_type != 99)) " + " AND coalesce(wdc.gb_parent_id, wdc.parent_id) in ("); sqlBuild.append(" "); List groupList = (List)params.get("groupList"); boolean first = true; for (CameraGroup group : groupList) { if (!first) { sqlBuild.append(","); } sqlBuild.append("'" + group.getDeviceId() + "'"); first = false; } sqlBuild.append(" )"); sqlBuild.append(" AND coalesce(wdc.gb_longitude, wdc.longitude) >= #{minLongitude} AND coalesce(wdc.gb_longitude, wdc.longitude) <= #{maxLongitude}"); sqlBuild.append(" AND coalesce(wdc.gb_latitude, wdc.latitude) >= #{minLatitude} AND coalesce(wdc.gb_latitude, wdc.latitude) <= #{maxLatitude}"); if (params.get("level") != null) { sqlBuild.append(" AND ( map_level <= #{level} or map_level is null )"); } return sqlBuild.toString(); } public String queryListInCircleForMysql(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(BASE_SQL_FOR_CAMERA_DEVICE); sqlBuild.append(" where wdc.channel_type = 0 AND wdc.data_type != 2 AND (wdc.gb_ptz_type is null or ( wdc.gb_ptz_type != 98 AND wdc.gb_ptz_type != 99)) " + " AND coalesce(wdc.gb_parent_id, wdc.parent_id) in ("); sqlBuild.append(" "); List groupList = (List)params.get("groupList"); boolean first = true; for (CameraGroup group : groupList) { if (!first) { sqlBuild.append(","); } sqlBuild.append("'" + group.getDeviceId() + "'"); first = false; } sqlBuild.append(" )"); String geomTextBuilder = "point(" + params.get("centerLongitude") + " " + params.get("centerLatitude") + ")"; sqlBuild.append("AND ST_Distance_Sphere(point(coalesce(wdc.gb_longitude, wdc.longitude), coalesce(wdc.gb_latitude, wdc.latitude)), ST_GeomFromText('").append(geomTextBuilder).append("')) < #{radius}"); if (params.get("level") != null) { sqlBuild.append(" AND ( map_level <= #{level} or map_level is null )"); } return sqlBuild.toString(); } public String queryListInCircleForKingBase(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(BASE_SQL_FOR_CAMERA_DEVICE); sqlBuild.append(" where wdc.channel_type = 0 AND wdc.data_type != 2 AND (wdc.gb_ptz_type is null or ( wdc.gb_ptz_type != 98 AND wdc.gb_ptz_type != 99)) " + " AND coalesce(wdc.gb_parent_id, wdc.parent_id) in ("); sqlBuild.append(" "); List groupList = (List)params.get("groupList"); boolean first = true; for (CameraGroup group : groupList) { if (!first) { sqlBuild.append(","); } sqlBuild.append("'" + group.getDeviceId() + "'"); first = false; } sqlBuild.append(" )"); String geomTextBuilder = "point(" + params.get("centerLongitude") + " " + params.get("centerLatitude") + ")"; sqlBuild.append("AND ST_DistanceSphere(ST_MakePoint(coalesce(wdc.gb_longitude, wdc.longitude), coalesce(wdc.gb_latitude, wdc.latitude)), ST_GeomFromText('").append(geomTextBuilder).append("')) < #{radius}"); if (params.get("level") != null) { sqlBuild.append(" AND ( map_level <= #{level} or map_level is null )"); } return sqlBuild.toString(); } public String queryListInPolygonForMysql(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(BASE_SQL_FOR_CAMERA_DEVICE); sqlBuild.append(" where wdc.channel_type = 0 AND wdc.data_type != 2 AND (wdc.gb_ptz_type is null or ( wdc.gb_ptz_type != 98 AND wdc.gb_ptz_type != 99)) " + " AND coalesce(wdc.gb_parent_id, wdc.parent_id) in ("); sqlBuild.append(" "); List groupList = (List)params.get("groupList"); boolean first = true; for (CameraGroup group : groupList) { if (!first) { sqlBuild.append(","); } sqlBuild.append("'" + group.getDeviceId() + "'"); first = false; } sqlBuild.append(" )"); StringBuilder geomTextBuilder = new StringBuilder(); geomTextBuilder.append("POLYGON(("); List pointList = (List)params.get("pointList"); for (int i = 0; i < pointList.size(); i++) { if (i > 0) { geomTextBuilder.append(", "); } Point point = pointList.get(i); geomTextBuilder.append(point.getLng()).append(" ").append(point.getLat()); } geomTextBuilder.append("))"); sqlBuild.append("AND ST_Within(point(coalesce(wdc.gb_longitude, wdc.longitude), coalesce(wdc.gb_latitude, wdc.latitude)), ST_GeomFromText('").append(geomTextBuilder).append("'))"); if (params.get("level") != null) { sqlBuild.append(" AND ( map_level <= #{level} or map_level is null )"); } return sqlBuild.toString(); } public String queryListInPolygonForKingBase(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(BASE_SQL_FOR_CAMERA_DEVICE); sqlBuild.append(" where wdc.channel_type = 0 AND wdc.data_type != 2 AND (wdc.gb_ptz_type is null or ( wdc.gb_ptz_type != 98 AND wdc.gb_ptz_type != 99)) " + " AND coalesce(wdc.gb_parent_id, wdc.parent_id) in ("); sqlBuild.append(" "); List groupList = (List)params.get("groupList"); boolean first = true; for (CameraGroup group : groupList) { if (!first) { sqlBuild.append(","); } sqlBuild.append("'" + group.getDeviceId() + "'"); first = false; } sqlBuild.append(" )"); StringBuilder geomTextBuilder = new StringBuilder(); geomTextBuilder.append("POLYGON(("); List pointList = (List)params.get("pointList"); for (int i = 0; i < pointList.size(); i++) { if (i > 0) { geomTextBuilder.append(", "); } Point point = pointList.get(i); geomTextBuilder.append(point.getLng()).append(" ").append(point.getLat()); } geomTextBuilder.append("))"); sqlBuild.append("AND ST_Within(ST_MakePoint(coalesce(wdc.gb_longitude, wdc.longitude), coalesce(wdc.gb_latitude, wdc.latitude)), ST_GeomFromText('").append(geomTextBuilder).append("'))"); if (params.get("level") != null) { sqlBuild.append(" AND ( map_level <= #{level} or map_level is null )"); } return sqlBuild.toString(); } public String queryGbChannelByChannelDeviceIdAndGbDeviceId(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(BASE_SQL_FOR_CAMERA_DEVICE); sqlBuild.append(" where coalesce(wdc.gb_device_id, wdc.device_id) = #{channelDeviceId}"); if (params.get("gbDeviceId") != null) { sqlBuild.append(" AND wdc.data_type = 1 and wd.device_id = #{gbDeviceId}"); } return sqlBuild.toString(); } public String queryListByAddressAndDirectionType(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(BASE_SQL_FOR_CAMERA_DEVICE); sqlBuild.append(" where coalesce(wdc.gb_address, wdc.address) = #{address}"); if (params.get("directionType") != null) { sqlBuild.append(" and coalesce(wdc.gb_direction_type, wdc.direction_type) = #{directionType}"); } return sqlBuild.toString(); } public String queryListByDeviceIds(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(""); return sqlBuild.toString() ; } public String queryCameraChannelByIds(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(BASE_SQL_FOR_CAMERA_DEVICE); sqlBuild.append(" where wdc.id in ( "); List channelList = (List)params.get("channelList"); boolean first = true; for (CommonGBChannel channel : channelList) { if (!first) { sqlBuild.append(","); } sqlBuild.append(channel.getGbId()); first = false; } sqlBuild.append(" )"); return sqlBuild.toString() ; } public String queryListForSyMobile(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(BASE_SQL_FOR_CAMERA_DEVICE); sqlBuild.append(" WHERE wdc.gb_ptz_type = 99 and wdc.channel_type = 0 AND wdc.data_type != 2 "); if (params.get("business") != null) { sqlBuild.append(" AND coalesce(gb_business_group_id, business_group_id) = #{business}"); } return sqlBuild.toString(); } public String queryMeetingChannelList(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(BASE_SQL_FOR_CAMERA_DEVICE); sqlBuild.append(" WHERE wdc.channel_type = 0 AND wdc.data_type = 3 and wdc.gb_ptz_type = 98 and coalesce(wdc.gb_business_group_id, wdc.business_group_id) = #{business}"); return sqlBuild.toString(); } public String queryCameraChannelById(Map params ){ return BASE_SQL_FOR_CAMERA_DEVICE + " where wdc.id = #{gbId}"; } public String queryAllWithPosition(Map params ){ return BASE_SQL + " where channel_type = 0 " + " AND coalesce(gb_longitude, longitude) > 0" + " AND coalesce(gb_latitude, latitude) > 0 " + " ORDER BY map_level"; } public String queryListInExtent(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(BASE_SQL); sqlBuild.append(" where channel_type = 0 " + "AND coalesce(gb_longitude, longitude) > #{minLng} " + "AND coalesce(gb_longitude, longitude) <= #{maxLng} " + "AND coalesce(gb_latitude, latitude) > #{minLat} " + "AND coalesce(gb_latitude, latitude) <= #{maxLat}"); return sqlBuild.toString(); } public String queryListOutExtent(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(BASE_SQL); sqlBuild.append(" where channel_type = 0 AND ( " + "coalesce(gb_longitude, longitude) <= #{minLng} " + "or coalesce(gb_longitude, longitude) > #{maxLng} " + "or coalesce(gb_latitude, latitude) <= #{minLat} " + "or coalesce(gb_latitude, latitude) > #{maxLat}" + ")"); return sqlBuild.toString(); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/dao/provider/DeviceChannelProvider.java ================================================ package com.genersoft.iot.vmp.gb28181.dao.provider; import com.genersoft.iot.vmp.common.enums.ChannelDataType; import org.springframework.util.ObjectUtils; import java.util.List; import java.util.Map; public class DeviceChannelProvider { public String getBaseSelectSql(){ return "SELECT " + " dc.id,\n" + " dc.data_device_id,\n" + " dc.create_time,\n" + " dc.update_time,\n" + " dc.sub_count,\n" + " dc.stream_id,\n" + " dc.has_audio,\n" + " dc.gps_time,\n" + " dc.stream_identification,\n" + " dc.channel_type,\n" + " d.device_id as parent_device_id,\n" + " coalesce(d.custom_name, d.name) as parent_name,\n" + " coalesce(dc.gb_device_id, dc.device_id) as device_id,\n" + " coalesce(dc.gb_name, dc.name) as name,\n" + " coalesce(dc.gb_manufacturer, dc.manufacturer) as manufacturer,\n" + " coalesce(dc.gb_model, dc.model) as model,\n" + " coalesce(dc.gb_owner, dc.owner) as owner,\n" + " coalesce(dc.gb_civil_code, dc.civil_code) as civil_code,\n" + " coalesce(dc.gb_block, dc.block) as block,\n" + " coalesce(dc.gb_address, dc.address) as address,\n" + " coalesce(dc.gb_parental, dc.parental) as parental,\n" + " coalesce(dc.gb_parent_id, dc.parent_id) as parent_id,\n" + " coalesce(dc.gb_safety_way, dc.safety_way) as safety_way,\n" + " coalesce(dc.gb_register_way, dc.register_way) as register_way,\n" + " coalesce(dc.gb_cert_num, dc.cert_num) as cert_num,\n" + " coalesce(dc.gb_certifiable, dc.certifiable) as certifiable,\n" + " coalesce(dc.gb_err_code, dc.err_code) as err_code,\n" + " coalesce(dc.gb_end_time, dc.end_time) as end_time,\n" + " coalesce(dc.gb_secrecy, dc.secrecy) as secrecy,\n" + " coalesce(dc.gb_ip_address, dc.ip_address) as ip_address,\n" + " coalesce(dc.gb_port, dc.port) as port,\n" + " coalesce(dc.gb_password, dc.password) as password,\n" + " coalesce(dc.gb_status, dc.status) as status,\n" + " coalesce(dc.gb_longitude, dc.longitude) as longitude,\n" + " coalesce(dc.gb_latitude, dc.latitude) as latitude,\n" + " coalesce(dc.gb_ptz_type, dc.ptz_type) as ptz_type,\n" + " coalesce(dc.gb_position_type, dc.position_type) as position_type,\n" + " coalesce(dc.gb_room_type, dc.room_type) as room_type,\n" + " coalesce(dc.gb_use_type, dc.use_type) as use_type,\n" + " coalesce(dc.gb_supply_light_type, dc.supply_light_type) as supply_light_type,\n" + " coalesce(dc.gb_direction_type, dc.direction_type) as direction_type,\n" + " coalesce(dc.gb_resolution, dc.resolution) as resolution,\n" + " coalesce(dc.gb_business_group_id, dc.business_group_id) as business_group_id,\n" + " coalesce(dc.gb_download_speed, dc.download_speed) as download_speed,\n" + " coalesce(dc.gb_svc_space_support_mod, dc.svc_space_support_mod) as svc_space_support_mod,\n" + " coalesce(dc.gb_svc_time_support_mode,dc.svc_time_support_mode) as svc_time_support_mode\n" + " from " + " wvp_device_channel dc " + " left join wvp_device d on d.id = dc.data_device_id " ; } public String queryChannels(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(getBaseSelectSql()); sqlBuild.append(" where data_type = " + ChannelDataType.GB28181); if (params.get("dataDeviceId") != null) { sqlBuild.append(" AND dc.data_device_id = #{dataDeviceId} "); } if (params.get("businessGroupId") != null ) { sqlBuild.append(" AND coalesce(dc.gb_business_group_id, dc.business_group_id)=#{businessGroupId} AND coalesce(dc.gb_parent_id, dc.parent_id) is null"); }else if (params.get("parentChannelId") != null ) { sqlBuild.append(" AND coalesce(dc.gb_parent_id, dc.parent_id)=#{parentChannelId}"); } if (params.get("civilCode") != null ) { sqlBuild.append(" AND (coalesce(dc.gb_civil_code, dc.civil_code) = #{civilCode} " + "OR (LENGTH(coalesce(dc.gb_device_id, dc.device_id))=LENGTH(#{civilCode}) + 2) AND coalesce(dc.gb_device_id, dc.device_id) LIKE concat(#{civilCode},'%'))"); } if (params.get("query") != null && !ObjectUtils.isEmpty(params.get("query"))) { sqlBuild.append(" AND (coalesce(dc.gb_device_id, dc.device_id) LIKE concat('%',#{query},'%') escape '/'" + " OR coalesce(dc.gb_name, dc.name) LIKE concat('%',#{query},'%') escape '/'"); if (params.get("queryParent") != null && (Boolean) params.get("queryParent")) { sqlBuild.append(" OR d.device_id LIKE concat('%',#{query},'%') escape '/'"); sqlBuild.append(" OR coalesce(d.custom_name, d.name) LIKE concat('%',#{query},'%') escape '/'"); } sqlBuild.append(")"); } if (params.get("hasStream") != null && (Boolean) params.get("hasStream")) { sqlBuild.append(" AND dc.stream_id IS NOT NULL"); } if (params.get("online") != null && (Boolean)params.get("online")) { sqlBuild.append(" AND coalesce(gb_status, status) = 'ON'"); } if (params.get("online") != null && !(Boolean)params.get("online")) { sqlBuild.append(" AND coalesce(gb_status, status) = 'OFF'"); } if (params.get("hasSubChannel") != null && (Boolean)params.get("hasSubChannel")) { sqlBuild.append(" AND dc.sub_count > 0"); } if (params.get("hasSubChannel") != null && !(Boolean)params.get("hasSubChannel")) { sqlBuild.append(" AND dc.sub_count = 0"); } List channelIds = (List)params.get("channelIds"); if (channelIds != null && !channelIds.isEmpty()) { sqlBuild.append(" AND dc.device_id in ("); boolean first = true; for (String id : channelIds) { if (!first) { sqlBuild.append(","); } sqlBuild.append(id); first = false; } sqlBuild.append(" )"); } sqlBuild.append(" ORDER BY d.device_id, dc.device_id"); return sqlBuild.toString(); } public String queryChannelsByDeviceDbId(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(getBaseSelectSql()); sqlBuild.append(" where data_type = " + ChannelDataType.GB28181 + " and dc.data_device_id = #{dataDeviceId}"); return sqlBuild.toString(); } public String queryAllChannels(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(getBaseSelectSql()); sqlBuild.append(" where data_type = " + ChannelDataType.GB28181 + " and dc.data_device_id = #{dataDeviceId}"); return sqlBuild.toString(); } public String getOne(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(getBaseSelectSql()); sqlBuild.append(" where dc.id=#{id}"); return sqlBuild.toString(); } public String getOneByDeviceId(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(getBaseSelectSql()); sqlBuild.append(" where data_type = " + ChannelDataType.GB28181 + " and dc.data_device_id=#{dataDeviceId} and coalesce(dc.gb_device_id, dc.device_id) = #{channelId}"); return sqlBuild.toString(); } public String queryByDeviceId(Map params ){ return getBaseSelectSql() + " where data_type = " + ChannelDataType.GB28181 + " and channel_type = 0 and coalesce(gb_device_id, device_id) = #{gbDeviceId}"; } public String queryById(Map params ){ return getBaseSelectSql() + " where data_type = " + ChannelDataType.GB28181 + " and channel_type = 0 and id = #{gbId}"; } public String queryList(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(getBaseSelectSql()); sqlBuild.append(" where channel_type = 0 and data_type = " + ChannelDataType.GB28181); if (params.get("query") != null) { sqlBuild.append(" AND (coalesce(gb_device_id, device_id) LIKE concat('%',#{query},'%')" + " OR coalesce(gb_name, name) LIKE concat('%',#{query},'%') )") ; } if (params.get("online") != null && (Boolean)params.get("online")) { sqlBuild.append(" AND coalesce(gb_status, status) = 'ON'"); } if (params.get("online") != null && !(Boolean)params.get("online")) { sqlBuild.append(" AND coalesce(gb_status, status) = 'OFF'"); } if (params.get("hasCivilCode") != null && (Boolean)params.get("hasCivilCode")) { sqlBuild.append(" AND coalesce(gb_civil_code, civil_code) is not null"); } if (params.get("hasCivilCode") != null && !(Boolean)params.get("hasCivilCode")) { sqlBuild.append(" AND coalesce(gb_civil_code, civil_code) is null"); } if (params.get("hasGroup") != null && (Boolean)params.get("hasGroup")) { sqlBuild.append(" AND coalesce(gb_parent_id, parent_id) is not null"); } if (params.get("hasGroup") != null && !(Boolean)params.get("hasGroup")) { sqlBuild.append(" AND coalesce(gb_parent_id, parent_id) is null"); } return sqlBuild.toString(); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/event/EventPublisher.java ================================================ package com.genersoft.iot.vmp.gb28181.event; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm; import com.genersoft.iot.vmp.gb28181.bean.MobilePosition; import com.genersoft.iot.vmp.gb28181.bean.Platform; import com.genersoft.iot.vmp.gb28181.event.alarm.AlarmEvent; import com.genersoft.iot.vmp.gb28181.event.channel.ChannelEvent; import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent; import com.genersoft.iot.vmp.gb28181.event.subscribe.mobilePosition.MobilePositionEvent; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.event.mediaServer.MediaServerOfflineEvent; import com.genersoft.iot.vmp.media.event.mediaServer.MediaServerOnlineEvent; import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Component; import java.util.*; /** * @description:Event事件通知推送器,支持推送在线事件、离线事件 * @author: swwheihei * @date: 2020年5月6日 上午11:30:50 */ @Slf4j @Component public class EventPublisher { @Autowired private ApplicationEventPublisher applicationEventPublisher; @Autowired private UserSetting userSetting; @Autowired private IRedisRpcService redisRpcService; /** * 设备报警事件 * @param deviceAlarm */ public void deviceAlarmEventPublish(DeviceAlarm deviceAlarm) { AlarmEvent alarmEvent = new AlarmEvent(this); alarmEvent.setAlarmInfo(deviceAlarm); applicationEventPublisher.publishEvent(alarmEvent); } public void mediaServerOfflineEventPublish(MediaServer mediaServer){ MediaServerOfflineEvent outEvent = new MediaServerOfflineEvent(this); outEvent.setMediaServer(mediaServer); applicationEventPublisher.publishEvent(outEvent); } public void mediaServerOnlineEventPublish(MediaServer mediaServer) { MediaServerOnlineEvent outEvent = new MediaServerOnlineEvent(this); outEvent.setMediaServer(mediaServer); applicationEventPublisher.publishEvent(outEvent); } public void channelEventPublish(CommonGBChannel commonGBChannel, ChannelEvent.ChannelEventMessageType type) { channelEventPublish(Collections.singletonList(commonGBChannel), type); } public void channelEventPublishForUpdate(CommonGBChannel commonGBChannel, CommonGBChannel deviceChannelForOld) { log.info("[通道改变内部分发-更新] {}", commonGBChannel.getGbDeviceId()); ChannelEvent channelEvent = ChannelEvent.getInstanceForUpdate(this, Collections.singletonList(commonGBChannel), Collections.singletonList(deviceChannelForOld)); applicationEventPublisher.publishEvent(channelEvent); } public void channelEventPublishForUpdate(List channelList, List channelListForOld) { log.info("[通道改变内部分发-更新] 数量: {}", channelList.size()); ChannelEvent channelEvent = ChannelEvent.getInstanceForUpdate(this, channelList, channelListForOld); applicationEventPublisher.publishEvent(channelEvent); } public void channelEventPublish(List channelList, ChannelEvent.ChannelEventMessageType type) { log.info("[通道改变内部分发-{}] 数量: {}", type, channelList.size()); ChannelEvent channelEvent = ChannelEvent.getInstance(this, type, channelList); applicationEventPublisher.publishEvent(channelEvent); } public void catalogEventPublish(Platform platform, CommonGBChannel deviceChannel, String type) { catalogEventPublish(platform, Collections.singletonList(deviceChannel), type); } public void catalogEventPublish(Platform platform, List deviceChannels, String type) { if (platform != null && !userSetting.getServerId().equals(platform.getServerId())) { log.info("[国标级联] 目录状态推送, 此上级平台由其他服务处理,消息已经忽略"); return; } CatalogEvent outEvent = new CatalogEvent(this); List channels = new ArrayList<>(); if (deviceChannels.size() > 1) { // 数据去重 Set gbIdSet = new HashSet<>(); for (CommonGBChannel deviceChannel : deviceChannels) { if (deviceChannel != null && deviceChannel.getGbDeviceId() != null && !gbIdSet.contains(deviceChannel.getGbDeviceId())) { gbIdSet.add(deviceChannel.getGbDeviceId()); channels.add(deviceChannel); } } }else { channels = deviceChannels; } outEvent.setChannels(channels); outEvent.setType(type); if (platform != null) { outEvent.setPlatform(platform); } applicationEventPublisher.publishEvent(outEvent); } public void mobilePositionEventPublish(MobilePosition mobilePosition) { MobilePositionEvent event = new MobilePositionEvent(this); event.setMobilePosition(mobilePosition); applicationEventPublisher.publishEvent(event); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/event/MessageSubscribe.java ================================================ package com.genersoft.iot.vmp.gb28181.event; import com.genersoft.iot.vmp.gb28181.event.sip.MessageEvent; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.DelayQueue; /** * @author lin */ @Slf4j @Component public class MessageSubscribe { private final Map> subscribes = new ConcurrentHashMap<>(); private final DelayQueue> delayQueue = new DelayQueue<>(); @Scheduled(fixedDelay = 200) //每200毫秒执行 public void execute(){ while (!delayQueue.isEmpty()) { try { MessageEvent take = delayQueue.take(); // 出现超时异常 if(take.getCallback() != null) { take.getCallback().run(ErrorCode.ERROR486.getCode(), "消息超时未回复", null); } subscribes.remove(take.getKey()); } catch (InterruptedException e) { throw new RuntimeException(e); } } } public void addSubscribe(MessageEvent event) { MessageEvent messageEvent = subscribes.get(event.getKey()); if (messageEvent != null) { subscribes.remove(event.getKey()); delayQueue.remove(messageEvent); } subscribes.put(event.getKey(), event); delayQueue.offer(event); } public MessageEvent getSubscribe(String key) { return subscribes.get(key); } public void removeSubscribe(String key) { if(key == null){ return; } MessageEvent messageEvent = subscribes.get(key); if (messageEvent != null) { subscribes.remove(key); delayQueue.remove(messageEvent); } } public boolean isEmpty(){ return subscribes.isEmpty(); } public Integer size() { return subscribes.size(); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/event/SipSubscribe.java ================================================ package com.genersoft.iot.vmp.gb28181.event; import com.genersoft.iot.vmp.gb28181.bean.DeviceNotFoundEvent; import com.genersoft.iot.vmp.gb28181.event.sip.SipEvent; import gov.nist.javax.sip.message.SIPRequest; import gov.nist.javax.sip.message.SIPResponse; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.ObjectUtils; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import javax.sip.DialogTerminatedEvent; import javax.sip.ResponseEvent; import javax.sip.TimeoutEvent; import javax.sip.TransactionTerminatedEvent; import javax.sip.header.WarningHeader; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.DelayQueue; /** * @author lin */ @Slf4j @Component public class SipSubscribe { private final Map subscribes = new ConcurrentHashMap<>(); private final DelayQueue delayQueue = new DelayQueue<>(); @Scheduled(fixedDelay = 200) //每200毫秒执行 public void execute(){ while (!delayQueue.isEmpty()) { try { SipEvent take = delayQueue.take(); // 出现超时异常 if(take.getErrorEvent() != null) { EventResult eventResult = new EventResult<>(); eventResult.type = EventResultType.timeout; eventResult.msg = "消息超时未回复"; eventResult.statusCode = -1024; take.getErrorEvent().response(eventResult); } subscribes.remove(take.getKey()); } catch (InterruptedException e) { throw new RuntimeException(e); } } } public interface Event { void response(EventResult eventResult); } /** * */ public enum EventResultType{ // 超时 timeout, // 回复 response, // 事务已结束 transactionTerminated, // 会话已结束 dialogTerminated, // 设备未找到 deviceNotFoundEvent, // 消息发送失败 cmdSendFailEvent, // 消息发送失败 failedToGetPort, // 收到失败的回复 failedResult } public static class EventResult{ public int statusCode; public EventResultType type; public String msg; public String callId; public T event; public EventResult() { } public EventResult(T event) { this.event = event; if (event instanceof ResponseEvent) { ResponseEvent responseEvent = (ResponseEvent)event; SIPResponse response = (SIPResponse)responseEvent.getResponse(); this.type = EventResultType.response; if (response != null) { WarningHeader warningHeader = (WarningHeader)response.getHeader(WarningHeader.NAME); if (warningHeader != null && !ObjectUtils.isEmpty(warningHeader.getText())) { this.msg = ""; if (warningHeader.getCode() > 0) { this.msg += warningHeader.getCode() + ":"; } if (warningHeader.getAgent() != null) { this.msg += warningHeader.getCode() + ":"; } if (warningHeader.getText() != null) { this.msg += warningHeader.getText(); } }else { this.msg = response.getReasonPhrase(); } this.statusCode = response.getStatusCode(); this.callId = response.getCallIdHeader().getCallId(); } }else if (event instanceof TimeoutEvent) { TimeoutEvent timeoutEvent = (TimeoutEvent)event; this.type = EventResultType.timeout; this.msg = "消息超时未回复"; this.statusCode = -1024; if (timeoutEvent.isServerTransaction()) { this.callId = ((SIPRequest)timeoutEvent.getServerTransaction().getRequest()).getCallIdHeader().getCallId(); }else { this.callId = ((SIPRequest)timeoutEvent.getClientTransaction().getRequest()).getCallIdHeader().getCallId(); } }else if (event instanceof TransactionTerminatedEvent) { TransactionTerminatedEvent transactionTerminatedEvent = (TransactionTerminatedEvent)event; this.type = EventResultType.transactionTerminated; this.msg = "事务已结束"; this.statusCode = -1024; if (transactionTerminatedEvent.isServerTransaction()) { this.callId = ((SIPRequest)transactionTerminatedEvent.getServerTransaction().getRequest()).getCallIdHeader().getCallId(); }else { this.callId = ((SIPRequest)transactionTerminatedEvent.getClientTransaction().getRequest()).getCallIdHeader().getCallId(); } }else if (event instanceof DialogTerminatedEvent) { DialogTerminatedEvent dialogTerminatedEvent = (DialogTerminatedEvent)event; this.type = EventResultType.dialogTerminated; this.msg = "会话已结束"; this.statusCode = -1024; this.callId = dialogTerminatedEvent.getDialog().getCallId().getCallId(); }else if (event instanceof DeviceNotFoundEvent) { this.type = EventResultType.deviceNotFoundEvent; this.msg = "设备未找到"; this.statusCode = -1024; this.callId = ((DeviceNotFoundEvent) event).getCallId(); } } } public void addSubscribe(String key, SipEvent event) { SipEvent sipEvent = subscribes.get(key); if (sipEvent != null) { subscribes.remove(key); delayQueue.remove(sipEvent); } subscribes.put(key, event); delayQueue.offer(event); } public SipEvent getSubscribe(String key) { return subscribes.get(key); } public void removeSubscribe(String key) { if(key == null){ return; } SipEvent sipEvent = subscribes.get(key); if (sipEvent != null) { subscribes.remove(key); delayQueue.remove(sipEvent); } } public boolean isEmpty(){ return subscribes.isEmpty(); } public Integer size() { return subscribes.size(); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/event/alarm/AlarmEvent.java ================================================ package com.genersoft.iot.vmp.gb28181.event.alarm; import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm; import org.springframework.context.ApplicationEvent; import java.io.Serial; /** * @description: 报警事件 * @author: lawrencehj * @data: 2021-01-20 */ public class AlarmEvent extends ApplicationEvent { @Serial private static final long serialVersionUID = 1L; public AlarmEvent(Object source) { super(source); } private DeviceAlarm deviceAlarm; public DeviceAlarm getAlarmInfo() { return deviceAlarm; } public void setAlarmInfo(DeviceAlarm deviceAlarm) { this.deviceAlarm = deviceAlarm; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/event/alarm/AlarmEventListener.java ================================================ package com.genersoft.iot.vmp.gb28181.event.alarm; import com.genersoft.iot.vmp.gb28181.session.SseSessionManager; import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; import org.springframework.context.ApplicationListener; import org.springframework.stereotype.Component; import jakarta.annotation.Resource; /** * 报警事件监听器. * * @author lawrencehj * @author xiaoQQya * @since 2021/01/20 */ @Slf4j @Component public class AlarmEventListener implements ApplicationListener { @Resource private SseSessionManager sseSessionManager; @Override public void onApplicationEvent(@NotNull AlarmEvent event) { if (log.isDebugEnabled()) { log.debug("设备报警事件触发, deviceId: {}, {}", event.getAlarmInfo().getDeviceId(), event.getAlarmInfo().getAlarmDescription()); } sseSessionManager.sendForAll("message", event.getAlarmInfo()); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/event/channel/ChannelEvent.java ================================================ package com.genersoft.iot.vmp.gb28181.event.channel; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import lombok.Getter; import lombok.Setter; import org.springframework.context.ApplicationEvent; import java.io.Serial; import java.util.List; /** * 通道事件 */ @Setter @Getter public class ChannelEvent extends ApplicationEvent { @Serial private static final long serialVersionUID = 1L; public ChannelEvent(Object source) { super(source); } private List channels; private List oldChannels; private ChannelEventMessageType messageType; public enum ChannelEventMessageType { ADD, UPDATE, DEL, ON, OFF, VLOST, DEFECT } public static ChannelEvent getInstance(Object source, ChannelEventMessageType messageType, List channelList) { ChannelEvent channelEvent = new ChannelEvent(source); channelEvent.setMessageType(messageType); channelEvent.setChannels(channelList); return channelEvent; } public static ChannelEvent getInstanceForUpdate(Object source, List channelList, List channelListForOld) { ChannelEvent channelEvent = new ChannelEvent(source); channelEvent.setMessageType(ChannelEventMessageType.UPDATE); channelEvent.setChannels(channelList); channelEvent.setOldChannels(channelListForOld); return channelEvent; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/event/record/RecordInfoEndEvent.java ================================================ package com.genersoft.iot.vmp.gb28181.event.record; import com.genersoft.iot.vmp.gb28181.bean.RecordInfo; import lombok.Getter; import lombok.Setter; import org.springframework.context.ApplicationEvent; import java.io.Serial; /** * @description: 录像查询结束时间 * @author: pan * @data: 2022-02-23 */ @Setter @Getter public class RecordInfoEndEvent extends ApplicationEvent { @Serial private static final long serialVersionUID = 1L; public RecordInfoEndEvent(Object source) { super(source); } private RecordInfo recordInfo; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/event/record/RecordInfoEvent.java ================================================ package com.genersoft.iot.vmp.gb28181.event.record; import com.genersoft.iot.vmp.gb28181.bean.RecordInfo; import lombok.Getter; import lombok.Setter; import org.springframework.context.ApplicationEvent; import java.io.Serial; /** * @description: 录像查询结束时间 * @author: pan * @data: 2022-02-23 */ @Setter @Getter public class RecordInfoEvent extends ApplicationEvent { @Serial private static final long serialVersionUID = 1L; public RecordInfoEvent(Object source) { super(source); } private RecordInfo recordInfo; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/event/record/RecordInfoEventListener.java ================================================ package com.genersoft.iot.vmp.gb28181.event.record; import com.genersoft.iot.vmp.gb28181.bean.RecordInfo; import lombok.extern.slf4j.Slf4j; import org.springframework.context.ApplicationListener; import org.springframework.stereotype.Component; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * @description: 录像查询结束事件 * @author: pan * @data: 2022-02-23 */ @Slf4j @Component public class RecordInfoEventListener implements ApplicationListener { private final Map handlerMap = new ConcurrentHashMap<>(); public interface RecordEndEventHandler{ void handler(RecordInfo recordInfo); } @Override public void onApplicationEvent(RecordInfoEvent event) { String deviceId = event.getRecordInfo().getDeviceId(); String channelId = event.getRecordInfo().getChannelId(); int count = event.getRecordInfo().getCount(); int sumNum = event.getRecordInfo().getSumNum(); log.info("录像查询事件触发,deviceId:{}, channelId: {}, 录像数量{}/{}条", event.getRecordInfo().getDeviceId(), event.getRecordInfo().getChannelId(), count,sumNum); if (!handlerMap.isEmpty()) { RecordEndEventHandler handler = handlerMap.get(deviceId + channelId); log.info("录像查询事件触发, 发送订阅,deviceId:{}, channelId: {}", event.getRecordInfo().getDeviceId(), event.getRecordInfo().getChannelId()); if (handler !=null){ handler.handler(event.getRecordInfo()); if (count ==sumNum){ handlerMap.remove(deviceId + channelId); } } } } /** * 添加 */ public void addEndEventHandler(String device, String channelId, RecordEndEventHandler recordEndEventHandler) { log.info("录像查询事件添加监听,deviceId:{}, channelId: {}", device, channelId); handlerMap.put(device + channelId, recordEndEventHandler); } /** * 添加 */ public void delEndEventHandler(String device, String channelId) { log.info("录像查询事件移除监听,deviceId:{}, channelId: {}", device, channelId); handlerMap.remove(device + channelId); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/event/sip/MessageEvent.java ================================================ package com.genersoft.iot.vmp.gb28181.event.sip; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import lombok.Data; import org.jetbrains.annotations.NotNull; import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit; @Data public class MessageEvent implements Delayed { /** * 超时时间(单位: 毫秒) */ private long delay; private String cmdType; private String sn; private String deviceId; private String result; private T t; private ErrorCallback callback; @Override public long getDelay(@NotNull TimeUnit unit) { return unit.convert(delay - System.currentTimeMillis(), TimeUnit.MILLISECONDS); } @Override public int compareTo(@NotNull Delayed o) { return (int) (this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS)); } public String getKey(){ return cmdType + sn; } public static MessageEvent getInstance(String cmdType, String sn, String deviceId, Long delay, ErrorCallback callback){ MessageEvent messageEvent = new MessageEvent<>(); messageEvent.cmdType = cmdType; messageEvent.sn = sn; messageEvent.deviceId = deviceId; messageEvent.callback = callback; if (delay == null) { messageEvent.delay = System.currentTimeMillis() + 1000; }else { messageEvent.delay = System.currentTimeMillis() + delay; } return messageEvent; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/event/sip/SipEvent.java ================================================ package com.genersoft.iot.vmp.gb28181.event.sip; import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo; import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; import lombok.Data; import org.jetbrains.annotations.NotNull; import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit; @Data public class SipEvent implements Delayed { private String key; /** * 成功的回调 */ private SipSubscribe.Event okEvent; /** * 错误的回调,包括超时 */ private SipSubscribe.Event errorEvent; /** * 超时时间(单位: 毫秒) */ private long delay; private SipTransactionInfo sipTransactionInfo; public static SipEvent getInstance(String key, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent, long delay) { SipEvent sipEvent = new SipEvent(); sipEvent.setKey(key); sipEvent.setOkEvent(okEvent); sipEvent.setErrorEvent(errorEvent); sipEvent.setDelay(System.currentTimeMillis() + delay); return sipEvent; } @Override public long getDelay(@NotNull TimeUnit unit) { return unit.convert(delay - System.currentTimeMillis(), TimeUnit.MILLISECONDS); } @Override public int compareTo(@NotNull Delayed o) { return (int) (this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS)); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/event/subscribe/catalog/CatalogEvent.java ================================================ package com.genersoft.iot.vmp.gb28181.event.subscribe.catalog; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import com.genersoft.iot.vmp.gb28181.bean.Platform; import lombok.Getter; import lombok.Setter; import org.springframework.context.ApplicationEvent; import java.util.List; @Setter @Getter public class CatalogEvent extends ApplicationEvent { public CatalogEvent(Object source) { super(source); } /** * 上线 */ public static final String ON = "ON"; /** * 离线 */ public static final String OFF = "OFF"; /** * 视频丢失 */ public static final String VLOST = "VLOST"; /** * 故障 */ public static final String DEFECT = "DEFECT"; /** * 增加 */ public static final String ADD = "ADD"; /** * 删除 */ public static final String DEL = "DEL"; /** * 更新 */ public static final String UPDATE = "UPDATE"; private List channels; private String type; private Platform platform; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/event/subscribe/catalog/CatalogEventLister.java ================================================ package com.genersoft.iot.vmp.gb28181.event.subscribe.catalog; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import com.genersoft.iot.vmp.gb28181.bean.Platform; import com.genersoft.iot.vmp.gb28181.bean.SubscribeHolder; import com.genersoft.iot.vmp.gb28181.bean.SubscribeInfo; import com.genersoft.iot.vmp.gb28181.service.IPlatformChannelService; import com.genersoft.iot.vmp.gb28181.service.IPlatformService; import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationListener; import javax.sip.InvalidArgumentException; import javax.sip.SipException; import java.text.ParseException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * catalog事件 */ @Slf4j //@Component public class CatalogEventLister implements ApplicationListener { @Autowired private IPlatformService platformService; @Autowired private IPlatformChannelService platformChannelService; @Autowired private ISIPCommanderForPlatform sipCommanderFroPlatform; @Autowired private SubscribeHolder subscribeHolder; @Autowired private UserSetting userSetting; @Override public void onApplicationEvent(CatalogEvent event) { SubscribeInfo subscribe = null; Platform parentPlatform = null; log.info("[Catalog事件: {}]通道数量: {}", event.getType(), event.getChannels().size()); Map> platformMap = new HashMap<>(); Map channelMap = new HashMap<>(); if (event.getPlatform() != null) { parentPlatform = event.getPlatform(); if (parentPlatform.getServerGBId() == null) { log.info("[Catalog事件: {}] 平台服务国标编码未找到", event.getType()); return; } subscribe = subscribeHolder.getCatalogSubscribe(parentPlatform.getServerGBId()); if (subscribe == null) { log.info("[Catalog事件: {}] 未订阅目录事件", event.getType()); return; } }else { List allPlatform = platformService.queryAll(userSetting.getServerId()); // 获取所用订阅 List platforms = subscribeHolder.getAllCatalogSubscribePlatform(allPlatform); if (event.getChannels() != null) { if (!platforms.isEmpty()) { for (CommonGBChannel deviceChannel : event.getChannels()) { List parentPlatformsForGB = platformChannelService.queryPlatFormListByChannelDeviceId( deviceChannel.getGbId(), platforms); platformMap.put(deviceChannel.getGbDeviceId(), parentPlatformsForGB); channelMap.put(deviceChannel.getGbDeviceId(), deviceChannel); } }else { log.info("[Catalog事件: {}] 未订阅目录事件", event.getType()); } }else { log.info("[Catalog事件: {}] 事件内通道数为0", event.getType()); } } switch (event.getType()) { case CatalogEvent.ON: case CatalogEvent.OFF: case CatalogEvent.DEL: if (parentPlatform != null) { List channels = new ArrayList<>(); if (event.getChannels() != null) { channels.addAll(event.getChannels()); } if (!channels.isEmpty()) { log.info("[Catalog事件: {}]平台:{},影响通道{}个", event.getType(), parentPlatform.getServerGBId(), channels.size()); try { sipCommanderFroPlatform.sendNotifyForCatalogOther(event.getType(), parentPlatform, channels, subscribe, null); } catch (InvalidArgumentException | ParseException | NoSuchFieldException | SipException | IllegalAccessException e) { log.error("[命令发送失败] 国标级联 Catalog通知: {}", e.getMessage()); } } }else if (!platformMap.keySet().isEmpty()) { for (String serverGbId : platformMap.keySet()) { List platformList = platformMap.get(serverGbId); if (platformList != null && !platformList.isEmpty()) { for (Platform platform : platformList) { SubscribeInfo subscribeInfo = subscribeHolder.getCatalogSubscribe(platform.getServerGBId()); if (subscribeInfo == null) { continue; } log.info("[Catalog事件: {}]平台:{},影响通道{}", event.getType(), platform.getServerGBId(), serverGbId); List deviceChannelList = new ArrayList<>(); CommonGBChannel deviceChannel = new CommonGBChannel(); deviceChannel.setGbDeviceId(serverGbId); deviceChannelList.add(deviceChannel); try { sipCommanderFroPlatform.sendNotifyForCatalogOther(event.getType(), platform, deviceChannelList, subscribeInfo, null); } catch (InvalidArgumentException | ParseException | NoSuchFieldException | SipException | IllegalAccessException e) { log.error("[命令发送失败] 国标级联 Catalog通知: {}", e.getMessage()); } } }else { log.info("[Catalog事件: {}] 未找到上级平台: {}", event.getType(), serverGbId); } } } break; case CatalogEvent.VLOST: break; case CatalogEvent.DEFECT: break; case CatalogEvent.ADD: case CatalogEvent.UPDATE: if (parentPlatform != null) { List deviceChannelList = new ArrayList<>(); if (event.getChannels() != null) { deviceChannelList.addAll(event.getChannels()); } if (!deviceChannelList.isEmpty()) { log.info("[Catalog事件: {}]平台:{},影响通道{}个", event.getType(), parentPlatform.getServerGBId(), deviceChannelList.size()); try { sipCommanderFroPlatform.sendNotifyForCatalogAddOrUpdate(event.getType(), parentPlatform, deviceChannelList, subscribe, null); } catch (InvalidArgumentException | ParseException | NoSuchFieldException | SipException | IllegalAccessException e) { log.error("[命令发送失败] 国标级联 Catalog通知: {}", e.getMessage()); } } }else if (!platformMap.keySet().isEmpty()) { for (String gbId : platformMap.keySet()) { List parentPlatforms = platformMap.get(gbId); if (parentPlatforms != null && !parentPlatforms.isEmpty()) { for (Platform platform : parentPlatforms) { SubscribeInfo subscribeInfo = subscribeHolder.getCatalogSubscribe(platform.getServerGBId()); if (subscribeInfo == null) { continue; } log.info("[Catalog事件: {}]平台:{},影响通道{}", event.getType(), platform.getServerGBId(), gbId); List channelList = new ArrayList<>(); CommonGBChannel deviceChannel = channelMap.get(gbId); channelList.add(deviceChannel); try { sipCommanderFroPlatform.sendNotifyForCatalogAddOrUpdate(event.getType(), platform, channelList, subscribeInfo, null); } catch (InvalidArgumentException | ParseException | NoSuchFieldException | SipException | IllegalAccessException e) { log.error("[命令发送失败] 国标级联 Catalog通知: {}", e.getMessage()); } } } } } break; default: break; } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/event/subscribe/mobilePosition/MobilePositionEvent.java ================================================ package com.genersoft.iot.vmp.gb28181.event.subscribe.mobilePosition; import com.genersoft.iot.vmp.gb28181.bean.MobilePosition; import lombok.Getter; import lombok.Setter; import org.springframework.context.ApplicationEvent; public class MobilePositionEvent extends ApplicationEvent { public MobilePositionEvent(Object source) { super(source); } @Getter @Setter private MobilePosition mobilePosition; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/event/subscribe/mobilePosition/MobilePositionEventLister.java ================================================ package com.genersoft.iot.vmp.gb28181.event.subscribe.mobilePosition; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import com.genersoft.iot.vmp.gb28181.bean.Platform; import com.genersoft.iot.vmp.gb28181.bean.SubscribeHolder; import com.genersoft.iot.vmp.gb28181.bean.SubscribeInfo; import com.genersoft.iot.vmp.gb28181.service.IPlatformChannelService; import com.genersoft.iot.vmp.gb28181.service.IPlatformService; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderForPlatform; import com.genersoft.iot.vmp.service.bean.GPSMsgInfo; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationListener; import org.springframework.stereotype.Component; import javax.sip.InvalidArgumentException; import javax.sip.SipException; import java.text.ParseException; import java.util.List; /** * 移动位置通知消息转发 */ @Slf4j @Component public class MobilePositionEventLister implements ApplicationListener { @Autowired private IPlatformService platformService; @Autowired private IPlatformChannelService platformChannelService; @Autowired private SIPCommanderForPlatform sipCommanderForPlatform; @Autowired private SubscribeHolder subscribeHolder; @Autowired private UserSetting userSetting; @Override public void onApplicationEvent(MobilePositionEvent event) { if (event.getMobilePosition().getChannelId() == 0) { return; } List allPlatforms = platformService.queryAll(userSetting.getServerId()); // 获取所用订阅 List platforms = subscribeHolder.getAllMobilePositionSubscribePlatform(allPlatforms); if (platforms.isEmpty()) { return; } List platformsForGB = platformChannelService.queryPlatFormListByChannelDeviceId(event.getMobilePosition().getChannelId(), platforms); for (Platform platform : platformsForGB) { if (log.isDebugEnabled()){ log.debug("[向上级发送MobilePosition] 通道:{},平台:{}, 位置: {}:{}", event.getMobilePosition().getChannelId(), platform.getServerGBId(), event.getMobilePosition().getLongitude(), event.getMobilePosition().getLatitude()); } SubscribeInfo subscribe = subscribeHolder.getMobilePositionSubscribe(platform.getServerGBId()); try { GPSMsgInfo gpsMsgInfo = GPSMsgInfo.getInstance(event.getMobilePosition()); // 获取通道编号 CommonGBChannel commonGBChannel = platformChannelService.queryChannelByPlatformIdAndChannelId(platform.getId(), event.getMobilePosition().getChannelId()); sipCommanderForPlatform.sendNotifyMobilePosition(platform, gpsMsgInfo, commonGBChannel, subscribe); } catch (InvalidArgumentException | ParseException | NoSuchFieldException | SipException | IllegalAccessException e) { log.error("[命令发送失败] 国标级联 Catalog通知: {}", e.getMessage()); } } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/service/IDeviceAlarmService.java ================================================ package com.genersoft.iot.vmp.gb28181.service; import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm; import com.github.pagehelper.PageInfo; import java.util.List; /** * 报警相关业务处理 */ public interface IDeviceAlarmService { /** * 根据多个添加获取报警列表 * @param page 当前页 * @param count 每页数量 * @param deviceId 设备id * @param alarmPriority 报警级别, 1为一级警情, 2为二级警情, 3为三级警情, 4为四级 警情- * @param alarmMethod 报警方式 , 1为电话报警, 2为设备报警, 3为短信报警, 4为 GPS报警, 5为视频报警, 6为设备故障报警, * 7其他报警;可以为直接组合如12为电话报警或 设备报警- * @param alarmType 报警类型 * @param startTime 开始时间 * @param endTime 结束时间 * @return 报警列表 */ PageInfo getAllAlarm(int page, int count, String deviceId, String channelId, String alarmPriority, String alarmMethod, String alarmType, String startTime, String endTime); /** * 添加一个报警 * @param deviceAlarm 添加报警 */ void add(DeviceAlarm deviceAlarm); /** * 清空时间以前的报警 * @param id 数据库id * @param deviceIdList 制定需要清理的设备id * @param time 不写时间则清空所有时间的 */ int clearAlarmBeforeTime(Integer id, List deviceIdList, String time); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/service/IDeviceChannelService.java ================================================ package com.genersoft.iot.vmp.gb28181.service; import com.genersoft.iot.vmp.common.enums.DeviceControlType; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.vmanager.bean.ResourceBaseInfo; import com.genersoft.iot.vmp.web.gb28181.dto.DeviceChannelExtend; import com.github.pagehelper.PageInfo; import jakarta.validation.constraints.NotNull; import org.dom4j.Element; import java.util.List; /** * 国标通道业务类 * @author lin */ public interface IDeviceChannelService { /** * 批量添加设备通道 */ int updateChannels(Device device, List channels); /** * 获取统计信息 * @return */ ResourceBaseInfo getOverview(); /** * 获取一个通道 */ DeviceChannel getOne(String deviceId, String channelId); DeviceChannel getOneForSource(String deviceId, String channelId); /** * 修改通道的码流类型 */ void updateChannelStreamIdentification(DeviceChannel channel); List queryChaneListByDeviceId(String deviceId); void updateChannelGPS(Device device, DeviceChannel deviceChannel, MobilePosition mobilePosition); void startPlay(Integer channelId, String stream); void stopPlay(Integer channelId); void online(DeviceChannel channel); void offline(DeviceChannel channel); void deleteForNotify(DeviceChannel channel); void cleanChannelsForDevice(int deviceId); boolean resetChannels(int deviceDbId, List deviceChannels); PageInfo getSubChannels(int deviceDbId, String channelId, String query, Boolean channelType, Boolean online, int page, int count); List queryChannelExtendsByDeviceId(String deviceId, List channelIds, Boolean online); PageInfo queryChannelsByDeviceId(String deviceId, String query, Boolean channelType, Boolean online, int page, int count); PageInfo queryChannels(String query, Boolean queryParent, Boolean channelType, Boolean online, Boolean hasStream, int page, int count); List queryDeviceWithAsMessageChannel(); DeviceChannel getRawChannel(int id); DeviceChannel getOneById(Integer channelId); DeviceChannel getOneForSourceById(Integer channelId); DeviceChannel getBroadcastChannel(int deviceDbId); void changeAudio(Integer channelId, Boolean audio); void updateChannelStatusForNotify(DeviceChannel channel); void addChannel(DeviceChannel channel); void updateChannelForNotify(DeviceChannel channel); DeviceChannel getOneForSource(int deviceDbId, String channelId); DeviceChannel getOneBySourceId(int deviceDbId, String channelId); List queryChaneIdListByDeviceDbIds(List deviceDbId); void handlePtzCmd(@NotNull Integer dataDeviceId, @NotNull Integer gbId, Element rootElement, DeviceControlType type, ErrorCallback callback); void queryRecordInfo(Device device, DeviceChannel channel, String startTime, String endTime, ErrorCallback object); void queryRecordInfo(CommonGBChannel channel, String startTime, String endTime, ErrorCallback object); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/service/IDeviceService.java ================================================ package com.genersoft.iot.vmp.gb28181.service; import com.genersoft.iot.vmp.common.CommonCallback; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.vmanager.bean.ResourceBaseInfo; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import com.github.pagehelper.PageInfo; import org.springframework.transaction.annotation.Transactional; import java.util.List; /** * 设备相关业务处理 * @author lin */ public interface IDeviceService { /** * 设备上线 * @param device 设备信息 */ void online(Device device, SipTransactionInfo sipTransactionInfo); /** * 设备下线 * @param deviceId 设备编号 */ void offline(String deviceId, String reason, boolean check); /** * 添加目录订阅 * @param device 设备信息 * @return 布尔 */ boolean addCatalogSubscribe(Device device, SipTransactionInfo transactionInfo); /** * 移除目录订阅 * @param device 设备信息 * @return 布尔 */ boolean removeCatalogSubscribe(Device device, CommonCallback callback); /** * 添加移动位置订阅 * @param device 设备信息 * @return 布尔 */ boolean addMobilePositionSubscribe(Device device, SipTransactionInfo transactionInfo); /** * 移除移动位置订阅 * @param device 设备信息 * @return 布尔 */ boolean removeMobilePositionSubscribe(Device device, CommonCallback callback); /** * 移除移动位置订阅 * @param deviceId 设备ID * @return 同步状态 */ SyncStatus getChannelSyncStatus(String deviceId); /** * 查看是否仍在同步 * @param deviceId 设备ID * @return 布尔 */ Boolean isSyncRunning(String deviceId); /** * 通道同步 * @param device 设备信息 */ void sync(Device device); /** * 查询设备信息 * @param deviceId 设备编号 * @return 设备信息 */ Device getDeviceByDeviceId(String deviceId); /** * 获取所有在线设备 * @return 设备列表 */ List getAllOnlineDevice(String serverId); List getAllByStatus(Boolean status); /** * 判断是否注册已经失效 * @param device 设备信息 * @return 布尔 */ boolean expire(Device device); /** * 检查设备状态 * @param device 设备信息 */ Boolean getDeviceStatus(Device device); /** * 根据IP和端口获取设备信息 * @param host IP * @param port 端口 * @return 设备信息 */ Device getDeviceByHostAndPort(String host, int port); /** * 更新设备 * @param device 设备信息 */ void updateDevice(Device device); @Transactional void updateDeviceList(List deviceList); /** * 检查设备编号是否已经存在 * @param deviceId 设备编号 * @return */ boolean isExist(String deviceId); /** * 添加设备 * @param device */ void addCustomDevice(Device device); /** * 页面表单更新设备信息 * @param device */ void updateCustomDevice(Device device); /** * 删除设备 * @param deviceId * @return */ boolean delete(String deviceId); /** * 获取统计信息 * @return */ ResourceBaseInfo getOverview(); /** * 获取所有设备 */ List getAll(); PageInfo getAll(int page, int count, String query, Boolean status); Device getDevice(Integer gbDeviceDbId); Device getDeviceByChannelId(Integer channelId); Device getDeviceBySourceChannelDeviceId(String requesterId); void subscribeCatalog(int id, int cycle); void subscribeMobilePosition(int id, int cycle, int interval); WVPResult devicesSync(Device device); void deviceBasicConfig(Device device, BasicParam basicParam, ErrorCallback callback); void deviceConfigQuery(Device device, String channelId, String configType, ErrorCallback callback); void teleboot(Device device); void record(Device device, String channelId, String recordCmdStr, ErrorCallback callback); void guard(Device device, String guardCmdStr, ErrorCallback callback); void resetAlarm(Device device, String channelId, String alarmMethod, String alarmType, ErrorCallback callback); void iFrame(Device device, String channelId); void homePosition(Device device, String channelId, Boolean enabled, Integer resetTime, Integer presetIndex, ErrorCallback callback); void dragZoomIn(Device device, String channelId, int length, int width, int midpointx, int midpointy, int lengthx, int lengthy, ErrorCallback callback); void dragZoomOut(Device device, String channelId, int length, int width, int midpointx, int midpointy, int lengthx, int lengthy, ErrorCallback callback); void deviceStatus(Device device, ErrorCallback callback); void updateDeviceHeartInfo(Device device); void alarm(Device device, String startPriority, String endPriority, String alarmMethod, String alarmType, String startTime, String endTime, ErrorCallback callback); void deviceInfo(Device device, ErrorCallback callback); void queryPreset(Device device, String channelId, ErrorCallback> callback); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/service/IGbChannelControlService.java ================================================ package com.genersoft.iot.vmp.gb28181.service; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import java.util.List; public interface IGbChannelControlService { void ptz(CommonGBChannel channel, FrontEndControlCodeForPTZ frontEndControlCode, ErrorCallback callback); void fi(CommonGBChannel channel, FrontEndControlCodeForFI frontEndControlCode, ErrorCallback callback); void preset(CommonGBChannel channel, FrontEndControlCodeForPreset frontEndControlCode, ErrorCallback callback); void tour(CommonGBChannel channel, FrontEndControlCodeForTour frontEndControlCode, ErrorCallback callback); void scan(CommonGBChannel channel, FrontEndControlCodeForScan frontEndControlCode, ErrorCallback callback); void wiper(CommonGBChannel channel, FrontEndControlCodeForWiper controlCode, ErrorCallback callback); void auxiliary(CommonGBChannel channel, FrontEndControlCodeForAuxiliary frontEndControlCode, ErrorCallback callback); void queryPreset(CommonGBChannel channel, ErrorCallback> callback); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/service/IGbChannelPlayService.java ================================================ package com.genersoft.iot.vmp.gb28181.service; import com.genersoft.iot.vmp.common.InviteSessionType; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import com.genersoft.iot.vmp.gb28181.bean.CommonRecordInfo; import com.genersoft.iot.vmp.gb28181.bean.InviteMessageInfo; import com.genersoft.iot.vmp.gb28181.bean.Platform; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import java.util.List; public interface IGbChannelPlayService { void startInvite(CommonGBChannel channel, InviteMessageInfo inviteInfo, Platform platform, ErrorCallback callback); void stopInvite(InviteSessionType type, CommonGBChannel channel, String stream); void playback(CommonGBChannel channel, Long startTime, Long stopTime, ErrorCallback callback); void download(CommonGBChannel channel, Long startTime, Long stopTime, Integer downloadSpeed, ErrorCallback callback); void stopPlay(CommonGBChannel channel); void play(CommonGBChannel channel, Platform platform, Boolean record, ErrorCallback callback); void stopPlayback(CommonGBChannel channel, String stream); void stopDownload(CommonGBChannel channel, String stream); void playbackPause(CommonGBChannel channel, String streamId); void playbackResume(CommonGBChannel channel, String streamId); void playbackSeek(CommonGBChannel channel, String stream, long seekTime); void playbackSpeed(CommonGBChannel channel, String stream, Double speed); void queryRecord(CommonGBChannel channel, String startTime, String endTime, ErrorCallback> callback); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/service/IGbChannelService.java ================================================ package com.genersoft.iot.vmp.gb28181.service; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.controller.bean.Extent; import com.genersoft.iot.vmp.service.bean.GPSMsgInfo; import com.genersoft.iot.vmp.streamPush.bean.StreamPush; import com.github.pagehelper.PageInfo; import java.util.Collection; import java.util.List; import java.util.Map; public interface IGbChannelService { CommonGBChannel queryByDeviceId(String gbDeviceId); int add(CommonGBChannel commonGBChannel); int delete(int gbId); void delete(Collection ids); int update(CommonGBChannel commonGBChannel); int offline(CommonGBChannel commonGBChannel); int offline(List commonGBChannelList, boolean permission); int online(CommonGBChannel commonGBChannel); int online(List commonGBChannelList, boolean permission); void batchAdd(List commonGBChannels); void updateStatus(List channelList); CommonGBChannel getOne(int id); List getIndustryCodeList(); List getDeviceTypeList(); List getNetworkIdentificationTypeList(); void reset(int id, List chanelFields); PageInfo queryListByCivilCode(int page, int count, String query, Boolean online, Integer channelType, String civilCode); PageInfo queryListByParentId(int page, int count, String query, Boolean online, Integer channelType, String groupDeviceId); void removeCivilCode(List allChildren); void addChannelToRegion(String civilCode, List channelIds); void deleteChannelToRegion(String civilCode, List channelIds); void deleteChannelToRegionByCivilCode(String civilCode); void deleteChannelToRegionByChannelIds(List channelIds); void addChannelToRegionByGbDevice(String civilCode, List deviceIds); void deleteChannelToRegionByGbDevice(List deviceIds); void removeParentIdByBusinessGroup(String businessGroup); void removeParentIdByGroupList(List groupList); void updateBusinessGroup(String oldBusinessGroup, String newBusinessGroup); void updateParentIdGroup(String oldParentId, String newParentId); void addChannelToGroup(String parentId, String businessGroup, List channelIds); void deleteChannelToGroup(String parentId, String businessGroup, List channelIds); void addChannelToGroupByGbDevice(String parentId, String businessGroup, List deviceIds); void deleteChannelToGroupByGbDevice(List deviceIds); void batchUpdateForStreamPushRedisMsg(List commonGBChannels, boolean permission); CommonGBChannel queryOneWithPlatform(Integer platformId, String channelDeviceId); void updateCivilCode(String oldCivilCode, String newCivilCode); List queryListByStreamPushList(List streamPushList); PageInfo queryList(int page, int count, String query, Boolean online, Boolean hasRecordPlan, Integer channelType, String civilCode, String parentDeviceId); PageInfo queryListByCivilCodeForUnusual(int page, int count, String query, Boolean online, Integer channelType); void clearChannelCivilCode(Boolean all, List channelIds); PageInfo queryListByParentForUnusual(int page, int count, String query, Boolean online, Integer channelType); void clearChannelParent(Boolean all, List channelIds); void updateGPSFromGPSMsgInfo(List gpsMsgInfoList); void updateGPS(List channelList); List queryListForMap(String query, Boolean online, Boolean hasRecordPlan, Integer channelType); CommonGBChannel queryCommonChannelByDeviceChannel(DeviceChannel channel); void resetLevel(); byte[] getTile(int z, int x, int y, String geoCoordSys); String drawThin(Map zoomParam, Extent extent, String geoCoordSys); DrawThinProcess thinProgress(String id); void saveThin(String id); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/service/IGroupService.java ================================================ package com.genersoft.iot.vmp.gb28181.service; import com.genersoft.iot.vmp.gb28181.bean.Group; import com.genersoft.iot.vmp.gb28181.bean.GroupTree; import com.github.pagehelper.PageInfo; import java.util.Collection; import java.util.List; import java.util.Map; public interface IGroupService { void add(Group group); List queryAllChildren(Integer id); void update(Group group); Group queryGroupByDeviceId(String regionDeviceId); List queryForTree(String query, Integer parent, Boolean hasChannel); boolean delete(int id); boolean batchAdd(List groupList); List getPath(String deviceId, String businessGroup); PageInfo queryList(Integer page, Integer count, String query); Group queryGroupByAlias(String groupAlias); Map queryGroupByAliasMap(); void saveByAlias(Collection groups); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/service/IInviteStreamService.java ================================================ package com.genersoft.iot.vmp.gb28181.service; import com.genersoft.iot.vmp.common.InviteInfo; import com.genersoft.iot.vmp.common.InviteSessionType; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import java.util.List; /** * 记录国标点播的状态,包括实时预览,下载,录像回放 */ public interface IInviteStreamService { /** * 更新点播的状态信息 */ void updateInviteInfo(InviteInfo inviteInfo); void updateInviteInfo(InviteInfo inviteInfo, Long time); InviteInfo updateInviteInfoForStream(InviteInfo inviteInfo, String stream); /** * 获取点播的状态信息 */ InviteInfo getInviteInfo(InviteSessionType type, Integer channelId, String stream); /** * 移除点播的状态信息 */ void removeInviteInfo(InviteSessionType type, Integer channelId, String stream); /** * 移除点播的状态信息 */ void removeInviteInfo(InviteInfo inviteInfo); /** * 移除点播的状态信息 */ void removeInviteInfoByDeviceAndChannel(InviteSessionType inviteSessionType, Integer channelId); List getAllInviteInfo(); /** * 获取点播的状态信息 */ InviteInfo getInviteInfoByDeviceAndChannel(InviteSessionType type, Integer channelId); /** * 获取点播的状态信息 */ InviteInfo getInviteInfoByStream(InviteSessionType type, String stream); /** * 添加一个invite回调 */ void once(InviteSessionType type, Integer channelId, String stream, ErrorCallback callback); /** * 调用一个invite回调 */ void call(InviteSessionType type, Integer channelId, String stream, int code, String msg, StreamInfo data); /** * 清空一个设备的所有invite信息 */ void clearInviteInfo(String deviceId); /** * 统计同一个zlm下的国标收流个数 */ int getStreamInfoCount(String mediaServerId); /** * 获取MediaServer下的流信息 */ InviteInfo getInviteInfoBySSRC(String ssrc); /** * 更新ssrc */ InviteInfo updateInviteInfoForSSRC(InviteInfo inviteInfo, String ssrcInResponse); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/service/IPTZService.java ================================================ package com.genersoft.iot.vmp.gb28181.service; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.Preset; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import java.util.List; public interface IPTZService { void ptz(Device device, String channelId, int cmdCode, int horizonSpeed, int verticalSpeed, int zoomSpeed); void frontEndCommand(Device device, String channelId, int cmdCode, int parameter1, int parameter2, int combindCode2); void frontEndCommand(CommonGBChannel channel, Integer cmdCode, Integer parameter1, Integer parameter2, Integer combindCode2); void queryPresetList(CommonGBChannel channel, ErrorCallback> callback); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/service/IPlatformChannelService.java ================================================ package com.genersoft.iot.vmp.gb28181.service; import com.genersoft.iot.vmp.gb28181.bean.*; import com.github.pagehelper.PageInfo; import java.util.List; /** * 平台关联通道管理 * @author lin */ public interface IPlatformChannelService { PageInfo queryChannelList(int page, int count, String query, Integer channelType, Boolean online, Integer platformId, Boolean hasShare); int addAllChannel(Integer platformId); int removeAllChannel(Integer platformId); int addChannels(Integer platformId, List channelIds); int removeChannels(Integer platformId, List channelIds); void removeChannels(List ids); void removeChannel(int gbId); List queryByPlatform(Platform platform); void pushChannel(Integer platformId); void addChannelByDevice(Integer platformId, List deviceIds); void removeChannelByDevice(Integer platformId, List deviceIds); void updateCustomChannel(PlatformChannel channel); void checkGroupRemove(List channelList, List groups); void checkGroupAdd(List channelList); List queryPlatFormListByChannelDeviceId(Integer channelId, List platforms); CommonGBChannel queryChannelByPlatformIdAndChannelId(Integer platformId, Integer channelId); List queryChannelByPlatformIdAndChannelIds(Integer platformId, List channelIds); void checkRegionAdd(List channelList); void checkRegionRemove(List channelList, List regionList); List queryByPlatformBySharChannelId(String gbId); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/service/IPlatformService.java ================================================ package com.genersoft.iot.vmp.gb28181.service; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import com.genersoft.iot.vmp.gb28181.bean.Platform; import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo; import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.event.hook.HookSubscribe; import com.genersoft.iot.vmp.service.bean.InviteTimeOutCallback; import com.github.pagehelper.PageInfo; import javax.sip.InvalidArgumentException; import javax.sip.SipException; import java.text.ParseException; import java.util.List; /** * 国标平台的业务类 * @author lin */ public interface IPlatformService { Platform queryPlatformByServerGBId(String platformGbId); /** * 分页获取上级平台 * @param page * @param count * @return */ PageInfo queryPlatformList(int page, int count, String query); /** * 添加级联平台 * @param parentPlatform 级联平台 */ boolean add(Platform parentPlatform); /** * 添加级联平台 * @param parentPlatform 级联平台 */ boolean update(Platform parentPlatform); /** * 平台上线 * @param parentPlatform 平台信息 */ void online(Platform parentPlatform, SipTransactionInfo sipTransactionInfo); /** * 平台离线 * @param parentPlatform 平台信息 */ void offline(Platform parentPlatform); /** * 向上级平台发送位置订阅 * @param platformId 平台 */ void sendNotifyMobilePosition(String platformId); /** * 向上级发送语音喊话的消息 */ void broadcastInvite(Platform platform, CommonGBChannel channel, String sourceId, MediaServer mediaServerItem, HookSubscribe.Event hookEvent, SipSubscribe.Event errorEvent, InviteTimeOutCallback timeoutCallback) throws InvalidArgumentException, ParseException, SipException; /** * 语音喊话回复BYE */ void stopBroadcast(Platform platform, CommonGBChannel channel, String app, String stream, boolean sendBye, MediaServer mediaServerItem); void addSimulatedSubscribeInfo(Platform parentPlatform); Platform queryOne(Integer platformId); List queryEnablePlatformList(String serverId); boolean delete(Integer platformId); List queryAll(String serverId); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/service/IPlayService.java ================================================ package com.genersoft.iot.vmp.gb28181.service; import com.genersoft.iot.vmp.common.InviteInfo; import com.genersoft.iot.vmp.common.InviteSessionType; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.conf.exception.ServiceException; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.controller.bean.AudioBroadcastEvent; import com.genersoft.iot.vmp.media.bean.MediaInfo; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.service.bean.SSRCInfo; import com.genersoft.iot.vmp.vmanager.bean.AudioBroadcastResult; import gov.nist.javax.sip.message.SIPResponse; import javax.sip.InvalidArgumentException; import javax.sip.SipException; import javax.sip.header.CallIdHeader; import java.text.ParseException; /** * 点播处理 */ public interface IPlayService { SSRCInfo play(MediaServer mediaServerItem, String deviceId, String channelId, String ssrc, ErrorCallback callback); void play(Device device, DeviceChannel channel, ErrorCallback callback); StreamInfo onPublishHandlerForPlay(MediaServer mediaServerItem, MediaInfo mediaInfo, Device device, DeviceChannel channel); MediaServer getNewMediaServerItem(Device device); void playBack(Device device, DeviceChannel channel, String startTime, String endTime, ErrorCallback callback); void zlmServerOffline(MediaServer mediaServer); void download(Device device, DeviceChannel channel, String startTime, String endTime, int downloadSpeed, ErrorCallback callback); StreamInfo getDownLoadInfo(Device device, DeviceChannel channel, String stream); void zlmServerOnline(MediaServer mediaServer); AudioBroadcastResult audioBroadcast(String deviceId, String channelDeviceId, Boolean broadcastMode); boolean audioBroadcastCmd(Device device, DeviceChannel channel, MediaServer mediaServerItem, String app, String stream, int timeout, boolean isFromPlatform, AudioBroadcastEvent event) throws InvalidArgumentException, ParseException, SipException; boolean audioBroadcastInUse(Device device, DeviceChannel channel); void stopAudioBroadcast(Device device, DeviceChannel channel); void playbackPause(String streamId) throws ServiceException, InvalidArgumentException, ParseException, SipException; void playbackResume(String streamId) throws ServiceException, InvalidArgumentException, ParseException, SipException; void playbackSeek(String streamId, long seekTime) throws InvalidArgumentException, ParseException, SipException; void playbackSpeed(String streamId, double speed) throws InvalidArgumentException, ParseException, SipException; void startPushStream(SendRtpInfo sendRtpItem, DeviceChannel channel, SIPResponse sipResponse, Platform platform, CallIdHeader callIdHeader); void startSendRtpStreamFailHand(SendRtpInfo sendRtpItem, Platform platform, CallIdHeader callIdHeader); void talkCmd(Device device, DeviceChannel channel, MediaServer mediaServerItem, String stream, AudioBroadcastEvent event); void stopTalk(Device device, DeviceChannel channel, Boolean streamIsReady); void getSnap(String deviceId, String channelId, String fileName, ErrorCallback errorCallback); void stop(InviteSessionType type, Device device, DeviceChannel channel, String stream); void stop(InviteInfo inviteInfo); void play(CommonGBChannel channel, Boolean record, ErrorCallback callback); void stopPlay(InviteSessionType inviteSessionType, CommonGBChannel channel); void stop(InviteSessionType inviteSessionType, CommonGBChannel channel, String stream); void playBack(CommonGBChannel channel, Long startTime, Long stopTime, ErrorCallback callback); void download(CommonGBChannel channel, Long startTime, Long stopTime, Integer downloadSpeed, ErrorCallback callback); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/service/IRegionService.java ================================================ package com.genersoft.iot.vmp.gb28181.service; import com.genersoft.iot.vmp.gb28181.bean.Region; import com.genersoft.iot.vmp.gb28181.bean.RegionTree; import com.github.pagehelper.PageInfo; import java.util.List; public interface IRegionService { void add(Region region); boolean deleteByDeviceId(Integer regionDeviceId); /** * 查询区划列表 */ PageInfo query(String query, int page, int count); /** * 更新区域 */ void update(Region region); List getAllChild(String parent); Region queryRegionByDeviceId(String regionDeviceId); List queryForTree(Integer parent, Boolean hasChannel); void syncFromChannel(); boolean delete(int id); boolean batchAdd(List regionList); List getPath(String deviceId); String getDescription(String civilCode); void addByCivilCode(String civilCode); PageInfo queryList(int page, int count, String query); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/service/ISourceDownloadService.java ================================================ package com.genersoft.iot.vmp.gb28181.service; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import com.genersoft.iot.vmp.service.bean.ErrorCallback; /** * 资源能力接入-录像下载 */ public interface ISourceDownloadService { void download(CommonGBChannel channel, Long startTime, Long stopTime, Integer downloadSpeed, ErrorCallback callback); void stopDownload(CommonGBChannel channel, String stream); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/service/ISourcePTZService.java ================================================ package com.genersoft.iot.vmp.gb28181.service; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import java.util.List; /** * 资源能力接入-云台控制 */ public interface ISourcePTZService { void ptz(CommonGBChannel channel, FrontEndControlCodeForPTZ frontEndControlCode, ErrorCallback callback); void preset(CommonGBChannel channel, FrontEndControlCodeForPreset frontEndControlCode, ErrorCallback callback); void fi(CommonGBChannel channel, FrontEndControlCodeForFI frontEndControlCode, ErrorCallback callback); void tour(CommonGBChannel channel, FrontEndControlCodeForTour frontEndControlCode, ErrorCallback callback); void scan(CommonGBChannel channel, FrontEndControlCodeForScan frontEndControlCode, ErrorCallback callback); void auxiliary(CommonGBChannel channel, FrontEndControlCodeForAuxiliary frontEndControlCode, ErrorCallback callback); void wiper(CommonGBChannel channel, FrontEndControlCodeForWiper frontEndControlCode, ErrorCallback callback); void queryPreset(CommonGBChannel channel, ErrorCallback> callback); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/service/ISourcePlayService.java ================================================ package com.genersoft.iot.vmp.gb28181.service; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import com.genersoft.iot.vmp.gb28181.bean.Platform; import com.genersoft.iot.vmp.service.bean.ErrorCallback; /** * 资源能力接入-实时录像 */ public interface ISourcePlayService { void play(CommonGBChannel channel, Platform platform, Boolean record, ErrorCallback callback); void stopPlay(CommonGBChannel channel); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/service/ISourcePlaybackService.java ================================================ package com.genersoft.iot.vmp.gb28181.service; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import com.genersoft.iot.vmp.gb28181.bean.CommonRecordInfo; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import java.util.List; /** * 资源能力接入-录像回放 */ public interface ISourcePlaybackService { void playback(CommonGBChannel channel, Long startTime, Long stopTime, ErrorCallback callback); void stopPlayback(CommonGBChannel channel, String stream); void playbackPause(CommonGBChannel channel, String stream); void playbackResume(CommonGBChannel channel, String stream); void playbackSeek(CommonGBChannel channel, String stream, long seekTime); void playbackSpeed(CommonGBChannel channel, String stream, Double speed); void queryRecord(CommonGBChannel channel, String startTime, String endTime, ErrorCallback> callback); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/DeviceAlarmServiceImpl.java ================================================ package com.genersoft.iot.vmp.gb28181.service.impl; import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm; import com.genersoft.iot.vmp.gb28181.dao.DeviceAlarmMapper; import com.genersoft.iot.vmp.gb28181.service.IDeviceAlarmService; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service public class DeviceAlarmServiceImpl implements IDeviceAlarmService { @Autowired private DeviceAlarmMapper deviceAlarmMapper; @Override public PageInfo getAllAlarm(int page, int count, String deviceId, String channelId, String alarmPriority, String alarmMethod, String alarmType, String startTime, String endTime) { PageHelper.startPage(page, count); List all = deviceAlarmMapper.query(deviceId, channelId, alarmPriority, alarmMethod, alarmType, startTime, endTime); return new PageInfo<>(all); } @Override public void add(DeviceAlarm deviceAlarm) { deviceAlarmMapper.add(deviceAlarm); } @Override public int clearAlarmBeforeTime(Integer id, List deviceIdList, String time) { return deviceAlarmMapper.clearAlarmBeforeTime(id, deviceIdList, time); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/DeviceChannelServiceImpl.java ================================================ package com.genersoft.iot.vmp.gb28181.service.impl; import com.genersoft.iot.vmp.common.InviteInfo; import com.genersoft.iot.vmp.common.InviteSessionType; import com.genersoft.iot.vmp.common.enums.ChannelDataType; import com.genersoft.iot.vmp.common.enums.DeviceControlType; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.dao.DeviceChannelMapper; import com.genersoft.iot.vmp.gb28181.dao.DeviceMapper; import com.genersoft.iot.vmp.gb28181.dao.DeviceMobilePositionMapper; import com.genersoft.iot.vmp.gb28181.dao.PlatformChannelMapper; import com.genersoft.iot.vmp.gb28181.event.EventPublisher; import com.genersoft.iot.vmp.gb28181.event.record.RecordInfoEndEvent; import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent; import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService; import com.genersoft.iot.vmp.gb28181.service.IInviteStreamService; import com.genersoft.iot.vmp.gb28181.service.IPlatformChannelService; import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander; import com.genersoft.iot.vmp.gb28181.utils.SipUtils; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcPlayService; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.utils.Coordtransform; import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.ResourceBaseInfo; import com.genersoft.iot.vmp.web.gb28181.dto.DeviceChannelExtend; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import jakarta.validation.constraints.NotNull; import lombok.extern.slf4j.Slf4j; import org.dom4j.Element; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import javax.sip.InvalidArgumentException; import javax.sip.SipException; import javax.sip.message.Response; import java.text.ParseException; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.TimeUnit; import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText; /** * @author lin */ @Slf4j @Service public class DeviceChannelServiceImpl implements IDeviceChannelService { @Autowired private EventPublisher eventPublisher; @Autowired private IInviteStreamService inviteStreamService; @Autowired private DeviceChannelMapper channelMapper; @Autowired private PlatformChannelMapper platformChannelMapper; @Autowired private DeviceMapper deviceMapper; @Autowired private DeviceMobilePositionMapper deviceMobilePositionMapper; @Autowired private UserSetting userSetting; @Autowired private IRedisCatchStorage redisCatchStorage; @Autowired private IPlatformChannelService platformChannelService; @Autowired private IRedisRpcPlayService redisRpcPlayService; @Autowired private ISIPCommander commander; // 记录录像查询的结果等待 private final Map> topicSubscribers = new ConcurrentHashMap<>(); /** * 监听录像查询结束事件 */ @Async("taskExecutor") @org.springframework.context.event.EventListener public void onApplicationEvent(RecordInfoEndEvent event) { SynchronousQueue queue = topicSubscribers.get("record" + event.getRecordInfo().getSn()); if (queue != null) { queue.offer(event.getRecordInfo()); } } @Autowired private ISIPCommander cmder; @Override public int updateChannels(Device device, List channels) { if (CollectionUtils.isEmpty(channels)) { return 0; } List addChannels = new ArrayList<>(); List updateChannels = new ArrayList<>(); HashMap channelsInStore = new HashMap<>(); int result = 0; List channelList = channelMapper.queryChannelsByDeviceDbId(device.getId()); if (channelList.isEmpty()) { for (DeviceChannel channel : channels) { channel.setDataDeviceId(device.getId()); InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, channel.getId()); if (inviteInfo != null && inviteInfo.getStreamInfo() != null) { channel.setStreamId(inviteInfo.getStreamInfo().getStream()); } String now = DateUtil.getNow(); channel.setUpdateTime(now); channel.setCreateTime(now); addChannels.add(channel); } }else { for (DeviceChannel deviceChannel : channelList) { channelsInStore.put(deviceChannel.getDataDeviceId() + deviceChannel.getDeviceId(), deviceChannel); } for (DeviceChannel channel : channels) { InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, channel.getId()); if (inviteInfo != null && inviteInfo.getStreamInfo() != null) { channel.setStreamId(inviteInfo.getStreamInfo().getStream()); } String now = DateUtil.getNow(); channel.setUpdateTime(now); DeviceChannel deviceChannelInDb = channelsInStore.get(channel.getDataDeviceId() + channel.getDeviceId()); if ( deviceChannelInDb != null) { channel.setId(deviceChannelInDb.getId()); channel.setUpdateTime(now); updateChannels.add(channel); }else { channel.setCreateTime(now); channel.setUpdateTime(now); addChannels.add(channel); } } } Set channelSet = new HashSet<>(); // 滤重 List addChannelList = new ArrayList<>(); List updateChannelList = new ArrayList<>(); addChannels.forEach(channel -> { if (channelSet.add(channel.getDeviceId())) { addChannelList.add(channel); } }); channelSet.clear(); updateChannels.forEach(channel -> { if (channelSet.add(channel.getDeviceId())) { updateChannelList.add(channel); } }); int limitCount = 500; if (!addChannelList.isEmpty()) { if (addChannelList.size() > limitCount) { for (int i = 0; i < addChannelList.size(); i += limitCount) { int toIndex = i + limitCount; if (i + limitCount > addChannelList.size()) { toIndex = addChannelList.size(); } result += channelMapper.batchAdd(addChannelList.subList(i, toIndex)); } }else { result += channelMapper.batchAdd(addChannelList); } } if (!updateChannelList.isEmpty()) { if (updateChannelList.size() > limitCount) { for (int i = 0; i < updateChannelList.size(); i += limitCount) { int toIndex = i + limitCount; if (i + limitCount > updateChannelList.size()) { toIndex = updateChannelList.size(); } result += channelMapper.batchUpdate(updateChannelList.subList(i, toIndex)); } }else { result += channelMapper.batchUpdate(updateChannelList); } } return result; } @Override public ResourceBaseInfo getOverview() { int online = channelMapper.getOnlineCount(); int total = channelMapper.getAllChannelCount(); return new ResourceBaseInfo(total, online); } @Override public void online(DeviceChannel channel) { channelMapper.online(channel.getId()); } @Override public void offline(DeviceChannel channel) { channelMapper.offline(channel.getId()); } @Override public void deleteForNotify(DeviceChannel channel) { channelMapper.deleteForNotify(channel); } @Override public DeviceChannel getOne(String deviceId, String channelId){ Device device = deviceMapper.getDeviceByDeviceId(deviceId); if (device == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到设备:" + deviceId); } return channelMapper.getOneByDeviceId(device.getId(), channelId); } @Override public DeviceChannel getOneForSource(String deviceId, String channelId){ Device device = deviceMapper.getDeviceByDeviceId(deviceId); if (device == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到设备:" + deviceId); } return channelMapper.getOneByDeviceIdForSource(device.getId(), channelId); } @Override public DeviceChannel getOneForSource(int deviceDbId, String channelId) { return channelMapper.getOneByDeviceIdForSource(deviceDbId, channelId); } @Override public DeviceChannel getOneBySourceId(int deviceDbId, String channelId) { return channelMapper.getOneBySourceChannelId(deviceDbId, channelId); } @Override public void updateChannelStreamIdentification(DeviceChannel channel) { Assert.hasLength(channel.getStreamIdentification(), "码流标识必须存在"); if (ObjectUtils.isEmpty(channel.getStreamIdentification())) { log.info("[重置通道码流类型] 设备: {}, 码流: {}", channel.getDeviceId(), channel.getStreamIdentification()); }else { log.info("[更新通道码流类型] 设备: {}, 通道:{}, 码流: {}", channel.getDeviceId(), channel.getDeviceId(), channel.getStreamIdentification()); } if (channel.getId() > 0) { channelMapper.updateChannelStreamIdentification(channel); }else { channelMapper.updateAllChannelStreamIdentification(channel.getStreamIdentification()); } } @Override public List queryChaneListByDeviceId(String deviceId) { Device device = deviceMapper.getDeviceByDeviceId(deviceId); if (device == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到通道:" + deviceId); } return channelMapper.queryChannelsByDeviceDbId(device.getId()); } @Override public List queryChaneIdListByDeviceDbIds(List deviceDbIds) { return channelMapper.queryChaneIdListByDeviceDbIds(deviceDbIds); } @Override public void handlePtzCmd(@NotNull Integer dataDeviceId, @NotNull Integer gbId, Element rootElement, DeviceControlType type, ErrorCallback callback) { // 根据通道ID,获取所属设备 Device device = deviceMapper.query(dataDeviceId); if (device == null) { // 不存在则回复404 log.warn("[INFO 消息] 通道所属设备不存在, 设备ID: {}", dataDeviceId); callback.run(Response.NOT_FOUND, "device not found", null); return; } DeviceChannel deviceChannel = channelMapper.getOneForSource(gbId); if (deviceChannel == null) { log.warn("[deviceControl] 未找到设备原始通道, 设备: {}({}),通道编号:{}", device.getName(), device.getDeviceId(), gbId); callback.run(Response.NOT_FOUND, "channel not found", null); return; } log.info("[deviceControl] 命令: {}, 设备: {}({}), 通道{}({}", type, device.getName(), device.getDeviceId(), deviceChannel.getName(), deviceChannel.getDeviceId()); String cmdString = getText(rootElement, type.getVal()); try { cmder.fronEndCmd(device, deviceChannel.getDeviceId(), cmdString, errorResult->{ callback.run(errorResult.statusCode, errorResult.msg, null); }, errorResult->{ callback.run(errorResult.statusCode, errorResult.msg, null); }); } catch (InvalidArgumentException | SipException | ParseException e) { log.error("[命令发送失败] 云台/前端: {}", e.getMessage()); } } @Override public void updateChannelGPS(Device device, DeviceChannel deviceChannel, MobilePosition mobilePosition) { if (device.getGeoCoordSys().equalsIgnoreCase("GCJ02")) { Double[] wgs84Position = Coordtransform.GCJ02ToWGS84(mobilePosition.getLongitude(), mobilePosition.getLatitude()); mobilePosition.setLongitude(wgs84Position[0]); mobilePosition.setLatitude(wgs84Position[1]); Double[] wgs84PositionForChannel = Coordtransform.GCJ02ToWGS84(deviceChannel.getLongitude(), deviceChannel.getLatitude()); deviceChannel.setGbLongitude(wgs84PositionForChannel[0]); deviceChannel.setGbLatitude(wgs84PositionForChannel[1]); } if (userSetting.getSavePositionHistory()) { deviceMobilePositionMapper.insertNewPosition(mobilePosition); } if (deviceChannel.getDeviceId().equals(device.getDeviceId())) { deviceChannel.setDeviceId(null); } if (deviceChannel.getGpsTime() == null) { deviceChannel.setGpsTime(DateUtil.getNow()); } int updated = channelMapper.updatePosition(deviceChannel); if (updated == 0) { return; } List deviceChannels = new ArrayList<>(); if (deviceChannel.getDeviceId() == null) { // 有的设备这里上报的deviceId与通道Id是一样,这种情况更新设备下的全部通道 List deviceChannelsInDb = queryChaneListByDeviceId(device.getDeviceId()); deviceChannels.addAll(deviceChannelsInDb); }else { deviceChannels.add(deviceChannel); } if (deviceChannels.isEmpty()) { return; } if (deviceChannels.size() > 100) { log.warn("[更新通道位置信息后发送通知] 设备可能是平台,上报的位置信息未标明通道编号," + "导致所有通道被更新位置, deviceId:{}", device.getDeviceId()); } for (DeviceChannel channel : deviceChannels) { // 向关联了该通道并且开启移动位置订阅的上级平台发送移动位置订阅消息 mobilePosition.setChannelId(channel.getId()); mobilePosition.setChannelDeviceId(channel.getDeviceId()); try { eventPublisher.mobilePositionEventPublish(mobilePosition); }catch (Exception e) { log.error("[向上级转发移动位置失败] ", e); } } } @Override public void startPlay(Integer channelId, String stream) { channelMapper.startPlay(channelId, stream); } @Override public void stopPlay(Integer channelId) { channelMapper.stopPlayById(channelId); } @Override public void cleanChannelsForDevice(int deviceId) { channelMapper.cleanChannelsByDeviceId(deviceId); } @Override @Transactional public boolean resetChannels(int deviceDbId, List deviceChannelList) { if (CollectionUtils.isEmpty(deviceChannelList)) { return false; } List allChannels = channelMapper.queryAllChannelsForRefresh(deviceDbId); Map allChannelMap = new HashMap<>(); if (!allChannels.isEmpty()) { for (DeviceChannel deviceChannel : allChannels) { allChannelMap.put(deviceChannel.getDataDeviceId() + deviceChannel.getDeviceId(), deviceChannel); } } // 数据去重 List channels = new ArrayList<>(); List updateChannels = new ArrayList<>(); List addChannels = new ArrayList<>(); List deleteChannels = new ArrayList<>(); StringBuilder stringBuilder = new StringBuilder(); Map subContMap = new HashMap<>(); for (DeviceChannel deviceChannel : deviceChannelList) { DeviceChannel channelInDb = allChannelMap.get(deviceChannel.getDataDeviceId() + deviceChannel.getDeviceId()); if (channelInDb != null) { deviceChannel.setStreamId(channelInDb.getStreamId()); deviceChannel.setHasAudio(channelInDb.isHasAudio()); deviceChannel.setId(channelInDb.getId()); if (channelInDb.getStatus() != null && !channelInDb.getStatus().equalsIgnoreCase(deviceChannel.getStatus())){ List platformList = platformChannelMapper.queryParentPlatformByChannelId(deviceChannel.getDeviceId()); if (!CollectionUtils.isEmpty(platformList)){ platformList.forEach(platform->{ eventPublisher.catalogEventPublish(platform, deviceChannel.buildCommonGBChannelForStatus(), deviceChannel.getStatus().equals("ON")? CatalogEvent.ON:CatalogEvent.OFF); }); } } deviceChannel.setUpdateTime(DateUtil.getNow()); updateChannels.add(deviceChannel); }else { deviceChannel.setCreateTime(DateUtil.getNow()); deviceChannel.setUpdateTime(DateUtil.getNow()); addChannels.add(deviceChannel); } allChannelMap.remove(deviceChannel.getDataDeviceId() + deviceChannel.getDeviceId()); channels.add(deviceChannel); if (!ObjectUtils.isEmpty(deviceChannel.getParentId())) { if (subContMap.get(deviceChannel.getParentId()) == null) { subContMap.put(deviceChannel.getParentId(), 1); }else { Integer count = subContMap.get(deviceChannel.getParentId()); subContMap.put(deviceChannel.getParentId(), count++); } } } deleteChannels.addAll(allChannelMap.values()); if (!channels.isEmpty()) { for (DeviceChannel channel : channels) { if (subContMap.get(channel.getDeviceId()) != null){ Integer count = subContMap.get(channel.getDeviceId()); if (count > 0) { channel.setSubCount(count); channel.setParental(1); } } } } if (stringBuilder.length() > 0) { log.info("[目录查询]收到的数据存在重复: {}" , stringBuilder); } if(CollectionUtils.isEmpty(channels)){ log.info("通道重设,数据为空={}" , deviceChannelList); return false; } int limitCount = 500; if (!addChannels.isEmpty()) { if (addChannels.size() > limitCount) { for (int i = 0; i < addChannels.size(); i += limitCount) { int toIndex = i + limitCount; if (i + limitCount > addChannels.size()) { toIndex = addChannels.size(); } channelMapper.batchAdd(addChannels.subList(i, toIndex)); } }else { channelMapper.batchAdd(addChannels); } } if (!updateChannels.isEmpty()) { if (updateChannels.size() > limitCount) { for (int i = 0; i < updateChannels.size(); i += limitCount) { int toIndex = i + limitCount; if (i + limitCount > updateChannels.size()) { toIndex = updateChannels.size(); } channelMapper.batchUpdate(updateChannels.subList(i, toIndex)); } }else { channelMapper.batchUpdate(updateChannels); } // 不对收到的通道做比较,已确定是否真的发生变化,所以不发送更新通知 } if (!deleteChannels.isEmpty()) { try { // 这些通道可能关联了,上级平台需要删除同时发送消息 List ids = new ArrayList<>(); deleteChannels.stream().forEach(deviceChannel -> { ids.add(deviceChannel.getId()); }); platformChannelService.removeChannels(ids); }catch (Exception e) { log.error("[移除通道国标级联共享失败]", e); } if (deleteChannels.size() > limitCount) { for (int i = 0; i < deleteChannels.size(); i += limitCount) { int toIndex = i + limitCount; if (i + limitCount > deleteChannels.size()) { toIndex = deleteChannels.size(); } channelMapper.batchDel(deleteChannels.subList(i, toIndex)); } }else { channelMapper.batchDel(deleteChannels); } } return true; } @Override public PageInfo getSubChannels(int deviceDbId, String channelId, String query, Boolean channelType, Boolean online, int page, int count) { PageHelper.startPage(page, count); String civilCode = null; String parentId = null; String businessGroupId = null; if (channelId.length() <= 8) { civilCode = channelId; }else { GbCode decode = GbCode.decode(channelId); if (Integer.parseInt(decode.getTypeCode()) == 215) { businessGroupId = channelId; }else { parentId = channelId; } } if (query != null) { query = query.replaceAll("/", "//") .replaceAll("%", "/%") .replaceAll("_", "/_"); } List all = channelMapper.queryChannels(deviceDbId, civilCode, businessGroupId, parentId, query, false, channelType, online, null, null); return new PageInfo<>(all); } @Override public List queryChannelExtendsByDeviceId(String deviceId, List channelIds, Boolean online) { return channelMapper.queryChannelsWithDeviceInfo(deviceId, null,null, null, online,channelIds); } @Override public PageInfo queryChannelsByDeviceId(String deviceId, String query, Boolean hasSubChannel, Boolean online, int page, int count) { Device device = deviceMapper.getDeviceByDeviceId(deviceId); if (device == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到设备:" + deviceId); } if (query != null) { query = query.replaceAll("/", "//") .replaceAll("%", "/%") .replaceAll("_", "/_"); } PageHelper.startPage(page, count); List all = channelMapper.queryChannels(device.getId(), null, null, null, query, false, hasSubChannel, online, null, null); return new PageInfo<>(all); } @Override public PageInfo queryChannels(String query, Boolean queryParent, Boolean hasSubChannel, Boolean online, Boolean hasStream, int page, int count) { PageHelper.startPage(page, count); if (query != null) { query = query.replaceAll("/", "//") .replaceAll("%", "/%") .replaceAll("_", "/_"); } List all = channelMapper.queryChannels(null, null, null, null, query, queryParent, hasSubChannel, online, null, hasStream); return new PageInfo<>(all); } @Override public List queryDeviceWithAsMessageChannel() { return deviceMapper.queryDeviceWithAsMessageChannel(); } @Override public DeviceChannel getRawChannel(int id) { return deviceMapper.getRawChannel(id); } @Override public DeviceChannel getOneById(Integer channelId) { return channelMapper.getOne(channelId); } @Override public DeviceChannel getOneForSourceById(Integer channelId) { return channelMapper.getOneForSource(channelId); } @Override public DeviceChannel getBroadcastChannel(int deviceDbId) { List channels = channelMapper.queryChannelsByDeviceDbId(deviceDbId); if (channels.size() == 1) { return channels.get(0); } for (DeviceChannel channel : channels) { // 获取137类型的 if (SipUtils.isFrontEnd(channel.getDeviceId())) { return channel; } } return null; } @Override public void changeAudio(Integer channelId, Boolean audio) { channelMapper.changeAudio(channelId, audio); } @Override public void updateChannelStatusForNotify(DeviceChannel channel) { channelMapper.updateStatus(channel); } @Override public void addChannel(DeviceChannel channel) { channel.setDataType(ChannelDataType.GB28181); channel.setDataDeviceId(channel.getDataDeviceId()); channelMapper.add(channel); } @Override public void updateChannelForNotify(DeviceChannel channel) { channelMapper.updateChannelForNotify(channel); } @Override public void queryRecordInfo(Device device, DeviceChannel channel, String startTime, String endTime, ErrorCallback callback) { if (!userSetting.getServerId().equals(device.getServerId())){ redisRpcPlayService.queryRecordInfo(device.getServerId(), channel.getId(), startTime, endTime, callback); return; } try { int sn = (int)((Math.random()*9+1)*100000); commander.recordInfoQuery(device, channel.getDeviceId(), startTime, endTime, sn, null, null, eventResult -> { try { // 消息发送成功, 监听等待数据到来 SynchronousQueue queue = new SynchronousQueue<>(); topicSubscribers.put("record" + sn, queue); RecordInfo recordInfo = queue.poll(userSetting.getRecordInfoTimeout(), TimeUnit.MILLISECONDS); if (recordInfo != null) { callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), recordInfo); }else { callback.run(ErrorCode.ERROR100.getCode(), ErrorCode.ERROR100.getMsg(), recordInfo); } } catch (InterruptedException e) { callback.run(ErrorCode.ERROR100.getCode(), e.getMessage(), null); } finally { this.topicSubscribers.remove("record" + sn); } }, (eventResult -> { callback.run(ErrorCode.ERROR100.getCode(), "查询录像失败, status: " + eventResult.statusCode + ", message: " + eventResult.msg, null); })); } catch (InvalidArgumentException | SipException | ParseException e) { log.error("[命令发送失败] 查询录像: {}", e.getMessage()); throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage()); } } @Override public void queryRecordInfo(CommonGBChannel channel, String startTime, String endTime, ErrorCallback callback) { if (channel.getDataType() != ChannelDataType.GB28181){ // 只支持国标的语音喊话 log.warn("[INFO 消息] 非国标设备, 通道ID: {}", channel.getGbId()); callback.run(ErrorCode.ERROR100.getCode(), "非国标设备", null); return; } Device device = deviceMapper.query(channel.getDataDeviceId()); if (device == null) { log.warn("[点播] 未找到通道{}的设备信息", channel); callback.run(ErrorCode.ERROR100.getCode(), "设备不存在", null); return; } DeviceChannel deviceChannel = getOneForSourceById(channel.getGbId()); queryRecordInfo(device, deviceChannel, startTime, endTime, callback); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/DeviceServiceImpl.java ================================================ package com.genersoft.iot.vmp.gb28181.service.impl; import com.alibaba.fastjson2.JSON; import com.genersoft.iot.vmp.common.CommonCallback; import com.genersoft.iot.vmp.common.enums.ChannelDataType; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.dao.CommonGBChannelMapper; import com.genersoft.iot.vmp.gb28181.dao.DeviceChannelMapper; import com.genersoft.iot.vmp.gb28181.dao.DeviceMapper; import com.genersoft.iot.vmp.gb28181.dao.PlatformChannelMapper; import com.genersoft.iot.vmp.gb28181.event.EventPublisher; import com.genersoft.iot.vmp.gb28181.event.channel.ChannelEvent; import com.genersoft.iot.vmp.gb28181.service.IDeviceService; import com.genersoft.iot.vmp.gb28181.service.IInviteStreamService; import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager; import com.genersoft.iot.vmp.gb28181.session.SipInviteSessionManager; import com.genersoft.iot.vmp.gb28181.task.deviceStatus.DeviceStatusTask; import com.genersoft.iot.vmp.gb28181.task.deviceStatus.DeviceStatusTaskInfo; import com.genersoft.iot.vmp.gb28181.task.deviceStatus.DeviceStatusTaskRunner; import com.genersoft.iot.vmp.gb28181.task.deviceSubscribe.SubscribeTask; import com.genersoft.iot.vmp.gb28181.task.deviceSubscribe.SubscribeTaskInfo; import com.genersoft.iot.vmp.gb28181.task.deviceSubscribe.SubscribeTaskRunner; import com.genersoft.iot.vmp.gb28181.task.deviceSubscribe.impl.SubscribeTaskForCatalog; import com.genersoft.iot.vmp.gb28181.task.deviceSubscribe.impl.SubscribeTaskForMobilPosition; import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander; import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd.CatalogResponseMessageHandler; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.service.IMediaServerService; import com.genersoft.iot.vmp.service.IReceiveRtpServerService; import com.genersoft.iot.vmp.service.ISendRtpServerService; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcService; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.ResourceBaseInfo; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import gov.nist.javax.sip.message.SIPResponse; import jakarta.validation.constraints.NotNull; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.core.annotation.Order; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; import javax.sip.InvalidArgumentException; import javax.sip.ResponseEvent; import javax.sip.SipException; import java.text.ParseException; import java.time.Instant; import java.util.*; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.TimeUnit; /** * 设备业务(目录订阅) */ @Slf4j @Service @Order(value=16) public class DeviceServiceImpl implements IDeviceService, CommandLineRunner { @Autowired private ISIPCommander sipCommander; @Autowired private CatalogResponseMessageHandler catalogResponseMessageHandler; @Autowired private IRedisCatchStorage redisCatchStorage; @Autowired private IInviteStreamService inviteStreamService; @Autowired private DeviceMapper deviceMapper; @Autowired private PlatformChannelMapper platformChannelMapper; @Autowired private DeviceChannelMapper deviceChannelMapper; @Autowired private CommonGBChannelMapper commonGBChannelMapper; @Autowired private EventPublisher eventPublisher; @Autowired private ISendRtpServerService sendRtpServerService; @Autowired private UserSetting userSetting; @Autowired private ISIPCommander commander; @Autowired private SipInviteSessionManager sessionManager; @Autowired private IMediaServerService mediaServerService; @Autowired private IReceiveRtpServerService receiveRtpServerService; @Autowired private AudioBroadcastManager audioBroadcastManager; @Autowired private IRedisRpcService redisRpcService; @Autowired private SubscribeTaskRunner subscribeTaskRunner; @Autowired private DeviceStatusTaskRunner deviceStatusTaskRunner; private Device getDeviceByDeviceIdFromDb(String deviceId) { return deviceMapper.getDeviceByDeviceId(deviceId); } @Override public void run(String... args) throws Exception { // 清理数据库不存在但是redis中存在的数据 List devicesInDb = getAll(); if (devicesInDb.isEmpty()) { redisCatchStorage.removeAllDevice(); }else { List devicesInRedis = redisCatchStorage.getAllDevices(); if (!devicesInRedis.isEmpty()) { Map deviceMapInDb = new HashMap<>(); devicesInDb.parallelStream().forEach(device -> { deviceMapInDb.put(device.getDeviceId(), device); }); devicesInRedis.parallelStream().forEach(device -> { if (deviceMapInDb.get(device.getDeviceId()) == null && userSetting.getServerId().equals(device.getServerId())) { redisCatchStorage.removeDevice(device.getDeviceId()); } }); } } // 重置cseq计数 redisCatchStorage.resetAllCSEQ(); // 处理设备状态 List allTaskInfo = deviceStatusTaskRunner.getAllTaskInfo(); List onlineDeviceIds = new ArrayList<>(); if (!allTaskInfo.isEmpty()) { for (DeviceStatusTaskInfo taskInfo : allTaskInfo) { Device device = getDeviceByDeviceId(taskInfo.getDeviceId()); if (device == null) { deviceStatusTaskRunner.removeTask(taskInfo.getDeviceId()); continue; } // 恢复定时任务, TCP因为连接已经断开必须等待设备重新连接 DeviceStatusTask deviceStatusTask = DeviceStatusTask.getInstance(taskInfo.getDeviceId(), taskInfo.getTransactionInfo(), taskInfo.getExpireTime() + 1000 + System.currentTimeMillis(), this::deviceStatusExpire); deviceStatusTaskRunner.addTask(deviceStatusTask); onlineDeviceIds.add(taskInfo.getDeviceId()); } // 除了记录的设备以外, 其他设备全部离线 List onlineDevice = getAllOnlineDevice(userSetting.getServerId()); if (!onlineDevice.isEmpty()) { List offlineDevices = new ArrayList<>(); for (Device device : onlineDevice) { if (!onlineDeviceIds.contains(device.getDeviceId())) { // 此设备需要离线 device.setOnLine(false); // 清理离线设备的相关缓存 cleanOfflineDevice(device); // 更新数据库 offlineDevices.add(device); } } if (!offlineDevices.isEmpty()) { offlineByIds(offlineDevices); } } }else { // 所有设备全部离线 List onlineDevice = getAllOnlineDevice(userSetting.getServerId()); for (Device device : onlineDevice) { // 此设备需要离线 device.setOnLine(false); // 清理离线设备的相关缓存 cleanOfflineDevice(device); } offlineByIds(onlineDevice); } // 处理订阅任务 List taskInfoList = subscribeTaskRunner.getAllTaskInfo(); if (!taskInfoList.isEmpty()) { for (SubscribeTaskInfo taskInfo : taskInfoList) { if (taskInfo == null) { continue; } Device device = getDeviceByDeviceId(taskInfo.getDeviceId()); if (device == null || !device.isOnLine() || !onlineDeviceIds.contains(taskInfo.getDeviceId())) { subscribeTaskRunner.removeSubscribe(taskInfo.getKey()); continue; } if (SubscribeTaskForCatalog.name.equals(taskInfo.getName())) { device.setSubscribeCycleForCatalog((int)taskInfo.getExpireTime()); SubscribeTask subscribeTask = SubscribeTaskForCatalog.getInstance(device, this::catalogSubscribeExpire, taskInfo.getTransactionInfo()); if (subscribeTask != null) { subscribeTaskRunner.addSubscribe(subscribeTask); } }else if (SubscribeTaskForMobilPosition.name.equals(taskInfo.getName())) { device.setSubscribeCycleForMobilePosition((int)taskInfo.getExpireTime()); SubscribeTask subscribeTask = SubscribeTaskForMobilPosition.getInstance(device, this::mobilPositionSubscribeExpire, taskInfo.getTransactionInfo()); if (subscribeTask != null) { subscribeTaskRunner.addSubscribe(subscribeTask); } } } } } private void offlineByIds(List offlineDevices) { if (offlineDevices.isEmpty()) { log.info("[更新多个离线设备信息] 参数为空"); return; } deviceMapper.offlineByList(offlineDevices); for (Device device : offlineDevices) { device.setOnLine(false); redisCatchStorage.updateDevice(device); } } private void cleanOfflineDevice(Device device) { if (subscribeTaskRunner.containsKey(SubscribeTaskForCatalog.getKey(device))) { subscribeTaskRunner.removeSubscribe(SubscribeTaskForCatalog.getKey(device)); } if (subscribeTaskRunner.containsKey(SubscribeTaskForMobilPosition.getKey(device))) { subscribeTaskRunner.removeSubscribe(SubscribeTaskForMobilPosition.getKey(device)); } // 离线释放所有ssrc List ssrcTransactions = sessionManager.getSsrcTransactionByDeviceId(device.getDeviceId()); if (ssrcTransactions != null && !ssrcTransactions.isEmpty()) { for (SsrcTransaction ssrcTransaction : ssrcTransactions) { mediaServerService.releaseSsrc(ssrcTransaction.getMediaServerId(), ssrcTransaction.getSsrc()); receiveRtpServerService.closeRTPServerByMediaServerId(ssrcTransaction.getMediaServerId(), ssrcTransaction.getApp(), ssrcTransaction.getStream()); sessionManager.removeByCallId(ssrcTransaction.getCallId()); } } // 移除订阅 removeCatalogSubscribe(device, null); removeMobilePositionSubscribe(device, null); List audioBroadcastCatches = audioBroadcastManager.getByDeviceId(device.getDeviceId()); if (!audioBroadcastCatches.isEmpty()) { for (AudioBroadcastCatch audioBroadcastCatch : audioBroadcastCatches) { SendRtpInfo sendRtpItem = sendRtpServerService.queryByChannelId(audioBroadcastCatch.getChannelId(), device.getDeviceId()); if (sendRtpItem != null) { sendRtpServerService.delete(sendRtpItem); MediaServer mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId()); mediaServerService.stopSendRtp(mediaInfo, sendRtpItem.getApp(), sendRtpItem.getStream(), null); } audioBroadcastManager.del(audioBroadcastCatch.getChannelId()); } } } private void deviceStatusExpire(String deviceId, SipTransactionInfo transactionInfo) { log.info("[设备状态] 到期, 编号: {}", deviceId); offline(deviceId, "保活到期", true); } @Override public void online(Device device, SipTransactionInfo sipTransactionInfo) { log.info("[设备上线] deviceId:{}->{}:{}", device.getDeviceId(), device.getIp(), device.getPort()); Device deviceInRedis = redisCatchStorage.getDevice(device.getDeviceId()); Device deviceInDb = getDeviceByDeviceIdFromDb(device.getDeviceId()); String now = DateUtil.getNow(); if (deviceInRedis != null && deviceInDb == null) { // redis 存在脏数据 inviteStreamService.clearInviteInfo(device.getDeviceId()); } device.setUpdateTime(now); device.setKeepaliveTime(now); if (device.getHeartBeatCount() == null) { // 读取设备配置, 获取心跳间隔和心跳超时次数, 在次之前暂时设置为默认值 device.setHeartBeatCount(3); device.setHeartBeatInterval(60); device.setPositionCapability(0); } if (sipTransactionInfo != null) { device.setSipTransactionInfo(sipTransactionInfo); }else { if (deviceInRedis != null) { device.setSipTransactionInfo(deviceInRedis.getSipTransactionInfo()); } } // 第一次上线 或则设备之前是离线状态--进行通道同步和设备信息查询 if (deviceInDb == null) { device.setOnLine(true); device.setCreateTime(now); device.setUpdateTime(now); log.info("[设备上线,首次注册]: {},查询设备信息以及通道信息", device.getDeviceId()); if(device.getStreamMode() == null) { device.setStreamMode("TCP-PASSIVE"); } deviceMapper.add(device); redisCatchStorage.updateDevice(device); try { commander.deviceInfoQuery(device, null); commander.deviceConfigQuery(device, null, "BasicParam", null); } catch (InvalidArgumentException | SipException | ParseException e) { log.error("[命令发送失败] 查询设备信息: {}", e.getMessage()); } sync(device); }else { device.setServerId(userSetting.getServerId()); if(!deviceInDb.isOnLine()){ device.setOnLine(true); device.setCreateTime(now); deviceMapper.update(device); redisCatchStorage.updateDevice(device); if (userSetting.getSyncChannelOnDeviceOnline()) { log.info("[设备上线,离线状态下重新注册]: {},查询设备信息以及通道信息", device.getDeviceId()); try { commander.deviceInfoQuery(device, null); } catch (InvalidArgumentException | SipException | ParseException e) { log.error("[命令发送失败] 查询设备信息: {}", e.getMessage()); } sync(device); }else { if (isDevice(device.getDeviceId())) { sync(device); } } // 上线添加订阅 if (device.getSubscribeCycleForCatalog() > 0 && !subscribeTaskRunner.containsKey(SubscribeTaskForCatalog.getKey(device))) { // 查询在线设备那些开启了订阅,为设备开启定时的目录订阅 addCatalogSubscribe(device, null); } if (device.getSubscribeCycleForMobilePosition() > 0 && !subscribeTaskRunner.containsKey(SubscribeTaskForMobilPosition.getKey(device))) { addMobilePositionSubscribe(device, null); } if (userSetting.getDeviceStatusNotify()) { // 发送redis消息 redisCatchStorage.sendDeviceOrChannelStatus(device.getDeviceId(), null, true); } }else { deviceMapper.update(device); redisCatchStorage.updateDevice(device); } if (deviceChannelMapper.queryChannelsByDeviceDbId(device.getId()).isEmpty()) { log.info("[设备上线]: {},通道数为0,查询通道信息", device.getDeviceId()); sync(device); } } long expiresTime = Math.min(device.getExpires(), device.getHeartBeatInterval() * device.getHeartBeatCount()) * 1000L; if (deviceStatusTaskRunner.containsKey(device.getDeviceId())) { if (sipTransactionInfo == null) { deviceStatusTaskRunner.updateDelay(device.getDeviceId(), expiresTime + System.currentTimeMillis()); }else { deviceStatusTaskRunner.removeTask(device.getDeviceId()); DeviceStatusTask task = DeviceStatusTask.getInstance(device.getDeviceId(), sipTransactionInfo, expiresTime + System.currentTimeMillis(), this::deviceStatusExpire); deviceStatusTaskRunner.addTask(task); } }else { DeviceStatusTask task = DeviceStatusTask.getInstance(device.getDeviceId(), sipTransactionInfo, expiresTime + System.currentTimeMillis(), this::deviceStatusExpire); deviceStatusTaskRunner.addTask(task); } } @Override @Transactional public void offline(String deviceId, String reason, boolean check) { Device device = getDeviceByDeviceIdFromDb(deviceId); if (device == null) { log.warn("[设备不存在] device:{}", deviceId); return; } // 主动查询设备状态, 没有HostAddress无法发送请求,可能是手动添加的设备 if (check && device.getHostAddress() != null) { Boolean deviceStatus = getDeviceStatus(device); if (deviceStatus != null && deviceStatus) { log.info("[设备离线] 主动探测发现设备在线,暂不处理 device:{}", deviceId); online(device, null); return; } } log.info("[设备离线] {}, device:{}, 心跳间隔: {},心跳超时次数: {}, 上次心跳时间:{}, 上次注册时间: {}", reason, deviceId, device.getHeartBeatInterval(), device.getHeartBeatCount(), device.getKeepaliveTime(), device.getRegisterTime()); device.setOnLine(false); cleanOfflineDevice(device); redisCatchStorage.updateDevice(device); deviceMapper.update(device); if (userSetting.getDeviceStatusNotify()) { // 发送redis消息 redisCatchStorage.sendDeviceOrChannelStatus(device.getDeviceId(), null, false); } if (isDevice(deviceId)) { channelOfflineByDevice(device); } } private void channelOfflineByDevice(Device device) { // 进行通道离线 List channelList = commonGBChannelMapper.queryOnlineListsByGbDeviceId(device.getId()); if (channelList.isEmpty()) { return; } deviceChannelMapper.offlineByDeviceId(device.getId()); // 发送通道离线通知 eventPublisher.channelEventPublish(channelList, ChannelEvent.ChannelEventMessageType.OFF); } private boolean isDevice(String deviceId) { GbCode decode = GbCode.decode(deviceId); if (decode == null) { return true; } int code = Integer.parseInt(decode.getTypeCode()); return code <= 199; } // 订阅丢失检查 @Scheduled(fixedDelay = 10, timeUnit = TimeUnit.SECONDS) public void lostCheckForSubscribe(){ // 获取所有设备 List deviceList = redisCatchStorage.getAllDevices(); if (deviceList.isEmpty()) { return; } for (Device device : deviceList) { if (device == null || !device.isOnLine() || !userSetting.getServerId().equals(device.getServerId())) { continue; } if (device.getSubscribeCycleForCatalog() > 0 && !subscribeTaskRunner.containsKey(SubscribeTaskForCatalog.getKey(device))) { log.debug("[订阅丢失] 目录订阅, 编号: {}, 重新发起订阅", device.getDeviceId()); addCatalogSubscribe(device, null); } if (device.getSubscribeCycleForMobilePosition() > 0 && !subscribeTaskRunner.containsKey(SubscribeTaskForMobilPosition.getKey(device))) { log.debug("[订阅丢失] 移动位置订阅, 编号: {}, 重新发起订阅", device.getDeviceId()); addMobilePositionSubscribe(device, null); } } } // 设备状态丢失检查 @Scheduled(fixedDelay = 30, timeUnit = TimeUnit.SECONDS) public void lostCheckForStatus(){ // 获取所有设备 List deviceList = redisCatchStorage.getAllDevices(); if (deviceList.isEmpty()) { return; } for (Device device : deviceList) { if (device == null || !device.isOnLine() || !userSetting.getServerId().equals(device.getServerId())) { continue; } if (!deviceStatusTaskRunner.containsKey(device.getDeviceId())) { log.debug("[状态丢失] 执行设备离线, 编号: {},", device.getDeviceId()); offline(device.getDeviceId(), "", true); } } } private void catalogSubscribeExpire(String deviceId, SipTransactionInfo transactionInfo) { log.info("[目录订阅] 到期, 编号: {}", deviceId); Device device = getDeviceByDeviceId(deviceId); if (device == null) { log.info("[目录订阅] 到期, 编号: {}, 设备不存在, 忽略", deviceId); return; } if (device.isOnLine() && device.getSubscribeCycleForCatalog() > 0) { addCatalogSubscribe(device, transactionInfo); } } private void mobilPositionSubscribeExpire(String deviceId, SipTransactionInfo transactionInfo) { log.info("[移动位置订阅] 到期, 编号: {}", deviceId); Device device = getDeviceByDeviceId(deviceId); if (device == null) { log.info("[移动位置订阅] 到期, 编号: {}, 设备不存在, 忽略", deviceId); return; } if (device.isOnLine() && device.getSubscribeCycleForMobilePosition() > 0) { addMobilePositionSubscribe(device, transactionInfo); } } @Override public boolean addCatalogSubscribe(@NotNull Device device, SipTransactionInfo transactionInfo) { if (device == null || device.getSubscribeCycleForCatalog() < 0) { return false; } if (transactionInfo == null) { log.info("[添加目录订阅] 设备 {}", device.getDeviceId()); }else { log.info("[目录订阅续期] 设备 {}", device.getDeviceId()); } try { sipCommander.catalogSubscribe(device, transactionInfo, eventResult -> { ResponseEvent event = (ResponseEvent) eventResult.event; // 成功 log.info("[目录订阅]成功: {}", device.getDeviceId()); if (!subscribeTaskRunner.containsKey(SubscribeTaskForCatalog.getKey(device))) { SIPResponse response = (SIPResponse) event.getResponse(); SipTransactionInfo transactionInfoForResponse = new SipTransactionInfo(response); SubscribeTask subscribeTask = SubscribeTaskForCatalog.getInstance(device, this::catalogSubscribeExpire, transactionInfoForResponse); if (subscribeTask != null) { subscribeTaskRunner.addSubscribe(subscribeTask); } }else { subscribeTaskRunner.updateDelay(SubscribeTaskForCatalog.getKey(device), (device.getSubscribeCycleForCatalog() * 1000L - 500L) + System.currentTimeMillis()); } },eventResult -> { // 失败 log.warn("[目录订阅]失败,信令发送失败: {}-{} ", device.getDeviceId(), eventResult.msg); }); } catch (InvalidArgumentException | SipException | ParseException e) { log.error("[命令发送失败] 目录订阅: {}", e.getMessage()); return false; } return true; } @Override public boolean removeCatalogSubscribe(@NotNull Device device, CommonCallback callback) { String key = SubscribeTaskForCatalog.getKey(device); if (subscribeTaskRunner.containsKey(key)) { log.info("[移除目录订阅]: {}", device.getDeviceId()); SipTransactionInfo transactionInfo = subscribeTaskRunner.getTransactionInfo(key); if (transactionInfo == null) { log.warn("[移除目录订阅] 未找到事务信息,{}", device.getDeviceId()); } try { device.setSubscribeCycleForCatalog(0); sipCommander.catalogSubscribe(device, transactionInfo, eventResult -> { // 成功 log.info("[取消目录订阅]成功: {}", device.getDeviceId()); subscribeTaskRunner.removeSubscribe(SubscribeTaskForCatalog.getKey(device)); if (callback != null) { callback.run(true); } },eventResult -> { // 失败 log.warn("[取消目录订阅]失败,信令发送失败: {}-{} ", device.getDeviceId(), eventResult.msg); }); }catch (Exception e) { // 失败 log.warn("[取消目录订阅]失败: {}-{} ", device.getDeviceId(), e.getMessage()); } } return true; } @Override public boolean addMobilePositionSubscribe(@NotNull Device device, SipTransactionInfo transactionInfo) { if (transactionInfo == null) { log.info("[添加移动位置订阅] 设备 {}", device.getDeviceId()); }else { log.info("[移动位置订阅续期] 设备 {}", device.getDeviceId()); } try { sipCommander.mobilePositionSubscribe(device, transactionInfo, eventResult -> { ResponseEvent event = (ResponseEvent) eventResult.event; // 成功 log.info("[移动位置订阅]成功: {}", device.getDeviceId()); if (!subscribeTaskRunner.containsKey(SubscribeTaskForMobilPosition.getKey(device))) { SIPResponse response = (SIPResponse) event.getResponse(); SipTransactionInfo transactionInfoForResponse = new SipTransactionInfo(response); SubscribeTask subscribeTask = SubscribeTaskForMobilPosition.getInstance(device, this::mobilPositionSubscribeExpire, transactionInfoForResponse); if (subscribeTask != null) { subscribeTaskRunner.addSubscribe(subscribeTask); } }else { subscribeTaskRunner.updateDelay(SubscribeTaskForMobilPosition.getKey(device), (device.getSubscribeCycleForMobilePosition() * 1000L - 500L) + System.currentTimeMillis()); } },eventResult -> { // 失败 log.warn("[移动位置订阅]失败,信令发送失败: {}-{} ", device.getDeviceId(), eventResult.msg); }); } catch (InvalidArgumentException | SipException | ParseException e) { log.error("[命令发送失败] 移动位置订阅: {}", e.getMessage()); return false; } return true; } @Override public boolean removeMobilePositionSubscribe(Device device, CommonCallback callback) { String key = SubscribeTaskForMobilPosition.getKey(device); if (subscribeTaskRunner.containsKey(key)) { log.info("[移除移动位置订阅]: {}", device.getDeviceId()); SipTransactionInfo transactionInfo = subscribeTaskRunner.getTransactionInfo(key); if (transactionInfo == null) { log.warn("[移除移动位置订阅] 未找到事务信息,{}", device.getDeviceId()); } try { device.setSubscribeCycleForMobilePosition(0); sipCommander.mobilePositionSubscribe(device, transactionInfo, eventResult -> { // 成功 log.info("[取消移动位置订阅]成功: {}", device.getDeviceId()); subscribeTaskRunner.removeSubscribe(SubscribeTaskForMobilPosition.getKey(device)); if (callback != null) { callback.run(true); } },eventResult -> { // 失败 log.warn("[取消移动位置订阅]失败,信令发送失败: {}-{} ", device.getDeviceId(), eventResult.msg); }); }catch (Exception e) { // 失败 log.warn("[取消移动位置订阅]失败: {}-{} ", device.getDeviceId(), e.getMessage()); } } return true; } @Override public SyncStatus getChannelSyncStatus(String deviceId) { Device device = deviceMapper.getDeviceByDeviceId(deviceId); if (device == null) { throw new ControllerException(ErrorCode.ERROR404.getCode(), "设备不存在"); } if (!userSetting.getServerId().equals(device.getServerId())) { return redisRpcService.getChannelSyncStatus(device.getServerId(), deviceId); } return catalogResponseMessageHandler.getChannelSyncProgress(deviceId); } @Override public Boolean isSyncRunning(String deviceId) { return catalogResponseMessageHandler.isSyncRunning(deviceId); } @Override public void sync(Device device) { if (catalogResponseMessageHandler.isSyncRunning(device.getDeviceId())) { SyncStatus syncStatus = catalogResponseMessageHandler.getChannelSyncProgress(device.getDeviceId()); log.info("[同步通道] 同步已存在, 设备: {}, 同步信息: {}", device.getDeviceId(), JSON.toJSON(syncStatus)); return; } int sn = (int)((Math.random()*9+1)*100000); catalogResponseMessageHandler.setChannelSyncReady(device, sn); try { sipCommander.catalogQuery(device, sn, event -> { String errorMsg = String.format("同步通道失败,错误码: %s, %s", event.statusCode, event.msg); log.info("[同步通道]失败,编号: {}, 错误码: {}, {}", device.getDeviceId(), event.statusCode, event.msg); catalogResponseMessageHandler.setChannelSyncEnd(device.getDeviceId(), sn, errorMsg); }); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[同步通道], 信令发送失败:{}", e.getMessage() ); String errorMsg = String.format("同步通道失败,信令发送失败: %s", e.getMessage()); catalogResponseMessageHandler.setChannelSyncEnd(device.getDeviceId(), sn, errorMsg); } } @Override public Device getDeviceByDeviceId(String deviceId) { Device device = redisCatchStorage.getDevice(deviceId); if (device == null) { device = getDeviceByDeviceIdFromDb(deviceId); if (device != null) { redisCatchStorage.updateDevice(device); } } return device; } @Override public List getAllOnlineDevice(String serverId) { return deviceMapper.getOnlineDevicesByServerId(serverId); } @Override public List getAllByStatus(Boolean status) { return deviceMapper.getDevices(ChannelDataType.GB28181, status); } @Override public boolean expire(Device device) { Instant registerTimeDate = Instant.from(DateUtil.formatter.parse(device.getRegisterTime())); Instant expireInstant = registerTimeDate.plusMillis(TimeUnit.SECONDS.toMillis(device.getExpires())); return expireInstant.isBefore(Instant.now()); } @Override public Boolean getDeviceStatus(@NotNull Device device) { SynchronousQueue queue = new SynchronousQueue<>(); try { sipCommander.deviceStatusQuery(device, ((code, msg, data) -> { queue.offer(msg); })); String data = queue.poll(10, TimeUnit.SECONDS); if (data != null && "ONLINE".equalsIgnoreCase(data.trim())) { return Boolean.TRUE; }else { return Boolean.FALSE; } } catch (InvalidArgumentException | SipException | ParseException | InterruptedException e) { log.error("[命令发送失败] 设备状态查询: {}", e.getMessage()); } return null; } @Override public Device getDeviceByHostAndPort(String host, int port) { return deviceMapper.getDeviceByHostAndPort(host, port); } @Override public void updateDevice(Device device) { device.setCharset(device.getCharset() == null ? "" : device.getCharset().toUpperCase()); device.setUpdateTime(DateUtil.getNow()); if (deviceMapper.update(device) > 0) { redisCatchStorage.updateDevice(device); } } @Transactional @Override public void updateDeviceList(List deviceList) { if (deviceList.isEmpty()){ log.info("[批量更新设备] 列表为空,更细失败"); return; } if (deviceList.size() == 1) { updateDevice(deviceList.get(0)); }else { for (Device device : deviceList) { device.setCharset(device.getCharset() == null ? "" : device.getCharset().toUpperCase()); device.setUpdateTime(DateUtil.getNow()); } int limitCount = 300; if (!deviceList.isEmpty()) { if (deviceList.size() > limitCount) { for (int i = 0; i < deviceList.size(); i += limitCount) { int toIndex = i + limitCount; if (i + limitCount > deviceList.size()) { toIndex = deviceList.size(); } deviceMapper.batchUpdate(deviceList.subList(i, toIndex)); } }else { deviceMapper.batchUpdate(deviceList); } for (Device device : deviceList) { redisCatchStorage.updateDevice(device); } } } } @Override public boolean isExist(String deviceId) { return getDeviceByDeviceIdFromDb(deviceId) != null; } @Override public void addCustomDevice(Device device) { device.setOnLine(false); device.setCreateTime(DateUtil.getNow()); device.setUpdateTime(DateUtil.getNow()); if(device.getStreamMode() == null) { device.setStreamMode("TCP-PASSIVE"); } deviceMapper.addCustomDevice(device); } @Override public void updateCustomDevice(Device device) { // 订阅状态的修改使用一个单独方法控制,此处不再进行状态修改 Device deviceInStore = deviceMapper.query(device.getId()); if (deviceInStore == null) { log.warn("更新设备时未找到设备信息"); return; } if (deviceInStore.getGeoCoordSys() != null) { // 坐标系变化,需要重新计算GCJ02坐标和WGS84坐标 if (!deviceInStore.getGeoCoordSys().equals(device.getGeoCoordSys())) { deviceInStore.setGeoCoordSys(device.getGeoCoordSys()); } }else { deviceInStore.setGeoCoordSys("WGS84"); } if (device.getCharset() == null) { deviceInStore.setCharset("GB2312"); } deviceMapper.updateCustom(device); redisCatchStorage.updateDevice(device); } @Override @Transactional public boolean delete(String deviceId) { Device device = getDeviceByDeviceIdFromDb(deviceId); Assert.notNull(device, "未找到设备"); if (subscribeTaskRunner.containsKey(SubscribeTaskForCatalog.getKey(device))) { removeCatalogSubscribe(device, null); } if (subscribeTaskRunner.containsKey(SubscribeTaskForMobilPosition.getKey(device))) { removeMobilePositionSubscribe(device, null); } if (deviceStatusTaskRunner.containsKey(deviceId)) { deviceStatusTaskRunner.removeTask(deviceId); } List commonGBChannels = commonGBChannelMapper.queryByDataTypeAndDeviceIds(1, List.of(device.getId())); try { // 发送catalog eventPublisher.channelEventPublish(commonGBChannels, ChannelEvent.ChannelEventMessageType.DEL); } catch (Exception e) { log.warn("[多个通道删除] 发送失败,数量:{}", commonGBChannels.size(), e); } platformChannelMapper.delChannelForDeviceId(deviceId); deviceChannelMapper.cleanChannelsByDeviceId(device.getId()); deviceMapper.del(deviceId); redisCatchStorage.removeDevice(deviceId); inviteStreamService.clearInviteInfo(deviceId); return true; } @Override public ResourceBaseInfo getOverview() { List onlineDevices = deviceMapper.getOnlineDevices(); List all = deviceMapper.getAll(); return new ResourceBaseInfo(all.size(), onlineDevices.size()); } @Override public List getAll() { return deviceMapper.getAll(); } @Override public PageInfo getAll(int page, int count, String query, Boolean status) { PageHelper.startPage(page, count); if (query != null) { query = query.replaceAll("/", "//") .replaceAll("%", "/%") .replaceAll("_", "/_"); } List all = deviceMapper.getDeviceList(ChannelDataType.GB28181, query, status); return new PageInfo<>(all); } @Override public Device getDevice(Integer id) { return deviceMapper.query(id); } @Override public Device getDeviceByChannelId(Integer channelId) { return deviceMapper.queryByChannelId(ChannelDataType.GB28181,channelId); } @Override public Device getDeviceBySourceChannelDeviceId(String channelId) { return deviceMapper.getDeviceBySourceChannelDeviceId(ChannelDataType.GB28181,channelId); } @Override public void subscribeCatalog(int id, int cycle) { Device device = deviceMapper.query(id); Assert.notNull(device, "未找到设备"); Assert.isTrue(device.isOnLine(), "设备已离线"); if (device.getSubscribeCycleForCatalog() == cycle) { return; } if (!userSetting.getServerId().equals(device.getServerId())) { redisRpcService.subscribeCatalog(id, cycle); return; } // 目录订阅相关的信息 if (device.getSubscribeCycleForCatalog() > 0) { // 订阅周期不同,则先取消 removeCatalogSubscribe(device, result->{ device.setSubscribeCycleForCatalog(cycle); updateDevice(device); if (cycle > 0) { // 开启订阅 addCatalogSubscribe(device, null); } }); }else { // 开启订阅 device.setSubscribeCycleForCatalog(cycle); updateDevice(device); addCatalogSubscribe(device, null); } } @Override public void subscribeMobilePosition(int id, int cycle, int interval) { Device device = deviceMapper.query(id); Assert.notNull(device, "未找到设备"); if (!device.isOnLine()) { // 开启订阅 device.setSubscribeCycleForMobilePosition(cycle); device.setMobilePositionSubmissionInterval(interval); updateDevice(device); if (subscribeTaskRunner.containsKey(SubscribeTaskForMobilPosition.getKey(device))) { subscribeTaskRunner.removeSubscribe(SubscribeTaskForMobilPosition.getKey(device)); } throw new ControllerException(ErrorCode.ERROR100.getCode(), "设备已离线"); } if (device.getSubscribeCycleForMobilePosition() == cycle) { return; } if (!userSetting.getServerId().equals(device.getServerId())) { redisRpcService.subscribeMobilePosition(id, cycle, interval); return; } // 目录订阅相关的信息 if (device.getSubscribeCycleForMobilePosition() > 0) { // 订阅周期已经开启,则先取消 removeMobilePositionSubscribe(device, result->{ // 开启订阅 device.setSubscribeCycleForMobilePosition(cycle); device.setMobilePositionSubmissionInterval(interval); updateDevice(device); if (cycle > 0) { addMobilePositionSubscribe(device, null); } }); }else { // 订阅未开启 device.setSubscribeCycleForMobilePosition(cycle); device.setMobilePositionSubmissionInterval(interval); updateDevice(device); // 开启订阅 addMobilePositionSubscribe(device, null); } } @Override public void updateDeviceHeartInfo(Device device) { Device deviceInDb = deviceMapper.query(device.getId()); if (deviceInDb == null) { return; } if (!Objects.equals(deviceInDb.getHeartBeatCount(), device.getHeartBeatCount()) || !Objects.equals(deviceInDb.getHeartBeatInterval(), device.getHeartBeatInterval())) { deviceInDb.setHeartBeatCount(device.getHeartBeatCount()); deviceInDb.setHeartBeatInterval(device.getHeartBeatInterval()); deviceInDb.setPositionCapability(device.getPositionCapability()); updateDevice(deviceInDb); long expiresTime = Math.min(device.getExpires(), device.getHeartBeatInterval() * device.getHeartBeatCount()) * 1000L; if (deviceStatusTaskRunner.containsKey(device.getDeviceId())) { deviceStatusTaskRunner.updateDelay(device.getDeviceId(), expiresTime + System.currentTimeMillis()); } } } @Override public WVPResult devicesSync(Device device) { if (device.getServerId() != null && !userSetting.getServerId().equals(device.getServerId())) { return redisRpcService.devicesSync(device.getServerId(), device.getDeviceId()); } // 已存在则返回进度 if (isSyncRunning(device.getDeviceId())) { SyncStatus channelSyncStatus = getChannelSyncStatus(device.getDeviceId()); WVPResult wvpResult = new WVPResult(); if (channelSyncStatus.getErrorMsg() != null) { wvpResult.setCode(ErrorCode.ERROR100.getCode()); wvpResult.setMsg(channelSyncStatus.getErrorMsg()); }else if (channelSyncStatus.getTotal() == null || channelSyncStatus.getTotal() == 0){ wvpResult.setCode(ErrorCode.SUCCESS.getCode()); wvpResult.setMsg("等待通道信息..."); }else { wvpResult.setCode(ErrorCode.SUCCESS.getCode()); wvpResult.setMsg(ErrorCode.SUCCESS.getMsg()); wvpResult.setData(channelSyncStatus); } return wvpResult; } sync(device); WVPResult wvpResult = new WVPResult<>(); wvpResult.setCode(0); wvpResult.setMsg("开始同步"); return wvpResult; } @Override public void deviceBasicConfig(Device device, BasicParam basicParam, ErrorCallback callback) { if (!userSetting.getServerId().equals(device.getServerId())) { WVPResult result = redisRpcService.deviceBasicConfig(device.getServerId(), device, basicParam); if (result.getCode() == ErrorCode.SUCCESS.getCode()) { callback.run(result.getCode(), result.getMsg(), result.getData()); } return; } try { sipCommander.deviceBasicConfigCmd(device, basicParam, callback); } catch (InvalidArgumentException | SipException | ParseException e) { log.error("[命令发送失败] 设备配置: {}", e.getMessage()); callback.run(ErrorCode.ERROR100.getCode(), "命令发送: " + e.getMessage(), null); throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送: " + e.getMessage()); } } @Override public void deviceConfigQuery(Device device, String channelId, String configType, ErrorCallback callback) { if (!userSetting.getServerId().equals(device.getServerId())) { WVPResult result = redisRpcService.deviceConfigQuery(device.getServerId(), device, channelId, configType); callback.run(result.getCode(), result.getMsg(), result.getData()); return; } try { sipCommander.deviceConfigQuery(device, channelId, configType, callback); } catch (InvalidArgumentException | SipException | ParseException e) { log.error("[命令发送失败] 获取设备配置: {}", e.getMessage()); callback.run(ErrorCode.ERROR100.getCode(), "命令发送: " + e.getMessage(), null); throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送: " + e.getMessage()); } } @Override public void teleboot(Device device) { if (!userSetting.getServerId().equals(device.getServerId())) { redisRpcService.teleboot(device.getServerId(), device); } try { sipCommander.teleBootCmd(device); } catch (InvalidArgumentException | SipException | ParseException e) { log.error("[命令发送失败] 远程启动: {}", e.getMessage()); throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage()); } } @Override public void record(Device device, String channelId, String recordCmdStr, ErrorCallback callback) { if (!userSetting.getServerId().equals(device.getServerId())) { WVPResult result = redisRpcService.recordControl(device.getServerId(), device, channelId, recordCmdStr); callback.run(result.getCode(), result.getMsg(), result.getData()); return; } try { sipCommander.recordCmd(device, channelId, recordCmdStr, callback); } catch (InvalidArgumentException | SipException | ParseException e) { log.error("[命令发送失败] 开始/停止录像: {}", e.getMessage()); callback.run(ErrorCode.ERROR100.getCode(), "命令发送: " + e.getMessage(), null); throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送: " + e.getMessage()); } } @Override public void guard(Device device, String guardCmdStr, ErrorCallback callback) { if (!userSetting.getServerId().equals(device.getServerId())) { WVPResult result = redisRpcService.guard(device.getServerId(), device, guardCmdStr); callback.run(result.getCode(), result.getMsg(), result.getData()); return; } try { sipCommander.guardCmd(device, guardCmdStr, callback); } catch (InvalidArgumentException | SipException | ParseException e) { log.error("[命令发送失败] 布防/撤防操作: {}", e.getMessage()); callback.run(ErrorCode.ERROR100.getCode(), "命令发送: " + e.getMessage(), null); throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送: " + e.getMessage()); } } @Override public void resetAlarm(Device device, String channelId, String alarmMethod, String alarmType, ErrorCallback callback) { if (!userSetting.getServerId().equals(device.getServerId())) { WVPResult result = redisRpcService.resetAlarm(device.getServerId(), device, channelId, alarmMethod, alarmType); callback.run(result.getCode(), result.getMsg(), result.getData()); return; } try { sipCommander.alarmResetCmd(device, alarmMethod, alarmType, callback); } catch (InvalidArgumentException | SipException | ParseException e) { log.error("[命令发送失败] 布防/撤防操作: {}", e.getMessage()); callback.run(ErrorCode.ERROR100.getCode(), "命令发送: " + e.getMessage(), null); throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送: " + e.getMessage()); } } @Override public void iFrame(Device device, String channelId) { if (!userSetting.getServerId().equals(device.getServerId())) { redisRpcService.iFrame(device.getServerId(), device, channelId); return; } try { sipCommander.iFrameCmd(device, channelId); } catch (InvalidArgumentException | SipException | ParseException e) { log.error("[命令发送失败] 强制关键帧操作: {}", e.getMessage()); throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送: " + e.getMessage()); } } @Override public void homePosition(Device device, String channelId, Boolean enabled, Integer resetTime, Integer presetIndex, ErrorCallback callback) { if (!userSetting.getServerId().equals(device.getServerId())) { WVPResult result = redisRpcService.homePosition(device.getServerId(), device, channelId, enabled, resetTime, presetIndex); callback.run(result.getCode(), result.getMsg(), result.getData()); return; } try { sipCommander.homePositionCmd(device, channelId, enabled, resetTime, presetIndex, callback); } catch (InvalidArgumentException | SipException | ParseException e) { log.error("[命令发送失败] 看守位控制: {}", e.getMessage()); callback.run(ErrorCode.ERROR100.getCode(), "命令发送: " + e.getMessage(), null); throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage()); } } @Override public void dragZoomIn(Device device, String channelId, int length, int width, int midpointx, int midpointy, int lengthx, int lengthy, ErrorCallback callback) { if (!userSetting.getServerId().equals(device.getServerId())) { redisRpcService.dragZoomIn(device.getServerId(), device, channelId, length, width, midpointx, midpointy, lengthx, lengthy); return; } StringBuffer cmdXml = new StringBuffer(200); cmdXml.append("\r\n"); cmdXml.append("" + length+ "\r\n"); cmdXml.append("" + width+ "\r\n"); cmdXml.append("" + midpointx+ "\r\n"); cmdXml.append("" + midpointy+ "\r\n"); cmdXml.append("" + lengthx+ "\r\n"); cmdXml.append("" + lengthy+ "\r\n"); cmdXml.append("\r\n"); try { sipCommander.dragZoomCmd(device, channelId, cmdXml.toString(), callback); } catch (InvalidArgumentException | SipException | ParseException e) { log.error("[命令发送失败] 拉框放大: {}", e.getMessage()); callback.run(ErrorCode.ERROR100.getCode(), "命令发送: " + e.getMessage(), null); throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage()); } } @Override public void dragZoomOut(Device device, String channelId, int length, int width, int midpointx, int midpointy, int lengthx, int lengthy, ErrorCallback callback) { if (!userSetting.getServerId().equals(device.getServerId())) { redisRpcService.dragZoomOut(device.getServerId(), device, channelId, length, width, midpointx, midpointy, lengthx, lengthy); return; } StringBuffer cmdXml = new StringBuffer(200); cmdXml.append("\r\n"); cmdXml.append("" + length+ "\r\n"); cmdXml.append("" + width+ "\r\n"); cmdXml.append("" + midpointx+ "\r\n"); cmdXml.append("" + midpointy+ "\r\n"); cmdXml.append("" + lengthx+ "\r\n"); cmdXml.append("" + lengthy+ "\r\n"); cmdXml.append("\r\n"); try { sipCommander.dragZoomCmd(device, channelId, cmdXml.toString(), callback); } catch (InvalidArgumentException | SipException | ParseException e) { log.error("[命令发送失败] 拉框放大: {}", e.getMessage()); callback.run(ErrorCode.ERROR100.getCode(), "命令发送: " + e.getMessage(), null); throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage()); } } @Override public void deviceStatus(Device device, ErrorCallback callback) { if (!userSetting.getServerId().equals(device.getServerId())) { WVPResult result = redisRpcService.deviceStatus(device.getServerId(), device); callback.run(result.getCode(), result.getMsg(), result.getData()); return; } try { sipCommander.deviceStatusQuery(device, (code, msg, data) -> { if ("ONLINE".equalsIgnoreCase(data.trim())) { online(device, null); }else { offline(device.getDeviceId(), "设备状态查询结果:" + data.trim(), true); } if (callback != null) { callback.run(code, msg, data); } }); } catch (InvalidArgumentException | SipException | ParseException e) { log.error("[命令发送失败] 获取设备状态: {}", e.getMessage()); callback.run(ErrorCode.ERROR100.getCode(), "命令发送: " + e.getMessage(), null); throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage()); } } @Override public void alarm(Device device, String startPriority, String endPriority, String alarmMethod, String alarmType, String startTime, String endTime, ErrorCallback callback) { if (!userSetting.getServerId().equals(device.getServerId())) { WVPResult result = redisRpcService.alarm(device.getServerId(), device, startPriority, endPriority, alarmMethod, alarmType, startTime, endTime); callback.run(result.getCode(), result.getMsg(), result.getData()); return; } String startAlarmTime = ""; if (startTime != null) { startAlarmTime = DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(startTime); } String endAlarmTime = ""; if (startTime != null) { endAlarmTime = DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(endTime); } try { sipCommander.alarmInfoQuery(device, startPriority, endPriority, alarmMethod, alarmType, startAlarmTime, endAlarmTime, callback); } catch (InvalidArgumentException | SipException | ParseException e) { log.error("[命令发送失败] 获取设备状态: {}", e.getMessage()); callback.run(ErrorCode.ERROR100.getCode(), "命令发送: " + e.getMessage(), null); throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage()); } } @Override public void deviceInfo(Device device, ErrorCallback callback) { if (!userSetting.getServerId().equals(device.getServerId())) { WVPResult result = redisRpcService.deviceInfo(device.getServerId(), device); callback.run(result.getCode(), result.getMsg(), result.getData()); return; } try { sipCommander.deviceInfoQuery(device, callback); } catch (InvalidArgumentException | SipException | ParseException e) { log.error("[命令发送失败] 获取设备信息: {}", e.getMessage()); callback.run(ErrorCode.ERROR100.getCode(), "命令发送: " + e.getMessage(), null); throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage()); } } @Override public void queryPreset(Device device, String channelId, ErrorCallback> callback) { if (!userSetting.getServerId().equals(device.getServerId())) { WVPResult> result = redisRpcService.queryPreset(device.getServerId(), device, channelId); callback.run(result.getCode(), result.getMsg(), result.getData()); return; } try { sipCommander.presetQuery(device, channelId, callback); } catch (InvalidArgumentException | SipException | ParseException e) { log.error("[命令发送失败] 预制位查询: {}", e.getMessage()); callback.run(ErrorCode.ERROR100.getCode(), "命令发送: " + e.getMessage(), null); throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage()); } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/GbChannelControlServiceImpl.java ================================================ package com.genersoft.iot.vmp.gb28181.service.impl; import com.genersoft.iot.vmp.common.enums.ChannelDataType; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.service.IGbChannelControlService; import com.genersoft.iot.vmp.gb28181.service.ISourcePTZService; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import javax.sip.message.Response; import java.util.List; import java.util.Map; @Service @Slf4j public class GbChannelControlServiceImpl implements IGbChannelControlService { @Autowired private Map sourcePTZServiceMap; @Override public void ptz(CommonGBChannel channel, FrontEndControlCodeForPTZ frontEndControlCode, ErrorCallback callback) { log.info("[通用通道] 云台控制, 类型: {}, 编号:{}", channel.getDataType(), channel.getGbDeviceId()); Integer dataType = channel.getDataType(); ISourcePTZService sourcePTZService = sourcePTZServiceMap.get(ChannelDataType.PTZ_SERVICE + dataType); if (sourcePTZService == null) { // 通道数据异常 log.error("[点播通用通道] 类型: {} 不支持云台控制", dataType); throw new PlayException(Response.BUSY_HERE, "channel not support"); } sourcePTZService.ptz(channel, frontEndControlCode, callback); } @Override public void preset(CommonGBChannel channel, FrontEndControlCodeForPreset frontEndControlCode, ErrorCallback callback) { log.info("[通用通道] 预置位控制, 类型: {}, 编号:{}", channel.getDataType(), channel.getGbDeviceId()); Integer dataType = channel.getDataType(); ISourcePTZService sourcePTZService = sourcePTZServiceMap.get(ChannelDataType.PTZ_SERVICE + dataType); if (sourcePTZService == null) { // 通道数据异常 log.error("[点播通用通道] 类型: {} 不支持预置位控制", dataType); throw new PlayException(Response.BUSY_HERE, "channel not support"); } sourcePTZService.preset(channel, frontEndControlCode, callback); } @Override public void fi(CommonGBChannel channel, FrontEndControlCodeForFI frontEndControlCode, ErrorCallback callback) { log.info("[通用通道] FI指令, 类型: {}, 编号:{}", channel.getDataType(), channel.getGbDeviceId()); Integer dataType = channel.getDataType(); ISourcePTZService sourcePTZService = sourcePTZServiceMap.get(ChannelDataType.PTZ_SERVICE + dataType); if (sourcePTZService == null) { // 通道数据异常 log.error("[点播通用通道] 类型: {} 不支持FI指令", dataType); throw new PlayException(Response.BUSY_HERE, "channel not support"); } sourcePTZService.fi(channel, frontEndControlCode, callback); } @Override public void tour(CommonGBChannel channel, FrontEndControlCodeForTour frontEndControlCode, ErrorCallback callback) { log.info("[通用通道] 巡航指令, 类型: {}, 编号:{}", channel.getDataType(), channel.getGbDeviceId()); Integer dataType = channel.getDataType(); ISourcePTZService sourcePTZService = sourcePTZServiceMap.get(ChannelDataType.PTZ_SERVICE + dataType); if (sourcePTZService == null) { // 通道数据异常 log.error("[点播通用通道] 类型: {} 不支持巡航指令", dataType); throw new PlayException(Response.BUSY_HERE, "channel not support"); } sourcePTZService.tour(channel, frontEndControlCode, callback); } @Override public void scan(CommonGBChannel channel, FrontEndControlCodeForScan frontEndControlCode, ErrorCallback callback) { log.info("[通用通道] 扫描指令, 类型: {}, 编号:{}", channel.getDataType(), channel.getGbDeviceId()); Integer dataType = channel.getDataType(); ISourcePTZService sourcePTZService = sourcePTZServiceMap.get(ChannelDataType.PTZ_SERVICE + dataType); if (sourcePTZService == null) { // 通道数据异常 log.error("[点播通用通道] 类型: {} 不支持扫描指令", dataType); throw new PlayException(Response.BUSY_HERE, "channel not support"); } sourcePTZService.scan(channel, frontEndControlCode, callback); } @Override public void auxiliary(CommonGBChannel channel, FrontEndControlCodeForAuxiliary frontEndControlCode, ErrorCallback callback) { log.info("[通用通道] 辅助开关控制指令, 类型: {}, 编号:{}", channel.getDataType(), channel.getGbDeviceId()); Integer dataType = channel.getDataType(); ISourcePTZService sourcePTZService = sourcePTZServiceMap.get(ChannelDataType.PTZ_SERVICE + dataType); if (sourcePTZService == null) { // 通道数据异常 log.error("[点播通用通道] 类型: {} 不支持辅助开关控制指令", dataType); throw new PlayException(Response.BUSY_HERE, "channel not support"); } sourcePTZService.auxiliary(channel, frontEndControlCode, callback); } @Override public void wiper(CommonGBChannel channel, FrontEndControlCodeForWiper frontEndControlCode, ErrorCallback callback) { log.info("[通用通道] 雨刷控制, 类型: {}, 编号:{}", channel.getDataType(), channel.getGbDeviceId()); Integer dataType = channel.getDataType(); ISourcePTZService sourcePTZService = sourcePTZServiceMap.get(ChannelDataType.PTZ_SERVICE + dataType); if (sourcePTZService == null) { // 通道数据异常 log.error("[点播通用通道] 类型: {} 不支持雨刷控制", dataType); throw new PlayException(Response.BUSY_HERE, "channel not support"); } sourcePTZService.wiper(channel, frontEndControlCode, callback); } @Override public void queryPreset(CommonGBChannel channel, ErrorCallback> callback) { log.info("[通用通道] 预置位查询, 类型: {}, 编号:{}", channel.getDataType(), channel.getGbDeviceId()); Integer dataType = channel.getDataType(); ISourcePTZService sourcePTZService = sourcePTZServiceMap.get(ChannelDataType.PTZ_SERVICE + dataType); if (sourcePTZService == null) { // 通道数据异常 log.error("[点播通用通道] 类型: {} 不支持预置位查询", dataType); throw new PlayException(Response.BUSY_HERE, "channel not support"); } sourcePTZService.queryPreset(channel, callback); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/GbChannelPlayServiceImpl.java ================================================ package com.genersoft.iot.vmp.gb28181.service.impl; import com.genersoft.iot.vmp.common.InviteSessionType; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.common.enums.ChannelDataType; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.dao.CommonGBChannelMapper; import com.genersoft.iot.vmp.gb28181.service.IGbChannelPlayService; import com.genersoft.iot.vmp.gb28181.service.ISourceDownloadService; import com.genersoft.iot.vmp.gb28181.service.ISourcePlayService; import com.genersoft.iot.vmp.gb28181.service.ISourcePlaybackService; import com.genersoft.iot.vmp.jt1078.service.Ijt1078PlayService; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.service.bean.InviteErrorCode; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import javax.sip.message.Response; import java.util.List; import java.util.Map; @Slf4j @Service public class GbChannelPlayServiceImpl implements IGbChannelPlayService { @Autowired private UserSetting userSetting; @Autowired private CommonGBChannelMapper channelMapper; @Autowired private Map sourcePlayServiceMap; @Autowired private Ijt1078PlayService jt1078PlayService; @Autowired private Map sourcePlaybackServiceMap; @Autowired private Map sourceDownloadServiceMap; @Override public void startInvite(CommonGBChannel channel, InviteMessageInfo inviteInfo, Platform platform, ErrorCallback callback) { if (channel == null || inviteInfo == null || callback == null || channel.getDataType() == null) { log.warn("[通用通道点播] 参数异常, channel: {}, inviteInfo: {}, callback: {}", channel != null, inviteInfo != null, callback != null); throw new PlayException(Response.SERVER_INTERNAL_ERROR, "server internal error"); } log.info("[点播通用通道] 类型:{}, 通道: {}({})", inviteInfo.getSessionName(), channel.getGbName(), channel.getGbDeviceId()); if ("Play".equalsIgnoreCase(inviteInfo.getSessionName())) { play(channel, platform, userSetting.getRecordSip(), callback); }else if ("Playback".equals(inviteInfo.getSessionName())) { playback(channel, inviteInfo.getStartTime(), inviteInfo.getStopTime(), callback); }else if ("Download".equals(inviteInfo.getSessionName())) { Integer downloadSpeed = Integer.parseInt(inviteInfo.getDownloadSpeed()); // 国标通道 download(channel, inviteInfo.getStartTime(), inviteInfo.getStopTime(), downloadSpeed, callback); }else { // 不支持的点播方式 log.error("[点播通用通道] 不支持的点播方式:{}, {}({})", inviteInfo.getSessionName(), channel.getGbName(), channel.getGbDeviceId()); throw new PlayException(Response.BAD_REQUEST, "bad request"); } } @Override public void stopInvite(InviteSessionType type, CommonGBChannel channel, String stream) { switch (type) { case PLAY: stopPlay(channel); break; case PLAYBACK: stopPlayback(channel, stream); break; case DOWNLOAD: stopDownload(channel, stream); break; default: // 通道数据异常 log.error("[点播通用通道] 类型编号: {} 不支持此类型请求", type); throw new PlayException(Response.BUSY_HERE, "channel not support"); } } @Override public void play(CommonGBChannel channel, Platform platform, Boolean record, ErrorCallback callback) { log.info("[通用通道] 播放, 类型: {}, 编号:{}", ChannelDataType.getDateTypeDesc(channel.getDataType()), channel.getGbDeviceId()); Integer dataType = channel.getDataType(); ISourcePlayService sourceChannelPlayService = sourcePlayServiceMap.get(ChannelDataType.PLAY_SERVICE + dataType); if (sourceChannelPlayService == null) { // 通道数据异常 log.error("[点播通用通道] 类型编号: {} 不支持实时流预览", ChannelDataType.getDateTypeDesc(channel.getDataType())); throw new PlayException(Response.BUSY_HERE, "channel not support"); } sourceChannelPlayService.play(channel, platform, record, (code, msg, data) -> { if (code == InviteErrorCode.SUCCESS.getCode()) { // 将流ID记录到数据库 if (channel.getDataType() != ChannelDataType.GB28181) { channelMapper.updateStream(channel.getGbId(), data.getStream()); } } callback.run(code, msg, data); }); } @Override public void playback(CommonGBChannel channel, Long startTime, Long stopTime, ErrorCallback callback) { log.info("[通用通道] 回放, 类型: {}, 编号:{}", ChannelDataType.getDateTypeDesc(channel.getDataType()), channel.getGbDeviceId()); Integer dataType = channel.getDataType(); ISourcePlaybackService playbackService = sourcePlaybackServiceMap.get(ChannelDataType.PLAYBACK_SERVICE + dataType); if (playbackService == null) { // 通道数据异常 log.error("[点播通用通道] 类型编号: {} 不支持回放", dataType); throw new PlayException(Response.BUSY_HERE, "channel not support"); } playbackService.playback(channel, startTime, stopTime, callback); } @Override public void download(CommonGBChannel channel, Long startTime, Long stopTime, Integer downloadSpeed, ErrorCallback callback){ log.info("[通用通道] 录像下载, 类型: {}, 编号:{}", ChannelDataType.getDateTypeDesc(channel.getDataType()), channel.getGbDeviceId()); Integer dataType = channel.getDataType(); ISourceDownloadService downloadService = sourceDownloadServiceMap.get(ChannelDataType.DOWNLOAD_SERVICE + dataType); if (downloadService == null) { // 通道数据异常 log.error("[点播通用通道] 类型编号: {} 不支持录像下载", dataType); throw new PlayException(Response.BUSY_HERE, "channel not support"); } downloadService.download(channel, startTime, stopTime, downloadSpeed, callback); } @Override public void stopPlay(CommonGBChannel channel) { Integer dataType = channel.getDataType(); ISourcePlayService sourceChannelPlayService = sourcePlayServiceMap.get(ChannelDataType.PLAY_SERVICE + dataType); if (sourceChannelPlayService == null) { // 通道数据异常 log.error("[点播通用通道] 类型编号: {} 不支持停止实时流", dataType); throw new PlayException(Response.BUSY_HERE, "channel not support"); } sourceChannelPlayService.stopPlay(channel); channelMapper.updateStream(channel.getGbId(), null); } @Override public void stopPlayback(CommonGBChannel channel, String stream) { log.info("[通用通道] 停止回放, 类型: {}, 编号:{}", ChannelDataType.getDateTypeDesc(channel.getDataType()), channel.getGbDeviceId()); Integer dataType = channel.getDataType(); ISourcePlaybackService playbackService = sourcePlaybackServiceMap.get(ChannelDataType.PLAYBACK_SERVICE + dataType); if (playbackService == null) { // 通道数据异常 log.error("[点播通用通道] 类型编号: {} 不支持回放", dataType); throw new PlayException(Response.BUSY_HERE, "channel not support"); } playbackService.stopPlayback(channel, stream); } @Override public void stopDownload(CommonGBChannel channel, String stream) { log.info("[通用通道] 停止录像下载, 类型: {}, 编号:{} stream: {}", ChannelDataType.getDateTypeDesc(channel.getDataType()), channel.getGbDeviceId(), stream); Integer dataType = channel.getDataType(); ISourceDownloadService downloadService = sourceDownloadServiceMap.get(ChannelDataType.DOWNLOAD_SERVICE + dataType); if (downloadService == null) { // 通道数据异常 log.error("[点播通用通道] 类型编号: {} 不支持录像下载", dataType); throw new PlayException(Response.BUSY_HERE, "channel not support"); } downloadService.stopDownload(channel, stream); } @Override public void playbackPause(CommonGBChannel channel, String stream) { log.info("[通用通道] 回放暂停, 类型: {}, 编号:{} stream:{}", ChannelDataType.getDateTypeDesc(channel.getDataType()), channel.getGbDeviceId(), stream); Integer dataType = channel.getDataType(); ISourcePlaybackService playbackService = sourcePlaybackServiceMap.get(ChannelDataType.PLAYBACK_SERVICE + dataType); if (playbackService == null) { // 通道数据异常 log.error("[点播通用通道] 类型编号: {} 不支持回放暂停", dataType); throw new PlayException(Response.BUSY_HERE, "channel not support"); } playbackService.playbackPause(channel, stream); } @Override public void playbackResume(CommonGBChannel channel, String stream) { log.info("[通用通道] 回放暂停恢复, 类型: {}, 编号:{} stream:{}", ChannelDataType.getDateTypeDesc(channel.getDataType()), channel.getGbDeviceId(), stream); Integer dataType = channel.getDataType(); ISourcePlaybackService playbackService = sourcePlaybackServiceMap.get(ChannelDataType.PLAYBACK_SERVICE + dataType); if (playbackService == null) { // 通道数据异常 log.error("[点播通用通道] 类型编号: {} 不支持回放暂停恢复", dataType); throw new PlayException(Response.BUSY_HERE, "channel not support"); } playbackService.playbackResume(channel, stream); } @Override public void playbackSeek(CommonGBChannel channel, String stream, long seekTime) { log.info("[通用通道] 回放拖动播放, 类型: {}, 编号:{} stream:{}", ChannelDataType.getDateTypeDesc(channel.getDataType()), channel.getGbDeviceId(), stream); Integer dataType = channel.getDataType(); ISourcePlaybackService playbackService = sourcePlaybackServiceMap.get(ChannelDataType.PLAYBACK_SERVICE + dataType); if (playbackService == null) { // 通道数据异常 log.error("[点播通用通道] 类型编号: {} 不支持回放暂停恢复", dataType); throw new PlayException(Response.BUSY_HERE, "channel not support"); } playbackService.playbackSeek(channel, stream, seekTime); } @Override public void playbackSpeed(CommonGBChannel channel, String stream, Double speed) { log.info("[通用通道] 回放倍速播放, 类型: {}, 编号:{} stream:{}", ChannelDataType.getDateTypeDesc(channel.getDataType()), channel.getGbDeviceId(), stream); Integer dataType = channel.getDataType(); ISourcePlaybackService playbackService = sourcePlaybackServiceMap.get(ChannelDataType.PLAYBACK_SERVICE + dataType); if (playbackService == null) { // 通道数据异常 log.error("[点播通用通道] 类型编号: {} 不支持回放暂停恢复", dataType); throw new PlayException(Response.BUSY_HERE, "channel not support"); } playbackService.playbackSpeed(channel, stream, speed); } @Override public void queryRecord(CommonGBChannel channel, String startTime, String endTime, ErrorCallback> callback) { log.info("[通用通道] 录像查询, 类型: {}, 编号:{}", ChannelDataType.getDateTypeDesc(channel.getDataType()), channel.getGbDeviceId()); Integer dataType = channel.getDataType(); ISourcePlaybackService playbackService = sourcePlaybackServiceMap.get(ChannelDataType.PLAYBACK_SERVICE + dataType); if (playbackService == null) { // 通道数据异常 log.error("[点播通用通道] 类型编号: {} 不支持回放暂停恢复", dataType); throw new PlayException(Response.BUSY_HERE, "channel not support"); } playbackService.queryRecord(channel, startTime, endTime, callback); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/GbChannelServiceImpl.java ================================================ package com.genersoft.iot.vmp.gb28181.service.impl; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.common.VideoManagerConstants; import com.genersoft.iot.vmp.common.enums.ChannelDataType; import com.genersoft.iot.vmp.conf.DynamicTask; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.controller.bean.Extent; import com.genersoft.iot.vmp.gb28181.dao.CommonGBChannelMapper; import com.genersoft.iot.vmp.gb28181.dao.GroupMapper; import com.genersoft.iot.vmp.gb28181.dao.PlatformChannelMapper; import com.genersoft.iot.vmp.gb28181.dao.RegionMapper; import com.genersoft.iot.vmp.gb28181.event.EventPublisher; import com.genersoft.iot.vmp.gb28181.event.channel.ChannelEvent; import com.genersoft.iot.vmp.gb28181.service.IGbChannelService; import com.genersoft.iot.vmp.gb28181.service.IPlatformChannelService; import com.genersoft.iot.vmp.gb28181.utils.VectorTileCatch; import com.genersoft.iot.vmp.service.bean.GPSMsgInfo; import com.genersoft.iot.vmp.streamPush.bean.StreamPush; import com.genersoft.iot.vmp.utils.Coordtransform; import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.utils.TileUtils; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import com.google.common.base.CaseFormat; import lombok.extern.slf4j.Slf4j; import no.ecc.vectortile.VectorTileEncoder; import org.jetbrains.annotations.NotNull; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.GeometryFactory; import org.locationtech.jts.geom.Point; import org.springframework.beans.BeanWrapper; import org.springframework.beans.BeanWrapperImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import java.beans.PropertyDescriptor; import java.time.Duration; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicReference; @Slf4j @Service public class GbChannelServiceImpl implements IGbChannelService, CommandLineRunner { @Autowired private EventPublisher eventPublisher; @Autowired private CommonGBChannelMapper commonGBChannelMapper; @Autowired private PlatformChannelMapper platformChannelMapper; @Autowired private IPlatformChannelService platformChannelService; @Autowired private RegionMapper regionMapper; @Autowired private GroupMapper groupMapper; @Autowired private DynamicTask dynamicTask; @Autowired private RedisTemplate redisTemplate; @Autowired private VectorTileCatch vectorTileCatch; private final GeometryFactory geometryFactory = new GeometryFactory(); @Override public void run(String... args) throws Exception { // 启动时重新发布抽稀图层 List channelList = commonGBChannelMapper.queryAllWithPosition(); Map> zoomCameraMap = new ConcurrentHashMap<>(); channelList.stream().forEach(commonGBChannel -> { if (commonGBChannel.getMapLevel() == null) { return; } List channelListForZoom = zoomCameraMap.computeIfAbsent(commonGBChannel.getMapLevel(), k -> new ArrayList<>()); channelListForZoom.add(commonGBChannel); }); String id = "DEFAULT"; List beforeData = new ArrayList<>(); for (Integer zoom : zoomCameraMap.keySet()) { beforeData.addAll(zoomCameraMap.get(zoom)); log.info("[抽稀-发布mvt矢量瓦片] ID:{},当前层级: {}, ", id, zoom); // 按照 z/x/y 数据组织数据, 矢量数据暂时保存在内存中 // 按照范围生成 x y范围, saveTile(id, zoom, "WGS84", beforeData); saveTile(id, zoom, "GCJ02", beforeData); } } @Override public CommonGBChannel queryByDeviceId(String gbDeviceId) { List commonGBChannels = commonGBChannelMapper.queryByDeviceId(gbDeviceId); if (commonGBChannels.isEmpty()) { return null; }else { return commonGBChannels.get(0); } } @Override public int add(CommonGBChannel commonGBChannel) { if (commonGBChannel.getDataType() == null || commonGBChannel.getDataDeviceId() == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "缺少通道数据类型或通道数据关联设备ID"); } CommonGBChannel commonGBChannelInDb = commonGBChannelMapper.queryByDataId(commonGBChannel.getDataType(), commonGBChannel.getDataDeviceId()); Assert.isNull(commonGBChannelInDb, "此推流已经关联通道"); // 检验国标编号是否重复 List channelList = commonGBChannelMapper.queryByDeviceId(commonGBChannel.getGbDeviceId()); Assert.isTrue(channelList.isEmpty(), "国标编号已经存在"); commonGBChannel.setCreateTime(DateUtil.getNow()); commonGBChannel.setUpdateTime(DateUtil.getNow()); int result = commonGBChannelMapper.insert(commonGBChannel); try { // 发送通知 eventPublisher.channelEventPublish(commonGBChannel, ChannelEvent.ChannelEventMessageType.ADD); } catch (Exception e) { log.warn("[通道移除通知] 发送失败,{}", commonGBChannel.getGbDeviceId(), e); } return result; } @Override @Transactional public int delete(int gbId) { // 移除国标级联关联的信息 try { platformChannelService.removeChannel(gbId); }catch (Exception e) { log.error("[移除通道国标级联共享失败]", e); } CommonGBChannel channel = commonGBChannelMapper.queryById(gbId); if (channel != null) { commonGBChannelMapper.delete(gbId); try { // 发送通知 eventPublisher.channelEventPublish(channel, ChannelEvent.ChannelEventMessageType.DEL); } catch (Exception e) { log.warn("[通道移除通知] 发送失败,{}", channel.getGbDeviceId(), e); } } return 1; } @Override @Transactional public void delete(Collection ids) { // 移除国标级联关联的信息 try { platformChannelService.removeChannels(new ArrayList<>(ids)); }catch (Exception e) { log.error("[移除通道国标级联共享失败]", e); } List channelListInDb = commonGBChannelMapper.queryByIds(ids); if (channelListInDb.isEmpty()) { return; } commonGBChannelMapper.batchDelete(channelListInDb); try { // 发送通知 eventPublisher.channelEventPublish(channelListInDb, ChannelEvent.ChannelEventMessageType.DEL); } catch (Exception e) { log.warn("[通道移除通知] 发送失败", e); } } @Override public int update(CommonGBChannel commonGBChannel) { log.info("[更新通道] 通道ID: {}, ", commonGBChannel.toString()); if (commonGBChannel.getGbId() <= 0) { log.warn("[更新通道] 未找到数据库ID,更新失败, {}({})", commonGBChannel.getGbName(), commonGBChannel.getGbDeviceId()); return 0; } // 确定编号是否重复 List channels = commonGBChannelMapper.queryByDeviceId(commonGBChannel.getGbDeviceId()); if (channels.size() > 1) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "国标编号重复,请修改编号后保存"); } CommonGBChannel oldChannel = commonGBChannelMapper.queryById(commonGBChannel.getGbId()); commonGBChannel.setUpdateTime(DateUtil.getNow()); int result = commonGBChannelMapper.update(commonGBChannel); if (result > 0) { try { CommonGBChannel newChannel = commonGBChannelMapper.queryById(commonGBChannel.getGbId()); // 发送通知 eventPublisher.channelEventPublishForUpdate(newChannel, oldChannel); if (newChannel.getGbLongitude() != null && !Objects.equals(oldChannel.getGbLongitude(), newChannel.getGbLongitude()) && newChannel.getGbLatitude() != null && !Objects.equals(oldChannel.getGbLatitude(), newChannel.getGbLatitude())) { MobilePosition mobilePosition = new MobilePosition(); mobilePosition.setDeviceId(newChannel.getGbDeviceId()); mobilePosition.setChannelId(newChannel.getGbId()); mobilePosition.setChannelDeviceId(newChannel.getGbDeviceId()); mobilePosition.setDeviceName(newChannel.getGbName()); mobilePosition.setCreateTime(DateUtil.getNow()); mobilePosition.setTime(DateUtil.getNow()); mobilePosition.setLongitude(newChannel.getGbLongitude()); mobilePosition.setLatitude(newChannel.getGbLatitude()); eventPublisher.mobilePositionEventPublish(mobilePosition); } } catch (Exception e) { log.warn("[更新通道通知] 发送失败,{}", JSONObject.toJSONString(commonGBChannel), e); } } return result; } @Override public int offline(CommonGBChannel commonGBChannel) { if (commonGBChannel.getGbId() <= 0) { log.warn("[通道离线] 未找到数据库ID,更新失败, {}({})", commonGBChannel.getGbName(), commonGBChannel.getGbDeviceId()); return 0; } int result = commonGBChannelMapper.updateStatusById(commonGBChannel.getGbId(), "OFF"); if (result > 0) { try { // 发送通知 eventPublisher.channelEventPublish(commonGBChannel, ChannelEvent.ChannelEventMessageType.OFF); } catch (Exception e) { log.warn("[通道离线通知] 发送失败,{}", commonGBChannel.getGbDeviceId(), e); } } return result; } @Override @Transactional public int offline(List commonGBChannelList, boolean permission) { if (commonGBChannelList.isEmpty()) { log.warn("[多个通道离线] 通道数量为0,更新失败"); return 0; } log.info("[通道离线] 共 {} 个", commonGBChannelList.size()); int result = 0; if (permission) { int limitCount = 1000; if (commonGBChannelList.size() > limitCount) { for (int i = 0; i < commonGBChannelList.size(); i += limitCount) { int toIndex = i + limitCount; if (i + limitCount > commonGBChannelList.size()) { toIndex = commonGBChannelList.size(); } result += commonGBChannelMapper.updateStatusForListById(commonGBChannelList.subList(i, toIndex), "OFF"); } } else { result += commonGBChannelMapper.updateStatusForListById(commonGBChannelList, "OFF"); } log.info("[通道离线] 保存入库 共 {} 个改变", result); } try { // 发送catalog eventPublisher.channelEventPublish(commonGBChannelList, ChannelEvent.ChannelEventMessageType.OFF); } catch (Exception e) { log.warn("[多个通道离线] 发送失败,数量:{}", commonGBChannelList.size(), e); } return result; } @Override public int online(CommonGBChannel commonGBChannel) { if (commonGBChannel.getGbId() <= 0) { log.warn("[通道上线] 未找到数据库ID,更新失败, {}({})", commonGBChannel.getGbName(), commonGBChannel.getGbDeviceId()); return 0; } int result = commonGBChannelMapper.updateStatusById(commonGBChannel.getGbId(), "ON"); if (result > 0) { try { // 发送通知 eventPublisher.channelEventPublish(commonGBChannel, ChannelEvent.ChannelEventMessageType.ON); } catch (Exception e) { log.warn("[通道上线通知] 发送失败,{}", commonGBChannel.getGbDeviceId(), e); } } return 0; } @Override @Transactional public int online(List commonGBChannelList, boolean permission) { if (commonGBChannelList.isEmpty()) { log.warn("[多个通道上线] 通道数量为0,更新失败"); return 0; } int result = 0; if (permission) { // 批量更新 int limitCount = 1000; if (commonGBChannelList.size() > limitCount) { for (int i = 0; i < commonGBChannelList.size(); i += limitCount) { int toIndex = i + limitCount; if (i + limitCount > commonGBChannelList.size()) { toIndex = commonGBChannelList.size(); } result += commonGBChannelMapper.updateStatusForListById(commonGBChannelList.subList(i, toIndex), "ON"); } } else { result += commonGBChannelMapper.updateStatusForListById(commonGBChannelList, "ON"); } } try { // 发送catalog eventPublisher.channelEventPublish(commonGBChannelList, ChannelEvent.ChannelEventMessageType.ON); } catch (Exception e) { log.warn("[多个通道上线] 发送失败,数量:{}", commonGBChannelList.size(), e); } return result; } @Override @Transactional public void batchAdd(List commonGBChannels) { if (commonGBChannels.isEmpty()) { log.warn("[新增多个通道] 通道数量为0,更新失败"); return; } // 批量保存 int limitCount = 1000; int result = 0; if (commonGBChannels.size() > limitCount) { for (int i = 0; i < commonGBChannels.size(); i += limitCount) { int toIndex = i + limitCount; if (i + limitCount > commonGBChannels.size()) { toIndex = commonGBChannels.size(); } result += commonGBChannelMapper.batchAdd(commonGBChannels.subList(i, toIndex)); } } else { result += commonGBChannelMapper.batchAdd(commonGBChannels); } try { // 发送catalog eventPublisher.channelEventPublish(commonGBChannels, ChannelEvent.ChannelEventMessageType.ADD); } catch (Exception e) { log.warn("[多个通道新增] 发送失败,数量:{}", commonGBChannels.size(), e); } log.warn("[新增多个通道] 通道数量为{},成功保存:{}", commonGBChannels.size(), result); } @Override public void batchUpdateForStreamPushRedisMsg(List commonGBChannels, boolean permission) { if (commonGBChannels.isEmpty()) { log.warn("[更新多个通道] 通道数量为0,更新失败"); return; } List oldCommonGBChannelList = commonGBChannelMapper.queryOldChanelListByChannels(commonGBChannels); if (permission) { // 批量保存 int limitCount = 1000; int result = 0; if (commonGBChannels.size() > limitCount) { for (int i = 0; i < commonGBChannels.size(); i += limitCount) { int toIndex = i + limitCount; if (i + limitCount > commonGBChannels.size()) { toIndex = commonGBChannels.size(); } result += commonGBChannelMapper.batchUpdate(commonGBChannels.subList(i, toIndex)); } } else { result += commonGBChannelMapper.batchUpdate(commonGBChannels); } log.info("[更新多个通道] 通道数量为{},成功保存:{}", commonGBChannels.size(), result); } // 发送通过更新通知 try { // 发送通知 eventPublisher.channelEventPublishForUpdate(commonGBChannels, oldCommonGBChannelList); } catch (Exception e) { log.warn("[更新多个通道] 发送失败,{}个", commonGBChannels.size(), e); } } @Override @Transactional public void updateStatus(List commonGBChannels) { if (commonGBChannels.isEmpty()) { log.warn("[更新多个通道状态] 通道数量为0,更新失败"); return; } List oldChanelListByChannels = commonGBChannelMapper.queryOldChanelListByChannels(commonGBChannels); int limitCount = 1000; int result = 0; if (commonGBChannels.size() > limitCount) { for (int i = 0; i < commonGBChannels.size(); i += limitCount) { int toIndex = i + limitCount; if (i + limitCount > commonGBChannels.size()) { toIndex = commonGBChannels.size(); } result += commonGBChannelMapper.updateStatus(commonGBChannels.subList(i, toIndex)); } } else { result += commonGBChannelMapper.updateStatus(commonGBChannels); } log.warn("[更新多个通道状态] 通道数量为{},成功保存:{}", commonGBChannels.size(), result); // 发送通过更新通知 try { // 发送通知 eventPublisher.channelEventPublishForUpdate(commonGBChannels, oldChanelListByChannels); } catch (Exception e) { log.warn("[更新多个通道] 发送失败,{}个", commonGBChannels.size(), e); } } @Override public CommonGBChannel getOne(int id) { return commonGBChannelMapper.queryById(id); } @Override public List getIndustryCodeList() { IndustryCodeTypeEnum[] values = IndustryCodeTypeEnum.values(); List result = new ArrayList<>(values.length); for (IndustryCodeTypeEnum value : values) { result.add(IndustryCodeType.getInstance(value)); } Collections.sort(result); return result; } @Override public List getDeviceTypeList() { DeviceTypeEnum[] values = DeviceTypeEnum.values(); List result = new ArrayList<>(values.length); for (DeviceTypeEnum value : values) { result.add(DeviceType.getInstance(value)); } Collections.sort(result); return result; } @Override public List getNetworkIdentificationTypeList() { NetworkIdentificationTypeEnum[] values = NetworkIdentificationTypeEnum.values(); List result = new ArrayList<>(values.length); for (NetworkIdentificationTypeEnum value : values) { result.add(NetworkIdentificationType.getInstance(value)); } Collections.sort(result); return result; } @Override public void reset(int id, List chanelFields) { log.info("[重置国标通道] id: {}", id); Assert.notEmpty(chanelFields, "待重置字段为空"); CommonGBChannel channel = getOne(id); if (channel == null) { log.warn("[重置国标通道] 未找到对应Id的通道: id: {}", id); throw new ControllerException(ErrorCode.ERROR400); } if (channel.getDataType() != ChannelDataType.GB28181) { log.warn("[重置国标通道] 非国标下级通道无法重置: id: {}", id); throw new ControllerException(ErrorCode.ERROR100.getCode(), "非国标下级通道无法重置"); } List dbFields = new ArrayList<>(); for (String chanelField : chanelFields) { BeanWrapperImpl wrapper = new BeanWrapperImpl(channel); if (wrapper.isReadableProperty(chanelField)) { dbFields.add(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, chanelField)); } } Assert.notEmpty(dbFields, "待重置字段为空"); // 这个多加一个参数,为了防止将非国标的通道通过此方法清空内容,导致意外发生 commonGBChannelMapper.reset(id, dbFields, DateUtil.getNow()); CommonGBChannel channelNew = getOne(id); // 发送通过更新通知 try { // 发送通知 eventPublisher.channelEventPublishForUpdate(channelNew, channel); } catch (Exception e) { log.warn("[通道移除通知] 发送失败,{}", channelNew.getGbDeviceId(), e); } } @Override public PageInfo queryListByCivilCode(int page, int count, String query, Boolean online, Integer channelType, String civilCode) { PageHelper.startPage(page, count); if (query != null) { query = query.replaceAll("/", "//") .replaceAll("%", "/%") .replaceAll("_", "/_"); } List all = commonGBChannelMapper.queryListByCivilCode(query, online, channelType, civilCode); return new PageInfo<>(all); } @Override public PageInfo queryListByParentId(int page, int count, String query, Boolean online, Integer channelType, String groupDeviceId) { PageHelper.startPage(page, count); if (query != null) { query = query.replaceAll("/", "//") .replaceAll("%", "/%") .replaceAll("_", "/_"); } List all = commonGBChannelMapper.queryListByParentId(query, online, channelType, groupDeviceId); return new PageInfo<>(all); } @Override public void removeCivilCode(List allChildren) { commonGBChannelMapper.removeCivilCode(allChildren); // TODO 是否需要通知上级, 或者等添加新的行政区划时发送更新通知 } @Override public void addChannelToRegion(String civilCode, List channelIds) { List channelList = commonGBChannelMapper.queryByIds(channelIds); if (channelList.isEmpty()) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "所有通道Id不存在"); } List channelListForOld = new ArrayList<>(channelList); for (CommonGBChannel channel : channelList) { channel.setGbCivilCode(civilCode); } int result = commonGBChannelMapper.updateRegion(civilCode, channelList); // 发送通知 if (result > 0) { platformChannelService.checkRegionAdd(channelList); try { // 发送catalog eventPublisher.channelEventPublishForUpdate(channelList, channelListForOld); } catch (Exception e) { log.warn("[多个通道添加行政区划] 发送失败,数量:{}", channelList.size(), e); } } } @Override @Transactional public void deleteChannelToRegion(String civilCode, List channelIds) { if (!ObjectUtils.isEmpty(civilCode)) { deleteChannelToRegionByCivilCode(civilCode); } if (!ObjectUtils.isEmpty(channelIds)) { deleteChannelToRegionByChannelIds(channelIds); } } @Override public void deleteChannelToRegionByCivilCode(String civilCode) { List channelList = commonGBChannelMapper.queryByCivilCode(civilCode); if (channelList.isEmpty()) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "所有通道Id不存在"); } int result = commonGBChannelMapper.removeCivilCodeByChannels(channelList); Region region = regionMapper.queryByDeviceId(civilCode); if (region == null) { platformChannelService.checkRegionRemove(channelList, null); }else { List regionList = new ArrayList<>(); regionList.add(region); platformChannelService.checkRegionRemove(channelList, regionList); } // TODO 发送通知 // if (result > 0) { // try { // // 发送catalog // eventPublisher.catalogEventPublish(null, channelList, CatalogEvent.UPDATE); // }catch (Exception e) { // log.warn("[多个通道添加行政区划] 发送失败,数量:{}", channelList.size(), e); // } // } } @Override public void deleteChannelToRegionByChannelIds(List channelIds) { List channelList = commonGBChannelMapper.queryByIds(channelIds); if (channelList.isEmpty()) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "所有通道Id不存在"); } int result = commonGBChannelMapper.removeCivilCodeByChannels(channelList); platformChannelService.checkRegionRemove(channelList, null); // TODO 发送通知 // if (result > 0) { // try { // // 发送catalog // eventPublisher.catalogEventPublish(null, channelList, CatalogEvent.UPDATE); // }catch (Exception e) { // log.warn("[多个通道添加行政区划] 发送失败,数量:{}", channelList.size(), e); // } // } } @Override public void addChannelToRegionByGbDevice(String civilCode, List deviceIds) { List channelList = commonGBChannelMapper.queryByDataTypeAndDeviceIds(ChannelDataType.GB28181, deviceIds); if (channelList.isEmpty()) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "所有通道Id不存在"); } List channelListForOld = new ArrayList<>(channelList); for (CommonGBChannel channel : channelList) { channel.setGbCivilCode(civilCode); } int result = commonGBChannelMapper.updateRegion(civilCode, channelList); // 发送通知 if (result > 0) { try { // 发送catalog eventPublisher.channelEventPublishForUpdate(channelList, channelListForOld); } catch (Exception e) { log.warn("[多个通道添加行政区划] 发送失败,数量:{}", channelList.size(), e); } } } @Override public void deleteChannelToRegionByGbDevice(List deviceIds) { List channelList = commonGBChannelMapper.queryByDataTypeAndDeviceIds(ChannelDataType.GB28181, deviceIds); if (channelList.isEmpty()) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "所有通道Id不存在"); } int result = commonGBChannelMapper.removeCivilCodeByChannels(channelList); platformChannelService.checkRegionRemove(channelList, null); } @Override @Transactional public void removeParentIdByBusinessGroup(String businessGroup) { List channelList = commonGBChannelMapper.queryByBusinessGroup(businessGroup); if (channelList.isEmpty()) { return; } int result = commonGBChannelMapper.removeParentIdByChannels(channelList); List groupList = groupMapper.queryByBusinessGroup(businessGroup); platformChannelService.checkGroupRemove(channelList, groupList); } @Override public void removeParentIdByGroupList(List groupList) { List channelList = commonGBChannelMapper.queryByGroupList(groupList); if (channelList.isEmpty()) { return; } commonGBChannelMapper.removeParentIdByChannels(channelList); platformChannelService.checkGroupRemove(channelList, groupList); } @Override public void updateBusinessGroup(String oldBusinessGroup, String newBusinessGroup) { List channelList = commonGBChannelMapper.queryByBusinessGroup(oldBusinessGroup); if (channelList.isEmpty()) { log.info("[更新业务分组] 发现未关联任何通道: {}", oldBusinessGroup); return; } List channelListForOld = new ArrayList<>(channelList); int result = commonGBChannelMapper.updateBusinessGroupByChannelList(newBusinessGroup, channelList); if (result > 0) { for (CommonGBChannel channel : channelList) { channel.setGbBusinessGroupId(newBusinessGroup); } // 发送catalog try { eventPublisher.channelEventPublishForUpdate(channelList, channelListForOld); } catch (Exception e) { log.warn("[多个通道业务分组] 发送失败,数量:{}", channelList.size(), e); } } } @Override public void updateParentIdGroup(String oldParentId, String newParentId) { List channelList = commonGBChannelMapper.queryByParentId(oldParentId); if (channelList.isEmpty()) { return; } List channelListForOld = new ArrayList<>(channelList); int result = commonGBChannelMapper.updateParentIdByChannelList(newParentId, channelList); if (result > 0) { for (CommonGBChannel channel : channelList) { channel.setGbParentId(newParentId); } // 发送catalog try { eventPublisher.channelEventPublishForUpdate(channelList, channelListForOld); } catch (Exception e) { log.warn("[多个通道业务分组] 发送失败,数量:{}", channelList.size(), e); } } } @Override @Transactional public void addChannelToGroup(String parentId, String businessGroup, List channelIds) { List channelList = commonGBChannelMapper.queryByIds(channelIds); if (channelList.isEmpty()) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "所有通道Id不存在"); } List channelListForOld = new ArrayList<>(channelList); int result = commonGBChannelMapper.updateGroup(parentId, businessGroup, channelList); for (CommonGBChannel commonGBChannel : channelList) { commonGBChannel.setGbParentId(parentId); commonGBChannel.setGbBusinessGroupId(businessGroup); } // 发送通知 if (result > 0) { platformChannelService.checkGroupAdd(channelList); try { // 发送catalog eventPublisher.channelEventPublishForUpdate(channelList, channelListForOld); } catch (Exception e) { log.warn("[多个通道添加行政区划] 发送失败,数量:{}", channelList.size(), e); } } } @Override public void deleteChannelToGroup(String parentId, String businessGroup, List channelIds) { List channelList = commonGBChannelMapper.queryByIds(channelIds); if (channelList.isEmpty()) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "所有通道Id不存在"); } commonGBChannelMapper.removeParentIdByChannels(channelList); Group group = groupMapper.queryOneByDeviceId(parentId, businessGroup); if (group == null) { platformChannelService.checkGroupRemove(channelList, null); }else { List groupList = new ArrayList<>(); groupList.add(group); platformChannelService.checkGroupRemove(channelList, groupList); } } @Override @Transactional public void addChannelToGroupByGbDevice(String parentId, String businessGroup, List deviceIds) { List channelList = commonGBChannelMapper.queryByDataTypeAndDeviceIds(ChannelDataType.GB28181, deviceIds); if (channelList.isEmpty()) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "所有通道Id不存在"); } List channelListForOld = new ArrayList<>(channelList); for (CommonGBChannel channel : channelList) { channel.setGbParentId(parentId); channel.setGbBusinessGroupId(businessGroup); } int result = commonGBChannelMapper.updateGroup(parentId, businessGroup, channelList); for (CommonGBChannel commonGBChannel : channelList) { commonGBChannel.setGbParentId(parentId); commonGBChannel.setGbBusinessGroupId(businessGroup); } // 发送通知 if (result > 0) { platformChannelService.checkGroupAdd(channelList); try { // 发送catalog eventPublisher.channelEventPublishForUpdate(channelList, channelListForOld); } catch (Exception e) { log.warn("[多个通道添加行政区划] 发送失败,数量:{}", channelList.size(), e); } } } @Override public void deleteChannelToGroupByGbDevice(List deviceIds) { List channelList = commonGBChannelMapper.queryByDataTypeAndDeviceIds(ChannelDataType.GB28181, deviceIds); if (channelList.isEmpty()) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "所有通道Id不存在"); } commonGBChannelMapper.removeParentIdByChannels(channelList); platformChannelService.checkGroupRemove(channelList, null); } @Override public CommonGBChannel queryOneWithPlatform(Integer platformId, String channelDeviceId) { // 防止共享的通道编号重复 List channelList = platformChannelMapper.queryOneWithPlatform(platformId, channelDeviceId); if (!channelList.isEmpty()) { return channelList.get(channelList.size() - 1); }else { return null; } } @Override public void updateCivilCode(String oldCivilCode, String newCivilCode) { List channelList = commonGBChannelMapper.queryByCivilCode(oldCivilCode); if (channelList.isEmpty()) { return; } List channelListForOld = new ArrayList<>(channelList); int result = commonGBChannelMapper.updateCivilCodeByChannelList(newCivilCode, channelList); if (result > 0) { for (CommonGBChannel channel : channelList) { channel.setGbCivilCode(newCivilCode); } // 发送catalog try { eventPublisher.channelEventPublishForUpdate(channelList, channelListForOld); } catch (Exception e) { log.warn("[多个通道业务分组] 发送失败,数量:{}", channelList.size(), e); } } } @Override public List queryListByStreamPushList(List streamPushList) { return commonGBChannelMapper.queryListByStreamPushList(ChannelDataType.STREAM_PUSH, streamPushList); } @Override public PageInfo queryList(int page, int count, String query, Boolean online, Boolean hasRecordPlan, Integer channelType, String civilCode, String parentDeviceId) { PageHelper.startPage(page, count); if (query != null) { query = query.replaceAll("/", "//") .replaceAll("%", "/%") .replaceAll("_", "/_"); } List all = commonGBChannelMapper.queryList(query, online, hasRecordPlan, channelType, civilCode, parentDeviceId); return new PageInfo<>(all); } @Override public PageInfo queryListByCivilCodeForUnusual(int page, int count, String query, Boolean online, Integer channelType) { PageHelper.startPage(page, count); if (query != null) { query = query.replaceAll("/", "//") .replaceAll("%", "/%") .replaceAll("_", "/_"); } List all = commonGBChannelMapper.queryListByCivilCodeForUnusual(query, online, channelType); return new PageInfo<>(all); } @Override public void clearChannelCivilCode(Boolean all, List channelIds) { List channelIdsForClear; if (all != null && all) { channelIdsForClear = commonGBChannelMapper.queryAllForUnusualCivilCode(); }else { channelIdsForClear = channelIds; } commonGBChannelMapper.removeCivilCodeByChannelIds(channelIdsForClear); } @Override public PageInfo queryListByParentForUnusual(int page, int count, String query, Boolean online, Integer channelType) { PageHelper.startPage(page, count); if (query != null) { query = query.replaceAll("/", "//") .replaceAll("%", "/%") .replaceAll("_", "/_"); } List all = commonGBChannelMapper.queryListByParentForUnusual(query, online, channelType); return new PageInfo<>(all); } @Override public void clearChannelParent(Boolean all, List channelIds) { List channelIdsForClear; if (all != null && all) { channelIdsForClear = commonGBChannelMapper.queryAllForUnusualParent(); }else { channelIdsForClear = channelIds; } commonGBChannelMapper.removeParentIdByChannelIds(channelIdsForClear); } @Override public void updateGPSFromGPSMsgInfo(List gpsMsgInfoList) { if (gpsMsgInfoList == null || gpsMsgInfoList.isEmpty()) { return; } // 此处来源默认为WGS84, 所以直接入库 commonGBChannelMapper.updateGpsByDeviceId(gpsMsgInfoList); // // Map gpsMsgInfoMap = new ConcurrentReferenceHashMap<>(); // for (GPSMsgInfo gpsMsgInfo : gpsMsgInfoList) { // gpsMsgInfoMap.put(gpsMsgInfo.getId(), gpsMsgInfo); // } // // List channelList = commonGBChannelMapper.queryByGbDeviceIds(new ArrayList<>(gpsMsgInfoMap.keySet())); // if (channelList.isEmpty()) { // return; // } // channelList.forEach(commonGBChannel -> { // MobilePosition mobilePosition = new MobilePosition(); // mobilePosition.setDeviceId(commonGBChannel.getGbDeviceId()); // mobilePosition.setChannelId(commonGBChannel.getGbId()); // mobilePosition.setDeviceName(commonGBChannel.getGbName()); // mobilePosition.setCreateTime(DateUtil.getNow()); // mobilePosition.setTime(DateUtil.getNow()); // mobilePosition.setLongitude(commonGBChannel.getGbLongitude()); // mobilePosition.setLatitude(commonGBChannel.getGbLatitude()); // eventPublisher.mobilePositionEventPublish(mobilePosition); // }); } @Transactional @Override public void updateGPS(List commonGBChannels) { int limitCount = 1000; if (commonGBChannels.size() > limitCount) { for (int i = 0; i < commonGBChannels.size(); i += limitCount) { int toIndex = i + limitCount; if (i + limitCount > commonGBChannels.size()) { toIndex = commonGBChannels.size(); } commonGBChannelMapper.updateGps(commonGBChannels.subList(i, toIndex)); } } else { commonGBChannelMapper.updateGps(commonGBChannels); } } @Override public List queryListForMap(String query, Boolean online, Boolean hasRecordPlan, Integer channelType) { if (query != null) { query = query.replaceAll("/", "//") .replaceAll("%", "/%") .replaceAll("_", "/_"); } return commonGBChannelMapper.queryList(query, online, hasRecordPlan, channelType, null, null); } @Override public CommonGBChannel queryCommonChannelByDeviceChannel(DeviceChannel channel) { return commonGBChannelMapper.queryCommonChannelByDeviceChannel(channel.getDataType(), channel.getDataDeviceId(), channel.getDeviceId()); } @Override public void resetLevel() { commonGBChannelMapper.resetLevel(); } @Override public byte[] getTile(int z, int x, int y, String geoCoordSys) { double minLon = TileUtils.tile2lon(x, z); double maxLon = TileUtils.tile2lon(x + 1, z); double maxLat = TileUtils.tile2lat(y, z); double minLat = TileUtils.tile2lat(y + 1, z); if (geoCoordSys != null) { if (geoCoordSys.equalsIgnoreCase("GCJ02")) { Double[] minPosition = Coordtransform.GCJ02ToWGS84(minLon, minLat); minLon = minPosition[0]; minLat = minPosition[1]; Double[] maxPosition = Coordtransform.GCJ02ToWGS84(maxLon, maxLat); maxLon = maxPosition[0]; maxLat = maxPosition[1]; } } // 从数据库查询对应的数据 List channelList = commonGBChannelMapper.queryCameraChannelInBox(minLon, maxLon, minLat, maxLat); VectorTileEncoder encoder = new VectorTileEncoder(); if (!channelList.isEmpty()) { channelList.forEach(commonGBChannel -> { double lon = commonGBChannel.getGbLongitude(); double lat = commonGBChannel.getGbLatitude(); // 转换为目标坐标系 if (geoCoordSys != null) { if (geoCoordSys.equalsIgnoreCase("GCJ02")) { Double[] minPosition = Coordtransform.WGS84ToGCJ02(lon, lat); lon = minPosition[0]; lat = minPosition[1]; } } // 将 lon/lat 转为瓦片内像素坐标(0..256) double[] px = TileUtils.lonLatToTilePixel(lon, lat, z, x, y); Point pointGeom = geometryFactory.createPoint(new Coordinate(px[0], px[1])); Map beanMap = getStringObjectMap(commonGBChannel); encoder.addFeature("points", beanMap, pointGeom); }); } return encoder.encode(); } @NotNull private static Map getStringObjectMap(CommonGBChannel commonGBChannel) { BeanWrapper wrapper = new BeanWrapperImpl(commonGBChannel); PropertyDescriptor[] pds = wrapper.getPropertyDescriptors(); Map beanMap = new HashMap<>(); for (PropertyDescriptor pd : pds) { if (pd.getReadMethod() != null && !"class".equals(pd.getName())) { Object value = wrapper.getPropertyValue(pd.getName()); beanMap.put(pd.getName(), value); } } return beanMap; } @Override public String drawThin(Map zoomParam, Extent extent, String geoCoordSys) { long time = System.currentTimeMillis(); String id = UUID.randomUUID().toString(); List channelListInExtent; if (extent == null) { log.info("[抽稀] ID: {}, 未设置范围,从数据库读取摄像头的范围", id); extent = commonGBChannelMapper.queryExtent(); channelListInExtent = commonGBChannelMapper.queryAllWithPosition(); }else { if (geoCoordSys != null && geoCoordSys.equalsIgnoreCase("GCJ02")) { Double[] maxPosition = Coordtransform.GCJ02ToWGS84(extent.getMaxLng(), extent.getMaxLat()); Double[] minPosition = Coordtransform.GCJ02ToWGS84(extent.getMinLng(), extent.getMinLat()); extent.setMaxLng(maxPosition[0]); extent.setMaxLat(maxPosition[1]); extent.setMinLng(minPosition[0]); extent.setMinLat(minPosition[1]); } // 获取数据源 channelListInExtent = commonGBChannelMapper.queryListInExtent(extent.getMinLng(), extent.getMaxLng(), extent.getMinLat(), extent.getMaxLat()); } Assert.isTrue(!channelListInExtent.isEmpty(), "通道数据为空"); log.info("[开始抽稀] ID: {}, 范围,[{}, {}, {}, {}]", id, extent.getMinLng(), extent.getMinLat(), extent.getMaxLng(), extent.getMaxLat()); Extent finalExtent = extent; // 记录进度 saveProcess(id, 0, "开始抽稀"); dynamicTask.startDelay(id, () -> { try { // 存储每层的抽稀结果, key为层级(zoom),value为摄像头数组 Map> zoomCameraMap = new HashMap<>(); // 冗余一份已经处理过的摄像头的数据, 避免多次循环获取 Map useCameraMap = new HashMap<>(); AtomicReference process = new AtomicReference<>((double) 0); for (Integer zoom : zoomParam.keySet()) { Map useCameraMapForZoom = new HashMap<>(); Map cameraMapForZoom = new HashMap<>(); if (Objects.equals(zoom, Collections.max(zoomParam.keySet()))) { // 最大层级不进行抽稀, 将未进行抽稀的数据直接存储到这个层级 for (CommonGBChannel channel : channelListInExtent) { if (!useCameraMap.containsKey(channel.getGbId())) { channel.setMapLevel(zoom); // 这个的key跟后面的不一致是因为无需抽稀, 直接存储原始数据 cameraMapForZoom.put(channel.getGbId() + "", channel); useCameraMap.put(channel.getGbId(), channel); } } }else { Double diff = zoomParam.get(zoom); // 对这个层级展开抽稀 log.info("[抽稀] ID:{},当前层级: {}, 坐标间隔: {}", id, zoom, diff); // 更新上级图层的数据到当前层级,确保当前层级展示时考虑到之前层级的数据 for (CommonGBChannel channel : useCameraMap.values()) { int lngGrid = (int)(channel.getGbLongitude() / diff); int latGrid = (int)(channel.getGbLatitude() / diff); String gridKey = latGrid + ":" + lngGrid; useCameraMapForZoom.put(gridKey, channel); } // 对数据开始执行抽稀 for (CommonGBChannel channel : channelListInExtent) { // 已经分配再其他层级的,本层级不再使用 if (useCameraMap.containsKey(channel.getGbId())) { continue; } int lngGrid = (int)(channel.getGbLongitude() / diff); int latGrid = (int)(channel.getGbLatitude() / diff); // 数据网格Id String gridKey = latGrid + ":" + lngGrid; if (useCameraMapForZoom.containsKey(gridKey)) { continue; } if (cameraMapForZoom.containsKey(gridKey)) { CommonGBChannel oldChannel = cameraMapForZoom.get(gridKey); // 如果一个网格存在多个数据,则选择最接近中心点的, 目前只选择了经度方向作为参考 if (channel.getGbLongitude() % diff < oldChannel.getGbLongitude() % diff) { channel.setMapLevel(zoom); cameraMapForZoom.put(gridKey, channel); useCameraMap.put(channel.getGbId(), channel); useCameraMap.remove(oldChannel.getGbId()); oldChannel.setMapLevel(null); } }else { channel.setMapLevel(zoom); cameraMapForZoom.put(gridKey, channel); useCameraMap.put(channel.getGbId(), channel); } } } // 存储 zoomCameraMap.put(zoom, cameraMapForZoom.values()); process.updateAndGet(v -> (v + 0.5 / zoomParam.size())); saveProcess(id, process.get(), "抽稀图层: " + zoom); } // 抽稀完成, 对数据发布mvt矢量瓦片 List beforeData = new ArrayList<>(); for (Integer zoom : zoomCameraMap.keySet()) { beforeData.addAll(zoomCameraMap.get(zoom)); log.info("[抽稀-发布mvt矢量瓦片] ID:{},当前层级: {}", id, zoom); // 按照 z/x/y 数据组织数据, 矢量数据暂时保存在内存中 // 按照范围生成 x y范围, saveTile(id, zoom, "WGS84", beforeData); saveTile(id, zoom, "GCJ02", beforeData); process.updateAndGet(v -> (v + 0.5 / zoomParam.size())); saveProcess(id, process.get(), "发布矢量瓦片: " + zoom); } // 记录原始数据,未保存做准备 vectorTileCatch.addSource(id, new ArrayList<>(useCameraMap.values())); log.info("[抽稀完成] ID:{}, 耗时: {}ms", id, (System.currentTimeMillis() - time)); saveProcess(id, 1, "抽稀完成"); } catch (Exception e) { log.info("[抽稀] 失败 ID:{}", id, e); } }, 1); return id; } private void saveTile(String id, int z, String geoCoordSys, Collection commonGBChannelList ) { Map encoderMap = new HashMap<>(); commonGBChannelList.forEach(commonGBChannel -> { double lon = commonGBChannel.getGbLongitude(); double lat = commonGBChannel.getGbLatitude(); if (geoCoordSys != null && geoCoordSys.equalsIgnoreCase("GCJ02")) { Double[] minPosition = Coordtransform.WGS84ToGCJ02(lon, lat); lon = minPosition[0]; lat = minPosition[1]; } double[] doubles = TileUtils.lonLatToTileXY(lon, lat, z); int x = (int) doubles[0]; int y = (int) doubles[1]; String key = z + "_" + x + "_" + y + "_" + geoCoordSys; VectorTileEncoder encoder =encoderMap.get(key); if (encoder == null) { encoder = new VectorTileEncoder(); encoderMap.put(key, encoder); } // 将 lon/lat 转为瓦片内像素坐标(0..256) double[] px = TileUtils.lonLatToTilePixel(lon, lat, z, x, y); Point pointGeom = geometryFactory.createPoint(new Coordinate(px[0], px[1])); Map beanMap = getStringObjectMap(commonGBChannel); encoder.addFeature("points", beanMap, pointGeom); }); encoderMap.forEach((key, encoder) -> { vectorTileCatch.addVectorTile(id, key, encoder.encode()); }); } private void saveProcess(String id, double process, String msg) { String key = VideoManagerConstants.DRAW_THIN_PROCESS_PREFIX + id; Duration duration = Duration.ofMinutes(30); redisTemplate.opsForValue().set(key, new DrawThinProcess(process, msg), duration); } @Override public DrawThinProcess thinProgress(String id) { String key = VideoManagerConstants.DRAW_THIN_PROCESS_PREFIX + id; return (DrawThinProcess) redisTemplate.opsForValue().get(key); } @Override @Transactional public void saveThin(String id) { commonGBChannelMapper.resetLevel(); List channelList = vectorTileCatch.getChannelList(id); if (channelList != null && !channelList.isEmpty()) { int limitCount = 1000; if (channelList.size() > limitCount) { for (int i = 0; i < channelList.size(); i += limitCount) { int toIndex = i + limitCount; if (i + limitCount > channelList.size()) { toIndex = channelList.size(); } commonGBChannelMapper.saveLevel(channelList.subList(i, toIndex)); } } else { commonGBChannelMapper.saveLevel(channelList); } } vectorTileCatch.save(id); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/GroupServiceImpl.java ================================================ package com.genersoft.iot.vmp.gb28181.service.impl; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.dao.CommonGBChannelMapper; import com.genersoft.iot.vmp.gb28181.dao.GroupMapper; import com.genersoft.iot.vmp.gb28181.event.EventPublisher; import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent; import com.genersoft.iot.vmp.gb28181.service.IGbChannelService; import com.genersoft.iot.vmp.gb28181.service.IGroupService; import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import com.google.common.collect.Lists; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import java.util.*; /** * 区域管理类 */ @Service @Slf4j public class GroupServiceImpl implements IGroupService { @Autowired private GroupMapper groupManager; @Autowired private CommonGBChannelMapper commonGBChannelMapper; @Autowired private IGbChannelService gbChannelService; @Autowired private EventPublisher eventPublisher; @Autowired private RedisTemplate redisTemplate; @Override public void add(Group group) { Assert.notNull(group, "参数不可为NULL"); Assert.notNull(group.getDeviceId(), "分组编号不可为NULL"); Assert.isTrue(group.getDeviceId().trim().length() == 20, "分组编号必须为20位"); Assert.notNull(group.getName(), "分组名称不可为NULL"); GbCode gbCode = GbCode.decode(group.getDeviceId()); Assert.notNull(gbCode, "分组编号不满足国标定义"); // 查询数据库中已经存在的. List groupListInDb = groupManager.queryInGroupListByDeviceId(Lists.newArrayList(group)); if (!ObjectUtils.isEmpty(groupListInDb)){ throw new ControllerException(ErrorCode.ERROR100.getCode(), String.format("该节点编号 %s 已存在", group.getDeviceId())); } if ("215".equals(gbCode.getTypeCode())){ // 添加业务分组 addBusinessGroup(group); }else { Assert.isTrue("216".equals(gbCode.getTypeCode()), "创建虚拟组织时设备编号11-13位应使用216"); // 添加虚拟组织 addGroup(group); } } private void addGroup(Group group) { // 建立虚拟组织 Assert.notNull(group.getBusinessGroup(), "所属的业务分组分组不存在"); Group businessGroup = groupManager.queryBusinessGroup(group.getBusinessGroup()); Assert.notNull(businessGroup, "所属的业务分组分组不存在"); if (!ObjectUtils.isEmpty(group.getParentDeviceId())) { Group parentGroup = groupManager.queryOneByDeviceId(group.getParentDeviceId(), group.getBusinessGroup()); Assert.notNull(parentGroup, "所属的上级分组分组不存在"); }else { group.setParentDeviceId(null); } group.setCreateTime(DateUtil.getNow()); group.setUpdateTime(DateUtil.getNow()); groupManager.add(group); } private void addBusinessGroup(Group group) { group.setBusinessGroup(group.getDeviceId()); group.setCreateTime(DateUtil.getNow()); group.setUpdateTime(DateUtil.getNow()); groupManager.addBusinessGroup(group); } @Override public List queryAllChildren(Integer id) { List children = groupManager.getChildren(id); if (ObjectUtils.isEmpty(children)) { return children; } for (int i = 0; i < children.size(); i++) { children.addAll(queryAllChildren(children.get(i).getId())); } return children; } @Override @Transactional public void update(Group group) { Assert.isTrue(group.getId()> 0, "更新必须携带分组ID"); Assert.notNull(group.getDeviceId(), "编号不可为NULL"); Assert.notNull(group.getBusinessGroup(), "业务分组不可为NULL"); Group groupInDb = groupManager.queryOne(group.getId()); Assert.notNull(groupInDb, "分组不存在"); // 查询数据库中已经存在的. List groupListInDb = groupManager.queryInGroupListByDeviceId(Lists.newArrayList(group)); if (!ObjectUtils.isEmpty(groupListInDb) && groupListInDb.get(0).getId() != group.getId()){ throw new ControllerException(ErrorCode.ERROR100.getCode(), String.format("该该节点编号 %s 已存在", group.getDeviceId())); } group.setName(group.getName()); group.setUpdateTime(DateUtil.getNow()); groupManager.update(group); // 修改他的子节点 if (!group.getDeviceId().equals(groupInDb.getDeviceId()) || !group.getBusinessGroup().equals(groupInDb.getBusinessGroup())) { List groupList = queryAllChildren(groupInDb.getId()); if (!groupList.isEmpty()) { int result = groupManager.updateChild(groupInDb.getId(), group); if (result > 0) { for (Group chjildGroup : groupList) { chjildGroup.setParentDeviceId(group.getDeviceId()); chjildGroup.setBusinessGroup(group.getBusinessGroup()); // 将变化信息发送通知 CommonGBChannel channel = CommonGBChannel.build(chjildGroup); try { // 发送catalog eventPublisher.channelEventPublishForUpdate(channel, null); }catch (Exception e) { log.warn("[业务分组/虚拟组织变化] 发送失败,{}", group.getDeviceId(), e); } } } } } // 将变化信息发送通知 CommonGBChannel channel = CommonGBChannel.build(group); try { // 发送catalog eventPublisher.channelEventPublishForUpdate(channel, null); }catch (Exception e) { log.warn("[业务分组/虚拟组织变化] 发送失败,{}", group.getDeviceId(), e); } // 由于编号变化,会需要处理太多内容以及可能发送大量消息,所以目前更新只只支持重命名 GbCode decode = GbCode.decode(group.getDeviceId()); if (!groupInDb.getDeviceId().equals(group.getDeviceId())) { if (decode.getTypeCode().equals("215")) { // 业务分组变化。需要将其下的所有业务分组修改 gbChannelService.updateBusinessGroup(groupInDb.getDeviceId(), group.getDeviceId()); }else { // 虚拟组织修改,需要把其下的子节点修改父节点ID gbChannelService.updateParentIdGroup(groupInDb.getDeviceId(), group.getDeviceId()); } } } @Override public Group queryGroupByDeviceId(String regionDeviceId) { return groupManager.queryOneByOnlyDeviceId(regionDeviceId); } @Override public List queryForTree(String query, Integer parentId, Boolean hasChannel) { List groupTrees = groupManager.queryForTree(query, parentId); if (parentId == null) { return groupTrees; } // 查询含有的通道 Group parentGroup = groupManager.queryOne(parentId); if (parentGroup != null && hasChannel != null && hasChannel) { List groupTreesForChannel = commonGBChannelMapper.queryForGroupTreeByParentId(query, parentGroup.getDeviceId()); if (!ObjectUtils.isEmpty(groupTreesForChannel)) { groupTrees.addAll(groupTreesForChannel); } } return groupTrees; } @Override @Transactional public boolean delete(int id) { Group group = groupManager.queryOne(id); Assert.notNull(group, "分组不存在"); List groupListForDelete = new ArrayList<>(); GbCode gbCode = GbCode.decode(group.getDeviceId()); if (gbCode.getTypeCode().equals("215")) { List groupList = groupManager.queryByBusinessGroup(group.getDeviceId()); if (!groupList.isEmpty()) { groupListForDelete.addAll(groupList); } // 业务分组 gbChannelService.removeParentIdByBusinessGroup(group.getDeviceId()); }else { List groupList = queryAllChildren(group.getId()); if (!groupList.isEmpty()) { groupListForDelete.addAll(groupList); } groupListForDelete.add(group); gbChannelService.removeParentIdByGroupList(groupListForDelete); } groupManager.batchDelete(groupListForDelete); for (Group groupForDelete : groupListForDelete) { // 删除平台关联的分组信息。同时发送通知 List platformList = groupManager.queryForPlatformByGroupId(groupForDelete.getId()); if ( !platformList.isEmpty()) { groupManager.deletePlatformGroup(groupForDelete.getId()); // 将变化信息发送通知 CommonGBChannel channel = CommonGBChannel.build(groupForDelete); for (Platform platform : platformList) { try { // 发送catalog eventPublisher.catalogEventPublish(platform, channel, CatalogEvent.DEL); }catch (Exception e) { log.warn("[业务分组/虚拟组织删除] 发送失败,{}", groupForDelete.getDeviceId(), e); } } } } return true; } @Override @Transactional public boolean batchAdd(List groupList) { if (groupList== null || groupList.isEmpty()) { return false; } Map groupMapForVerification = new HashMap<>(); for (Group group : groupList) { groupMapForVerification.put(group.getDeviceId(), group); } // 查询数据库中已经存在的. List groupListInDb = groupManager.queryInGroupListByDeviceId(groupList); if (!groupListInDb.isEmpty()) { for (Group group : groupListInDb) { groupMapForVerification.remove(group.getDeviceId()); } } if (!groupMapForVerification.isEmpty()) { List groupListForAdd = new ArrayList<>(groupMapForVerification.values()); groupManager.batchAdd(groupListForAdd); // 更新分组关系 groupManager.updateParentId(groupListForAdd); groupManager.updateParentIdWithBusinessGroup(groupListForAdd); } return true; } @Override public List getPath(String deviceId, String businessGroup) { Group businessGroupInDb = groupManager.queryBusinessGroup(businessGroup); if (businessGroupInDb == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "业务分组不存在"); } Group group = groupManager.queryOneByDeviceId(deviceId, businessGroup); if (group == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "虚拟组织不存在"); } List allParent = getAllParent(group); List groupList = new LinkedList<>(allParent); groupList.add(group); return groupList; } private List getAllParent(Group group) { if (group.getParentId() == null || group.getBusinessGroup() == null) { return new ArrayList<>(); } Group parent = groupManager.queryOneByDeviceId(group.getParentDeviceId(), group.getBusinessGroup()); if (parent == null) { return new ArrayList<>(); } List allParent = getAllParent(parent); allParent.add(parent); return allParent; } @Override public PageInfo queryList(Integer page, Integer count, String query) { PageHelper.startPage(page, count); if (query != null) { query = query.replaceAll("/", "//") .replaceAll("%", "/%") .replaceAll("_", "/_"); } List all = groupManager.query(query, null, null); return new PageInfo<>(all); } @Override public Group queryGroupByAlias(String groupAlias) { return groupManager.queryGroupByAlias(groupAlias); } @Override public Map queryGroupByAliasMap() { return groupManager.queryGroupByAliasMap(); } @Override @Transactional public void saveByAlias(Collection groups) { // 清空别名数据 groupManager.deleteHasAlias(); // 写入新数据 groupManager.batchAdd(new ArrayList<>(groups)); // 修复数据丢失的parentID groupManager.fixParentId(); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/InviteStreamServiceImpl.java ================================================ package com.genersoft.iot.vmp.gb28181.service.impl; import com.alibaba.fastjson2.JSON; import com.genersoft.iot.vmp.common.*; import com.genersoft.iot.vmp.common.enums.MediaApp; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.dao.DeviceChannelMapper; import com.genersoft.iot.vmp.gb28181.dao.DeviceMapper; import com.genersoft.iot.vmp.gb28181.service.IInviteStreamService; import com.genersoft.iot.vmp.media.event.media.MediaDepartureEvent; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.event.EventListener; import org.springframework.data.redis.core.Cursor; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ScanOptions; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; @Slf4j @Service public class InviteStreamServiceImpl implements IInviteStreamService { private final Map>> inviteErrorCallbackMap = new ConcurrentHashMap<>(); @Autowired private RedisTemplate redisTemplate; @Autowired private UserSetting userSetting; @Autowired private DeviceMapper deviceMapper; @Autowired private DeviceChannelMapper deviceChannelMapper; /** * 流离开的处理 */ @Async("taskExecutor") @EventListener public void onApplicationEvent(MediaDepartureEvent event) { if ("rtsp".equals(event.getSchema()) && MediaApp.GB28181.equals(event.getApp())) { InviteInfo inviteInfo = getInviteInfoByStream(null, event.getStream()); if (inviteInfo != null && (inviteInfo.getType() == InviteSessionType.PLAY || inviteInfo.getType() == InviteSessionType.PLAYBACK)) { removeInviteInfo(inviteInfo); Device device = deviceMapper.getDeviceByDeviceId(inviteInfo.getDeviceId()); if (device != null) { deviceChannelMapper.stopPlayById(inviteInfo.getChannelId()); } } } } @Override public void updateInviteInfo(InviteInfo inviteInfo) { if (InviteSessionStatus.ready == inviteInfo.getStatus()) { updateInviteInfo(inviteInfo, Long.valueOf(userSetting.getPlayTimeout()) * 2); } else { updateInviteInfo(inviteInfo, null); } } @Override public void updateInviteInfo(InviteInfo inviteInfo, Long time) { if (inviteInfo == null || (inviteInfo.getDeviceId() == null || inviteInfo.getChannelId() == null)) { log.warn("[更新Invite信息],参数不全: {}", JSON.toJSON(inviteInfo)); return; } InviteInfo inviteInfoForUpdate; if (InviteSessionStatus.ready == inviteInfo.getStatus()) { if (inviteInfo.getDeviceId() == null || inviteInfo.getChannelId() == null || inviteInfo.getType() == null || inviteInfo.getStream() == null ) { return; } inviteInfoForUpdate = inviteInfo; } else { InviteInfo inviteInfoInRedis = getInviteInfo(inviteInfo.getType(), inviteInfo.getChannelId(), inviteInfo.getStream()); if (inviteInfoInRedis == null) { log.warn("[更新Invite信息],未从缓存中读取到Invite信息: deviceId: {}, channel: {}, stream: {}", inviteInfo.getDeviceId(), inviteInfo.getChannelId(), inviteInfo.getStream()); return; } if (inviteInfo.getStreamInfo() != null) { inviteInfoInRedis.setStreamInfo(inviteInfo.getStreamInfo()); } if (inviteInfo.getSsrcInfo() != null) { inviteInfoInRedis.setSsrcInfo(inviteInfo.getSsrcInfo()); } if (inviteInfo.getStreamMode() != null) { inviteInfoInRedis.setStreamMode(inviteInfo.getStreamMode()); } if (inviteInfo.getReceiveIp() != null) { inviteInfoInRedis.setReceiveIp(inviteInfo.getReceiveIp()); } if (inviteInfo.getReceivePort() != null) { inviteInfoInRedis.setReceivePort(inviteInfo.getReceivePort()); } if (inviteInfo.getStatus() != null) { inviteInfoInRedis.setStatus(inviteInfo.getStatus()); } inviteInfoForUpdate = inviteInfoInRedis; } if (inviteInfoForUpdate.getCreateTime() == null) { inviteInfoForUpdate.setCreateTime(System.currentTimeMillis()); } String key = VideoManagerConstants.INVITE_PREFIX; String objectKey = inviteInfoForUpdate.getType() + ":" + inviteInfoForUpdate.getChannelId() + ":" + inviteInfoForUpdate.getStream(); if (time != null && time > 0) { inviteInfoForUpdate.setExpirationTime(time); } redisTemplate.opsForHash().put(key, objectKey, inviteInfoForUpdate); } @Override public InviteInfo updateInviteInfoForStream(InviteInfo inviteInfo, String stream) { InviteInfo inviteInfoInDb = getInviteInfo(inviteInfo.getType(), inviteInfo.getChannelId(), inviteInfo.getStream()); if (inviteInfoInDb == null) { return null; } removeInviteInfo(inviteInfoInDb); String key = VideoManagerConstants.INVITE_PREFIX; String objectKey = inviteInfo.getType() + ":" + inviteInfo.getChannelId() + ":" + stream; inviteInfoInDb.setStream(stream); if (inviteInfoInDb.getSsrcInfo() != null) { inviteInfoInDb.getSsrcInfo().setStream(stream); } if (InviteSessionStatus.ready == inviteInfo.getStatus()) { inviteInfoInDb.setExpirationTime((long) (userSetting.getPlayTimeout() * 2)); } if (inviteInfoInDb.getCreateTime() == null) { inviteInfoInDb.setCreateTime(System.currentTimeMillis()); } redisTemplate.opsForHash().put(key, objectKey, inviteInfoInDb); return inviteInfoInDb; } @Override public InviteInfo getInviteInfo(InviteSessionType type, Integer channelId, String stream) { String key = VideoManagerConstants.INVITE_PREFIX; String keyPattern = (type != null ? type : "*") + ":" + (channelId != null ? channelId : "*") + ":" + (stream != null ? stream : "*"); ScanOptions options = ScanOptions.scanOptions().match(keyPattern).count(20).build(); try (Cursor> cursor = redisTemplate.opsForHash().scan(key, options)) { if (cursor.hasNext()) { InviteInfo inviteInfo = (InviteInfo) cursor.next().getValue(); cursor.close(); return inviteInfo; } } catch (Exception e) { log.error("[Redis-InviteInfo] 查询异常: ", e); } return null; } @Override public List getAllInviteInfo() { List result = new ArrayList<>(); String key = VideoManagerConstants.INVITE_PREFIX; List values = redisTemplate.opsForHash().values(key); if(values.isEmpty()) { return result; } for (Object value : values) { result.add((InviteInfo)value); } return result; } @Override public InviteInfo getInviteInfoByDeviceAndChannel(InviteSessionType type, Integer channelId) { return getInviteInfo(type, channelId, null); } @Override public InviteInfo getInviteInfoByStream(InviteSessionType type, String stream) { return getInviteInfo(type, null, stream); } @Override public void removeInviteInfo(InviteSessionType type, Integer channelId, String stream) { String key = VideoManagerConstants.INVITE_PREFIX; if (type == null && channelId == null && stream == null) { redisTemplate.opsForHash().delete(key); return; } InviteInfo inviteInfo = getInviteInfo(type, channelId, stream); if (inviteInfo != null) { String objectKey = inviteInfo.getType() + ":" + inviteInfo.getChannelId() + ":" + inviteInfo.getStream(); redisTemplate.opsForHash().delete(key, objectKey); } } @Override public void removeInviteInfoByDeviceAndChannel(InviteSessionType inviteSessionType, Integer channelId) { removeInviteInfo(inviteSessionType, channelId, null); } @Override public void removeInviteInfo(InviteInfo inviteInfo) { removeInviteInfo(inviteInfo.getType(), inviteInfo.getChannelId(), inviteInfo.getStream()); } @Override public void once(InviteSessionType type, Integer channelId, String stream, ErrorCallback callback) { String key = buildKey(type, channelId, stream); List> callbacks = inviteErrorCallbackMap.computeIfAbsent(key, k -> new CopyOnWriteArrayList<>()); callbacks.add(callback); } private String buildKey(InviteSessionType type, Integer channelId, String stream) { String key = type + ":" + channelId; // 如果ssrc未null那么可以实现一个通道只能一次操作,ssrc不为null则可以支持一个通道多次invite if (stream != null) { key += (":" + stream); } return key; } @Override public void clearInviteInfo(String deviceId) { List inviteInfoList = getAllInviteInfo(); for (InviteInfo inviteInfo : inviteInfoList) { if (inviteInfo.getDeviceId().equals(deviceId)) { removeInviteInfo(inviteInfo); } } } @Override public int getStreamInfoCount(String mediaServerId) { int count = 0; String key = VideoManagerConstants.INVITE_PREFIX; List values = redisTemplate.opsForHash().values(key); if (values.isEmpty()) { return count; } for (Object value : values) { InviteInfo inviteInfo = (InviteInfo)value; if (inviteInfo != null && inviteInfo.getStreamInfo() != null && inviteInfo.getStreamInfo().getMediaServer() != null && inviteInfo.getStreamInfo().getMediaServer().getId().equals(mediaServerId)) { if (inviteInfo.getType().equals(InviteSessionType.DOWNLOAD) && inviteInfo.getStreamInfo().getProgress() == 1) { continue; } count++; } } return count; } @Override public void call(InviteSessionType type, Integer channelId, String stream, int code, String msg, StreamInfo data) { String key = buildSubStreamKey(type, channelId, stream); List> callbacks = inviteErrorCallbackMap.get(key); if (callbacks == null || callbacks.isEmpty()) { return; } for (ErrorCallback callback : callbacks) { if (callback != null) { callback.run(code, msg, data); } } inviteErrorCallbackMap.remove(key); } private String buildSubStreamKey(InviteSessionType type, Integer channelId, String stream) { String key = type + ":" + channelId; if (stream != null) { key += (":" + stream); } return key; } @Override public InviteInfo getInviteInfoBySSRC(String ssrc) { List inviteInfoList = getAllInviteInfo(); if (inviteInfoList.isEmpty()) { return null; } for (InviteInfo inviteInfo : inviteInfoList) { if (inviteInfo.getSsrcInfo() != null && ssrc.equals(inviteInfo.getSsrcInfo().getSsrc())) { return inviteInfo; } } return null; } @Override public InviteInfo updateInviteInfoForSSRC(InviteInfo inviteInfo, String ssrc) { InviteInfo inviteInfoInDb = getInviteInfo(inviteInfo.getType(), inviteInfo.getChannelId(), inviteInfo.getStream()); if (inviteInfoInDb == null) { return null; } removeInviteInfo(inviteInfoInDb); String key = VideoManagerConstants.INVITE_PREFIX; String objectKey = inviteInfo.getType() + ":" + inviteInfo.getChannelId() + ":" + inviteInfo.getStream(); if (inviteInfoInDb.getSsrcInfo() != null) { inviteInfoInDb.getSsrcInfo().setSsrc(ssrc); } redisTemplate.opsForHash().put(key, objectKey, inviteInfoInDb); return inviteInfoInDb; } @Scheduled(fixedRate = 10000) //定时检测,清理错误的redis数据,防止因为错误数据导致的点播不可用 public void execute(){ String key = VideoManagerConstants.INVITE_PREFIX; if(redisTemplate.opsForHash().size(key) == 0) { return; } List values = redisTemplate.opsForHash().values(key); for (Object value : values) { InviteInfo inviteInfo = (InviteInfo)value; if (inviteInfo.getStreamInfo() != null) { continue; } if (inviteInfo.getCreateTime() == null || inviteInfo.getExpirationTime() == 0) { removeInviteInfo(inviteInfo); } long time = inviteInfo.getCreateTime() + inviteInfo.getExpirationTime(); if (System.currentTimeMillis() > time) { removeInviteInfo(inviteInfo); } } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/PTZServiceImpl.java ================================================ package com.genersoft.iot.vmp.gb28181.service.impl; import com.genersoft.iot.vmp.common.enums.ChannelDataType; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; import com.genersoft.iot.vmp.gb28181.bean.Preset; import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService; import com.genersoft.iot.vmp.gb28181.service.IDeviceService; import com.genersoft.iot.vmp.gb28181.service.IPTZService; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcPlayService; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.Assert; import javax.sip.InvalidArgumentException; import javax.sip.SipException; import java.text.ParseException; import java.util.List; @Slf4j @Service public class PTZServiceImpl implements IPTZService { @Autowired private SIPCommander cmder; @Autowired private UserSetting userSetting; @Autowired private IRedisRpcPlayService redisRpcPlayService; @Autowired private IDeviceChannelService deviceChannelService; @Autowired private IDeviceService deviceService; @Override public void ptz(Device device, String channelId, int cmdCode, int horizonSpeed, int verticalSpeed, int zoomSpeed) { try { cmder.frontEndCmd(device, channelId, cmdCode, horizonSpeed, verticalSpeed, zoomSpeed); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 云台控制: {}", e.getMessage()); throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage()); } } @Override public void frontEndCommand(Device device, String channelId, int cmdCode, int parameter1, int parameter2, int combindCode2) { // 判断设备是否属于当前平台, 如果不属于则发起自动调用 if (!userSetting.getServerId().equals(device.getServerId())) { // 通道ID DeviceChannel deviceChannel = deviceChannelService.getOneForSource(device.getDeviceId(), channelId); Assert.notNull(deviceChannel, "通道不存在"); String msg = redisRpcPlayService.frontEndCommand(device.getServerId(), deviceChannel.getId(), cmdCode, parameter1, parameter2, combindCode2); if (msg != null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), msg); } return; } try { cmder.frontEndCmd(device, channelId, cmdCode, parameter1, parameter2, combindCode2); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 前端控制: {}", e.getMessage()); throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage()); } } @Override public void frontEndCommand(CommonGBChannel channel, Integer cmdCode, Integer parameter1, Integer parameter2, Integer combindCode2) { if (channel.getDataType() != ChannelDataType.GB28181) { // 只有国标通道的支持云台控制 log.warn("[INFO 消息] 只有国标通道的支持云台控制, 通道ID: {}", channel.getGbId()); throw new ControllerException(ErrorCode.ERROR100.getCode(), "不支持"); } Device device = deviceService.getDevice(channel.getDataDeviceId()); if (device == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到设备ID"); } DeviceChannel deviceChannel = deviceChannelService.getOneById(channel.getGbId()); frontEndCommand(device, deviceChannel.getDeviceId(), cmdCode, parameter1, parameter2, combindCode2); } @Override public void queryPresetList(CommonGBChannel channel, ErrorCallback> callback) { if (channel.getDataType() != ChannelDataType.GB28181) { // 只有国标通道的支持云台控制 log.warn("[INFO 消息] 只有国标通道的支持云台控制, 通道ID: {}", channel.getGbId()); throw new ControllerException(ErrorCode.ERROR100.getCode(), "不支持"); } Device device = deviceService.getDevice(channel.getDataDeviceId()); if (device == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到设备"); } DeviceChannel deviceChannel = deviceChannelService.getOneForSourceById(channel.getGbId()); if (deviceChannel == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到通道"); } deviceService.queryPreset(device, deviceChannel.getDeviceId(), callback); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/PlatformChannelServiceImpl.java ================================================ package com.genersoft.iot.vmp.gb28181.service.impl; import com.genersoft.iot.vmp.common.enums.ChannelDataType; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.controller.bean.ChannelListForRpcParam; import com.genersoft.iot.vmp.gb28181.dao.*; import com.genersoft.iot.vmp.gb28181.event.EventPublisher; import com.genersoft.iot.vmp.gb28181.event.channel.ChannelEvent; import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent; import com.genersoft.iot.vmp.gb28181.service.IPlatformChannelService; import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcService; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; import javax.sip.InvalidArgumentException; import javax.sip.SipException; import java.text.ParseException; import java.util.*; import java.util.stream.Collectors; /** * @author lin */ @Slf4j @Service public class PlatformChannelServiceImpl implements IPlatformChannelService { @Autowired private PlatformChannelMapper platformChannelMapper; @Autowired private EventPublisher eventPublisher; @Autowired private GroupMapper groupMapper; @Autowired private RegionMapper regionMapper; @Autowired private CommonGBChannelMapper commonGBChannelMapper; @Autowired private PlatformMapper platformMapper; @Autowired private ISIPCommanderForPlatform sipCommanderFroPlatform; @Autowired private SubscribeHolder subscribeHolder; @Autowired private UserSetting userSetting; @Autowired private IRedisRpcService redisRpcService; // 监听通道信息变化 @EventListener public void onApplicationEvent(ChannelEvent event) { if (event.getChannels().isEmpty()) { log.info("[国标级联-处理通道变化事件] 通道数量为空"); return; } String deviceIds = event.getChannels().stream().map(CommonGBChannel::getGbDeviceId).collect(Collectors.joining(",")); log.info("[国标级联-处理通道变化事件] 类型: {}, 通道: {}", event.getMessageType(), deviceIds); // 获取通道所关联的平台 List allPlatform = platformMapper.queryByServerId(userSetting.getServerId()); if (allPlatform.isEmpty()) { log.info("[国标级联-处理通道变化事件] 没有当前服务负责的平台"); return; } // 获取所用订阅 List platforms = subscribeHolder.getAllCatalogSubscribePlatform(allPlatform); Map> platformMap = new HashMap<>(); Map channelMap = new HashMap<>(); if (platforms.isEmpty()) { log.info("[国标级联-处理通道变化事件] 没有关联的平台的目录订阅"); return; } for (CommonGBChannel deviceChannel : event.getChannels()) { List parentPlatformsForGB = queryPlatFormListByChannelDeviceId( deviceChannel.getGbId(), platforms); platformMap.put(deviceChannel.getGbDeviceId(), parentPlatformsForGB); channelMap.put(deviceChannel.getGbDeviceId(), deviceChannel); } if (platformMap.isEmpty()) { log.info("[国标级联-处理通道变化事件] 开启订阅的平台都没有关联通道: {}", deviceIds); return; } switch (event.getMessageType()) { case ON: case OFF: case DEL: for (String serverGbId : platformMap.keySet()) { List platformList = platformMap.get(serverGbId); if (platformList != null && !platformList.isEmpty()) { for (Platform platform : platformList) { SubscribeInfo subscribeInfo = subscribeHolder.getCatalogSubscribe(platform.getServerGBId()); if (subscribeInfo == null) { continue; } log.info("[Catalog事件: {}]平台:{},影响通道{}", event.getMessageType(), platform.getServerGBId(), serverGbId); List deviceChannelList = new ArrayList<>(); CommonGBChannel deviceChannel = new CommonGBChannel(); deviceChannel.setGbDeviceId(serverGbId); deviceChannelList.add(deviceChannel); try { sipCommanderFroPlatform.sendNotifyForCatalogOther(event.getMessageType().name(), platform, deviceChannelList, subscribeInfo, null); } catch (InvalidArgumentException | ParseException | NoSuchFieldException | SipException | IllegalAccessException e) { log.error("[命令发送失败] 国标级联 Catalog通知: {}", e.getMessage()); } } }else { log.info("[Catalog事件: {}] 未找到上级平台: {}", event.getMessageType(), serverGbId); } } break; case VLOST: break; case DEFECT: break; case ADD: case UPDATE: for (String gbId : platformMap.keySet()) { List parentPlatforms = platformMap.get(gbId); if (parentPlatforms != null && !parentPlatforms.isEmpty()) { for (Platform platform : parentPlatforms) { SubscribeInfo subscribeInfo = subscribeHolder.getCatalogSubscribe(platform.getServerGBId()); if (subscribeInfo == null) { continue; } log.info("[Catalog事件: {}]平台:{},影响通道{}", event.getMessageType(), platform.getServerGBId(), gbId); List channelList = new ArrayList<>(); CommonGBChannel deviceChannel = channelMap.get(gbId); channelList.add(deviceChannel); try { sipCommanderFroPlatform.sendNotifyForCatalogAddOrUpdate(event.getMessageType().name(), platform, channelList, subscribeInfo, null); } catch (InvalidArgumentException | ParseException | NoSuchFieldException | SipException | IllegalAccessException e) { log.error("[命令发送失败] 国标级联 Catalog通知: {}", e.getMessage()); } } } } break; default: break; } } @EventListener public void onApplicationEvent(CatalogEvent event) { String deviceIds = event.getChannels().stream().map(CommonGBChannel::getGbDeviceId).collect(Collectors.joining(",")); log.info("[Catalog事件: {}] 通道: {}", event.getType(), deviceIds); Platform platform = event.getPlatform(); if (platform == null || platform.getServerGBId() == null) { log.info("[Catalog事件: {}] 缺少通道或通道数据异常: {}", event.getType(), deviceIds); return; } SubscribeInfo subscribe = subscribeHolder.getCatalogSubscribe(platform.getServerGBId()); if (subscribe == null) { log.info("[Catalog事件: {}] 平台未被目录订阅,取消发送: {}", event.getType(), deviceIds); return; } switch (event.getType()) { case CatalogEvent.ON: case CatalogEvent.OFF: case CatalogEvent.DEL: List channels = new ArrayList<>(); if (event.getChannels() != null) { channels.addAll(event.getChannels()); } if (!channels.isEmpty()) { log.info("[Catalog事件: {}]平台:{},影响通道{}", event.getType(), platform.getServerGBId(), deviceIds); try { sipCommanderFroPlatform.sendNotifyForCatalogOther(event.getType(), platform, channels, subscribe, null); } catch (InvalidArgumentException | ParseException | NoSuchFieldException | SipException | IllegalAccessException e) { log.error("[命令发送失败] 国标级联 Catalog通知: {}", e.getMessage()); } } break; case CatalogEvent.VLOST: break; case CatalogEvent.DEFECT: break; case CatalogEvent.ADD: case CatalogEvent.UPDATE: List deviceChannelList = new ArrayList<>(); if (event.getChannels() != null) { deviceChannelList.addAll(event.getChannels()); } if (!deviceChannelList.isEmpty()) { log.info("[Catalog事件: {}]平台:{},影响通道{}", event.getType(), platform.getServerGBId(), deviceIds); try { sipCommanderFroPlatform.sendNotifyForCatalogAddOrUpdate(event.getType(), platform, deviceChannelList, subscribe, null); } catch (InvalidArgumentException | ParseException | NoSuchFieldException | SipException | IllegalAccessException e) { log.error("[命令发送失败] 国标级联 Catalog通知: {}", e.getMessage()); } } break; default: break; } } @Override public PageInfo queryChannelList(int page, int count, String query, Integer channelType, Boolean online, Integer platformId, Boolean hasShare) { PageHelper.startPage(page, count); if (query != null) { query = query.replaceAll("/", "//") .replaceAll("%", "/%") .replaceAll("_", "/_"); } List all = platformChannelMapper.queryForPlatformForWebList(platformId, query, channelType, online, hasShare); return new PageInfo<>(all); } /** * 获取通道使用的分组中未分享的 */ @Transactional public Set getGroupNotShareByChannelList(List channelList, Integer platformId) { // 获取分组中未分享的节点 Set groupList = groupMapper.queryNotShareGroupForPlatformByChannelList(channelList, platformId); // 获取这些节点的所有父节点 if (groupList.isEmpty()) { return new HashSet<>(); } Set allGroup = getAllGroup(groupList); allGroup.addAll(groupList); // 获取全部节点中未分享的 return groupMapper.queryNotShareGroupForPlatformByGroupList(allGroup, platformId); } /** * 获取通道使用的分组中未分享的 */ private Set getRegionNotShareByChannelList(List channelList, Integer platformId) { // 获取分组中未分享的节点 Set regionSet = regionMapper.queryNotShareRegionForPlatformByChannelList(channelList, platformId); // 获取这些节点的所有父节点 if (regionSet.isEmpty()) { return new HashSet<>(); } Set allRegion = getAllRegion(regionSet); allRegion.addAll(regionSet); // 获取全部节点中未分享的 return regionMapper.queryNotShareRegionForPlatformByRegionList(allRegion, platformId); } /** * 移除空的共享,并返回移除的分组 */ @Transactional public Set deleteEmptyGroup(Set groupSet, Integer platformId) { Iterator iterator = groupSet.iterator(); while (iterator.hasNext()) { Group group = iterator.next(); // groupSet 为当前通道直接使用的分组,如果已经没有子分组与其他的通道,则可以移除 // 获取分组子节点 Set children = platformChannelMapper.queryShareChildrenGroup(group.getId(), platformId); if (!children.isEmpty()) { iterator.remove(); continue; } // 获取分组关联的通道 List channelList = commonGBChannelMapper.queryShareChannelByParentId(group.getDeviceId(), platformId); if (!channelList.isEmpty()) { iterator.remove(); continue; } platformChannelMapper.removePlatformGroupById(group.getId(), platformId); } // 如果空了,说明没有通道需要处理了 if (groupSet.isEmpty()) { return new HashSet<>(); } Set parent = platformChannelMapper.queryShareParentGroupByGroupSet(groupSet, platformId); if (parent.isEmpty()) { return groupSet; }else { Set parentGroupSet = deleteEmptyGroup(parent, platformId); groupSet.addAll(parentGroupSet); return groupSet; } } /** * 移除空的共享,并返回移除的行政区划 */ private Set deleteEmptyRegion(Set regionSet, Integer platformId) { Iterator iterator = regionSet.iterator(); while (iterator.hasNext()) { Region region = iterator.next(); // groupSet 为当前通道直接使用的分组,如果已经没有子分组与其他的通道,则可以移除 // 获取分组子节点 Set children = platformChannelMapper.queryShareChildrenRegion(region.getDeviceId(), platformId); if (!children.isEmpty()) { iterator.remove(); continue; } // 获取分组关联的通道 List channelList = commonGBChannelMapper.queryShareChannelByCivilCode(region.getDeviceId(), platformId); if (!channelList.isEmpty()) { iterator.remove(); continue; } platformChannelMapper.removePlatformRegionById(region.getId(), platformId); } // 如果空了,说明没有通道需要处理了 if (regionSet.isEmpty()) { return new HashSet<>(); } Set parent = platformChannelMapper.queryShareParentRegionByRegionSet(regionSet, platformId); if (parent.isEmpty()) { return regionSet; }else { Set parentGroupSet = deleteEmptyRegion(parent, platformId); regionSet.addAll(parentGroupSet); return regionSet; } } private Set getAllGroup(Set groupList ) { if (groupList.isEmpty()) { return new HashSet<>(); } Set channelList = groupMapper.queryParentInChannelList(groupList); if (channelList.isEmpty()) { return channelList; } Set allParentRegion = getAllGroup(channelList); channelList.addAll(allParentRegion); return channelList; } private Set getAllRegion(Set regionSet ) { if (regionSet.isEmpty()) { return new HashSet<>(); } Set channelList = regionMapper.queryParentInChannelList(regionSet); if (channelList.isEmpty()) { return channelList; } Set allParentRegion = getAllRegion(channelList); channelList.addAll(allParentRegion); return channelList; } @Override @Transactional public int addAllChannel(Integer platformId) { List channelListNotShare = platformChannelMapper.queryNotShare(platformId, null); Assert.notEmpty(channelListNotShare, "所有通道已共享"); return addChannelList(platformId, channelListNotShare); } @Override @Transactional public int addChannels(Integer platformId, List channelIds) { List channelListNotShare = platformChannelMapper.queryNotShare(platformId, channelIds); Assert.notEmpty(channelListNotShare, "通道已共享"); return addChannelList(platformId, channelListNotShare); } @Transactional public int addChannelList(Integer platformId, List channelList) { Platform platform = platformMapper.query(platformId); Assert.notNull(platform, "平台不存在"); String channelDeviceIds = channelList.stream().map(CommonGBChannel::getGbDeviceId).collect(Collectors.joining(",")); log.info("[共享通道] 平台:{}, 通道:{}", platform.getServerGBId(), channelDeviceIds); if (!userSetting.getServerId().equals(platform.getServerId())) { List channelIdList = channelList.stream().map(CommonGBChannel::getGbId).toList(); int result = redisRpcService.addPlatformChannelList(platform.getServerId(), new ChannelListForRpcParam(channelIdList, platformId)); if (result > 0) { log.info("[跨平台-共享通道] 成功, 平台:{}, 通道:{}", platform.getServerGBId(), channelDeviceIds); }else { log.info("[跨平台-共享通道] 失败, 平台:{}, 通道:{}", platform.getServerGBId(), channelDeviceIds); } return result; } int result = platformChannelMapper.addChannels(platformId, channelList); if (result > 0) { // 查询通道相关的行政区划信息是否共享,如果没共享就添加 Set regionListNotShare = getRegionNotShareByChannelList(channelList, platformId); if (!regionListNotShare.isEmpty()) { int addGroupResult = platformChannelMapper.addPlatformRegion(new ArrayList<>(regionListNotShare), platformId); if (addGroupResult > 0) { for (Region region : regionListNotShare) { // 分组信息排序时需要将顶层排在最后 channelList.add(0, CommonGBChannel.build(region)); } } } // 查询通道相关的分组信息是否共享,如果没共享就添加 Set groupListNotShare = getGroupNotShareByChannelList(channelList, platformId); if (!groupListNotShare.isEmpty()) { int addGroupResult = platformChannelMapper.addPlatformGroup(new ArrayList<>(groupListNotShare), platformId); if (addGroupResult > 0) { for (Group group : groupListNotShare) { // 分组信息排序时需要将顶层排在最后 channelList.add(0, CommonGBChannel.build(group)); } } } // 发送消息 try { // 发送catalog eventPublisher.catalogEventPublish(platform, channelList, CatalogEvent.ADD); } catch (Exception e) { log.warn("[关联通道] 发送失败,数量:{}", channelList.size(), e); } } return result; } @Override public int removeAllChannel(Integer platformId) { Platform platform = platformMapper.query(platformId); if (platform == null) { return 0; } log.info("[取消共享通道] 平台:{}, 通道:全部", platform.getServerGBId()); if (!userSetting.getServerId().equals(platform.getServerId())) { int result = redisRpcService.removeAllPlatformChannel(platform.getServerId(), platformId); if (result > 0) { log.info("[跨平台-取消共享通道] 成功, 平台:{}, 通道:全部", platform.getServerGBId()); }else { log.info("[跨平台-取消共享通道] 失败, 平台:{}, 通道:全部", platform.getServerGBId()); } return result; } List channelListShare = platformChannelMapper.queryShare(platformId, null); Assert.notEmpty(channelListShare, "未共享任何通道"); int result = platformChannelMapper.removeChannelsWithPlatform(platformId, channelListShare); if (result > 0) { // 查询通道相关的分组信息 Set regionSet = regionMapper.queryByChannelList(channelListShare); Set deleteRegion = deleteEmptyRegion(regionSet, platformId); if (!deleteRegion.isEmpty()) { for (Region region : deleteRegion) { channelListShare.add(0, CommonGBChannel.build(region)); } } // 查询通道相关的分组信息 Set groupSet = groupMapper.queryByChannelList(channelListShare); Set deleteGroup = deleteEmptyGroup(groupSet, platformId); if (!deleteGroup.isEmpty()) { for (Group group : deleteGroup) { channelListShare.add(0, CommonGBChannel.build(group)); } } // 发送消息 try { // 发送catalog eventPublisher.catalogEventPublish(platform, channelListShare, CatalogEvent.DEL); } catch (Exception e) { log.warn("[移除全部关联通道] 发送失败,数量:{}", channelListShare.size(), e); } } return result; } @Override @Transactional public void addChannelByDevice(Integer platformId, List deviceIds) { List channelList = commonGBChannelMapper.queryByGbDeviceIdsForIds(ChannelDataType.GB28181, deviceIds); addChannels(platformId, channelList); } @Override @Transactional public void removeChannelByDevice(Integer platformId, List deviceIds) { List channelList = commonGBChannelMapper.queryByGbDeviceIdsForIds(ChannelDataType.GB28181, deviceIds); removeChannels(platformId, channelList); } @Transactional public int removeChannelList(Integer platformId, List channelList) { Platform platform = platformMapper.query(platformId); if (platform == null) { log.info("[移除关联通道] 平台{}未查询到", platformId); return 0; } String channelDeviceIds = channelList.stream().map(CommonGBChannel::getGbDeviceId).collect(Collectors.joining(",")); log.info("[取消共享通道] 平台:{}, 通道: {}", platform.getServerGBId(), channelDeviceIds); if (!userSetting.getServerId().equals(platform.getServerId())) { List channelIds = channelList.stream().map(CommonGBChannel::getGbId).toList(); int result = redisRpcService.removePlatformChannelList(platform.getServerId(), new ChannelListForRpcParam(channelIds, platformId)); if (result > 0) { log.info("[跨平台-取消共享通道] 成功, 平台:{}, 通道: {}", platform.getServerGBId(), channelDeviceIds); }else { log.info("[跨平台-取消共享通道] 失败, 平台:{}, 通道: {}", platform.getServerGBId(), channelDeviceIds); } return result; } String deviceIds = channelList.stream().map(CommonGBChannel::getGbDeviceId).collect(Collectors.joining(",")); int result = platformChannelMapper.removeChannelsWithPlatform(platformId, channelList); if (result <= 0) { log.info("[取消共享通道] 平台{}未关联通道: {}", platformId, deviceIds); return 0; } // 查询通道相关的分组信息 Set regionSet = regionMapper.queryByChannelList(channelList); Set deleteRegion = deleteEmptyRegion(regionSet, platformId); if (!deleteRegion.isEmpty()) { for (Region region : deleteRegion) { channelList.add(0, CommonGBChannel.build(region)); } } // 查询通道相关的分组信息 Set groupSet = groupMapper.queryByChannelList(channelList); Set deleteGroup = deleteEmptyGroup(groupSet, platformId); if (!deleteGroup.isEmpty()) { for (Group group : deleteGroup) { channelList.add(0, CommonGBChannel.build(group)); } } // 发送消息 try { // 发送catalog eventPublisher.catalogEventPublish(platform, channelList, CatalogEvent.DEL); } catch (Exception e) { log.warn("[取消共享通道] 发送失败,数量:{}", channelList.size(), e); } return result; } @Override @Transactional public int removeChannels(Integer platformId, List channelIds) { List channelList = platformChannelMapper.queryShare(platformId, channelIds); if (channelList.isEmpty()) { log.info("[移除通道] 通道列表为空"); return 0; } return removeChannelList(platformId, channelList); } @Override @Transactional public void removeChannels(List ids) { List platformList = platformChannelMapper.queryPlatFormListByChannelList(ids); if (platformList.isEmpty()) { log.info("[移除多个通道] 未查询到通道关联的平台"); return; } for (Platform platform : platformList) { removeChannels(platform.getId(), ids); } } @Override @Transactional public void removeChannel(int channelId) { List platformList = platformChannelMapper.queryPlatFormListByChannelId(channelId); if (platformList.isEmpty()) { log.info("[移除多个通道] 未查询到通道:{} 关联的平台", channelId); return; } for (Platform platform : platformList) { ArrayList ids = new ArrayList<>(); ids.add(channelId); removeChannels(platform.getId(), ids); } } @Override public List queryByPlatform(Platform platform) { if (platform == null) { log.info("[查询通道所属平台] 平台参数为NULL"); return null; } List commonGBChannelList = commonGBChannelMapper.queryWithPlatform(platform.getId()); if (commonGBChannelList.isEmpty()) { return new ArrayList<>(); } List channelList = new ArrayList<>(); // 是否包含平台信息 if (platform.getCatalogWithPlatform() > 0) { CommonGBChannel channel = CommonGBChannel.build(platform); channelList.add(channel); } // 关联的行政区划信息 if (platform.getCatalogWithRegion() > 0) { // 查询关联平台的行政区划信息 List regionChannelList = regionMapper.queryByPlatform(platform.getId()); if (!regionChannelList.isEmpty()) { channelList.addAll(regionChannelList); } } if (platform.getCatalogWithGroup() > 0) { // 关联的分组信息 List groupChannelList = groupMapper.queryForPlatform(platform.getId()); if (!groupChannelList.isEmpty()) { channelList.addAll(groupChannelList); } } channelList.addAll(commonGBChannelList); return channelList; } @Override public void pushChannel(Integer platformId) { Platform platform = platformMapper.query(platformId); Assert.notNull(platform, "平台不存在"); if (!userSetting.getServerId().equals(platform.getServerId())) { boolean result = redisRpcService.pushPlatformChannel(platform.getServerId(), platformId); if (result) { log.info("[跨平台-主动推送通道] 成功, 平台:{}", platform.getServerGBId()); }else { log.info("[跨平台-主动推送通道] 失败, 平台:{}", platform.getServerGBId()); } return; } List channelList = queryByPlatform(platform); if (channelList.isEmpty()){ log.info("[推送通道] 平台:{} 未查询到通道信息", platform.getServerGBId()); return; } SubscribeInfo subscribeInfo = SubscribeInfo.buildSimulated(platform.getServerGBId(), platform.getServerIp()); try { sipCommanderFroPlatform.sendNotifyForCatalogAddOrUpdate(CatalogEvent.ADD, platform, channelList, subscribeInfo, null); } catch (InvalidArgumentException | ParseException | NoSuchFieldException | SipException | IllegalAccessException e) { log.error("[命令发送失败] 国标级联 Catalog通知: {}", e.getMessage()); } } @Override public void updateCustomChannel(PlatformChannel channel) { Platform platform = platformMapper.query(channel.getPlatformId()); Assert.notNull(platform, "平台不存在"); log.info("[国标级联-自定义共享通道] 平台:{}, 通道:{}", platform.getServerGBId(), channel); if (!userSetting.getServerId().equals(platform.getServerId())) { boolean result = redisRpcService.updateCustomPlatformChannel(platform.getServerId(), channel); if (result) { log.info("[国标级联-自定义共享通道] 成功, 平台:{}, 通道:{}", platform.getServerGBId(), channel); }else { log.info("[国标级联-自定义共享通道] 失败, 平台:{}, 通道:{}", platform.getServerGBId(), channel); } return; } platformChannelMapper.updateCustomChannel(channel); CommonGBChannel commonGBChannel = platformChannelMapper.queryShareChannel(channel.getPlatformId(), channel.getGbId()); // 发送消息 try { // 发送catalog eventPublisher.catalogEventPublish(platform, commonGBChannel, CatalogEvent.UPDATE); } catch (Exception e) { log.warn("[国标级联-自定义共享通道] 发送失败, 平台ID: {}, 通道: {}({})", channel.getPlatformId(), channel.getGbName(), channel.getId(), e); } } @Override @Transactional public void checkGroupRemove(List channelList, List groupList) { List channelIds = new ArrayList<>(); channelList.stream().forEach(commonGBChannel -> { channelIds.add(commonGBChannel.getGbId()); }); // 获取关联这些通道的平台 List platformList = platformChannelMapper.queryPlatFormListByChannelList(channelIds); if (platformList.isEmpty()) { String deviceIds = channelList.stream().map(CommonGBChannel::getGbDeviceId).collect(Collectors.joining(",")); log.info("[获取关联这些通道的平台] 未查询到通道关联的平台, 通道如下 {}", deviceIds); return; } for (Platform platform : platformList) { Set groupSet; if (groupList == null || groupList.isEmpty()) { groupSet = platformChannelMapper.queryShareGroup(platform.getId()); }else { groupSet = new HashSet<>(groupList); } // 清理空的分组并发送消息 Set deleteGroup = deleteEmptyGroup(groupSet, platform.getId()); List channelListForEvent = new ArrayList<>(); if (!deleteGroup.isEmpty()) { for (Group group : deleteGroup) { channelListForEvent.add(0, CommonGBChannel.build(group)); } } // 发送消息 try { // 发送catalog eventPublisher.catalogEventPublish(platform, channelListForEvent, CatalogEvent.DEL); } catch (Exception e) { log.warn("[移除关联通道] 发送失败,数量:{}", channelList.size(), e); } } } @Override @Transactional public void checkRegionRemove(List channelList, List regionList) { List channelIds = new ArrayList<>(); channelList.stream().forEach(commonGBChannel -> { channelIds.add(commonGBChannel.getGbId()); }); // 获取关联这些通道的平台 List platformList = platformChannelMapper.queryPlatFormListByChannelList(channelIds); if (platformList.isEmpty()) { String deviceIds = channelList.stream().map(CommonGBChannel::getGbDeviceId).collect(Collectors.joining(",")); log.info("[获取关联这些通道的平台] 未查询到通道关联的平台, 通道如下 {}", deviceIds); return; } for (Platform platform : platformList) { Set regionSet; if (regionList == null || regionList.isEmpty()) { regionSet = platformChannelMapper.queryShareRegion(platform.getId()); }else { regionSet = new HashSet<>(regionList); } // 清理空的分组并发送消息 Set deleteRegion = deleteEmptyRegion(regionSet, platform.getId()); List channelListForEvent = new ArrayList<>(); if (!deleteRegion.isEmpty()) { for (Region region : deleteRegion) { channelListForEvent.add(0, CommonGBChannel.build(region)); } } // 发送消息 try { // 发送catalog eventPublisher.catalogEventPublish(platform, channelListForEvent, CatalogEvent.DEL); } catch (Exception e) { log.warn("[移除关联通道] 发送失败,数量:{}", channelList.size(), e); } } } @Override @Transactional public void checkGroupAdd(List channelList) { List channelIds = new ArrayList<>(); channelList.stream().forEach(commonGBChannel -> { channelIds.add(commonGBChannel.getGbId()); }); List platformList = platformChannelMapper.queryPlatFormListByChannelList(channelIds); if (platformList.isEmpty()) { String deviceIds = channelList.stream().map(CommonGBChannel::getGbDeviceId).collect(Collectors.joining(",")); log.info("[获取关联这些通道的平台] 未查询到通道关联的平台, 通道如下 {}", deviceIds); return; } for (Platform platform : platformList) { Set addGroup = getGroupNotShareByChannelList(channelList, platform.getId()); List channelListForEvent = new ArrayList<>(); if (!addGroup.isEmpty()) { for (Group group : addGroup) { channelListForEvent.add(0, CommonGBChannel.build(group)); } platformChannelMapper.addPlatformGroup(addGroup, platform.getId()); // 发送消息 try { // 发送catalog eventPublisher.catalogEventPublish(platform, channelListForEvent, CatalogEvent.ADD); } catch (Exception e) { log.warn("[移除关联通道] 发送失败,数量:{}", channelList.size(), e); } } } } @Override public void checkRegionAdd(List channelList) { List channelIds = new ArrayList<>(); channelList.stream().forEach(commonGBChannel -> { channelIds.add(commonGBChannel.getGbId()); }); List platformList = platformChannelMapper.queryPlatFormListByChannelList(channelIds); if (platformList.isEmpty()) { String deviceIds = channelList.stream().map(CommonGBChannel::getGbDeviceId).collect(Collectors.joining(",")); log.info("[获取关联这些通道的平台] 未查询到通道关联的平台, 通道如下 {}", deviceIds); return; } for (Platform platform : platformList) { Set addRegion = getRegionNotShareByChannelList(channelList, platform.getId()); List channelListForEvent = new ArrayList<>(); if (!addRegion.isEmpty()) { for (Region region : addRegion) { channelListForEvent.add(0, CommonGBChannel.build(region)); } platformChannelMapper.addPlatformRegion(new ArrayList<>(addRegion), platform.getId()); // 发送消息 try { // 发送catalog eventPublisher.catalogEventPublish(platform, channelListForEvent, CatalogEvent.ADD); } catch (Exception e) { log.warn("[移除关联通道] 发送失败,数量:{}", channelList.size(), e); } } } } @Override public List queryPlatFormListByChannelDeviceId(Integer channelId, List platforms) { return platformChannelMapper.queryPlatFormListForGBWithGBId(channelId, platforms); } @Override public CommonGBChannel queryChannelByPlatformIdAndChannelId(Integer platformId, Integer channelId) { return platformChannelMapper.queryShareChannel(platformId, channelId); } @Override public List queryChannelByPlatformIdAndChannelIds(Integer platformId, List channelIds) { return platformChannelMapper.queryShare(platformId, channelIds); } @Override public List queryByPlatformBySharChannelId(String channelDeviceId) { List commonGBChannels = commonGBChannelMapper.queryByDeviceId(channelDeviceId); ArrayList ids = new ArrayList<>(); for (CommonGBChannel commonGBChannel : commonGBChannels) { ids.add(commonGBChannel.getGbId()); } return platformChannelMapper.queryPlatFormListByChannelList(ids); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/PlatformServiceImpl.java ================================================ package com.genersoft.iot.vmp.gb28181.service.impl; import com.genersoft.iot.vmp.common.*; import com.genersoft.iot.vmp.common.enums.MediaApp; import com.genersoft.iot.vmp.conf.DynamicTask; import com.genersoft.iot.vmp.conf.SipConfig; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.dao.PlatformChannelMapper; import com.genersoft.iot.vmp.gb28181.dao.PlatformMapper; import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; import com.genersoft.iot.vmp.gb28181.service.IGbChannelService; import com.genersoft.iot.vmp.gb28181.service.IInviteStreamService; import com.genersoft.iot.vmp.gb28181.service.IPlatformService; import com.genersoft.iot.vmp.gb28181.session.SSRCFactory; import com.genersoft.iot.vmp.gb28181.session.SipInviteSessionManager; import com.genersoft.iot.vmp.gb28181.task.platformStatus.PlatformKeepaliveTask; import com.genersoft.iot.vmp.gb28181.task.platformStatus.PlatformRegisterTask; import com.genersoft.iot.vmp.gb28181.task.platformStatus.PlatformRegisterTaskInfo; import com.genersoft.iot.vmp.gb28181.task.platformStatus.PlatformStatusTaskRunner; import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; import com.genersoft.iot.vmp.gb28181.utils.SipUtils; import com.genersoft.iot.vmp.media.bean.MediaInfo; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.event.hook.HookData; import com.genersoft.iot.vmp.media.event.hook.HookSubscribe; import com.genersoft.iot.vmp.media.event.media.MediaDepartureEvent; import com.genersoft.iot.vmp.media.event.mediaServer.MediaSendRtpStoppedEvent; import com.genersoft.iot.vmp.media.service.IMediaServerService; import com.genersoft.iot.vmp.service.IReceiveRtpServerService; import com.genersoft.iot.vmp.service.ISendRtpServerService; import com.genersoft.iot.vmp.service.bean.*; import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcService; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import gov.nist.javax.sip.message.SIPResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.context.event.EventListener; import org.springframework.core.annotation.Order; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; import javax.sdp.*; import javax.sip.InvalidArgumentException; import javax.sip.ResponseEvent; import javax.sip.SipException; import java.text.ParseException; import java.util.List; import java.util.UUID; import java.util.Vector; import java.util.concurrent.TimeUnit; /** * @author lin */ @Slf4j @Service @Order(value=15) public class PlatformServiceImpl implements IPlatformService, CommandLineRunner { @Autowired private PlatformMapper platformMapper; @Autowired private IRedisCatchStorage redisCatchStorage; @Autowired private SSRCFactory ssrcFactory; @Autowired private IMediaServerService mediaServerService; @Autowired private ISIPCommanderForPlatform commanderForPlatform; @Autowired private DynamicTask dynamicTask; @Autowired private SubscribeHolder subscribeHolder; @Autowired private UserSetting userSetting; @Autowired private IRedisRpcService redisRpcService; @Autowired private SipConfig sipConfig; @Autowired private SipInviteSessionManager sessionManager; @Autowired private IInviteStreamService inviteStreamService; @Autowired private PlatformChannelMapper platformChannelMapper; @Autowired private IGbChannelService channelService; @Autowired private ISendRtpServerService sendRtpServerService; @Autowired private IReceiveRtpServerService receiveRtpServerService; @Autowired private PlatformStatusTaskRunner statusTaskRunner; @Override public void run(String... args) throws Exception { // 查找国标推流 List sendRtpItems = redisCatchStorage.queryAllSendRTPServer(); if (!sendRtpItems.isEmpty()) { for (SendRtpInfo sendRtpItem : sendRtpItems) { MediaServer mediaServerItem = mediaServerService.getOne(sendRtpItem.getMediaServerId()); CommonGBChannel channel = channelService.getOne(sendRtpItem.getChannelId()); if (channel == null){ continue; } sendRtpServerService.delete(sendRtpItem); if (mediaServerItem != null) { ssrcFactory.releaseSsrc(sendRtpItem.getMediaServerId(), sendRtpItem.getSsrc()); boolean stopResult = mediaServerService.initStopSendRtp(mediaServerItem, sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getSsrc()); if (stopResult) { Platform platform = queryPlatformByServerGBId(sendRtpItem.getTargetId()); if (platform != null && userSetting.getServerId().equals(platform.getServerId())) { try { commanderForPlatform.streamByeCmd(platform, sendRtpItem, channel); } catch (InvalidArgumentException | ParseException | SipException e) { log.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage()); } } } } } } // 启动时 如果存在未过期的注册平台,则发送注销 List registerTaskInfoList = statusTaskRunner.getAllRegisterTaskInfo(); if (registerTaskInfoList.isEmpty()) { return; } for (PlatformRegisterTaskInfo taskInfo : registerTaskInfoList) { log.info("[国标级联] 启动服务是发现平台注册仍在有效期,注销: {}", taskInfo.getPlatformServerId()); Platform platform = queryPlatformByServerGBId(taskInfo.getPlatformServerId()); if (platform == null) { statusTaskRunner.removeRegisterTask(taskInfo.getPlatformServerId()); continue; } if (userSetting.getServerId().equals(platform.getServerId())) { sendUnRegister(platform, taskInfo.getSipTransactionInfo()); } } // 启动时所有平台默认离线 platformMapper.offlineAll(userSetting.getServerId()); } @Scheduled(fixedDelay = 20, timeUnit = TimeUnit.SECONDS) //每3秒执行一次 public void statusLostCheck(){ // 每隔20秒检测,是否存在启用但是未注册的平台,存在则发起注册 // 获取所有在线并且启用的平台 List platformList = platformMapper.queryServerIdsWithEnableAndServer(userSetting.getServerId()); if (platformList.isEmpty()) { return; } for (Platform platform : platformList) { if (statusTaskRunner.containsRegister(platform.getServerGBId()) && statusTaskRunner.containsKeepAlive(platform.getServerGBId())) { continue; } if (statusTaskRunner.containsRegister(platform.getServerGBId())) { SipTransactionInfo transactionInfo = statusTaskRunner.getRegisterTransactionInfo(platform.getServerGBId()); // 注销后出发平台离线, 如果是启用的平台,那么下次丢失检测会检测到并重新注册上线 sendUnRegister(platform, transactionInfo); }else { statusTaskRunner.removeKeepAliveTask(platform.getServerGBId()); sendRegister(platform, null); } } } private void sendRegister(Platform platform, SipTransactionInfo sipTransactionInfo) { try { commanderForPlatform.register(platform, sipTransactionInfo, eventResult -> { log.info("[国标级联] {}({}),注册失败", platform.getName(), platform.getServerGBId()); offline(platform); }, null); } catch (InvalidArgumentException | ParseException | SipException e) { log.error("[命令发送失败] 国标级联: {}", e.getMessage()); } } private void sendUnRegister(Platform platform, SipTransactionInfo sipTransactionInfo) { statusTaskRunner.removeRegisterTask(platform.getServerGBId()); statusTaskRunner.removeKeepAliveTask(platform.getServerGBId()); try { commanderForPlatform.unregister(platform, sipTransactionInfo, null, eventResult -> { log.info("[国标级联] 注销成功, 平台:{}", platform.getServerGBId()); }); } catch (InvalidArgumentException | ParseException | SipException e) { log.error("[命令发送失败] 国标级联: {}", e.getMessage()); } } // 定时监听国标级联所进行的WVP服务是否正常, 如果异常则选择新的wvp执行 @Scheduled(fixedDelay = 2, timeUnit = TimeUnit.SECONDS) //每3秒执行一次 public void execute(){ if (!userSetting.isAutoRegisterPlatform()) { return; } // 查找非平台的国标级联执行服务Id List serverIds = platformMapper.queryServerIdsWithEnableAndNotInServer(userSetting.getServerId()); if (serverIds == null || serverIds.isEmpty()) { return; } serverIds.forEach(serverId -> { // 检查每个是否存活 ServerInfo serverInfo = redisCatchStorage.queryServerInfo(serverId); if (serverInfo != null) { return; } log.info("[集群] 检测到 {} 已离线", serverId); redisCatchStorage.removeOfflineWVPInfo(serverId); String chooseServerId = redisCatchStorage.chooseOneServer(serverId); if (!userSetting.getServerId().equals(chooseServerId)){ return; } // 此平台需要选择新平台处理, 确定由当前平台即开始处理 List platformList = platformMapper.queryByServerId(serverId); platformList.forEach(platform -> { log.info("[集群] 由本平台开启上级平台{}({})的注册", platform.getName(), platform.getServerGBId()); // 设置平台使用当前平台的IP platform.setAddress(getIpWithSameNetwork(platform.getAddress())); platform.setServerId(userSetting.getServerId()); platformMapper.update(platform); // 检查就平台是否注册到期,没有则注销,由本平台重新注册 List taskInfoList = statusTaskRunner.getRegisterTransactionInfoByServerId(serverId); boolean needUnregister = false; SipTransactionInfo sipTransactionInfo = null; if (!taskInfoList.isEmpty()) { for (PlatformRegisterTaskInfo taskInfo : taskInfoList) { if (taskInfo.getPlatformServerId().equals(platform.getServerGBId()) && taskInfo.getSipTransactionInfo() != null) { needUnregister = true; sipTransactionInfo = taskInfo.getSipTransactionInfo(); break; } } } if (needUnregister) { sendUnRegister(platform, sipTransactionInfo); }else { // 开始注册 // 注册成功时由程序直接调用了online方法 sendRegister(platform, null); } }); }); } /** * 获取同网段的IP */ private String getIpWithSameNetwork(String ip){ if (ip == null || sipConfig.getMonitorIps().size() == 1) { return sipConfig.getMonitorIps().get(0); } String[] ipSplit = ip.split("\\."); String ip1 = null, ip2 = null, ip3 = null; for (String monitorIp : sipConfig.getMonitorIps()) { String[] monitorIpSplit = monitorIp.split("\\."); if (monitorIpSplit[0].equals(ipSplit[0]) && monitorIpSplit[1].equals(ipSplit[1]) && monitorIpSplit[2].equals(ipSplit[2])) { ip3 = monitorIp; }else if (monitorIpSplit[0].equals(ipSplit[0]) && monitorIpSplit[1].equals(ipSplit[1])) { ip2 = monitorIp; }else if (monitorIpSplit[0].equals(ipSplit[0])) { ip1 = monitorIp; } } if (ip3 != null) { return ip3; }else if (ip2 != null) { return ip2; }else if (ip1 != null) { return ip1; }else { return sipConfig.getMonitorIps().get(0); } } /** * 流离开的处理 */ @Async("taskExecutor") @EventListener public void onApplicationEvent(MediaDepartureEvent event) { List sendRtpItems = sendRtpServerService.queryByStream(event.getStream()); if (!sendRtpItems.isEmpty()) { for (SendRtpInfo sendRtpItem : sendRtpItems) { if (sendRtpItem != null && sendRtpItem.getApp().equals(event.getApp()) && sendRtpItem.isSendToPlatform()) { String platformId = sendRtpItem.getTargetId(); Platform platform = platformMapper.getParentPlatByServerGBId(platformId); CommonGBChannel channel = channelService.getOne(sendRtpItem.getChannelId()); try { if (platform != null && channel != null) { commanderForPlatform.streamByeCmd(platform, sendRtpItem, channel); sendRtpServerService.delete(sendRtpItem); } } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 发送BYE: {}", e.getMessage()); } } } } } /** * 发流停止 */ @Async("taskExecutor") @EventListener public void onApplicationEvent(MediaSendRtpStoppedEvent event) { List sendRtpItems = sendRtpServerService.queryByStream(event.getStream()); if (sendRtpItems != null && !sendRtpItems.isEmpty()) { for (SendRtpInfo sendRtpItem : sendRtpItems) { if (sendRtpItem != null && sendRtpItem.getApp().equals(event.getApp()) && sendRtpItem.isSendToPlatform()) { Platform platform = platformMapper.getParentPlatByServerGBId(sendRtpItem.getTargetId()); CommonGBChannel channel = channelService.getOne(sendRtpItem.getChannelId()); ssrcFactory.releaseSsrc(sendRtpItem.getMediaServerId(), sendRtpItem.getSsrc()); try { commanderForPlatform.streamByeCmd(platform, sendRtpItem, channel); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage()); } sendRtpServerService.delete(sendRtpItem); } } } } @Override public Platform queryPlatformByServerGBId(String platformGbId) { return platformMapper.getParentPlatByServerGBId(platformGbId); } @Override public PageInfo queryPlatformList(int page, int count, String query) { PageHelper.startPage(page, count); if (query != null) { query = query.replaceAll("/", "//") .replaceAll("%", "/%") .replaceAll("_", "/_"); } List all = platformMapper.queryList(query); return new PageInfo<>(all); } @Override public boolean add(Platform platform) { log.info("[国标级联]添加平台 {}", platform.getDeviceGBId()); if (platform.getCatalogGroup() == 0) { // 每次发送目录的数量默认为1 platform.setCatalogGroup(1); } platform.setServerId(userSetting.getServerId()); int result = platformMapper.add(platform); if (platform.isEnable()) { // 保存时启用就发送注册 // 注册成功时由程序直接调用了online方法 sendRegister(platform, null); } return result > 0; } @Override public boolean update(Platform platform) { Assert.isTrue(platform.getId() > 0, "ID必须存在"); log.info("[国标级联] 更新平台 {}({})", platform.getName(), platform.getDeviceGBId()); platform.setCharacterSet(platform.getCharacterSet().toUpperCase()); Platform platformInDb = platformMapper.query(platform.getId()); Assert.notNull(platformInDb, "平台不存在"); if (!userSetting.getServerId().equals(platformInDb.getServerId())) { return redisRpcService.updatePlatform(platformInDb.getServerId(), platform); } // 更新数据库 if (platform.getCatalogGroup() == 0) { platform.setCatalogGroup(1); } platformMapper.update(platform); if (statusTaskRunner.containsRegister(platformInDb.getServerGBId())) { SipTransactionInfo transactionInfo = statusTaskRunner.getRegisterTransactionInfo(platformInDb.getServerGBId()); // 注销后出发平台离线, 如果是启用的平台,那么下次丢失检测会检测到并重新注册上线 sendUnRegister(platformInDb, transactionInfo); }else if (platform.isEnable()) { sendRegister(platform, null); } return false; } @Override public void online(Platform platform, SipTransactionInfo sipTransactionInfo) { log.info("[国标级联]:{}, 平台上线", platform.getServerGBId()); PlatformRegisterTask registerTask = new PlatformRegisterTask(platform.getServerGBId(), platform.getExpires() * 1000L - 500L, sipTransactionInfo, (platformServerGbId) -> { this.registerExpire(platformServerGbId, sipTransactionInfo); }); statusTaskRunner.addRegisterTask(registerTask); PlatformKeepaliveTask keepaliveTask = new PlatformKeepaliveTask(platform.getServerGBId(), platform.getKeepTimeout() * 1000L, this::keepaliveExpire); statusTaskRunner.addKeepAliveTask(keepaliveTask); platformMapper.updateStatus(platform.getId(), true, userSetting.getServerId()); if (platform.getAutoPushChannel() != null && platform.getAutoPushChannel()) { if (subscribeHolder.getCatalogSubscribe(platform.getServerGBId()) == null) { log.info("[国标级联]:{}, 添加自动通道推送模拟订阅信息", platform.getServerGBId()); addSimulatedSubscribeInfo(platform); } }else { SubscribeInfo catalogSubscribe = subscribeHolder.getCatalogSubscribe(platform.getServerGBId()); if (catalogSubscribe != null && catalogSubscribe.getExpires() == -1) { subscribeHolder.removeCatalogSubscribe(platform.getServerGBId()); } } } /** * 注册到期处理 */ private void registerExpire(String platformServerId, SipTransactionInfo transactionInfo) { log.info("[国标级联] 注册到期, 上级平台编号: {}", platformServerId); Platform platform = queryPlatformByServerGBId(platformServerId); if (platform == null || !platform.isEnable()) { log.info("[国标级联] 注册到期, 上级平台编号: {}, 平台不存在或者未启用, 忽略", platformServerId); return; } sendRegister(platform, transactionInfo); } private void keepaliveExpire(String platformServerId, int failCount) { Platform platform = queryPlatformByServerGBId(platformServerId); if (platform == null || !platform.isEnable()) { log.info("[国标级联] 心跳到期, 上级平台编号: {}, 平台不存在或者未启用, 忽略", platformServerId); return; } try { commanderForPlatform.keepalive(platform, eventResult -> { // 心跳失败 if (eventResult.type != SipSubscribe.EventResultType.timeout) { log.warn("[国标级联] 发送心跳收到错误,code: {}, msg: {}", eventResult.statusCode, eventResult.msg); } // 心跳超时失败 if (failCount < 2) { log.info("[国标级联] 心跳发送超时, 平台服务编号: {}", platformServerId); PlatformKeepaliveTask keepaliveTask = new PlatformKeepaliveTask(platform.getServerGBId(), platform.getKeepTimeout() * 1000L, this::keepaliveExpire); keepaliveTask.setFailCount(failCount + 1); statusTaskRunner.addKeepAliveTask(keepaliveTask); }else { // 心跳超时三次, 不再发送心跳, 平台离线 log.info("[国标级联] 心跳发送超时三次,平台离线, 平台服务编号: {}", platformServerId); offline(platform); } }, eventResult -> { PlatformKeepaliveTask keepaliveTask = new PlatformKeepaliveTask(platform.getServerGBId(), platform.getKeepTimeout() * 1000L, this::keepaliveExpire); statusTaskRunner.addKeepAliveTask(keepaliveTask); }); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 国标级联 发送心跳: {}", e.getMessage()); if (failCount < 2) { PlatformKeepaliveTask keepaliveTask = new PlatformKeepaliveTask(platform.getServerGBId(), platform.getKeepTimeout() * 1000L, this::keepaliveExpire); keepaliveTask.setFailCount(failCount + 1); statusTaskRunner.addKeepAliveTask(keepaliveTask); }else { // 心跳超时三次, 不再发送心跳, 平台离线 log.info("[国标级联] 心跳发送失败三次,平台离线, 平台服务编号: {}", platformServerId); offline(platform); } } } @Override public void addSimulatedSubscribeInfo(Platform platform) { // 自动添加一条模拟的订阅信息 subscribeHolder.putCatalogSubscribe(platform.getServerGBId(), SubscribeInfo.buildSimulated(platform.getServerGBId(), platform.getServerIp())); } @Override public void offline(Platform platform) { log.info("[平台离线]:{}({})", platform.getName(), platform.getServerGBId()); statusTaskRunner.removeRegisterTask(platform.getServerGBId()); statusTaskRunner.removeKeepAliveTask(platform.getServerGBId()); subscribeHolder.removeCatalogSubscribe(platform.getServerGBId()); subscribeHolder.removeMobilePositionSubscribe(platform.getServerGBId()); platformMapper.updateStatus(platform.getId(), false, userSetting.getServerId()); // 停止所有推流 log.info("[平台离线] {}({}), 停止所有推流", platform.getName(), platform.getServerGBId()); stopAllPush(platform.getServerGBId()); } private void stopAllPush(String platformId) { List sendRtpItems = sendRtpServerService.queryForPlatform(platformId); if (sendRtpItems != null && !sendRtpItems.isEmpty()) { for (SendRtpInfo sendRtpItem : sendRtpItems) { ssrcFactory.releaseSsrc(sendRtpItem.getMediaServerId(), sendRtpItem.getSsrc()); sendRtpServerService.delete(sendRtpItem); MediaServer mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId()); mediaServerService.stopSendRtp(mediaInfo, sendRtpItem.getApp(), sendRtpItem.getStream(), null); } } } @Override public void sendNotifyMobilePosition(String platformId) { Platform platform = platformMapper.getParentPlatByServerGBId(platformId); if (platform == null) { return; } SubscribeInfo subscribe = subscribeHolder.getMobilePositionSubscribe(platform.getServerGBId()); if (subscribe != null) { List channelList = platformChannelMapper.queryShare(platform.getId(), null); if (channelList.isEmpty()) { return; } for (CommonGBChannel channel : channelList) { GPSMsgInfo gpsMsgInfo = redisCatchStorage.getGpsMsgInfo(channel.getGbDeviceId()); // 无最新位置则发送当前位置 if (gpsMsgInfo != null && (gpsMsgInfo.getLng() == 0 && gpsMsgInfo.getLat() == 0)) { gpsMsgInfo = null; } if (gpsMsgInfo == null && !userSetting.isSendPositionOnDemand()){ gpsMsgInfo = new GPSMsgInfo(); gpsMsgInfo.setId(channel.getGbDeviceId()); gpsMsgInfo.setLng(channel.getGbLongitude()); gpsMsgInfo.setLat(channel.getGbLatitude()); gpsMsgInfo.setAltitude(channel.getGpsAltitude()); gpsMsgInfo.setSpeed(channel.getGpsSpeed()); gpsMsgInfo.setDirection(channel.getGpsDirection()); gpsMsgInfo.setTime(channel.getGpsTime()); } // 无最新位置不发送 if (gpsMsgInfo != null) { // 发送GPS消息 try { commanderForPlatform.sendNotifyMobilePosition(platform, gpsMsgInfo, channel, subscribe); } catch (InvalidArgumentException | ParseException | NoSuchFieldException | SipException | IllegalAccessException e) { log.error("[命令发送失败] 国标级联 移动位置通知: {}", e.getMessage()); } } } } } @Override public void broadcastInvite(Platform platform, CommonGBChannel channel, String sourceId, MediaServer mediaServerItem, HookSubscribe.Event hookEvent, SipSubscribe.Event errorEvent, InviteTimeOutCallback timeoutCallback) throws InvalidArgumentException, ParseException, SipException { if (mediaServerItem == null) { log.info("[国标级联] 语音喊话未找到可用的zlm. platform: {}", platform.getServerGBId()); return; } InviteInfo inviteInfoForOld = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.BROADCAST, channel.getGbId()); if (inviteInfoForOld != null && inviteInfoForOld.getStreamInfo() != null) { // 如果zlm不存在这个流,则删除数据即可 MediaServer mediaServerItemForStreamInfo = mediaServerService.getOne(inviteInfoForOld.getStreamInfo().getMediaServer().getId()); if (mediaServerItemForStreamInfo != null) { Boolean ready = mediaServerService.isStreamReady(mediaServerItemForStreamInfo, inviteInfoForOld.getStreamInfo().getApp(), inviteInfoForOld.getStreamInfo().getStream()); if (!ready) { // 错误存在于redis中的数据 inviteStreamService.removeInviteInfo(inviteInfoForOld); }else { // 流确实尚在推流,直接回调结果 HookData hookData = new HookData(); hookData.setApp(inviteInfoForOld.getStreamInfo().getApp()); hookData.setStream(inviteInfoForOld.getStreamInfo().getStream()); hookData.setMediaServer(mediaServerItemForStreamInfo); hookEvent.response(hookData); return; } } } String streamId = null; if (mediaServerItem.isRtpEnable()) { streamId = String.format("%s_%s", platform.getServerGBId(), channel.getGbDeviceId()); } // 默认不进行SSRC校验, TODO 后续可改为配置 boolean ssrcCheck = false; int tcpMode; if (userSetting.getBroadcastForPlatform().equalsIgnoreCase("TCP-PASSIVE")) { tcpMode = 1; }else if (userSetting.getBroadcastForPlatform().equalsIgnoreCase("TCP-ACTIVE")) { tcpMode = 2; } else { tcpMode = 0; } SSRCInfo ssrcInfo = receiveRtpServerService.openGbRTPServer(mediaServerItem, streamId, null, tcpMode, false, ssrcCheck, true, false, ((code, msg, data) -> { if (code == InviteErrorCode.SUCCESS.getCode() && data != null && data.getHookData() != null) { log.info("[国标级联] 发起语音喊话 收到上级推流 deviceId: {}, channelId: {}", platform.getServerGBId(), channel.getGbDeviceId()); HookData hookData = data.getHookData(); // hook响应 onPublishHandlerForBroadcast(hookData.getMediaServer(), hookData.getMediaInfo(), platform, channel); // 收到流 if (hookEvent != null) { hookEvent.response(hookData); } }else { InviteInfo inviteInfoForBroadcast = inviteStreamService.getInviteInfo(InviteSessionType.BROADCAST, channel.getGbId(), null); if (inviteInfoForBroadcast == null) { log.info("[国标级联] 发起语音喊话 收流超时 deviceId: {}, channelId: {}", platform.getServerGBId(), channel.getGbDeviceId()); // 点播超时回复BYE 同时释放ssrc以及此次点播的资源 try { commanderForPlatform.streamByeCmd(platform, channel, data.getSsrcInfo().getApp(), data.getSsrcInfo().getStream(), null, null); } catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) { log.error("[点播超时], 发送BYE失败 {}", e.getMessage()); } finally { timeoutCallback.run(1, "收流超时"); mediaServerService.releaseSsrc(mediaServerItem.getId(), data.getSsrcInfo().getSsrc()); receiveRtpServerService.closeRTPServer(mediaServerItem, data.getSsrcInfo().getApp(), data.getSsrcInfo().getStream()); sessionManager.removeByStream(data.getSsrcInfo().getApp(), data.getSsrcInfo().getStream()); } } } })); if (ssrcInfo == null || ssrcInfo.getPort() < 0) { log.info("[国标级联] 发起语音喊话 开启端口监听失败, platform: {}, channel: {}", platform.getServerGBId(), channel.getGbDeviceId()); SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult<>(); eventResult.statusCode = -1; eventResult.msg = "端口监听失败"; eventResult.type = SipSubscribe.EventResultType.failedToGetPort; errorEvent.response(eventResult); return; } log.info("[国标级联] 语音喊话,发起Invite消息 deviceId: {}, channelId: {},收流端口: {}, 收流模式:{}, SSRC: {}, SSRC校验:{}", platform.getServerGBId(), channel.getGbDeviceId(), ssrcInfo.getPort(), userSetting.getBroadcastForPlatform(), ssrcInfo.getSsrc(), ssrcCheck); // 初始化redis中的invite消息状态 InviteInfo inviteInfo = InviteInfo.getInviteInfo(platform.getServerGBId(), channel.getGbId(), ssrcInfo.getStream(), ssrcInfo, mediaServerItem.getId(), mediaServerItem.getSdpIp(), ssrcInfo.getPort(), userSetting.getBroadcastForPlatform(), InviteSessionType.BROADCAST, InviteSessionStatus.ready, userSetting.getRecordSip()); inviteStreamService.updateInviteInfo(inviteInfo); commanderForPlatform.broadcastInviteCmd(platform, channel,sourceId, mediaServerItem, ssrcInfo, event -> { inviteOKHandler(event, ssrcInfo, tcpMode, ssrcCheck, mediaServerItem, platform, channel, null, inviteInfo, InviteSessionType.BROADCAST); }, eventResult -> { // 收到错误回复 if (errorEvent != null) { errorEvent.response(eventResult); } }); } public void onPublishHandlerForBroadcast(MediaServer mediaServerItem, MediaInfo mediaInfo, Platform platform, CommonGBChannel channel) { StreamInfo streamInfo = mediaServerService.getStreamInfoByAppAndStream(mediaServerItem, mediaInfo.getApp(), mediaInfo.getStream(), mediaInfo, null); streamInfo.setChannelId(channel.getGbId()); InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.BROADCAST, channel.getGbId()); if (inviteInfo != null) { inviteInfo.setStatus(InviteSessionStatus.ok); inviteInfo.setStreamInfo(streamInfo); inviteStreamService.updateInviteInfo(inviteInfo); } } private void inviteOKHandler(SipSubscribe.EventResult eventResult, SSRCInfo ssrcInfo, int tcpMode, boolean ssrcCheck, MediaServer mediaServerItem, Platform platform, CommonGBChannel channel, ErrorCallback callback, InviteInfo inviteInfo, InviteSessionType inviteSessionType){ inviteInfo.setStatus(InviteSessionStatus.ok); ResponseEvent responseEvent = (ResponseEvent) eventResult.event; String contentString = new String(responseEvent.getResponse().getRawContent()); String ssrcInResponse = SipUtils.getSsrcFromSdp(contentString); // 兼容回复的消息中缺少ssrc(y字段)的情况 if (ssrcInResponse == null) { ssrcInResponse = ssrcInfo.getSsrc(); } if (ssrcInfo.getSsrc().equals(ssrcInResponse)) { // ssrc 一致 if (mediaServerItem.isRtpEnable()) { // 多端口 if (tcpMode == 2) { tcpActiveHandler(platform, channel, contentString, mediaServerItem, tcpMode, ssrcCheck, ssrcInfo, callback); } }else { // 单端口 if (tcpMode == 2) { log.warn("[Invite 200OK] 单端口收流模式不支持tcp主动模式收流"); } } }else { log.info("[Invite 200OK] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse); // ssrc 不一致 if (mediaServerItem.isRtpEnable()) { // 多端口 if (ssrcCheck) { // ssrc检验 // 更新ssrc log.info("[Invite 200OK] SSRC修正 {}->{}", ssrcInfo.getSsrc(), ssrcInResponse); // 释放ssrc mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc()); Boolean result = mediaServerService.updateRtpServerSSRC(mediaServerItem, ssrcInfo.getApp(), ssrcInfo.getStream(), ssrcInResponse); if (!result) { try { log.warn("[Invite 200OK] 更新ssrc失败,停止喊话 {}/{}", platform.getServerGBId(), channel.getGbDeviceId()); commanderForPlatform.streamByeCmd(platform, channel, ssrcInfo.getApp(), ssrcInfo.getStream(), null, null); } catch (InvalidArgumentException | SipException | ParseException | SsrcTransactionNotFoundException e) { log.error("[命令发送失败] 停止播放, 发送BYE: {}", e.getMessage()); } finally { // 释放ssrc mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc()); receiveRtpServerService.closeRTPServer(mediaServerItem, ssrcInfo.getApp(), ssrcInfo.getStream()); sessionManager.removeByStream(ssrcInfo.getApp(), ssrcInfo.getStream()); callback.run(InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(), "下级自定义了ssrc,重新设置收流信息失败", null); inviteStreamService.call(inviteSessionType, channel.getGbId(), null, InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(), "下级自定义了ssrc,重新设置收流信息失败", null); } }else { ssrcInfo.setSsrc(ssrcInResponse); inviteInfo.setSsrcInfo(ssrcInfo); inviteInfo.setStream(ssrcInfo.getStream()); if (tcpMode == 2) { if (mediaServerItem.isRtpEnable()) { tcpActiveHandler(platform, channel, contentString, mediaServerItem, tcpMode, ssrcCheck, ssrcInfo, callback); }else { log.warn("[Invite 200OK] 单端口收流模式不支持tcp主动模式收流"); } } inviteStreamService.updateInviteInfo(inviteInfo); } }else { ssrcInfo.setSsrc(ssrcInResponse); inviteInfo.setSsrcInfo(ssrcInfo); inviteInfo.setStream(ssrcInfo.getStream()); if (tcpMode == 2) { if (mediaServerItem.isRtpEnable()) { tcpActiveHandler(platform, channel, contentString, mediaServerItem, tcpMode, ssrcCheck, ssrcInfo, callback); }else { log.warn("[Invite 200OK] 单端口收流模式不支持tcp主动模式收流"); } } inviteStreamService.updateInviteInfo(inviteInfo); } }else { if (ssrcInResponse != null) { // 单端口 // 重新订阅流上线 SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransactionByStream(ssrcInfo.getApp(), inviteInfo.getStream()); sessionManager.removeByStream(ssrcInfo.getApp(), inviteInfo.getStream()); inviteStreamService.updateInviteInfoForSSRC(inviteInfo, ssrcInResponse); ssrcTransaction.setPlatformId(platform.getServerGBId()); ssrcTransaction.setChannelId(channel.getGbId()); ssrcTransaction.setApp(ssrcInfo.getApp()); ssrcTransaction.setStream(inviteInfo.getStream()); ssrcTransaction.setSsrc(ssrcInResponse); ssrcTransaction.setMediaServerId(mediaServerItem.getId()); ssrcTransaction.setSipTransactionInfo(new SipTransactionInfo((SIPResponse) responseEvent.getResponse())); ssrcTransaction.setType(inviteSessionType); sessionManager.put(ssrcTransaction); } } } } private void tcpActiveHandler(Platform platform, CommonGBChannel channel, String contentString, MediaServer mediaServerItem, int tcpMode, boolean ssrcCheck, SSRCInfo ssrcInfo, ErrorCallback callback){ if (tcpMode != 2) { return; } String substring; if (contentString.indexOf("y=") > 0) { substring = contentString.substring(0, contentString.indexOf("y=")); }else { substring = contentString; } try { SessionDescription sdp = SdpFactory.getInstance().createSessionDescription(substring); int port = -1; Vector mediaDescriptions = sdp.getMediaDescriptions(true); for (Object description : mediaDescriptions) { MediaDescription mediaDescription = (MediaDescription) description; Media media = mediaDescription.getMedia(); Vector mediaFormats = media.getMediaFormats(false); if (mediaFormats.contains("8") || mediaFormats.contains("0")) { port = media.getMediaPort(); break; } } log.info("[TCP主动连接对方] serverGbId: {}, channelId: {}, 连接对方的地址:{}:{}, SSRC: {}, SSRC校验:{}", platform.getServerGBId(), channel.getGbDeviceId(), sdp.getConnection().getAddress(), port, ssrcInfo.getSsrc(), ssrcCheck); Boolean result = mediaServerService.connectRtpServer(mediaServerItem, sdp.getConnection().getAddress(), port, ssrcInfo.getApp(), ssrcInfo.getStream()); log.info("[TCP主动连接对方] 结果: {}", result); } catch (SdpException e) { log.error("[TCP主动连接对方] serverGbId: {}, channelId: {}, 解析200OK的SDP信息失败", platform.getServerGBId(), channel.getGbDeviceId(), e); receiveRtpServerService.closeRTPServer(mediaServerItem, ssrcInfo.getApp(), ssrcInfo.getStream()); // 释放ssrc mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc()); sessionManager.removeByStream(ssrcInfo.getApp(), ssrcInfo.getStream()); callback.run(InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(), InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null); inviteStreamService.call(InviteSessionType.PLAY, channel.getGbId(), null, InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(), InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null); } } @Override public void stopBroadcast(Platform platform, CommonGBChannel channel, String app, String stream, boolean sendBye, MediaServer mediaServerItem) { try { if (sendBye) { commanderForPlatform.streamByeCmd(platform, channel, app, stream, null, null); } } catch (InvalidArgumentException | SipException | ParseException | SsrcTransactionNotFoundException e) { log.warn("[消息发送失败] 停止语音对讲, 平台:{},通道:{}", platform.getId(), channel.getGbDeviceId() ); } finally { receiveRtpServerService.closeRTPServer(mediaServerItem, app, stream); InviteInfo inviteInfo = inviteStreamService.getInviteInfo(null, channel.getGbId(), stream); if (inviteInfo != null) { // 释放ssrc mediaServerService.releaseSsrc(mediaServerItem.getId(), inviteInfo.getSsrcInfo().getSsrc()); inviteStreamService.removeInviteInfo(inviteInfo); } sessionManager.removeByStream(app, stream); } } @Override public Platform queryOne(Integer platformId) { return platformMapper.query(platformId); } @Override public List queryEnablePlatformList(String serverId) { return platformMapper.queryEnableParentPlatformListByServerId(serverId,true); } @Override @Transactional public boolean delete(Integer platformId) { Platform platform = platformMapper.query(platformId); Assert.notNull(platform, "平台不存在"); log.info("[删除平台] {}/{} {}:{}", platform.getName(), platform.getServerGBId(), platform.getServerIp(), platform.getServerPort()); if (!userSetting.getServerId().equals(platform.getServerId())) { boolean result = redisRpcService.deletePlatform(platform.getServerId(), platformId); if (result) { log.info("[删除平台] 跨平台删除成功 {}/{}", platform.getName(), platform.getServerGBId()); }else { log.info("[删除平台] 跨平台删除失败 {}/{}", platform.getName(), platform.getServerGBId()); } return result; } try { if (statusTaskRunner.containsRegister(platform.getServerGBId())) { try { SipTransactionInfo transactionInfo = statusTaskRunner.getRegisterTransactionInfo(platform.getServerGBId()); sendUnRegister(platform, transactionInfo); }catch (Exception ignored) {} } platformMapper.delete(platform.getId()); statusTaskRunner.removeRegisterTask(platform.getServerGBId()); statusTaskRunner.removeKeepAliveTask(platform.getServerGBId()); subscribeHolder.removeCatalogSubscribe(platform.getServerGBId()); subscribeHolder.removeMobilePositionSubscribe(platform.getServerGBId()); }catch (Exception e) { throw new ControllerException(ErrorCode.ERROR100.getCode(), e.getMessage()); } return true; } @Override public List queryAll(String serverId) { return platformMapper.queryByServerId(serverId); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/PlayServiceImpl.java ================================================ package com.genersoft.iot.vmp.gb28181.service.impl; import com.genersoft.iot.vmp.common.*; import com.genersoft.iot.vmp.common.enums.MediaApp; import com.genersoft.iot.vmp.conf.DynamicTask; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.conf.exception.ServiceException; import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.controller.bean.AudioBroadcastEvent; import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; import com.genersoft.iot.vmp.gb28181.service.*; import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager; import com.genersoft.iot.vmp.gb28181.session.SSRCFactory; import com.genersoft.iot.vmp.gb28181.session.SipInviteSessionManager; import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander; import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; import com.genersoft.iot.vmp.gb28181.utils.SipUtils; import com.genersoft.iot.vmp.media.bean.MediaInfo; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.bean.RecordInfo; import com.genersoft.iot.vmp.media.event.hook.Hook; import com.genersoft.iot.vmp.media.event.hook.HookSubscribe; import com.genersoft.iot.vmp.media.event.hook.HookType; import com.genersoft.iot.vmp.media.event.media.MediaArrivalEvent; import com.genersoft.iot.vmp.media.event.media.MediaDepartureEvent; import com.genersoft.iot.vmp.media.event.media.MediaNotFoundEvent; import com.genersoft.iot.vmp.media.service.IMediaServerService; import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo; import com.genersoft.iot.vmp.service.ICloudRecordService; import com.genersoft.iot.vmp.service.IReceiveRtpServerService; import com.genersoft.iot.vmp.service.ISendRtpServerService; import com.genersoft.iot.vmp.service.bean.*; import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcPlayService; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.vmanager.bean.AudioBroadcastResult; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.StreamContent; import gov.nist.javax.sip.message.SIPResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.event.EventListener; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import javax.sdp.*; import javax.sip.InvalidArgumentException; import javax.sip.ResponseEvent; import javax.sip.SipException; import javax.sip.header.CallIdHeader; import javax.sip.message.Response; import java.io.File; import java.math.BigDecimal; import java.math.RoundingMode; import java.text.ParseException; import java.util.ArrayList; import java.util.List; import java.util.UUID; import java.util.Vector; @SuppressWarnings(value = {"rawtypes", "unchecked"}) @Slf4j @Service("playService") public class PlayServiceImpl implements IPlayService { @Autowired private ISIPCommander cmder; @Autowired private AudioBroadcastManager audioBroadcastManager; @Autowired private IDeviceService deviceService; @Autowired private ISIPCommanderForPlatform sipCommanderFroPlatform; @Autowired private IRedisCatchStorage redisCatchStorage; @Autowired private IInviteStreamService inviteStreamService; @Autowired private HookSubscribe subscribe; @Autowired private IMediaServerService mediaServerService; @Autowired private SipInviteSessionManager sessionManager; @Autowired private UserSetting userSetting; @Autowired private IDeviceChannelService deviceChannelService; @Autowired private DynamicTask dynamicTask; @Autowired private ISIPCommanderForPlatform commanderForPlatform; @Autowired private SSRCFactory ssrcFactory; @Autowired private IPlatformService platformService; @Autowired private IGbChannelService channelService; @Autowired private ISendRtpServerService sendRtpServerService; @Autowired private IReceiveRtpServerService receiveRtpServerService; @Autowired private ICloudRecordService cloudRecordService; @Autowired private IRedisRpcPlayService redisRpcPlayService; /** * 流到来的处理 */ @Async("taskExecutor") @EventListener public void onApplicationEvent(MediaArrivalEvent event) { if (MediaApp.GB28181_BROADCAST.equals(event.getApp()) || MediaApp.GB28181_TALK.equals(event.getApp())) { if (event.getStream().indexOf("_") > 0) { String[] streamArray = event.getStream().split("_"); if (streamArray.length == 2) { String deviceId = streamArray[0]; String channelId = streamArray[1]; Device device = deviceService.getDeviceByDeviceId(deviceId); DeviceChannel channel = deviceChannelService.getOneForSource(deviceId, channelId); if (device == null) { log.info("[语音对讲/喊话] 未找到设备:{}", deviceId); return; } if (channel == null) { log.info("[语音对讲/喊话] 未找到通道:{}", channelId); return; } if (MediaApp.GB28181_BROADCAST.equals(event.getApp())) { if (audioBroadcastManager.exit(channel.getId())) { stopAudioBroadcast(device, channel); } // 开启语音对讲通道 try { audioBroadcastCmd(device, channel, event.getMediaServer(), event.getApp(), event.getStream(), 60, false, (msg) -> log.info("[语音喊话] 通道建立成功, device: {}, channel: {}", deviceId, channelId)); } catch (InvalidArgumentException | ParseException | SipException e) { log.error("[命令发送失败] 语音对讲: {}", e.getMessage()); } }else if (MediaApp.GB28181_TALK.equals(event.getApp())) { // 开启语音对讲通道 talkCmd(device, channel, event.getMediaServer(), event.getStream(), (msg) -> log.info("[语音对讲] 通道建立成功, device: {}, channel: {}", deviceId, channelId)); } } } } } /** * 流离开的处理 */ @Async("taskExecutor") @EventListener public void onApplicationEvent(MediaDepartureEvent event) { List sendRtpInfos = sendRtpServerService.queryByStream(event.getStream()); if (!sendRtpInfos.isEmpty()) { for (SendRtpInfo sendRtpInfo : sendRtpInfos) { if (sendRtpInfo != null && sendRtpInfo.isSendToPlatform() && sendRtpInfo.getApp().equals(event.getApp())) { String platformId = sendRtpInfo.getTargetId(); Device device = deviceService.getDeviceByDeviceId(platformId); DeviceChannel channel = deviceChannelService.getOneById(sendRtpInfo.getChannelId()); try { if (device != null && channel != null) { cmder.streamByeCmd(device, channel.getDeviceId(), event.getApp(), event.getStream(), sendRtpInfo.getCallId(), null); if (sendRtpInfo.getPlayType().equals(InviteStreamType.BROADCAST) || sendRtpInfo.getPlayType().equals(InviteStreamType.TALK)) { AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(channel.getId()); if (audioBroadcastCatch != null) { // 来自上级平台的停止对讲 log.info("[停止对讲] 来自上级,平台:{}, 通道:{}", sendRtpInfo.getTargetId(), sendRtpInfo.getChannelId()); audioBroadcastManager.del(sendRtpInfo.getChannelId()); } } } } catch (SipException | InvalidArgumentException | ParseException | SsrcTransactionNotFoundException e) { log.error("[命令发送失败] 发送BYE: {}", e.getMessage()); } } } } if (MediaApp.GB28181_BROADCAST.equals(event.getApp()) || MediaApp.GB28181_TALK.equals(event.getApp())) { if (event.getStream().indexOf("_") > 0) { String[] streamArray = event.getStream().split("_"); if (streamArray.length == 2) { String deviceId = streamArray[0]; String channelId = streamArray[1]; Device device = deviceService.getDeviceByDeviceId(deviceId); if (device == null) { log.info("[语音对讲/喊话] 未找到设备:{}", deviceId); return; } DeviceChannel channel = deviceChannelService.getOneForSource(deviceId, channelId); if (channel == null) { log.info("[语音对讲/喊话] 未找到通道:{}", channelId); return; } if (MediaApp.GB28181_BROADCAST.equals(event.getApp())) { stopAudioBroadcast(device, channel); }else if (MediaApp.GB28181_TALK.equals(event.getApp())) { stopTalk(device, channel, false); } } } }else if (MediaApp.GB28181.equals(event.getApp())) { // 释放ssrc InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(null, event.getStream()); if (inviteInfo != null && inviteInfo.getStatus() == InviteSessionStatus.ok && inviteInfo.getStreamInfo() != null && inviteInfo.getSsrcInfo() != null) { // 发送bye stop(inviteInfo); } } } /** * 流未找到的处理 */ @Async("taskExecutor") @EventListener public void onApplicationEvent(MediaNotFoundEvent event) { if (!MediaApp.GB28181.equals(event.getApp())) { return; } String[] s = event.getStream().split("_"); if ((s.length != 2 && s.length != 4)) { return; } String deviceId = s[0]; String channelId = s[1]; Device device = redisCatchStorage.getDevice(deviceId); if (device == null || !device.isOnLine()) { return; } DeviceChannel deviceChannel = deviceChannelService.getOne(deviceId, channelId); if (deviceChannel == null) { return; } if (s.length == 2) { log.info("[ZLM HOOK] 预览流未找到, 发起自动点播:{}->{}->{}/{}", event.getMediaServer().getId(), event.getSchema(), event.getApp(), event.getStream()); play(event.getMediaServer(), deviceId, channelId, null, (code, msg, data) -> {}); } else if (s.length == 4) { // 此时为录像回放, 录像回放格式为> 设备ID_通道ID_开始时间_结束时间 String startTimeStr = s[2]; String endTimeStr = s[3]; if (startTimeStr == null || endTimeStr == null || startTimeStr.length() != 14 || endTimeStr.length() != 14) { return; } String startTime = DateUtil.urlToyyyy_MM_dd_HH_mm_ss(startTimeStr); String endTime = DateUtil.urlToyyyy_MM_dd_HH_mm_ss(endTimeStr); log.info("[ZLM HOOK] 回放流未找到, 发起自动点播:{}->{}->{}/{}-{}-{}", event.getMediaServer().getId(), event.getSchema(), event.getApp(), event.getStream(), startTime, endTime ); playBack(event.getMediaServer(), device, deviceChannel, startTime, endTime, (code, msg, data) -> {}); } } @Override public void play(Device device, DeviceChannel channel, ErrorCallback callback) { // 判断设备是否属于当前平台, 如果不属于则发起自动调用 if (!userSetting.getServerId().equals(device.getServerId())) { redisRpcPlayService.play(device.getServerId(), channel.getId(), callback); return; } MediaServer mediaServerItem = getNewMediaServerItem(device); if (mediaServerItem == null) { log.warn("[点播] 未找到可用的zlm deviceId: {},channelId:{}", device.getDeviceId(), channel.getDeviceId()); throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到可用的zlm"); } play(mediaServerItem, device, channel, null, userSetting.getRecordSip(), callback); } @Override public SSRCInfo play(MediaServer mediaServerItem, String deviceId, String channelId, String ssrc, ErrorCallback callback) { if (mediaServerItem == null) { log.warn("[点播] 未找到可用的zlm deviceId: {},channelId:{}", deviceId, channelId); throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到可用的zlm"); } Device device = redisCatchStorage.getDevice(deviceId); if (device.getStreamMode().equalsIgnoreCase("TCP-ACTIVE") && !mediaServerItem.isRtpEnable()) { log.warn("[点播] 单端口收流时不支持TCP主动方式收流 deviceId: {},channelId:{}", deviceId, channelId); throw new ControllerException(ErrorCode.ERROR100.getCode(), "单端口收流时不支持TCP主动方式收流"); } DeviceChannel channel = deviceChannelService.getOneForSource(deviceId, channelId); if (channel == null) { log.warn("[点播] 未找到通道 deviceId: {},channelId:{}", deviceId, channelId); throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到通道"); } return play(mediaServerItem, device, channel, ssrc, userSetting.getRecordSip(), callback); } private SSRCInfo play(MediaServer mediaServer, Device device, DeviceChannel channel, String ssrc, Boolean record, ErrorCallback callback) { if (mediaServer == null ) { if (callback != null) { callback.run(InviteErrorCode.ERROR_FOR_PARAMETER_ERROR.getCode(), InviteErrorCode.ERROR_FOR_PARAMETER_ERROR.getMsg(), null); } return null; } InviteInfo inviteInfoInCatch = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, channel.getId()); if (inviteInfoInCatch != null ) { if (inviteInfoInCatch.getStreamInfo() == null) { // 释放生成的ssrc,使用上一次申请的 ssrcFactory.releaseSsrc(mediaServer.getId(), ssrc); // 点播发起了但是尚未成功, 仅注册回调等待结果即可 inviteStreamService.once(InviteSessionType.PLAY, channel.getId(), null, callback); log.info("[点播开始] 已经请求中,等待结果, deviceId: {}, channelId({}): {}", device.getDeviceId(), channel.getDeviceId(), channel.getId()); return inviteInfoInCatch.getSsrcInfo(); }else { StreamInfo streamInfo = inviteInfoInCatch.getStreamInfo(); String streamId = streamInfo.getStream(); if (streamId == null) { callback.run(InviteErrorCode.ERROR_FOR_CATCH_DATA.getCode(), "点播失败, redis缓存streamId等于null", null); inviteStreamService.call(InviteSessionType.PLAY, channel.getId(), null, InviteErrorCode.ERROR_FOR_CATCH_DATA.getCode(), "点播失败, redis缓存streamId等于null", null); return inviteInfoInCatch.getSsrcInfo(); } MediaServer mediaInfo = streamInfo.getMediaServer(); Boolean ready = mediaServerService.isStreamReady(mediaInfo, MediaApp.GB28181, streamId); if (ready != null && ready) { if(callback != null) { callback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), streamInfo); } inviteStreamService.call(InviteSessionType.PLAY, channel.getId(), null, InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), streamInfo); log.info("[点播已存在] 直接返回, 设备编号: {}, 通道编号: {}", device.getDeviceId(), channel.getDeviceId()); return inviteInfoInCatch.getSsrcInfo(); }else { // 点播发起了但是尚未成功, 仅注册回调等待结果即可 inviteStreamService.once(InviteSessionType.PLAY, channel.getId(), null, callback); deviceChannelService.stopPlay(channel.getId()); inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, channel.getId()); } } } String streamId = String.format("%s_%s", device.getDeviceId(), channel.getDeviceId()); int tcpMode = device.getStreamMode().equals("TCP-ACTIVE")? 2: (device.getStreamMode().equals("TCP-PASSIVE")? 1:0); SSRCInfo ssrcInfo = receiveRtpServerService.openGbRTPServer(mediaServer, streamId, ssrc, tcpMode, false, device.isSsrcCheck(), false, !channel.isHasAudio(), (code, msg, result) -> { if (code == InviteErrorCode.SUCCESS.getCode() && result != null && result.getHookData() != null) { // hook 响应 StreamInfo streamInfo = onPublishHandlerForPlay(result.getHookData().getMediaServer(), result.getHookData().getMediaInfo(), device, channel); if (streamInfo == null){ if (callback != null) { callback.run(InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getCode(), InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getMsg(), null); } inviteStreamService.call(InviteSessionType.PLAY, channel.getId(), null, InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getCode(), InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getMsg(), null); return; } if (callback != null) { callback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), streamInfo); } inviteStreamService.call(InviteSessionType.PLAY, channel.getId(), null, InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), streamInfo); log.info("[点播成功] 设备编号: {}, 通道编号:{}, 码流类型:{}", device.getDeviceId(), channel.getDeviceId(), channel.getStreamIdentification()); snapOnPlay(result.getHookData().getMediaServer(), device.getDeviceId(), channel.getDeviceId(), streamId); }else { if (callback != null) { callback.run(code, msg, null); } inviteStreamService.call(InviteSessionType.PLAY, channel.getId(), null, code, msg, null); inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, channel.getId()); SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransactionByStream(MediaApp.GB28181, streamId); if (ssrcTransaction != null) { try { cmder.streamByeCmd(device, channel.getDeviceId(),MediaApp.GB28181, streamId, null, null); } catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) { log.error("[点播超时], 发送BYE失败 {}", e.getMessage()); } finally { sessionManager.removeByStream(MediaApp.GB28181, streamId); } } } }); if (ssrcInfo == null || ssrcInfo.getPort() <= 0) { log.info("[点播端口/SSRC]获取失败,设备编号:{}, 通道编号:{},ssrcInfo;{}", device.getDeviceId(), channel.getDeviceId(), ssrcInfo); callback.run(InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(), "获取端口或者ssrc失败", null); inviteStreamService.call(InviteSessionType.PLAY, channel.getId(), null, InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(), InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getMsg(), null); return null; } log.info("[点播开始] 设备编号: {}, 通道编号: {}, 收流端口: {}, 流ID:{}, 收流模式:{}, SSRC: {}, SSRC校验:{}", device.getDeviceId(), channel.getDeviceId(), ssrcInfo.getPort(), ssrcInfo.getStream(), channel.getStreamIdentification(), ssrcInfo.getSsrc(), device.isSsrcCheck()); // 初始化redis中的invite消息状态 InviteInfo inviteInfo = InviteInfo.getInviteInfo(device.getDeviceId(), channel.getId(), ssrcInfo.getStream(), ssrcInfo, mediaServer.getId(), mediaServer.getSdpIp(), ssrcInfo.getPort(), device.getStreamMode(), InviteSessionType.PLAY, InviteSessionStatus.ready, userSetting.getRecordSip()); if (record != null) { inviteInfo.setRecord(record); }else { inviteInfo.setRecord(userSetting.getRecordSip()); } inviteStreamService.updateInviteInfo(inviteInfo); try { cmder.playStreamCmd(mediaServer, ssrcInfo, device, channel, (eventResult) -> { // 处理收到200ok后的TCP主动连接以及SSRC不一致的问题 InviteOKHandler(eventResult, ssrcInfo, mediaServer, device, channel, callback, inviteInfo, InviteSessionType.PLAY); }, (event) -> { log.info("[点播失败]{}:{} deviceId: {}, channelId:{}",event.statusCode, event.msg, device.getDeviceId(), channel.getDeviceId()); receiveRtpServerService.closeRTPServer(mediaServer, ssrcInfo.getApp(), ssrcInfo.getStream()); sessionManager.removeByStream(ssrcInfo.getApp(), ssrcInfo.getStream()); if (callback != null) { callback.run(event.statusCode, event.msg, null); } inviteStreamService.call(InviteSessionType.PLAY, channel.getId(), null, event.statusCode, event.msg, null); inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, channel.getId()); }, userSetting.getPlayTimeout().longValue()); } catch (InvalidArgumentException | SipException | ParseException e) { log.error("[命令发送失败] 点播消息: {}", e.getMessage()); receiveRtpServerService.closeRTPServer(mediaServer, ssrcInfo.getApp(), ssrcInfo.getStream()); sessionManager.removeByStream(ssrcInfo.getApp(), ssrcInfo.getStream()); if (callback != null) { callback.run(InviteErrorCode.ERROR_FOR_SIP_SENDING_FAILED.getCode(), InviteErrorCode.ERROR_FOR_SIP_SENDING_FAILED.getMsg(), null); } inviteStreamService.call(InviteSessionType.PLAY, channel.getId(), null, InviteErrorCode.ERROR_FOR_SIP_SENDING_FAILED.getCode(), InviteErrorCode.ERROR_FOR_SIP_SENDING_FAILED.getMsg(), null); inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, channel.getId()); } return ssrcInfo; } private void talk(MediaServer mediaServerItem, Device device, DeviceChannel channel, String stream, SipSubscribe.Event errorEvent, Runnable timeoutCallback, AudioBroadcastEvent audioEvent) { String playSsrc = ssrcFactory.getPlaySsrc(mediaServerItem.getId()); if (playSsrc == null) { audioEvent.call("ssrc已经用尽"); return; } SendRtpInfo sendRtpInfo; try { sendRtpInfo = sendRtpServerService.createSendRtpInfo(mediaServerItem, null, null, playSsrc, device.getDeviceId(), MediaApp.GB28181_TALK, stream, channel.getId(), true, false); sendRtpInfo.setPlayType(InviteStreamType.TALK); }catch (PlayException e) { log.info("[语音对讲]开始 获取发流端口失败 deviceId: {}, channelId: {},", device.getDeviceId(), channel.getDeviceId()); return; } sendRtpInfo.setOnlyAudio(true); sendRtpInfo.setPt(8); sendRtpInfo.setStatus(1); sendRtpInfo.setTcpActive(false); sendRtpInfo.setUsePs(false); sendRtpInfo.setReceiveStream(stream + "_talk"); String callId = SipUtils.getNewCallId(); log.info("[语音对讲]开始 deviceId: {}, channelId: {},收流端口: {}, 收流模式:{}, SSRC: {}, SSRC校验:{}", device.getDeviceId(), channel.getDeviceId(), sendRtpInfo.getLocalPort(), device.getStreamMode(), sendRtpInfo.getSsrc(), false); // 超时处理 String timeOutTaskKey = UUID.randomUUID().toString(); dynamicTask.startDelay(timeOutTaskKey, () -> { log.info("[语音对讲] 收流超时 deviceId: {}, channelId: {},端口:{}, SSRC: {}", device.getDeviceId(), channel.getDeviceId(), sendRtpInfo.getPort(), sendRtpInfo.getSsrc()); timeoutCallback.run(); // 点播超时回复BYE 同时释放ssrc以及此次点播的资源 try { cmder.streamByeCmd(device, channel.getDeviceId(), null, null, callId, null); } catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) { log.error("[语音对讲]超时, 发送BYE失败 {}", e.getMessage()); } finally { timeoutCallback.run(); mediaServerService.releaseSsrc(mediaServerItem.getId(), sendRtpInfo.getSsrc()); sessionManager.removeByStream(sendRtpInfo.getApp(), sendRtpInfo.getStream()); } }, userSetting.getPlayTimeout()); try { Integer localPort = mediaServerService.startSendRtpPassive(mediaServerItem, sendRtpInfo, userSetting.getPlayTimeout() * 1000); if (localPort == null || localPort <= 0) { timeoutCallback.run(); mediaServerService.releaseSsrc(mediaServerItem.getId(), sendRtpInfo.getSsrc()); sessionManager.removeByStream(sendRtpInfo.getApp(), sendRtpInfo.getStream()); return; } sendRtpInfo.setPort(localPort); }catch (ControllerException e) { mediaServerService.releaseSsrc(mediaServerItem.getId(), sendRtpInfo.getSsrc()); log.info("[语音对讲]失败 deviceId: {}, channelId: {}", device.getDeviceId(), channel.getDeviceId()); audioEvent.call("失败, " + e.getMessage()); // 查看是否已经建立了通道,存在则发送bye stopTalk(device, channel); } // 查看设备是否已经在推流 try { cmder.talkStreamCmd(mediaServerItem, sendRtpInfo, device, channel, callId, (hookData) -> { log.info("[语音对讲] 流已生成, 开始推流: " + hookData); dynamicTask.stop(timeOutTaskKey); // TODO 暂不做处理 }, (hookData) -> { log.info("[语音对讲] 设备开始推流: " + hookData); dynamicTask.stop(timeOutTaskKey); }, (event) -> { dynamicTask.stop(timeOutTaskKey); if (event.event instanceof ResponseEvent) { ResponseEvent responseEvent = (ResponseEvent) event.event; if (responseEvent.getResponse() instanceof SIPResponse) { SIPResponse response = (SIPResponse) responseEvent.getResponse(); sendRtpInfo.setFromTag(response.getFromTag()); sendRtpInfo.setToTag(response.getToTag()); sendRtpInfo.setCallId(response.getCallIdHeader().getCallId()); sendRtpServerService.update(sendRtpInfo); SsrcTransaction ssrcTransaction = SsrcTransaction.buildForDevice(device.getDeviceId(), sendRtpInfo.getChannelId(), response.getCallIdHeader().getCallId(), sendRtpInfo.getApp(), sendRtpInfo.getStream(), sendRtpInfo.getSsrc(), sendRtpInfo.getMediaServerId(), response, InviteSessionType.TALK); sessionManager.put(ssrcTransaction); } else { log.error("[语音对讲]收到的消息错误,response不是SIPResponse"); } } else { log.error("[语音对讲]收到的消息错误,event不是ResponseEvent"); } }, (event) -> { dynamicTask.stop(timeOutTaskKey); receiveRtpServerService.closeRTPServer(mediaServerItem, sendRtpInfo.getApp(), sendRtpInfo.getStream()); // 释放ssrc mediaServerService.releaseSsrc(mediaServerItem.getId(), sendRtpInfo.getSsrc()); sessionManager.removeByStream(sendRtpInfo.getApp(), sendRtpInfo.getStream()); errorEvent.response(event); }, userSetting.getPlayTimeout().longValue()); } catch (InvalidArgumentException | SipException | ParseException e) { log.error("[命令发送失败] 对讲消息: {}", e.getMessage()); dynamicTask.stop(timeOutTaskKey); receiveRtpServerService.closeRTPServer(mediaServerItem, sendRtpInfo.getApp(), sendRtpInfo.getStream()); // 释放ssrc mediaServerService.releaseSsrc(mediaServerItem.getId(), sendRtpInfo.getSsrc()); sessionManager.removeByStream(sendRtpInfo.getApp(), sendRtpInfo.getStream()); SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(); eventResult.type = SipSubscribe.EventResultType.cmdSendFailEvent; eventResult.statusCode = -1; eventResult.msg = "命令发送失败"; errorEvent.response(eventResult); } // } } private void tcpActiveHandler(Device device, DeviceChannel channel, String contentString, MediaServer mediaServerItem, SSRCInfo ssrcInfo, ErrorCallback callback){ if (!device.getStreamMode().equalsIgnoreCase("TCP-ACTIVE")) { return; } String substring; if (contentString.indexOf("y=") > 0) { substring = contentString.substring(0, contentString.indexOf("y=")); }else { substring = contentString; } try { SessionDescription sdp = SdpFactory.getInstance().createSessionDescription(substring); int port = -1; Vector mediaDescriptions = sdp.getMediaDescriptions(true); for (Object description : mediaDescriptions) { MediaDescription mediaDescription = (MediaDescription) description; Media media = mediaDescription.getMedia(); Vector mediaFormats = media.getMediaFormats(false); if (mediaFormats.contains("96")) { port = media.getMediaPort(); break; } } log.info("[TCP主动连接对方] deviceId: {}, channelId: {}, 连接对方的地址:{}:{}, 收流模式:{}, SSRC: {}, SSRC校验:{}", device.getDeviceId(), channel.getDeviceId(), sdp.getConnection().getAddress(), port, device.getStreamMode(), ssrcInfo.getSsrc(), device.isSsrcCheck()); Boolean result = mediaServerService.connectRtpServer(mediaServerItem, sdp.getConnection().getAddress(), port, ssrcInfo.getApp(), ssrcInfo.getStream()); log.info("[TCP主动连接对方] 结果: {}" , result); if (!result) { // 主动连接失败,结束流程, 清理数据 receiveRtpServerService.closeRTPServer(mediaServerItem, ssrcInfo.getApp(), ssrcInfo.getStream()); sessionManager.removeByStream(ssrcInfo.getApp(), ssrcInfo.getStream()); callback.run(InviteErrorCode.ERROR_FOR_TCP_ACTIVE_CONNECTION_REFUSED_ERROR.getCode(), InviteErrorCode.ERROR_FOR_TCP_ACTIVE_CONNECTION_REFUSED_ERROR.getMsg(), null); inviteStreamService.call(InviteSessionType.BROADCAST, channel.getId(), null, InviteErrorCode.ERROR_FOR_TCP_ACTIVE_CONNECTION_REFUSED_ERROR.getCode(), InviteErrorCode.ERROR_FOR_TCP_ACTIVE_CONNECTION_REFUSED_ERROR.getMsg(), null); } } catch (SdpException e) { log.error("[TCP主动连接对方] deviceId: {}, channelId: {}, 解析200OK的SDP信息失败", device.getDeviceId(), channel.getDeviceId(), e); receiveRtpServerService.closeRTPServer(mediaServerItem, ssrcInfo.getApp(), ssrcInfo.getStream()); sessionManager.removeByStream(ssrcInfo.getApp(), ssrcInfo.getStream()); callback.run(InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(), InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null); inviteStreamService.call(InviteSessionType.BROADCAST, channel.getId(), null, InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(), InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null); } } /** * 点播成功时调用截图. * * @param mediaServerItemInuse media * @param deviceId 设备 ID * @param channelId 通道 ID * @param stream ssrc */ private void snapOnPlay(MediaServer mediaServerItemInuse, String deviceId, String channelId, String stream) { String path = "snap"; String fileName = deviceId + "_" + channelId + ".jpg"; // 请求截图 log.info("[请求截图]: " + fileName); mediaServerService.getSnap(mediaServerItemInuse, MediaApp.GB28181, stream, 15, 1, path, fileName); } public StreamInfo onPublishHandlerForPlay(MediaServer mediaServerItem, MediaInfo mediaInfo, Device device, DeviceChannel channel) { StreamInfo streamInfo = null; streamInfo = onPublishHandler(mediaServerItem, mediaInfo, device, channel); if (streamInfo != null) { deviceChannelService.startPlay(channel.getId(), streamInfo.getStream()); InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, channel.getId()); if (inviteInfo != null) { inviteInfo.setStatus(InviteSessionStatus.ok); inviteInfo.setStreamInfo(streamInfo); inviteStreamService.updateInviteInfo(inviteInfo); } } return streamInfo; } private StreamInfo onPublishHandlerForPlayback(MediaServer mediaServerItem, MediaInfo mediaInfo, Device device, DeviceChannel channel, String startTime, String endTime) { StreamInfo streamInfo = onPublishHandler(mediaServerItem, mediaInfo, device, channel); if (streamInfo != null) { streamInfo.setStartTime(startTime); streamInfo.setEndTime(endTime); InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(InviteSessionType.PLAYBACK, mediaInfo.getStream()); if (inviteInfo != null) { inviteInfo.setStatus(InviteSessionStatus.ok); inviteInfo.setStreamInfo(streamInfo); inviteStreamService.updateInviteInfo(inviteInfo); } } return streamInfo; } @Override public MediaServer getNewMediaServerItem(Device device) { if (device == null) { return null; } MediaServer mediaServerItem; if (ObjectUtils.isEmpty(device.getMediaServerId()) || "auto".equals(device.getMediaServerId())) { mediaServerItem = mediaServerService.getMediaServerForMinimumLoad(null); } else { mediaServerItem = mediaServerService.getOne(device.getMediaServerId()); } if (mediaServerItem == null) { log.warn("点播时未找到可使用的ZLM..."); } return mediaServerItem; } @Override public void playBack(Device device, DeviceChannel channel, String startTime, String endTime, ErrorCallback callback) { if (device == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "设备不存在"); } if (channel == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "通道不存在"); } if (!userSetting.getServerId().equals(device.getServerId())) { redisRpcPlayService.playback(device.getServerId(), channel.getId(), startTime, endTime, callback); return; } MediaServer newMediaServerItem = getNewMediaServerItem(device); if (newMediaServerItem == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到可用的节点"); } if (device.getStreamMode().equalsIgnoreCase("TCP-ACTIVE") && ! newMediaServerItem.isRtpEnable()) { log.warn("[录像回放] 单端口收流时不支持TCP主动方式收流 deviceId: {},channelId:{}", device.getDeviceId(), channel.getDeviceId()); throw new ControllerException(ErrorCode.ERROR100.getCode(), "单端口收流时不支持TCP主动方式收流"); } playBack(newMediaServerItem, device, channel, startTime, endTime, callback); } private void playBack(MediaServer mediaServer, Device device, DeviceChannel channel, String startTime, String endTime, ErrorCallback callback) { String startTimeStr = startTime.replace("-", "") .replace(":", "") .replace(" ", ""); String endTimeTimeStr = endTime.replace("-", "") .replace(":", "") .replace(" ", ""); String stream = device.getDeviceId() + "_" + channel.getDeviceId() + "_" + startTimeStr + "_" + endTimeTimeStr; int tcpMode = device.getStreamMode().equals("TCP-ACTIVE")? 2: (device.getStreamMode().equals("TCP-PASSIVE")? 1:0); SSRCInfo ssrcInfo = receiveRtpServerService.openGbRTPServer(mediaServer, stream, null, tcpMode, true, device.isSsrcCheck(), false, !channel.isHasAudio(), (code, msg, result) -> { if (code == InviteErrorCode.SUCCESS.getCode() && result != null && result.getHookData() != null) { // hook响应 StreamInfo streamInfo = onPublishHandlerForPlayback(result.getHookData().getMediaServer(), result.getHookData().getMediaInfo(), device, channel, startTime, endTime); if (streamInfo == null) { log.warn("设备回放API调用失败!"); callback.run(InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getCode(), InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getMsg(), null); return; } callback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), streamInfo); log.info("[录像回放] 成功 deviceId: {}, channelId: {}, 开始时间: {}, 结束时间: {}", device.getDeviceId(), channel.getGbDeviceId(), startTime, endTime); }else { if (callback != null) { callback.run(code, msg, null); } inviteStreamService.call(InviteSessionType.PLAYBACK, channel.getId(), null, code, msg, null); inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAYBACK, channel.getId()); SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransactionByStream(MediaApp.GB28181, stream); if (ssrcTransaction != null) { try { cmder.streamByeCmd(device, channel.getDeviceId(),MediaApp.GB28181, stream, null, null); } catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) { log.error("[录像回放] 发送BYE失败 {}", e.getMessage()); } finally { sessionManager.removeByStream(MediaApp.GB28181, stream); } } } }); if (ssrcInfo == null || ssrcInfo.getPort() <= 0) { log.info("[回放端口/SSRC]获取失败,deviceId={},channelId={},ssrcInfo={}", device.getDeviceId(), channel.getDeviceId(), ssrcInfo); if (callback != null) { callback.run(InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(), "获取端口或者ssrc失败", null); } inviteStreamService.call(InviteSessionType.PLAY, channel.getId(), null, InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(), InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getMsg(), null); return; } log.info("[录像回放] deviceId: {}, channelId: {}, 开始时间: {}, 结束时间: {}, 收流端口:{}, 收流模式:{}, SSRC: {}, SSRC校验:{}", device.getDeviceId(), channel.getGbDeviceId(), startTime, endTime, ssrcInfo.getPort(), device.getStreamMode(), ssrcInfo.getSsrc(), device.isSsrcCheck()); // 初始化redis中的invite消息状态 InviteInfo inviteInfo = InviteInfo.getInviteInfo(device.getDeviceId(), channel.getId(), ssrcInfo.getStream(), ssrcInfo, mediaServer.getId(), mediaServer.getSdpIp(), ssrcInfo.getPort(), device.getStreamMode(), InviteSessionType.PLAYBACK, InviteSessionStatus.ready, userSetting.getRecordSip()); inviteStreamService.updateInviteInfo(inviteInfo); try { cmder.playbackStreamCmd(mediaServer, ssrcInfo, device, channel, startTime, endTime, eventResult -> { // 处理收到200ok后的TCP主动连接以及SSRC不一致的问题 InviteOKHandler(eventResult, ssrcInfo, mediaServer, device, channel, callback, inviteInfo, InviteSessionType.PLAYBACK); }, eventResult -> { log.info("[录像回放] 失败,{} {}", eventResult.statusCode, eventResult.msg); if (callback != null) { callback.run(eventResult.statusCode, eventResult.msg, null); } receiveRtpServerService.closeRTPServer(mediaServer, ssrcInfo.getApp(), ssrcInfo.getStream()); sessionManager.removeByStream(ssrcInfo.getApp(), ssrcInfo.getStream()); inviteStreamService.removeInviteInfo(inviteInfo); }, userSetting.getPlayTimeout().longValue()); } catch (InvalidArgumentException | SipException | ParseException e) { log.error("[命令发送失败] 录像回放: {}", e.getMessage()); if (callback != null) { callback.run(InviteErrorCode.FAIL.getCode(), e.getMessage(), null); } receiveRtpServerService.closeRTPServer(mediaServer, ssrcInfo.getApp(), ssrcInfo.getStream()); sessionManager.removeByStream(ssrcInfo.getApp(), ssrcInfo.getStream()); inviteStreamService.removeInviteInfo(inviteInfo); } } private void InviteOKHandler(SipSubscribe.EventResult eventResult, SSRCInfo ssrcInfo, MediaServer mediaServerItem, Device device, DeviceChannel channel, ErrorCallback callback, InviteInfo inviteInfo, InviteSessionType inviteSessionType){ inviteInfo.setStatus(InviteSessionStatus.ok); ResponseEvent responseEvent = (ResponseEvent) eventResult.event; String contentString = new String(responseEvent.getResponse().getRawContent()); String ssrcInResponse = SipUtils.getSsrcFromSdp(contentString); // 兼容回复的消息中缺少ssrc(y字段)的情况 if (ssrcInResponse == null) { ssrcInResponse = ssrcInfo.getSsrc(); } if (ssrcInfo.getSsrc().equals(ssrcInResponse)) { // ssrc 一致 if (mediaServerItem.isRtpEnable()) { // 多端口 if (device.getStreamMode().equalsIgnoreCase("TCP-ACTIVE")) { tcpActiveHandler(device, channel, contentString, mediaServerItem, ssrcInfo, callback); } }else { // 单端口 if (device.getStreamMode().equalsIgnoreCase("TCP-ACTIVE")) { log.warn("[Invite 200OK] 单端口收流模式不支持tcp主动模式收流"); } } }else { log.info("[Invite 200OK] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse); // ssrc 不一致 if (mediaServerItem.isRtpEnable()) { // 多端口 if (device.isSsrcCheck()) { // ssrc检验 // 更新ssrc log.info("[Invite 200OK] SSRC修正 {}->{}", ssrcInfo.getSsrc(), ssrcInResponse); // 释放ssrc mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc()); Boolean result = mediaServerService.updateRtpServerSSRC(mediaServerItem, ssrcInfo.getApp(), ssrcInfo.getStream(), ssrcInResponse); if (!result) { try { log.warn("[Invite 200OK] 更新ssrc失败,停止点播 {}/{}", device.getDeviceId(), channel.getDeviceId()); cmder.streamByeCmd(device, channel.getDeviceId(), ssrcInfo.getApp(), ssrcInfo.getStream(), null, null); } catch (InvalidArgumentException | SipException | ParseException | SsrcTransactionNotFoundException e) { log.error("[命令发送失败] 停止播放, 发送BYE: {}", e.getMessage()); } // 释放ssrc mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc()); sessionManager.removeByStream(ssrcInfo.getApp(), ssrcInfo.getStream()); callback.run(InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(), "下级自定义了ssrc,重新设置收流信息失败", null); inviteStreamService.call(inviteSessionType, channel.getId(), null, InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(), "下级自定义了ssrc,重新设置收流信息失败", null); }else { ssrcInfo.setSsrc(ssrcInResponse); inviteInfo.setSsrcInfo(ssrcInfo); inviteInfo.setStream(ssrcInfo.getStream()); if (device.getStreamMode().equalsIgnoreCase("TCP-ACTIVE")) { if (mediaServerItem.isRtpEnable()) { tcpActiveHandler(device, channel, contentString, mediaServerItem, ssrcInfo, callback); }else { log.warn("[Invite 200OK] 单端口收流模式不支持tcp主动模式收流"); } } inviteStreamService.updateInviteInfo(inviteInfo); } } }else { if (ssrcInResponse != null) { // 单端口 // 重新订阅流上线 SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransactionByStream(MediaApp.GB28181, inviteInfo.getStream()); sessionManager.removeByStream(MediaApp.GB28181, inviteInfo.getStream()); inviteStreamService.updateInviteInfoForSSRC(inviteInfo, ssrcInResponse); ssrcTransaction.setDeviceId(device.getDeviceId()); ssrcTransaction.setChannelId(ssrcTransaction.getChannelId()); ssrcTransaction.setCallId(ssrcTransaction.getCallId()); ssrcTransaction.setSsrc(ssrcInResponse); ssrcTransaction.setApp(MediaApp.GB28181); ssrcTransaction.setStream(inviteInfo.getStream()); ssrcTransaction.setMediaServerId(mediaServerItem.getId()); ssrcTransaction.setSipTransactionInfo(new SipTransactionInfo((SIPResponse) responseEvent.getResponse())); ssrcTransaction.setType(inviteSessionType); sessionManager.put(ssrcTransaction); } } } } @Override public void download(Device device, DeviceChannel channel, String startTime, String endTime, int downloadSpeed, ErrorCallback callback) { if (!userSetting.getServerId().equals(device.getServerId())) { redisRpcPlayService.download(device.getServerId(), channel.getId(), startTime, endTime, downloadSpeed, callback); return; } MediaServer newMediaServerItem = this.getNewMediaServerItem(device); if (newMediaServerItem == null) { callback.run(InviteErrorCode.ERROR_FOR_ASSIST_NOT_READY.getCode(), InviteErrorCode.ERROR_FOR_ASSIST_NOT_READY.getMsg(), null); return; } download(newMediaServerItem, device, channel, startTime, endTime, downloadSpeed, callback); } private void download(MediaServer mediaServer, Device device, DeviceChannel channel, String startTime, String endTime, int downloadSpeed, ErrorCallback callback) { if (mediaServer == null ) { callback.run(InviteErrorCode.ERROR_FOR_PARAMETER_ERROR.getCode(), InviteErrorCode.ERROR_FOR_PARAMETER_ERROR.getMsg(), null); return; } int tcpMode = device.getStreamMode().equals("TCP-ACTIVE")? 2: (device.getStreamMode().equals("TCP-PASSIVE")? 1:0); SSRCInfo ssrcInfo = receiveRtpServerService.openGbRTPServer(mediaServer, null, null, tcpMode, false, device.isSsrcCheck(), false, !channel.isHasAudio(), (code, msg, result) -> { if (code == InviteErrorCode.SUCCESS.getCode() && result != null && result.getHookData() != null) { // hook响应 StreamInfo streamInfo = onPublishHandlerForDownload(mediaServer, result.getHookData().getMediaInfo(), device, channel, startTime, endTime); if (streamInfo == null) { log.warn("[录像下载] 获取流地址信息失败"); callback.run(InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getCode(), InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getMsg(), null); return; } callback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), streamInfo); log.info("[录像下载] 调用成功 deviceId: {}, channelId: {}, 开始时间: {}, 结束时间: {}", device.getDeviceId(), channel.getDeviceId(), startTime, endTime); }else { if (callback != null) { callback.run(code, msg, null); } inviteStreamService.call(InviteSessionType.DOWNLOAD, channel.getId(), null, code, msg, null); inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.DOWNLOAD, channel.getId()); if (result != null && result.getSsrcInfo() != null) { SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransactionByStream(result.getSsrcInfo().getApp(), result.getSsrcInfo().getStream()); if (ssrcTransaction != null) { try { cmder.streamByeCmd(device, channel.getDeviceId(), ssrcTransaction.getApp(), ssrcTransaction.getStream(), null, null); } catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) { log.error("[录像下载] 发送BYE失败 {}", e.getMessage()); } finally { sessionManager.removeByStream(ssrcTransaction.getApp(), ssrcTransaction.getStream()); } } } } }); if (ssrcInfo == null || ssrcInfo.getPort() <= 0) { log.info("[录像下载端口/SSRC]获取失败,deviceId={},channelId={},ssrcInfo={}", device.getDeviceId(), channel.getDeviceId(), ssrcInfo); if (callback != null) { callback.run(InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(), "获取端口或者ssrc失败", null); } inviteStreamService.call(InviteSessionType.PLAY, channel.getId(), null, InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(), InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getMsg(), null); return; } log.info("[录像下载] deviceId: {}, channelId: {}, 开始时间: {}, 结束时间: {}, 下载速度:{}, 收流端口:{}, 收流模式:{}, SSRC: {}({}), SSRC校验:{}", device.getDeviceId(), channel.getDeviceId(), startTime, endTime, downloadSpeed, ssrcInfo.getPort(), device.getStreamMode(), ssrcInfo.getSsrc(), String.format("%08x", Long.parseLong(ssrcInfo.getSsrc())).toUpperCase(), device.isSsrcCheck()); // 初始化redis中的invite消息状态 InviteInfo inviteInfo = InviteInfo.getInviteInfo(device.getDeviceId(), channel.getId(), ssrcInfo.getStream(), ssrcInfo, mediaServer.getId(), mediaServer.getSdpIp(), ssrcInfo.getPort(), device.getStreamMode(), InviteSessionType.DOWNLOAD, InviteSessionStatus.ready, true); inviteInfo.setStartTime(startTime); inviteInfo.setEndTime(endTime); inviteStreamService.updateInviteInfo(inviteInfo); try { cmder.downloadStreamCmd(mediaServer, ssrcInfo, device, channel, startTime, endTime, downloadSpeed, eventResult -> { // 对方返回错误 callback.run(InviteErrorCode.FAIL.getCode(), String.format("录像下载失败, 错误码: %s, %s", eventResult.statusCode, eventResult.msg), null); receiveRtpServerService.closeRTPServer(mediaServer, ssrcInfo.getApp(), ssrcInfo.getStream()); sessionManager.removeByStream(ssrcInfo.getApp(), ssrcInfo.getStream()); inviteStreamService.removeInviteInfo(inviteInfo); }, eventResult ->{ // 处理收到200ok后的TCP主动连接以及SSRC不一致的问题 InviteOKHandler(eventResult, ssrcInfo, mediaServer, device, channel, callback, inviteInfo, InviteSessionType.DOWNLOAD); // 注册录像回调事件,录像下载结束后写入下载地址 HookSubscribe.Event hookEventForRecord = (hookData) -> { log.info("[录像下载] 收到录像写入磁盘消息: , {}/{}-{}", inviteInfo.getDeviceId(), inviteInfo.getChannelId(), ssrcInfo.getStream()); log.info("[录像下载] 收到录像写入磁盘消息内容: " + hookData); RecordInfo recordInfo = hookData.getRecordInfo(); DownloadFileInfo downloadFileInfo = mediaServerService.getDownloadFilePath(mediaServer, recordInfo); InviteInfo inviteInfoForNew = inviteStreamService.getInviteInfo(inviteInfo.getType() , inviteInfo.getChannelId(), inviteInfo.getStream()); if (inviteInfoForNew != null && inviteInfoForNew.getStreamInfo() != null) { inviteInfoForNew.getStreamInfo().setDownLoadFilePath(downloadFileInfo); // 不可以马上移除会导致后续接口拿不到下载地址 inviteStreamService.updateInviteInfo(inviteInfoForNew, 60*15L); } }; Hook hook = Hook.getInstance(HookType.on_record_mp4, MediaApp.GB28181, ssrcInfo.getStream(), mediaServer.getId()); // 设置过期时间,下载失败时自动处理订阅数据 hook.setExpireTime(System.currentTimeMillis() + 24 * 60 * 60 * 1000); subscribe.addSubscribe(hook, hookEventForRecord); }, userSetting.getPlayTimeout().longValue()); } catch (InvalidArgumentException | SipException | ParseException e) { log.error("[命令发送失败] 录像下载: {}", e.getMessage()); callback.run(InviteErrorCode.FAIL.getCode(),e.getMessage(), null); receiveRtpServerService.closeRTPServer(mediaServer, ssrcInfo.getApp(), ssrcInfo.getStream()); sessionManager.removeByStream(ssrcInfo.getApp(), ssrcInfo.getStream()); inviteStreamService.removeInviteInfo(inviteInfo); } } @Override public StreamInfo getDownLoadInfo(Device device, DeviceChannel channel, String stream) { InviteInfo inviteInfo = inviteStreamService.getInviteInfo(InviteSessionType.DOWNLOAD, channel.getId(), stream); if (inviteInfo == null) { String app = MediaApp.GB28181; StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(app, stream); if (streamAuthorityInfo != null) { List allList = cloudRecordService.getAllList(null, app, stream, null, null, null, streamAuthorityInfo.getCallId(), null); if (allList.isEmpty()) { log.warn("[获取下载进度] 未查询到录像下载的信息 {}/{}-{}", device.getDeviceId(), channel.getDeviceId(), stream); return null; } String mediaServerId = allList.get(0).getMediaServerId(); MediaServer mediaServer = mediaServerService.getOne(mediaServerId); if (mediaServer == null) { log.warn("[获取下载进度] 未查询到录像下载的节点信息 {}/{}-{}", device.getDeviceId(), channel.getDeviceId(), stream); return null; } log.warn("[获取下载进度] 发现下载已经结束,直接从数据库获取到文件 {}/{}-{}", device.getDeviceId(), channel.getDeviceId(), stream); DownloadFileInfo downloadFileInfo = mediaServerService.getDownloadFilePath(mediaServer, RecordInfo.getInstance(allList.get(0))); StreamInfo streamInfo = new StreamInfo(); streamInfo.setDownLoadFilePath(downloadFileInfo); streamInfo.setApp(app); streamInfo.setStream(stream); streamInfo.setServerId(mediaServerId); streamInfo.setProgress(1.0); return streamInfo; } } if (inviteInfo == null || inviteInfo.getStreamInfo() == null) { log.warn("[获取下载进度] 未查询到录像下载的信息 {}/{}-{}", device.getDeviceId(), channel.getDeviceId(), stream); return null; } if (inviteInfo.getStreamInfo().getProgress() == 1) { return inviteInfo.getStreamInfo(); } // 获取当前已下载时长 MediaServer mediaServerItem = inviteInfo.getStreamInfo().getMediaServer(); if (mediaServerItem == null) { log.warn("[获取下载进度] 查询录像信息时发现节点不存在"); return null; } String app = MediaApp.GB28181; Long duration = mediaServerService.updateDownloadProcess(mediaServerItem, app, stream); if (duration == null || duration == 0) { inviteInfo.getStreamInfo().setProgress(0); } else { String startTime = inviteInfo.getStreamInfo().getStartTime(); String endTime = inviteInfo.getStreamInfo().getEndTime(); // 此时start和end单位是秒 long start = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime); long end = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime); BigDecimal currentCount = new BigDecimal(duration); BigDecimal totalCount = new BigDecimal((end - start) * 1000); BigDecimal divide = currentCount.divide(totalCount, 2, RoundingMode.HALF_UP); double process = divide.doubleValue(); if (process > 0.999) { process = 1.0; } inviteInfo.getStreamInfo().setProgress(process); } inviteStreamService.updateInviteInfo(inviteInfo); return inviteInfo.getStreamInfo(); } private StreamInfo onPublishHandlerForDownload(MediaServer mediaServerItemInuse, MediaInfo mediaInfo, Device device, DeviceChannel channel, String startTime, String endTime) { StreamInfo streamInfo = onPublishHandler(mediaServerItemInuse, mediaInfo, device, channel); if (streamInfo != null) { streamInfo.setProgress(0); streamInfo.setStartTime(startTime); streamInfo.setEndTime(endTime); InviteInfo inviteInfo = inviteStreamService.getInviteInfo(InviteSessionType.DOWNLOAD, channel.getId(), streamInfo.getStream()); if (inviteInfo != null) { log.info("[录像下载] 更新invite消息中的stream信息"); inviteInfo.setStatus(InviteSessionStatus.ok); inviteInfo.setStreamInfo(streamInfo); inviteStreamService.updateInviteInfo(inviteInfo); } } return streamInfo; } public StreamInfo onPublishHandler(MediaServer mediaServerItem, MediaInfo mediaInfo, Device device, DeviceChannel channel) { StreamInfo streamInfo = mediaServerService.getStreamInfoByAppAndStream(mediaServerItem, MediaApp.GB28181, mediaInfo.getStream(), mediaInfo, null); streamInfo.setDeviceId(device.getDeviceId()); streamInfo.setChannelId(channel.getId()); return streamInfo; } @Override public void zlmServerOffline(MediaServer mediaServer) { // 处理正在向上推流的上级平台 List sendRtpInfos = sendRtpServerService.queryAll(); if (!sendRtpInfos.isEmpty()) { for (SendRtpInfo sendRtpInfo : sendRtpInfos) { if (sendRtpInfo.getMediaServerId().equals(mediaServer.getId()) && sendRtpInfo.isSendToPlatform()) { Platform platform = platformService.queryPlatformByServerGBId(sendRtpInfo.getTargetId()); CommonGBChannel channel = channelService.getOne(sendRtpInfo.getChannelId()); try { sipCommanderFroPlatform.streamByeCmd(platform, sendRtpInfo, channel); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage()); } } } } // 处理正在观看的国标设备 List allSsrc = sessionManager.getAll(); if (allSsrc.size() > 0) { for (SsrcTransaction ssrcTransaction : allSsrc) { if (ssrcTransaction.getMediaServerId().equals(mediaServer.getId())) { Device device = deviceService.getDeviceByDeviceId(ssrcTransaction.getDeviceId()); if (device == null) { continue; } DeviceChannel deviceChannel = deviceChannelService.getOneById(ssrcTransaction.getChannelId()); if (deviceChannel == null) { continue; } try { cmder.streamByeCmd(device, deviceChannel.getDeviceId(), ssrcTransaction.getApp(), ssrcTransaction.getStream(), null, null); } catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) { log.error("[zlm离线]为正在使用此zlm的设备, 发送BYE失败 {}", e.getMessage()); } } } } } @Override public AudioBroadcastResult audioBroadcast(String deviceId, String channelDeviceId, Boolean broadcastMode) { Device device = deviceService.getDeviceByDeviceId(deviceId); if (device == null) { throw new ControllerException(ErrorCode.ERROR400.getCode(), "未找到设备: " + deviceId); } DeviceChannel deviceChannel = deviceChannelService.getOne(deviceId, channelDeviceId); if (deviceChannel == null) { throw new ControllerException(ErrorCode.ERROR400.getCode(), "未找到通道: " + channelDeviceId); } if (!userSetting.getServerId().equals(device.getServerId())) { return redisRpcPlayService.audioBroadcast(device.getServerId(), deviceId, channelDeviceId, broadcastMode); } log.info("[语音喊话] device: {}, channel: {}", device.getDeviceId(), deviceChannel.getDeviceId()); MediaServer mediaServerItem = mediaServerService.getMediaServerForMinimumLoad(null); if (broadcastMode == null) { broadcastMode = true; } String app = broadcastMode ? MediaApp.GB28181_BROADCAST : MediaApp.GB28181_TALK; String stream = device.getDeviceId() + "_" + deviceChannel.getDeviceId(); AudioBroadcastResult audioBroadcastResult = new AudioBroadcastResult(); audioBroadcastResult.setApp(app); audioBroadcastResult.setStream(stream); audioBroadcastResult.setStreamInfo(new StreamContent(mediaServerService.getStreamInfoByAppAndStream(mediaServerItem, app, stream, null, null, null, false))); audioBroadcastResult.setCodec("G.711"); return audioBroadcastResult; } @Override public boolean audioBroadcastCmd(Device device, DeviceChannel deviceChannel, MediaServer mediaServerItem, String app, String stream, int timeout, boolean isFromPlatform, AudioBroadcastEvent event) throws InvalidArgumentException, ParseException, SipException { Assert.notNull(device, "设备不存在"); Assert.notNull(deviceChannel, "通道不存在"); log.info("[语音喊话] device: {}, channel: {}", device.getDeviceId(), deviceChannel.getDeviceId()); // 查询通道使用状态 if (audioBroadcastManager.exit(deviceChannel.getId())) { SendRtpInfo sendRtpInfo = sendRtpServerService.queryByChannelId(deviceChannel.getId(), device.getDeviceId()); if (sendRtpInfo != null && sendRtpInfo.isOnlyAudio()) { // 查询流是否存在,不存在则认为是异常状态 Boolean streamReady = mediaServerService.isStreamReady(mediaServerItem, sendRtpInfo.getApp(), sendRtpInfo.getStream()); if (streamReady) { log.warn("语音广播已经开启: {}", deviceChannel.getDeviceId()); event.call("语音广播已经开启"); return false; } else { stopAudioBroadcast(device, deviceChannel); } } } // 发送通知 cmder.audioBroadcastCmd(device, deviceChannel.getDeviceId(), eventResultForOk -> { // 发送成功 AudioBroadcastCatch audioBroadcastCatch = new AudioBroadcastCatch(device.getDeviceId(), deviceChannel.getId(), mediaServerItem, app, stream, event, AudioBroadcastCatchStatus.Ready, isFromPlatform); audioBroadcastManager.update(audioBroadcastCatch); // 等待invite消息, 超时则结束 String key = VideoManagerConstants.BROADCAST_WAITE_INVITE + device.getDeviceId(); if (!SipUtils.isFrontEnd(device.getDeviceId())) { key += audioBroadcastCatch.getChannelId(); } dynamicTask.startDelay(key, ()->{ log.info("[语音广播]等待invite消息超时:{}/{}", device.getDeviceId(), deviceChannel.getDeviceId()); stopAudioBroadcast(device, deviceChannel); }, 10*1000); }, eventResultForError -> { // 发送失败 log.error("语音广播发送失败: {}:{}", deviceChannel.getDeviceId(), eventResultForError.msg); event.call("语音广播发送失败"); stopAudioBroadcast(device, deviceChannel); }); return true; } @Override public boolean audioBroadcastInUse(Device device, DeviceChannel channel) { if (audioBroadcastManager.exit(channel.getId())) { SendRtpInfo sendRtpInfo = sendRtpServerService.queryByChannelId(channel.getId(), device.getDeviceId()); if (sendRtpInfo != null && sendRtpInfo.isOnlyAudio()) { // 查询流是否存在,不存在则认为是异常状态 MediaServer mediaServerServiceOne = mediaServerService.getOne(sendRtpInfo.getMediaServerId()); Boolean streamReady = mediaServerService.isStreamReady(mediaServerServiceOne, sendRtpInfo.getApp(), sendRtpInfo.getStream()); if (streamReady) { log.warn("语音广播通道使用中: {}", channel.getDeviceId()); return true; } } } return false; } @Override public void stopAudioBroadcast(Device device, DeviceChannel channel) { log.info("[停止对讲] 设备:{}, 通道:{}", device.getDeviceId(), channel.getDeviceId()); List audioBroadcastCatchList = new ArrayList<>(); if (channel == null) { audioBroadcastCatchList.addAll(audioBroadcastManager.getByDeviceId(device.getDeviceId())); } else { audioBroadcastCatchList.addAll(audioBroadcastManager.getByDeviceId(device.getDeviceId())); } if (!audioBroadcastCatchList.isEmpty()) { for (AudioBroadcastCatch audioBroadcastCatch : audioBroadcastCatchList) { if (audioBroadcastCatch == null) { continue; } SendRtpInfo sendRtpInfo = sendRtpServerService.queryByChannelId(channel.getId(), device.getDeviceId()); if (sendRtpInfo != null) { sendRtpServerService.delete(sendRtpInfo); MediaServer mediaServer = mediaServerService.getOne(sendRtpInfo.getMediaServerId()); mediaServerService.stopSendRtp(mediaServer, sendRtpInfo.getApp(), sendRtpInfo.getStream(), null); try { cmder.streamByeCmdForDeviceInvite(device, channel.getDeviceId(), audioBroadcastCatch.getSipTransactionInfo(), null); } catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) { log.error("[消息发送失败] 发送语音喊话BYE失败"); } } audioBroadcastManager.del(channel.getId()); } } } @Override public void zlmServerOnline(MediaServer mediaServer) { // 获取 List inviteInfoList = inviteStreamService.getAllInviteInfo(); if (inviteInfoList.isEmpty()) { return; } List rtpServerList = mediaServerService.listRtpServer(mediaServer); if (rtpServerList.isEmpty()) { return; } for (InviteInfo inviteInfo : inviteInfoList) { if (!rtpServerList.contains(inviteInfo.getStream())){ inviteStreamService.removeInviteInfo(inviteInfo); } } } @Override public void playbackPause(String streamId) throws ServiceException, InvalidArgumentException, ParseException, SipException { InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(InviteSessionType.PLAYBACK, streamId); if (null == inviteInfo || inviteInfo.getStreamInfo() == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "streamId不存在"); } Device device = deviceService.getDeviceByDeviceId(inviteInfo.getDeviceId()); if (device == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "设备不存在"); } if (!userSetting.getServerId().equals(device.getServerId())) { redisRpcPlayService.playbackPause(device.getServerId(), streamId); return; } inviteInfo.getStreamInfo().setPause(true); inviteStreamService.updateInviteInfo(inviteInfo); MediaServer mediaServerItem = inviteInfo.getStreamInfo().getMediaServer(); if (null == mediaServerItem) { log.warn("mediaServer 不存在!"); throw new ServiceException("mediaServer不存在"); } // zlm 暂停RTP超时检查 // 使用zlm中的流ID String streamKey = inviteInfo.getStream(); if (!mediaServerItem.isRtpEnable()) { streamKey = Long.toHexString(Long.parseLong(inviteInfo.getSsrcInfo().getSsrc())).toUpperCase(); } Boolean result = mediaServerService.pauseRtpCheck(mediaServerItem, streamKey); if (!result) { throw new ServiceException("暂停RTP接收失败"); } DeviceChannel channel = deviceChannelService.getOneById(inviteInfo.getChannelId()); cmder.playPauseCmd(device, channel, inviteInfo.getStreamInfo()); } @Override public void playbackResume(String streamId) throws ServiceException, InvalidArgumentException, ParseException, SipException { InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(InviteSessionType.PLAYBACK, streamId); if (null == inviteInfo || inviteInfo.getStreamInfo() == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "streamId不存在"); } Device device = deviceService.getDeviceByDeviceId(inviteInfo.getDeviceId()); if (device == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "设备不存在"); } if (!userSetting.getServerId().equals(device.getServerId())) { redisRpcPlayService.playbackResume(device.getServerId(), streamId); return; } inviteInfo.getStreamInfo().setPause(false); inviteStreamService.updateInviteInfo(inviteInfo); MediaServer mediaServerItem = inviteInfo.getStreamInfo().getMediaServer(); if (null == mediaServerItem) { log.warn("mediaServer 不存在!"); throw new ServiceException("mediaServer不存在"); } // 使用zlm中的流ID String streamKey = inviteInfo.getStream(); if (!mediaServerItem.isRtpEnable()) { streamKey = Long.toHexString(Long.parseLong(inviteInfo.getSsrcInfo().getSsrc())).toUpperCase(); } boolean result = mediaServerService.resumeRtpCheck(mediaServerItem, streamKey); if (!result) { throw new ServiceException("继续RTP接收失败"); } DeviceChannel channel = deviceChannelService.getOneById(inviteInfo.getChannelId()); cmder.playResumeCmd(device, channel, inviteInfo.getStreamInfo()); } @Override public void playbackSeek(String streamId, long seekTime) throws InvalidArgumentException, ParseException, SipException { InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(InviteSessionType.PLAYBACK, streamId); if (null == inviteInfo || inviteInfo.getStreamInfo() == null) { log.warn("streamId不存在!"); throw new ControllerException(ErrorCode.ERROR400.getCode(), "streamId不存在"); } Device device = deviceService.getDeviceByDeviceId(inviteInfo.getDeviceId()); DeviceChannel channel = deviceChannelService.getOneById(inviteInfo.getChannelId()); cmder.playSeekCmd(device, channel, inviteInfo.getStreamInfo(), seekTime); } @Override public void playbackSpeed(String streamId, double speed) throws InvalidArgumentException, ParseException, SipException { InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(InviteSessionType.PLAYBACK, streamId); if (null == inviteInfo || inviteInfo.getStreamInfo() == null) { log.warn("streamId不存在!"); throw new ControllerException(ErrorCode.ERROR400.getCode(), "streamId不存在"); } Device device = deviceService.getDeviceByDeviceId(inviteInfo.getDeviceId()); DeviceChannel channel = deviceChannelService.getOneById(inviteInfo.getChannelId()); cmder.playSpeedCmd(device, channel, inviteInfo.getStreamInfo(), speed); } @Override public void startPushStream(SendRtpInfo sendRtpInfo, DeviceChannel channel, SIPResponse sipResponse, Platform platform, CallIdHeader callIdHeader) { // 开始发流 MediaServer mediaInfo = mediaServerService.getOne(sendRtpInfo.getMediaServerId()); if (mediaInfo != null) { try { if (sendRtpInfo.isTcpActive()) { mediaServerService.startSendRtpPassive(mediaInfo, sendRtpInfo, null); } else { mediaServerService.startSendRtp(mediaInfo, sendRtpInfo); } redisCatchStorage.sendPlatformStartPlayMsg(sendRtpInfo, channel, platform); }catch (ControllerException e) { log.error("RTP推流失败: {}", e.getMessage()); startSendRtpStreamFailHand(sendRtpInfo, platform, callIdHeader); return; } log.info("RTP推流成功[ {}/{} ],{}, ", sendRtpInfo.getApp(), sendRtpInfo.getStream(), sendRtpInfo.isTcpActive()?"被动发流": sendRtpInfo.getIp() + ":" + sendRtpInfo.getPort()); } } @Override public void startSendRtpStreamFailHand(SendRtpInfo sendRtpInfo, Platform platform, CallIdHeader callIdHeader) { if (sendRtpInfo.isOnlyAudio()) { Device device = deviceService.getDeviceByDeviceId(sendRtpInfo.getTargetId()); DeviceChannel deviceChannel = deviceChannelService.getOneById(sendRtpInfo.getChannelId()); AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(sendRtpInfo.getChannelId()); if (audioBroadcastCatch != null) { try { cmder.streamByeCmd(device, deviceChannel.getDeviceId(), audioBroadcastCatch.getSipTransactionInfo(), null); } catch (SipException | ParseException | InvalidArgumentException | SsrcTransactionNotFoundException exception) { log.error("[命令发送失败] 停止语音对讲: {}", exception.getMessage()); } } } else { if (platform != null) { // 向上级平台 CommonGBChannel channel = channelService.getOne(sendRtpInfo.getChannelId()); try { commanderForPlatform.streamByeCmd(platform, sendRtpInfo, channel); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage()); } } } } @Override public void talkCmd(Device device, DeviceChannel channel, MediaServer mediaServerItem, String stream, AudioBroadcastEvent event) { if (device == null || channel == null) { return; } // TODO 必须多端口模式才支持语音喊话鹤语音对讲 log.info("[语音对讲] device: {}, channel: {}", device.getDeviceId(), channel.getDeviceId()); // 查询通道使用状态 if (audioBroadcastManager.exit(channel.getId())) { SendRtpInfo sendRtpInfo = sendRtpServerService.queryByChannelId(channel.getId(), device.getDeviceId()); if (sendRtpInfo != null && sendRtpInfo.isOnlyAudio()) { // 查询流是否存在,不存在则认为是异常状态 MediaServer mediaServer = mediaServerService.getOne(sendRtpInfo.getMediaServerId()); Boolean streamReady = mediaServerService.isStreamReady(mediaServer, sendRtpInfo.getApp(), sendRtpInfo.getStream()); if (streamReady) { log.warn("[语音对讲] 正在语音广播,无法开启语音通话: {}", channel.getDeviceId()); event.call("正在语音广播"); return; } else { stopAudioBroadcast(device, channel); } } } SendRtpInfo sendRtpInfo = sendRtpServerService.queryByChannelId(channel.getId(), device.getDeviceId()); if (sendRtpInfo != null) { MediaServer mediaServer = mediaServerService.getOne(sendRtpInfo.getMediaServerId()); Boolean streamReady = mediaServerService.isStreamReady(mediaServer, MediaApp.GB28181_TALK, sendRtpInfo.getReceiveStream()); if (streamReady) { log.warn("[语音对讲] 进行中: {}", channel.getDeviceId()); event.call("语音对讲进行中"); return; } else { stopTalk(device, channel); } } talk(mediaServerItem, device, channel, stream, eventResult -> { log.warn("[语音对讲] 失败,{}/{}, 错误码 {} {}", device.getDeviceId(), channel.getDeviceId(), eventResult.statusCode, eventResult.msg); event.call("失败,错误码 " + eventResult.statusCode + ", " + eventResult.msg); }, () -> { log.warn("[语音对讲] 失败,{}/{} 超时", device.getDeviceId(), channel.getDeviceId()); event.call("失败,超时 "); stopTalk(device, channel); }, errorMsg -> { log.warn("[语音对讲] 失败,{}/{} {}", device.getDeviceId(), channel.getDeviceId(), errorMsg); event.call(errorMsg); stopTalk(device, channel); }); } private void stopTalk(Device device, DeviceChannel channel) { stopTalk(device, channel, null); } @Override public void stopTalk(Device device, DeviceChannel channel, Boolean streamIsReady) { log.info("[语音对讲] 停止, {}/{}", device.getDeviceId(), channel.getDeviceId()); SendRtpInfo sendRtpInfo = sendRtpServerService.queryByChannelId(channel.getId(), device.getDeviceId()); if (sendRtpInfo == null) { log.info("[语音对讲] 停止失败, 未找到发送信息,可能已经停止"); return; } // 停止向设备推流 String mediaServerId = sendRtpInfo.getMediaServerId(); if (mediaServerId == null) { return; } MediaServer mediaServer = mediaServerService.getOne(mediaServerId); if (streamIsReady == null || streamIsReady) { mediaServerService.stopSendRtp(mediaServer, sendRtpInfo.getApp(), sendRtpInfo.getStream(), sendRtpInfo.getSsrc()); } ssrcFactory.releaseSsrc(mediaServerId, sendRtpInfo.getSsrc()); SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransactionByStream(sendRtpInfo.getApp(), sendRtpInfo.getStream()); if (ssrcTransaction != null) { try { cmder.streamByeCmd(device, channel.getDeviceId(), sendRtpInfo.getApp(), sendRtpInfo.getStream(), null, null); } catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) { log.info("[语音对讲] 停止消息发送失败,可能已经停止"); } } sendRtpServerService.deleteByChannel(channel.getId(), device.getDeviceId()); } @Override public void getSnap(String deviceId, String channelId, String fileName, ErrorCallback errorCallback) { Device device = deviceService.getDeviceByDeviceId(deviceId); Assert.notNull(device, "设备不存在"); DeviceChannel channel = deviceChannelService.getOne(deviceId, channelId); Assert.notNull(channel, "通道不存在"); InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, channel.getId()); if (inviteInfo != null) { if (inviteInfo.getStreamInfo() != null) { // 已存在线直接截图 MediaServer mediaServer = inviteInfo.getStreamInfo().getMediaServer(); String path = "snap"; // 请求截图 log.info("[请求截图]: " + fileName); mediaServerService.getSnap(mediaServer, MediaApp.GB28181, inviteInfo.getStreamInfo().getStream(), 15, 1, path, fileName); File snapFile = new File(path + File.separator + fileName); if (snapFile.exists()) { errorCallback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), snapFile.getAbsoluteFile()); }else { errorCallback.run(InviteErrorCode.FAIL.getCode(), InviteErrorCode.FAIL.getMsg(), null); } return; } } MediaServer newMediaServerItem = getNewMediaServerItem(device); play(newMediaServerItem, deviceId, channelId, null, (code, msg, data)->{ if (code == InviteErrorCode.SUCCESS.getCode()) { InviteInfo inviteInfoForPlay = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, channel.getId()); if (inviteInfoForPlay != null && inviteInfoForPlay.getStreamInfo() != null) { getSnap(deviceId, channelId, fileName, errorCallback); }else { errorCallback.run(InviteErrorCode.FAIL.getCode(), InviteErrorCode.FAIL.getMsg(), null); } }else { errorCallback.run(InviteErrorCode.FAIL.getCode(), InviteErrorCode.FAIL.getMsg(), null); } }); } @Override public void stop(InviteSessionType type, Device device, DeviceChannel channel, String stream) { if (!userSetting.getServerId().equals(device.getServerId())) { redisRpcPlayService.stop(device.getServerId(), type, channel.getId(), stream); }else { InviteInfo inviteInfo = inviteStreamService.getInviteInfo(type, channel.getId(), stream); if (inviteInfo == null) { if (type == InviteSessionType.PLAY) { deviceChannelService.stopPlay(channel.getId()); } return; } inviteStreamService.removeInviteInfo(inviteInfo); if (InviteSessionStatus.ok == inviteInfo.getStatus()) { try { log.info("[停止点播/回放/下载] {}/{}", device.getDeviceId(), channel.getDeviceId()); cmder.streamByeCmd(device, channel.getDeviceId(), MediaApp.GB28181, inviteInfo.getStream(), null, null); } catch (InvalidArgumentException | SipException | ParseException | SsrcTransactionNotFoundException e) { log.error("[命令发送失败] 停止点播/回放/下载, 发送BYE: {}", e.getMessage()); throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage()); } } if (inviteInfo.getType() == InviteSessionType.PLAY) { deviceChannelService.stopPlay(channel.getId()); } if (inviteInfo.getStreamInfo() != null) { receiveRtpServerService.closeRTPServer(inviteInfo.getStreamInfo().getMediaServer(), MediaApp.GB28181, stream); } } } @Override public void stop(InviteInfo inviteInfo) { Assert.notNull(inviteInfo, "参数异常"); DeviceChannel channel = deviceChannelService.getOneForSourceById(inviteInfo.getChannelId()); if (channel == null) { log.warn("[停止点播] 发现通道不存在"); return; } Device device = deviceService.getDevice(channel.getDataDeviceId()); if (device == null) { log.warn("[停止点播] 发现设备不存在"); return; } inviteStreamService.removeInviteInfo(inviteInfo); if (InviteSessionStatus.ok == inviteInfo.getStatus()) { try { log.info("[停止点播/回放/下载] {}/{}", device.getDeviceId(), channel.getDeviceId()); cmder.streamByeCmd(device, channel.getDeviceId(), MediaApp.GB28181, inviteInfo.getStream(), null, null); } catch (InvalidArgumentException | SipException | ParseException | SsrcTransactionNotFoundException e) { log.warn("[命令发送失败] 停止点播/回放/下载, 发送BYE: {}", e.getMessage()); } } if (inviteInfo.getType() == InviteSessionType.PLAY) { deviceChannelService.stopPlay(channel.getId()); } if (inviteInfo.getStreamInfo() != null) { receiveRtpServerService.closeRTPServer(inviteInfo.getStreamInfo().getMediaServer(), MediaApp.GB28181, inviteInfo.getStream()); } } @Override public void play(CommonGBChannel channel, Boolean record, ErrorCallback callback) { Device device = deviceService.getDevice(channel.getDataDeviceId()); if (device == null) { log.warn("[点播] 未找到通道{}的设备信息", channel); throw new PlayException(Response.SERVER_INTERNAL_ERROR, "server internal error"); } DeviceChannel deviceChannel = deviceChannelService.getOneForSourceById(channel.getGbId()); MediaServer mediaServerItem = getNewMediaServerItem(device); if (mediaServerItem == null) { log.warn("[点播] 未找到可用的zlm deviceId: {},channelId:{}", device.getDeviceId(), deviceChannel.getDeviceId()); throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到可用的zlm"); } play(mediaServerItem, device, deviceChannel, null, record, callback); } @Override public void stopPlay(InviteSessionType inviteSessionType, CommonGBChannel channel) { Device device = deviceService.getDevice(channel.getDataDeviceId()); if (device == null) { log.warn("[停止播放] 未找到通道{}的设备信息", channel); throw new PlayException(Response.SERVER_INTERNAL_ERROR, "server internal error"); } DeviceChannel deviceChannel = deviceChannelService.getOneForSourceById(channel.getGbId()); String stream = String.format("%s_%s", device.getDeviceId(), deviceChannel.getDeviceId()); stop(inviteSessionType, device, deviceChannel, stream); } @Override public void stop(InviteSessionType inviteSessionType, CommonGBChannel channel, String stream) { Device device = deviceService.getDevice(channel.getDataDeviceId()); if (device == null) { log.warn("[停止播放] 未找到通道{}的设备信息", channel); throw new PlayException(Response.SERVER_INTERNAL_ERROR, "server internal error"); } DeviceChannel deviceChannel = deviceChannelService.getOneForSourceById(channel.getGbId()); stop(inviteSessionType, device, deviceChannel, stream); } @Override public void playBack(CommonGBChannel channel, Long startTime, Long stopTime, ErrorCallback callback) { if (startTime == null || stopTime == null) { throw new PlayException(Response.BAD_REQUEST, "bad request"); } // 国标通道 Device device = deviceService.getDevice(channel.getDataDeviceId()); if (device == null) { log.warn("[点播] 未找到通道{}的设备信息", channel); throw new PlayException(Response.SERVER_INTERNAL_ERROR, "server internal error"); } DeviceChannel deviceChannel = deviceChannelService.getOneById(channel.getGbId()); if (deviceChannel == null) { log.warn("[点播] 未找到通道{}", channel.getGbDeviceId()); throw new PlayException(Response.SERVER_INTERNAL_ERROR, "server internal error"); } String startTimeStr = DateUtil.timestampTo_yyyy_MM_dd_HH_mm_ss(startTime); String stopTimeStr = DateUtil.timestampTo_yyyy_MM_dd_HH_mm_ss(stopTime); playBack(device, deviceChannel, startTimeStr, stopTimeStr, callback); } @Override public void download(CommonGBChannel channel, Long startTime, Long stopTime, Integer downloadSpeed, ErrorCallback callback) { if (startTime == null || stopTime == null || downloadSpeed == null) { throw new PlayException(Response.BAD_REQUEST, "bad request"); } // 国标通道 Device device = deviceService.getDevice(channel.getDataDeviceId()); if (device == null) { log.warn("[点播] 未找到通道{}的设备信息", channel); throw new PlayException(Response.SERVER_INTERNAL_ERROR, "server internal error"); } DeviceChannel deviceChannel = deviceChannelService.getOneById(channel.getGbId()); if (deviceChannel == null) { log.warn("[点播] 未找到通道{}", channel.getGbDeviceId()); throw new PlayException(Response.SERVER_INTERNAL_ERROR, "server internal error"); } String startTimeStr = DateUtil.timestampTo_yyyy_MM_dd_HH_mm_ss(startTime); String stopTimeStr = DateUtil.timestampTo_yyyy_MM_dd_HH_mm_ss(stopTime); download(device, deviceChannel, startTimeStr, stopTimeStr, downloadSpeed, callback); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/RegionServiceImpl.java ================================================ package com.genersoft.iot.vmp.gb28181.service.impl; import com.genersoft.iot.vmp.common.CivilCodePo; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import com.genersoft.iot.vmp.gb28181.bean.Region; import com.genersoft.iot.vmp.gb28181.bean.RegionTree; import com.genersoft.iot.vmp.gb28181.dao.CommonGBChannelMapper; import com.genersoft.iot.vmp.gb28181.dao.RegionMapper; import com.genersoft.iot.vmp.gb28181.event.EventPublisher; import com.genersoft.iot.vmp.gb28181.service.IGbChannelService; import com.genersoft.iot.vmp.gb28181.service.IRegionService; import com.genersoft.iot.vmp.utils.CivilCodeUtil; import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import lombok.extern.slf4j.Slf4j; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DuplicateKeyException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import java.util.*; /** * 区域管理类 */ @Service @Slf4j public class RegionServiceImpl implements IRegionService { @Autowired private RegionMapper regionMapper; @Autowired private CommonGBChannelMapper commonGBChannelMapper; @Autowired private IGbChannelService gbChannelService; @Autowired private EventPublisher eventPublisher; @Override public void add(Region region) { Assert.hasLength(region.getName(), "名称必须存在"); Assert.hasLength(region.getDeviceId(), "国标编号必须存在"); if (ObjectUtils.isEmpty(region.getParentDeviceId()) || ObjectUtils.isEmpty(region.getParentDeviceId().trim())) { region.setParentDeviceId(null); } region.setCreateTime(DateUtil.getNow()); region.setUpdateTime(DateUtil.getNow()); try { regionMapper.add(region); }catch (DuplicateKeyException e){ throw new ControllerException(ErrorCode.ERROR100.getCode(), "此行政区划已存在"); } } @Override @Transactional public boolean deleteByDeviceId(Integer regionDeviceId) { Region region = regionMapper.queryOne(regionDeviceId); // 获取所有子节点 List allChildren = getAllChildren(regionDeviceId); allChildren.add(region); // 设置使用这些节点的通道的civilCode为null, gbChannelService.removeCivilCode(allChildren); regionMapper.batchDelete(allChildren); return true; } private List getAllChildren(Integer deviceId) { if (deviceId == null) { return new ArrayList<>(); } List children = regionMapper.getChildren(deviceId); if (ObjectUtils.isEmpty(children)) { return children; } List regions = new ArrayList<>(children); for (Region region : children) { if (region.getDeviceId().length() < 8) { regions.addAll(getAllChildren(region.getId())); } } return regions; } @Override public PageInfo query(String query, int page, int count) { PageHelper.startPage(page, count); if (query != null) { query = query.replaceAll("/", "//") .replaceAll("%", "/%") .replaceAll("_", "/_"); } List regionList = regionMapper.query(query, null); return new PageInfo<>(regionList); } @Override @Transactional public void update(Region region) { Assert.notNull(region.getDeviceId(), "编号不可为NULL"); Assert.notNull(region.getName(), "名称不可为NULL"); Region regionInDb = regionMapper.queryOne(region.getId()); Assert.notNull(regionInDb, "待更新行政区划在数据库中不存在"); if (!regionInDb.getDeviceId().equals(region.getDeviceId())) { Region regionNewInDb = regionMapper.queryByDeviceId(region.getDeviceId()); Assert.isNull(regionNewInDb, "此行政区划已存在"); // 编号发生变化,把分配了这个行政区划的通道全部更新,并发送数据 gbChannelService.updateCivilCode(regionInDb.getDeviceId(), region.getDeviceId()); // 子节点信息更新 regionMapper.updateChild(region.getId(), region.getDeviceId()); } regionMapper.update(region); // 发送变化通知 try { // 发送catalog eventPublisher.channelEventPublishForUpdate(CommonGBChannel.build(region), null); }catch (Exception e) { log.warn("[行政区划变化] 发送失败,{}", region.getDeviceId(), e); } } @Override public List getAllChild(String parent) { List allChild = CivilCodeUtil.INSTANCE.getAllChild(parent); Collections.sort(allChild); return allChild; } @Override public Region queryRegionByDeviceId(String regionDeviceId) { return null; } @Override public List queryForTree(Integer parent, Boolean hasChannel) { List regionList = regionMapper.queryForTree(parent); if (parent != null && hasChannel != null && hasChannel) { Region parentRegion = regionMapper.queryOne(parent); if (parentRegion != null) { List channelList = commonGBChannelMapper.queryForRegionTreeByCivilCode(parentRegion.getDeviceId()); regionList.addAll(channelList); } } return regionList; } @Override public void syncFromChannel() { // 获取未初始化的行政区划节点 List civilCodeList = regionMapper.getUninitializedCivilCode(); if (civilCodeList.isEmpty()) { return; } List regionList = new ArrayList<>(); // 收集节点的父节点,用于验证哪些节点的父节点不存在,方便一并存入 Map regionMapForVerification = new HashMap<>(); civilCodeList.forEach(civilCode->{ CivilCodePo civilCodePo = CivilCodeUtil.INSTANCE.getCivilCodePo(civilCode); if (civilCodePo != null) { Region region = Region.getInstance(civilCodePo); regionList.add(region); // 获取全部的父节点 List civilCodePoList = CivilCodeUtil.INSTANCE.getAllParentCode(civilCode); if (!civilCodePoList.isEmpty()) { for (CivilCodePo codePo : civilCodePoList) { regionMapForVerification.put(codePo.getCode(), Region.getInstance(codePo)); } } } }); if (regionList.isEmpty()){ return; } if (!regionMapForVerification.isEmpty()) { // 查询数据库中已经存在的. List civilCodesInDb = regionMapper.queryInList(regionMapForVerification.keySet()); if (!civilCodesInDb.isEmpty()) { for (String code : civilCodesInDb) { regionMapForVerification.remove(code); } } } for (Region region : regionList) { regionMapForVerification.put(region.getDeviceId(), region); } regionMapper.batchAdd(new ArrayList<>(regionMapForVerification.values())); } @Override public boolean delete(int id) { return regionMapper.delete(id) > 0; } @Override @Transactional public boolean batchAdd(List regionList) { if (regionList== null || regionList.isEmpty()) { return false; } Map regionMapForVerification = new HashMap<>(); for (Region region : regionList) { regionMapForVerification.put(region.getDeviceId(), region); } // 查询数据库中已经存在的. List regionListInDb = regionMapper.queryInRegionListByDeviceId(regionList); if (!regionListInDb.isEmpty()) { for (Region region : regionListInDb) { regionMapForVerification.remove(region.getDeviceId()); } } if (!regionMapForVerification.isEmpty()) { List regions = new ArrayList<>(regionMapForVerification.values()); regionMapper.batchAdd(regions); regionMapper.updateParentId(regions); } return true; } @Override public List getPath(String deviceId) { Region region = regionMapper.queryByDeviceId(deviceId); if (region == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "行政区划不存在"); } List allParent = getAllParent(region); List regionList = new LinkedList<>(allParent); regionList.add(region); return regionList; } private List getAllParent(Region region) { if (region.getParentId() == null) { return new ArrayList<>(); } Region parent = regionMapper.queryByDeviceId(region.getParentDeviceId()); if (parent == null) { return new ArrayList<>(); } List allParent = getAllParent(parent); allParent.add(parent); return allParent; } @Override public String getDescription(String civilCode) { CivilCodePo civilCodePo = CivilCodeUtil.INSTANCE.getCivilCodePo(civilCode); Assert.notNull(civilCodePo, String.format("节点%s未查询到", civilCode)); StringBuilder sb = new StringBuilder(); sb.append(civilCodePo.getName()); List civilCodePoList = CivilCodeUtil.INSTANCE.getAllParentCode(civilCode); if (civilCodePoList.isEmpty()) { return sb.toString(); } for (int i = 0; i < civilCodePoList.size(); i++) { CivilCodePo item = civilCodePoList.get(i); sb.insert(0, item.getName()); if (i != civilCodePoList.size() - 1) { sb.insert(0, "/"); } } return sb.toString(); } @Override @Transactional public void addByCivilCode(String civilCode) { CivilCodePo civilCodePo = CivilCodeUtil.INSTANCE.getCivilCodePo(civilCode); // 查询是否已经存在此节点 Assert.notNull(civilCodePo, String.format("节点%s未查询到", civilCode)); List civilCodePoList = CivilCodeUtil.INSTANCE.getAllParentCode(civilCode); civilCodePoList.add(civilCodePo); Set civilCodeSet = regionMapper.queryInCivilCodePoList(civilCodePoList); if (!civilCodeSet.isEmpty()) { civilCodePoList.removeIf(item -> civilCodeSet.contains(item.getCode())); } if (civilCodePoList.isEmpty()) { return; } int parentId = -1; for (int i = civilCodePoList.size() - 1; i > -1; i--) { CivilCodePo codePo = civilCodePoList.get(i); Region region = new Region(); region.setDeviceId(codePo.getCode()); region.setParentDeviceId(codePo.getParentCode()); region.setName(civilCodePo.getName()); region.setCreateTime(DateUtil.getNow()); region.setUpdateTime(DateUtil.getNow()); if (parentId == -1 && codePo.getParentCode() != null) { Region parentRegion = regionMapper.queryByDeviceId(codePo.getParentCode()); if (parentRegion == null){ log.error(String.format("行政区划%sy已存在,但查询错误", codePo.getParentCode())); throw new ControllerException(ErrorCode.ERROR100.getCode(), String.format("行政区划%sy已存在,但查询错误", codePo.getParentCode())); } region.setParentId(parentRegion.getId()); }else { region.setParentId(parentId); } regionMapper.add(region); parentId = region.getId(); } } @Override public PageInfo queryList(int page, int count, String query) { PageHelper.startPage(page, count); if (query != null) { query = query.replaceAll("/", "//") .replaceAll("%", "/%") .replaceAll("_", "/_"); } List all = regionMapper.query(query, null); return new PageInfo<>(all); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/SourceDownloadServiceForGbImpl.java ================================================ package com.genersoft.iot.vmp.gb28181.service.impl; import com.genersoft.iot.vmp.common.InviteSessionType; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.common.enums.ChannelDataType; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import com.genersoft.iot.vmp.gb28181.service.IPlayService; import com.genersoft.iot.vmp.gb28181.service.ISourceDownloadService; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Slf4j @Service(ChannelDataType.DOWNLOAD_SERVICE + ChannelDataType.GB28181) public class SourceDownloadServiceForGbImpl implements ISourceDownloadService { @Autowired private IPlayService deviceChannelPlayService; @Override public void download(CommonGBChannel channel, Long startTime, Long stopTime, Integer downloadSpeed, ErrorCallback callback) { } @Override public void stopDownload(CommonGBChannel channel, String stream) { // 国标通道 try { deviceChannelPlayService.stop(InviteSessionType.DOWNLOAD, channel, stream); } catch (Exception e) { log.error("[停止下载失败] {}({})", channel.getGbName(), channel.getGbDeviceId(), e); } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/SourcePTZServiceForGbImpl.java ================================================ package com.genersoft.iot.vmp.gb28181.service.impl; import com.genersoft.iot.vmp.common.enums.ChannelDataType; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.service.IPTZService; import com.genersoft.iot.vmp.gb28181.service.ISourcePTZService; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Slf4j @Service(ChannelDataType.PTZ_SERVICE + ChannelDataType.GB28181) public class SourcePTZServiceForGbImpl implements ISourcePTZService { @Autowired private IPTZService ptzService; @Override public void ptz(CommonGBChannel channel, FrontEndControlCodeForPTZ frontEndControlCode, ErrorCallback callback) { try { int cmdCode = 0; int panSpeed = 0; int titleSpeed = 0; int zoomSpeed = 0; if (frontEndControlCode != null) { if (frontEndControlCode.getPan() != null) { if (frontEndControlCode.getPan() == 0) { cmdCode = cmdCode | 1 << 1; } else if (frontEndControlCode.getPan() == 1) { cmdCode = cmdCode | 1; } } if (frontEndControlCode.getTilt() != null) { if (frontEndControlCode.getTilt() == 0) { cmdCode = cmdCode | 1 << 3; } else if (frontEndControlCode.getTilt() == 1) { cmdCode = cmdCode | 1 << 2; } } if (frontEndControlCode.getZoom() != null) { if (frontEndControlCode.getZoom() == 0) { cmdCode = cmdCode | 1 << 5; } else if (frontEndControlCode.getZoom() == 1) { cmdCode = cmdCode | 1 << 4; } } if (frontEndControlCode.getPanSpeed() == null) { callback.run(ErrorCode.ERROR100.getCode(), "参数异常", null); return; } if (frontEndControlCode.getTiltSpeed() == null) { callback.run(ErrorCode.ERROR100.getCode(), "参数异常", null); return; } if (frontEndControlCode.getZoomSpeed() == null) { callback.run(ErrorCode.ERROR100.getCode(), "参数异常", null); return; } panSpeed = (int)(frontEndControlCode.getPanSpeed()/100D* 255); titleSpeed = (int)(frontEndControlCode.getTiltSpeed()/100D* 255);; zoomSpeed = (int)(frontEndControlCode.getZoomSpeed()/100D* 16); } ptzService.frontEndCommand(channel, cmdCode, panSpeed, titleSpeed, zoomSpeed); callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), null); }catch (Exception e) { log.error("[云台控制失败] ", e); callback.run(ErrorCode.ERROR100.getCode(), e.getMessage(), null); } } @Override public void preset(CommonGBChannel channel, FrontEndControlCodeForPreset frontEndControlCode, ErrorCallback callback) { try { int cmdCode = 0; int parameter1 = 0; int parameter2 = 0; int parameter3 = 0; if (frontEndControlCode != null) { if (frontEndControlCode.getCode() != null) { if (frontEndControlCode.getCode() == 1) { cmdCode = 0x81; } else if (frontEndControlCode.getCode() == 2) { cmdCode = 0x82; }else if (frontEndControlCode.getCode() == 3) { cmdCode = 0x83; } } if (frontEndControlCode.getPresetId() == null) { callback.run(ErrorCode.ERROR100.getCode(), "参数异常", null); return; } parameter2 = frontEndControlCode.getPresetId(); } ptzService.frontEndCommand(channel, cmdCode, parameter1, parameter2, parameter3); callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), null); }catch (Exception e) { log.error("[预置位控制失败] ", e); callback.run(ErrorCode.ERROR100.getCode(), e.getMessage(), null); } } @Override public void fi(CommonGBChannel channel, FrontEndControlCodeForFI frontEndControlCode, ErrorCallback callback) { try { int cmdCode = 1 << 6; int focusSpeed = 0; int irisSpeed = 0; int parameter3 = 0; if (frontEndControlCode != null) { if (frontEndControlCode.getFocus() != null) { if (frontEndControlCode.getFocus() == 0) { cmdCode = cmdCode | 1 << 1; } else if (frontEndControlCode.getFocus() == 1) { cmdCode = cmdCode | 1; }else { log.error("[FI失败] 未知的聚焦指令 {}", frontEndControlCode.getFocus()); callback.run(ErrorCode.ERROR100.getCode(), "未知的指令", null); } } if (frontEndControlCode.getIris() != null) { if (frontEndControlCode.getIris() == 0) { cmdCode = cmdCode | 1 << 3; } else if (frontEndControlCode.getIris() == 1) { cmdCode = cmdCode | 1 << 2; }else { log.error("[FI失败] 未知的光圈指令 {}", frontEndControlCode.getIris()); callback.run(ErrorCode.ERROR100.getCode(), "未知的指令", null); } } if (frontEndControlCode.getFocusSpeed() == null) { callback.run(ErrorCode.ERROR100.getCode(), "参数异常", null); return; } if (frontEndControlCode.getIrisSpeed() == null) { callback.run(ErrorCode.ERROR100.getCode(), "参数异常", null); return; } focusSpeed = frontEndControlCode.getFocusSpeed(); irisSpeed = frontEndControlCode.getIrisSpeed(); } ptzService.frontEndCommand(channel, cmdCode, focusSpeed, irisSpeed, parameter3); callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), null); }catch (Exception e) { log.error("[云台控制失败] ", e); callback.run(ErrorCode.ERROR100.getCode(), e.getMessage(), null); } } @Override public void tour(CommonGBChannel channel, FrontEndControlCodeForTour frontEndControlCode, ErrorCallback callback) { try { int cmdCode = 0; int parameter1 = 0; int parameter2 = 0; int parameter3 = 0; if (frontEndControlCode != null) { if (frontEndControlCode.getCode() != null) { if (frontEndControlCode.getCode() == 1) { cmdCode = 0x84; if (frontEndControlCode.getPresetId() == null) { callback.run(ErrorCode.ERROR100.getCode(), "参数异常", null); return; } parameter2 = frontEndControlCode.getPresetId(); } else if (frontEndControlCode.getCode() == 2) { cmdCode = 0x85; if (frontEndControlCode.getPresetId() == null) { callback.run(ErrorCode.ERROR100.getCode(), "参数异常", null); return; } parameter2 = frontEndControlCode.getPresetId(); }else if (frontEndControlCode.getCode() == 3) { cmdCode = 0x86; if (frontEndControlCode.getPresetId() == null) { callback.run(ErrorCode.ERROR100.getCode(), "参数异常", null); return; } parameter2 = frontEndControlCode.getPresetId(); if (frontEndControlCode.getTourSpeed() == null) { callback.run(ErrorCode.ERROR100.getCode(), "参数异常", null); return; } parameter3 = frontEndControlCode.getTourSpeed(); }else if (frontEndControlCode.getCode() == 4) { cmdCode = 0x87; if (frontEndControlCode.getPresetId() == null) { callback.run(ErrorCode.ERROR100.getCode(), "参数异常", null); return; } parameter2 = frontEndControlCode.getPresetId(); if (frontEndControlCode.getTourTime() == null) { callback.run(ErrorCode.ERROR100.getCode(), "参数异常", null); return; } parameter3 = frontEndControlCode.getTourTime(); }else if (frontEndControlCode.getCode() == 5) { cmdCode = 0x88; }else if (frontEndControlCode.getCode() == 6) { }else { log.error("[巡航控制失败] 未知的指令 {}", frontEndControlCode.getCode()); callback.run(ErrorCode.ERROR100.getCode(), "未知的指令", null); } if (frontEndControlCode.getTourId() == null) { callback.run(ErrorCode.ERROR100.getCode(), "参数异常", null); return; } parameter1 = frontEndControlCode.getTourId(); } } ptzService.frontEndCommand(channel, cmdCode, parameter1, parameter2, parameter3); callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), null); }catch (Exception e) { log.error("[巡航控制失败] ", e); callback.run(ErrorCode.ERROR100.getCode(), e.getMessage(), null); } } @Override public void scan(CommonGBChannel channel, FrontEndControlCodeForScan frontEndControlCode, ErrorCallback callback) { try { int cmdCode = 0; int parameter1 = 0; int parameter2 = 0; int parameter3 = 0; if (frontEndControlCode != null) { if (frontEndControlCode.getCode() != null) { if (frontEndControlCode.getCode() == 1) { cmdCode = 0x89; if (frontEndControlCode.getScanId() == null) { callback.run(ErrorCode.ERROR100.getCode(), "参数异常", null); return; } parameter1 = frontEndControlCode.getScanId(); } else if (frontEndControlCode.getCode() == 2) { cmdCode = 0x89; if (frontEndControlCode.getScanId() == null) { callback.run(ErrorCode.ERROR100.getCode(), "参数异常", null); return; } parameter1 = frontEndControlCode.getScanId(); parameter2 = 1; }else if (frontEndControlCode.getCode() == 3) { cmdCode = 0x89; if (frontEndControlCode.getScanId() == null) { callback.run(ErrorCode.ERROR100.getCode(), "参数异常", null); return; } parameter1 = frontEndControlCode.getScanId(); parameter2 = 2; }else if (frontEndControlCode.getCode() == 4) { cmdCode = 0x8A; if (frontEndControlCode.getScanId() == null) { callback.run(ErrorCode.ERROR100.getCode(), "参数异常", null); return; } if (frontEndControlCode.getScanSpeed() == null) { callback.run(ErrorCode.ERROR100.getCode(), "参数异常", null); return; } parameter1 = frontEndControlCode.getScanId(); parameter2 = frontEndControlCode.getScanSpeed(); }else if (frontEndControlCode.getCode() == 5) { }else { log.error("[巡航控制失败] 未知的指令 {}", frontEndControlCode.getCode()); callback.run(ErrorCode.ERROR100.getCode(), "未知的指令", null); } } } ptzService.frontEndCommand(channel, cmdCode, parameter1, parameter2, parameter3); callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), null); }catch (Exception e) { log.error("[巡航控制失败] ", e); callback.run(ErrorCode.ERROR100.getCode(), e.getMessage(), null); } } @Override public void auxiliary(CommonGBChannel channel, FrontEndControlCodeForAuxiliary frontEndControlCode, ErrorCallback callback) { try { int cmdCode = 0; int parameter1 = 0; int parameter2 = 0; int parameter3 = 0; if (frontEndControlCode != null) { if (frontEndControlCode.getCode() != null) { if (frontEndControlCode.getCode() == 1) { cmdCode = 0x8C; if (frontEndControlCode.getAuxiliaryId() == null) { callback.run(ErrorCode.ERROR100.getCode(), "参数异常", null); return; } parameter1 = frontEndControlCode.getAuxiliaryId(); } else if (frontEndControlCode.getCode() == 2) { cmdCode = 0x8D; if (frontEndControlCode.getAuxiliaryId() == null) { callback.run(ErrorCode.ERROR100.getCode(), "参数异常", null); return; } parameter1 = frontEndControlCode.getAuxiliaryId(); }else { log.error("[辅助开关失败] 未知的指令 {}", frontEndControlCode.getCode()); callback.run(ErrorCode.ERROR100.getCode(), "未知的指令", null); } } } ptzService.frontEndCommand(channel, cmdCode, parameter1, parameter2, parameter3); callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), null); }catch (Exception e) { log.error("[辅助开关失败] ", e); callback.run(ErrorCode.ERROR100.getCode(), e.getMessage(), null); } } @Override public void wiper(CommonGBChannel channel, FrontEndControlCodeForWiper frontEndControlCode, ErrorCallback callback) { try { int cmdCode = 0; int parameter1 = 1; int parameter2 = 0; int parameter3 = 0; if (frontEndControlCode != null) { if (frontEndControlCode.getCode() != null) { if (frontEndControlCode.getCode() == 1) { cmdCode = 0x8C; } else if (frontEndControlCode.getCode() == 2) { cmdCode = 0x8D; }else { log.error("[雨刷开关失败] 未知的指令 {}", frontEndControlCode.getCode()); callback.run(ErrorCode.ERROR100.getCode(), "未知的指令", null); } } } ptzService.frontEndCommand(channel, cmdCode, parameter1, parameter2, parameter3); callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), null); }catch (Exception e) { log.error("[雨刷开关失败] ", e); callback.run(ErrorCode.ERROR100.getCode(), e.getMessage(), null); } } @Override public void queryPreset(CommonGBChannel channel, ErrorCallback> callback) { ptzService.queryPresetList(channel, callback); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/SourcePlayServiceForGbImpl.java ================================================ package com.genersoft.iot.vmp.gb28181.service.impl; import com.genersoft.iot.vmp.common.InviteSessionType; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.common.enums.ChannelDataType; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import com.genersoft.iot.vmp.gb28181.bean.Platform; import com.genersoft.iot.vmp.gb28181.bean.PlayException; import com.genersoft.iot.vmp.gb28181.service.IPlayService; import com.genersoft.iot.vmp.gb28181.service.ISourcePlayService; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import javax.sip.message.Response; @Slf4j @Service(ChannelDataType.PLAY_SERVICE + ChannelDataType.GB28181) public class SourcePlayServiceForGbImpl implements ISourcePlayService { @Autowired private IPlayService deviceChannelPlayService; @Override public void play(CommonGBChannel channel, Platform platform, Boolean record, ErrorCallback callback) { // 国标通道 try { deviceChannelPlayService.play(channel, record, callback); } catch (PlayException e) { callback.run(e.getCode(), e.getMsg(), null); } catch (ControllerException e) { log.error("[点播失败] {}({}), {}", channel.getGbName(), channel.getGbDeviceId(), e.getMsg()); callback.run(Response.BUSY_HERE, "busy here", null); } catch (Exception e) { log.error("[点播失败] {}({})", channel.getGbName(), channel.getGbDeviceId(), e); callback.run(Response.BUSY_HERE, "busy here", null); } } @Override public void stopPlay(CommonGBChannel channel) { // 国标通道 try { deviceChannelPlayService.stopPlay(InviteSessionType.PLAY, channel); } catch (Exception e) { log.error("[停止点播失败] {}({})", channel.getGbName(), channel.getGbDeviceId(), e); } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/SourcePlaybackServiceForGbImpl.java ================================================ package com.genersoft.iot.vmp.gb28181.service.impl; import com.genersoft.iot.vmp.common.InviteSessionType; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.common.enums.ChannelDataType; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import com.genersoft.iot.vmp.gb28181.bean.CommonRecordInfo; import com.genersoft.iot.vmp.gb28181.bean.PlayException; import com.genersoft.iot.vmp.gb28181.bean.RecordItem; import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService; import com.genersoft.iot.vmp.gb28181.service.IPlayService; import com.genersoft.iot.vmp.gb28181.service.ISourcePlaybackService; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import javax.sip.message.Response; import java.util.ArrayList; import java.util.List; @Slf4j @Service(ChannelDataType.PLAYBACK_SERVICE + ChannelDataType.GB28181) public class SourcePlaybackServiceForGbImpl implements ISourcePlaybackService { @Autowired private IPlayService playService; @Autowired private IDeviceChannelService channelService; @Override public void playback(CommonGBChannel channel, Long startTime, Long stopTime, ErrorCallback callback) { try { playService.playBack(channel, startTime, stopTime, callback); } catch (PlayException e) { callback.run(e.getCode(), e.getMsg(), null); } catch (Exception e) { callback.run(Response.BUSY_HERE, "busy here", null); } } @Override public void stopPlayback(CommonGBChannel channel, String stream) { // 国标通道 try { playService.stop(InviteSessionType.PLAYBACK, channel, stream); } catch (Exception e) { log.error("[停止点播失败] {}({})", channel.getGbName(), channel.getGbDeviceId(), e); } } @Override public void playbackPause(CommonGBChannel channel, String stream) { // 国标通道 try { playService.playbackPause(stream); } catch (Exception e) { log.error("[停止点播失败] {}({})", channel.getGbName(), channel.getGbDeviceId(), e); } } @Override public void playbackResume(CommonGBChannel channel, String stream) { // 国标通道 try { playService.playbackPause(stream); } catch (Exception e) { log.error("[停止点播失败] {}({})", channel.getGbName(), channel.getGbDeviceId(), e); } } @Override public void playbackSeek(CommonGBChannel channel, String stream, long seekTime) { // 国标通道 try { playService.playbackPause(stream); } catch (Exception e) { log.error("[停止点播失败] {}({})", channel.getGbName(), channel.getGbDeviceId(), e); } } @Override public void playbackSpeed(CommonGBChannel channel, String stream, Double speed) { // 国标通道 try { playService.playbackSpeed(stream, speed); } catch (Exception e) { log.error("[停止点播失败] {}({})", channel.getGbName(), channel.getGbDeviceId(), e); } } @Override public void queryRecord(CommonGBChannel channel, String startTime, String endTime, ErrorCallback> callback) { channelService.queryRecordInfo(channel, startTime, endTime, (code, msg, data) -> { if (code == ErrorCode.SUCCESS.getCode()) { List recordList = data.getRecordList(); List recordInfoList = new ArrayList<>(); for (RecordItem recordItem : recordList) { CommonRecordInfo recordInfo = new CommonRecordInfo(); recordInfo.setStartTime(recordItem.getStartTime()); recordInfo.setEndTime(recordItem.getEndTime()); recordInfo.setFileSize(recordItem.getFileSize()); recordInfoList.add(recordInfo); } callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), recordInfoList); }else { callback.run(code, msg, null); } }); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/session/AudioBroadcastManager.java ================================================ package com.genersoft.iot.vmp.gb28181.session; import com.genersoft.iot.vmp.conf.SipConfig; import com.genersoft.iot.vmp.gb28181.bean.AudioBroadcastCatch; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * 语音广播消息管理类 * @author lin */ @Slf4j @Component public class AudioBroadcastManager { @Autowired private SipConfig config; public static Map data = new ConcurrentHashMap<>(); public void update(AudioBroadcastCatch audioBroadcastCatch) { data.put(audioBroadcastCatch.getChannelId(), audioBroadcastCatch); } public void del(Integer channelId) { data.remove(channelId); } public List getAll(){ Collection values = data.values(); return new ArrayList<>(values); } public boolean exit(Integer channelId) { return data.containsKey(channelId); } public AudioBroadcastCatch get(Integer channelId) { return data.get(channelId); } public List getByDeviceId(String deviceId) { List audioBroadcastCatchList= new ArrayList<>(); for (AudioBroadcastCatch broadcastCatch : data.values()) { if (broadcastCatch.getDeviceId().equals(deviceId)) { audioBroadcastCatchList.add(broadcastCatch); } } return audioBroadcastCatchList; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/session/CatalogDataManager.java ================================================ package com.genersoft.iot.vmp.gb28181.session; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService; import com.genersoft.iot.vmp.gb28181.service.IGroupService; import com.genersoft.iot.vmp.gb28181.service.IRegionService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; @Slf4j @Component public class CatalogDataManager implements CommandLineRunner { @Autowired private IDeviceChannelService deviceChannelService; @Autowired private IRegionService regionService; @Autowired private IGroupService groupService; @Autowired private RedisTemplate redisTemplate; private final Map dataMap = new ConcurrentHashMap<>(); private final String key = "VMP_CATALOG_DATA"; public String buildMapKey(String deviceId, int sn ) { return deviceId + "_" + sn; } public void addReady(Device device, int sn ) { CatalogData catalogData = dataMap.get(buildMapKey(device.getDeviceId(),sn)); if (catalogData != null) { Set redisKeysForChannel = catalogData.getRedisKeysForChannel(); if (redisKeysForChannel != null && !redisKeysForChannel.isEmpty()) { for (String deleteKey : redisKeysForChannel) { redisTemplate.opsForHash().delete(key, deleteKey); } } Set redisKeysForRegion = catalogData.getRedisKeysForRegion(); if (redisKeysForRegion != null && !redisKeysForRegion.isEmpty()) { for (String deleteKey : redisKeysForRegion) { redisTemplate.opsForHash().delete(key, deleteKey); } } Set redisKeysForGroup = catalogData.getRedisKeysForGroup(); if (redisKeysForGroup != null && !redisKeysForGroup.isEmpty()) { for (String deleteKey : redisKeysForGroup) { redisTemplate.opsForHash().delete(key, deleteKey); } } dataMap.remove(buildMapKey(device.getDeviceId(),sn)); } catalogData = new CatalogData(); catalogData.setDevice(device); catalogData.setSn(sn); catalogData.setStatus(CatalogData.CatalogDataStatus.ready); catalogData.setTime(Instant.now()); dataMap.put(buildMapKey(device.getDeviceId(),sn), catalogData); } public void put(String deviceId, int sn, int total, Device device, List deviceChannelList, List regionList, List groupList) { CatalogData catalogData = dataMap.get(buildMapKey(device.getDeviceId(),sn)); if (catalogData == null ) { log.warn("[缓存-Catalog] 未找到缓存对象,可能已经结束"); return; } catalogData.setStatus(CatalogData.CatalogDataStatus.runIng); catalogData.setTotal(total); catalogData.setTime(Instant.now()); if (deviceChannelList != null && !deviceChannelList.isEmpty()) { for (DeviceChannel deviceChannel : deviceChannelList) { String keyForChannel = "CHANNEL:" + deviceId + ":" + deviceChannel.getDeviceId() + ":" + sn; redisTemplate.opsForHash().put(key, keyForChannel, deviceChannel); catalogData.getRedisKeysForChannel().add(keyForChannel); } } if (regionList != null && !regionList.isEmpty()) { for (Region region : regionList) { String keyForRegion = "REGION:" + deviceId + ":" + region.getDeviceId() + ":" + sn; redisTemplate.opsForHash().put(key, keyForRegion, region); catalogData.getRedisKeysForRegion().add(keyForRegion); } } if (groupList != null && !groupList.isEmpty()) { for (Group group : groupList) { String keyForGroup = "GROUP:" + deviceId + ":" + group.getDeviceId() + ":" + sn; redisTemplate.opsForHash().put(key, keyForGroup, group); catalogData.getRedisKeysForGroup().add(keyForGroup); } } } public List getDeviceChannelList(String deviceId, int sn) { List result = new ArrayList<>(); CatalogData catalogData = dataMap.get(buildMapKey(deviceId,sn)); if (catalogData == null ) { log.warn("[Redis-Catalog] 未找到缓存对象,可能已经结束"); return result; } for (String objectKey : catalogData.getRedisKeysForChannel()) { DeviceChannel deviceChannel = (DeviceChannel) redisTemplate.opsForHash().get(key, objectKey); if (deviceChannel != null) { result.add(deviceChannel); } } return result; } public List getRegionList(String deviceId, int sn) { List result = new ArrayList<>(); CatalogData catalogData = dataMap.get(buildMapKey(deviceId,sn)); if (catalogData == null ) { log.warn("[Redis-Catalog] 未找到缓存对象,可能已经结束"); return result; } for (String objectKey : catalogData.getRedisKeysForRegion()) { Region region = (Region) redisTemplate.opsForHash().get(key, objectKey); if (region != null) { result.add(region); } } return result; } public List getGroupList(String deviceId, int sn) { List result = new ArrayList<>(); CatalogData catalogData = dataMap.get(buildMapKey(deviceId,sn)); if (catalogData == null ) { log.warn("[Redis-Catalog] 未找到缓存对象,可能已经结束"); return result; } for (String objectKey : catalogData.getRedisKeysForGroup()) { Group group = (Group) redisTemplate.opsForHash().get(key, objectKey); if (group != null) { result.add(group); } } return result; } public SyncStatus getSyncStatus(String deviceId) { if (dataMap.isEmpty()) { return null; } Set keySet = dataMap.keySet(); for (String key : keySet) { CatalogData catalogData = dataMap.get(key); if (catalogData != null && deviceId.equals(catalogData.getDevice().getDeviceId())) { SyncStatus syncStatus = new SyncStatus(); syncStatus.setCurrent(catalogData.getRedisKeysForChannel().size()); syncStatus.setTotal(catalogData.getTotal()); syncStatus.setErrorMsg(catalogData.getErrorMsg()); syncStatus.setTime(catalogData.getTime()); if (catalogData.getStatus().equals(CatalogData.CatalogDataStatus.ready) || catalogData.getStatus().equals(CatalogData.CatalogDataStatus.end)) { syncStatus.setSyncIng(false); }else { syncStatus.setSyncIng(true); } if (catalogData.getErrorMsg() != null) { // 失败的同步信息,返回一次后直接移除 dataMap.remove(key); } return syncStatus; } } return null; } public boolean isSyncRunning(String deviceId) { if (dataMap.isEmpty()) { return false; } Set keySet = dataMap.keySet(); for (String key : keySet) { CatalogData catalogData = dataMap.get(key); if (catalogData != null && deviceId.equals(catalogData.getDevice().getDeviceId())) { // 此时检查是否过期 Instant instantBefore30S = Instant.now().minusMillis(TimeUnit.SECONDS.toMillis(30)); if ((catalogData.getStatus().equals(CatalogData.CatalogDataStatus.end) || catalogData.getStatus().equals(CatalogData.CatalogDataStatus.ready)) && catalogData.getTime().isBefore(instantBefore30S)) { dataMap.remove(key); return false; } return !catalogData.getStatus().equals(CatalogData.CatalogDataStatus.end); } } return false; } @Override public void run(String... args) throws Exception { // 启动时清理旧的数据 redisTemplate.delete(key); } @Scheduled(fixedDelay = 5 * 1000) //每5秒执行一次, 发现数据5秒未更新则移除数据并认为数据接收超时 private void timerTask(){ if (dataMap.isEmpty()) { return; } Set keys = dataMap.keySet(); // 消息间等待间隔最大五秒 Instant instantBefore5S = Instant.now().minusMillis(TimeUnit.SECONDS.toMillis(5)); // 消息接收完毕,等待30秒后移除数据 Instant instantBefore30S = Instant.now().minusMillis(TimeUnit.SECONDS.toMillis(30)); // 初次等待的时间长度,兼容部分下级平台发送初次数据很慢的情况 Instant instantBefore2M = Instant.now().minusMillis(TimeUnit.MINUTES.toMillis(2)); for (String dataKey : keys) { CatalogData catalogData = dataMap.get(dataKey); if (catalogData.getStatus().equals(CatalogData.CatalogDataStatus.ready)) { if ( catalogData.getTime().isBefore(instantBefore2M)) { String errorMsg = "同步失败,等待回复超时"; catalogData.setErrorMsg(errorMsg); catalogData.setStatus(CatalogData.CatalogDataStatus.end); } }else if (catalogData.getStatus().equals(CatalogData.CatalogDataStatus.runIng)) { if ( catalogData.getTime().isBefore(instantBefore5S)) { String deviceId = catalogData.getDevice().getDeviceId(); int sn = catalogData.getSn(); List deviceChannelList = getDeviceChannelList(deviceId, sn); try { if (catalogData.getTotal() == deviceChannelList.size()) { deviceChannelService.resetChannels(catalogData.getDevice().getId(), deviceChannelList); }else { deviceChannelService.updateChannels(catalogData.getDevice(), deviceChannelList); } List regionList = getRegionList(deviceId, sn); if ( regionList!= null && !regionList.isEmpty()) { regionService.batchAdd(regionList); } List groupList = getGroupList(deviceId, sn); if (groupList != null && !groupList.isEmpty()) { groupService.batchAdd(groupList); } }catch (Exception e) { log.error("[国标通道同步] 入库失败: ", e); } String errorMsg = "更新成功,共" + catalogData.getTotal() + "条,已更新" + deviceChannelList.size() + "条"; catalogData.setErrorMsg(errorMsg); catalogData.setStatus(CatalogData.CatalogDataStatus.end); } }else { if (catalogData.getTime().isBefore(instantBefore30S)) { dataMap.remove(dataKey); Set redisKeysForChannel = catalogData.getRedisKeysForChannel(); if (redisKeysForChannel != null && !redisKeysForChannel.isEmpty()) { for (String deleteKey : redisKeysForChannel) { redisTemplate.opsForHash().delete(key, deleteKey); } } Set redisKeysForRegion = catalogData.getRedisKeysForRegion(); if (redisKeysForRegion != null && !redisKeysForRegion.isEmpty()) { for (String deleteKey : redisKeysForRegion) { redisTemplate.opsForHash().delete(key, deleteKey); } } Set redisKeysForGroup = catalogData.getRedisKeysForGroup(); if (redisKeysForGroup != null && !redisKeysForGroup.isEmpty()) { for (String deleteKey : redisKeysForGroup) { redisTemplate.opsForHash().delete(key, deleteKey); } } } } } } public void setChannelSyncEnd(String deviceId, int sn, String errorMsg) { CatalogData catalogData = dataMap.get(buildMapKey(deviceId,sn)); if (catalogData == null) { return; } catalogData.setStatus(CatalogData.CatalogDataStatus.end); catalogData.setErrorMsg(errorMsg); catalogData.setTime(Instant.now()); } public int size(String deviceId, int sn) { CatalogData catalogData = dataMap.get(buildMapKey(deviceId,sn)); if (catalogData == null) { return 0; } return catalogData.getRedisKeysForChannel().size() + catalogData.getErrorChannel().size(); } public int sumNum(String deviceId, int sn) { CatalogData catalogData = dataMap.get(buildMapKey(deviceId,sn)); if (catalogData == null) { return 0; } return catalogData.getTotal(); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/session/CommonSessionManager.java ================================================ package com.genersoft.iot.vmp.gb28181.session; import com.genersoft.iot.vmp.common.CommonCallback; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.util.Calendar; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * 通用回调管理 */ @Component public class CommonSessionManager { public static Map callbackMap = new ConcurrentHashMap<>(); /** * 存储回调相关的信息 */ class CommonSession{ public String session; public long createTime; public int timeout; public CommonCallback callback; public CommonCallback timeoutCallback; } /** * 添加回调 * @param sessionId 唯一标识 * @param callback 回调 * @param timeout 超时时间, 单位分钟 */ public void add(String sessionId, CommonCallback callback, CommonCallback timeoutCallback, Integer timeout) { CommonSession commonSession = new CommonSession(); commonSession.session = sessionId; commonSession.callback = callback; commonSession.createTime = System.currentTimeMillis(); if (timeoutCallback != null) { commonSession.timeoutCallback = timeoutCallback; } if (timeout != null) { commonSession.timeout = timeout; } callbackMap.put(sessionId, commonSession); } public void add(String sessionId, CommonCallback callback) { add(sessionId, callback, null, 1); } public CommonCallback get(String sessionId, boolean destroy) { CommonSession commonSession = callbackMap.get(sessionId); if (destroy) { callbackMap.remove(sessionId); } return commonSession.callback; } public CommonCallback get(String sessionId) { return get(sessionId, false); } public void delete(String sessionID) { callbackMap.remove(sessionID); } @Scheduled(fixedRate= 60) //每分钟执行一次 public void execute(){ Calendar cal = Calendar.getInstance(); cal.add(Calendar.MINUTE, -1); for (String session : callbackMap.keySet()) { if (callbackMap.get(session).createTime < cal.getTimeInMillis()) { // 超时 if (callbackMap.get(session).timeoutCallback != null) { callbackMap.get(session).timeoutCallback.run("timeout"); } callbackMap.remove(session); } } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/session/SSRCFactory.java ================================================ package com.genersoft.iot.vmp.gb28181.session; import com.genersoft.iot.vmp.conf.SipConfig; import com.genersoft.iot.vmp.conf.UserSetting; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; import java.util.Set; /** * ssrc使用 */ @Slf4j @Component public class SSRCFactory { /** * 播流最大并发个数 */ private static final Integer MAX_STREAM_COUNT = 10000; /** * 播流最大并发个数 */ private static final String SSRC_INFO_KEY = "VMP_SSRC_INFO_"; @Autowired private StringRedisTemplate redisTemplate; @Autowired private SipConfig sipConfig; @Autowired private UserSetting userSetting; public void initMediaServerSSRC(String mediaServerId, Set usedSet) { String sipDomain = sipConfig.getDomain(); String ssrcPrefix = sipDomain.length() >= 8 ? sipDomain.substring(3, 8) : sipDomain; String redisKey = SSRC_INFO_KEY + userSetting.getServerId() + "_" + mediaServerId; List ssrcList = new ArrayList<>(); for (int i = 1; i < MAX_STREAM_COUNT; i++) { String ssrc = String.format("%s%04d", ssrcPrefix, i); if (null == usedSet || !usedSet.contains(ssrc)) { ssrcList.add(ssrc); } } if (redisTemplate.opsForSet().size(redisKey) != null) { redisTemplate.delete(redisKey); } redisTemplate.opsForSet().add(redisKey, ssrcList.toArray(new String[0])); } /** * 获取视频预览的SSRC值,第一位固定为0 * * @return ssrc */ public String getPlaySsrc(String mediaServerId) { return "0" + getSN(mediaServerId); } /** * 获取录像回放的SSRC值,第一位固定为1 */ public String getPlayBackSsrc(String mediaServerId) { return "1" + getSN(mediaServerId); } /** * 释放ssrc,主要用完的ssrc一定要释放,否则会耗尽 * * @param ssrc 需要重置的ssrc */ public void releaseSsrc(String mediaServerId, String ssrc) { if (ssrc == null) { return; } String sn = ssrc.substring(1); String redisKey = SSRC_INFO_KEY + userSetting.getServerId() + "_" + mediaServerId; redisTemplate.opsForSet().add(redisKey, sn); } /** * 获取后四位数SN,随机数 */ private String getSN(String mediaServerId) { String redisKey = SSRC_INFO_KEY + userSetting.getServerId() + "_" + mediaServerId; Long size = redisTemplate.opsForSet().size(redisKey); if (size == null || size == 0) { log.info("[获取 SSRC 失败] redisKey: {}", redisKey); throw new RuntimeException("ssrc已经用完"); } else { // 在集合中移除并返回一个随机成员。 return redisTemplate.opsForSet().pop(redisKey); } } /** * 重置一个流媒体服务的所有ssrc * * @param mediaServerId 流媒体服务ID */ public void reset(String mediaServerId) { this.initMediaServerSSRC(mediaServerId, null); } /** * 是否已经存在了某个MediaServer的SSRC信息 * * @param mediaServerId 流媒体服务ID */ public boolean hasMediaServerSSRC(String mediaServerId) { String redisKey = SSRC_INFO_KEY + userSetting.getServerId() + "_" + mediaServerId; return Boolean.TRUE.equals(redisTemplate.hasKey(redisKey)); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/session/SipInviteSessionManager.java ================================================ package com.genersoft.iot.vmp.gb28181.session; import com.genersoft.iot.vmp.common.VideoManagerConstants; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; /** * 视频流session管理器,管理视频预览、预览回放的通信句柄 */ @Component public class SipInviteSessionManager { @Autowired private UserSetting userSetting; @Autowired private RedisTemplate redisTemplate; /** * 添加一个点播/回放的事务信息 */ public void put(SsrcTransaction ssrcTransaction){ redisTemplate.opsForHash().put(VideoManagerConstants.SIP_INVITE_SESSION_STREAM + userSetting.getServerId() , ssrcTransaction.getApp() + ssrcTransaction.getStream(), ssrcTransaction); redisTemplate.opsForHash().put(VideoManagerConstants.SIP_INVITE_SESSION_CALL_ID + userSetting.getServerId() , ssrcTransaction.getCallId(), ssrcTransaction); } public SsrcTransaction getSsrcTransactionByStream(String app, String stream){ String key = VideoManagerConstants.SIP_INVITE_SESSION_STREAM + userSetting.getServerId(); return (SsrcTransaction)redisTemplate.opsForHash().get(key, app + stream); } public SsrcTransaction getSsrcTransactionByCallId(String callId){ String key = VideoManagerConstants.SIP_INVITE_SESSION_CALL_ID + userSetting.getServerId(); return (SsrcTransaction)redisTemplate.opsForHash().get(key, callId); } public List getSsrcTransactionByDeviceId(String deviceId){ String key = VideoManagerConstants.SIP_INVITE_SESSION_CALL_ID + userSetting.getServerId(); List values = redisTemplate.opsForHash().values(key); List result = new ArrayList<>(); for (Object value : values) { SsrcTransaction ssrcTransaction = (SsrcTransaction) value; if (ssrcTransaction != null && deviceId.equals(ssrcTransaction.getDeviceId())) { result.add(ssrcTransaction); } } return result; } public void removeByStream(String app, String stream) { SsrcTransaction ssrcTransaction = getSsrcTransactionByStream(app, stream); if (ssrcTransaction == null ) { return; } redisTemplate.opsForHash().delete(VideoManagerConstants.SIP_INVITE_SESSION_STREAM + userSetting.getServerId(), app + stream); if (ssrcTransaction.getCallId() != null) { redisTemplate.opsForHash().delete(VideoManagerConstants.SIP_INVITE_SESSION_CALL_ID + userSetting.getServerId(), ssrcTransaction.getCallId()); } } public void removeByCallId(String callId) { SsrcTransaction ssrcTransaction = getSsrcTransactionByCallId(callId); if (ssrcTransaction == null ) { return; } redisTemplate.opsForHash().delete(VideoManagerConstants.SIP_INVITE_SESSION_CALL_ID + userSetting.getServerId(), callId); if (ssrcTransaction.getStream() != null) { redisTemplate.opsForHash().delete(VideoManagerConstants.SIP_INVITE_SESSION_STREAM + userSetting.getServerId(), ssrcTransaction.getApp() + ssrcTransaction.getStream()); } } public List getAll() { String key = VideoManagerConstants.SIP_INVITE_SESSION_CALL_ID + userSetting.getServerId(); List values = redisTemplate.opsForHash().values(key); List result = new ArrayList<>(); for (Object value : values) { result.add((SsrcTransaction) value); } return result; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/session/SseSessionManager.java ================================================ package com.genersoft.iot.vmp.gb28181.session; import com.genersoft.iot.vmp.conf.DynamicTask; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @Component @Slf4j public class SseSessionManager { private static final Map sseSessionMap = new ConcurrentHashMap<>(); @Autowired private DynamicTask dynamicTask; public SseEmitter conect(String browserId){ SseEmitter sseEmitter = new SseEmitter(0L); sseEmitter.onError((err)-> { log.error("[SSE推送] 连接错误, 浏览器 ID: {}, {}", browserId, err.getMessage()); sseSessionMap.remove(browserId); sseEmitter.completeWithError(err); }); // sseEmitter.onTimeout(() -> { // log.info("[SSE推送] 连接超时, 浏览器 ID: {}", browserId); // sseSessionMap.remove(browserId); // sseEmitter.complete(); // dynamicTask.stop(key); // }); sseEmitter.onCompletion(() -> { log.info("[SSE推送] 连接结束, 浏览器 ID: {}", browserId); sseSessionMap.remove(browserId); }); sseSessionMap.put(browserId, sseEmitter); log.info("[SSE推送] 连接已建立, 浏览器 ID: {}, 当前在线数: {}", browserId, sseSessionMap.size()); return sseEmitter; } @Scheduled(fixedRate = 1000) //每1秒执行一次 public void execute(){ if (sseSessionMap.isEmpty()){ return; } sendForAll("keepalive", "alive"); } public void sendForAll(String event, Object data) { for (String browserId : sseSessionMap.keySet()) { SseEmitter sseEmitter = sseSessionMap.get(browserId); if (sseEmitter == null) { continue; }; try { sseEmitter.send(SseEmitter.event().name(event).data(data)); } catch (Exception e) { log.error("[SSE推送] 发送失败: {}", e.getMessage()); sseSessionMap.remove(browserId); sseEmitter.completeWithError(e); } } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/task/deviceStatus/DeviceStatusTask.java ================================================ package com.genersoft.iot.vmp.gb28181.task.deviceStatus; import com.genersoft.iot.vmp.common.DeviceStatusCallback; import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit; @Slf4j @Data public class DeviceStatusTask implements Delayed { private String deviceId; private SipTransactionInfo transactionInfo; /** * 超时时间(单位: 毫秒) */ private long delayTime; private DeviceStatusCallback callback; public static DeviceStatusTask getInstance(String deviceId, SipTransactionInfo transactionInfo, long delayTime, DeviceStatusCallback callback) { DeviceStatusTask deviceStatusTask = new DeviceStatusTask(); deviceStatusTask.setDeviceId(deviceId); deviceStatusTask.setTransactionInfo(transactionInfo); deviceStatusTask.setDelayTime(delayTime); deviceStatusTask.setCallback(callback); return deviceStatusTask; } public void expired() { if (callback == null) { log.info("[设备离线] 未找到过期处理回调, {}", deviceId); return; } callback.run(deviceId, transactionInfo); } @Override public long getDelay(@NotNull TimeUnit unit) { return unit.convert(delayTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS); } @Override public int compareTo(@NotNull Delayed o) { return (int) (this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS)); } public DeviceStatusTaskInfo getInfo(){ DeviceStatusTaskInfo taskInfo = new DeviceStatusTaskInfo(); taskInfo.setTransactionInfo(transactionInfo); taskInfo.setDeviceId(deviceId); return taskInfo; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/task/deviceStatus/DeviceStatusTaskInfo.java ================================================ package com.genersoft.iot.vmp.gb28181.task.deviceStatus; import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo; import lombok.Data; @Data public class DeviceStatusTaskInfo{ private String deviceId; private SipTransactionInfo transactionInfo; /** * 过期时间,单位毫秒 */ private long expireTime; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/task/deviceStatus/DeviceStatusTaskRunner.java ================================================ package com.genersoft.iot.vmp.gb28181.task.deviceStatus; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo; import com.genersoft.iot.vmp.utils.redis.RedisUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.DelayQueue; import java.util.concurrent.TimeUnit; @Slf4j @Component public class DeviceStatusTaskRunner { private final Map subscribes = new ConcurrentHashMap<>(); private final DelayQueue delayQueue = new DelayQueue<>(); @Autowired private RedisTemplate redisTemplate; @Autowired private UserSetting userSetting; private final String prefix = "VMP_DEVICE_STATUS"; // 状态过期检查 @Scheduled(fixedDelay = 500, timeUnit = TimeUnit.MILLISECONDS) public void expirationCheck(){ while (!delayQueue.isEmpty()) { DeviceStatusTask take = null; try { take = delayQueue.take(); try { removeTask(take.getDeviceId()); take.expired(); }catch (Exception e) { log.error("[设备状态到期] 到期处理时出现异常, 设备编号: {} ", take.getDeviceId()); } } catch (InterruptedException e) { log.error("[设备状态任务] ", e); } } } public void addTask(DeviceStatusTask task) { Duration duration = Duration.ofSeconds((task.getDelayTime() - System.currentTimeMillis())/1000); if (duration.getSeconds() < 0) { return; } subscribes.put(task.getDeviceId(), task); String key = String.format("%s_%s_%s", prefix, userSetting.getServerId(), task.getDeviceId()); redisTemplate.opsForValue().set(key, task.getInfo(), duration); delayQueue.offer(task); } public boolean removeTask(String key) { DeviceStatusTask task = subscribes.get(key); if (task == null) { return false; } String redisKey = String.format("%s_%s_%s", prefix, userSetting.getServerId(), task.getDeviceId()); redisTemplate.delete(redisKey); subscribes.remove(key); if (delayQueue.contains(task)) { boolean remove = delayQueue.remove(task); if (!remove) { log.info("[移除状态任务] 从延时队列内移除失败: {}", key); } } return true; } public SipTransactionInfo getTransactionInfo(String key) { DeviceStatusTask task = subscribes.get(key); if (task == null) { return null; } return task.getTransactionInfo(); } public boolean updateDelay(String key, long expirationTime) { DeviceStatusTask task = subscribes.get(key); if (task == null) { return false; } log.debug("[更新状态任务时间] 编号: {}", key); // 如果值更改时间,如果队列中有多个元素时 超时无法出发。目前采用移除再加入的方法 delayQueue.remove(task); task.setDelayTime(expirationTime); delayQueue.offer(task); String redisKey = String.format("%s_%s_%s", prefix, userSetting.getServerId(), task.getDeviceId()); Duration duration = Duration.ofSeconds((expirationTime - System.currentTimeMillis())/1000); redisTemplate.expire(redisKey, duration); return true; } public boolean containsKey(String key) { return subscribes.containsKey(key); } public List getAllTaskInfo(){ String scanKey = String.format("%s_%s_*", prefix, userSetting.getServerId()); List values = RedisUtil.scan(redisTemplate, scanKey); if (values.isEmpty()) { return new ArrayList<>(); } List result = new ArrayList<>(); for (Object value : values) { String redisKey = (String)value; DeviceStatusTaskInfo taskInfo = (DeviceStatusTaskInfo)redisTemplate.opsForValue().get(redisKey); if (taskInfo == null) { continue; } Long expire = redisTemplate.getExpire(redisKey, TimeUnit.MILLISECONDS); taskInfo.setExpireTime(expire); result.add(taskInfo); } return result; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/task/deviceSubscribe/SubscribeTask.java ================================================ package com.genersoft.iot.vmp.gb28181.task.deviceSubscribe; import com.genersoft.iot.vmp.common.SubscribeCallback; import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo; import lombok.Data; import org.jetbrains.annotations.NotNull; import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit; @Data public abstract class SubscribeTask implements Delayed { private String deviceId; private SubscribeCallback callback; private SipTransactionInfo transactionInfo; /** * 超时时间(单位: 毫秒) */ private long delayTime; public abstract void expired(); public abstract String getKey(); public abstract String getName(); @Override public long getDelay(@NotNull TimeUnit unit) { return unit.convert(delayTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS); } @Override public int compareTo(@NotNull Delayed o) { return (int) (this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS)); } public SubscribeTaskInfo getInfo(){ SubscribeTaskInfo subscribeTaskInfo = new SubscribeTaskInfo(); subscribeTaskInfo.setName(getName()); subscribeTaskInfo.setTransactionInfo(transactionInfo); subscribeTaskInfo.setDeviceId(deviceId); subscribeTaskInfo.setKey(getKey()); return subscribeTaskInfo; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/task/deviceSubscribe/SubscribeTaskInfo.java ================================================ package com.genersoft.iot.vmp.gb28181.task.deviceSubscribe; import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo; import lombok.Data; @Data public class SubscribeTaskInfo { private String deviceId; private SipTransactionInfo transactionInfo; private String name; private String key; /** * 过期时间,单位: 秒 */ private long expireTime; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/task/deviceSubscribe/SubscribeTaskRunner.java ================================================ package com.genersoft.iot.vmp.gb28181.task.deviceSubscribe; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo; import com.genersoft.iot.vmp.utils.redis.RedisUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.DelayQueue; import java.util.concurrent.TimeUnit; @Slf4j @Component public class SubscribeTaskRunner{ private final Map subscribes = new ConcurrentHashMap<>(); private final DelayQueue delayQueue = new DelayQueue<>(); @Autowired private RedisTemplate redisTemplate; @Autowired private UserSetting userSetting; private final String prefix = "VMP_DEVICE_SUBSCRIBE"; // 订阅过期检查 @Scheduled(fixedDelay = 500, timeUnit = TimeUnit.MILLISECONDS) public void expirationCheck(){ while (!delayQueue.isEmpty()) { SubscribeTask take = null; try { take = delayQueue.take(); try { removeSubscribe(take.getKey()); take.expired(); }catch (Exception e) { log.error("[设备订阅到期] {} 到期处理时出现异常, 设备编号: {} ", take.getName(), take.getDeviceId()); } } catch (InterruptedException e) { log.error("[设备订阅任务] ", e); } } } public void addSubscribe(SubscribeTask task) { Duration duration = Duration.ofSeconds((task.getDelayTime() - System.currentTimeMillis())/1000); if (duration.getSeconds() < 0) { return; } subscribes.put(task.getKey(), task); String key = String.format("%s_%s_%s", prefix, userSetting.getServerId(), task.getKey()); redisTemplate.opsForValue().set(key, task.getInfo(), duration); delayQueue.offer(task); } public boolean removeSubscribe(String key) { SubscribeTask task = subscribes.get(key); if (task == null) { return false; } String redisKey = String.format("%s_%s_%s", prefix, userSetting.getServerId(), task.getKey()); redisTemplate.delete(redisKey); subscribes.remove(key); if (delayQueue.contains(task)) { boolean remove = delayQueue.remove(task); if (!remove) { log.info("[移除订阅任务] 从延时队列内移除失败: {}", key); } } return true; } public SipTransactionInfo getTransactionInfo(String key) { SubscribeTask task = subscribes.get(key); if (task == null) { return null; } return task.getTransactionInfo(); } public boolean updateDelay(String key, long expirationTime) { SubscribeTask task = subscribes.get(key); if (task == null) { return false; } log.info("[更新订阅任务时间] {}, 编号: {}", task.getName(), key); delayQueue.remove(task); task.setDelayTime(expirationTime); delayQueue.offer(task); String redisKey = String.format("%s_%s_%s", prefix, userSetting.getServerId(), task.getKey()); Duration duration = Duration.ofSeconds((expirationTime - System.currentTimeMillis())/1000); redisTemplate.expire(redisKey, duration); return true; } public boolean containsKey(String key) { return subscribes.containsKey(key); } public List getAllTaskInfo(){ String scanKey = String.format("%s_%s_*", prefix, userSetting.getServerId()); List values = RedisUtil.scan(redisTemplate, scanKey); if (values.isEmpty()) { return new ArrayList<>(); } List result = new ArrayList<>(); for (Object value : values) { String redisKey = (String)value; SubscribeTaskInfo taskInfo = (SubscribeTaskInfo)redisTemplate.opsForValue().get(redisKey); if (taskInfo == null) { continue; } Long expire = redisTemplate.getExpire(redisKey, TimeUnit.SECONDS); taskInfo.setExpireTime(expire); result.add(taskInfo); } return result; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/task/deviceSubscribe/impl/SubscribeTaskForCatalog.java ================================================ package com.genersoft.iot.vmp.gb28181.task.deviceSubscribe.impl; import com.genersoft.iot.vmp.common.SubscribeCallback; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo; import com.genersoft.iot.vmp.gb28181.task.deviceSubscribe.SubscribeTask; import lombok.extern.slf4j.Slf4j; @Slf4j public class SubscribeTaskForCatalog extends SubscribeTask { public static final String name = "catalog"; public static SubscribeTask getInstance(Device device, SubscribeCallback callback, SipTransactionInfo transactionInfo) { if (device.getSubscribeCycleForCatalog() <= 0) { return null; } SubscribeTaskForCatalog subscribeTaskForCatalog = new SubscribeTaskForCatalog(); subscribeTaskForCatalog.setDelayTime((device.getSubscribeCycleForCatalog() * 1000L - 500L) + System.currentTimeMillis()); subscribeTaskForCatalog.setDeviceId(device.getDeviceId()); subscribeTaskForCatalog.setCallback(callback); subscribeTaskForCatalog.setTransactionInfo(transactionInfo); return subscribeTaskForCatalog; } @Override public void expired() { if (super.getCallback() == null) { log.info("[设备订阅到期] 目录订阅 未找到到期处理回调, 编号: {}", getDeviceId()); return; } getCallback().run(getDeviceId(), getTransactionInfo()); } @Override public String getKey() { return String.format("%s_%s", name, getDeviceId()); } @Override public String getName() { return name; } public static String getKey(Device device) { return String.format("%s_%s", SubscribeTaskForCatalog.name, device.getDeviceId()); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/task/deviceSubscribe/impl/SubscribeTaskForMobilPosition.java ================================================ package com.genersoft.iot.vmp.gb28181.task.deviceSubscribe.impl; import com.genersoft.iot.vmp.common.SubscribeCallback; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo; import com.genersoft.iot.vmp.gb28181.task.deviceSubscribe.SubscribeTask; import lombok.extern.slf4j.Slf4j; @Slf4j public class SubscribeTaskForMobilPosition extends SubscribeTask { public static final String name = "mobilPosition"; public static SubscribeTask getInstance(Device device, SubscribeCallback callback, SipTransactionInfo transactionInfo) { if (device.getSubscribeCycleForMobilePosition() <= 0) { return null; } SubscribeTaskForMobilPosition subscribeTaskForMobilPosition = new SubscribeTaskForMobilPosition(); subscribeTaskForMobilPosition.setDelayTime((device.getSubscribeCycleForMobilePosition() * 1000L - 500L) + System.currentTimeMillis()); subscribeTaskForMobilPosition.setDeviceId(device.getDeviceId()); subscribeTaskForMobilPosition.setCallback(callback); subscribeTaskForMobilPosition.setTransactionInfo(transactionInfo); return subscribeTaskForMobilPosition; } @Override public void expired() { if (super.getCallback() == null) { log.info("[设备订阅到期] 移动位置订阅 未找到到期处理回调, 编号: {}", getDeviceId()); return; } getCallback().run(getDeviceId(), getTransactionInfo()); } @Override public String getKey() { return String.format("%s_%s", name, getDeviceId()); } @Override public String getName() { return name; } public static String getKey(Device device) { return String.format("%s_%s", SubscribeTaskForMobilPosition.name, device.getDeviceId()); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/task/platformStatus/PlatformKeepaliveTask.java ================================================ package com.genersoft.iot.vmp.gb28181.task.platformStatus; import com.genersoft.iot.vmp.gb28181.bean.PlatformKeepaliveCallback; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit; /** * 平台心跳任务 */ @Slf4j public class PlatformKeepaliveTask implements Delayed { @Getter private String platformServerId; /** * 超时时间(单位: 毫秒) */ @Getter @Setter private long delayTime; /** * 到期回调 */ @Getter private PlatformKeepaliveCallback callback; /** * 心跳发送失败次数 */ @Getter @Setter private int failCount; public PlatformKeepaliveTask(String platformServerId, long delayTime, PlatformKeepaliveCallback callback) { this.platformServerId = platformServerId; this.delayTime = System.currentTimeMillis() + delayTime; this.callback = callback; } public void expired() { if (callback == null) { log.info("[平台心跳到期] 未找到到期处理回调, 平台上级编号: {}", platformServerId); return; } getCallback().run(platformServerId, failCount); } @Override public long getDelay(@NotNull TimeUnit unit) { return unit.convert(delayTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS); } @Override public int compareTo(@NotNull Delayed o) { return (int) (this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS)); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/task/platformStatus/PlatformRegisterTask.java ================================================ package com.genersoft.iot.vmp.gb28181.task.platformStatus; import com.genersoft.iot.vmp.common.CommonCallback; import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit; /** * 平台注册任务 */ @Slf4j public class PlatformRegisterTask implements Delayed { @Getter private String platformServerId; /** * 超时时间(单位: 毫秒) */ @Getter @Setter private long delayTime; @Getter private SipTransactionInfo sipTransactionInfo; /** * 到期回调 */ @Getter private CommonCallback callback; public PlatformRegisterTask(String platformServerId, long delayTime, SipTransactionInfo sipTransactionInfo, CommonCallback callback) { this.platformServerId = platformServerId; this.delayTime = System.currentTimeMillis() + delayTime; this.callback = callback; this.sipTransactionInfo = sipTransactionInfo; } public void expired() { if (callback == null) { log.info("[平台注册到期] 未找到到期处理回调, 平台上级编号: {}", platformServerId); return; } getCallback().run(platformServerId); } @Override public long getDelay(@NotNull TimeUnit unit) { return unit.convert(delayTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS); } @Override public int compareTo(@NotNull Delayed o) { return (int) (this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS)); } public PlatformRegisterTaskInfo getInfo() { PlatformRegisterTaskInfo taskInfo = new PlatformRegisterTaskInfo(); taskInfo.setPlatformServerId(platformServerId); taskInfo.setSipTransactionInfo(sipTransactionInfo); return taskInfo; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/task/platformStatus/PlatformRegisterTaskInfo.java ================================================ package com.genersoft.iot.vmp.gb28181.task.platformStatus; import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo; import lombok.Data; import lombok.extern.slf4j.Slf4j; /** * 平台注册任务可序列化的信息 */ @Slf4j @Data public class PlatformRegisterTaskInfo{ private String platformServerId; private SipTransactionInfo sipTransactionInfo; /** * 过期时间,单位: 毫秒 */ private long expireTime; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/task/platformStatus/PlatformStatusTaskRunner.java ================================================ package com.genersoft.iot.vmp.gb28181.task.platformStatus; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo; import com.genersoft.iot.vmp.utils.redis.RedisUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.DelayQueue; import java.util.concurrent.TimeUnit; @Slf4j @Component public class PlatformStatusTaskRunner { private final Map registerSubscribes = new ConcurrentHashMap<>(); private final DelayQueue registerDelayQueue = new DelayQueue<>(); private final Map keepaliveSubscribes = new ConcurrentHashMap<>(); private final DelayQueue keepaliveTaskDelayQueue = new DelayQueue<>(); @Autowired private RedisTemplate redisTemplate; @Autowired private UserSetting userSetting; private final String prefix = "VMP_PLATFORM_STATUS"; // 订阅过期检查 @Scheduled(fixedDelay = 500, timeUnit = TimeUnit.MILLISECONDS) public void expirationCheckForRegister(){ while (!registerDelayQueue.isEmpty()) { PlatformRegisterTask take = null; try { take = registerDelayQueue.take(); try { removeRegisterTask(take.getPlatformServerId()); take.expired(); }catch (Exception e) { log.error("[平台注册到期] 到期处理时出现异常, 平台上级编号: {} ", take.getPlatformServerId()); } } catch (InterruptedException e) { log.error("[平台注册到期] ", e); } } } @Scheduled(fixedDelay = 500, timeUnit = TimeUnit.MILLISECONDS) public void expirationCheckForKeepalive(){ while (!keepaliveTaskDelayQueue.isEmpty()) { PlatformKeepaliveTask take = null; try { take = keepaliveTaskDelayQueue.take(); try { removeKeepAliveTask(take.getPlatformServerId()); take.expired(); }catch (Exception e) { log.error("[平台心跳到期] 到期处理时出现异常, 平台上级编号: {} ", take.getPlatformServerId()); } } catch (InterruptedException e) { log.error("[平台心跳到期] ", e); } } } public void addRegisterTask(PlatformRegisterTask task) { Duration duration = Duration.ofSeconds((task.getDelayTime() - System.currentTimeMillis())/1000); if (duration.getSeconds() < 0) { return; } registerSubscribes.put(task.getPlatformServerId(), task); String key = String.format("%s_%s_%s", prefix, userSetting.getServerId(), task.getPlatformServerId()); redisTemplate.opsForValue().set(key, task.getInfo(), duration); registerDelayQueue.offer(task); } public boolean removeRegisterTask(String platformServerId) { PlatformRegisterTask task = registerSubscribes.get(platformServerId); if (task != null) { registerSubscribes.remove(platformServerId); } String redisKey = String.format("%s_%s_%s", prefix, userSetting.getServerId(), platformServerId); redisTemplate.delete(redisKey); if (registerDelayQueue.contains(task)) { boolean remove = registerDelayQueue.remove(task); if (!remove) { log.info("[移除平台注册任务] 从延时队列内移除失败: {}", platformServerId); } } return true; } public SipTransactionInfo getRegisterTransactionInfo(String platformServerId) { PlatformRegisterTask task = registerSubscribes.get(platformServerId); if (task == null) { return null; } return task.getSipTransactionInfo(); } public boolean containsRegister(String platformServerId) { return registerSubscribes.containsKey(platformServerId); } public List getAllRegisterTaskInfo(){ return getRegisterTransactionInfoByServerId(userSetting.getServerId()); } public void addKeepAliveTask(PlatformKeepaliveTask task) { Duration duration = Duration.ofSeconds((task.getDelayTime() - System.currentTimeMillis())/1000); if (duration.getSeconds() < 0) { return; } keepaliveSubscribes.put(task.getPlatformServerId(), task); keepaliveTaskDelayQueue.offer(task); } public boolean removeKeepAliveTask(String platformServerId) { PlatformKeepaliveTask task = keepaliveSubscribes.get(platformServerId); if (task != null) { keepaliveSubscribes.remove(platformServerId); } if (keepaliveTaskDelayQueue.contains(task)) { boolean remove = keepaliveTaskDelayQueue.remove(task); if (!remove) { log.info("[移除平台心跳任务] 从延时队列内移除失败: {}", platformServerId); } } return true; } public boolean containsKeepAlive(String platformServerId) { return keepaliveSubscribes.containsKey(platformServerId); } public List getRegisterTransactionInfoByServerId(String serverId) { String scanKey = String.format("%s_%s_*", prefix, serverId); List values = RedisUtil.scan(redisTemplate, scanKey); if (values.isEmpty()) { return new ArrayList<>(); } List result = new ArrayList<>(); for (Object value : values) { String redisKey = (String)value; PlatformRegisterTaskInfo taskInfo = (PlatformRegisterTaskInfo)redisTemplate.opsForValue().get(redisKey); if (taskInfo == null) { continue; } Long expire = redisTemplate.getExpire(redisKey, TimeUnit.MILLISECONDS); taskInfo.setExpireTime(expire); result.add(taskInfo); } return result; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/ISIPProcessorObserver.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit; import javax.sip.SipListener; public interface ISIPProcessorObserver extends SipListener { } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPProcessorObserver.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit; import com.genersoft.iot.vmp.gb28181.event.EventPublisher; import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; import com.genersoft.iot.vmp.gb28181.event.sip.SipEvent; import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor; import com.genersoft.iot.vmp.gb28181.transmit.event.response.ISIPResponseProcessor; import gov.nist.javax.sip.message.SIPResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; import javax.sip.*; import javax.sip.header.CSeqHeader; import javax.sip.header.CallIdHeader; import javax.sip.message.Response; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * @description: SIP信令处理类观察者 * @author: panlinlin * @date: 2021年11月5日 下午15:32 */ @Slf4j @Component public class SIPProcessorObserver implements ISIPProcessorObserver { private static final Map requestProcessorMap = new ConcurrentHashMap<>(); private static final Map responseProcessorMap = new ConcurrentHashMap<>(); @Autowired private SipSubscribe sipSubscribe; @Autowired private EventPublisher eventPublisher; /** * 添加 request订阅 * @param method 方法名 * @param processor 处理程序 */ public void addRequestProcessor(String method, ISIPRequestProcessor processor) { requestProcessorMap.put(method, processor); } /** * 添加 response订阅 * @param method 方法名 * @param processor 处理程序 */ public void addResponseProcessor(String method, ISIPResponseProcessor processor) { responseProcessorMap.put(method, processor); } /** * 分发RequestEvent事件 * @param requestEvent RequestEvent事件 */ @Override @Async("taskExecutor") public void processRequest(RequestEvent requestEvent) { String method = requestEvent.getRequest().getMethod(); ISIPRequestProcessor sipRequestProcessor = requestProcessorMap.get(method); if (sipRequestProcessor == null) { log.warn("不支持方法{}的request", method); // TODO 回复错误玛 return; } requestProcessorMap.get(method).process(requestEvent); } /** * 分发ResponseEvent事件 * @param responseEvent responseEvent事件 */ @Override @Async("taskExecutor") public void processResponse(ResponseEvent responseEvent) { SIPResponse response = (SIPResponse)responseEvent.getResponse(); int status = response.getStatusCode(); // Success if (((status >= Response.OK) && (status < Response.MULTIPLE_CHOICES)) || status == Response.UNAUTHORIZED) { ISIPResponseProcessor sipRequestProcessor = responseProcessorMap.get(response.getCSeqHeader().getMethod()); if (sipRequestProcessor != null) { sipRequestProcessor.process(responseEvent); } CallIdHeader callIdHeader = response.getCallIdHeader(); CSeqHeader cSeqHeader = response.getCSeqHeader(); if (callIdHeader != null) { SipEvent sipEvent = sipSubscribe.getSubscribe(callIdHeader.getCallId() + cSeqHeader.getSeqNumber()); if (sipEvent != null) { if (sipEvent.getOkEvent() != null) { SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult<>(responseEvent); sipEvent.getOkEvent().response(eventResult); } sipSubscribe.removeSubscribe(callIdHeader.getCallId() + cSeqHeader.getSeqNumber()); } } } else if ((status >= Response.TRYING) && (status < Response.OK)) { // 增加其它无需回复的响应,如101、180等 // 更新sip订阅的时间 // sipSubscribe.updateTimeout(response.getCallIdHeader().getCallId()); } else { log.warn("接收到失败的response响应!status:" + status + ",message:" + response.getReasonPhrase()); if (responseEvent.getResponse() != null && !sipSubscribe.isEmpty() ) { CallIdHeader callIdHeader = response.getCallIdHeader(); CSeqHeader cSeqHeader = response.getCSeqHeader(); if (callIdHeader != null) { SipEvent sipEvent = sipSubscribe.getSubscribe(callIdHeader.getCallId() + cSeqHeader.getSeqNumber()); if (sipEvent != null ) { if (sipEvent.getErrorEvent() != null) { SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult<>(responseEvent); sipEvent.getErrorEvent().response(eventResult); } sipSubscribe.removeSubscribe(callIdHeader.getCallId() + cSeqHeader.getSeqNumber()); } } } if (responseEvent.getDialog() != null) { responseEvent.getDialog().delete(); } } } /** * 向超时订阅发送消息 * @param timeoutEvent timeoutEvent事件 */ @Override public void processTimeout(TimeoutEvent timeoutEvent) { log.info("[消息发送超时]"); // ClientTransaction clientTransaction = timeoutEvent.getClientTransaction(); // // if (clientTransaction != null) { // log.info("[发送错误订阅] clientTransaction != null"); // Request request = clientTransaction.getRequest(); // if (request != null) { // log.info("[发送错误订阅] request != null"); // CallIdHeader callIdHeader = (CallIdHeader) request.getHeader(CallIdHeader.NAME); // if (callIdHeader != null) { // log.info("[发送错误订阅]"); // SipSubscribe.Event subscribe = sipSubscribe.getErrorSubscribe(callIdHeader.getCallId()); // SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(timeoutEvent); // if (subscribe != null){ // subscribe.response(eventResult); // } // sipSubscribe.removeOkSubscribe(callIdHeader.getCallId()); // sipSubscribe.removeErrorSubscribe(callIdHeader.getCallId()); // } // } // } // eventPublisher.requestTimeOut(timeoutEvent); } @Override public void processIOException(IOExceptionEvent exceptionEvent) { System.out.println("processIOException"); } @Override public void processTransactionTerminated(TransactionTerminatedEvent transactionTerminatedEvent) { // if (transactionTerminatedEvent.isServerTransaction()) { // ServerTransaction serverTransaction = transactionTerminatedEvent.getServerTransaction(); // serverTransaction.get // } // Transaction transaction = null; // System.out.println("processTransactionTerminated"); // if (transactionTerminatedEvent.isServerTransaction()) { // transaction = transactionTerminatedEvent.getServerTransaction(); // }else { // transaction = transactionTerminatedEvent.getClientTransaction(); // } // // System.out.println(transaction.getBranchId()); // System.out.println(transaction.getState()); // System.out.println(transaction.getRequest().getMethod()); // CallIdHeader header = (CallIdHeader)transaction.getRequest().getHeader(CallIdHeader.NAME); // SipSubscribe.EventResult terminatedEventEventResult = new SipSubscribe.EventResult<>(transactionTerminatedEvent); // sipSubscribe.getErrorSubscribe(header.getCallId()).response(terminatedEventEventResult); } @Override public void processDialogTerminated(DialogTerminatedEvent dialogTerminatedEvent) { CallIdHeader callId = dialogTerminatedEvent.getDialog().getCallId(); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPSender.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit; import com.genersoft.iot.vmp.conf.SipConfig; import com.genersoft.iot.vmp.gb28181.SipLayer; import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo; import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; import com.genersoft.iot.vmp.gb28181.event.sip.SipEvent; import com.genersoft.iot.vmp.gb28181.utils.SipUtils; import com.genersoft.iot.vmp.utils.GitUtil; import gov.nist.javax.sip.SipProviderImpl; import gov.nist.javax.sip.address.SipUri; import gov.nist.javax.sip.message.SIPRequest; import gov.nist.javax.sip.message.SIPResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; import javax.sip.SipException; import javax.sip.header.*; import javax.sip.message.Message; import javax.sip.message.Request; import javax.sip.message.Response; import java.text.ParseException; /** * 发送SIP消息 * * @author lin */ @Slf4j @Component public class SIPSender { @Autowired private SipLayer sipLayer; @Autowired private GitUtil gitUtil; @Autowired private SipSubscribe sipSubscribe; @Autowired private SipConfig sipConfig; public void transmitRequest(String ip, Message message) throws SipException, ParseException { transmitRequest(ip, message, null, null, null); } public void transmitRequest(String ip, Message message, SipSubscribe.Event errorEvent) throws SipException, ParseException { transmitRequest(ip, message, errorEvent, null, null); } public void transmitRequest(String ip, Message message, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws SipException { transmitRequest(ip, message, errorEvent, okEvent, null); } public void transmitRequest(String ip, Message message, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent, Long timeout) throws SipException { ViaHeader viaHeader = (ViaHeader) message.getHeader(ViaHeader.NAME); String transport = "UDP"; if (viaHeader == null) { log.warn("[消息头缺失]: ViaHeader, 使用默认的UDP方式处理数据"); } else { transport = viaHeader.getTransport(); } if (message.getHeader(UserAgentHeader.NAME) == null) { try { message.addHeader(SipUtils.createUserAgentHeader(gitUtil)); } catch (ParseException e) { log.error("添加UserAgentHeader失败", e); } } CallIdHeader callIdHeader = (CallIdHeader) message.getHeader(CallIdHeader.NAME); CSeqHeader cSeqHeader = (CSeqHeader) message.getHeader(CSeqHeader.NAME); String key = callIdHeader.getCallId() + cSeqHeader.getSeqNumber(); if (okEvent != null || errorEvent != null) { FromHeader fromHeader = (FromHeader) message.getHeader(FromHeader.NAME); SipEvent sipEvent = SipEvent.getInstance(key, eventResult -> { sipSubscribe.removeSubscribe(key); if(okEvent != null) { okEvent.response(eventResult); } }, (eventResult -> { sipSubscribe.removeSubscribe(key); if (errorEvent != null) { errorEvent.response(eventResult); } }), timeout == null ? sipConfig.getTimeout() : timeout); SipTransactionInfo sipTransactionInfo = new SipTransactionInfo(); sipTransactionInfo.setFromTag(fromHeader.getTag()); sipTransactionInfo.setCallId(callIdHeader.getCallId()); if (message instanceof SIPResponse) { SIPResponse response = (SIPResponse) message; sipTransactionInfo.setToTag(response.getToHeader().getTag()); sipTransactionInfo.setViaBranch(response.getTopmostViaHeader().getBranch()); }else if (message instanceof SIPRequest) { SIPRequest request = (SIPRequest) message; sipTransactionInfo.setViaBranch(request.getTopmostViaHeader().getBranch()); SipUri sipUri = (SipUri)request.getRequestLine().getUri(); sipTransactionInfo.setUser(sipUri.getUser()); } ExpiresHeader expiresHeader = (ExpiresHeader) message.getHeader(ExpiresHeader.NAME); if (expiresHeader != null) { sipTransactionInfo.setExpires(expiresHeader.getExpires()); } sipEvent.setSipTransactionInfo(sipTransactionInfo); sipSubscribe.addSubscribe(key, sipEvent); } try { if ("TCP".equals(transport)) { SipProviderImpl tcpSipProvider = sipLayer.getTcpSipProvider(ip); if (tcpSipProvider == null) { log.error("[发送信息失败] 未找到tcp://{}的监听信息", ip); return; } if (message instanceof Request) { tcpSipProvider.sendRequest((Request) message); } else if (message instanceof Response) { tcpSipProvider.sendResponse((Response) message); } } else if ("UDP".equals(transport)) { SipProviderImpl sipProvider = sipLayer.getUdpSipProvider(ip); if (sipProvider == null) { log.error("[发送信息失败] 未找到udp://{}的监听信息", ip); return; } if (message instanceof Request) { sipProvider.sendRequest((Request) message); } else if (message instanceof Response) { sipProvider.sendResponse((Response) message); } } }catch (SipException e) { sipSubscribe.removeSubscribe(key); throw e; } } public CallIdHeader getNewCallIdHeader(String ip, String transport) { if (ObjectUtils.isEmpty(transport)) { return sipLayer.getUdpSipProvider() != null ? sipLayer.getUdpSipProvider().getNewCallId() : sipLayer.getTcpSipProvider().getNewCallId(); } SipProviderImpl sipProvider; if (ObjectUtils.isEmpty(ip)) { sipProvider = transport.equalsIgnoreCase("TCP") ? sipLayer.getTcpSipProvider() : sipLayer.getUdpSipProvider(); } else { sipProvider = transport.equalsIgnoreCase("TCP") ? sipLayer.getTcpSipProvider(ip) : sipLayer.getUdpSipProvider(ip); } if (sipProvider == null) { sipProvider = transport.equalsIgnoreCase("TCP") ? sipLayer.getTcpSipProvider() : sipLayer.getUdpSipProvider(); } if (sipProvider != null) { return sipProvider.getNewCallId(); } else { log.warn("[新建CallIdHeader失败], ip={}, transport={}", ip, transport); return null; } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/DeferredResultHolder.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.callback; import com.genersoft.iot.vmp.vmanager.bean.DeferredResultEx; import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; import org.springframework.web.context.request.async.DeferredResult; import java.util.Collection; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** * @description: 异步请求处理 * @author: swwheihei * @date: 2020年5月8日 下午7:59:05 */ @SuppressWarnings(value = {"rawtypes", "unchecked"}) @Component public class DeferredResultHolder { public static final String CALLBACK_CMD_PLAY = "CALLBACK_PLAY"; public static final String CALLBACK_CMD_PLAYBACK = "CALLBACK_PLAYBACK"; public static final String CALLBACK_CMD_DOWNLOAD = "CALLBACK_DOWNLOAD"; public static final String UPLOAD_FILE_CHANNEL = "UPLOAD_FILE_CHANNEL"; public static final String CALLBACK_CMD_MOBILE_POSITION = "CALLBACK_CMD_MOBILE_POSITION"; public static final String CALLBACK_CMD_SNAP= "CALLBACK_SNAP"; private Map> map = new ConcurrentHashMap<>(); public void put(String key, String id, DeferredResultEx result) { Map deferredResultMap = map.get(key); if (deferredResultMap == null) { deferredResultMap = new ConcurrentHashMap<>(); map.put(key, deferredResultMap); } deferredResultMap.put(id, result); } public void put(String key, String id, DeferredResult result) { Map deferredResultMap = map.computeIfAbsent(key, k -> new ConcurrentHashMap<>()); deferredResultMap.put(id, new DeferredResultEx(result)); } public DeferredResultEx get(String key, String id) { Map deferredResultMap = map.get(key); if (deferredResultMap == null || ObjectUtils.isEmpty(id)) { return null; } return deferredResultMap.get(id); } public Collection getAllByKey(String key) { Map deferredResultMap = map.get(key); if (deferredResultMap == null) { return null; } return deferredResultMap.values(); } public boolean exist(String key, String id){ if (key == null) { return false; } Map deferredResultMap = map.get(key); if (id == null) { return deferredResultMap != null; }else { return deferredResultMap != null && deferredResultMap.get(id) != null; } } /** * 释放单个请求 * @param msg */ public void invokeResult(RequestMessage msg) { Map deferredResultMap = map.get(msg.getKey()); if (deferredResultMap == null) { return; } DeferredResultEx result = deferredResultMap.get(msg.getId()); if (result == null) { return; } result.getDeferredResult().setResult(msg.getData()); deferredResultMap.remove(msg.getId()); if (deferredResultMap.size() == 0) { map.remove(msg.getKey()); } } /** * 释放所有的请求 * @param msg */ public void invokeAllResult(RequestMessage msg) { Map deferredResultMap = map.get(msg.getKey()); if (deferredResultMap == null) { return; } synchronized (this) { deferredResultMap = map.get(msg.getKey()); if (deferredResultMap == null) { return; } Set ids = deferredResultMap.keySet(); for (String id : ids) { DeferredResultEx result = deferredResultMap.get(id); if (result == null) { return; } if (result.getFilter() != null) { Object handler = result.getFilter().handler(msg.getData()); result.getDeferredResult().setResult(handler); }else { result.getDeferredResult().setResult(msg.getData()); } } map.remove(msg.getKey()); } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/RequestMessage.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.callback; import lombok.Data; /** * @description: 请求信息定义 * @author: swwheihei * @date: 2020年5月8日 下午1:09:18 */ @Data public class RequestMessage { private String id; private String key; private Object data; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.cmd; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm; import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; import com.genersoft.iot.vmp.media.event.hook.HookSubscribe; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.service.bean.SSRCInfo; import gov.nist.javax.sip.message.SIPRequest; import javax.sip.InvalidArgumentException; import javax.sip.SipException; import java.text.ParseException; import java.util.List; /** * @description:设备能力接口,用于定义设备的控制、查询能力 * @author: swwheihei * @date: 2020年5月3日 下午9:16:34 */ public interface ISIPCommander { /** * 云台控制,支持方向与缩放控制 * * @param device 控制设备 * @param channelId 预览通道 * @param leftRight 镜头左移右移 0:停止 1:左移 2:右移 * @param upDown 镜头上移下移 0:停止 1:上移 2:下移 * @param inOut 镜头放大缩小 0:停止 1:缩小 2:放大 * @param moveSpeed 镜头移动速度 * @param zoomSpeed 镜头缩放速度 */ void ptzCmd(Device device,String channelId,int leftRight, int upDown, int inOut, int moveSpeed, int zoomSpeed) throws InvalidArgumentException, SipException, ParseException; /** * 前端控制,包括PTZ指令、FI指令、预置位指令、巡航指令、扫描指令和辅助开关指令 * * @param device 控制设备 * @param channelId 预览通道 * @param cmdCode 指令码 * @param parameter1 数据1 * @param parameter2 数据2 * @param combineCode2 组合码2 */ void frontEndCmd(Device device, String channelId, int cmdCode, int parameter1, int parameter2, int combineCode2) throws SipException, InvalidArgumentException, ParseException; /** * 前端控制指令(用于转发上级指令) * @param device 控制设备 * @param channelId 预览通道 * @param cmdString 前端控制指令串 */ void fronEndCmd(Device device, String channelId, String cmdString, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException; /** * 请求预览视频流 * @param device 视频设备 * @param channel 预览通道 */ void playStreamCmd(MediaServer mediaServerItem, SSRCInfo ssrcInfo, Device device, DeviceChannel channel, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent, Long timeout) throws InvalidArgumentException, SipException, ParseException; /** * 请求回放视频流 * * @param device 视频设备 * @param channel 预览通道 * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss */ void playbackStreamCmd(MediaServer mediaServerItem, SSRCInfo ssrcInf, Device device, DeviceChannel channel, String startTime, String endTime, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent, Long timeout) throws InvalidArgumentException, SipException, ParseException; /** * 请求历史媒体下载 * * @param device 视频设备 * @param channel 预览通道 * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss * @param downloadSpeed 下载倍速参数 */ void downloadStreamCmd(MediaServer mediaServerItem, SSRCInfo ssrcInfo, Device device, DeviceChannel channel, String startTime, String endTime, int downloadSpeed, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent, Long timeout) throws InvalidArgumentException, SipException, ParseException; /** * 视频流停止 */ void streamByeCmd(Device device, String channelId, String app, String stream, String callId, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException; void talkStreamCmd(MediaServer mediaServerItem, SendRtpInfo sendRtpItem, Device device, DeviceChannel channelId, String callId, HookSubscribe.Event event, HookSubscribe.Event eventForPush, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent, Long timeout) throws InvalidArgumentException, SipException, ParseException; void streamByeCmd(Device device, String channelId, SipTransactionInfo sipTransactionInfo, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException; /** * 回放暂停 */ void playPauseCmd(Device device, DeviceChannel channel, StreamInfo streamInfo) throws InvalidArgumentException, ParseException, SipException; /** * 回放恢复 */ void playResumeCmd(Device device, DeviceChannel channel, StreamInfo streamInfo) throws InvalidArgumentException, ParseException, SipException; /** * 回放拖动播放 */ void playSeekCmd(Device device, DeviceChannel channel, StreamInfo streamInfo, long seekTime) throws InvalidArgumentException, ParseException, SipException; /** * 回放倍速播放 */ void playSpeedCmd(Device device, DeviceChannel channel, StreamInfo streamInfo, Double speed) throws InvalidArgumentException, ParseException, SipException; /** * 回放控制 * @param device * @param streamInfo * @param content */ void playbackControlCmd(Device device, DeviceChannel channel, StreamInfo streamInfo, String content,SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws SipException, InvalidArgumentException, ParseException; void streamByeCmdForDeviceInvite(Device device, String channelId, SipTransactionInfo sipTransactionInfo, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException; /** * /** * 语音广播 * * @param device 视频设备 */ void audioBroadcastCmd(Device device, String channelId, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException; /** * 音视频录像控制 * * @param device 视频设备 * @param channelId 预览通道 * @param recordCmdStr 录像命令:Record / StopRecord */ void recordCmd(Device device, String channelId, String recordCmdStr, ErrorCallback callback) throws InvalidArgumentException, SipException, ParseException; /** * 远程启动控制命令 * * @param device 视频设备 */ void teleBootCmd(Device device) throws InvalidArgumentException, SipException, ParseException; /** * 报警布防/撤防命令 * * @param device 视频设备 */ void guardCmd(Device device, String guardCmdStr, ErrorCallback callback) throws InvalidArgumentException, SipException, ParseException; /** * 报警复位命令 * * @param device 视频设备 * @param alarmMethod 报警方式(可选) * @param alarmType 报警类型(可选) */ void alarmResetCmd(Device device, String alarmMethod, String alarmType, ErrorCallback callback) throws InvalidArgumentException, SipException, ParseException; /** * 强制关键帧命令,设备收到此命令应立刻发送一个IDR帧 * * @param device 视频设备 * @param channelId 预览通道 */ void iFrameCmd(Device device, String channelId) throws InvalidArgumentException, SipException, ParseException; /** * 看守位控制命令 * */ void homePositionCmd(Device device, String channelId, Boolean enabled, Integer resetTime, Integer presetIndex, ErrorCallback callback) throws InvalidArgumentException, SipException, ParseException; /** * 设备配置命令 * * @param device 视频设备 */ void deviceConfigCmd(Device device); /** * 设备配置命令:basicParam */ void deviceBasicConfigCmd(Device device, BasicParam basicParam, ErrorCallback callback) throws InvalidArgumentException, SipException, ParseException; /** * 查询设备状态 * * @param device 视频设备 */ void deviceStatusQuery(Device device, ErrorCallback callback) throws InvalidArgumentException, SipException, ParseException; /** * 查询设备信息 * * @param device 视频设备 * @param callback * @return */ void deviceInfoQuery(Device device, ErrorCallback callback) throws InvalidArgumentException, SipException, ParseException; /** * 查询目录列表 * * @param device 视频设备 */ void catalogQuery(Device device, int sn, SipSubscribe.Event errorEvent) throws SipException, InvalidArgumentException, ParseException; /** * 查询录像信息 * * @param device 视频设备 * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss * @param sn */ void recordInfoQuery(Device device, String channelId, String startTime, String endTime, int sn, Integer Secrecy, String type, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException; /** * 查询报警信息 * * @param device 视频设备 * @param startPriority 报警起始级别(可选) * @param endPriority 报警终止级别(可选) * @param alarmMethod 报警方式条件(可选) * @param alarmType 报警类型 * @param startTime 报警发生起始时间(可选) * @param endTime 报警发生终止时间(可选) * @return true = 命令发送成功 */ void alarmInfoQuery(Device device, String startPriority, String endPriority, String alarmMethod, String alarmType, String startTime, String endTime, ErrorCallback callback) throws InvalidArgumentException, SipException, ParseException; /** * 查询设备配置 * * @param device 视频设备 * @param channelId 通道编码(可选) * @param configType 配置类型: */ void deviceConfigQuery(Device device, String channelId, String configType, ErrorCallback callback) throws InvalidArgumentException, SipException, ParseException; /** * 查询设备预置位置 * * @param device 视频设备 */ void presetQuery(Device device, String channelId, ErrorCallback> callback) throws InvalidArgumentException, SipException, ParseException; /** * 查询移动设备位置数据 * * @param device 视频设备 */ void mobilePositionQuery(Device device, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException; /** * 订阅、取消订阅移动位置 * * @param device 视频设备 * @return true = 命令发送成功 */ SIPRequest mobilePositionSubscribe(Device device, SipTransactionInfo transactionInfo, SipSubscribe.Event okEvent , SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException; /** * 订阅、取消订阅报警信息 * @param device 视频设备 * @param expires 订阅过期时间(0 = 取消订阅) * @param startPriority 报警起始级别(可选) * @param endPriority 报警终止级别(可选) * @param startTime 报警发生起始时间(可选) * @param endTime 报警发生终止时间(可选) * @return true = 命令发送成功 */ void alarmSubscribe(Device device, int expires, String startPriority, String endPriority, String alarmMethod, String startTime, String endTime) throws InvalidArgumentException, SipException, ParseException; /** * 订阅、取消订阅目录信息 * @param device 视频设备 * @return true = 命令发送成功 */ SIPRequest catalogSubscribe(Device device, SipTransactionInfo transactionInfo, SipSubscribe.Event okEvent ,SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException; /** * 拉框控制命令 * * @param device 控制设备 * @param channelId 通道id * @param cmdString 前端控制指令串 */ void dragZoomCmd(Device device, String channelId, String cmdString, ErrorCallback callback) throws InvalidArgumentException, SipException, ParseException; void playbackControlCmd(Device device, DeviceChannel channel, String stream, String content, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws SipException, InvalidArgumentException, ParseException; /** * 向设备发送报警NOTIFY消息, 用于互联结构下,此时将设备当成一个平级平台看待 * @param device 设备 * @param deviceAlarm 报警信息信息 * @return */ void sendAlarmMessage(Device device, DeviceAlarm deviceAlarm) throws InvalidArgumentException, SipException, ParseException; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommanderForPlatform.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.cmd; import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; import com.genersoft.iot.vmp.media.event.hook.HookSubscribe; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.service.bean.GPSMsgInfo; import com.genersoft.iot.vmp.service.bean.SSRCInfo; import javax.sip.InvalidArgumentException; import javax.sip.SipException; import javax.sip.header.WWWAuthenticateHeader; import java.text.ParseException; import java.util.List; public interface ISIPCommanderForPlatform { /** * 向上级平台注册 * * @param parentPlatform * @return */ void register(Platform parentPlatform, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws InvalidArgumentException, ParseException, SipException; void register(Platform parentPlatform, SipTransactionInfo sipTransactionInfo, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws InvalidArgumentException, ParseException, SipException; void register(Platform parentPlatform, SipTransactionInfo sipTransactionInfo, WWWAuthenticateHeader www, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent, boolean isRegister) throws SipException, InvalidArgumentException, ParseException; /** * 向上级平台注销 * * @param parentPlatform * @return */ void unregister(Platform parentPlatform, SipTransactionInfo sipTransactionInfo, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws InvalidArgumentException, ParseException, SipException; /** * 向上级平发送心跳信息 * * @param parentPlatform * @return callId(作为接受回复的判定) */ String keepalive(Platform parentPlatform, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws SipException, InvalidArgumentException, ParseException; /** * 向上级回复通道信息 * * @param channel 通道信息 * @param parentPlatform 平台信息 * @param sn * @param fromTag * @param size * @return */ void catalogQuery(CommonGBChannel channel, Platform parentPlatform, String sn, String fromTag, int size) throws SipException, InvalidArgumentException, ParseException; void catalogQuery(List channels, Platform parentPlatform, String sn, String fromTag) throws InvalidArgumentException, ParseException, SipException; /** * 向上级回复DeviceInfo查询信息 * * @param parentPlatform 平台信息 * @param sn SN * @param fromTag FROM头的tag信息 * @return */ void deviceInfoResponse(Platform parentPlatform, Device device, String sn, String fromTag) throws SipException, InvalidArgumentException, ParseException; /** * 向上级回复DeviceStatus查询信息 * * @param parentPlatform 平台信息 * @param sn * @param fromTag * @return */ void deviceStatusResponse(Platform parentPlatform, String channelId, String sn, String fromTag, Boolean status) throws SipException, InvalidArgumentException, ParseException; /** * 向上级回复移动位置订阅消息 * * @param parentPlatform 平台信息 * @param gpsMsgInfo GPS信息 * @param subscribeInfo 订阅相关的信息 * @return */ void sendNotifyMobilePosition(Platform parentPlatform, GPSMsgInfo gpsMsgInfo, CommonGBChannel channel, SubscribeInfo subscribeInfo) throws InvalidArgumentException, ParseException, NoSuchFieldException, SipException, IllegalAccessException; /** * 向上级回复报警消息 * * @param parentPlatform 平台信息 * @param deviceAlarm 报警信息信息 * @return */ void sendAlarmMessage(Platform parentPlatform, DeviceAlarm deviceAlarm) throws SipException, InvalidArgumentException, ParseException; /** * 回复catalog事件-增加/更新 * * @param parentPlatform * @param deviceChannels */ void sendNotifyForCatalogAddOrUpdate(String type, Platform parentPlatform, List deviceChannels, SubscribeInfo subscribeInfo, Integer index) throws InvalidArgumentException, ParseException, NoSuchFieldException, SipException, IllegalAccessException; /** * 回复catalog事件-删除 * * @param parentPlatform * @param deviceChannels */ void sendNotifyForCatalogOther(String type, Platform parentPlatform, List deviceChannels, SubscribeInfo subscribeInfo, Integer index) throws InvalidArgumentException, ParseException, NoSuchFieldException, SipException, IllegalAccessException; /** * 回复recordInfo * * @param deviceChannel 通道信息 * @param parentPlatform 平台信息 * @param fromTag fromTag * @param recordInfo 录像信息 */ void recordInfo(CommonGBChannel deviceChannel, Platform parentPlatform, String fromTag, RecordInfo recordInfo) throws SipException, InvalidArgumentException, ParseException; /** * 录像播放推送完成时发送MediaStatus消息 * * @param platform * @param sendRtpItem * @return */ void sendMediaStatusNotify(Platform platform, SendRtpInfo sendRtpItem, CommonGBChannel channel) throws SipException, InvalidArgumentException, ParseException; void streamByeCmd(Platform platform, SendRtpInfo sendRtpItem, CommonGBChannel channel) throws SipException, InvalidArgumentException, ParseException; void streamByeCmd(Platform platform, CommonGBChannel channel, String app, String stream, String callId, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException; void broadcastInviteCmd(Platform platform, CommonGBChannel channel, String sourceId, MediaServer mediaServerItem, SSRCInfo ssrcInfo, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws ParseException, SipException, InvalidArgumentException; void broadcastResultCmd(Platform platform, CommonGBChannel deviceChannel, String sn, boolean result, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderPlarformProvider.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.cmd; import com.genersoft.iot.vmp.conf.SipConfig; import com.genersoft.iot.vmp.gb28181.SipLayer; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.utils.SipUtils; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.utils.GitUtil; import com.genersoft.iot.vmp.utils.IpPortUtil; import gov.nist.javax.sip.message.MessageFactoryImpl; import gov.nist.javax.sip.message.SIPRequest; import jakarta.validation.constraints.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.DigestUtils; import javax.sip.InvalidArgumentException; import javax.sip.PeerUnavailableException; import javax.sip.SipFactory; import javax.sip.address.Address; import javax.sip.address.SipURI; import javax.sip.header.*; import javax.sip.message.Request; import java.text.ParseException; import java.util.ArrayList; import java.util.UUID; /** * @description: 平台命令request创造器 TODO 冗余代码太多待优化 * @author: panll * @date: 2020年5月6日 上午9:29:02 */ @Component public class SIPRequestHeaderPlarformProvider { @Autowired private SipConfig sipConfig; @Autowired private SipLayer sipLayer; @Autowired private GitUtil gitUtil; @Autowired private IRedisCatchStorage redisCatchStorage; public Request createRegisterRequest(@NotNull Platform parentPlatform, long CSeq, String fromTag, String toTag, CallIdHeader callIdHeader, int expires) throws ParseException, InvalidArgumentException, PeerUnavailableException { Request request = null; String sipAddress = parentPlatform.getDeviceIp() + ":" + parentPlatform.getDevicePort(); //请求行 SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), parentPlatform.getServerIp() + ":" + parentPlatform.getServerPort()); //via ArrayList viaHeaders = new ArrayList(); ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(), parentPlatform.getDevicePort(), parentPlatform.getTransport(), SipUtils.getNewViaTag()); viaHeader.setRPort(); viaHeaders.add(viaHeader); //from SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getDeviceGBId(), sipConfig.getDomain()); Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI); FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, fromTag); //to SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getDeviceGBId(), sipConfig.getDomain()); Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI); ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress,toTag); //Forwards MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70); //ceq CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(CSeq, Request.REGISTER); request = SipFactory.getInstance().createMessageFactory().createRequest(requestLine, Request.REGISTER, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards); Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory() .createSipURI(parentPlatform.getDeviceGBId(), sipAddress)); request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress)); ExpiresHeader expiresHeader = SipFactory.getInstance().createHeaderFactory().createExpiresHeader(expires); request.addHeader(expiresHeader); request.addHeader(SipUtils.createUserAgentHeader(gitUtil)); return request; } public Request createRegisterRequest(@NotNull Platform parentPlatform, String fromTag, String toTag, WWWAuthenticateHeader www , CallIdHeader callIdHeader, int expires) throws ParseException, PeerUnavailableException, InvalidArgumentException { Request registerRequest = createRegisterRequest(parentPlatform, redisCatchStorage.getCSEQ(), fromTag, toTag, callIdHeader, expires); SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), IpPortUtil.concatenateIpAndPort(parentPlatform.getServerIp(), String.valueOf(parentPlatform.getServerPort()))); if (www == null) { AuthorizationHeader authorizationHeader = SipFactory.getInstance().createHeaderFactory().createAuthorizationHeader("Digest"); String username = parentPlatform.getUsername(); if ( username == null || username.isEmpty()) { authorizationHeader.setUsername(parentPlatform.getDeviceGBId()); } else { authorizationHeader.setUsername(username); } authorizationHeader.setURI(requestURI); authorizationHeader.setAlgorithm("MD5"); registerRequest.addHeader(authorizationHeader); return registerRequest; } String realm = www.getRealm(); String nonce = www.getNonce(); String scheme = www.getScheme(); // 参考 https://blog.csdn.net/y673533511/article/details/88388138 // qop 保护质量 包含auth(默认的)和auth-int(增加了报文完整性检测)两种策略 String qop = www.getQop(); String cNonce = null; String nc = "00000001"; if (qop != null) { if ("auth".equalsIgnoreCase(qop)) { // 客户端随机数,这是一个不透明的字符串值,由客户端提供,并且客户端和服务器都会使用,以避免用明文文本。 // 这使得双方都可以查验对方的身份,并对消息的完整性提供一些保护 cNonce = UUID.randomUUID().toString(); }else if ("auth-int".equalsIgnoreCase(qop)){ // TODO } } String HA1 = DigestUtils.md5DigestAsHex((parentPlatform.getDeviceGBId() + ":" + realm + ":" + parentPlatform.getPassword()).getBytes()); String HA2=DigestUtils.md5DigestAsHex((Request.REGISTER + ":" + requestURI.toString()).getBytes()); StringBuffer reStr = new StringBuffer(200); reStr.append(HA1); reStr.append(":"); reStr.append(nonce); reStr.append(":"); if (qop != null) { reStr.append(nc); reStr.append(":"); reStr.append(cNonce); reStr.append(":"); reStr.append(qop); reStr.append(":"); } reStr.append(HA2); String RESPONSE = DigestUtils.md5DigestAsHex(reStr.toString().getBytes()); AuthorizationHeader authorizationHeader = SipFactory.getInstance().createHeaderFactory().createAuthorizationHeader(scheme); authorizationHeader.setUsername(parentPlatform.getDeviceGBId()); authorizationHeader.setRealm(realm); authorizationHeader.setNonce(nonce); authorizationHeader.setURI(requestURI); authorizationHeader.setResponse(RESPONSE); authorizationHeader.setAlgorithm("MD5"); if (qop != null) { authorizationHeader.setQop(qop); authorizationHeader.setCNonce(cNonce); authorizationHeader.setNonceCount(1); } registerRequest.addHeader(authorizationHeader); return registerRequest; } public Request createMessageRequest(Platform parentPlatform, String content, SendRtpInfo sendRtpItem) throws PeerUnavailableException, ParseException, InvalidArgumentException { CallIdHeader callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(sendRtpItem.getCallId()); callIdHeader.setCallId(sendRtpItem.getCallId()); return createMessageRequest(parentPlatform, content, sendRtpItem.getToTag(), SipUtils.getNewViaTag(), sendRtpItem.getFromTag(), callIdHeader); } public Request createMessageRequest(Platform parentPlatform, String content, String fromTag, String viaTag, CallIdHeader callIdHeader) throws PeerUnavailableException, ParseException, InvalidArgumentException { return createMessageRequest(parentPlatform, content, fromTag, viaTag, null, callIdHeader); } public Request createMessageRequest(Platform parentPlatform, String content, String fromTag, String viaTag, String toTag, CallIdHeader callIdHeader) throws PeerUnavailableException, ParseException, InvalidArgumentException { Request request = null; String serverAddress = parentPlatform.getServerIp()+ ":" + parentPlatform.getServerPort(); // sipuri SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), serverAddress); // via ArrayList viaHeaders = new ArrayList(); ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(), parentPlatform.getDevicePort(), parentPlatform.getTransport(), viaTag); viaHeader.setRPort(); viaHeaders.add(viaHeader); // from // SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getDeviceGBId(), parentPlatform.getDeviceIp() + ":" + parentPlatform.getDeviceIp()); SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getDeviceGBId(), sipConfig.getDomain()); Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI); FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, fromTag); // to SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), serverAddress); Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI); ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress, toTag); // Forwards MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70); // ceq CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.MESSAGE); MessageFactoryImpl messageFactory = (MessageFactoryImpl) SipFactory.getInstance().createMessageFactory(); // 设置编码, 防止中文乱码 messageFactory.setDefaultContentEncodingCharset(parentPlatform.getCharacterSet()); request = messageFactory.createRequest(requestURI, Request.MESSAGE, callIdHeader, cSeqHeader, fromHeader, toHeader, viaHeaders, maxForwards); request.addHeader(SipUtils.createUserAgentHeader(gitUtil)); ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml"); request.setContent(content, contentTypeHeader); return request; } public SIPRequest createNotifyRequest(Platform parentPlatform, String content, SubscribeInfo subscribeInfo) throws PeerUnavailableException, ParseException, InvalidArgumentException { SIPRequest request = null; // sipuri SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), IpPortUtil.concatenateIpAndPort(parentPlatform.getServerIp(), String.valueOf(parentPlatform.getServerPort()))); // via ArrayList viaHeaders = new ArrayList<>(); ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(), parentPlatform.getDevicePort(), parentPlatform.getTransport(), SipUtils.getNewViaTag()); viaHeader.setRPort(); viaHeaders.add(viaHeader); // from SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getDeviceGBId(), parentPlatform.getDeviceIp() + ":" + parentPlatform.getDevicePort()); Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI); FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, subscribeInfo.getTransactionInfo() != null ? subscribeInfo.getTransactionInfo() .getToTag(): subscribeInfo.getSimulatedToTag()); // to SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), parentPlatform.getServerGBDomain()); Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI); ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress, subscribeInfo.getTransactionInfo() != null ?subscribeInfo.getTransactionInfo().getFromTag(): subscribeInfo.getSimulatedFromTag()); // Forwards MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70); // ceq CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.NOTIFY); MessageFactoryImpl messageFactory = (MessageFactoryImpl) SipFactory.getInstance().createMessageFactory(); // 设置编码, 防止中文乱码 messageFactory.setDefaultContentEncodingCharset("gb2312"); CallIdHeader callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(subscribeInfo.getTransactionInfo() != null ? subscribeInfo.getTransactionInfo().getCallId(): subscribeInfo.getSimulatedCallId()); request = (SIPRequest) messageFactory.createRequest(requestURI, Request.NOTIFY, callIdHeader, cSeqHeader, fromHeader, toHeader, viaHeaders, maxForwards); request.addHeader(SipUtils.createUserAgentHeader(gitUtil)); EventHeader event = SipFactory.getInstance().createHeaderFactory().createEventHeader(subscribeInfo.getEventType()); if (subscribeInfo.getEventId() != null) { event.setEventId(subscribeInfo.getEventId()); } request.addHeader(event); SubscriptionStateHeader active = SipFactory.getInstance().createHeaderFactory().createSubscriptionStateHeader("active"); request.setHeader(active); String sipAddress = parentPlatform.getDeviceIp() + ":" + parentPlatform.getDevicePort(); Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory() .createSipURI(parentPlatform.getDeviceGBId(), sipAddress)); request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress)); ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml"); request.setContent(content, contentTypeHeader); return request; } public SIPRequest createByeRequest(Platform platform, SendRtpInfo sendRtpItem, CommonGBChannel channel) throws PeerUnavailableException, ParseException, InvalidArgumentException { if (sendRtpItem == null ) { return null; } SIPRequest request = null; // sipuri SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(platform.getServerGBId(), IpPortUtil.concatenateIpAndPort(platform.getServerIp(), String.valueOf(platform.getServerPort()))); // via ArrayList viaHeaders = new ArrayList<>(); ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(platform.getDeviceIp(), platform.getDevicePort(), platform.getTransport(), SipUtils.getNewViaTag()); viaHeader.setRPort(); viaHeaders.add(viaHeader); // from SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(channel.getGbDeviceId(), platform.getDeviceIp() + ":" + platform.getDevicePort()); Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI); FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, sendRtpItem.getToTag()); // to SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(platform.getServerGBId(), platform.getServerGBDomain()); Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI); ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress, sendRtpItem.getFromTag()); // Forwards MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70); // ceq CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.BYE); CallIdHeader callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(sendRtpItem.getCallId()); request = (SIPRequest) SipFactory.getInstance().createMessageFactory().createRequest(requestURI, Request.BYE, callIdHeader, cSeqHeader, fromHeader, toHeader, viaHeaders, maxForwards); request.addHeader(SipUtils.createUserAgentHeader(gitUtil)); String sipAddress = platform.getDeviceIp() + ":" + platform.getDevicePort(); Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory() .createSipURI(platform.getDeviceGBId(), sipAddress)); request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress)); return request; } public Request createInviteRequest(Platform platform,String sourceId, String channelId, String content, String viaTag, String fromTag, String ssrc, CallIdHeader callIdHeader) throws PeerUnavailableException, ParseException, InvalidArgumentException { Request request = null; //请求行 String platformHostAddress = platform.getServerIp() + ":" + platform.getServerPort(); String localHostAddress = sipLayer.getLocalIp(platform.getDeviceIp())+":"+ platform.getDevicePort(); SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(sourceId, platformHostAddress); //via ArrayList viaHeaders = new ArrayList(); ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(platform.getDeviceIp()), platform.getDevicePort(), platform.getTransport(), viaTag); viaHeader.setRPort(); viaHeaders.add(viaHeader); //from SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(platform.getDeviceGBId(), sipConfig.getDomain()); Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI); FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, fromTag); //必须要有标记,否则无法创建会话,无法回应ack //to SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sourceId, platformHostAddress); Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI); ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress,null); //Forwards MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70); //ceq CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.INVITE); request = SipFactory.getInstance().createMessageFactory().createRequest(requestLine, Request.INVITE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards); request.addHeader(SipUtils.createUserAgentHeader(gitUtil)); Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(),localHostAddress)); request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress)); // Subject SubjectHeader subjectHeader = SipFactory.getInstance().createHeaderFactory().createSubjectHeader(String.format("%s:%s,%s:%s", sourceId, ssrc, channelId, 0)); request.addHeader(subjectHeader); ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("APPLICATION", "SDP"); request.setContent(content, contentTypeHeader); return request; } public Request createByteRequest(Platform platform, String channelId, SipTransactionInfo transactionInfo) throws PeerUnavailableException, ParseException, InvalidArgumentException { String deviceHostAddress = platform.getDeviceIp() + ":" + platform.getDevicePort(); Request request = null; SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, deviceHostAddress); // via ArrayList viaHeaders = new ArrayList(); ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(platform.getDeviceIp()), platform.getDevicePort(), platform.getTransport(), SipUtils.getNewViaTag()); viaHeaders.add(viaHeader); //from SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(),sipConfig.getDomain()); Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI); FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, transactionInfo.isAsSender()?transactionInfo.getFromTag():transactionInfo.getToTag()); //to SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, deviceHostAddress); Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI); ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress,transactionInfo.isAsSender()?transactionInfo.getToTag():transactionInfo.getFromTag()); //Forwards MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70); //ceq CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.BYE); CallIdHeader callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(transactionInfo.getCallId()); request = SipFactory.getInstance().createMessageFactory().createRequest(requestLine, Request.BYE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards); request.addHeader(SipUtils.createUserAgentHeader(gitUtil)); Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), IpPortUtil.concatenateIpAndPort(sipLayer.getLocalIp(platform.getDeviceIp()), String.valueOf(platform.getDevicePort())))); request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress)); request.addHeader(SipUtils.createUserAgentHeader(gitUtil)); return request; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.cmd; import com.genersoft.iot.vmp.conf.SipConfig; import com.genersoft.iot.vmp.gb28181.SipLayer; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo; import com.genersoft.iot.vmp.gb28181.utils.SipUtils; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.utils.GitUtil; import com.genersoft.iot.vmp.utils.IpPortUtil; import gov.nist.javax.sip.message.SIPRequest; import gov.nist.javax.sip.message.SIPResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.sip.InvalidArgumentException; import javax.sip.PeerUnavailableException; import javax.sip.SipException; import javax.sip.SipFactory; import javax.sip.address.Address; import javax.sip.address.SipURI; import javax.sip.header.*; import javax.sip.message.Request; import java.text.ParseException; import java.util.ArrayList; /** * @description:摄像头命令request创造器 TODO 冗余代码太多待优化 * @author: swwheihei * @date: 2020年5月6日 上午9:29:02 */ @Component public class SIPRequestHeaderProvider { @Autowired private SipConfig sipConfig; @Autowired private SipLayer sipLayer; @Autowired private GitUtil gitUtil; @Autowired private IRedisCatchStorage redisCatchStorage; public Request createMessageRequest(Device device, String content, String viaTag, String fromTag, String toTag, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException { Request request = null; // sipuri SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress()); // via ArrayList viaHeaders = new ArrayList<>(); ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), viaTag); viaHeader.setRPort(); viaHeaders.add(viaHeader); // from SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain()); Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI); FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, fromTag); // to SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress()); Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI); ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress, toTag); // Forwards MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70); // ceq CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.MESSAGE); request = SipFactory.getInstance().createMessageFactory().createRequest(requestURI, Request.MESSAGE, callIdHeader, cSeqHeader, fromHeader, toHeader, viaHeaders, maxForwards); request.addHeader(SipUtils.createUserAgentHeader(gitUtil)); ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml"); request.setContent(content, contentTypeHeader); return request; } public Request createInviteRequest(Device device, String channelId, String content, String viaTag, String fromTag, String toTag, String ssrc, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException { Request request = null; //请求行 SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress()); //via ArrayList viaHeaders = new ArrayList(); HeaderFactory headerFactory = SipFactory.getInstance().createHeaderFactory(); ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), viaTag); viaHeader.setRPort(); viaHeaders.add(viaHeader); //from SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain()); Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI); FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, fromTag); //必须要有标记,否则无法创建会话,无法回应ack //to SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress()); Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI); ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress,null); //Forwards MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70); //ceq CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.INVITE); request = SipFactory.getInstance().createMessageFactory().createRequest(requestLine, Request.INVITE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards); request.addHeader(SipUtils.createUserAgentHeader(gitUtil)); Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), IpPortUtil.concatenateIpAndPort(sipLayer.getLocalIp(device.getLocalIp()), String.valueOf(sipConfig.getPort())))); // Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), device.getHost().getIp()+":"+device.getHost().getPort())); request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress)); // Subject SubjectHeader subjectHeader = SipFactory.getInstance().createHeaderFactory().createSubjectHeader(String.format("%s:%s,%s:%s", channelId, ssrc, sipConfig.getId(), 0)); request.addHeader(subjectHeader); ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("APPLICATION", "SDP"); request.setContent(content, contentTypeHeader); return request; } public Request createPlaybackInviteRequest(Device device, String channelId, String content, String viaTag, String fromTag, String toTag, CallIdHeader callIdHeader, String ssrc) throws ParseException, InvalidArgumentException, PeerUnavailableException { Request request = null; //请求行 SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress()); // via ArrayList viaHeaders = new ArrayList(); ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), viaTag); viaHeader.setRPort(); viaHeaders.add(viaHeader); //from SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain()); Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI); FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, fromTag); //必须要有标记,否则无法创建会话,无法回应ack //to SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress()); Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI); ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress,null); //Forwards MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70); //ceq CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.INVITE); request = SipFactory.getInstance().createMessageFactory().createRequest(requestLine, Request.INVITE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards); Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), IpPortUtil.concatenateIpAndPort(sipLayer.getLocalIp(device.getLocalIp()), String.valueOf(sipConfig.getPort())))); // Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), device.getHost().getIp()+":"+device.getHost().getPort())); request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress)); request.addHeader(SipUtils.createUserAgentHeader(gitUtil)); // Subject SubjectHeader subjectHeader = SipFactory.getInstance().createHeaderFactory().createSubjectHeader(String.format("%s:%s,%s:%s", channelId, ssrc, sipConfig.getId(), 0)); request.addHeader(subjectHeader); ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("APPLICATION", "SDP"); request.setContent(content, contentTypeHeader); return request; } public Request createByteRequest(Device device, String channelId, SipTransactionInfo transactionInfo) throws ParseException, InvalidArgumentException, PeerUnavailableException { Request request = null; //请求行 SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress()); // SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress()); // via ArrayList viaHeaders = new ArrayList(); // ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), transactionInfo.getViaBranch()); ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), SipUtils.getNewViaTag()); // viaHeader.setRPort(); viaHeaders.add(viaHeader); //from // SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(),sipConfig.getDomain()); SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), IpPortUtil.concatenateIpAndPort(sipLayer.getLocalIp(device.getLocalIp()), String.valueOf(sipConfig.getPort()))); Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI); FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, transactionInfo.getFromTag()); //to SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId,device.getHostAddress()); // SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(device.getDeviceId(),device.getHostAddress()); Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI); ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress, transactionInfo.getToTag()); //Forwards MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70); //ceq CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.BYE); CallIdHeader callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(transactionInfo.getCallId()); request = SipFactory.getInstance().createMessageFactory().createRequest(requestLine, Request.BYE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards); request.addHeader(SipUtils.createUserAgentHeader(gitUtil)); Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), IpPortUtil.concatenateIpAndPort(sipLayer.getLocalIp(device.getLocalIp()), String.valueOf(sipConfig.getPort())))); request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress)); request.addHeader(SipUtils.createUserAgentHeader(gitUtil)); return request; } public Request createByteRequestForDeviceInvite(Device device, String channelId, SipTransactionInfo transactionInfo) throws ParseException, InvalidArgumentException, PeerUnavailableException { Request request = null; //请求行 SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress()); // via ArrayList viaHeaders = new ArrayList(); ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), SipUtils.getNewViaTag()); viaHeaders.add(viaHeader); //from SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(),sipConfig.getDomain()); Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI); FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, transactionInfo.getToTag()); //to SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId,device.getHostAddress()); Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI); ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress, transactionInfo.getFromTag()); //Forwards MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70); //ceq CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.BYE); CallIdHeader callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(transactionInfo.getCallId()); request = SipFactory.getInstance().createMessageFactory().createRequest(requestLine, Request.BYE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards); request.addHeader(SipUtils.createUserAgentHeader(gitUtil)); Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), IpPortUtil.concatenateIpAndPort(sipLayer.getLocalIp(device.getLocalIp()), String.valueOf(sipConfig.getPort())))); request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress)); request.addHeader(SipUtils.createUserAgentHeader(gitUtil)); return request; } public Request createSubscribeRequest(Device device, String content, SipTransactionInfo sipTransactionInfo, Integer expires, String event, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException { Request request = null; // sipuri SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress()); // via ArrayList viaHeaders = new ArrayList(); ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), SipUtils.getNewViaTag()); viaHeader.setRPort(); viaHeaders.add(viaHeader); // from SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain()); Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI); FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, sipTransactionInfo == null ? SipUtils.getNewFromTag() :sipTransactionInfo.getFromTag()); // to SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress()); Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI); ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress, sipTransactionInfo == null ? null :sipTransactionInfo.getToTag()); // Forwards MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70); // ceq CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.SUBSCRIBE); request = SipFactory.getInstance().createMessageFactory().createRequest(requestURI, Request.SUBSCRIBE, callIdHeader, cSeqHeader, fromHeader, toHeader, viaHeaders, maxForwards); Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), IpPortUtil.concatenateIpAndPort(sipLayer.getLocalIp(device.getLocalIp()), String.valueOf(sipConfig.getPort())))); request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress)); // Expires ExpiresHeader expireHeader = SipFactory.getInstance().createHeaderFactory().createExpiresHeader(expires); request.addHeader(expireHeader); // Event EventHeader eventHeader = SipFactory.getInstance().createHeaderFactory().createEventHeader(event); if (sipTransactionInfo != null && sipTransactionInfo.getEventId() != null) { eventHeader.setEventId(sipTransactionInfo.getEventId()); }else { int random = (int) Math.floor(Math.random() * 10000); eventHeader.setEventId(random + ""); } request.addHeader(eventHeader); ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml"); request.setContent(content, contentTypeHeader); request.addHeader(SipUtils.createUserAgentHeader(gitUtil)); return request; } public SIPRequest createInfoRequest(Device device, String channelId, String content, SipTransactionInfo transactionInfo) throws SipException, ParseException, InvalidArgumentException { if (device == null || transactionInfo == null) { return null; } SIPRequest request = null; //请求行 SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress()); // via ArrayList viaHeaders = new ArrayList(); ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), SipUtils.getNewViaTag()); viaHeaders.add(viaHeader); //from SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(),sipConfig.getDomain()); Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI); FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, transactionInfo.getFromTag()); //to SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId,device.getHostAddress()); Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI); ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress, transactionInfo.getToTag()); //Forwards MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70); //ceq CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.INFO); CallIdHeader callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(transactionInfo.getCallId()); request = (SIPRequest)SipFactory.getInstance().createMessageFactory().createRequest(requestLine, Request.INFO, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards); request.addHeader(SipUtils.createUserAgentHeader(gitUtil)); Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), IpPortUtil.concatenateIpAndPort(sipLayer.getLocalIp(device.getLocalIp()), String.valueOf(sipConfig.getPort())))); request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress)); request.addHeader(SipUtils.createUserAgentHeader(gitUtil)); if (content != null) { ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("Application", "MANSRTSP"); request.setContent(content, contentTypeHeader); } return request; } public Request createAckRequest(String localIp, SipURI sipURI, SIPResponse sipResponse) throws ParseException, InvalidArgumentException, PeerUnavailableException { // via ArrayList viaHeaders = new ArrayList(); ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(localIp, sipConfig.getPort(), sipResponse.getTopmostViaHeader().getTransport(), SipUtils.getNewViaTag()); viaHeaders.add(viaHeader); //Forwards MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70); //ceq CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(sipResponse.getCSeqHeader().getSeqNumber(), Request.ACK); Request request = SipFactory.getInstance().createMessageFactory().createRequest(sipURI, Request.ACK, sipResponse.getCallIdHeader(), cSeqHeader, sipResponse.getFromHeader(), sipResponse.getToHeader(), viaHeaders, maxForwards); request.addHeader(SipUtils.createUserAgentHeader(gitUtil)); Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), IpPortUtil.concatenateIpAndPort(localIp, String.valueOf(sipConfig.getPort())))); request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress)); request.addHeader(SipUtils.createUserAgentHeader(gitUtil)); return request; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.cmd.impl; import com.genersoft.iot.vmp.common.InviteSessionType; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.common.enums.MediaApp; import com.genersoft.iot.vmp.conf.SipConfig; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException; import com.genersoft.iot.vmp.gb28181.SipLayer; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.event.MessageSubscribe; import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; import com.genersoft.iot.vmp.gb28181.event.sip.MessageEvent; import com.genersoft.iot.vmp.gb28181.session.SipInviteSessionManager; import com.genersoft.iot.vmp.gb28181.transmit.SIPSender; import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander; import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderProvider; import com.genersoft.iot.vmp.gb28181.utils.NumericUtil; import com.genersoft.iot.vmp.gb28181.utils.SipUtils; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.event.hook.Hook; import com.genersoft.iot.vmp.media.event.hook.HookSubscribe; import com.genersoft.iot.vmp.media.event.hook.HookType; import com.genersoft.iot.vmp.media.service.IMediaServerService; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.service.bean.SSRCInfo; import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import gov.nist.javax.sip.message.SIPRequest; import gov.nist.javax.sip.message.SIPResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.DependsOn; import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; import javax.sip.InvalidArgumentException; import javax.sip.ResponseEvent; import javax.sip.SipException; import javax.sip.SipFactory; import javax.sip.header.CallIdHeader; import javax.sip.message.Request; import java.text.ParseException; import java.util.List; /** * @description:设备能力接口,用于定义设备的控制、查询能力 * @author: swwheihei * @date: 2020年5月3日 下午9:22:48 */ @Component @DependsOn("sipLayer") @Slf4j public class SIPCommander implements ISIPCommander { @Autowired private SipConfig sipConfig; @Autowired private SipLayer sipLayer; @Autowired private SIPSender sipSender; @Autowired private SIPRequestHeaderProvider headerProvider; @Autowired private SipInviteSessionManager sessionManager; @Autowired private UserSetting userSetting; @Autowired private HookSubscribe subscribe; @Autowired private IMediaServerService mediaServerService; @Autowired private MessageSubscribe messageSubscribe; /** * 云台指令码计算 * * @param cmdCode 指令码 * @param parameter1 数据1 * @param parameter2 数据2 * @param combineCode2 组合码2 */ public static String frontEndCmdString(int cmdCode, int parameter1, int parameter2, int combineCode2) { StringBuilder builder = new StringBuilder("A50F01"); String strTmp; strTmp = String.format("%02X", cmdCode); builder.append(strTmp, 0, 2); strTmp = String.format("%02X", parameter1); builder.append(strTmp, 0, 2); strTmp = String.format("%02X", parameter2); builder.append(strTmp, 0, 2); strTmp = String.format("%02X", combineCode2 << 4); builder.append(strTmp, 0, 2); //计算校验码 int checkCode = (0XA5 + 0X0F + 0X01 + cmdCode + parameter1 + parameter2 + (combineCode2 << 4)) % 0X100; strTmp = String.format("%02X", checkCode); builder.append(strTmp, 0, 2); return builder.toString(); } /** * 云台控制,支持方向与缩放控制 * * @param device 控制设备 * @param channelId 预览通道 * @param leftRight 镜头左移右移 0:停止 1:左移 2:右移 * @param upDown 镜头上移下移 0:停止 1:上移 2:下移 * @param inOut 镜头放大缩小 0:停止 1:缩小 2:放大 * @param moveSpeed 镜头移动速度 * @param zoomSpeed 镜头缩放速度 */ @Override public void ptzCmd(Device device, String channelId, int leftRight, int upDown, int inOut, int moveSpeed, int zoomSpeed) throws InvalidArgumentException, SipException, ParseException { String cmdStr = SipUtils.cmdString(leftRight, upDown, inOut, moveSpeed, zoomSpeed); StringBuilder ptzXml = new StringBuilder(200); String charset = device.getCharset(); ptzXml.append("\r\n"); ptzXml.append("\r\n"); ptzXml.append("DeviceControl\r\n"); ptzXml.append("" + (int) ((Math.random() * 9 + 1) * 100000) + "\r\n"); ptzXml.append("" + channelId + "\r\n"); ptzXml.append("" + cmdStr + "\r\n"); ptzXml.append("\r\n"); ptzXml.append("5\r\n"); ptzXml.append("\r\n"); ptzXml.append("\r\n"); Request request = headerProvider.createMessageRequest(device, ptzXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()),request); } /** * 前端控制,包括PTZ指令、FI指令、预置位指令、巡航指令、扫描指令和辅助开关指令 * * @param device 控制设备 * @param channelId 预览通道 * @param cmdCode 指令码 * @param parameter1 数据1 * @param parameter2 数据2 * @param combineCode2 组合码2 */ @Override public void frontEndCmd(Device device, String channelId, int cmdCode, int parameter1, int parameter2, int combineCode2) throws SipException, InvalidArgumentException, ParseException { String cmdStr = frontEndCmdString(cmdCode, parameter1, parameter2, combineCode2); StringBuffer ptzXml = new StringBuffer(200); String charset = device.getCharset(); ptzXml.append("\r\n"); ptzXml.append("\r\n"); ptzXml.append("DeviceControl\r\n"); ptzXml.append("" + (int) ((Math.random() * 9 + 1) * 100000) + "\r\n"); ptzXml.append("" + channelId + "\r\n"); ptzXml.append("" + cmdStr + "\r\n"); ptzXml.append("\r\n"); ptzXml.append("5\r\n"); ptzXml.append("\r\n"); ptzXml.append("\r\n"); SIPRequest request = (SIPRequest) headerProvider.createMessageRequest(device, ptzXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()),request); } /** * 前端控制指令(用于转发上级指令) * * @param device 控制设备 * @param channelId 预览通道 * @param cmdString 前端控制指令串 */ @Override public void fronEndCmd(Device device, String channelId, String cmdString, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException { StringBuffer ptzXml = new StringBuffer(200); String charset = device.getCharset(); ptzXml.append("\r\n"); ptzXml.append("\r\n"); ptzXml.append("DeviceControl\r\n"); ptzXml.append("" + (int) ((Math.random() * 9 + 1) * 100000) + "\r\n"); ptzXml.append("" + channelId + "\r\n"); ptzXml.append("" + cmdString + "\r\n"); ptzXml.append("\r\n"); ptzXml.append("5\r\n"); ptzXml.append("\r\n"); ptzXml.append("\r\n"); Request request = headerProvider.createMessageRequest(device, ptzXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()),request, errorEvent, okEvent); } /** * 请求预览视频流 * * @param device 视频设备 * @param channel 预览通道 * @param errorEvent sip错误订阅 */ @Override public void playStreamCmd(MediaServer mediaServerItem, SSRCInfo ssrcInfo, Device device, DeviceChannel channel, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent, Long timeout) throws InvalidArgumentException, SipException, ParseException { String stream = ssrcInfo.getStream(); if (device == null) { return; } String sdpIp; if (!ObjectUtils.isEmpty(device.getSdpIp())) { sdpIp = device.getSdpIp(); }else { sdpIp = mediaServerItem.getSdpIp(); } StringBuffer content = new StringBuffer(200); content.append("v=0\r\n"); content.append("o=" + device.getDeviceId() + " 0 0 IN IP4 " + sdpIp + "\r\n"); content.append("s=Play\r\n"); content.append("c=IN IP4 " + sdpIp + "\r\n"); content.append("t=0 0\r\n"); if (userSetting.getSeniorSdp()) { if ("TCP-PASSIVE".equalsIgnoreCase(device.getStreamMode())) { content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 126 125 99 34 98 97\r\n"); } else if ("TCP-ACTIVE".equalsIgnoreCase(device.getStreamMode())) { content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 126 125 99 34 98 97\r\n"); } else if ("UDP".equalsIgnoreCase(device.getStreamMode())) { content.append("m=video " + ssrcInfo.getPort() + " RTP/AVP 96 126 125 99 34 98 97\r\n"); } content.append("a=recvonly\r\n"); content.append("a=rtpmap:96 PS/90000\r\n"); content.append("a=fmtp:126 profile-level-id=42e01e\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 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".equalsIgnoreCase(device.getStreamMode())) { // tcp被动模式 content.append("a=setup:passive\r\n"); content.append("a=connection:new\r\n"); } else if ("TCP-ACTIVE".equalsIgnoreCase(device.getStreamMode())) { // tcp主动模式 content.append("a=setup:active\r\n"); content.append("a=connection:new\r\n"); } } else { if ("TCP-PASSIVE".equalsIgnoreCase(device.getStreamMode())) { content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 97 98 99\r\n"); } else if ("TCP-ACTIVE".equalsIgnoreCase(device.getStreamMode())) { content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 97 98 99\r\n"); } else if ("UDP".equalsIgnoreCase(device.getStreamMode())) { 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".equalsIgnoreCase(device.getStreamMode())) { // tcp被动模式 content.append("a=setup:passive\r\n"); content.append("a=connection:new\r\n"); } else if ("TCP-ACTIVE".equalsIgnoreCase(device.getStreamMode())) { // tcp主动模式 content.append("a=setup:active\r\n"); content.append("a=connection:new\r\n"); } } if (!ObjectUtils.isEmpty(channel.getStreamIdentification())) { content.append("a=" + channel.getStreamIdentification() + "\r\n"); } content.append("y=" + ssrcInfo.getSsrc() + "\r\n");//ssrc // f字段:f= v/编码格式/分辨率/帧率/码率类型/码率大小a/编码格式/码率大小/采样率 // content.append("f=v/2/6/25/1/4000a/6/8/1" + "\r\n"); // 未发现支持此特性的设备 Request request = headerProvider.createInviteRequest(device, channel.getDeviceId(), content.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, ssrcInfo.getSsrc(),sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, (e -> { sessionManager.removeByStream(ssrcInfo.getApp(), ssrcInfo.getStream()); mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc()); errorEvent.response(e); }), e -> { ResponseEvent responseEvent = (ResponseEvent) e.event; SIPResponse response = (SIPResponse) responseEvent.getResponse(); String callId = response.getCallIdHeader().getCallId(); SsrcTransaction ssrcTransaction = SsrcTransaction.buildForDevice(device.getDeviceId(), channel.getId(), callId,ssrcInfo.getApp(), ssrcInfo.getStream(), ssrcInfo.getSsrc(), mediaServerItem.getId(), response, InviteSessionType.PLAY); sessionManager.put(ssrcTransaction); okEvent.response(e); }, timeout); } /** * 请求回放视频流 * * @param device 视频设备 * @param channel 预览通道 * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss */ @Override public void playbackStreamCmd(MediaServer mediaServerItem, SSRCInfo ssrcInfo, Device device, DeviceChannel channel, String startTime, String endTime, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent, Long timeout) throws InvalidArgumentException, SipException, ParseException { log.info("{} 分配的ZLM为: {} [{}:{}]", ssrcInfo.getStream(), mediaServerItem.getId(), mediaServerItem.getSdpIp(), ssrcInfo.getPort()); String sdpIp; if (!ObjectUtils.isEmpty(device.getSdpIp())) { sdpIp = device.getSdpIp(); }else { sdpIp = mediaServerItem.getSdpIp(); } StringBuffer content = new StringBuffer(200); content.append("v=0\r\n"); content.append("o=" + device.getDeviceId() + " 0 0 IN IP4 " + sdpIp + "\r\n"); content.append("s=Playback\r\n"); content.append("u=" + channel.getDeviceId() + ":0\r\n"); content.append("c=IN IP4 " + sdpIp + "\r\n"); content.append("t=" + DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime) + " " + DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime) + "\r\n"); String streamMode = device.getStreamMode(); if (userSetting.getSeniorSdp()) { if ("TCP-PASSIVE".equalsIgnoreCase(streamMode)) { content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 126 125 99 34 98 97\r\n"); } else if ("TCP-ACTIVE".equalsIgnoreCase(streamMode)) { content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 126 125 99 34 98 97\r\n"); } else if ("UDP".equalsIgnoreCase(streamMode)) { content.append("m=video " + ssrcInfo.getPort() + " RTP/AVP 96 126 125 99 34 98 97\r\n"); } content.append("a=recvonly\r\n"); content.append("a=rtpmap:96 PS/90000\r\n"); content.append("a=fmtp:126 profile-level-id=42e01e\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 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".equalsIgnoreCase(streamMode)) { // tcp被动模式 content.append("a=setup:passive\r\n"); content.append("a=connection:new\r\n"); } else if ("TCP-ACTIVE".equalsIgnoreCase(streamMode)) { // tcp主动模式 content.append("a=setup:active\r\n"); content.append("a=connection:new\r\n"); } } else { if ("TCP-PASSIVE".equalsIgnoreCase(streamMode)) { content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 97 98 99\r\n"); } else if ("TCP-ACTIVE".equalsIgnoreCase(streamMode)) { content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 97 98 99\r\n"); } else if ("UDP".equalsIgnoreCase(streamMode)) { 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: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".equalsIgnoreCase(streamMode)) { // tcp被动模式 content.append("a=setup:passive\r\n"); content.append("a=connection:new\r\n"); } else if ("TCP-ACTIVE".equalsIgnoreCase(streamMode)) { // tcp主动模式 content.append("a=setup:active\r\n"); content.append("a=connection:new\r\n"); } } //ssrc content.append("y=" + ssrcInfo.getSsrc() + "\r\n"); Request request = headerProvider.createPlaybackInviteRequest(device, channel.getDeviceId(), content.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()), ssrcInfo.getSsrc()); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, event -> { ResponseEvent responseEvent = (ResponseEvent) event.event; SIPResponse response = (SIPResponse) responseEvent.getResponse(); SsrcTransaction ssrcTransaction = SsrcTransaction.buildForDevice(device.getDeviceId(), channel.getId(), sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()), device.getTransport()).getCallId(), ssrcInfo.getApp(), ssrcInfo.getStream(), ssrcInfo.getSsrc(), mediaServerItem.getId(), response, InviteSessionType.PLAYBACK); sessionManager.put(ssrcTransaction); okEvent.response(event); }, timeout); } /** * 请求历史媒体下载 */ @Override public void downloadStreamCmd(MediaServer mediaServerItem, SSRCInfo ssrcInfo, Device device, DeviceChannel channel, String startTime, String endTime, int downloadSpeed, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent, Long timeout) throws InvalidArgumentException, SipException, ParseException { log.info("[发送-请求历史媒体下载-命令] 流ID: {},节点为: {} [{}:{}]", ssrcInfo.getStream(), mediaServerItem.getId(), mediaServerItem.getSdpIp(), ssrcInfo.getPort()); String sdpIp; if (!ObjectUtils.isEmpty(device.getSdpIp())) { sdpIp = device.getSdpIp(); }else { sdpIp = mediaServerItem.getSdpIp(); } StringBuffer content = new StringBuffer(200); content.append("v=0\r\n"); content.append("o=" + device.getDeviceId() + " 0 0 IN IP4 " + sdpIp + "\r\n"); content.append("s=Download\r\n"); content.append("u=" + channel.getDeviceId() + ":0\r\n"); content.append("c=IN IP4 " + sdpIp + "\r\n"); content.append("t=" + DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime) + " " + DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime) + "\r\n"); String streamMode = device.getStreamMode().toUpperCase(); if (userSetting.getSeniorSdp()) { if ("TCP-PASSIVE".equals(streamMode)) { content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 126 125 99 34 98 97\r\n"); } else if ("TCP-ACTIVE".equals(streamMode)) { content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 126 125 99 34 98 97\r\n"); } else if ("UDP".equals(streamMode)) { content.append("m=video " + ssrcInfo.getPort() + " RTP/AVP 96 126 125 99 34 98 97\r\n"); } content.append("a=recvonly\r\n"); content.append("a=rtpmap:96 PS/90000\r\n"); content.append("a=fmtp:126 profile-level-id=42e01e\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:98 H264/90000\r\n"); content.append("a=rtpmap:97 MPEG4/90000\r\n"); if ("TCP-PASSIVE".equals(streamMode)) { // tcp被动模式 content.append("a=setup:passive\r\n"); content.append("a=connection:new\r\n"); } else if ("TCP-ACTIVE".equals(streamMode)) { // tcp主动模式 content.append("a=setup:active\r\n"); content.append("a=connection:new\r\n"); } } else { if ("TCP-PASSIVE".equals(streamMode)) { 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 97 98 99\r\n"); } else if ("UDP".equals(streamMode)) { 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: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"); } else if ("TCP-ACTIVE".equals(streamMode)) { // tcp主动模式 content.append("a=setup:active\r\n"); content.append("a=connection:new\r\n"); } } content.append("a=downloadspeed:" + downloadSpeed + "\r\n"); content.append("y=" + ssrcInfo.getSsrc() + "\r\n");//ssrc log.debug("此时请求下载信令的ssrc===>{}",ssrcInfo.getSsrc()); // 添加订阅 CallIdHeader newCallIdHeader = sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()), device.getTransport()); Request request = headerProvider.createPlaybackInviteRequest(device, channel.getDeviceId(), content.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,newCallIdHeader, ssrcInfo.getSsrc()); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, event -> { ResponseEvent responseEvent = (ResponseEvent) event.event; SIPResponse response = (SIPResponse) responseEvent.getResponse(); String contentString =new String(response.getRawContent()); String ssrc = SipUtils.getSsrcFromSdp(contentString); SsrcTransaction ssrcTransaction = SsrcTransaction.buildForDevice(device.getDeviceId(), channel.getId(), response.getCallIdHeader().getCallId(), ssrcInfo.getApp(), ssrcInfo.getStream(), ssrc, mediaServerItem.getId(), response, InviteSessionType.DOWNLOAD); sessionManager.put(ssrcTransaction); okEvent.response(event); }, timeout); } @Override public void talkStreamCmd(MediaServer mediaServerItem, SendRtpInfo sendRtpItem, Device device, DeviceChannel channel, String callId, HookSubscribe.Event event, HookSubscribe.Event eventForPush, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent, Long timeout) throws InvalidArgumentException, SipException, ParseException { String stream = sendRtpItem.getStream(); if (device == null) { return; } if (!mediaServerItem.isRtpEnable()) { // 单端口暂不支持语音喊话 log.info("[语音喊话] 单端口暂不支持此操作"); return; } log.info("[语音喊话] {} 分配的ZLM为: {} [{}:{}]", stream, mediaServerItem.getId(), mediaServerItem.getIp(), sendRtpItem.getPort()); Hook hook = Hook.getInstance(HookType.on_media_arrival, MediaApp.GB28181, stream, mediaServerItem.getId()); subscribe.addSubscribe(hook, (hookData) -> { if (event != null) { event.response(hookData); subscribe.removeSubscribe(hook); } }); CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()), device.getTransport()); callIdHeader.setCallId(callId); Hook publishHook = Hook.getInstance(HookType.on_publish, MediaApp.GB28181, stream, mediaServerItem.getId()); subscribe.addSubscribe(publishHook, (hookData) -> { if (eventForPush != null) { eventForPush.response(hookData); } }); // StringBuffer content = new StringBuffer(200); content.append("v=0\r\n"); content.append("o=" + device.getDeviceId() + " 0 0 IN IP4 " + mediaServerItem.getSdpIp() + "\r\n"); content.append("s=Talk\r\n"); content.append("c=IN IP4 " + mediaServerItem.getSdpIp() + "\r\n"); content.append("t=0 0\r\n"); content.append("m=audio " + sendRtpItem.getPort() + " TCP/RTP/AVP 8\r\n"); content.append("a=setup:passive\r\n"); content.append("a=connection:new\r\n"); content.append("a=sendrecv\r\n"); content.append("a=rtpmap:8 PCMA/8000\r\n"); content.append("y=" + sendRtpItem.getSsrc() + "\r\n");//ssrc // f字段:f= v/编码格式/分辨率/帧率/码率类型/码率大小a/编码格式/码率大小/采样率 content.append("f=v/////a/1/8/1" + "\r\n"); Request request = headerProvider.createInviteRequest(device, channel.getDeviceId(), content.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, sendRtpItem.getSsrc(), callIdHeader); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, (e -> { sessionManager.removeByStream(sendRtpItem.getApp(), sendRtpItem.getStream()); mediaServerService.releaseSsrc(mediaServerItem.getId(), sendRtpItem.getSsrc()); errorEvent.response(e); }), e -> { // 这里为例避免一个通道的点播只有一个callID这个参数使用一个固定值 ResponseEvent responseEvent = (ResponseEvent) e.event; SIPResponse response = (SIPResponse) responseEvent.getResponse(); SsrcTransaction ssrcTransaction = SsrcTransaction.buildForDevice(device.getDeviceId(), channel.getId(), MediaApp.GB28181_TALK,sendRtpItem.getApp(), stream, sendRtpItem.getSsrc(), mediaServerItem.getId(), response, InviteSessionType.TALK); sessionManager.put(ssrcTransaction); okEvent.response(e); }, timeout); } /** * 视频流停止 */ @Override public void streamByeCmd(Device device, String channelId, String app, String stream, String callId, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException { if (device == null) { log.warn("[发送BYE] device为null"); return; } SsrcTransaction ssrcTransaction = null; if (callId != null) { ssrcTransaction = sessionManager.getSsrcTransactionByCallId(callId); }else if (stream != null) { ssrcTransaction = sessionManager.getSsrcTransactionByStream(app, stream); } if (ssrcTransaction == null) { log.info("[发送BYE] 未找到事务信息,设备: device: {}, channel: {}", device.getDeviceId(), channelId); throw new SsrcTransactionNotFoundException(device.getDeviceId(), channelId, callId, stream); } log.info("[发送BYE] 设备: device: {}, channel: {}, callId: {}", device.getDeviceId(), channelId, ssrcTransaction.getCallId()); sessionManager.removeByCallId(ssrcTransaction.getCallId()); Request byteRequest = headerProvider.createByteRequest(device, channelId, ssrcTransaction.getSipTransactionInfo()); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), byteRequest, null, okEvent); } @Override public void streamByeCmd(Device device, String channelId, SipTransactionInfo sipTransactionInfo, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException { Request byteRequest = headerProvider.createByteRequest(device, channelId, sipTransactionInfo); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), byteRequest, null, okEvent); } @Override public void streamByeCmdForDeviceInvite(Device device, String channelId, SipTransactionInfo sipTransactionInfo, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException { Request byteRequest = headerProvider.createByteRequestForDeviceInvite(device, channelId, sipTransactionInfo); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), byteRequest, null, okEvent); } /** * 语音广播 * * @param device 视频设备 */ @Override public void audioBroadcastCmd(Device device, String channelId, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException { StringBuffer broadcastXml = new StringBuffer(200); String charset = device.getCharset(); broadcastXml.append("\r\n"); broadcastXml.append("\r\n"); broadcastXml.append("Broadcast\r\n"); broadcastXml.append("" + (int)((Math.random()*9+1)*100000) + "\r\n"); broadcastXml.append("" + sipConfig.getId() + "\r\n"); broadcastXml.append("" + channelId + "\r\n"); broadcastXml.append("\r\n"); Request request = headerProvider.createMessageRequest(device, broadcastXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, okEvent); } /** * 音视频录像控制 * * @param device 视频设备 * @param channelId 预览通道 * @param recordCmdStr 录像命令:Record / StopRecord */ @Override public void recordCmd(Device device, String channelId, String recordCmdStr, ErrorCallback callback) throws InvalidArgumentException, SipException, ParseException { final String cmdType = "DeviceControl"; final int sn = (int) ((Math.random() * 9 + 1) * 100000); StringBuffer cmdXml = new StringBuffer(200); String charset = device.getCharset(); cmdXml.append("\r\n"); cmdXml.append("\r\n"); cmdXml.append("" + cmdType + "\r\n"); cmdXml.append("" + sn + "\r\n"); if (ObjectUtils.isEmpty(channelId)) { cmdXml.append("" + device.getDeviceId() + "\r\n"); } else { cmdXml.append("" + channelId + "\r\n"); } cmdXml.append("" + recordCmdStr + "\r\n"); cmdXml.append("\r\n"); MessageEvent messageEvent = MessageEvent.getInstance(cmdType, sn + "", channelId, 1000L, callback); messageSubscribe.addSubscribe(messageEvent); Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, eventResult -> { messageSubscribe.removeSubscribe(messageEvent.getKey()); callback.run(ErrorCode.ERROR100.getCode(), "失败," + eventResult.msg, null); },null); } /** * 远程启动控制命令 * * @param device 视频设备 */ @Override public void teleBootCmd(Device device) throws InvalidArgumentException, SipException, ParseException { StringBuffer cmdXml = new StringBuffer(200); String charset = device.getCharset(); cmdXml.append("\r\n"); cmdXml.append("\r\n"); cmdXml.append("DeviceControl\r\n"); cmdXml.append("" + (int) ((Math.random() * 9 + 1) * 100000) + "\r\n"); cmdXml.append("" + device.getDeviceId() + "\r\n"); cmdXml.append("Boot\r\n"); cmdXml.append("\r\n"); Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request); } /** * 报警布防/撤防命令 * * @param device 视频设备 * @param guardCmdStr "SetGuard"/"ResetGuard" */ @Override public void guardCmd(Device device, String guardCmdStr, ErrorCallback callback) throws InvalidArgumentException, SipException, ParseException { String cmdType = "DeviceControl"; int sn = (int) ((Math.random() * 9 + 1) * 100000); StringBuffer cmdXml = new StringBuffer(200); String charset = device.getCharset(); cmdXml.append("\r\n"); cmdXml.append("\r\n"); cmdXml.append("" + cmdType + "\r\n"); cmdXml.append("" + sn + "\r\n"); cmdXml.append("" + device.getDeviceId() + "\r\n"); cmdXml.append("" + guardCmdStr + "\r\n"); cmdXml.append("\r\n"); MessageEvent messageEvent = MessageEvent.getInstance(cmdType, sn + "", device.getDeviceId(), 1000L, callback); messageSubscribe.addSubscribe(messageEvent); Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, eventResult -> { messageSubscribe.removeSubscribe(messageEvent.getKey()); callback.run(ErrorCode.ERROR100.getCode(), "失败," + eventResult.msg, null); }); } /** * 报警复位命令 * * @param device 视频设备 */ @Override public void alarmResetCmd(Device device, String alarmMethod, String alarmType, ErrorCallback callback) throws InvalidArgumentException, SipException, ParseException { String cmdType = "DeviceControl"; int sn = (int) ((Math.random() * 9 + 1) * 100000); StringBuffer cmdXml = new StringBuffer(200); String charset = device.getCharset(); cmdXml.append("\r\n"); cmdXml.append("\r\n"); cmdXml.append("" + cmdType + "\r\n"); cmdXml.append("" + sn + "\r\n"); cmdXml.append("" + device.getDeviceId() + "\r\n"); cmdXml.append("ResetAlarm\r\n"); if (!ObjectUtils.isEmpty(alarmMethod) || !ObjectUtils.isEmpty(alarmType)) { cmdXml.append("\r\n"); } if (!ObjectUtils.isEmpty(alarmMethod)) { cmdXml.append("" + alarmMethod + "\r\n"); } if (!ObjectUtils.isEmpty(alarmType)) { cmdXml.append("" + alarmType + "\r\n"); } if (!ObjectUtils.isEmpty(alarmMethod) || !ObjectUtils.isEmpty(alarmType)) { cmdXml.append("\r\n"); } cmdXml.append("\r\n"); MessageEvent messageEvent = MessageEvent.getInstance(cmdType, sn + "", device.getDeviceId(), 1000L, callback); messageSubscribe.addSubscribe(messageEvent); Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, eventResult -> { messageSubscribe.removeSubscribe(messageEvent.getKey()); callback.run(ErrorCode.ERROR100.getCode(), "失败," + eventResult.msg, null); }); } /** * 强制关键帧命令,设备收到此命令应立刻发送一个IDR帧 * * @param device 视频设备 * @param channelId 预览通道 */ @Override public void iFrameCmd(Device device, String channelId) throws InvalidArgumentException, SipException, ParseException { StringBuffer cmdXml = new StringBuffer(200); String charset = device.getCharset(); cmdXml.append("\r\n"); cmdXml.append("\r\n"); cmdXml.append("DeviceControl\r\n"); cmdXml.append("" + (int) ((Math.random() * 9 + 1) * 100000) + "\r\n"); if (ObjectUtils.isEmpty(channelId)) { cmdXml.append("" + device.getDeviceId() + "\r\n"); } else { cmdXml.append("" + channelId + "\r\n"); } cmdXml.append("Send\r\n"); cmdXml.append("\r\n"); Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request); } /** * 看守位控制命令 * * @param device 视频设备 * @param channelId 通道id,非通道则是设备本身 * @param enabled 看守位使能:1 = 开启,0 = 关闭 * @param resetTime 自动归位时间间隔,开启看守位时使用,单位:秒(s) * @param presetIndex 调用预置位编号,开启看守位时使用,取值范围0~255 */ @Override public void homePositionCmd(Device device, String channelId, Boolean enabled, Integer resetTime, Integer presetIndex, ErrorCallback callback) throws InvalidArgumentException, SipException, ParseException { String cmdType = "DeviceControl"; int sn = (int) ((Math.random() * 9 + 1) * 100000); StringBuffer cmdXml = new StringBuffer(200); String charset = device.getCharset(); cmdXml.append("\r\n"); cmdXml.append("\r\n"); cmdXml.append("" + cmdType + "\r\n"); cmdXml.append("" + sn + "\r\n"); if (ObjectUtils.isEmpty(channelId)) { channelId = device.getDeviceId(); } cmdXml.append("" + channelId + "\r\n"); cmdXml.append("\r\n"); if (enabled) { cmdXml.append("1\r\n"); cmdXml.append("" + resetTime + "\r\n"); cmdXml.append("" + presetIndex + "\r\n"); } else { cmdXml.append("0\r\n"); } cmdXml.append("\r\n"); cmdXml.append("\r\n"); MessageEvent messageEvent = MessageEvent.getInstance(cmdType, sn + "", channelId, 1000L, callback); messageSubscribe.addSubscribe(messageEvent); Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, eventResult -> { messageSubscribe.removeSubscribe(messageEvent.getKey()); callback.run(ErrorCode.ERROR100.getCode(), "失败," + eventResult.msg, null); }); } /** * 设备配置命令 * * @param device 视频设备 */ @Override public void deviceConfigCmd(Device device) { // TODO Auto-generated method stub } /** * 设备配置命令:basicParam */ @Override public void deviceBasicConfigCmd(Device device, BasicParam basicParam, ErrorCallback callback) throws InvalidArgumentException, SipException, ParseException { int sn = (int) ((Math.random() * 9 + 1) * 100000); String cmdType = "DeviceConfig"; StringBuffer cmdXml = new StringBuffer(200); String charset = device.getCharset(); cmdXml.append("\r\n"); cmdXml.append("\r\n"); cmdXml.append("" + cmdType + "\r\n"); cmdXml.append("" + sn + "\r\n"); String channelId = basicParam.getChannelId(); if (ObjectUtils.isEmpty(channelId)) { channelId = device.getDeviceId(); } cmdXml.append("" + channelId + "\r\n"); cmdXml.append("\r\n"); if (!ObjectUtils.isEmpty(basicParam.getName())) { cmdXml.append("" + basicParam.getName() + "\r\n"); } if (NumericUtil.isInteger(basicParam.getExpiration())) { if (Integer.parseInt(basicParam.getExpiration()) > 0) { cmdXml.append("" + basicParam.getExpiration() + "\r\n"); } } if (basicParam.getHeartBeatInterval() != null && basicParam.getHeartBeatInterval() > 0) { cmdXml.append("" + basicParam.getHeartBeatInterval() + "\r\n"); } if (basicParam.getHeartBeatCount() != null && basicParam.getHeartBeatCount() > 0) { cmdXml.append("" + basicParam.getHeartBeatCount() + "\r\n"); } cmdXml.append("\r\n"); cmdXml.append("\r\n"); MessageEvent messageEvent = MessageEvent.getInstance(cmdType, sn + "", channelId, 1000L, callback); messageSubscribe.addSubscribe(messageEvent); Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, eventResult -> { messageSubscribe.removeSubscribe(messageEvent.getKey()); callback.run(ErrorCode.ERROR100.getCode(), "失败," + eventResult.msg, null); }); } /** * 查询设备状态 * * @param device 视频设备 */ @Override public void deviceStatusQuery(Device device, ErrorCallback callback) throws InvalidArgumentException, SipException, ParseException { String cmdType = "DeviceStatus"; int sn = (int) ((Math.random() * 9 + 1) * 100000); String charset = device.getCharset(); StringBuffer catalogXml = new StringBuffer(200); catalogXml.append("\r\n"); catalogXml.append("\r\n"); catalogXml.append("" + cmdType + "\r\n"); catalogXml.append("" + sn + "\r\n"); catalogXml.append("" + device.getDeviceId() + "\r\n"); catalogXml.append("\r\n"); MessageEvent messageEvent = MessageEvent.getInstance(cmdType, sn + "", device.getDeviceId(), 1000L, callback); messageSubscribe.addSubscribe(messageEvent); Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, eventResult -> { messageSubscribe.removeSubscribe(messageEvent.getKey()); callback.run(ErrorCode.ERROR100.getCode(), "失败," + eventResult.msg, null); }); } /** * 查询设备信息 * * @param device 视频设备 * @param callback */ @Override public void deviceInfoQuery(Device device, ErrorCallback callback) throws InvalidArgumentException, SipException, ParseException { String cmdType = "DeviceInfo"; String sn = (int) ((Math.random() * 9 + 1) * 100000) + ""; StringBuffer catalogXml = new StringBuffer(200); String charset = device.getCharset(); catalogXml.append("\r\n"); catalogXml.append("\r\n"); catalogXml.append("" + cmdType +"\r\n"); catalogXml.append("" + sn + "\r\n"); catalogXml.append("" + device.getDeviceId() + "\r\n"); catalogXml.append("\r\n"); MessageEvent messageEvent = MessageEvent.getInstance(cmdType, sn, device.getDeviceId(), 1000L, callback); messageSubscribe.addSubscribe(messageEvent); Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, eventResult -> { messageSubscribe.removeSubscribe(messageEvent.getKey()); if (callback != null) { callback.run(ErrorCode.ERROR100.getCode(), "失败," + eventResult.msg, null); } }); } /** * 查询目录列表 * * @param device 视频设备 */ @Override public void catalogQuery(Device device, int sn, SipSubscribe.Event errorEvent) throws SipException, InvalidArgumentException, ParseException { StringBuffer catalogXml = new StringBuffer(200); String charset = device.getCharset(); catalogXml.append("\r\n"); catalogXml.append("\r\n"); catalogXml.append(" Catalog\r\n"); catalogXml.append(" " + sn + "\r\n"); catalogXml.append(" " + device.getDeviceId() + "\r\n"); catalogXml.append("\r\n"); Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent); } /** * 查询录像信息 * * @param device 视频设备 * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss */ @Override public void recordInfoQuery(Device device, String channelId, String startTime, String endTime, int sn, Integer secrecy, String type, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException { if (secrecy == null) { secrecy = 0; } if (type == null) { type = "all"; } StringBuffer recordInfoXml = new StringBuffer(200); String charset = device.getCharset(); recordInfoXml.append("\r\n"); recordInfoXml.append("\r\n"); recordInfoXml.append("RecordInfo\r\n"); recordInfoXml.append("" + sn + "\r\n"); recordInfoXml.append("" + channelId + "\r\n"); if (startTime != null) { recordInfoXml.append("" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(startTime) + "\r\n"); } if (endTime != null) { recordInfoXml.append("" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(endTime) + "\r\n"); } if (secrecy != null) { recordInfoXml.append(" " + secrecy + " \r\n"); } if (type != null) { // 大华NVR要求必须增加一个值为all的文本元素节点Type recordInfoXml.append("" + type + "\r\n"); } recordInfoXml.append("\r\n"); Request request = headerProvider.createMessageRequest(device, recordInfoXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, okEvent); } /** * 查询报警信息 * * @param device 视频设备 * @param startPriority 报警起始级别(可选) * @param endPriority 报警终止级别(可选) * @param alarmMethod 报警方式条件(可选) * @param alarmType 报警类型 * @param startTime 报警发生起始时间(可选) * @param endTime 报警发生终止时间(可选) * @return true = 命令发送成功 */ @Override public void alarmInfoQuery(Device device, String startPriority, String endPriority, String alarmMethod, String alarmType, String startTime, String endTime, ErrorCallback callback) throws InvalidArgumentException, SipException, ParseException { String cmdType = "Alarm"; String sn = (int) ((Math.random() * 9 + 1) * 100000) + ""; StringBuffer cmdXml = new StringBuffer(200); String charset = device.getCharset(); cmdXml.append("\r\n"); cmdXml.append("\r\n"); cmdXml.append("" + cmdType + "\r\n"); cmdXml.append("" + sn + "\r\n"); cmdXml.append("" + device.getDeviceId() + "\r\n"); if (!ObjectUtils.isEmpty(startPriority)) { cmdXml.append("" + startPriority + "\r\n"); } if (!ObjectUtils.isEmpty(endPriority)) { cmdXml.append("" + endPriority + "\r\n"); } if (!ObjectUtils.isEmpty(alarmMethod)) { cmdXml.append("" + alarmMethod + "\r\n"); } if (!ObjectUtils.isEmpty(alarmType)) { cmdXml.append("" + alarmType + "\r\n"); } if (!ObjectUtils.isEmpty(startTime)) { cmdXml.append("" + startTime + "\r\n"); } if (!ObjectUtils.isEmpty(endTime)) { cmdXml.append("" + endTime + "\r\n"); } cmdXml.append("\r\n"); MessageEvent messageEvent = MessageEvent.getInstance(cmdType, sn, device.getDeviceId(), 1000L, callback); messageSubscribe.addSubscribe(messageEvent); Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, eventResult -> { messageSubscribe.removeSubscribe(messageEvent.getKey()); callback.run(ErrorCode.ERROR100.getCode(), "失败," + eventResult.msg, null); }); } /** * 查询设备配置 * * @param device 视频设备 * @param channelId 通道编码(可选) * @param configType 配置类型: */ @Override public void deviceConfigQuery(Device device, String channelId, String configType, ErrorCallback callback) throws InvalidArgumentException, SipException, ParseException { String cmdType = "ConfigDownload"; int sn = (int) ((Math.random() * 9 + 1) * 100000); StringBuffer cmdXml = new StringBuffer(200); String charset = device.getCharset(); cmdXml.append("\r\n"); cmdXml.append("\r\n"); cmdXml.append("" + cmdType + "\r\n"); cmdXml.append("" + sn + "\r\n"); if (ObjectUtils.isEmpty(channelId)) { cmdXml.append("" + device.getDeviceId() + "\r\n"); } else { cmdXml.append("" + channelId + "\r\n"); } cmdXml.append("" + configType + "\r\n"); cmdXml.append("\r\n"); MessageEvent messageEvent = MessageEvent.getInstance(cmdType, sn + "", channelId, 1000L, callback); messageSubscribe.addSubscribe(messageEvent); Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, eventResult -> { messageSubscribe.removeSubscribe(messageEvent.getKey()); if (callback != null) { callback.run(ErrorCode.ERROR100.getCode(), "失败," + eventResult.msg, null); } }); } /** * 查询设备预置位置 * * @param device 视频设备 */ @Override public void presetQuery(Device device, String channelId, ErrorCallback> callback) throws InvalidArgumentException, SipException, ParseException { String cmdType = "PresetQuery"; int sn = (int) ((Math.random() * 9 + 1) * 100000); StringBuffer cmdXml = new StringBuffer(200); String charset = device.getCharset(); cmdXml.append("\r\n"); cmdXml.append("\r\n"); cmdXml.append("" + cmdType + "\r\n"); cmdXml.append("" + sn + "\r\n"); if (ObjectUtils.isEmpty(channelId)) { cmdXml.append("" + device.getDeviceId() + "\r\n"); } else { cmdXml.append("" + channelId + "\r\n"); } cmdXml.append("\r\n"); MessageEvent> messageEvent = MessageEvent.getInstance(cmdType, sn + "", channelId, 4000L, callback); messageSubscribe.addSubscribe(messageEvent); log.info("[预置位查询] 设备编号: {}, 通道编号: {}, SN: {}", device.getDeviceId(), channelId, sn); Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, eventResult -> { messageSubscribe.removeSubscribe(messageEvent.getKey()); callback.run(ErrorCode.ERROR100.getCode(), "失败," + eventResult.msg, null); }); } /** * 查询移动设备位置数据 * * @param device 视频设备 */ @Override public void mobilePositionQuery(Device device, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException { StringBuffer mobilePositionXml = new StringBuffer(200); String charset = device.getCharset(); mobilePositionXml.append("\r\n"); mobilePositionXml.append("\r\n"); mobilePositionXml.append("MobilePosition\r\n"); mobilePositionXml.append("" + (int) ((Math.random() * 9 + 1) * 100000) + "\r\n"); mobilePositionXml.append("" + device.getDeviceId() + "\r\n"); mobilePositionXml.append("60\r\n"); mobilePositionXml.append("\r\n"); Request request = headerProvider.createMessageRequest(device, mobilePositionXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent); } /** * 订阅、取消订阅移动位置 * * @param device 视频设备 * @return true = 命令发送成功 */ @Override public SIPRequest mobilePositionSubscribe(Device device, SipTransactionInfo sipTransactionInfo, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException { StringBuffer subscribePositionXml = new StringBuffer(200); String charset = device.getCharset(); subscribePositionXml.append("\r\n"); subscribePositionXml.append("\r\n"); subscribePositionXml.append("MobilePosition\r\n"); subscribePositionXml.append("" + (int) ((Math.random() * 9 + 1) * 100000) + "\r\n"); subscribePositionXml.append("" + device.getDeviceId() + "\r\n"); if (device.getSubscribeCycleForMobilePosition() > 0) { subscribePositionXml.append("" + device.getMobilePositionSubmissionInterval() + "\r\n"); }else { subscribePositionXml.append("5\r\n"); } subscribePositionXml.append("\r\n"); CallIdHeader callIdHeader; if (sipTransactionInfo != null) { callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(sipTransactionInfo.getCallId()); } else { callIdHeader = sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()); } int subscribeCycleForMobilePosition = device.getSubscribeCycleForMobilePosition(); if (subscribeCycleForMobilePosition > 0) { // 移动位置订阅有效期不小于 30 秒 subscribeCycleForMobilePosition = Math.max(subscribeCycleForMobilePosition, 30); } SIPRequest request = (SIPRequest) headerProvider.createSubscribeRequest(device, subscribePositionXml.toString(), sipTransactionInfo, subscribeCycleForMobilePosition, "presence",callIdHeader); //Position;id=" + tm.substring(tm.length() - 4)); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, okEvent); return request; } /** * 订阅、取消订阅报警信息 * * @param device 视频设备 * @param expires 订阅过期时间(0 = 取消订阅) * @param startPriority 报警起始级别(可选) * @param endPriority 报警终止级别(可选) * @param alarmMethod 报警方式条件(可选) * @param startTime 报警发生起始时间(可选) * @param endTime 报警发生终止时间(可选) * @return true = 命令发送成功 */ @Override public void alarmSubscribe(Device device, int expires, String startPriority, String endPriority, String alarmMethod, String startTime, String endTime) throws InvalidArgumentException, SipException, ParseException { StringBuffer cmdXml = new StringBuffer(200); String charset = device.getCharset(); cmdXml.append("\r\n"); cmdXml.append("\r\n"); cmdXml.append("Alarm\r\n"); cmdXml.append("" + (int) ((Math.random() * 9 + 1) * 100000) + "\r\n"); cmdXml.append("" + device.getDeviceId() + "\r\n"); if (!ObjectUtils.isEmpty(startPriority)) { cmdXml.append("" + startPriority + "\r\n"); } if (!ObjectUtils.isEmpty(endPriority)) { cmdXml.append("" + endPriority + "\r\n"); } if (!ObjectUtils.isEmpty(alarmMethod)) { cmdXml.append("" + alarmMethod + "\r\n"); } if (!ObjectUtils.isEmpty(startTime)) { cmdXml.append("" + startTime + "\r\n"); } if (!ObjectUtils.isEmpty(endTime)) { cmdXml.append("" + endTime + "\r\n"); } cmdXml.append("\r\n"); Request request = headerProvider.createSubscribeRequest(device, cmdXml.toString(), null, expires, "presence",sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request); } @Override public SIPRequest catalogSubscribe(Device device, SipTransactionInfo sipTransactionInfo, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException { StringBuffer cmdXml = new StringBuffer(200); String charset = device.getCharset(); cmdXml.append("\r\n"); cmdXml.append("\r\n"); cmdXml.append("Catalog\r\n"); cmdXml.append("" + (int) ((Math.random() * 9 + 1) * 100000) + "\r\n"); cmdXml.append("" + device.getDeviceId() + "\r\n"); cmdXml.append("\r\n"); CallIdHeader callIdHeader; if (sipTransactionInfo != null) { callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(sipTransactionInfo.getCallId()); } else { callIdHeader = sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()); } int subscribeCycleForCatalog = device.getSubscribeCycleForCatalog(); if (subscribeCycleForCatalog > 0) { // 目录订阅有效期不小于 30 秒 subscribeCycleForCatalog = Math.max(subscribeCycleForCatalog, 30); } // 有效时间默认为60秒以上 SIPRequest request = (SIPRequest) headerProvider.createSubscribeRequest(device, cmdXml.toString(), sipTransactionInfo, subscribeCycleForCatalog, "Catalog", callIdHeader); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, okEvent); return request; } @Override public void dragZoomCmd(Device device, String channelId, String cmdString, ErrorCallback callback) throws InvalidArgumentException, SipException, ParseException { String cmdType = "DeviceControl"; int sn = (int) ((Math.random() * 9 + 1) * 100000); StringBuffer dragXml = new StringBuffer(200); String charset = device.getCharset(); dragXml.append("\r\n"); dragXml.append("\r\n"); dragXml.append("" + cmdType + "\r\n"); dragXml.append("" + sn + "\r\n"); if (ObjectUtils.isEmpty(channelId)) { dragXml.append("" + device.getDeviceId() + "\r\n"); } else { dragXml.append("" + channelId + "\r\n"); } dragXml.append(cmdString); dragXml.append("\r\n"); MessageEvent messageEvent = MessageEvent.getInstance(cmdType, sn + "", channelId, 1000L, callback); messageSubscribe.addSubscribe(messageEvent); Request request = headerProvider.createMessageRequest(device, dragXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()),request); } /** * 回放暂停 */ @Override public void playPauseCmd(Device device, DeviceChannel channel, StreamInfo streamInfo) throws InvalidArgumentException, ParseException, SipException { StringBuffer content = new StringBuffer(200); content.append("PAUSE RTSP/1.0\r\n"); content.append("CSeq: " + getInfoCseq() + "\r\n"); content.append("PauseTime: now\r\n"); playbackControlCmd(device, channel, streamInfo, content.toString(), null, null); } /** * 回放恢复 */ @Override public void playResumeCmd(Device device, DeviceChannel channel, StreamInfo streamInfo) throws InvalidArgumentException, ParseException, SipException { StringBuffer content = new StringBuffer(200); content.append("PLAY RTSP/1.0\r\n"); content.append("CSeq: " + getInfoCseq() + "\r\n"); content.append("Range: npt=now-\r\n"); playbackControlCmd(device, channel, streamInfo, content.toString(), null, null); } /** * 回放拖动播放 */ @Override public void playSeekCmd(Device device, DeviceChannel channel, StreamInfo streamInfo, long seekTime) throws InvalidArgumentException, ParseException, SipException { StringBuffer content = new StringBuffer(200); content.append("PLAY RTSP/1.0\r\n"); content.append("CSeq: " + getInfoCseq() + "\r\n"); content.append("Range: npt=" + Math.abs(seekTime) + "-\r\n"); playbackControlCmd(device, channel, streamInfo, content.toString(), null, null); } /** * 回放倍速播放 */ @Override public void playSpeedCmd(Device device, DeviceChannel channel, StreamInfo streamInfo, Double speed) throws InvalidArgumentException, ParseException, SipException { StringBuffer content = new StringBuffer(200); content.append("PLAY RTSP/1.0\r\n"); content.append("CSeq: " + getInfoCseq() + "\r\n"); content.append("Scale: " + String.format("%.6f", speed) + "\r\n"); playbackControlCmd(device, channel, streamInfo, content.toString(), null, null); } private int getInfoCseq() { return (int) ((Math.random() * 9 + 1) * Math.pow(10, 8)); } @Override public void playbackControlCmd(Device device, DeviceChannel channel, StreamInfo streamInfo, String content, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws SipException, InvalidArgumentException, ParseException { playbackControlCmd(device, channel, streamInfo.getStream(), content, errorEvent, okEvent); } @Override public void playbackControlCmd(Device device, DeviceChannel channel, String stream, String content, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws SipException, InvalidArgumentException, ParseException { SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransactionByStream(MediaApp.GB28181, stream); if (ssrcTransaction == null) { log.info("[回放控制]未找到视频流信息,设备:{}, 流ID: {}", device.getDeviceId(), stream); return; } SIPRequest request = headerProvider.createInfoRequest(device, channel.getDeviceId(), content, ssrcTransaction.getSipTransactionInfo()); if (request == null) { log.info("[回放控制]构建Request信息失败,设备:{}, 流ID: {}", device.getDeviceId(), stream); return; } sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, okEvent); } @Override public void sendAlarmMessage(Device device, DeviceAlarm deviceAlarm) throws InvalidArgumentException, SipException, ParseException { if (device == null) { return; } log.info("[发送报警通知]设备: {}/{}->{},{}", device.getDeviceId(), deviceAlarm.getChannelId(), deviceAlarm.getLongitude(), deviceAlarm.getLatitude()); String characterSet = device.getCharset(); StringBuffer deviceStatusXml = new StringBuffer(600); deviceStatusXml.append("\r\n"); deviceStatusXml.append("\r\n"); deviceStatusXml.append("Alarm\r\n"); deviceStatusXml.append("" + (int) ((Math.random() * 9 + 1) * 100000) + "\r\n"); deviceStatusXml.append("" + deviceAlarm.getChannelId() + "\r\n"); deviceStatusXml.append("" + deviceAlarm.getAlarmPriority() + "\r\n"); deviceStatusXml.append("" + deviceAlarm.getAlarmMethod() + "\r\n"); deviceStatusXml.append("" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(deviceAlarm.getAlarmTime()) + "\r\n"); deviceStatusXml.append("" + deviceAlarm.getAlarmDescription() + "\r\n"); deviceStatusXml.append("" + deviceAlarm.getLongitude() + "\r\n"); deviceStatusXml.append("" + deviceAlarm.getLatitude() + "\r\n"); deviceStatusXml.append("\r\n"); deviceStatusXml.append("" + deviceAlarm.getAlarmType() + "\r\n"); deviceStatusXml.append("\r\n"); deviceStatusXml.append("\r\n"); Request request = headerProvider.createMessageRequest(device, deviceStatusXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()),request); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderForPlatform.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.cmd.impl; import com.alibaba.fastjson2.JSON; import com.genersoft.iot.vmp.common.InviteSessionType; import com.genersoft.iot.vmp.common.enums.MediaApp; import com.genersoft.iot.vmp.conf.DynamicTask; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException; import com.genersoft.iot.vmp.gb28181.SipLayer; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; import com.genersoft.iot.vmp.gb28181.session.SipInviteSessionManager; import com.genersoft.iot.vmp.gb28181.transmit.SIPSender; import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderPlarformProvider; import com.genersoft.iot.vmp.gb28181.utils.SipUtils; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.event.hook.Hook; import com.genersoft.iot.vmp.media.event.hook.HookSubscribe; import com.genersoft.iot.vmp.media.event.hook.HookType; import com.genersoft.iot.vmp.media.service.IMediaServerService; import com.genersoft.iot.vmp.service.IReceiveRtpServerService; import com.genersoft.iot.vmp.service.bean.GPSMsgInfo; import com.genersoft.iot.vmp.service.bean.SSRCInfo; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.utils.GitUtil; import gov.nist.javax.sip.message.MessageFactoryImpl; import gov.nist.javax.sip.message.SIPRequest; import gov.nist.javax.sip.message.SIPResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.DependsOn; import org.springframework.lang.Nullable; import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; import javax.sip.InvalidArgumentException; import javax.sip.ResponseEvent; import javax.sip.SipException; import javax.sip.SipFactory; import javax.sip.header.CallIdHeader; import javax.sip.header.WWWAuthenticateHeader; import javax.sip.message.Request; import java.text.ParseException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @Slf4j @Component @DependsOn("sipLayer") public class SIPCommanderForPlatform implements ISIPCommanderForPlatform { @Autowired private SIPRequestHeaderPlarformProvider headerProviderPlatformProvider; @Autowired private IRedisCatchStorage redisCatchStorage; @Autowired private IMediaServerService mediaServerService; @Autowired private IReceiveRtpServerService receiveRtpServerService; @Autowired private SipLayer sipLayer; @Autowired private SIPSender sipSender; @Autowired private HookSubscribe subscribe; @Autowired private UserSetting userSetting; @Autowired private SipInviteSessionManager sessionManager; @Autowired private DynamicTask dynamicTask; @Autowired private GitUtil gitUtil; @Override public void register(Platform parentPlatform, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws InvalidArgumentException, ParseException, SipException { register(parentPlatform, null, null, errorEvent, okEvent, true); } @Override public void register(Platform parentPlatform, SipTransactionInfo sipTransactionInfo, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws InvalidArgumentException, ParseException, SipException { register(parentPlatform, sipTransactionInfo, null, errorEvent, okEvent, true); } @Override public void unregister(Platform parentPlatform, SipTransactionInfo sipTransactionInfo, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws InvalidArgumentException, ParseException, SipException { register(parentPlatform, sipTransactionInfo, null, errorEvent, okEvent, false); } @Override public void register(Platform parentPlatform, @Nullable SipTransactionInfo sipTransactionInfo, @Nullable WWWAuthenticateHeader www, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent, boolean isRegister) throws SipException, InvalidArgumentException, ParseException { Request request; CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(parentPlatform.getDeviceIp(),parentPlatform.getTransport()); String fromTag = SipUtils.getNewFromTag(); String toTag = null; if (sipTransactionInfo != null ) { if (sipTransactionInfo.getCallId() != null) { callIdHeader.setCallId(sipTransactionInfo.getCallId()); } if (sipTransactionInfo.getFromTag() != null) { fromTag = sipTransactionInfo.getFromTag(); } if (sipTransactionInfo.getToTag() != null) { toTag = sipTransactionInfo.getToTag(); } } if (www == null ) { request = headerProviderPlatformProvider.createRegisterRequest(parentPlatform, redisCatchStorage.getCSEQ(), fromTag, toTag, callIdHeader, isRegister? parentPlatform.getExpires() : 0); }else { request = headerProviderPlatformProvider.createRegisterRequest(parentPlatform, fromTag, toTag, www, callIdHeader, isRegister? parentPlatform.getExpires() : 0); } sipSender.transmitRequest(parentPlatform.getDeviceIp(), request, (event)->{ if (event != null) { log.info("[国标级联]:{}, 注册失败: {} ", parentPlatform.getServerGBId(), event.msg); } if (errorEvent != null ) { errorEvent.response(event); } }, okEvent, 2000L); } @Override public String keepalive(Platform parentPlatform, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws SipException, InvalidArgumentException, ParseException { log.info("[国标级联] 发送心跳, 上级平台: {}/{}", parentPlatform.getName(), parentPlatform.getServerGBId()); String characterSet = parentPlatform.getCharacterSet(); StringBuffer keepaliveXml = new StringBuffer(200); keepaliveXml.append("\r\n") .append("\r\n") .append("Keepalive\r\n") .append("" + (int)((Math.random()*9+1)*100000) + "\r\n") .append("" + parentPlatform.getDeviceGBId() + "\r\n") .append("OK\r\n") .append("\r\n"); CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(parentPlatform.getDeviceIp(),parentPlatform.getTransport()); Request request = headerProviderPlatformProvider.createMessageRequest( parentPlatform, keepaliveXml.toString(), SipUtils.getNewFromTag(), SipUtils.getNewViaTag(), callIdHeader); sipSender.transmitRequest(parentPlatform.getDeviceIp(), request, errorEvent, okEvent); return callIdHeader.getCallId(); } /** * 向上级回复通道信息 * @param channel 通道信息 * @param parentPlatform 平台信息 */ @Override public void catalogQuery(CommonGBChannel channel, Platform parentPlatform, String sn, String fromTag, int size) throws SipException, InvalidArgumentException, ParseException { if ( parentPlatform ==null) { return ; } List channels = new ArrayList<>(); if (channel != null) { channels.add(channel); } String catalogXml = getCatalogXml(channels, sn, parentPlatform, size); // callid CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(parentPlatform.getDeviceIp(),parentPlatform.getTransport()); Request request = headerProviderPlatformProvider.createMessageRequest(parentPlatform, catalogXml.toString(), fromTag, SipUtils.getNewViaTag(), callIdHeader); sipSender.transmitRequest(parentPlatform.getDeviceIp(), request); } @Override public void catalogQuery(List channels, Platform parentPlatform, String sn, String fromTag) throws InvalidArgumentException, ParseException, SipException { if ( parentPlatform ==null) { return ; } sendCatalogResponse(channels, parentPlatform, sn, fromTag, 0, true); } private String getCatalogXml(List channels, String sn, Platform platform, int size) { String characterSet = platform.getCharacterSet(); StringBuffer catalogXml = new StringBuffer(600); catalogXml.append("\r\n") .append("\r\n") .append("Catalog\r\n") .append("" +sn + "\r\n") .append("" + platform.getDeviceGBId() + "\r\n") .append("" + size + "\r\n") .append("\r\n"); if (!channels.isEmpty()) { for (CommonGBChannel channel : channels) { catalogXml.append(channel.encode(platform.getDeviceGBId())); } } catalogXml.append("\r\n"); catalogXml.append("\r\n"); return catalogXml.toString(); } private void sendCatalogResponse(List channels, Platform parentPlatform, String sn, String fromTag, int index, boolean sendAfterResponse) throws SipException, InvalidArgumentException, ParseException { if (index > channels.size()) { return; } String catalogXml; if (channels.isEmpty()) { catalogXml = getCatalogXml(Collections.emptyList(), sn, parentPlatform, 0); }else { List subChannelList; if (index + parentPlatform.getCatalogGroup() < channels.size()) { subChannelList = channels.subList(index, index + parentPlatform.getCatalogGroup()); }else { subChannelList = channels.subList(index, channels.size()); } catalogXml = getCatalogXml(subChannelList, sn, parentPlatform, channels.size()); } // callid CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(parentPlatform.getDeviceIp(),parentPlatform.getTransport()); SIPRequest request = (SIPRequest)headerProviderPlatformProvider.createMessageRequest(parentPlatform, catalogXml, fromTag, SipUtils.getNewViaTag(), callIdHeader); String timeoutTaskKey = "catalog_task_" + parentPlatform.getServerGBId() + sn; String callId = request.getCallIdHeader().getCallId(); log.info("[命令发送] 国标级联{} 目录查询回复: 共{}条,已发送{}条", parentPlatform.getServerGBId(), channels.size(), Math.min(index + parentPlatform.getCatalogGroup(), channels.size())); log.debug(catalogXml); if (sendAfterResponse) { // 默认按照收到200回复后发送下一条, 如果超时收不到回复,就以30毫秒的间隔直接发送。 sipSender.transmitRequest(parentPlatform.getDeviceIp(), request, eventResult -> { if (eventResult.statusCode == -1024) { // 消息发送超时, 以30毫秒的间隔直接发送 int indexNext = index + parentPlatform.getCatalogGroup(); try { sendCatalogResponse(channels, parentPlatform, sn, fromTag, indexNext, false); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 国标级联 目录查询回复: {}", e.getMessage()); } return; } log.error("[目录推送失败] 国标级联 platform : {}, code: {}, msg: {}, 停止发送", parentPlatform.getServerGBId(), eventResult.statusCode, eventResult.msg); dynamicTask.stop(timeoutTaskKey); }, eventResult -> { dynamicTask.stop(timeoutTaskKey); int indexNext = index + parentPlatform.getCatalogGroup(); try { sendCatalogResponse(channels, parentPlatform, sn, fromTag, indexNext, true); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 国标级联 目录查询回复: {}", e.getMessage()); } }); }else { sipSender.transmitRequest(parentPlatform.getDeviceIp(), request, eventResult -> { log.error("[目录推送失败] 国标级联 platform : {}, code: {}, msg: {}", parentPlatform.getServerGBId(), eventResult.statusCode, eventResult.msg); }, null); dynamicTask.startDelay(timeoutTaskKey, ()->{ int indexNext = index + parentPlatform.getCatalogGroup(); try { sendCatalogResponse(channels, parentPlatform, sn, fromTag, indexNext, false); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 国标级联 目录查询回复: {}", e.getMessage()); } }, 100); } } /** * 向上级回复DeviceInfo查询信息 * @param parentPlatform 平台信息 * @param sn * @param fromTag * @return */ @Override public void deviceInfoResponse(Platform parentPlatform, Device device, String sn, String fromTag) throws SipException, InvalidArgumentException, ParseException { if (parentPlatform == null) { return; } String deviceId = device == null ? parentPlatform.getDeviceGBId() : device.getDeviceId(); String deviceName = device == null ? parentPlatform.getName() : device.getName(); String manufacturer = device == null ? "WVP-28181-PRO" : device.getManufacturer(); String model = device == null ? "platform" : device.getModel(); String firmware = device == null ? gitUtil.getBuildVersion() : device.getFirmware(); String characterSet = parentPlatform.getCharacterSet(); StringBuffer deviceInfoXml = new StringBuffer(600); deviceInfoXml.append("\r\n"); deviceInfoXml.append("\r\n"); deviceInfoXml.append("DeviceInfo\r\n"); deviceInfoXml.append("" +sn + "\r\n"); deviceInfoXml.append("" + deviceId + "\r\n"); deviceInfoXml.append("" + deviceName + "\r\n"); deviceInfoXml.append("" + manufacturer + "\r\n"); deviceInfoXml.append("" + model + "\r\n"); deviceInfoXml.append("" + firmware + "\r\n"); deviceInfoXml.append("OK\r\n"); deviceInfoXml.append("\r\n"); CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(parentPlatform.getDeviceIp(),parentPlatform.getTransport()); Request request = headerProviderPlatformProvider.createMessageRequest(parentPlatform, deviceInfoXml.toString(), fromTag, SipUtils.getNewViaTag(), callIdHeader); sipSender.transmitRequest(parentPlatform.getDeviceIp(), request); } /** * 向上级回复DeviceStatus查询信息 * @param parentPlatform 平台信息 * @param sn * @param fromTag * @return */ @Override public void deviceStatusResponse(Platform parentPlatform, String channelId, String sn, String fromTag, Boolean status) throws SipException, InvalidArgumentException, ParseException { if (parentPlatform == null) { return ; } String statusStr = null; if (status != null) { statusStr = (status)?"ONLINE":"OFFLINE"; } String characterSet = parentPlatform.getCharacterSet(); StringBuffer deviceStatusXml = new StringBuffer(600); deviceStatusXml.append("\r\n") .append("\r\n") .append("DeviceStatus\r\n") .append("" +sn + "\r\n") .append("" + channelId + "\r\n"); if (statusStr == null) { deviceStatusXml.append("ERROR\r\n"); }else { deviceStatusXml.append("OK\r\n") .append(""+statusStr+"\r\n") .append("OK\r\n"); } deviceStatusXml.append("\r\n"); CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(parentPlatform.getDeviceIp(),parentPlatform.getTransport()); Request request = headerProviderPlatformProvider.createMessageRequest(parentPlatform, deviceStatusXml.toString(), fromTag, SipUtils.getNewViaTag(), callIdHeader); sipSender.transmitRequest(parentPlatform.getDeviceIp(), request); } @Override public void sendNotifyMobilePosition(Platform parentPlatform, GPSMsgInfo gpsMsgInfo, CommonGBChannel channel, SubscribeInfo subscribeInfo) throws InvalidArgumentException, ParseException, NoSuchFieldException, SipException, IllegalAccessException { if (parentPlatform == null) { return; } log.info("[发送 移动位置订阅] {}/{}->{},{}", parentPlatform.getServerGBId(), gpsMsgInfo.getId(), gpsMsgInfo.getLng(), gpsMsgInfo.getLat()); if (log.isDebugEnabled()) { log.debug("[发送 移动位置订阅] {}/{}->{},{}", parentPlatform.getServerGBId(), gpsMsgInfo.getId(), gpsMsgInfo.getLng(), gpsMsgInfo.getLat()); } String characterSet = parentPlatform.getCharacterSet(); StringBuffer deviceStatusXml = new StringBuffer(600); deviceStatusXml.append("\r\n") .append("\r\n") .append("MobilePosition\r\n") .append("" + (int)((Math.random()*9+1)*100000) + "\r\n") .append("" + channel.getGbDeviceId() + "\r\n") .append("\r\n") .append("" + gpsMsgInfo.getLng() + "\r\n") .append("" + gpsMsgInfo.getLat() + "\r\n") .append("" + gpsMsgInfo.getSpeed() + "\r\n") .append("" + gpsMsgInfo.getDirection() + "\r\n") .append("" + gpsMsgInfo.getAltitude() + "\r\n") .append("\r\n"); sendNotify(parentPlatform, deviceStatusXml.toString(), subscribeInfo, eventResult -> { log.error("发送NOTIFY通知消息失败。错误:{} {}", eventResult.statusCode, eventResult.msg); }, null); } @Override public void sendAlarmMessage(Platform parentPlatform, DeviceAlarm deviceAlarm) throws SipException, InvalidArgumentException, ParseException { if (parentPlatform == null) { return; } log.info("[发送报警通知]平台: {}/{}->{},{}: {}", parentPlatform.getServerGBId(), deviceAlarm.getChannelId(), deviceAlarm.getLongitude(), deviceAlarm.getLatitude(), JSON.toJSONString(deviceAlarm)); String characterSet = parentPlatform.getCharacterSet(); StringBuffer deviceStatusXml = new StringBuffer(600); deviceStatusXml.append("\r\n") .append("\r\n") .append("Alarm\r\n") .append("" + (int)((Math.random()*9+1)*100000) + "\r\n") .append("" + deviceAlarm.getChannelId() + "\r\n") .append("" + deviceAlarm.getAlarmPriority() + "\r\n") .append("" + deviceAlarm.getAlarmMethod() + "\r\n") .append("" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(deviceAlarm.getAlarmTime()) + "\r\n") .append("" + deviceAlarm.getAlarmDescription() + "\r\n") .append("" + deviceAlarm.getLongitude() + "\r\n") .append("" + deviceAlarm.getLatitude() + "\r\n") .append("\r\n") .append("" + deviceAlarm.getAlarmType() + "\r\n") .append("\r\n") .append("\r\n"); CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(parentPlatform.getDeviceIp(),parentPlatform.getTransport()); Request request = headerProviderPlatformProvider.createMessageRequest(parentPlatform, deviceStatusXml.toString(), SipUtils.getNewFromTag(), SipUtils.getNewViaTag(), callIdHeader); sipSender.transmitRequest(parentPlatform.getDeviceIp(), request); } @Override public void sendNotifyForCatalogAddOrUpdate(String type, Platform parentPlatform, List deviceChannels, SubscribeInfo subscribeInfo, Integer index) throws InvalidArgumentException, ParseException, NoSuchFieldException, SipException, IllegalAccessException { if (parentPlatform == null || deviceChannels == null || deviceChannels.isEmpty() || subscribeInfo == null) { return; } if (index == null) { index = 0; } if (index >= deviceChannels.size()) { return; } List channels; if (index + parentPlatform.getCatalogGroup() < deviceChannels.size()) { channels = deviceChannels.subList(index, index + parentPlatform.getCatalogGroup()); }else { channels = deviceChannels.subList(index, deviceChannels.size()); } Integer finalIndex = index; String catalogXmlContent = getCatalogXmlContentForCatalogAddOrUpdate(parentPlatform, channels, deviceChannels.size(), type, subscribeInfo); String channelDeviceIds = channels.stream().map(CommonGBChannel::getGbDeviceId).collect(Collectors.joining(",")); log.info("[发送NOTIFY通知]类型: {},通道: {}", type, channelDeviceIds); sendNotify(parentPlatform, catalogXmlContent, subscribeInfo, eventResult -> { log.error("发送NOTIFY通知消息失败。错误:{} {}", eventResult.statusCode, eventResult.msg); log.error(catalogXmlContent); }, (eventResult -> { try { sendNotifyForCatalogAddOrUpdate(type, parentPlatform, deviceChannels, subscribeInfo, finalIndex + parentPlatform.getCatalogGroup()); } catch (InvalidArgumentException | ParseException | NoSuchFieldException | SipException | IllegalAccessException e) { log.error("[命令发送失败] 国标级联 NOTIFY通知: {}", e.getMessage()); } })); } private void sendNotify(Platform parentPlatform, String catalogXmlContent, SubscribeInfo subscribeInfo, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent ) throws SipException, ParseException, InvalidArgumentException { MessageFactoryImpl messageFactory = (MessageFactoryImpl) SipFactory.getInstance().createMessageFactory(); String characterSet = parentPlatform.getCharacterSet(); // 设置编码, 防止中文乱码 messageFactory.setDefaultContentEncodingCharset(characterSet); SIPRequest notifyRequest = headerProviderPlatformProvider.createNotifyRequest(parentPlatform, catalogXmlContent, subscribeInfo); sipSender.transmitRequest(parentPlatform.getDeviceIp(), notifyRequest, errorEvent, okEvent); } private String getCatalogXmlContentForCatalogAddOrUpdate(Platform platform, List channels, int sumNum, String type, SubscribeInfo subscribeInfo) { StringBuffer catalogXml = new StringBuffer(600); String characterSet = platform.getCharacterSet(); catalogXml.append("\r\n") .append("\r\n") .append("Catalog\r\n") .append("" + (int) ((Math.random() * 9 + 1) * 100000) + "\r\n") .append("" + platform.getDeviceGBId() + "\r\n") .append(""+ sumNum +"\r\n") .append("\r\n"); if (!channels.isEmpty()) { for (CommonGBChannel channel : channels) { catalogXml.append(channel.encode(type, platform.getDeviceGBId())); } } catalogXml.append("\r\n") .append("\r\n"); return catalogXml.toString(); } @Override public void sendNotifyForCatalogOther(String type, Platform parentPlatform, List deviceChannels, SubscribeInfo subscribeInfo, Integer index) throws InvalidArgumentException, ParseException, NoSuchFieldException, SipException, IllegalAccessException { if (parentPlatform == null || deviceChannels == null || deviceChannels.size() == 0 || subscribeInfo == null) { log.warn("[缺少必要参数]"); return; } if (index == null) { index = 0; } if (index >= deviceChannels.size()) { return; } List channels; if (index + parentPlatform.getCatalogGroup() < deviceChannels.size()) { channels = deviceChannels.subList(index, index + parentPlatform.getCatalogGroup()); }else { channels = deviceChannels.subList(index, deviceChannels.size()); } log.info("[发送NOTIFY通知]类型: {},发送数量: {}", type, channels.size()); Integer finalIndex = index; String catalogXmlContent = getCatalogXmlContentForCatalogOther(parentPlatform, channels, type); sendNotify(parentPlatform, catalogXmlContent, subscribeInfo, eventResult -> { log.error("发送NOTIFY通知消息失败。错误:{} {}", eventResult.statusCode, eventResult.msg); }, eventResult -> { try { sendNotifyForCatalogOther(type, parentPlatform, deviceChannels, subscribeInfo, finalIndex + parentPlatform.getCatalogGroup()); } catch (InvalidArgumentException | ParseException | NoSuchFieldException | SipException | IllegalAccessException e) { log.error("[命令发送失败] 国标级联 NOTIFY通知: {}", e.getMessage()); } }); } private String getCatalogXmlContentForCatalogOther(Platform platform, List channels, String type) { String characterSet = platform.getCharacterSet(); StringBuffer catalogXml = new StringBuffer(600); catalogXml.append("\r\n") .append("\r\n") .append("Catalog\r\n") .append("" + (int) ((Math.random() * 9 + 1) * 100000) + "\r\n") .append("" + platform.getDeviceGBId() + "\r\n") .append("1\r\n") .append("\r\n"); if (!channels.isEmpty()) { for (CommonGBChannel channel : channels) { catalogXml.append(channel.encode(type, platform.getDeviceGBId())); } } catalogXml.append("\r\n") .append("\r\n"); return catalogXml.toString(); } @Override public void recordInfo(CommonGBChannel deviceChannel, Platform parentPlatform, String fromTag, RecordInfo recordInfo) throws SipException, InvalidArgumentException, ParseException { if ( parentPlatform ==null) { return ; } log.info("[国标级联] 发送录像数据通道: {}", recordInfo.getChannelId()); String characterSet = parentPlatform.getCharacterSet(); StringBuffer recordXml = new StringBuffer(600); recordXml.append("\r\n") .append("\r\n") .append("RecordInfo\r\n") .append("" +recordInfo.getSn() + "\r\n") .append("" + deviceChannel.getGbDeviceId() + "\r\n") .append("" + recordInfo.getSumNum() + "\r\n"); if (recordInfo.getRecordList() == null ) { recordXml.append("\r\n"); }else { recordXml.append("\r\n"); if (recordInfo.getRecordList().size() > 0) { for (RecordItem recordItem : recordInfo.getRecordList()) { recordXml.append("\r\n"); if (deviceChannel != null) { recordXml.append("" + deviceChannel.getGbDeviceId() + "\r\n") .append("" + recordItem.getName() + "\r\n") .append("" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(recordItem.getStartTime()) + "\r\n") .append("" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(recordItem.getEndTime()) + "\r\n") .append("" + recordItem.getSecrecy() + "\r\n") .append("" + recordItem.getType() + "\r\n"); if (!ObjectUtils.isEmpty(recordItem.getFileSize())) { recordXml.append("" + recordItem.getFileSize() + "\r\n"); } if (!ObjectUtils.isEmpty(recordItem.getFilePath())) { recordXml.append("" + recordItem.getFilePath() + "\r\n"); } } recordXml.append("\r\n"); } } } recordXml.append("\r\n") .append("\r\n"); log.debug("[国标级联] 发送录像数据通道:{}, 内容: {}", recordInfo.getChannelId(), recordXml); // callid CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(parentPlatform.getDeviceIp(),parentPlatform.getTransport()); Request request = headerProviderPlatformProvider.createMessageRequest(parentPlatform, recordXml.toString(), fromTag, SipUtils.getNewViaTag(), callIdHeader); sipSender.transmitRequest(parentPlatform.getDeviceIp(), request, null, eventResult -> { log.info("[国标级联] 发送录像数据通道:{}, 发送成功", recordInfo.getChannelId()); }); } @Override public void sendMediaStatusNotify(Platform parentPlatform, SendRtpInfo sendRtpInfo, CommonGBChannel channel) throws SipException, InvalidArgumentException, ParseException { if (channel == null || parentPlatform == null) { return; } String characterSet = parentPlatform.getCharacterSet(); StringBuffer mediaStatusXml = new StringBuffer(200); mediaStatusXml.append("\r\n") .append("\r\n") .append("MediaStatus\r\n") .append("" + (int)((Math.random()*9+1)*100000) + "\r\n") .append("" + channel.getGbDeviceId() + "\r\n") .append("121\r\n") .append("\r\n"); SIPRequest messageRequest = (SIPRequest)headerProviderPlatformProvider.createMessageRequest(parentPlatform, mediaStatusXml.toString(), sendRtpInfo); sipSender.transmitRequest(parentPlatform.getDeviceIp(),messageRequest); } @Override public synchronized void streamByeCmd(Platform platform, SendRtpInfo sendRtpItem, CommonGBChannel channel) throws SipException, InvalidArgumentException, ParseException { if (sendRtpItem == null ) { log.info("[向上级发送BYE], sendRtpItem 为NULL"); return; } if (platform == null) { log.info("[向上级发送BYE], platform 为NULL"); return; } log.info("[向上级发送BYE], {}/{}", platform.getServerGBId(), sendRtpItem.getChannelId()); String mediaServerId = sendRtpItem.getMediaServerId(); MediaServer mediaServerItem = mediaServerService.getOne(mediaServerId); if (mediaServerItem != null) { mediaServerService.releaseSsrc(mediaServerItem.getId(), sendRtpItem.getSsrc()); receiveRtpServerService.closeRTPServer(mediaServerItem, sendRtpItem.getApp(), sendRtpItem.getStream()); } SIPRequest byeRequest = headerProviderPlatformProvider.createByeRequest(platform, sendRtpItem, channel); if (byeRequest == null) { log.warn("[向上级发送bye]:无法创建 byeRequest"); } sipSender.transmitRequest(platform.getDeviceIp(),byeRequest); } @Override public void streamByeCmd(Platform platform, CommonGBChannel channel, String app, String stream, String callId, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException { SsrcTransaction ssrcTransaction = null; if (callId != null) { ssrcTransaction = sessionManager.getSsrcTransactionByCallId(callId); }else if (stream != null) { ssrcTransaction = sessionManager.getSsrcTransactionByStream(app, stream); } if (ssrcTransaction == null) { throw new SsrcTransactionNotFoundException(platform.getServerGBId(), channel.getGbDeviceId(), callId, stream); } sessionManager.removeByStream(ssrcTransaction.getApp(), ssrcTransaction.getStream()); Request byteRequest = headerProviderPlatformProvider.createByteRequest(platform, channel.getGbDeviceId(), ssrcTransaction.getSipTransactionInfo()); sipSender.transmitRequest(sipLayer.getLocalIp(platform.getDeviceIp()), byteRequest, null, okEvent); } @Override public void broadcastResultCmd(Platform platform, CommonGBChannel deviceChannel, String sn, boolean result, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException { if (platform == null || deviceChannel == null) { return; } String characterSet = platform.getCharacterSet(); StringBuffer mediaStatusXml = new StringBuffer(200); mediaStatusXml.append("\r\n") .append("\r\n") .append("Broadcast\r\n") .append("" + sn + "\r\n") .append("" + deviceChannel.getGbDeviceId() + "\r\n") .append("" + (result?"OK":"ERROR") + "\r\n") .append("\r\n"); CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(platform.getDeviceIp(), platform.getTransport()); SIPRequest messageRequest = (SIPRequest)headerProviderPlatformProvider.createMessageRequest(platform, mediaStatusXml.toString(), SipUtils.getNewFromTag(), SipUtils.getNewViaTag(), callIdHeader); sipSender.transmitRequest(platform.getDeviceIp(),messageRequest, errorEvent, okEvent); } @Override public void broadcastInviteCmd(Platform platform, CommonGBChannel channel,String sourceId, MediaServer mediaServerItem, SSRCInfo ssrcInfo, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws ParseException, SipException, InvalidArgumentException { String stream = ssrcInfo.getStream(); if (platform == null) { return; } log.info("{} 分配的ZLM为: {} [{}:{}]", stream, mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort()); String sdpIp = mediaServerItem.getSdpIp(); StringBuffer content = new StringBuffer(200); content.append("v=0\r\n"); content.append("o=" + platform.getDeviceGBId() + " 0 0 IN IP4 " + sdpIp + "\r\n"); content.append("s=Play\r\n"); content.append("u=" + channel.getGbDeviceId() + ":0\r\n"); content.append("c=IN IP4 " + sdpIp + "\r\n"); content.append("t=0 0\r\n"); if ("TCP-PASSIVE".equalsIgnoreCase(userSetting.getBroadcastForPlatform())) { content.append("m=audio " + ssrcInfo.getPort() + " TCP/RTP/AVP 8 96\r\n"); } else if ("TCP-ACTIVE".equalsIgnoreCase(userSetting.getBroadcastForPlatform())) { content.append("m=audio " + ssrcInfo.getPort() + " TCP/RTP/AVP 8 96\r\n"); } else if ("UDP".equalsIgnoreCase(userSetting.getBroadcastForPlatform())) { content.append("m=audio " + ssrcInfo.getPort() + " RTP/AVP 8 96\r\n"); } content.append("a=recvonly\r\n"); content.append("a=rtpmap:8 PCMA/8000\r\n"); content.append("a=rtpmap:96 PS/90000\r\n"); if ("TCP-PASSIVE".equalsIgnoreCase(userSetting.getBroadcastForPlatform())) { content.append("a=setup:passive\r\n"); content.append("a=connection:new\r\n"); }else if ("TCP-ACTIVE".equalsIgnoreCase(userSetting.getBroadcastForPlatform())) { content.append("a=setup:active\r\n"); content.append("a=connection:new\r\n"); } content.append("y=" + ssrcInfo.getSsrc() + "\r\n");//ssrc // f字段:f= v/编码格式/分辨率/帧率/码率类型/码率大小a/编码格式/码率大小/采样率 content.append("f=v/2/5/25/1/4096a/1/8/1\r\n"); CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(sipLayer.getLocalIp(platform.getDeviceIp()), platform.getTransport()); Request request = headerProviderPlatformProvider.createInviteRequest(platform, sourceId, channel.getGbDeviceId(), content.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), ssrcInfo.getSsrc(), callIdHeader); sipSender.transmitRequest(sipLayer.getLocalIp(platform.getDeviceIp()), request, (e -> { sessionManager.removeByStream(ssrcInfo.getApp(), ssrcInfo.getStream()); mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc()); errorEvent.response(e); }), e -> { ResponseEvent responseEvent = (ResponseEvent) e.event; SIPResponse response = (SIPResponse) responseEvent.getResponse(); SsrcTransaction ssrcTransaction = SsrcTransaction.buildForPlatform(platform.getServerGBId(), channel.getGbId(), callIdHeader.getCallId(), ssrcInfo.getApp(), stream, ssrcInfo.getSsrc(), mediaServerItem.getId(), response, InviteSessionType.BROADCAST); sessionManager.put(ssrcTransaction); okEvent.response(e); }); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/ISIPRequestProcessor.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.event.request; import javax.sip.RequestEvent; /** * @description: 对SIP事件进行处理,包括request, response, timeout, ioException, transactionTerminated,dialogTerminated * @author: panlinlin * @date: 2021年11月5日 15:47 */ public interface ISIPRequestProcessor { void process(RequestEvent event); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/SIPRequestProcessorParent.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.event.request; import com.genersoft.iot.vmp.gb28181.bean.Platform; import com.genersoft.iot.vmp.gb28181.transmit.SIPSender; import com.genersoft.iot.vmp.gb28181.utils.SipUtils; import com.genersoft.iot.vmp.utils.IpPortUtil; import com.google.common.primitives.Bytes; import gov.nist.javax.sip.message.SIPRequest; import gov.nist.javax.sip.message.SIPResponse; import lombok.extern.slf4j.Slf4j; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.io.SAXReader; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.ObjectUtils; import javax.sip.*; import javax.sip.address.Address; import javax.sip.address.SipURI; import javax.sip.header.ContentTypeHeader; import javax.sip.header.ExpiresHeader; import javax.sip.header.HeaderFactory; import javax.sip.message.MessageFactory; import javax.sip.message.Request; import javax.sip.message.Response; import java.io.ByteArrayInputStream; import java.text.ParseException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * @description:处理接收IPCamera发来的SIP协议请求消息 * @author: songww * @date: 2020年5月3日 下午4:42:22 */ @Slf4j public abstract class SIPRequestProcessorParent { @Autowired private SIPSender sipSender; public HeaderFactory getHeaderFactory() { try { return SipFactory.getInstance().createHeaderFactory(); } catch (PeerUnavailableException e) { log.error("未处理的异常 ", e); } return null; } public MessageFactory getMessageFactory() { try { return SipFactory.getInstance().createMessageFactory(); } catch (PeerUnavailableException e) { log.error("未处理的异常 ", e); } return null; } class ResponseAckExtraParam{ String content; ContentTypeHeader contentTypeHeader; SipURI sipURI; int expires = -1; } /*** * 回复状态码 * 100 trying * 200 OK * 400 * 404 */ public SIPResponse responseAck(SIPRequest sipRequest, int statusCode) throws SipException, InvalidArgumentException, ParseException { return responseAck(sipRequest, statusCode, null); } public SIPResponse responseAck(SIPRequest sipRequest, int statusCode, String msg) throws SipException, InvalidArgumentException, ParseException { return responseAck(sipRequest, statusCode, msg, null); } public SIPResponse responseAck(SIPRequest sipRequest, int statusCode, String msg, ResponseAckExtraParam responseAckExtraParam) throws SipException, InvalidArgumentException, ParseException { if (sipRequest.getToHeader().getTag() == null) { sipRequest.getToHeader().setTag(SipUtils.getNewTag()); } SIPResponse response = (SIPResponse)getMessageFactory().createResponse(statusCode, sipRequest); response.setStatusCode(statusCode); if (msg != null) { response.setReasonPhrase(msg); } if (responseAckExtraParam != null) { if (responseAckExtraParam.sipURI != null && sipRequest.getMethod().equals(Request.INVITE)) { log.debug("responseSdpAck SipURI: {}:{}", responseAckExtraParam.sipURI.getHost(), responseAckExtraParam.sipURI.getPort()); Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress( SipFactory.getInstance().createAddressFactory().createSipURI(responseAckExtraParam.sipURI.getUser(), IpPortUtil.concatenateIpAndPort(responseAckExtraParam.sipURI.getHost(), String.valueOf(responseAckExtraParam.sipURI.getPort())) )); response.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress)); } if (responseAckExtraParam.contentTypeHeader != null) { response.setContent(responseAckExtraParam.content, responseAckExtraParam.contentTypeHeader); } if (sipRequest.getMethod().equals(Request.SUBSCRIBE)) { if (responseAckExtraParam.expires == -1) { log.error("[参数不全] 2xx的SUBSCRIBE回复,必须设置Expires header"); }else { ExpiresHeader expiresHeader = SipFactory.getInstance().createHeaderFactory().createExpiresHeader(responseAckExtraParam.expires); response.addHeader(expiresHeader); } } }else { if (sipRequest.getMethod().equals(Request.SUBSCRIBE)) { log.error("[参数不全] 2xx的SUBSCRIBE回复,必须设置Expires header"); } } // 发送response sipSender.transmitRequest(sipRequest.getLocalAddress().getHostAddress(), response); return response; } /** * 回复带sdp的200 */ public SIPResponse responseSdpAck(SIPRequest request, String sdp, Platform platform) throws SipException, InvalidArgumentException, ParseException { ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("APPLICATION", "SDP"); // 兼容国标中的使用编码@域名作为RequestURI的情况 SipURI sipURI = (SipURI)request.getRequestURI(); if (sipURI.getPort() == -1) { sipURI = SipFactory.getInstance().createAddressFactory().createSipURI(platform.getServerGBId(), IpPortUtil.concatenateIpAndPort(platform.getServerIp(), String.valueOf(platform.getServerPort()))); } ResponseAckExtraParam responseAckExtraParam = new ResponseAckExtraParam(); responseAckExtraParam.contentTypeHeader = contentTypeHeader; responseAckExtraParam.content = sdp; responseAckExtraParam.sipURI = sipURI; SIPResponse sipResponse = responseAck(request, Response.OK, null, responseAckExtraParam); return sipResponse; } /** * 回复带xml的200 */ public SIPResponse responseXmlAck(SIPRequest request, String xml, Platform platform, Integer expires) throws SipException, InvalidArgumentException, ParseException { ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml"); SipURI sipURI = (SipURI)request.getRequestURI(); if (sipURI.getPort() == -1) { sipURI = SipFactory.getInstance().createAddressFactory().createSipURI(platform.getServerGBId(), IpPortUtil.concatenateIpAndPort(platform.getServerIp(), String.valueOf(platform.getServerPort()))); } ResponseAckExtraParam responseAckExtraParam = new ResponseAckExtraParam(); responseAckExtraParam.contentTypeHeader = contentTypeHeader; responseAckExtraParam.content = xml; responseAckExtraParam.sipURI = sipURI; responseAckExtraParam.expires = expires; return responseAck(request, Response.OK, null, responseAckExtraParam); } public Element getRootElement(RequestEvent evt) throws DocumentException { return getRootElement(evt, "gb2312"); } public Element getRootElement(RequestEvent evt, String charset) throws DocumentException { byte[] rawContent = evt.getRequest().getRawContent(); if (evt.getRequest().getContentLength().getContentLength() == 0 || rawContent == null || rawContent.length == 0 || ObjectUtils.isEmpty(new String(rawContent))) { return null; } if (charset == null) { charset = "gb2312"; } SAXReader reader = new SAXReader(); reader.setEncoding(charset); // 对海康出现的未转义字符做处理。 String[] destStrArray = new String[]{"<",">","&","'","""}; // 或许可扩展兼容其他字符 char despChar = '&'; byte destBye = (byte) despChar; List result = new ArrayList<>(); for (int i = 0; i < rawContent.length; i++) { if (rawContent[i] == destBye) { boolean resul = false; for (String destStr : destStrArray) { if (i + destStr.length() <= rawContent.length) { byte[] bytes = Arrays.copyOfRange(rawContent, i, i + destStr.length()); resul = resul || (Arrays.equals(bytes,destStr.getBytes())); } } if (resul) { result.add(rawContent[i]); } }else { result.add(rawContent[i]); } } byte[] bytesResult = Bytes.toArray(result); Document xml; try { xml = reader.read(new ByteArrayInputStream(bytesResult)); }catch (DocumentException e) { log.warn("[xml解析异常]: 原文如下: \r\n{}", new String(bytesResult)); log.warn("[xml解析异常]: 原文如下: 尝试兼容性处理"); String[] xmlLineArray = new String(bytesResult).split("\\r?\\n"); // 兼容海康的address字段带有<破换xml结构导致无法解析xml的问题 StringBuilder stringBuilder = new StringBuilder(); for (String s : xmlLineArray) { if (s.startsWith("{}", fromUserId); SendRtpInfo sendRtpItem = sendRtpServerService.queryByCallId(callIdHeader.getCallId()); if (sendRtpItem == null) { log.warn("[收到ACK]:未找到来自{},callId: {}", fromUserId, callIdHeader.getCallId()); return; } // tcp主动时,此时是级联下级平台,在回复200ok时,本地已经请求zlm开启监听,跳过下面步骤 if (sendRtpItem.isTcpActive()) { log.info("收到ACK,rtp/{} TCP主动方式等收到上级连接后开始发流", sendRtpItem.getStream()); return; } MediaServer mediaServer = mediaServerService.getOne(sendRtpItem.getMediaServerId()); log.info("收到ACK,rtp/{}开始向上级推流, 目标={}:{},SSRC={}, 协议:{}", sendRtpItem.getStream(), sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc(), sendRtpItem.isTcp()?(sendRtpItem.isTcpActive()?"TCP主动":"TCP被动"):"UDP" ); Platform parentPlatform = platformService.queryPlatformByServerGBId(fromUserId); if (parentPlatform != null) { DeviceChannel deviceChannel = deviceChannelService.getOneForSourceById(sendRtpItem.getChannelId()); if (!userSetting.getServerId().equals(sendRtpItem.getServerId())) { WVPResult wvpResult = redisRpcService.startSendRtp(callIdHeader.getCallId(), sendRtpItem); if (wvpResult.getCode() == 0) { redisCatchStorage.sendPlatformStartPlayMsg(sendRtpItem, deviceChannel, parentPlatform); } } else { try { if (mediaServer != null) { if (sendRtpItem.isTcpActive()) { mediaServerService.startSendRtpPassive(mediaServer,sendRtpItem, null); } else { mediaServerService.startSendRtp(mediaServer, sendRtpItem); } }else { // mediaInfo 在集群的其他wvp里 } redisCatchStorage.sendPlatformStartPlayMsg(sendRtpItem, deviceChannel, parentPlatform); }catch (ControllerException e) { log.error("RTP推流失败: {}", e.getMessage()); playService.startSendRtpStreamFailHand(sendRtpItem, parentPlatform, callIdHeader); } } }else { Device device = deviceService.getDeviceByDeviceId(fromUserId); if (device == null) { log.warn("[收到ACK]:来自{},目标为({})的推流信息为找到流体服务[{}]信息",fromUserId, toUserId, sendRtpItem.getMediaServerId()); return; } // 设置为收到ACK后发送语音的设备已经在发送200OK开始发流了 if (!device.isBroadcastPushAfterAck()) { return; } if (mediaServer == null) { log.warn("[收到ACK]:来自{},目标为({})的推流信息为找到流体服务[{}]信息",fromUserId, toUserId, sendRtpItem.getMediaServerId()); return; } try { if (sendRtpItem.isTcpActive()) { mediaServerService.startSendRtpPassive(mediaServer, sendRtpItem, null); } else { mediaServerService.startSendRtp(mediaServer, sendRtpItem); } }catch (ControllerException e) { log.error("RTP推流失败: {}", e.getMessage()); playService.startSendRtpStreamFailHand(sendRtpItem, null, callIdHeader); } } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/ByeRequestProcessor.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl; import com.genersoft.iot.vmp.common.InviteInfo; import com.genersoft.iot.vmp.common.InviteSessionType; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.service.*; import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager; import com.genersoft.iot.vmp.gb28181.session.SipInviteSessionManager; import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver; import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander; import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor; import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; import com.genersoft.iot.vmp.media.bean.MediaInfo; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.service.IMediaServerService; import com.genersoft.iot.vmp.service.IReceiveRtpServerService; import com.genersoft.iot.vmp.service.ISendRtpServerService; import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcService; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import gov.nist.javax.sip.message.SIPRequest; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.sip.InvalidArgumentException; import javax.sip.RequestEvent; import javax.sip.SipException; import javax.sip.header.CallIdHeader; import javax.sip.message.Response; import java.text.ParseException; /** * SIP命令类型: BYE请求 */ @Slf4j @Component public class ByeRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor { private final String method = "BYE"; @Autowired private ISIPCommander cmder; @Autowired private ISendRtpServerService sendRtpServerService; @Autowired private IRedisCatchStorage redisCatchStorage; @Autowired private IInviteStreamService inviteStreamService; @Autowired private IPlatformService platformService; @Autowired private IDeviceService deviceService; @Autowired private IDeviceChannelService deviceChannelService; @Autowired private AudioBroadcastManager audioBroadcastManager; @Autowired private IGbChannelService channelService; @Autowired private IMediaServerService mediaServerService; @Autowired private SIPProcessorObserver sipProcessorObserver; @Autowired private SipInviteSessionManager sessionManager; @Autowired private IPlayService playService; @Autowired private UserSetting userSetting; @Autowired private IRedisRpcService redisRpcService; @Autowired private IReceiveRtpServerService receiveRtpServerService; @Override public void afterPropertiesSet() throws Exception { // 添加消息处理的订阅 sipProcessorObserver.addRequestProcessor(method, this); } /** * 处理BYE请求 */ @Override public void process(RequestEvent evt) { SIPRequest request = (SIPRequest) evt.getRequest(); try { responseAck(request, Response.OK); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[回复BYE信息失败],{}", e.getMessage()); } CallIdHeader callIdHeader = (CallIdHeader)evt.getRequest().getHeader(CallIdHeader.NAME); SendRtpInfo sendRtpItem = sendRtpServerService.queryByCallId(callIdHeader.getCallId()); // 收流端发送的停止 if (sendRtpItem != null){ CommonGBChannel channel = channelService.getOne(sendRtpItem.getChannelId()); log.info("[收到bye] 来自{},停止通道:{}, 类型: {}, callId: {}", sendRtpItem.getTargetId(), channel.getGbDeviceId(), sendRtpItem.getPlayType(), callIdHeader.getCallId()); String streamId = sendRtpItem.getStream(); log.info("[收到bye] 停止推流:{}, 媒体节点: {}", streamId, sendRtpItem.getMediaServerId()); if (sendRtpItem.getPlayType().equals(InviteStreamType.PUSH)) { // 不是本平台的就发送redis消息让其他wvp停止发流 Platform platform = platformService.queryPlatformByServerGBId(sendRtpItem.getTargetId()); if (platform != null) { redisCatchStorage.sendPlatformStopPlayMsg(sendRtpItem, platform, channel); if (!userSetting.getServerId().equals(sendRtpItem.getServerId())) { redisRpcService.stopSendRtp(sendRtpItem.getCallId()); sendRtpServerService.deleteByCallId(sendRtpItem.getCallId()); }else { MediaServer mediaServer = mediaServerService.getOne(sendRtpItem.getMediaServerId()); sendRtpServerService.deleteByCallId(callIdHeader.getCallId()); if (mediaServer != null) { mediaServerService.stopSendRtp(mediaServer, sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getSsrc()); if (userSetting.getUseCustomSsrcForParentInvite()) { mediaServerService.releaseSsrc(mediaServer.getId(), sendRtpItem.getSsrc()); } } } }else { log.info("[上级平台停止观看] 未找到平台{}的信息,发送redis消息失败", sendRtpItem.getTargetId()); } }else { MediaServer mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId()); sendRtpServerService.delete(sendRtpItem); mediaServerService.stopSendRtp(mediaInfo, sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getSsrc()); if (userSetting.getUseCustomSsrcForParentInvite()) { mediaServerService.releaseSsrc(mediaInfo.getId(), sendRtpItem.getSsrc()); } } if (sendRtpItem.getServerId().equals(userSetting.getServerId())) { MediaServer mediaServer = mediaServerService.getOne(sendRtpItem.getMediaServerId()); if (mediaServer != null) { AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(sendRtpItem.getChannelId()); if (audioBroadcastCatch != null && audioBroadcastCatch.getSipTransactionInfo().getCallId().equals(callIdHeader.getCallId())) { // 来自上级平台的停止对讲 log.info("[停止对讲] 来自上级,平台:{}, 通道:{}", sendRtpItem.getTargetId(), sendRtpItem.getChannelId()); audioBroadcastManager.del(sendRtpItem.getChannelId()); } MediaInfo mediaInfo = mediaServerService.getMediaInfo(mediaServer, sendRtpItem.getApp(), streamId); if (mediaInfo != null && mediaInfo.getReaderCount() <= 0) { log.info("[收到bye] {} 无其它观看者,通知设备停止推流", streamId); if (sendRtpItem.getPlayType().equals(InviteStreamType.PLAY)) { Device device = deviceService.getDeviceByDeviceId(sendRtpItem.getTargetId()); if (device == null) { log.info("[收到bye] {} 通知设备停止推流时未找到设备信息", streamId); return; } DeviceChannel deviceChannel = deviceChannelService.getOneForSourceById(sendRtpItem.getChannelId()); if (deviceChannel == null) { log.info("[收到bye] {} 通知设备停止推流时未找到通道信息", streamId); return; } try { log.info("[停止点播] {}/{}", sendRtpItem.getTargetId(), sendRtpItem.getChannelId()); cmder.streamByeCmd(device, deviceChannel.getDeviceId(), sendRtpItem.getApp(), sendRtpItem.getStream(), null, null); } catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) { log.error("[收到bye] {} 无其它观看者,通知设备停止推流, 发送BYE失败 {}",streamId, e.getMessage()); } } } } } else { // TODO 流再其他wvp上时应该通知这个wvp停止推流和发送BYE } } // 可能是设备发送的停止 SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransactionByCallId(callIdHeader.getCallId()); if (ssrcTransaction == null) { return; } log.info("[收到bye] 来自:{}, 通道: {}, 类型: {}", ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getType()); // TODO 结束点播 避免等待 if (ssrcTransaction.getPlatformId() != null ) { Platform platform = platformService.queryPlatformByServerGBId(ssrcTransaction.getPlatformId()); if (ssrcTransaction.getType().equals(InviteSessionType.BROADCAST)) { log.info("[收到bye] 上级停止语音对讲,来自:{}, 通道已停止推流: {}", ssrcTransaction.getPlatformId(), ssrcTransaction.getChannelId()); CommonGBChannel channel = channelService.getOne(ssrcTransaction.getChannelId()); if (channel == null) { log.info("[收到bye] 未找到通道,上级:{}, 通道:{}", ssrcTransaction.getPlatformId(), ssrcTransaction.getChannelId()); return; } String mediaServerId = ssrcTransaction.getMediaServerId(); platformService.stopBroadcast(platform, channel, ssrcTransaction.getApp(), ssrcTransaction.getStream(), false, mediaServerService.getOne(mediaServerId)); DeviceChannel deviceChannel = deviceChannelService.getOneForSourceById(channel.getGbId()); Device device = deviceService.getDevice(channel.getDataDeviceId()); playService.stopAudioBroadcast(device, deviceChannel); } }else { Device device = deviceService.getDeviceByDeviceId(ssrcTransaction.getDeviceId()); if (device == null) { log.info("[收到bye] 未找到设备:{} ", ssrcTransaction.getDeviceId()); return; } DeviceChannel channel = deviceChannelService.getOneForSourceById(ssrcTransaction.getChannelId()); if (channel == null) { log.info("[收到bye] 未找到通道,设备:{}, 通道:{}", ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId()); return; } switch (ssrcTransaction.getType()){ case PLAY: case PLAYBACK: case DOWNLOAD: InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, channel.getId()); if (inviteInfo != null) { deviceChannelService.stopPlay(channel.getId()); inviteStreamService.removeInviteInfo(inviteInfo); if (inviteInfo.getStreamInfo() != null) { receiveRtpServerService.closeRTPServer(inviteInfo.getStreamInfo().getMediaServer(), inviteInfo.getStreamInfo().getApp(), inviteInfo.getStreamInfo().getStream()); } } break; case BROADCAST: case TALK: // 查找来源的对讲设备,发送停止 Device sourceDevice = deviceService.getDeviceByChannelId(ssrcTransaction.getChannelId()); AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(channel.getId()); if (sourceDevice != null) { playService.stopAudioBroadcast(sourceDevice, channel); } if (audioBroadcastCatch != null) { // 来自上级平台的停止对讲 log.info("[停止对讲] 来自上级,平台:{}, 通道:{}", ssrcTransaction.getDeviceId(), channel.getDeviceId()); audioBroadcastManager.del(channel.getId()); } break; } // 释放ssrc MediaServer mediaServerItem = mediaServerService.getOne(ssrcTransaction.getMediaServerId()); if (mediaServerItem != null) { mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcTransaction.getSsrc()); } sessionManager.removeByCallId(ssrcTransaction.getCallId()); } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/CancelRequestProcessor.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl; import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver; import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor; import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.sip.RequestEvent; /** * SIP命令类型: CANCEL请求 */ @Component public class CancelRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor { private final String method = "CANCEL"; @Autowired private SIPProcessorObserver sipProcessorObserver; @Override public void afterPropertiesSet() throws Exception { // 添加消息处理的订阅 sipProcessorObserver.addRequestProcessor(method, this); } /** * 处理CANCEL请求 * * @param evt 事件 */ @Override public void process(RequestEvent evt) { // TODO 优先级99 Cancel Request消息实现,此消息一般为级联消息,上级给下级发送请求取消指令 } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl; import com.genersoft.iot.vmp.common.InviteSessionType; import com.genersoft.iot.vmp.common.VideoManagerConstants; import com.genersoft.iot.vmp.common.enums.ChannelDataType; import com.genersoft.iot.vmp.conf.DynamicTask; import com.genersoft.iot.vmp.conf.SipConfig; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.service.*; import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager; import com.genersoft.iot.vmp.gb28181.session.SSRCFactory; import com.genersoft.iot.vmp.gb28181.session.SipInviteSessionManager; import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver; import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor; import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; import com.genersoft.iot.vmp.gb28181.utils.SipUtils; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.service.IMediaServerService; import com.genersoft.iot.vmp.service.ISendRtpServerService; import com.genersoft.iot.vmp.service.bean.InviteErrorCode; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import gov.nist.javax.sdp.TimeDescriptionImpl; import gov.nist.javax.sdp.fields.TimeField; import gov.nist.javax.sdp.fields.URIField; import gov.nist.javax.sip.message.SIPRequest; import gov.nist.javax.sip.message.SIPResponse; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.sdp.*; import javax.sip.InvalidArgumentException; import javax.sip.RequestEvent; import javax.sip.SipException; import javax.sip.header.CallIdHeader; import javax.sip.message.Response; import java.text.ParseException; import java.util.List; import java.util.Vector; /** * SIP命令类型: INVITE请求 */ @Slf4j @SuppressWarnings("rawtypes") @Component public class InviteRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor { private final String method = "INVITE"; @Autowired private ISIPCommanderForPlatform cmderFroPlatform; @Autowired private IDeviceChannelService deviceChannelService; @Autowired private IDeviceService deviceService; @Autowired private IGbChannelService channelService; @Autowired private IGbChannelPlayService channelPlayService; @Autowired private ISendRtpServerService sendRtpServerService; @Autowired private IRedisCatchStorage redisCatchStorage; @Autowired private IMediaServerService mediaServerService; @Autowired private DynamicTask dynamicTask; @Autowired private IPlayService playService; @Autowired private IPlatformService platformService; @Autowired private AudioBroadcastManager audioBroadcastManager; @Autowired private SIPProcessorObserver sipProcessorObserver; @Autowired private SipConfig config; @Autowired private SipInviteSessionManager sessionManager; @Autowired private UserSetting userSetting; @Autowired private SSRCFactory ssrcFactory; @Override public void afterPropertiesSet() throws Exception { // 添加消息处理的订阅 sipProcessorObserver.addRequestProcessor(method, this); } /** * 处理invite请求 * * @param evt 请求消息 */ @Override public void process(RequestEvent evt) { SIPRequest request = (SIPRequest)evt.getRequest(); try { InviteMessageInfo inviteInfo = decode(evt); // 查询请求是否来自上级平台\设备 Platform platform = platformService.queryPlatformByServerGBId(inviteInfo.getRequesterId()); if (platform == null) { inviteFromDeviceHandle(request, inviteInfo); } else { // 查询平台下是否有该通道 CommonGBChannel channel= channelService.queryOneWithPlatform(platform.getId(), inviteInfo.getTargetChannelId()); if (channel == null) { log.info("[上级INVITE] 通道不存在,返回404: {}", inviteInfo.getTargetChannelId()); try { // 通道不存在,发404,资源不存在 responseAck(request, Response.NOT_FOUND); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] invite 通道不存在: {}", e.getMessage()); } return; } log.info("[上级INVITE] 平台:{}, 通道:{}({}), 收流地址:{}:{},收流方式:{}, 点播类型:{}, SSRC:{}", platform.getName(), channel.getGbName(), channel.getGbDeviceId(), inviteInfo.getIp(), inviteInfo.getPort(), inviteInfo.isTcp()?(inviteInfo.isTcpActive()?"TCP主动":"TCP被动"): "UDP", inviteInfo.getSessionName(), inviteInfo.getSsrc()); if(!userSetting.getUseCustomSsrcForParentInvite() && ObjectUtils.isEmpty(inviteInfo.getSsrc())) { log.warn("[上级INVITE] 点播失败, 上级未携带SSRC, 并且本级未设置使用自定义SSRC"); // 通道存在,发100,TRYING try { responseAck(request, Response.BAD_REQUEST); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 上级INVITE TRYING: {}", e.getMessage()); } return; } // 通道存在,发100,TRYING try { responseAck(request, Response.TRYING); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 上级INVITE TRYING: {}", e.getMessage()); } channelPlayService.startInvite(channel, inviteInfo, platform, ((code, msg, streamInfo) -> { if (code != InviteErrorCode.SUCCESS.getCode()) { try { responseAck(request, Response.BUSY_HERE , msg); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 上级INVITE 点播失败: {}", e.getMessage()); } }else { // 点播成功, TODO 可以在此处检测cancel命令是否存在,存在则不发送 if (userSetting.getUseCustomSsrcForParentInvite()) { // 上级平台点播时不使用上级平台指定的ssrc,使用自定义的ssrc,参考国标文档-点播外域设备媒体流SSRC处理方式 MediaServer mediaServer = mediaServerService.getOne(streamInfo.getMediaServer().getId()); if (mediaServer != null) { String ssrc = "Play".equalsIgnoreCase(inviteInfo.getSessionName()) ? ssrcFactory.getPlaySsrc(streamInfo.getMediaServer().getId()) : ssrcFactory.getPlayBackSsrc(streamInfo.getMediaServer().getId()); inviteInfo.setSsrc(ssrc); } } // 构建sendRTP内容 SendRtpInfo sendRtpItem = sendRtpServerService.createSendRtpInfo(streamInfo.getMediaServer(), inviteInfo.getIp(), inviteInfo.getPort(), inviteInfo.getSsrc(), platform.getServerGBId(), streamInfo.getApp(), streamInfo.getStream(), channel.getGbId(), inviteInfo.isTcp(), platform.isRtcp()); if (inviteInfo.isTcp() && inviteInfo.isTcpActive()) { sendRtpItem.setTcpActive(true); } sendRtpItem.setStatus(1); sendRtpItem.setCallId(inviteInfo.getCallId()); sendRtpItem.setPlayTypeByChannelDataType(channel.getDataType(), inviteInfo.getSessionName()); sendRtpItem.setServerId(streamInfo.getServerId()); sendRtpServerService.update(sendRtpItem); String sdpIp = streamInfo.getMediaServer().getSdpIp(); if (!ObjectUtils.isEmpty(platform.getSendStreamIp())) { sdpIp = platform.getSendStreamIp(); } String content = createSendSdp(sendRtpItem, inviteInfo, sdpIp); // 超时未收到Ack应该回复bye,当前等待时间为10秒 dynamicTask.startDelay(inviteInfo.getCallId(), () -> { log.info("[Ack ] 等待超时, {}/{}", inviteInfo.getCallId(), channel.getGbDeviceId()); mediaServerService.releaseSsrc(streamInfo.getMediaServer().getId(), sendRtpItem.getSsrc()); // 回复bye sendBye(platform, inviteInfo.getCallId()); }, 60 * 1000); try { responseSdpAck(request, content, platform); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 上级INVITE 发送 200(SDP): {}", e.getMessage()); } // tcp主动模式,回复sdp后开启监听 if (sendRtpItem.isTcpActive()) { MediaServer mediaServer = mediaServerService.getOne(sendRtpItem.getMediaServerId()); try { mediaServerService.startSendRtpPassive(mediaServer, sendRtpItem, 5); DeviceChannel deviceChannel = deviceChannelService.getOneForSourceById(sendRtpItem.getChannelId()); if (deviceChannel != null) { redisCatchStorage.sendPlatformStartPlayMsg(sendRtpItem, deviceChannel, platform); } }catch (ControllerException e) { log.warn("[上级INVITE] tcp主动模式 发流失败", e); sendBye(platform, inviteInfo.getCallId()); } } } })); } } catch (SdpException e) { // 参数不全, 发400,请求错误 try { responseAck(request, Response.BAD_REQUEST); } catch (SipException | InvalidArgumentException | ParseException sendException) { log.error("[命令发送失败] invite BAD_REQUEST: {}", sendException.getMessage()); } } catch (InviteDecodeException e) { try { responseAck(request, e.getCode(), e.getMsg()); } catch (SipException | InvalidArgumentException | ParseException sendException) { log.error("[命令发送失败] invite BAD_REQUEST: {}", sendException.getMessage()); } }catch (PlayException e) { try { responseAck(request, e.getCode(), e.getMsg()); } catch (SipException | InvalidArgumentException | ParseException sendException) { log.error("[命令发送失败] invite 点播失败: {}", sendException.getMessage()); } }catch (Exception e) { log.error("[Invite处理异常] ", e); try { responseAck(request, Response.SERVER_INTERNAL_ERROR, ""); } catch (SipException | InvalidArgumentException | ParseException sendException) { log.error("[命令发送失败] invite 点播失败: {}", sendException.getMessage()); } } } private InviteMessageInfo decode(RequestEvent evt) throws SdpException { InviteMessageInfo inviteInfo = new InviteMessageInfo(); SIPRequest request = (SIPRequest)evt.getRequest(); String[] channelIdArrayFromSub = SipUtils.getChannelIdFromRequest(request); // 解析sdp消息, 使用jainsip 自带的sdp解析方式 String contentString = new String(request.getRawContent()); Gb28181Sdp gb28181Sdp = SipUtils.parseSDP(contentString); SessionDescription sdp = gb28181Sdp.getBaseSdb(); String sessionName = sdp.getSessionName().getValue(); String channelIdFromSdp = null; if(StringUtils.equalsIgnoreCase("Playback", sessionName)){ URIField uriField = (URIField)sdp.getURI(); channelIdFromSdp = uriField.getURI().split(":")[0]; } final String channelId = StringUtils.isNotBlank(channelIdFromSdp) ? channelIdFromSdp : (channelIdArrayFromSub != null? channelIdArrayFromSub[0]: null); String requesterId = SipUtils.getUserIdFromFromHeader(request); CallIdHeader callIdHeader = (CallIdHeader) request.getHeader(CallIdHeader.NAME); if (requesterId == null || channelId == null) { log.warn("[解析INVITE消息] 无法从请求中获取到来源id,返回400错误"); throw new InviteDecodeException(Response.BAD_REQUEST, "request decode fail"); } log.info("[INVITE] 来源ID: {}, callId: {}, 来自:{}:{}", requesterId, callIdHeader.getCallId(), request.getRemoteAddress(), request.getRemotePort()); inviteInfo.setRequesterId(requesterId); inviteInfo.setTargetChannelId(channelId); if (channelIdArrayFromSub != null && channelIdArrayFromSub.length == 2) { inviteInfo.setSourceChannelId(channelIdArrayFromSub[1]); } inviteInfo.setSessionName(sessionName); inviteInfo.setSsrc(gb28181Sdp.getSsrc()); inviteInfo.setCallId(callIdHeader.getCallId()); // 如果是录像回放,则会存在录像的开始时间与结束时间 Long startTime = null; Long stopTime = null; if (sdp.getTimeDescriptions(false) != null && !sdp.getTimeDescriptions(false).isEmpty()) { TimeDescriptionImpl timeDescription = (TimeDescriptionImpl) (sdp.getTimeDescriptions(false).get(0)); TimeField startTimeFiled = (TimeField) timeDescription.getTime(); startTime = startTimeFiled.getStartTime(); stopTime = startTimeFiled.getStopTime(); } // 获取支持的格式 Vector mediaDescriptions = sdp.getMediaDescriptions(true); // 查看是否支持PS 负载96 //String ip = null; int port = -1; boolean mediaTransmissionTCP = false; Boolean tcpActive = null; for (Object description : mediaDescriptions) { MediaDescription mediaDescription = (MediaDescription) description; Media media = mediaDescription.getMedia(); Vector mediaFormats = media.getMediaFormats(false); if (mediaFormats.contains("96") || mediaFormats.contains("8")) { port = media.getMediaPort(); //String mediaType = media.getMediaType(); String protocol = media.getProtocol(); // 区分TCP发流还是udp, 当前默认udp if ("TCP/RTP/AVP".equalsIgnoreCase(protocol)) { String setup = mediaDescription.getAttribute("setup"); if (setup != null) { mediaTransmissionTCP = true; if ("active".equalsIgnoreCase(setup)) { tcpActive = true; } else if ("passive".equalsIgnoreCase(setup)) { tcpActive = false; } } } break; } } if (port == -1) { log.info("[解析INVITE消息] 不支持的媒体格式,返回415"); throw new InviteDecodeException(Response.UNSUPPORTED_MEDIA_TYPE, "unsupported media type"); } inviteInfo.setTcp(mediaTransmissionTCP); inviteInfo.setTcpActive(tcpActive != null? tcpActive: false); inviteInfo.setStartTime(startTime); inviteInfo.setStopTime(stopTime); Vector sdpMediaDescriptions = sdp.getMediaDescriptions(true); MediaDescription mediaDescription = null; String downloadSpeed = "1"; if (!sdpMediaDescriptions.isEmpty()) { mediaDescription = (MediaDescription) sdpMediaDescriptions.get(0); } if (mediaDescription != null) { downloadSpeed = mediaDescription.getAttribute("downloadspeed"); } inviteInfo.setIp(sdp.getConnection().getAddress()); inviteInfo.setPort(port); inviteInfo.setDownloadSpeed(downloadSpeed); return inviteInfo; } private String createSendSdp(SendRtpInfo sendRtpItem, InviteMessageInfo inviteInfo, String sdpIp) { StringBuilder content = new StringBuilder(200); content.append("v=0\r\n"); content.append("o=" + inviteInfo.getTargetChannelId() + " 0 0 IN IP4 " + sdpIp + "\r\n"); content.append("s=" + inviteInfo.getSessionName() + "\r\n"); content.append("c=IN IP4 " + sdpIp + "\r\n"); if ("Playback".equalsIgnoreCase(inviteInfo.getSessionName())) { content.append("t=" + inviteInfo.getStartTime() + " " + inviteInfo.getStopTime() + "\r\n"); } else { content.append("t=0 0\r\n"); } if (sendRtpItem.isTcp()) { content.append("m=video " + sendRtpItem.getLocalPort() + " TCP/RTP/AVP 96\r\n"); if (!sendRtpItem.isTcpActive()) { content.append("a=setup:active\r\n"); } else { content.append("a=setup:passive\r\n"); } }else { content.append("m=video " + sendRtpItem.getLocalPort() + " RTP/AVP 96\r\n"); } content.append("a=sendonly\r\n"); content.append("a=rtpmap:96 PS/90000\r\n"); content.append("y=" + sendRtpItem.getSsrc() + "\r\n"); content.append("f=\r\n"); return content.toString(); } private void sendBye(Platform platform, String callId) { try { SendRtpInfo sendRtpItem = sendRtpServerService.queryByCallId(callId); if (sendRtpItem == null) { return; } CommonGBChannel channel = channelService.getOne(sendRtpItem.getChannelId()); if (channel == null) { return; } cmderFroPlatform.streamByeCmd(platform, sendRtpItem, channel); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 上级INVITE 发送BYE: {}", e.getMessage()); } } public void inviteFromDeviceHandle(SIPRequest request, InviteMessageInfo inviteInfo) { if (inviteInfo.getSourceChannelId() == null) { log.warn("来自设备的Invite请求,无法从请求信息中确定请求来自的通道,已忽略,requesterId: {}", inviteInfo.getRequesterId()); try { responseAck(request, Response.FORBIDDEN); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 来自设备的Invite请求,无法从请求信息中确定所属设备 FORBIDDEN: {}", e.getMessage()); } return; } // 非上级平台请求,查询是否设备请求(通常为接收语音广播的设备) Device device = redisCatchStorage.getDevice(inviteInfo.getRequesterId()); // 判断requesterId是设备还是通道 if (device == null) { device = deviceService.getDeviceBySourceChannelDeviceId(inviteInfo.getRequesterId()); } if (device == null) { // 检查channelID是否可用 device = deviceService.getDeviceBySourceChannelDeviceId(inviteInfo.getSourceChannelId()); } if (device == null) { log.warn("来自设备的Invite请求,无法从请求信息中确定所属设备,已忽略,requesterId: {}/{}", inviteInfo.getRequesterId(), inviteInfo.getSourceChannelId()); try { responseAck(request, Response.FORBIDDEN); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 来自设备的Invite请求,无法从请求信息中确定所属设备 FORBIDDEN: {}", e.getMessage()); } return; } DeviceChannel deviceChannel = deviceChannelService.getOne(device.getDeviceId(), inviteInfo.getSourceChannelId()); if (deviceChannel == null) { List audioBroadcastCatchList = audioBroadcastManager.getByDeviceId(device.getDeviceId()); if (audioBroadcastCatchList.isEmpty()) { log.warn("来自设备的Invite请求,无法从请求信息中确定所属通道,已忽略,requesterId: {}/{}", inviteInfo.getRequesterId(), inviteInfo.getSourceChannelId()); try { responseAck(request, Response.FORBIDDEN); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 来自设备的Invite请求,无法从请求信息中确定所属设备 FORBIDDEN: {}", e.getMessage()); } return; }else { deviceChannel = deviceChannelService.getOneForSourceById(audioBroadcastCatchList.get(0).getChannelId()); } } AudioBroadcastCatch broadcastCatch = audioBroadcastManager.get(deviceChannel.getId()); if (broadcastCatch == null) { log.warn("来自设备的Invite请求非语音广播,已忽略,requesterId: {}/{}", inviteInfo.getRequesterId(), inviteInfo.getSourceChannelId()); try { responseAck(request, Response.FORBIDDEN); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 来自设备的Invite请求非语音广播 FORBIDDEN: {}", e.getMessage()); } return; } log.info("收到设备" + inviteInfo.getRequesterId() + "的语音广播Invite请求"); String key = VideoManagerConstants.BROADCAST_WAITE_INVITE + device.getDeviceId(); if (!SipUtils.isFrontEnd(device.getDeviceId())) { key += broadcastCatch.getChannelId(); } dynamicTask.stop(key); try { responseAck(request, Response.TRYING); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] invite BAD_REQUEST: {}", e.getMessage()); playService.stopAudioBroadcast(device, deviceChannel); return; } String contentString = new String(request.getRawContent()); try { Gb28181Sdp gb28181Sdp = SipUtils.parseSDP(contentString); SessionDescription sdp = gb28181Sdp.getBaseSdb(); // 获取支持的格式 Vector mediaDescriptions = sdp.getMediaDescriptions(true); // 查看是否支持PS 负载96 int port = -1; boolean mediaTransmissionTCP = false; Boolean tcpActive = null; for (int i = 0; i < mediaDescriptions.size(); i++) { MediaDescription mediaDescription = (MediaDescription) mediaDescriptions.get(i); Media media = mediaDescription.getMedia(); Vector mediaFormats = media.getMediaFormats(false); // if (mediaFormats.contains("8")) { port = media.getMediaPort(); String protocol = media.getProtocol(); // 区分TCP发流还是udp, 当前默认udp if ("TCP/RTP/AVP".equals(protocol)) { String setup = mediaDescription.getAttribute("setup"); if (setup != null) { mediaTransmissionTCP = true; if ("active".equals(setup)) { tcpActive = true; } else if ("passive".equals(setup)) { tcpActive = false; } } } break; // } } if (port == -1) { log.info("不支持的媒体格式,返回415"); // 回复不支持的格式 try { responseAck(request, Response.UNSUPPORTED_MEDIA_TYPE); // 不支持的格式,发415 } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] invite 不支持的媒体格式: {}", e.getMessage()); playService.stopAudioBroadcast(device, deviceChannel); return; } return; } String addressStr = sdp.getOrigin().getAddress(); log.info("设备{}请求语音流,地址:{}:{},ssrc:{}, {}", inviteInfo.getRequesterId(), addressStr, port, gb28181Sdp.getSsrc(), mediaTransmissionTCP ? (tcpActive ? "TCP主动" : "TCP被动") : "UDP"); MediaServer mediaServerItem = broadcastCatch.getMediaServerItem(); if (mediaServerItem == null) { log.warn("未找到语音喊话使用的zlm"); try { responseAck(request, Response.BUSY_HERE); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] invite 未找到可用的zlm: {}", e.getMessage()); playService.stopAudioBroadcast(device, deviceChannel); } return; } log.info("设备{}请求语音流, 收流地址:{}:{},ssrc:{}, {}, 对讲方式:{}", inviteInfo.getRequesterId(), addressStr, port, gb28181Sdp.getSsrc(), mediaTransmissionTCP ? (tcpActive ? "TCP主动" : "TCP被动") : "UDP", sdp.getSessionName().getValue()); CallIdHeader callIdHeader = (CallIdHeader) request.getHeader(CallIdHeader.NAME); SendRtpInfo sendRtpItem = sendRtpServerService.createSendRtpInfo(mediaServerItem, addressStr, port, gb28181Sdp.getSsrc(), inviteInfo.getRequesterId(), device.getDeviceId(), deviceChannel.getId(), mediaTransmissionTCP, false); if (sendRtpItem == null) { log.warn("服务器端口资源不足"); try { responseAck(request, Response.BUSY_HERE); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] invite 服务器端口资源不足: {}", e.getMessage()); playService.stopAudioBroadcast(device, deviceChannel); return; } return; } sendRtpItem.setPlayType(InviteStreamType.BROADCAST); sendRtpItem.setCallId(callIdHeader.getCallId()); sendRtpItem.setStatus(1); sendRtpItem.setApp(broadcastCatch.getApp()); sendRtpItem.setStream(broadcastCatch.getStream()); sendRtpItem.setPt(8); sendRtpItem.setUsePs(false); sendRtpItem.setRtcp(false); sendRtpItem.setOnlyAudio(true); sendRtpItem.setTcp(mediaTransmissionTCP); if (tcpActive != null) { sendRtpItem.setTcpActive(tcpActive); } sendRtpServerService.update(sendRtpItem); Boolean streamReady = mediaServerService.isStreamReady(mediaServerItem, broadcastCatch.getApp(), broadcastCatch.getStream()); if (streamReady) { sendOk(device, deviceChannel, sendRtpItem, sdp, request, mediaServerItem, mediaTransmissionTCP, gb28181Sdp.getSsrc()); } else { log.warn("[语音通话], 未发现待推送的流,app={},stream={}", broadcastCatch.getApp(), broadcastCatch.getStream()); try { responseAck(request, Response.GONE); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 语音通话 回复410失败, {}", e.getMessage()); return; } playService.stopAudioBroadcast(device, deviceChannel); } } catch (SdpException e) { log.error("[SDP解析异常]", e); playService.stopAudioBroadcast(device, deviceChannel); } } SIPResponse sendOk(Device device, DeviceChannel channel, SendRtpInfo sendRtpItem, SessionDescription sdp, SIPRequest request, MediaServer mediaServerItem, boolean mediaTransmissionTCP, String ssrc) { SIPResponse sipResponse = null; try { sendRtpItem.setStatus(2); sendRtpServerService.update(sendRtpItem); StringBuffer content = new StringBuffer(200); content.append("v=0\r\n"); content.append("o=" + config.getId() + " " + sdp.getOrigin().getSessionId() + " " + sdp.getOrigin().getSessionVersion() + " 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"); if (mediaTransmissionTCP) { content.append("m=audio " + sendRtpItem.getLocalPort() + " TCP/RTP/AVP 8\r\n"); } else { content.append("m=audio " + sendRtpItem.getLocalPort() + " RTP/AVP 8\r\n"); } content.append("a=rtpmap:8 PCMA/8000/1\r\n"); content.append("a=sendonly\r\n"); if (sendRtpItem.isTcp()) { content.append("a=connection:new\r\n"); if (!sendRtpItem.isTcpActive()) { content.append("a=setup:active\r\n"); } else { content.append("a=setup:passive\r\n"); } } content.append("y=" + ssrc + "\r\n"); content.append("f=v/////a/1/8/1\r\n"); Platform parentPlatform = new Platform(); parentPlatform.setServerIp(device.getIp()); parentPlatform.setServerPort(device.getPort()); parentPlatform.setServerGBId(device.getDeviceId()); sipResponse = responseSdpAck(request, content.toString(), parentPlatform); AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(sendRtpItem.getChannelId()); audioBroadcastCatch.setStatus(AudioBroadcastCatchStatus.Ok); audioBroadcastCatch.setSipTransactionInfoByRequest(sipResponse); audioBroadcastManager.update(audioBroadcastCatch); SsrcTransaction ssrcTransaction = SsrcTransaction.buildForDevice(device.getDeviceId(), sendRtpItem.getChannelId(), request.getCallIdHeader().getCallId(), sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getSsrc(), sendRtpItem.getMediaServerId(), sipResponse, InviteSessionType.BROADCAST); sessionManager.put(ssrcTransaction); // 开启发流,大华在收到200OK后就会开始建立连接 if (sendRtpItem.isTcpActive() || !device.isBroadcastPushAfterAck()) { if (sendRtpItem.isTcpActive()) { log.info("[语音喊话] 监听端口等待设备连接后推流"); }else { log.info("[语音喊话] 回复200OK后发现 BroadcastPushAfterAck为False,现在开始推流"); } playService.startPushStream(sendRtpItem, channel, sipResponse, parentPlatform, request.getCallIdHeader()); } } catch (SipException | InvalidArgumentException | ParseException | SdpParseException e) { log.error("[命令发送失败] 语音喊话 回复200OK(SDP): {}", e.getMessage()); } return sipResponse; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/NotifyRequestForCatalogProcessor.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.event.EventPublisher; import com.genersoft.iot.vmp.gb28181.event.channel.ChannelEvent; import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent; import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService; import com.genersoft.iot.vmp.gb28181.service.IGbChannelService; import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; import com.genersoft.iot.vmp.gb28181.utils.SipUtils; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.utils.Coordtransform; import com.genersoft.iot.vmp.utils.DateUtil; import lombok.extern.slf4j.Slf4j; import org.dom4j.DocumentException; import org.dom4j.Element; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; import javax.sip.RequestEvent; import javax.sip.header.FromHeader; import java.lang.reflect.InvocationTargetException; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; /** * SIP命令类型: NOTIFY请求中的目录请求处理 */ @Slf4j @Component public class NotifyRequestForCatalogProcessor extends SIPRequestProcessorParent { private final ConcurrentLinkedQueue channelList = new ConcurrentLinkedQueue<>(); private final ConcurrentLinkedQueue taskQueue = new ConcurrentLinkedQueue<>(); @Autowired private UserSetting userSetting; @Autowired private EventPublisher eventPublisher; @Autowired private IRedisCatchStorage redisCatchStorage; @Autowired private IDeviceChannelService deviceChannelService; @Autowired private IGbChannelService channelService; // @Scheduled(fixedRate = 2000) //每400毫秒执行一次 // public void showSize(){ // log.warn("[notify-目录订阅] 待处理消息数量: {}", taskQueue.size() ); // } public void process(RequestEvent evt) { if (taskQueue.size() >= userSetting.getMaxNotifyCountQueue()) { log.error("[notify-目录订阅] 待处理消息队列已满 {},返回486 BUSY_HERE,消息不做处理", userSetting.getMaxNotifyCountQueue()); return; } taskQueue.offer(new HandlerCatchData(evt, null, null)); } @Scheduled(fixedDelay = 400) //每400毫秒执行一次 public void executeTaskQueue(){ if (taskQueue.isEmpty()) { return; } List handlerCatchDataList = new ArrayList<>(); int size = taskQueue.size(); for (int i = 0; i < size; i++) { HandlerCatchData poll = taskQueue.poll(); if (poll != null) { handlerCatchDataList.add(poll); } } if (handlerCatchDataList.isEmpty()) { return; } for (HandlerCatchData take : handlerCatchDataList) { if (take == null) { continue; } RequestEvent evt = take.getEvt(); try { FromHeader fromHeader = (FromHeader) evt.getRequest().getHeader(FromHeader.NAME); String deviceId = SipUtils.getUserIdFromFromHeader(fromHeader); Device device = redisCatchStorage.getDevice(deviceId); if (device == null || !device.isOnLine()) { log.warn("[收到目录订阅]:{}, 但是设备已经离线", (device != null ? device.getDeviceId() : "")); continue; } Element rootElement = getRootElement(evt, device.getCharset()); if (rootElement == null) { log.warn("[ 收到目录订阅 ] content cannot be null, {}", evt.getRequest()); continue; } Element deviceListElement = rootElement.element("DeviceList"); if (deviceListElement == null) { log.warn("[ 收到目录订阅 ] content cannot be null, {}", evt.getRequest()); continue; } Iterator deviceListIterator = deviceListElement.elementIterator(); if (deviceListIterator != null) { // 遍历DeviceList while (deviceListIterator.hasNext()) { Element itemDevice = deviceListIterator.next(); CatalogChannelEvent catalogChannelEvent = null; try { catalogChannelEvent = CatalogChannelEvent.decode(itemDevice); if (catalogChannelEvent.getChannel() == null) { log.info("[解析CatalogChannelEvent]成功:但是解析通道信息失败, 原文如下: \n{}", new String(evt.getRequest().getRawContent())); continue; } catalogChannelEvent.getChannel().setDataDeviceId(device.getId()); if (catalogChannelEvent.getChannel().getLongitude() != null && catalogChannelEvent.getChannel().getLatitude() != null && catalogChannelEvent.getChannel().getLongitude() > 0 && catalogChannelEvent.getChannel().getLatitude() > 0) { if (device.checkWgs84()) { catalogChannelEvent.getChannel().setGbLongitude(catalogChannelEvent.getChannel().getLongitude()); catalogChannelEvent.getChannel().setGbLatitude(catalogChannelEvent.getChannel().getLatitude()); }else { Double[] wgs84Position = Coordtransform.GCJ02ToWGS84(catalogChannelEvent.getChannel().getLongitude(), catalogChannelEvent.getChannel().getLatitude()); catalogChannelEvent.getChannel().setGbLongitude(wgs84Position[0]); catalogChannelEvent.getChannel().setGbLatitude(wgs84Position[1]); } } } catch (InvocationTargetException | NoSuchMethodException | InstantiationException | IllegalAccessException e) { log.error("[解析CatalogChannelEvent]失败,", e); log.error("[解析CatalogChannelEvent]失败原文: \n{}", new String(evt.getRequest().getRawContent(), Charset.forName(device.getCharset()))); continue; } if (log.isDebugEnabled()){ log.debug("[收到目录订阅]:{}/{}-{}", device.getDeviceId(), catalogChannelEvent.getChannel().getDeviceId(), catalogChannelEvent.getEvent()); } DeviceChannel channel = catalogChannelEvent.getChannel(); switch (catalogChannelEvent.getEvent()) { case CatalogEvent.ON: // 上线 log.info("[收到通道上线通知] 来自设备: {}, 通道 {}", device.getDeviceId(), catalogChannelEvent.getChannel().getDeviceId()); channel.setStatus("ON"); channelList.add(NotifyCatalogChannel.getInstance(NotifyCatalogChannel.Type.STATUS_CHANGED, channel)); if (userSetting.getDeviceStatusNotify()) { // 发送redis消息 redisCatchStorage.sendDeviceOrChannelStatus(device.getDeviceId(), catalogChannelEvent.getChannel().getDeviceId(), true); } break; case CatalogEvent.OFF: // 离线 log.info("[收到通道离线通知] 来自设备: {}, 通道 {}", device.getDeviceId(), catalogChannelEvent.getChannel().getDeviceId()); if (userSetting.getRefuseChannelStatusChannelFormNotify()) { log.info("[收到通道离线通知] 但是平台已配置拒绝此消息,来自设备: {}, 通道 {}", device.getDeviceId(), catalogChannelEvent.getChannel().getDeviceId()); } else { channel.setStatus("OFF"); channelList.add(NotifyCatalogChannel.getInstance(NotifyCatalogChannel.Type.STATUS_CHANGED, channel)); if (userSetting.getDeviceStatusNotify()) { // 发送redis消息 redisCatchStorage.sendDeviceOrChannelStatus(device.getDeviceId(), catalogChannelEvent.getChannel().getDeviceId(), false); } } break; case CatalogEvent.VLOST: // 视频丢失 log.info("[收到通道视频丢失通知] 来自设备: {}, 通道 {}", device.getDeviceId(), catalogChannelEvent.getChannel().getDeviceId()); if (userSetting.getRefuseChannelStatusChannelFormNotify()) { log.info("[收到通道视频丢失通知] 但是平台已配置拒绝此消息,来自设备: {}, 通道 {}", device.getDeviceId(), catalogChannelEvent.getChannel().getDeviceId()); } else { channel.setStatus("OFF"); channelList.add(NotifyCatalogChannel.getInstance(NotifyCatalogChannel.Type.STATUS_CHANGED, channel)); if (userSetting.getDeviceStatusNotify()) { // 发送redis消息 redisCatchStorage.sendDeviceOrChannelStatus(device.getDeviceId(), catalogChannelEvent.getChannel().getDeviceId(), false); } } break; case CatalogEvent.DEFECT: // 故障 log.info("[收到通道视频故障通知] 来自设备: {}, 通道 {}", device.getDeviceId(), catalogChannelEvent.getChannel().getDeviceId()); if (userSetting.getRefuseChannelStatusChannelFormNotify()) { log.info("[收到通道视频故障通知] 但是平台已配置拒绝此消息,来自设备: {}, 通道 {}", device.getDeviceId(), catalogChannelEvent.getChannel().getDeviceId()); } else { channel.setStatus("OFF"); channelList.add(NotifyCatalogChannel.getInstance(NotifyCatalogChannel.Type.STATUS_CHANGED, channel)); if (userSetting.getDeviceStatusNotify()) { // 发送redis消息 redisCatchStorage.sendDeviceOrChannelStatus(device.getDeviceId(), catalogChannelEvent.getChannel().getDeviceId(), false); } } break; case CatalogEvent.ADD: // 增加 log.info("[收到增加通道通知] 来自设备: {}, 通道 {}", device.getDeviceId(), catalogChannelEvent.getChannel().getDeviceId()); // 判断此通道是否存在 DeviceChannel deviceChannel = deviceChannelService.getOneForSource(device.getId(), catalogChannelEvent.getChannel().getDeviceId()); if (deviceChannel != null) { log.info("[增加通道] 已存在,不发送通知只更新,设备: {}, 通道 {}", device.getDeviceId(), catalogChannelEvent.getChannel().getDeviceId()); channel.setId(deviceChannel.getId()); channel.setHasAudio(deviceChannel.isHasAudio()); channel.setUpdateTime(DateUtil.getNow()); channelList.add(NotifyCatalogChannel.getInstance(NotifyCatalogChannel.Type.UPDATE, channel)); } else { catalogChannelEvent.getChannel().setUpdateTime(DateUtil.getNow()); catalogChannelEvent.getChannel().setCreateTime(DateUtil.getNow()); channelList.add(NotifyCatalogChannel.getInstance(NotifyCatalogChannel.Type.ADD, channel)); if (userSetting.getDeviceStatusNotify()) { // 发送redis消息 redisCatchStorage.sendChannelAddOrDelete(device.getDeviceId(), catalogChannelEvent.getChannel().getDeviceId(), true); } } break; case CatalogEvent.DEL: // 删除 log.info("[收到删除通道通知] 来自设备: {}, 通道 {}", device.getDeviceId(), catalogChannelEvent.getChannel().getDeviceId()); channelList.add(NotifyCatalogChannel.getInstance(NotifyCatalogChannel.Type.DELETE, channel)); if (userSetting.getDeviceStatusNotify()) { // 发送redis消息 redisCatchStorage.sendChannelAddOrDelete(device.getDeviceId(), catalogChannelEvent.getChannel().getDeviceId(), false); } break; case CatalogEvent.UPDATE: // 更新 log.info("[收到更新通道通知] 来自设备: {}, 通道 {}", device.getDeviceId(), catalogChannelEvent.getChannel().getDeviceId()); // 判断此通道是否存在 DeviceChannel deviceChannelForUpdate = deviceChannelService.getOneForSource(device.getId(), catalogChannelEvent.getChannel().getDeviceId()); if (deviceChannelForUpdate != null) { channel.setId(deviceChannelForUpdate.getId()); channel.setHasAudio(deviceChannelForUpdate.isHasAudio()); channel.setUpdateTime(DateUtil.getNow()); channel.setUpdateTime(DateUtil.getNow()); channelList.add(NotifyCatalogChannel.getInstance(NotifyCatalogChannel.Type.UPDATE, channel)); } else { catalogChannelEvent.getChannel().setCreateTime(DateUtil.getNow()); catalogChannelEvent.getChannel().setUpdateTime(DateUtil.getNow()); channelList.add(NotifyCatalogChannel.getInstance(NotifyCatalogChannel.Type.ADD, channel)); if (userSetting.getDeviceStatusNotify()) { // 发送redis消息 redisCatchStorage.sendChannelAddOrDelete(device.getDeviceId(), catalogChannelEvent.getChannel().getDeviceId(), true); } } break; default: log.warn("[ NotifyCatalog ] event not found : {}", catalogChannelEvent.getEvent()); } } } } catch (DocumentException e) { log.error("未处理的异常 ", e); } } if (!channelList.isEmpty()) { executeSave(); } } @Transactional public void executeSave() { int size = channelList.size(); List channelListForSave = new ArrayList<>(); for (int i = 0; i < size; i++) { channelListForSave.add(channelList.poll()); } for (NotifyCatalogChannel notifyCatalogChannel : channelListForSave) { try { switch (notifyCatalogChannel.getType()) { case STATUS_CHANGED: deviceChannelService.updateChannelStatusForNotify(notifyCatalogChannel.getChannel()); CommonGBChannel channelForStatus = channelService.queryCommonChannelByDeviceChannel(notifyCatalogChannel.getChannel()); if ("ON".equals(notifyCatalogChannel.getChannel().getStatus()) ) { eventPublisher.channelEventPublish(channelForStatus, ChannelEvent.ChannelEventMessageType.ON); }else { eventPublisher.channelEventPublish(channelForStatus, ChannelEvent.ChannelEventMessageType.OFF); } break; case ADD: deviceChannelService.addChannel(notifyCatalogChannel.getChannel()); CommonGBChannel channelForAdd = channelService.getOne(notifyCatalogChannel.getChannel().getId()); eventPublisher.channelEventPublish(channelForAdd, ChannelEvent.ChannelEventMessageType.ADD); break; case UPDATE: CommonGBChannel oldCommonChannel = channelService.getOne(notifyCatalogChannel.getChannel().getId()); deviceChannelService.updateChannelForNotify(notifyCatalogChannel.getChannel()); CommonGBChannel channel = channelService.getOne(oldCommonChannel.getGbId()); eventPublisher.channelEventPublishForUpdate(channel, oldCommonChannel); break; case DELETE: CommonGBChannel oldCommonChannelForDelete = channelService.queryCommonChannelByDeviceChannel(notifyCatalogChannel.getChannel()); deviceChannelService.deleteForNotify(notifyCatalogChannel.getChannel()); eventPublisher.channelEventPublish(oldCommonChannelForDelete, ChannelEvent.ChannelEventMessageType.DEL); break; } }catch (Exception e) { log.error("[存储收到的通道-异常]类型:{},编号:{}", notifyCatalogChannel.getType(), notifyCatalogChannel.getChannel().getDeviceId(), e); } } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/NotifyRequestForMobilePositionProcessor.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; import com.genersoft.iot.vmp.gb28181.bean.HandlerCatchData; import com.genersoft.iot.vmp.gb28181.bean.MobilePosition; import com.genersoft.iot.vmp.gb28181.event.EventPublisher; import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService; import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; import com.genersoft.iot.vmp.gb28181.utils.NumericUtil; import com.genersoft.iot.vmp.gb28181.utils.SipUtils; import com.genersoft.iot.vmp.service.IMobilePositionService; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.utils.DateUtil; import lombok.extern.slf4j.Slf4j; import org.dom4j.DocumentException; import org.dom4j.Element; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; import javax.sip.RequestEvent; import javax.sip.header.FromHeader; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; /** * SIP命令类型: NOTIFY请求中的移动位置请求处理 */ @Slf4j @Component public class NotifyRequestForMobilePositionProcessor extends SIPRequestProcessorParent { private final ConcurrentLinkedQueue taskQueue = new ConcurrentLinkedQueue<>(); @Autowired private UserSetting userSetting; @Autowired private EventPublisher eventPublisher; @Autowired private IRedisCatchStorage redisCatchStorage; @Autowired private IDeviceChannelService deviceChannelService; @Autowired private IMobilePositionService mobilePositionService; public void process(RequestEvent evt) { if (taskQueue.size() >= userSetting.getMaxNotifyCountQueue()) { log.error("[notify-移动位置] 待处理消息队列已满 {},返回486 BUSY_HERE,消息不做处理", userSetting.getMaxNotifyCountQueue()); return; } taskQueue.offer(new HandlerCatchData(evt, null, null)); } @Scheduled(fixedDelay = 200) //每200毫秒执行一次 public void executeTaskQueue() { if (taskQueue.isEmpty()) { return; } List handlerCatchDataList = new ArrayList<>(); while (!taskQueue.isEmpty()) { handlerCatchDataList.add(taskQueue.poll()); } if (handlerCatchDataList.isEmpty()) { return; } for (HandlerCatchData take : handlerCatchDataList) { if (take == null) { continue; } RequestEvent evt = take.getEvt(); try { FromHeader fromHeader = (FromHeader) evt.getRequest().getHeader(FromHeader.NAME); String deviceId = SipUtils.getUserIdFromFromHeader(fromHeader); long startTime = System.currentTimeMillis(); // 回复 200 OK Element rootElement = getRootElement(evt); if (rootElement == null) { log.error("处理MobilePosition移动位置Notify时未获取到消息体,{}", evt.getRequest()); continue; } Device device = redisCatchStorage.getDevice(deviceId); if (device == null) { log.error("处理MobilePosition移动位置Notify时未获取到device,{}", deviceId); continue; } MobilePosition mobilePosition = new MobilePosition(); mobilePosition.setDeviceId(device.getDeviceId()); mobilePosition.setDeviceName(device.getName()); mobilePosition.setCreateTime(DateUtil.getNow()); DeviceChannel deviceChannel = null; List elements = rootElement.elements(); readDocument: for (Element element : elements) { switch (element.getName()){ case "DeviceID": String channelId = element.getStringValue(); deviceChannel = deviceChannelService.getOne(device.getDeviceId(), channelId); if (deviceChannel != null) { mobilePosition.setChannelId(deviceChannel.getId()); mobilePosition.setChannelDeviceId(deviceChannel.getDeviceId()); }else { log.error("[notify-移动位置] 未找到通道 {}/{}", device.getDeviceId(), channelId); break readDocument; } break; case "Time": String timeVal = element.getStringValue(); if (ObjectUtils.isEmpty(timeVal)) { mobilePosition.setTime(DateUtil.getNow()); } else { mobilePosition.setTime(SipUtils.parseTime(timeVal)); } break; case "Longitude": mobilePosition.setLongitude(Double.parseDouble(element.getStringValue())); break; case "Latitude": mobilePosition.setLatitude(Double.parseDouble(element.getStringValue())); break; case "Speed": String speedVal = element.getStringValue(); if (NumericUtil.isDouble(speedVal)) { mobilePosition.setSpeed(Double.parseDouble(speedVal)); } else { mobilePosition.setSpeed(0.0); } break; case "Direction": String directionVal = element.getStringValue(); if (NumericUtil.isDouble(directionVal)) { mobilePosition.setDirection(Double.parseDouble(directionVal)); } else { mobilePosition.setDirection(0.0); } break; case "Altitude": String altitudeVal = element.getStringValue(); if (NumericUtil.isDouble(altitudeVal)) { mobilePosition.setAltitude(Double.parseDouble(altitudeVal)); } else { mobilePosition.setAltitude(0.0); } break; } } if (deviceChannel == null) { continue; } log.info("[收到移动位置订阅通知]:{}/{}->{}.{}, 时间: {}", mobilePosition.getDeviceId(), mobilePosition.getChannelId(), mobilePosition.getLongitude(), mobilePosition.getLatitude(), System.currentTimeMillis() - startTime); mobilePosition.setReportSource("Mobile Position"); mobilePositionService.add(mobilePosition); // 向关联了该通道并且开启移动位置订阅的上级平台发送移动位置订阅消息 try { eventPublisher.mobilePositionEventPublish(mobilePosition); }catch (Exception e) { log.error("[MobilePositionEvent] 发送失败: ", e); } } catch (DocumentException e) { log.error("[收到移动位置订阅通知] 文档解析异常: \r\n{}", evt.getRequest(), e); } catch ( Exception e) { log.error("[收到移动位置订阅通知] 异常: ", e); } } } // @Scheduled(fixedRate = 10000) // public void execute(){ // logger.debug("[待处理Notify-移动位置订阅消息数量]: {}", taskQueue.size()); // } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/NotifyRequestProcessor.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl; import com.genersoft.iot.vmp.conf.SipConfig; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.event.EventPublisher; import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver; import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor; import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; import com.genersoft.iot.vmp.gb28181.utils.NumericUtil; import com.genersoft.iot.vmp.gb28181.utils.SipUtils; import com.genersoft.iot.vmp.gb28181.utils.XmlUtil; import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.utils.DateUtil; import gov.nist.javax.sip.message.SIPRequest; import lombok.extern.slf4j.Slf4j; import org.dom4j.DocumentException; import org.dom4j.Element; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.sip.InvalidArgumentException; import javax.sip.RequestEvent; import javax.sip.SipException; import javax.sip.header.FromHeader; import javax.sip.message.Response; import java.text.ParseException; /** * SIP命令类型: NOTIFY请求,这是作为上级发送订阅请求后,设备才会响应的 */ @Slf4j @Component public class NotifyRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor { @Autowired private SipConfig sipConfig; @Autowired private IRedisCatchStorage redisCatchStorage; @Autowired private EventPublisher publisher; private final String method = "NOTIFY"; @Autowired private SIPProcessorObserver sipProcessorObserver; @Autowired private IDeviceChannelService deviceChannelService; @Autowired private NotifyRequestForCatalogProcessor notifyRequestForCatalogProcessor; @Autowired private NotifyRequestForMobilePositionProcessor notifyRequestForMobilePositionProcessor; @Override public void afterPropertiesSet() throws Exception { // 添加消息处理的订阅 sipProcessorObserver.addRequestProcessor(method, this); } @Override public void process(RequestEvent evt) { try { responseAck((SIPRequest) evt.getRequest(), Response.OK, null, null); Element rootElement = getRootElement(evt); if (rootElement == null) { log.error("处理NOTIFY消息时未获取到消息体,{}", evt.getRequest()); responseAck((SIPRequest) evt.getRequest(), Response.OK, null, null); return; } String cmd = XmlUtil.getText(rootElement, "CmdType"); if (CmdType.CATALOG.equals(cmd)) { notifyRequestForCatalogProcessor.process(evt); } else if (CmdType.ALARM.equals(cmd)) { processNotifyAlarm(evt); } else if (CmdType.MOBILE_POSITION.equals(cmd)) { notifyRequestForMobilePositionProcessor.process(evt); } else { log.info("接收到消息:" + cmd); } } catch (SipException | InvalidArgumentException | ParseException e) { log.error("未处理的异常 ", e); } catch (DocumentException e) { throw new RuntimeException(e); } } /*** * 处理alarm设备报警Notify */ private void processNotifyAlarm(RequestEvent evt) { if (!sipConfig.isAlarm()) { return; } try { FromHeader fromHeader = (FromHeader) evt.getRequest().getHeader(FromHeader.NAME); String deviceId = SipUtils.getUserIdFromFromHeader(fromHeader); Element rootElement = getRootElement(evt); if (rootElement == null) { log.error("处理alarm设备报警Notify时未获取到消息体{}", evt.getRequest()); return; } Element deviceIdElement = rootElement.element("DeviceID"); String channelId = deviceIdElement.getText().toString(); Device device = redisCatchStorage.getDevice(deviceId); if (device == null) { log.warn("[ NotifyAlarm ] 未找到设备:{}", deviceId); return; } rootElement = getRootElement(evt, device.getCharset()); if (rootElement == null) { log.warn("[ NotifyAlarm ] content cannot be null, {}", evt.getRequest()); return; } DeviceAlarm deviceAlarm = new DeviceAlarm(); deviceAlarm.setDeviceId(deviceId); deviceAlarm.setDeviceName(device.getName()); deviceAlarm.setAlarmPriority(XmlUtil.getText(rootElement, "AlarmPriority")); deviceAlarm.setAlarmMethod(XmlUtil.getText(rootElement, "AlarmMethod")); String alarmTime = XmlUtil.getText(rootElement, "AlarmTime"); if (alarmTime == null) { log.warn("[ NotifyAlarm ] AlarmTime cannot be null"); return; } deviceAlarm.setAlarmTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(alarmTime)); if (XmlUtil.getText(rootElement, "AlarmDescription") == null) { deviceAlarm.setAlarmDescription(""); } else { deviceAlarm.setAlarmDescription(XmlUtil.getText(rootElement, "AlarmDescription")); } if (NumericUtil.isDouble(XmlUtil.getText(rootElement, "Longitude"))) { deviceAlarm.setLongitude(Double.parseDouble(XmlUtil.getText(rootElement, "Longitude"))); } else { deviceAlarm.setLongitude(0.00); } if (NumericUtil.isDouble(XmlUtil.getText(rootElement, "Latitude"))) { deviceAlarm.setLatitude(Double.parseDouble(XmlUtil.getText(rootElement, "Latitude"))); } else { deviceAlarm.setLatitude(0.00); } log.info("[收到Notify-Alarm]:{}/{}", device.getDeviceId(), deviceAlarm.getChannelId()); if ("4".equals(deviceAlarm.getAlarmMethod())) { DeviceChannel deviceChannel = deviceChannelService.getOne(device.getDeviceId(), channelId); if (deviceChannel == null) { log.warn("[解析报警通知] 未找到通道:{}/{}", device.getDeviceId(), channelId); }else { MobilePosition mobilePosition = new MobilePosition(); mobilePosition.setChannelId(deviceChannel.getId()); mobilePosition.setChannelDeviceId(deviceChannel.getDeviceId()); mobilePosition.setCreateTime(DateUtil.getNow()); mobilePosition.setDeviceId(deviceAlarm.getDeviceId()); mobilePosition.setTime(deviceAlarm.getAlarmTime()); mobilePosition.setLongitude(deviceAlarm.getLongitude()); mobilePosition.setLatitude(deviceAlarm.getLatitude()); mobilePosition.setReportSource("GPS Alarm"); // 更新device channel 的经纬度 deviceChannel.setLongitude(mobilePosition.getLongitude()); deviceChannel.setLatitude(mobilePosition.getLatitude()); deviceChannel.setGpsTime(mobilePosition.getTime()); deviceChannelService.updateChannelGPS(device, deviceChannel, mobilePosition); } } // 回复200 OK if (redisCatchStorage.deviceIsOnline(deviceId)) { publisher.deviceAlarmEventPublish(deviceAlarm); } } catch (DocumentException e) { log.error("未处理的异常 ", e); } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/RegisterRequestProcessor.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl; import com.genersoft.iot.vmp.conf.SipConfig; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.gb28181.auth.DigestServerAuthenticationHelper; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.GbCode; import com.genersoft.iot.vmp.gb28181.bean.GbSipDate; import com.genersoft.iot.vmp.common.RemoteAddressInfo; import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo; import com.genersoft.iot.vmp.gb28181.service.IDeviceService; import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver; import com.genersoft.iot.vmp.gb28181.transmit.SIPSender; import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor; import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; import com.genersoft.iot.vmp.gb28181.utils.SipUtils; import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.utils.IpPortUtil; import gov.nist.javax.sip.address.AddressImpl; import gov.nist.javax.sip.address.SipUri; import gov.nist.javax.sip.header.SIPDateHeader; import gov.nist.javax.sip.message.SIPRequest; import gov.nist.javax.sip.message.SIPResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; import javax.sip.RequestEvent; import javax.sip.SipException; import javax.sip.header.AuthorizationHeader; import javax.sip.header.ContactHeader; import javax.sip.header.FromHeader; import javax.sip.header.ViaHeader; import javax.sip.message.Request; import javax.sip.message.Response; import java.security.NoSuchAlgorithmException; import java.text.ParseException; import java.util.Calendar; import java.util.Locale; /** * SIP命令类型: REGISTER请求 */ @Slf4j @Component public class RegisterRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor { public final String method = "REGISTER"; @Autowired private SipConfig sipConfig; @Autowired private SIPProcessorObserver sipProcessorObserver; @Autowired private IDeviceService deviceService; @Autowired private SIPSender sipSender; @Autowired private UserSetting userSetting; @Override public void afterPropertiesSet() throws Exception { // 添加消息处理的订阅 sipProcessorObserver.addRequestProcessor(method, this); } /** * 收到注册请求 处理 */ @Override public void process(RequestEvent evt) { try { SIPRequest request = (SIPRequest) evt.getRequest(); Response response = null; boolean passwordCorrect = false; // 注册标志 boolean registerFlag = true; if (request.getExpires().getExpires() == 0) { // 注销成功 registerFlag = false; } FromHeader fromHeader = (FromHeader) request.getHeader(FromHeader.NAME); AddressImpl address = (AddressImpl) fromHeader.getAddress(); SipUri uri = (SipUri) address.getURI(); String deviceId = uri.getUser(); if (userSetting.isDeviceIdStrict()) { // 严格模式下,非20位设备ID不予处理 GbCode decode = GbCode.decode(deviceId); if (decode == null) { // 注册失败 response = getMessageFactory().createResponse(Response.FORBIDDEN, request); sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response); return; } } // 调整逻辑,如果为设置公共密码,那么就必须要预设用户信息,否则无法注册。 Device device = deviceService.getDeviceByDeviceId(deviceId); RemoteAddressInfo remoteAddressInfo = SipUtils.getRemoteAddressFromRequest(request, userSetting.getSipUseSourceIpAsRemoteAddress()); String requestAddress = remoteAddressInfo.getIp() + ":" + remoteAddressInfo.getPort(); String title = registerFlag ? "[注册请求]" : "[注销请求]"; log.info("{} 设备:{}, 开始处理: {}", title, deviceId, requestAddress); String password = null; if (device != null) { if (device.getSipTransactionInfo() != null && request.getCallIdHeader().getCallId().equals(device.getSipTransactionInfo().getCallId())) { log.info("{} 设备:{}, 注册续订: {}", title, device.getDeviceId(), device.getDeviceId()); if (registerFlag) { device.setExpires(request.getExpires().getExpires()); device.setIp(remoteAddressInfo.getIp()); device.setPort(remoteAddressInfo.getPort()); device.setHostAddress(IpPortUtil.concatenateIpAndPort(remoteAddressInfo.getIp(), String.valueOf(remoteAddressInfo.getPort()))); device.setLocalIp(request.getLocalAddress().getHostAddress()); Response registerOkResponse = getRegisterOkResponse(request); // 判断TCP还是UDP ViaHeader reqViaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME); String transport = reqViaHeader.getTransport(); device.setTransport("TCP".equalsIgnoreCase(transport) ? "TCP" : "UDP"); sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), registerOkResponse); device.setRegisterTime(DateUtil.getNow()); deviceService.online(device, null); } else { deviceService.offline(deviceId, "主动注销", false); } return; }else { // 正常注册, 用户信息未设置密码,并且公共密码也未设置,则关闭鉴权 if (!ObjectUtils.isEmpty(device.getPassword()) || !ObjectUtils.isEmpty(sipConfig.getPassword())) { password = (!ObjectUtils.isEmpty(device.getPassword())) ? device.getPassword() : sipConfig.getPassword(); } } }else { if (ObjectUtils.isEmpty(sipConfig.getPassword())) { log.info("{} 设备:{}, 地址: {}, 公共密码已经禁用,请添加用户信息后注册", title, deviceId, requestAddress); response = getMessageFactory().createResponse(Response.FORBIDDEN, request); sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response); return; }else { password = sipConfig.getPassword(); } } AuthorizationHeader authHead = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME); if (authHead == null && !ObjectUtils.isEmpty(password)) { log.info(title + " 设备:{}, 回复401: {}", deviceId, requestAddress); response = getMessageFactory().createResponse(Response.UNAUTHORIZED, request); new DigestServerAuthenticationHelper().generateChallenge(getHeaderFactory(), response, sipConfig.getDomain()); sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response); return; } // 校验密码是否正确 passwordCorrect = ObjectUtils.isEmpty(password) || new DigestServerAuthenticationHelper().doAuthenticatePlainTextPassword(request, password); if (!passwordCorrect) { // 注册失败 response = getMessageFactory().createResponse(Response.FORBIDDEN, request); response.setReasonPhrase("wrong password"); log.info(title + " 设备:{}, 密码/SIP服务器ID错误, 回复403: {}", deviceId, requestAddress); sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response); return; } // 携带授权头并且密码正确 response = getMessageFactory().createResponse(Response.OK, request); // 如果主动禁用了Date头,则不添加 if (!userSetting.isDisableDateHeader()) { // 添加date头 SIPDateHeader dateHeader = new SIPDateHeader(); // 使用自己修改的 GbSipDate gbSipDate = new GbSipDate(Calendar.getInstance(Locale.ENGLISH).getTimeInMillis()); dateHeader.setDate(gbSipDate); response.addHeader(dateHeader); } if (request.getExpires() == null) { response = getMessageFactory().createResponse(Response.BAD_REQUEST, request); sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response); return; } // 添加Contact头 response.addHeader(request.getHeader(ContactHeader.NAME)); // 添加Expires头 response.addHeader(request.getExpires()); if (device == null) { device = new Device(); device.setStreamMode("TCP-PASSIVE"); device.setCharset("GB2312"); device.setGeoCoordSys("WGS84"); device.setMediaServerId("auto"); device.setDeviceId(deviceId); device.setOnLine(false); } else { if (ObjectUtils.isEmpty(device.getStreamMode())) { device.setStreamMode("TCP-PASSIVE"); } if (ObjectUtils.isEmpty(device.getCharset())) { device.setCharset("GB2312"); } if (ObjectUtils.isEmpty(device.getGeoCoordSys())) { device.setGeoCoordSys("WGS84"); } } device.setServerId(userSetting.getServerId()); device.setIp(remoteAddressInfo.getIp()); device.setPort(remoteAddressInfo.getPort()); device.setHostAddress(IpPortUtil.concatenateIpAndPort(remoteAddressInfo.getIp(), String.valueOf(remoteAddressInfo.getPort()))); device.setLocalIp(request.getLocalAddress().getHostAddress()); if (request.getExpires().getExpires() == 0) { // 注销成功 registerFlag = false; } else { // 注册成功 device.setExpires(request.getExpires().getExpires()); registerFlag = true; // 判断TCP还是UDP ViaHeader reqViaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME); String transport = reqViaHeader.getTransport(); device.setTransport("TCP".equalsIgnoreCase(transport) ? "TCP" : "UDP"); } sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response); // 注册成功 // 保存到redis if (registerFlag) { log.info("[注册成功] deviceId: {}->{}", deviceId, requestAddress); device.setRegisterTime(DateUtil.getNow()); SipTransactionInfo sipTransactionInfo = new SipTransactionInfo((SIPResponse) response); deviceService.online(device, sipTransactionInfo); } else { log.info("[注销成功] deviceId: {}->{}", deviceId, requestAddress); deviceService.offline(deviceId, "主动注销", false); } } catch (SipException | NoSuchAlgorithmException | ParseException e) { log.error("未处理的异常 ", e); } } private Response getRegisterOkResponse(Request request) throws ParseException { // 携带授权头并且密码正确 Response response = getMessageFactory().createResponse(Response.OK, request); // 如果主动禁用了Date头,则不添加 if (!userSetting.isDisableDateHeader()) { // 添加date头 SIPDateHeader dateHeader = new SIPDateHeader(); // 使用自己修改的 GbSipDate gbSipDate = new GbSipDate(Calendar.getInstance(Locale.ENGLISH).getTimeInMillis()); dateHeader.setDate(gbSipDate); response.addHeader(dateHeader); } // 添加Contact头 response.addHeader(request.getHeader(ContactHeader.NAME)); // 添加Expires头 response.addHeader(request.getExpires()); return response; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/SubscribeRequestProcessor.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.service.IPlatformService; import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver; import com.genersoft.iot.vmp.gb28181.transmit.SIPSender; import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor; import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; import com.genersoft.iot.vmp.gb28181.utils.SipUtils; import com.genersoft.iot.vmp.gb28181.utils.XmlUtil; import gov.nist.javax.sip.message.SIPRequest; import gov.nist.javax.sip.message.SIPResponse; import lombok.extern.slf4j.Slf4j; import org.dom4j.DocumentException; import org.dom4j.Element; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.sip.InvalidArgumentException; import javax.sip.RequestEvent; import javax.sip.SipException; import javax.sip.header.EventHeader; import javax.sip.header.ExpiresHeader; import javax.sip.message.Response; import java.text.ParseException; /** * SIP命令类型: SUBSCRIBE请求 * @author lin */ @Slf4j @Component public class SubscribeRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor { private final String method = "SUBSCRIBE"; @Autowired private SIPProcessorObserver sipProcessorObserver; @Autowired private SubscribeHolder subscribeHolder; @Autowired private SIPSender sipSender; @Autowired private IPlatformService platformService; @Override public void afterPropertiesSet() throws Exception { // 添加消息处理的订阅 sipProcessorObserver.addRequestProcessor(method, this); } /** * 处理SUBSCRIBE请求 * * @param evt 事件 */ @Override public void process(RequestEvent evt) { SIPRequest request = (SIPRequest) evt.getRequest(); try { Element rootElement = getRootElement(evt); if (rootElement == null) { log.error("处理SUBSCRIBE请求 未获取到消息体{}", evt.getRequest()); responseAck(request, Response.BAD_REQUEST); return; } ExpiresHeader expires = request.getExpires(); if (expires == null) { log.error("处理SUBSCRIBE请求 未获取到ExpiresHeader{}", evt.getRequest()); responseAck(request, Response.BAD_REQUEST, "missing expires"); return; } String platformId = SipUtils.getUserIdFromFromHeader(request); String cmd = XmlUtil.getText(rootElement, "CmdType"); log.info("[收到订阅请求] 类型: {}, 来自: {}", cmd, platformId); if (CmdType.MOBILE_POSITION.equals(cmd)) { processNotifyMobilePosition(request, rootElement); // } else if (CmdType.ALARM.equals(cmd)) { // logger.info("接收到Alarm订阅"); // processNotifyAlarm(serverTransaction, rootElement); } else if (CmdType.CATALOG.equals(cmd)) { processNotifyCatalogList(request, rootElement); } else { log.info("接收到消息:{}", cmd); Response response = getMessageFactory().createResponse(200, request); if (response != null) { ExpiresHeader expireHeader = getHeaderFactory().createExpiresHeader(30); response.setExpires(expireHeader); } log.info("response : {}", response); sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response); } } catch (ParseException | SipException | InvalidArgumentException | DocumentException e) { log.error("未处理的异常 ", e); } } /** * 处理移动位置订阅消息 */ private void processNotifyMobilePosition(SIPRequest request, Element rootElement) throws SipException { if (request == null) { return; } String platformId = SipUtils.getUserIdFromFromHeader(request); String deviceId = XmlUtil.getText(rootElement, "DeviceID"); Platform platform = platformService.queryPlatformByServerGBId(platformId); if (platform == null) { return; } String sn = XmlUtil.getText(rootElement, "SN"); log.info("[回复上级的移动位置订阅请求]: {}", platformId); StringBuilder resultXml = new StringBuilder(200); resultXml.append("\r\n") .append("\r\n") .append("MobilePosition\r\n") .append("").append(sn).append("\r\n") .append("").append(deviceId).append("\r\n") .append("OK\r\n") .append("\r\n"); try { int expires = request.getExpires().getExpires(); SIPResponse response = responseXmlAck(request, resultXml.toString(), platform, expires); SubscribeInfo subscribeInfo = SubscribeInfo.getInstance(response, platformId, expires, (EventHeader)request.getHeader(EventHeader.NAME)); if (subscribeInfo.getExpires() > 0) { // GPS上报时间间隔 String interval = XmlUtil.getText(rootElement, "Interval"); if (interval == null) { subscribeInfo.setGpsInterval(5); }else { subscribeInfo.setGpsInterval(Integer.parseInt(interval)); } subscribeInfo.setSn(sn); } if (subscribeInfo.getExpires() == 0) { subscribeHolder.removeMobilePositionSubscribe(platformId); }else { subscribeInfo.setTransactionInfo(new SipTransactionInfo(response)); subscribeHolder.putMobilePositionSubscribe(platformId, subscribeInfo, ()->{ platformService.sendNotifyMobilePosition(platformId); }); } } catch (SipException | InvalidArgumentException | ParseException e) { log.error("未处理的异常 ", e); } } private void processNotifyAlarm(RequestEvent evt, Element rootElement) { } private void processNotifyCatalogList(SIPRequest request, Element rootElement) throws SipException { if (request == null) { log.info("[处理目录订阅] 发现request为NUll。已忽略"); return; } String platformId = SipUtils.getUserIdFromFromHeader(request); String deviceId = XmlUtil.getText(rootElement, "DeviceID"); Platform platform = platformService.queryPlatformByServerGBId(platformId); if (platform == null){ log.info("[处理目录订阅] 未找到平台 {}。已忽略", platformId); return; } String sn = XmlUtil.getText(rootElement, "SN"); log.info("[回复上级的目录订阅请求]: {}/{}", platformId, deviceId); StringBuilder resultXml = new StringBuilder(200); resultXml.append("\r\n") .append("\r\n") .append("Catalog\r\n") .append("").append(sn).append("\r\n") .append("").append(deviceId).append("\r\n") .append("OK\r\n") .append("\r\n"); try { int expires = request.getExpires().getExpires(); Platform parentPlatform = platformService.queryPlatformByServerGBId(platformId); SIPResponse response = responseXmlAck(request, resultXml.toString(), parentPlatform, expires); SubscribeInfo subscribeInfo = SubscribeInfo.getInstance(response, platformId, expires, (EventHeader)request.getHeader(EventHeader.NAME)); if (subscribeInfo.getExpires() == 0) { subscribeHolder.removeCatalogSubscribe(platformId); }else { subscribeInfo.setTransactionInfo(new SipTransactionInfo(response)); subscribeHolder.putCatalogSubscribe(platformId, subscribeInfo); } } catch (SipException | InvalidArgumentException | ParseException e) { log.error("未处理的异常 ", e); } if (subscribeHolder.getCatalogSubscribe(platformId) == null && platform.getAutoPushChannel() != null && platform.getAutoPushChannel()) { platformService.addSimulatedSubscribeInfo(platform); } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/info/InfoRequestProcessor.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.info; import com.genersoft.iot.vmp.common.enums.ChannelDataType; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; import com.genersoft.iot.vmp.gb28181.service.*; import com.genersoft.iot.vmp.gb28181.session.SipInviteSessionManager; import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor; import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; import com.genersoft.iot.vmp.service.ISendRtpServerService; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import gov.nist.javax.sip.message.SIPRequest; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.sip.InvalidArgumentException; import javax.sip.RequestEvent; import javax.sip.SipException; import javax.sip.header.CallIdHeader; import javax.sip.header.ContentTypeHeader; import javax.sip.message.Response; import java.text.ParseException; /** * INFO 一般用于国标级联时的回放控制 */ @Slf4j @Component public class InfoRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor { private final String method = "INFO"; @Autowired private SIPProcessorObserver sipProcessorObserver; @Autowired private IPlatformService platformService; @Autowired private SipSubscribe sipSubscribe; @Autowired private IRedisCatchStorage redisCatchStorage; @Autowired private IInviteStreamService inviteStreamService; @Autowired private IDeviceService deviceService; @Autowired private IGbChannelService channelService; @Autowired private IDeviceChannelService deviceChannelService; @Autowired private SIPCommander cmder; @Autowired private SipInviteSessionManager sessionManager; @Autowired private ISendRtpServerService sendRtpServerService; @Override public void afterPropertiesSet() throws Exception { // 添加消息处理的订阅 sipProcessorObserver.addRequestProcessor(method, this); } @Override public void process(RequestEvent evt) { SIPRequest request = (SIPRequest) evt.getRequest(); CallIdHeader callIdHeader = request.getCallIdHeader(); // 先从会话内查找 try { SendRtpInfo sendRtpInfo = sendRtpServerService.queryByCallId(callIdHeader.getCallId()); if (sendRtpInfo == null || !sendRtpInfo.isSendToPlatform()) { // 不存在则回复404 log.warn("[INFO 消息] 事务未找到, callID: {}", callIdHeader.getCallId()); responseAck(request, Response.NOT_FOUND, "transaction not found"); return; } // 查询上级平台是否存在 Platform platform = platformService.queryPlatformByServerGBId(sendRtpInfo.getTargetId()); if (platform == null || !platform.isStatus()) { // 不存在则回复404 log.warn("[INFO 消息] 平台未找到或者已离线: 平台: {}", sendRtpInfo.getTargetId()); responseAck(request, Response.NOT_FOUND, "platform "+ sendRtpInfo.getTargetId() +" not found or offline"); return; } CommonGBChannel channel = channelService.getOne(sendRtpInfo.getChannelId()); if (channel == null) { // 不存在则回复404 log.warn("[INFO 消息] 通道不存在: 通道ID: {}", sendRtpInfo.getChannelId()); responseAck(request, Response.NOT_FOUND, "channel not found or offline"); return; } // 判断通道类型 if (channel.getDataType() != ChannelDataType.GB28181) { // 非国标通道不支持录像回放控制 log.warn("[INFO 消息] 非国标通道不支持录像回放控制: 通道ID: {}", sendRtpInfo.getChannelId()); responseAck(request, Response.FORBIDDEN, ""); return; } // 根据通道ID,获取所属设备 Device device = deviceService.getDevice(channel.getDataDeviceId()); if (device == null) { // 不存在则回复404 log.warn("[INFO 消息] 通道所属设备不存在, 通道ID: {}", sendRtpInfo.getChannelId()); responseAck(request, Response.NOT_FOUND, "platform "+ sendRtpInfo.getChannelId() +" not found or offline"); return; } // 获取通道的原始信息 DeviceChannel deviceChannel = deviceChannelService.getOneForSourceById(sendRtpInfo.getChannelId()); // 向原始通道转发控制消息 ContentTypeHeader header = (ContentTypeHeader)evt.getRequest().getHeader(ContentTypeHeader.NAME); String contentType = header.getContentType(); String contentSubType = header.getContentSubType(); if ("Application".equalsIgnoreCase(contentType) && "MANSRTSP".equalsIgnoreCase(contentSubType)) { log.info("[INFO 消息] 平台: {}->{}({})/{}", platform.getServerGBId(), device.getName(), device.getDeviceId(), deviceChannel.getId()); // 不解析协议, 直接转发给对应的设备 cmder.playbackControlCmd(device, deviceChannel, sendRtpInfo.getStream(), new String(evt.getRequest().getRawContent()), eventResult -> { // 失败的回复 try { responseAck(request, eventResult.statusCode, eventResult.msg); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 国标级联 录像控制: {}", e.getMessage()); } }, eventResult -> { // 成功的回复 try { responseAck(request, eventResult.statusCode); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 国标级联 录像控制: {}", e.getMessage()); } }); } } catch (SipException e) { log.warn("SIP 回复错误", e); } catch (InvalidArgumentException e) { log.warn("参数无效", e); } catch (ParseException e) { log.warn("SIP回复时解析异常", e); } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/IMessageHandler.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.Platform; import org.dom4j.Element; import javax.sip.RequestEvent; public interface IMessageHandler { /** * 处理来自设备的信息 * @param evt * @param device */ void handForDevice(RequestEvent evt, Device device, Element element); /** * 处理来自平台的信息 * @param evt * @param parentPlatform */ void handForPlatform(RequestEvent evt, Platform parentPlatform, Element element); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/MessageHandlerAbstract.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.Platform; import com.genersoft.iot.vmp.gb28181.event.MessageSubscribe; import com.genersoft.iot.vmp.gb28181.event.sip.MessageEvent; import com.genersoft.iot.vmp.gb28181.service.IPlatformService; import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.query.cmd.CatalogQueryMessageHandler; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import gov.nist.javax.sip.message.SIPRequest; import lombok.extern.slf4j.Slf4j; import org.dom4j.Element; import org.springframework.beans.factory.annotation.Autowired; import javax.sip.InvalidArgumentException; import javax.sip.RequestEvent; import javax.sip.SipException; import javax.sip.message.Response; import java.text.ParseException; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText; @Slf4j public abstract class MessageHandlerAbstract extends SIPRequestProcessorParent implements IMessageHandler{ public Map messageHandlerMap = new ConcurrentHashMap<>(); @Autowired private IPlatformService platformService; @Autowired private MessageSubscribe messageSubscribe; public void addHandler(String cmdType, IMessageHandler messageHandler) { messageHandlerMap.put(cmdType, messageHandler); } @Override public void handForDevice(RequestEvent evt, Device device, Element element) { String cmd = getText(element, "CmdType"); if (cmd == null) { try { responseAck((SIPRequest) evt.getRequest(), Response.OK); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 回复200 OK: {}", e.getMessage()); } return; } IMessageHandler messageHandler = messageHandlerMap.get(cmd); if (messageHandler != null) { //两个国标平台互相级联时由于上一步判断导致本该在平台处理的消息 放到了设备的处理逻辑 //所以对目录查询单独做了校验 if(messageHandler instanceof CatalogQueryMessageHandler){ Platform parentPlatform = platformService.queryPlatformByServerGBId(device.getDeviceId()); messageHandler.handForPlatform(evt, parentPlatform, element); return; } messageHandler.handForDevice(evt, device, element); }else { handMessageEvent(element, null); } } @Override public void handForPlatform(RequestEvent evt, Platform parentPlatform, Element element) { String cmd = getText(element, "CmdType"); IMessageHandler messageHandler = messageHandlerMap.get(cmd); if (messageHandler != null) { messageHandler.handForPlatform(evt, parentPlatform, element); } } public void handMessageEvent(Element element, Object data) { String cmd = getText(element, "CmdType"); String sn = getText(element, "SN"); MessageEvent subscribe = (MessageEvent)messageSubscribe.getSubscribe(cmd + sn); if (subscribe != null && subscribe.getCallback() != null) { String result = getText(element, "Result"); if (result == null || "OK".equalsIgnoreCase(result) || data != null) { subscribe.getCallback().run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), data); }else { subscribe.getCallback().run(ErrorCode.ERROR100.getCode(), ErrorCode.ERROR100.getMsg(), result); } messageSubscribe.removeSubscribe(cmd + sn); } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/MessageRequestProcessor.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.DeviceNotFoundEvent; import com.genersoft.iot.vmp.gb28181.bean.Platform; import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction; import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; import com.genersoft.iot.vmp.gb28181.event.sip.SipEvent; import com.genersoft.iot.vmp.gb28181.service.IPlatformService; import com.genersoft.iot.vmp.gb28181.session.SipInviteSessionManager; import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver; import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor; import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; import com.genersoft.iot.vmp.gb28181.utils.SipUtils; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import gov.nist.javax.sip.message.SIPRequest; import lombok.extern.slf4j.Slf4j; import org.dom4j.DocumentException; import org.dom4j.Element; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.sip.InvalidArgumentException; import javax.sip.RequestEvent; import javax.sip.SipException; import javax.sip.header.CSeqHeader; import javax.sip.header.CallIdHeader; import javax.sip.message.Response; import java.text.ParseException; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @Slf4j @Component public class MessageRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor { private final String method = "MESSAGE"; private static final Map messageHandlerMap = new ConcurrentHashMap<>(); @Autowired private SIPProcessorObserver sipProcessorObserver; @Autowired private IPlatformService platformService; @Autowired private SipSubscribe sipSubscribe; @Autowired private IRedisCatchStorage redisCatchStorage; @Autowired private SipInviteSessionManager sessionManager; @Override public void afterPropertiesSet() throws Exception { // 添加消息处理的订阅 sipProcessorObserver.addRequestProcessor(method, this); } public void addHandler(String name, IMessageHandler handler) { messageHandlerMap.put(name, handler); } @Override public void process(RequestEvent evt) { SIPRequest sipRequest = (SIPRequest)evt.getRequest(); // logger.info("接收到消息:" + evt.getRequest()); String deviceId = SipUtils.getUserIdFromFromHeader(evt.getRequest()); CallIdHeader callIdHeader = sipRequest.getCallIdHeader(); CSeqHeader cSeqHeader = sipRequest.getCSeqHeader(); // 先从会话内查找 SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransactionByCallId(callIdHeader.getCallId()); // 兼容海康 媒体通知 消息from字段不是设备ID的问题 if (ssrcTransaction != null) { deviceId = ssrcTransaction.getDeviceId(); } SIPRequest request = (SIPRequest) evt.getRequest(); // 查询设备是否存在 Device device = redisCatchStorage.getDevice(deviceId); // 查询上级平台是否存在 Platform parentPlatform = platformService.queryPlatformByServerGBId(deviceId); try { if (device != null && parentPlatform != null) { String hostAddress = request.getRemoteAddress().getHostAddress(); int remotePort = request.getRemotePort(); if (device.getHostAddress().equals(hostAddress + ":" + remotePort)) { parentPlatform = null; }else { device = null; } } if (device == null && parentPlatform == null) { // 不存在则回复404 responseAck(request, Response.NOT_FOUND, "device "+ deviceId +" not found"); log.warn("[设备未找到 ]deviceId: {}, callId: {}", deviceId, callIdHeader.getCallId()); SipEvent sipEvent = sipSubscribe.getSubscribe(callIdHeader.getCallId() + cSeqHeader.getSeqNumber()); if (sipEvent != null && sipEvent.getErrorEvent() != null){ DeviceNotFoundEvent deviceNotFoundEvent = new DeviceNotFoundEvent(callIdHeader.getCallId()); SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(deviceNotFoundEvent); sipEvent.getErrorEvent().response(eventResult); } }else { Element rootElement; try { rootElement = getRootElement(evt); if (rootElement == null) { log.error("处理MESSAGE请求 未获取到消息体{}", evt.getRequest()); responseAck(request, Response.BAD_REQUEST, "content is null"); return; } String name = rootElement.getName(); IMessageHandler messageHandler = messageHandlerMap.get(name); if (messageHandler != null) { if (device != null) { messageHandler.handForDevice(evt, device, rootElement); }else { // 由于上面已经判断都为null则直接返回,所以这里device和parentPlatform必有一个不为null messageHandler.handForPlatform(evt, parentPlatform, rootElement); } }else { // 不支持的message // 不存在则回复415 responseAck(request, Response.UNSUPPORTED_MEDIA_TYPE, "Unsupported message type, must Control/Notify/Query/Response"); } } catch (DocumentException e) { log.warn("解析XML消息内容异常", e); // 不存在则回复404 responseAck(request, Response.BAD_REQUEST, e.getMessage()); } } } catch (SipException e) { log.warn("SIP 回复错误", e); } catch (InvalidArgumentException e) { log.warn("参数无效", e); } catch (ParseException e) { log.warn("SIP回复时解析异常", e); } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/control/ControlMessageHandler.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.control; import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.MessageHandlerAbstract; import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.MessageRequestProcessor; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** * 命令类型: 控制命令 * 命令类型: 设备控制: 远程启动, 录像控制(TODO), 报警布防/撤防命令(TODO), 报警复位命令(TODO), * 强制关键帧命令(TODO), 拉框放大/缩小控制命令(TODO), 看守位控制(TODO), 报警复位(TODO) * 命令类型: 设备配置: SVAC编码配置(TODO), 音频参数(TODO), SVAC解码配置(TODO) */ @Component public class ControlMessageHandler extends MessageHandlerAbstract implements InitializingBean { private final String messageType = "Control"; @Autowired private MessageRequestProcessor messageRequestProcessor; @Override public void afterPropertiesSet() throws Exception { messageRequestProcessor.addHandler(messageType, this); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/control/cmd/DeviceControlQueryMessageHandler.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.control.cmd; import com.genersoft.iot.vmp.common.enums.ChannelDataType; import com.genersoft.iot.vmp.common.enums.DeviceControlType; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService; import com.genersoft.iot.vmp.gb28181.service.IDeviceService; import com.genersoft.iot.vmp.gb28181.service.IGbChannelControlService; import com.genersoft.iot.vmp.gb28181.service.IGbChannelService; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; 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.control.ControlMessageHandler; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import gov.nist.javax.sip.message.SIPRequest; import lombok.extern.slf4j.Slf4j; import org.dom4j.Element; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; import javax.sip.InvalidArgumentException; import javax.sip.RequestEvent; import javax.sip.SipException; import javax.sip.address.SipURI; import javax.sip.message.Response; import java.text.ParseException; import java.util.List; import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText; import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.loadElement; @Slf4j @Component public class DeviceControlQueryMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler { private final String cmdType = "DeviceControl"; @Autowired private ControlMessageHandler controlMessageHandler; @Autowired private IGbChannelService channelService; @Autowired private IGbChannelControlService channelControlService; @Autowired private IDeviceService deviceService; @Autowired private IDeviceChannelService deviceChannelService; @Autowired private SIPCommander cmder; @Override public void afterPropertiesSet() throws Exception { controlMessageHandler.addHandler(cmdType, this); } @Override public void handForDevice(RequestEvent evt, Device device, Element element) { } @Override public void handForPlatform(RequestEvent evt, Platform platform, Element rootElement) { SIPRequest request = (SIPRequest) evt.getRequest(); // 此处是上级发出的DeviceControl指令 String targetGBId = ((SipURI) request.getToHeader().getAddress().getURI()).getUser(); String channelId = getText(rootElement, "DeviceID"); // 远程启动功能 if (!ObjectUtils.isEmpty(getText(rootElement, "TeleBoot"))) { // 拒绝远程启动命令 log.warn("[deviceControl] 远程启动命令, 禁用,不允许上级平台随意重启下级平台"); try { responseAck(request, Response.FORBIDDEN); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 错误信息: {}", e.getMessage()); } return; } DeviceControlType deviceControlType = DeviceControlType.typeOf(rootElement); CommonGBChannel channel = channelService.queryOneWithPlatform(platform.getId(), channelId); if (channel == null) { log.warn("[deviceControl] 未找到通道, 平台: {}({}),通道编号:{}", platform.getName(), platform.getServerGBId(), channelId); try { responseAck(request, Response.NOT_FOUND, "channel not found"); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 错误信息: {}", e.getMessage()); } return; } log.info("[deviceControl] 命令: {}, 平台: {}({})->{}", deviceControlType, platform.getName(), platform.getServerGBId(), channel.getGbId()); if (!ObjectUtils.isEmpty(deviceControlType)) { switch (deviceControlType) { case PTZ: handlePtzCmd(channel, rootElement, request, DeviceControlType.PTZ); break; case ALARM: handleAlarmCmd(channel, rootElement, request); break; case GUARD: handleGuardCmd(channel, rootElement, request, DeviceControlType.GUARD); break; case RECORD: handleRecordCmd(channel, rootElement, request, DeviceControlType.RECORD); break; case I_FRAME: handleIFameCmd(channel, request); break; case TELE_BOOT: handleTeleBootCmd(channel, request); break; case DRAG_ZOOM_IN: handleDragZoom(channel, rootElement, request, DeviceControlType.DRAG_ZOOM_IN); break; case DRAG_ZOOM_OUT: handleDragZoom(channel, rootElement, request, DeviceControlType.DRAG_ZOOM_OUT); break; case HOME_POSITION: handleHomePositionCmd(channel, rootElement, request, DeviceControlType.HOME_POSITION); break; default: break; } } } /** * 处理云台指令 */ private void handlePtzCmd(CommonGBChannel channel, Element rootElement, SIPRequest request, DeviceControlType type) { if (channel.getDataType() == ChannelDataType.GB28181) { deviceChannelService.handlePtzCmd(channel.getDataDeviceId(), channel.getGbId(), rootElement, type, ((code, msg, data) -> { try { responseAck(request, code, msg); } catch (InvalidArgumentException | SipException | ParseException exception) { log.error("[命令发送失败] 云台指令: {}", exception.getMessage()); } })); }else { // 解析云台控制参数 String cmdString = getText(rootElement, type.getVal()); IFrontEndControlCode frontEndControlCode = FrontEndCode.decode(cmdString); if (frontEndControlCode == null) { log.info("[INFO 消息] 不支持的控制方式"); try { responseAck(request, Response.FORBIDDEN, ""); } catch (InvalidArgumentException | SipException | ParseException exception) { log.error("[命令发送失败] 云台指令: {}", exception.getMessage()); } return; } switch (frontEndControlCode.getType()){ case PTZ: channelControlService.ptz(channel, (FrontEndControlCodeForPTZ)frontEndControlCode, ((code, msg, data) -> { try { if (code == ErrorCode.SUCCESS.getCode()) { responseAck(request, Response.OK); }else { responseAck(request, Response.FORBIDDEN); } } catch (InvalidArgumentException | SipException | ParseException exception) { log.error("[命令发送失败] 云台指令: {}", exception.getMessage()); } })); break; case FI: channelControlService.fi(channel, (FrontEndControlCodeForFI) frontEndControlCode, ((code, msg, data) -> { try { if (code == ErrorCode.SUCCESS.getCode()) { responseAck(request, Response.OK); }else { responseAck(request, Response.FORBIDDEN); } } catch (InvalidArgumentException | SipException | ParseException exception) { log.error("[命令发送失败] FI指令: {}", exception.getMessage()); } })); break; case PRESET: channelControlService.preset(channel, (FrontEndControlCodeForPreset) frontEndControlCode, ((code, msg, data) -> { try { if (code == ErrorCode.SUCCESS.getCode()) { responseAck(request, Response.OK); }else { responseAck(request, Response.FORBIDDEN); } } catch (InvalidArgumentException | SipException | ParseException exception) { log.error("[命令发送失败] 预置位指令: {}", exception.getMessage()); } })); break; case TOUR: channelControlService.tour(channel, (FrontEndControlCodeForTour) frontEndControlCode, ((code, msg, data) -> { try { if (code == ErrorCode.SUCCESS.getCode()) { responseAck(request, Response.OK); }else { responseAck(request, Response.FORBIDDEN); } } catch (InvalidArgumentException | SipException | ParseException exception) { log.error("[命令发送失败] 巡航指令: {}", exception.getMessage()); } })); break; case SCAN: channelControlService.scan(channel, (FrontEndControlCodeForScan) frontEndControlCode, ((code, msg, data) -> { try { if (code == ErrorCode.SUCCESS.getCode()) { responseAck(request, Response.OK); }else { responseAck(request, Response.FORBIDDEN); } } catch (InvalidArgumentException | SipException | ParseException exception) { log.error("[命令发送失败] 扫描指令: {}", exception.getMessage()); } })); break; case AUXILIARY: channelControlService.auxiliary(channel, (FrontEndControlCodeForAuxiliary) frontEndControlCode, ((code, msg, data) -> { try { if (code == ErrorCode.SUCCESS.getCode()) { responseAck(request, Response.OK); }else { responseAck(request, Response.FORBIDDEN); } } catch (InvalidArgumentException | SipException | ParseException exception) { log.error("[命令发送失败] 辅助开关指令: {}", exception.getMessage()); } })); break; default: log.info("[INFO 消息] 设备不支持的控制方式"); try { responseAck(request, Response.FORBIDDEN, ""); } catch (InvalidArgumentException | SipException | ParseException exception) { log.error("[命令发送失败] 云台指令: {}", exception.getMessage()); } } } } /** * 处理强制关键帧 */ private void handleIFameCmd(CommonGBChannel channel, SIPRequest request) { if (channel.getDataType() != ChannelDataType.GB28181) { // 只支持国标的云台控制 log.warn("[INFO 消息] 只支持国标的处理强制关键帧, 通道ID: {}", channel.getGbId()); try { responseAck(request, Response.FORBIDDEN, ""); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 错误信息: {}", e.getMessage()); } return; } // 根据通道ID,获取所属设备 Device device = deviceService.getDevice(channel.getDataDeviceId()); if (device == null) { // 不存在则回复404 log.warn("[INFO 消息] 通道所属设备不存在, 通道ID: {}", channel.getGbId()); try { responseAck(request, Response.NOT_FOUND, "device not found"); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 错误信息: {}", e.getMessage()); } return; } DeviceChannel deviceChannel = deviceChannelService.getOneForSourceById(channel.getGbId()); if (deviceChannel == null) { log.warn("[deviceControl] 未找到设备原始通道, 设备: {}({}),通道编号:{}", device.getName(), device.getDeviceId(), channel.getGbId()); try { responseAck(request, Response.NOT_FOUND, "channel not found"); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 错误信息: {}", e.getMessage()); } return; } log.info("[deviceControl] 命令: 强制关键帧, 设备: {}({}), 通道{}({}", device.getName(), device.getDeviceId(), deviceChannel.getName(), deviceChannel.getDeviceId()); try { cmder.iFrameCmd(device, deviceChannel.getDeviceId()); responseAck(request, Response.OK); } catch (InvalidArgumentException | SipException | ParseException e) { log.error("[命令发送失败] 强制关键帧: {}", e.getMessage()); } } /** * 处理重启命令 */ private void handleTeleBootCmd(CommonGBChannel channel, SIPRequest request) { if (channel.getDataType() != ChannelDataType.GB28181) { // 只支持国标的云台控制 log.warn("[INFO 消息] 只支持国标的重启命令, 通道ID: {}", channel.getGbId()); try { responseAck(request, Response.FORBIDDEN, ""); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 错误信息: {}", e.getMessage()); } return; } // 根据通道ID,获取所属设备 Device device = deviceService.getDevice(channel.getDataDeviceId()); if (device == null) { // 不存在则回复404 log.warn("[INFO 消息] 通道所属设备不存在, 通道ID: {}", channel.getGbId()); try { responseAck(request, Response.NOT_FOUND, "device not found"); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 错误信息: {}", e.getMessage()); } return; } try { cmder.teleBootCmd(device); responseAck(request, Response.OK); } catch (InvalidArgumentException | SipException | ParseException e) { log.error("[命令发送失败] 重启: {}", e.getMessage()); } } /** * 处理拉框控制 */ private void handleDragZoom(CommonGBChannel channel, Element rootElement, SIPRequest request, DeviceControlType type) { if (channel.getDataType() != ChannelDataType.GB28181) { // 只支持国标的云台控制 log.warn("[deviceControl-DragZoom] 只支持国标的拉框控制, 通道ID: {}", channel.getGbId()); try { responseAck(request, Response.FORBIDDEN, ""); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 错误信息: {}", e.getMessage()); } return; } // 根据通道ID,获取所属设备 Device device = deviceService.getDevice(channel.getDataDeviceId()); if (device == null) { // 不存在则回复404 log.warn("[deviceControl-DragZoom] 通道所属设备不存在, 通道ID: {}", channel.getGbId()); try { responseAck(request, Response.NOT_FOUND, "device not found"); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 错误信息: {}", e.getMessage()); } return; } DeviceChannel deviceChannel = deviceChannelService.getOneForSourceById(channel.getGbId()); if (deviceChannel == null) { log.warn("[deviceControl-DragZoom] 未找到设备原始通道, 设备: {}({}),通道编号:{}", device.getName(), device.getDeviceId(), channel.getGbId()); try { responseAck(request, Response.NOT_FOUND, "channel not found"); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 错误信息: {}", e.getMessage()); } return; } log.info("[deviceControl] 命令: {}, 设备: {}({}), 通道{}({}", type, device.getName(), device.getDeviceId(), deviceChannel.getName(), deviceChannel.getDeviceId()); try { DragZoomRequest dragZoomRequest = loadElement(rootElement, DragZoomRequest.class); DragZoomParam dragZoom = dragZoomRequest.getDragZoomIn(); if (dragZoom == null) { dragZoom = dragZoomRequest.getDragZoomOut(); } StringBuffer cmdXml = new StringBuffer(200); cmdXml.append("<" + type.getVal() + ">\r\n"); cmdXml.append("" + dragZoom.getLength() + "\r\n"); cmdXml.append("" + dragZoom.getWidth() + "\r\n"); cmdXml.append("" + dragZoom.getMidPointX() + "\r\n"); cmdXml.append("" + dragZoom.getMidPointY() + "\r\n"); cmdXml.append("" + dragZoom.getLengthX() + "\r\n"); cmdXml.append("" + dragZoom.getLengthY() + "\r\n"); cmdXml.append("\r\n"); cmder.dragZoomCmd(device, deviceChannel.getDeviceId(), cmdXml.toString(), (code, msg, data) -> { }); responseAck(request, Response.OK); } catch (Exception e) { log.error("[命令发送失败] 拉框控制: {}", e.getMessage()); } } /** * 处理看守位命令 */ private void handleHomePositionCmd(CommonGBChannel channel, Element rootElement, SIPRequest request, DeviceControlType type) { if (channel.getDataType() != ChannelDataType.GB28181) { // 只支持国标的云台控制 log.warn("[INFO 消息] 只支持国标的看守位命令, 通道ID: {}", channel.getGbId()); try { responseAck(request, Response.FORBIDDEN, ""); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 错误信息: {}", e.getMessage()); } return; } // 根据通道ID,获取所属设备 Device device = deviceService.getDevice(channel.getDataDeviceId()); if (device == null) { // 不存在则回复404 log.warn("[INFO 消息] 通道所属设备不存在, 通道ID: {}", channel.getGbId()); try { responseAck(request, Response.NOT_FOUND, "device not found"); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 错误信息: {}", e.getMessage()); } return; } DeviceChannel deviceChannel = deviceChannelService.getOneForSourceById(channel.getGbId()); if (deviceChannel == null) { log.warn("[deviceControl] 未找到设备原始通道, 设备: {}({}),通道编号:{}", device.getName(), device.getDeviceId(), channel.getGbId()); try { responseAck(request, Response.NOT_FOUND, "channel not found"); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 错误信息: {}", e.getMessage()); } return; } log.info("[deviceControl] 命令: {}, 设备: {}({}), 通道{}({}", type, device.getName(), device.getDeviceId(), deviceChannel.getName(), deviceChannel.getDeviceId()); try { HomePositionRequest homePosition = loadElement(rootElement, HomePositionRequest.class); //获取整个消息主体,我们只需要修改请求头即可 HomePositionRequest.HomePosition info = homePosition.getHomePosition(); cmder.homePositionCmd(device, deviceChannel.getDeviceId(), !"0".equals(info.getEnabled()), Integer.parseInt(info.getResetTime()), Integer.parseInt(info.getPresetIndex()), (code, msg, data) -> { if (code == ErrorCode.SUCCESS.getCode()) { onOk(request); }else { onError(request, code, msg); } }); } catch (Exception e) { log.error("[命令发送失败] 看守位设置: {}", e.getMessage()); } } /** * 处理告警消息 */ private void handleAlarmCmd(CommonGBChannel channel, Element rootElement, SIPRequest request) { if (channel.getDataType() != ChannelDataType.GB28181) { // 只支持国标的云台控制 log.warn("[INFO 消息] 只支持国标的告警消息, 通道ID: {}", channel.getGbId()); try { responseAck(request, Response.FORBIDDEN, ""); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 错误信息: {}", e.getMessage()); } return; } // 根据通道ID,获取所属设备 Device device = deviceService.getDevice(channel.getDataDeviceId()); if (device == null) { // 不存在则回复404 log.warn("[INFO 消息] 通道所属设备不存在, 通道ID: {}", channel.getGbId()); try { responseAck(request, Response.NOT_FOUND, "device not found"); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 错误信息: {}", e.getMessage()); } return; } //告警方法 String alarmMethod = ""; //告警类型 String alarmType = ""; List info = rootElement.elements("Info"); if (info != null) { for (Element element : info) { alarmMethod = getText(element, "AlarmMethod"); alarmType = getText(element, "AlarmType"); } } try { cmder.alarmResetCmd(device, alarmMethod, alarmType, (code, msg, data) -> { if (code == ErrorCode.SUCCESS.getCode()) { onOk(request); }else { onError(request, code, msg); } }); } catch (InvalidArgumentException | SipException | ParseException e) { log.error("[命令发送失败] 告警消息: {}", e.getMessage()); } } /** * 处理录像控制 */ private void handleRecordCmd(CommonGBChannel channel, Element rootElement, SIPRequest request, DeviceControlType type) { if (channel.getDataType() != ChannelDataType.GB28181) { // 只支持国标的云台控制 log.warn("[INFO 消息] 只支持国标的息录像控制, 通道ID: {}", channel.getGbId()); try { responseAck(request, Response.FORBIDDEN, ""); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 错误信息: {}", e.getMessage()); } return; } // 根据通道ID,获取所属设备 Device device = deviceService.getDevice(channel.getDataDeviceId()); if (device == null) { // 不存在则回复404 log.warn("[INFO 消息] 通道所属设备不存在, 通道ID: {}", channel.getGbId()); try { responseAck(request, Response.NOT_FOUND, "device not found"); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 错误信息: {}", e.getMessage()); } return; } DeviceChannel deviceChannel = deviceChannelService.getOneForSourceById(channel.getGbId()); if (deviceChannel == null) { // 拒绝远程启动命令 log.warn("[deviceControl] 未找到设备原始通道, 设备: {}({}),通道编号:{}", device.getName(), device.getDeviceId(), channel.getGbId()); try { responseAck(request, Response.NOT_FOUND, "channel not found"); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 错误信息: {}", e.getMessage()); } return; } log.info("[deviceControl] 命令: {}, 设备: {}({}), 通道{}({}", type, device.getName(), device.getDeviceId(), deviceChannel.getName(), deviceChannel.getDeviceId()); //获取整个消息主体,我们只需要修改请求头即可 String cmdString = getText(rootElement, type.getVal()); try { cmder.recordCmd(device, deviceChannel.getDeviceId(), cmdString, (code, msg, data) -> { if (code == ErrorCode.SUCCESS.getCode()) { onOk(request); }else { onError(request, code, msg); } }); } catch (InvalidArgumentException | SipException | ParseException e) { log.error("[命令发送失败] 录像控制: {}", e.getMessage()); } } /** * 处理报警布防/撤防命令 */ private void handleGuardCmd(CommonGBChannel channel, Element rootElement, SIPRequest request, DeviceControlType type) { if (channel.getDataType() != ChannelDataType.GB28181) { // 只支持国标的云台控制 log.warn("[INFO 消息] 只支持国标的报警布防/撤防命令, 通道ID: {}", channel.getGbId()); try { responseAck(request, Response.FORBIDDEN, ""); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 错误信息: {}", e.getMessage()); } return; } // 根据通道ID,获取所属设备 Device device = deviceService.getDevice(channel.getDataDeviceId()); if (device == null) { // 不存在则回复404 log.warn("[INFO 消息] 通道所属设备不存在, 通道ID: {}", channel.getGbId()); try { responseAck(request, Response.NOT_FOUND, "device not found"); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 错误信息: {}", e.getMessage()); } return; } //获取整个消息主体,我们只需要修改请求头即可 String cmdString = getText(rootElement, type.getVal()); try { cmder.guardCmd(device, cmdString,(code, msg, data) -> { if (code == ErrorCode.SUCCESS.getCode()) { onOk(request); }else { onError(request, code, msg); } }); } catch (InvalidArgumentException | SipException | ParseException e) { log.error("[命令发送失败] 布防/撤防命令: {}", e.getMessage()); } } /** * 错误响应处理 * */ private void onError(SIPRequest request, Integer code, String msg) { // 失败的回复 try { responseAck(request, code, msg); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 回复: {}", e.getMessage()); } } private void onError(SIPRequest request, SipSubscribe.EventResult errorResult) { onError(request, errorResult.statusCode, errorResult.msg); } /** * 成功响应处理 * * @param request 请求 */ private void onOk(SIPRequest request) { // 成功的回复 try { responseAck(request, Response.OK); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 回复: {}", e.getMessage()); } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/NotifyMessageHandler.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify; import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.MessageHandlerAbstract; import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.MessageRequestProcessor; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** * 命令类型: 通知命令, 参看 A.2.5 通知命令 * 命令类型: 状态信息(心跳)报送, 报警通知, 媒体通知, 移动设备位置数据,语音广播通知(TODO), 设备预置位(TODO) * @author lin */ @Component public class NotifyMessageHandler extends MessageHandlerAbstract implements InitializingBean { private final String messageType = "Notify"; @Autowired private MessageRequestProcessor messageRequestProcessor; @Override public void afterPropertiesSet() throws Exception { messageRequestProcessor.addHandler(messageType, this); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/AlarmNotifyMessageHandler.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.cmd; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.conf.SipConfig; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.event.EventPublisher; import com.genersoft.iot.vmp.gb28181.service.IDeviceAlarmService; import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService; 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.notify.NotifyMessageHandler; import com.genersoft.iot.vmp.gb28181.utils.NumericUtil; import com.genersoft.iot.vmp.gb28181.utils.XmlUtil; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.utils.DateUtil; import gov.nist.javax.sip.message.SIPRequest; import lombok.extern.slf4j.Slf4j; import org.dom4j.Element; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; 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.List; import java.util.concurrent.ConcurrentLinkedQueue; import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText; /** * 报警事件的处理,参考:9.4 */ @Slf4j @Component public class AlarmNotifyMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler { private final String cmdType = "Alarm"; @Autowired private NotifyMessageHandler notifyMessageHandler; @Autowired private EventPublisher publisher; @Autowired private UserSetting userSetting; @Autowired private SipConfig sipConfig; @Autowired private IRedisCatchStorage redisCatchStorage; @Autowired private IDeviceAlarmService deviceAlarmService; @Autowired private IDeviceChannelService deviceChannelService; private final ConcurrentLinkedQueue taskQueue = new ConcurrentLinkedQueue<>(); @Override public void afterPropertiesSet() throws Exception { notifyMessageHandler.addHandler(cmdType, this); } @Override public void handForDevice(RequestEvent evt, Device device, Element rootElement) { if (taskQueue.size() >= userSetting.getMaxNotifyCountQueue()) { log.error("[Alarm] 待处理消息队列已满 {},返回486 BUSY_HERE,消息不做处理", userSetting.getMaxNotifyCountQueue()); return; } taskQueue.offer(new SipMsgInfo(evt, device, rootElement)); } @Scheduled(fixedDelay = 200) public void executeTaskQueue() { if (taskQueue.isEmpty()) { return; } List handlerCatchDataList = new ArrayList<>(); int size = taskQueue.size(); for (int i = 0; i < size; i++) { SipMsgInfo poll = taskQueue.poll(); if (poll != null) { handlerCatchDataList.add(poll); } } if (handlerCatchDataList.isEmpty()) { return; } for (SipMsgInfo sipMsgInfo : handlerCatchDataList) { if (sipMsgInfo == null) { continue; } RequestEvent evt = sipMsgInfo.getEvt(); // 回复200 OK try { responseAck((SIPRequest) evt.getRequest(), Response.OK); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 报警通知回复: {}", e.getMessage()); } try { Device device = sipMsgInfo.getDevice(); Element deviceIdElement = sipMsgInfo.getRootElement().element("DeviceID"); String channelId = deviceIdElement.getText(); DeviceAlarm deviceAlarm = new DeviceAlarm(); deviceAlarm.setCreateTime(DateUtil.getNow()); deviceAlarm.setDeviceId(sipMsgInfo.getDevice().getDeviceId()); deviceAlarm.setDeviceName(sipMsgInfo.getDevice().getName()); deviceAlarm.setChannelId(channelId); deviceAlarm.setAlarmPriority(getText(sipMsgInfo.getRootElement(), "AlarmPriority")); deviceAlarm.setAlarmMethod(getText(sipMsgInfo.getRootElement(), "AlarmMethod")); String alarmTime = XmlUtil.getText(sipMsgInfo.getRootElement(), "AlarmTime"); if (alarmTime == null) { continue; } deviceAlarm.setAlarmTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(alarmTime)); String alarmDescription = getText(sipMsgInfo.getRootElement(), "AlarmDescription"); if (alarmDescription == null) { deviceAlarm.setAlarmDescription(""); } else { deviceAlarm.setAlarmDescription(alarmDescription); } String longitude = getText(sipMsgInfo.getRootElement(), "Longitude"); if (longitude != null && NumericUtil.isDouble(longitude)) { deviceAlarm.setLongitude(Double.parseDouble(longitude)); } else { deviceAlarm.setLongitude(0.00); } String latitude = getText(sipMsgInfo.getRootElement(), "Latitude"); if (latitude != null && NumericUtil.isDouble(latitude)) { deviceAlarm.setLatitude(Double.parseDouble(latitude)); } else { deviceAlarm.setLatitude(0.00); } if (!ObjectUtils.isEmpty(deviceAlarm.getAlarmMethod()) && deviceAlarm.getAlarmMethod().contains(DeviceAlarmMethod.GPS.getVal() + "")) { DeviceChannel deviceChannel = deviceChannelService.getOne(device.getDeviceId(), channelId); if (deviceChannel == null) { log.warn("[解析报警消息] 未找到通道:{}/{}", device.getDeviceId(), channelId); } else { MobilePosition mobilePosition = new MobilePosition(); mobilePosition.setCreateTime(DateUtil.getNow()); mobilePosition.setDeviceId(deviceAlarm.getDeviceId()); mobilePosition.setChannelId(deviceChannel.getId()); mobilePosition.setChannelDeviceId(deviceChannel.getDeviceId()); mobilePosition.setTime(deviceAlarm.getAlarmTime()); mobilePosition.setLongitude(deviceAlarm.getLongitude()); mobilePosition.setLatitude(deviceAlarm.getLatitude()); mobilePosition.setReportSource("GPS Alarm"); // 更新device channel 的经纬度 deviceChannel.setLongitude(mobilePosition.getLongitude()); deviceChannel.setLatitude(mobilePosition.getLatitude()); deviceChannel.setGpsTime(mobilePosition.getTime()); deviceChannelService.updateChannelGPS(device, deviceChannel, mobilePosition); } } if (!ObjectUtils.isEmpty(deviceAlarm.getDeviceId())) { if (deviceAlarm.getAlarmMethod().contains(DeviceAlarmMethod.Video.getVal() + "")) { deviceAlarm.setAlarmType(getText(sipMsgInfo.getRootElement().element("Info"), "AlarmType")); } } if (log.isDebugEnabled()) { log.debug("[收到报警通知]设备:{}, 内容:{}", device.getDeviceId(), JSON.toJSONString(deviceAlarm)); } // 作者自用判断,其他小伙伴需要此消息可以自行修改,但是不要提在pr里 if (DeviceAlarmMethod.Other.getVal() == Integer.parseInt(deviceAlarm.getAlarmMethod())) { // 发送给平台的报警信息。 发送redis通知 log.info("[发送给平台的报警信息]内容:{}", JSONObject.toJSONString(deviceAlarm)); AlarmChannelMessage alarmChannelMessage = new AlarmChannelMessage(); if (deviceAlarm.getAlarmMethod() != null) { alarmChannelMessage.setAlarmSn(Integer.parseInt(deviceAlarm.getAlarmMethod())); } alarmChannelMessage.setAlarmDescription(deviceAlarm.getAlarmDescription()); if (deviceAlarm.getAlarmType() != null) { alarmChannelMessage.setAlarmType(Integer.parseInt(deviceAlarm.getAlarmType())); } alarmChannelMessage.setGbId(channelId); redisCatchStorage.sendAlarmMsg(alarmChannelMessage); continue; } log.debug("存储报警信息、报警分类"); // 存储报警信息、报警分类 if (sipConfig.isAlarm()) { deviceAlarmService.add(deviceAlarm); } if (redisCatchStorage.deviceIsOnline(sipMsgInfo.getDevice().getDeviceId())) { publisher.deviceAlarmEventPublish(deviceAlarm); } } catch (Exception e) { log.error("未处理的异常 ", e); log.warn("[收到报警通知] 发现未处理的异常, {}\r\n{}", e.getMessage(), evt.getRequest()); } } } @Override public void handForPlatform(RequestEvent evt, Platform parentPlatform, Element rootElement) { log.info("收到来自平台[{}]的报警通知", parentPlatform.getServerGBId()); // 回复200 OK try { responseAck((SIPRequest) evt.getRequest(), Response.OK); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 国标级联 报警通知回复: {}", e.getMessage()); } Element deviceIdElement = rootElement.element("DeviceID"); String channelId = deviceIdElement.getText(); DeviceAlarm deviceAlarm = new DeviceAlarm(); deviceAlarm.setCreateTime(DateUtil.getNow()); deviceAlarm.setDeviceId(parentPlatform.getServerGBId()); deviceAlarm.setDeviceName(parentPlatform.getName()); deviceAlarm.setChannelId(channelId); deviceAlarm.setAlarmPriority(getText(rootElement, "AlarmPriority")); deviceAlarm.setAlarmMethod(getText(rootElement, "AlarmMethod")); String alarmTime = XmlUtil.getText(rootElement, "AlarmTime"); if (alarmTime == null) { return; } deviceAlarm.setAlarmTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(alarmTime)); String alarmDescription = getText(rootElement, "AlarmDescription"); if (alarmDescription == null) { deviceAlarm.setAlarmDescription(""); } else { deviceAlarm.setAlarmDescription(alarmDescription); } String longitude = getText(rootElement, "Longitude"); if (longitude != null && NumericUtil.isDouble(longitude)) { deviceAlarm.setLongitude(Double.parseDouble(longitude)); } else { deviceAlarm.setLongitude(0.00); } String latitude = getText(rootElement, "Latitude"); if (latitude != null && NumericUtil.isDouble(latitude)) { deviceAlarm.setLatitude(Double.parseDouble(latitude)); } else { deviceAlarm.setLatitude(0.00); } if (!ObjectUtils.isEmpty(deviceAlarm.getAlarmMethod())) { if (deviceAlarm.getAlarmMethod().contains(DeviceAlarmMethod.Video.getVal() + "")) { deviceAlarm.setAlarmType(getText(rootElement.element("Info"), "AlarmType")); } } if (channelId.equals(parentPlatform.getDeviceGBId())) { // 发送给平台的报警信息。 发送redis通知 AlarmChannelMessage alarmChannelMessage = new AlarmChannelMessage(); if (deviceAlarm.getAlarmMethod() != null) { alarmChannelMessage.setAlarmSn(Integer.parseInt(deviceAlarm.getAlarmMethod())); } alarmChannelMessage.setAlarmDescription(deviceAlarm.getAlarmDescription()); alarmChannelMessage.setGbId(channelId); if (deviceAlarm.getAlarmType() != null) { alarmChannelMessage.setAlarmType(Integer.parseInt(deviceAlarm.getAlarmType())); } redisCatchStorage.sendAlarmMsg(alarmChannelMessage); } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/BroadcastNotifyMessageHandler.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.cmd; import com.genersoft.iot.vmp.common.enums.ChannelDataType; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.service.*; import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager; import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; 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.notify.NotifyMessageHandler; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.service.IMediaServerService; import com.genersoft.iot.vmp.service.ISendRtpServerService; import gov.nist.javax.sip.message.SIPRequest; import lombok.extern.slf4j.Slf4j; import org.dom4j.Element; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.sip.InvalidArgumentException; import javax.sip.RequestEvent; import javax.sip.SipException; import javax.sip.message.Response; import java.text.ParseException; /** * 语音喊话请求 */ @Slf4j @Component public class BroadcastNotifyMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler { private final static String cmdType = "Broadcast"; @Autowired private NotifyMessageHandler notifyMessageHandler; @Autowired private IGbChannelService channelService; @Autowired private ISIPCommanderForPlatform commanderForPlatform; @Autowired private IMediaServerService mediaServerService; @Autowired private IPlayService playService; @Autowired private IDeviceService deviceService; @Autowired private IDeviceChannelService deviceChannelService; @Autowired private IPlatformService platformService; @Autowired private AudioBroadcastManager audioBroadcastManager; @Autowired private ISendRtpServerService sendRtpServerService; @Override public void afterPropertiesSet() throws Exception { notifyMessageHandler.addHandler(cmdType, this); } @Override public void handForDevice(RequestEvent evt, Device device, Element element) { } @Override public void handForPlatform(RequestEvent evt, Platform platform, Element rootElement) { // 来自上级平台的语音喊话请求 SIPRequest request = (SIPRequest) evt.getRequest(); try { Element snElement = rootElement.element("SN"); if (snElement == null) { responseAck(request, Response.BAD_REQUEST, "sn must not null"); return; } String sn = snElement.getText(); Element targetIDElement = rootElement.element("TargetID"); if (targetIDElement == null) { responseAck(request, Response.BAD_REQUEST, "TargetID must not null"); return; } String targetId = targetIDElement.getText(); Element sourceIdElement = rootElement.element("SourceID"); String sourceId; if (sourceIdElement != null) { sourceId = sourceIdElement.getText(); }else { sourceId = targetId; } log.info("[国标级联 语音喊话] platform: {}, channel: {}", platform.getServerGBId(), targetId); CommonGBChannel channel = channelService.queryOneWithPlatform(platform.getId(), targetId); if (channel == null) { log.warn("[国标级联 语音喊话] 未找到通道 platform: {}, channel: {}", platform.getServerGBId(), targetId); responseAck(request, Response.NOT_FOUND, "TargetID not found"); return; } if (channel.getDataType() != ChannelDataType.GB28181) { // 只支持国标的语音喊话 log.warn("[INFO 消息] 只支持国标的语音喊话命令, 通道ID: {}", channel.getGbId()); try { responseAck(request, Response.FORBIDDEN, ""); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 错误信息: {}", e.getMessage()); } return; } // 向下级发送语音的喊话请求 Device device = deviceService.getDevice(channel.getDataDeviceId()); if (device == null) { responseAck(request, Response.NOT_FOUND, "device not found"); return; } DeviceChannel deviceChannel = deviceChannelService.getOneForSourceById(channel.getGbId()); if (deviceChannel == null) { responseAck(request, Response.NOT_FOUND, "channel not found"); return; } responseAck(request, Response.OK); // 查看语音通道是否已经建立并且已经在使用 if (playService.audioBroadcastInUse(device, deviceChannel)) { commanderForPlatform.broadcastResultCmd(platform, channel, sn, false,null, null); return; } MediaServer mediaServerForMinimumLoad = mediaServerService.getMediaServerForMinimumLoad(null); commanderForPlatform.broadcastResultCmd(platform, channel, sn, true, eventResult->{ log.info("[国标级联] 语音喊话 回复失败 platform: {}, 错误:{}/{}", platform.getServerGBId(), eventResult.statusCode, eventResult.msg); }, eventResult->{ // 消息发送成功, 向上级发送invite,获取推流 try { platformService.broadcastInvite(platform, channel, sourceId, mediaServerForMinimumLoad, (hookData)->{ // 上级平台推流成功 AudioBroadcastCatch broadcastCatch = audioBroadcastManager.get(channel.getGbId()); if (broadcastCatch != null ) { if (playService.audioBroadcastInUse(device, deviceChannel)) { log.info("[国标级联] 语音喊话 设备正在使用中 platform: {}, channel: {}", platform.getServerGBId(), channel.getGbDeviceId()); // 查看语音通道已经建立且已经占用 回复BYE platformService.stopBroadcast(platform, channel, hookData.getApp(), hookData.getStream(), true, hookData.getMediaServer()); }else { // 查看语音通道已经建立但是未占用 broadcastCatch.setApp(hookData.getApp()); broadcastCatch.setStream(hookData.getStream()); broadcastCatch.setMediaServerItem(hookData.getMediaServer()); audioBroadcastManager.update(broadcastCatch); // 推流到设备 SendRtpInfo sendRtpItem = sendRtpServerService.queryByStream(hookData.getStream(), targetId); if (sendRtpItem == null) { log.warn("[国标级联] 语音喊话 异常,未找到发流信息, channelId: {}, stream: {}", targetId, hookData.getStream()); log.info("[国标级联] 语音喊话 重新开始,channelId: {}, stream: {}", targetId, hookData.getStream()); try { playService.audioBroadcastCmd(device, deviceChannel, hookData.getMediaServer(), hookData.getApp(), hookData.getStream(), 60, true, msg -> { log.info("[语音喊话] 通道建立成功, device: {}, channel: {}", device.getDeviceId(), targetId); }); } catch (SipException | InvalidArgumentException | ParseException e) { log.info("[消息发送失败] 国标级联 语音喊话 platform: {}", platform.getServerGBId()); } }else { // 发流 try { mediaServerService.startSendRtp(hookData.getMediaServer(), sendRtpItem); }catch (ControllerException e) { log.info("[语音喊话] 推流失败, 结果: {}", e.getMessage()); return; } log.info("[语音喊话] 自动推流成功, device: {}, channel: {}", device.getDeviceId(), targetId); } } }else { try { playService.audioBroadcastCmd(device, deviceChannel, hookData.getMediaServer(), hookData.getApp(), hookData.getStream(), 60, true, msg -> { log.info("[语音喊话] 通道建立成功, device: {}, channel: {}", device.getDeviceId(), targetId); }); } catch (SipException | InvalidArgumentException | ParseException e) { log.info("[消息发送失败] 国标级联 语音喊话 platform: {}", platform.getServerGBId()); } } }, eventResultForBroadcastInvite -> { // 收到错误 log.info("[国标级联-语音喊话] 与下级通道建立失败 device: {}, channel: {}, 错误:{}/{}", device.getDeviceId(), targetId, eventResultForBroadcastInvite.statusCode, eventResultForBroadcastInvite.msg); }, (code, msg)->{ // 超时 log.info("[国标级联-语音喊话] 与下级通道建立超时 device: {}, channel: {}, 错误:{}/{}", device.getDeviceId(), targetId, code, msg); }); } catch (SipException | InvalidArgumentException | ParseException e) { log.info("[消息发送失败] 国标级联 语音喊话 invite消息 platform: {}", platform.getServerGBId()); } }); } catch (SipException | InvalidArgumentException | ParseException e) { log.info("[消息发送失败] 国标级联 语音喊话 platform: {}", platform.getServerGBId()); } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/KeepaliveNotifyMessageHandler.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.cmd; import com.genersoft.iot.vmp.common.RemoteAddressInfo; import com.genersoft.iot.vmp.conf.DynamicTask; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.Platform; import com.genersoft.iot.vmp.gb28181.bean.SipMsgInfo; import com.genersoft.iot.vmp.gb28181.service.IDeviceService; import com.genersoft.iot.vmp.gb28181.task.deviceStatus.DeviceStatusTaskRunner; 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.notify.NotifyMessageHandler; import com.genersoft.iot.vmp.gb28181.utils.SipUtils; import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.utils.IpPortUtil; import gov.nist.javax.sip.message.SIPRequest; import lombok.extern.slf4j.Slf4j; import org.dom4j.Element; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; 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.List; import java.util.concurrent.ConcurrentLinkedQueue; /** * 状态信息(心跳)报送 */ @Slf4j @Component public class KeepaliveNotifyMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler { private final static String cmdType = "Keepalive"; private final ConcurrentLinkedQueue taskQueue = new ConcurrentLinkedQueue<>(); @Autowired private NotifyMessageHandler notifyMessageHandler; @Autowired private IDeviceService deviceService; @Autowired private DeviceStatusTaskRunner statusTaskRunner; @Autowired private UserSetting userSetting; @Autowired private DynamicTask dynamicTask; @Override public void afterPropertiesSet() throws Exception { notifyMessageHandler.addHandler(cmdType, this); } @Override public void handForDevice(RequestEvent evt, Device device, Element rootElement) { if (taskQueue.size() >= userSetting.getMaxNotifyCountQueue()) { log.error("[心跳] 待处理消息队列已满 {},返回486 BUSY_HERE,消息不做处理", userSetting.getMaxNotifyCountQueue()); return; } taskQueue.offer(new SipMsgInfo(evt, device, rootElement)); } @Scheduled(fixedDelay = 100) public void executeTaskQueue() { if (taskQueue.isEmpty()) { return; } List handlerCatchDataList = new ArrayList<>(); int size = taskQueue.size(); for (int i = 0; i < size; i++) { SipMsgInfo poll = taskQueue.poll(); if (poll != null) { handlerCatchDataList.add(poll); } } if (handlerCatchDataList.isEmpty()) { return; } List deviceListForUpdate = new ArrayList<>(); for (SipMsgInfo sipMsgInfo : handlerCatchDataList) { if (sipMsgInfo == null) { continue; } RequestEvent evt = sipMsgInfo.getEvt(); // 回复200 OK try { responseAck((SIPRequest) evt.getRequest(), Response.OK); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 心跳回复: {}", e.getMessage()); } Device device = sipMsgInfo.getDevice(); SIPRequest request = (SIPRequest) evt.getRequest(); RemoteAddressInfo remoteAddressInfo = SipUtils.getRemoteAddressFromRequest(request, userSetting.getSipUseSourceIpAsRemoteAddress()); if (device.getIp() == null || !device.getIp().equalsIgnoreCase(remoteAddressInfo.getIp()) || device.getPort() != remoteAddressInfo.getPort()) { log.info("[收到心跳] 地址变化, {}({}), {}:{}->{}", device.getName(), device.getDeviceId(), remoteAddressInfo.getIp(), remoteAddressInfo.getPort(), request.getLocalAddress().getHostAddress()); device.setPort(remoteAddressInfo.getPort()); device.setHostAddress(IpPortUtil.concatenateIpAndPort(remoteAddressInfo.getIp(), String.valueOf(remoteAddressInfo.getPort()))); device.setIp(remoteAddressInfo.getIp()); device.setLocalIp(request.getLocalAddress().getHostAddress()); } device.setKeepaliveTime(DateUtil.getNow()); if (device.isOnLine()) { deviceListForUpdate.add(device); long expiresTime = Math.min(device.getExpires(), device.getHeartBeatInterval() * device.getHeartBeatCount()) * 1000L; if (statusTaskRunner.containsKey(device.getDeviceId())) { statusTaskRunner.updateDelay(device.getDeviceId(), expiresTime + System.currentTimeMillis()); } } else { if (userSetting.getGbDeviceOnline() == 1) { // 对于已经离线的设备判断他的注册是否已经过期 deviceService.online(device, null); } } } if (!deviceListForUpdate.isEmpty()) { deviceService.updateDeviceList(deviceListForUpdate); } } @Override public void handForPlatform(RequestEvent evt, Platform parentPlatform, Element element) { // 个别平台保活不回复200OK会判定离线 try { responseAck((SIPRequest) evt.getRequest(), Response.OK); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 心跳回复: {}", e.getMessage()); } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/MediaStatusNotifyMessageHandler.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.cmd; import com.genersoft.iot.vmp.common.InviteInfo; import com.genersoft.iot.vmp.common.InviteSessionType; import com.genersoft.iot.vmp.common.enums.MediaApp; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.service.*; import com.genersoft.iot.vmp.gb28181.session.SipInviteSessionManager; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderForPlatform; 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.notify.NotifyMessageHandler; import com.genersoft.iot.vmp.media.event.hook.Hook; import com.genersoft.iot.vmp.media.event.hook.HookSubscribe; import com.genersoft.iot.vmp.media.event.hook.HookType; import com.genersoft.iot.vmp.service.ISendRtpServerService; import gov.nist.javax.sip.message.SIPRequest; import lombok.extern.slf4j.Slf4j; import org.dom4j.Element; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.sip.InvalidArgumentException; import javax.sip.RequestEvent; import javax.sip.SipException; import javax.sip.header.CallIdHeader; import javax.sip.message.Response; import java.text.ParseException; import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText; /** * 媒体通知 */ @Slf4j @Component public class MediaStatusNotifyMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler { private final String cmdType = "MediaStatus"; @Autowired private NotifyMessageHandler notifyMessageHandler; @Autowired private SIPCommander cmder; @Autowired private SIPCommanderForPlatform sipCommanderFroPlatform; @Autowired private IPlatformChannelService platformChannelService; @Autowired private IPlatformService platformService; @Autowired private HookSubscribe subscribe; @Autowired private IInviteStreamService inviteStreamService; @Autowired private SipInviteSessionManager sessionManager; @Autowired private IDeviceChannelService deviceChannelService; @Autowired private IPlayService playService; @Autowired private ISendRtpServerService sendRtpServerService; @Override public void afterPropertiesSet() throws Exception { notifyMessageHandler.addHandler(cmdType, this); } @Override public void handForDevice(RequestEvent evt, Device device, Element rootElement) { // 回复200 OK try { responseAck((SIPRequest) evt.getRequest(), Response.OK); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 国标级联 录像流推送完毕,回复200OK: {}", e.getMessage()); } CallIdHeader callIdHeader = (CallIdHeader)evt.getRequest().getHeader(CallIdHeader.NAME); String NotifyType =getText(rootElement, "NotifyType"); if ("121".equals(NotifyType)){ log.info("[录像流]推送完毕,收到关流通知"); SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransactionByCallId(callIdHeader.getCallId()); if (ssrcTransaction != null) { log.info("[录像流]推送完毕,关流通知, device: {}, channelId: {}", ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId()); InviteInfo inviteInfo = inviteStreamService.getInviteInfo(InviteSessionType.DOWNLOAD, ssrcTransaction.getChannelId(), ssrcTransaction.getStream()); if (inviteInfo != null) { playService.stop(inviteInfo); } // 去除监听流注销自动停止下载的监听 Hook hook = Hook.getInstance(HookType.on_media_arrival, MediaApp.GB28181, ssrcTransaction.getStream(), ssrcTransaction.getMediaServerId()); subscribe.removeSubscribe(hook); if (ssrcTransaction.getPlatformId() != null) { // 如果级联播放,需要给上级发送此通知 TODO 多个上级同时观看一个下级 可能存在停错的问题,需要将点播CallId进行上下级绑定 SendRtpInfo sendRtpInfo = sendRtpServerService.queryByChannelId(ssrcTransaction.getChannelId(), ssrcTransaction.getPlatformId()); if (sendRtpInfo != null) { Platform parentPlatform = platformService.queryPlatformByServerGBId(sendRtpInfo.getTargetId()); if (parentPlatform == null) { log.warn("[级联消息发送]:发送MediaStatus发现上级平台{}不存在", sendRtpInfo.getTargetId()); return; } CommonGBChannel channel = platformChannelService.queryChannelByPlatformIdAndChannelId(parentPlatform.getId(), sendRtpInfo.getChannelId()); if (channel == null) { log.warn("[级联消息发送]:发送MediaStatus发现通道{}不存在", sendRtpInfo.getChannelId()); return; } try { sipCommanderFroPlatform.sendMediaStatusNotify(parentPlatform, sendRtpInfo, channel); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 国标级联 录像播放完毕: {}", e.getMessage()); } } } }else { log.info("[录像流]推送完毕,关流通知, 但是未找到对应的下载信息"); } } } @Override public void handForPlatform(RequestEvent evt, Platform parentPlatform, Element element) { } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/MobilePositionNotifyMessageHandler.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.cmd; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService; 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.notify.NotifyMessageHandler; import com.genersoft.iot.vmp.gb28181.utils.NumericUtil; import com.genersoft.iot.vmp.gb28181.utils.SipUtils; import com.genersoft.iot.vmp.utils.DateUtil; import gov.nist.javax.sip.message.SIPRequest; import lombok.extern.slf4j.Slf4j; import org.dom4j.DocumentException; import org.dom4j.Element; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; import javax.sip.InvalidArgumentException; import javax.sip.RequestEvent; import javax.sip.SipException; import javax.sip.message.Response; import java.text.ParseException; import java.util.concurrent.ConcurrentLinkedQueue; import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText; /** * 移动设备位置数据通知,设备主动发起,不需要上级订阅 */ @Slf4j @Component public class MobilePositionNotifyMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler { private final String cmdType = "MobilePosition"; @Autowired private NotifyMessageHandler notifyMessageHandler; @Autowired private IDeviceChannelService deviceChannelService; private ConcurrentLinkedQueue taskQueue = new ConcurrentLinkedQueue<>(); @Qualifier("taskExecutor") @Autowired private ThreadPoolTaskExecutor taskExecutor; @Override public void afterPropertiesSet() throws Exception { notifyMessageHandler.addHandler(cmdType, this); } @Override public void handForDevice(RequestEvent evt, Device device, Element rootElement) { boolean isEmpty = taskQueue.isEmpty(); taskQueue.offer(new SipMsgInfo(evt, device, rootElement)); // 回复200 OK try { responseAck((SIPRequest) evt.getRequest(), Response.OK); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 移动位置通知回复: {}", e.getMessage()); } if (isEmpty) { taskExecutor.execute(() -> { while (!taskQueue.isEmpty()) { SipMsgInfo sipMsgInfo = taskQueue.poll(); try { Element rootElementAfterCharset = getRootElement(sipMsgInfo.getEvt(), sipMsgInfo.getDevice().getCharset()); if (rootElementAfterCharset == null) { log.warn("[移动位置通知] {}处理失败,未识别到信息体", device.getDeviceId()); continue; } String channelId = getText(rootElementAfterCharset, "DeviceID"); DeviceChannel deviceChannel = deviceChannelService.getOne(device.getDeviceId(), channelId); if (deviceChannel == null) { log.warn("[解析移动位置通知] 未找到通道:{}/{}", device.getDeviceId(), channelId); continue; } MobilePosition mobilePosition = new MobilePosition(); mobilePosition.setCreateTime(DateUtil.getNow()); if (!ObjectUtils.isEmpty(sipMsgInfo.getDevice().getName())) { mobilePosition.setDeviceName(sipMsgInfo.getDevice().getName()); } mobilePosition.setDeviceId(sipMsgInfo.getDevice().getDeviceId()); mobilePosition.setChannelId(deviceChannel.getId()); mobilePosition.setChannelDeviceId(deviceChannel.getDeviceId()); String time = getText(rootElementAfterCharset, "Time"); if (ObjectUtils.isEmpty(time)){ mobilePosition.setTime(DateUtil.getNow()); }else { mobilePosition.setTime(SipUtils.parseTime(time)); } mobilePosition.setLongitude(Double.parseDouble(getText(rootElementAfterCharset, "Longitude"))); mobilePosition.setLatitude(Double.parseDouble(getText(rootElementAfterCharset, "Latitude"))); if (NumericUtil.isDouble(getText(rootElementAfterCharset, "Speed"))) { mobilePosition.setSpeed(Double.parseDouble(getText(rootElementAfterCharset, "Speed"))); } else { mobilePosition.setSpeed(0.0); } if (NumericUtil.isDouble(getText(rootElementAfterCharset, "Direction"))) { mobilePosition.setDirection(Double.parseDouble(getText(rootElementAfterCharset, "Direction"))); } else { mobilePosition.setDirection(0.0); } if (NumericUtil.isDouble(getText(rootElementAfterCharset, "Altitude"))) { mobilePosition.setAltitude(Double.parseDouble(getText(rootElementAfterCharset, "Altitude"))); } else { mobilePosition.setAltitude(0.0); } mobilePosition.setReportSource("Mobile Position"); // 更新device channel 的经纬度 deviceChannel.setLongitude(mobilePosition.getLongitude()); deviceChannel.setLatitude(mobilePosition.getLatitude()); deviceChannel.setGpsTime(mobilePosition.getTime()); deviceChannelService.updateChannelGPS(device, deviceChannel, mobilePosition); } catch (DocumentException e) { log.error("未处理的异常 ", e); } catch (Exception e) { log.warn("[移动位置通知] 发现未处理的异常, \r\n{}", evt.getRequest()); log.error("[移动位置通知] 异常内容: ", e); } } }); } } @Override public void handForPlatform(RequestEvent evt, Platform parentPlatform, Element element) { } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/QueryMessageHandler.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.query; import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.MessageHandlerAbstract; import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.MessageRequestProcessor; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** * 命令类型: 查询指令 * 命令类型: 设备状态, 设备目录信息, 设备信息, 文件目录检索(TODO), 报警(TODO), 设备配置(TODO), 设备预置位(TODO), 移动设备位置数据(TODO) */ @Component public class QueryMessageHandler extends MessageHandlerAbstract implements InitializingBean { private final String messageType = "Query"; @Autowired private MessageRequestProcessor messageRequestProcessor; @Override public void afterPropertiesSet() throws Exception { messageRequestProcessor.addHandler(messageType, this); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/AlarmQueryMessageHandler.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.query.cmd; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.Platform; 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.query.QueryMessageHandler; import gov.nist.javax.sip.message.SIPRequest; import lombok.extern.slf4j.Slf4j; import org.dom4j.Element; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.sip.InvalidArgumentException; import javax.sip.RequestEvent; import javax.sip.SipException; import javax.sip.message.Response; import java.text.ParseException; @Slf4j @Component public class AlarmQueryMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler { private final String cmdType = "Alarm"; @Autowired private QueryMessageHandler queryMessageHandler; @Override public void afterPropertiesSet() throws Exception { queryMessageHandler.addHandler(cmdType, this); } @Override public void handForDevice(RequestEvent evt, Device device, Element element) { } @Override public void handForPlatform(RequestEvent evt, Platform parentPlatform, Element rootElement) { log.info("不支持alarm查询"); try { responseAck((SIPRequest) evt.getRequest(), Response.NOT_FOUND, "not support alarm query"); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 国标级联 alarm查询回复200OK: {}", e.getMessage()); } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/CatalogQueryMessageHandler.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.query.cmd; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.Platform; import com.genersoft.iot.vmp.gb28181.service.IGbChannelService; import com.genersoft.iot.vmp.gb28181.service.IPlatformChannelService; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderForPlatform; 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.query.QueryMessageHandler; import gov.nist.javax.sip.message.SIPRequest; import lombok.extern.slf4j.Slf4j; import org.dom4j.Element; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.sip.InvalidArgumentException; import javax.sip.RequestEvent; import javax.sip.SipException; import javax.sip.header.FromHeader; import javax.sip.message.Response; import java.text.ParseException; import java.util.Collections; import java.util.List; @Slf4j @Component public class CatalogQueryMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler { private final String cmdType = "Catalog"; @Autowired private QueryMessageHandler queryMessageHandler; @Autowired private IGbChannelService channelService; @Autowired private IPlatformChannelService platformChannelService; @Autowired private SIPCommanderForPlatform cmderFroPlatform; @Override public void afterPropertiesSet() throws Exception { queryMessageHandler.addHandler(cmdType, this); } @Override public void handForDevice(RequestEvent evt, Device device, Element element) { try { // 回复200 OK responseAck((SIPRequest) evt.getRequest(), Response.FORBIDDEN); } catch (SipException | InvalidArgumentException | ParseException ignored) {} } @Override public void handForPlatform(RequestEvent evt, Platform platform, Element rootElement) { FromHeader fromHeader = (FromHeader) evt.getRequest().getHeader(FromHeader.NAME); try { // 回复200 OK responseAck((SIPRequest) evt.getRequest(), Response.OK); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 国标级联 目录查询回复200OK: {}", e.getMessage()); } Element snElement = rootElement.element("SN"); String sn = snElement.getText(); List channelList = platformChannelService.queryByPlatform(platform); try { if (!channelList.isEmpty()) { cmderFroPlatform.catalogQuery(channelList, platform, sn, fromHeader.getTag()); }else { // 回复无通道 cmderFroPlatform.catalogQuery(Collections.emptyList(), platform, sn, fromHeader.getTag()); } } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 国标级联 目录查询回复: {}", e.getMessage()); } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/DeviceInfoQueryMessageHandler.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.query.cmd; import com.genersoft.iot.vmp.common.enums.ChannelDataType; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.Platform; import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService; import com.genersoft.iot.vmp.gb28181.service.IDeviceService; import com.genersoft.iot.vmp.gb28181.service.IGbChannelService; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderForPlatform; 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.query.QueryMessageHandler; import gov.nist.javax.sip.message.SIPRequest; import lombok.extern.slf4j.Slf4j; import org.dom4j.Element; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.sip.InvalidArgumentException; import javax.sip.RequestEvent; import javax.sip.SipException; import javax.sip.header.FromHeader; import javax.sip.message.Response; import java.text.ParseException; import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText; @Slf4j @Component public class DeviceInfoQueryMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler { private final String cmdType = "DeviceInfo"; @Autowired private QueryMessageHandler queryMessageHandler; @Autowired private SIPCommanderForPlatform cmderFroPlatform; @Autowired private IDeviceService deviceService; @Autowired private IGbChannelService channelService; @Autowired private IDeviceChannelService deviceChannelService; @Override public void afterPropertiesSet() throws Exception { queryMessageHandler.addHandler(cmdType, this); } @Override public void handForDevice(RequestEvent evt, Device device, Element rootElement) { } @Override public void handForPlatform(RequestEvent evt, Platform platform, Element rootElement) { log.info("[DeviceInfo查询]消息"); SIPRequest request = (SIPRequest) evt.getRequest(); FromHeader fromHeader = (FromHeader) evt.getRequest().getHeader(FromHeader.NAME); String sn = rootElement.element("SN").getText(); /*根据WVP原有的数据结构,设备和通道是分开放置,设备信息都是存放在设备表里,通道表里的设备信息不可作为真实信息处理 大部分NVR/IPC设备对他的通道信息实现都是返回默认的值没有什么参考价值。NVR/IPC通道我们统一使用设备表的设备信息来作为返回。 我们这里使用查询数据库的方式来实现这个设备信息查询的功能,在其他地方对设备信息更新达到正确的目的。*/ String channelId = getText(rootElement, "DeviceID"); // 查询这是通道id还是设备id if (platform.getDeviceGBId().equals(channelId)) { // id指向平台的国标编号,那么就是查询平台的信息 try { cmderFroPlatform.deviceInfoResponse(platform, null, sn, fromHeader.getTag()); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 国标级联 DeviceInfo查询回复: {}", e.getMessage()); } return; } CommonGBChannel channel = channelService.queryOneWithPlatform(platform.getId(), channelId); if (channel == null) { // 不存在则回复404 log.warn("[DeviceInfo] 通道不存在: 通道编号: {}", channelId); try { responseAck(request, Response.NOT_FOUND, "channel not found or offline"); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] DeviceInfo查询回复: {}", e.getMessage()); return; } return; } // 判断通道类型 if (channel.getDataType() != ChannelDataType.GB28181) { // 非国标通道不支持录像回放控制 log.warn("[DeviceInfo] 非国标通道不支持录像回放控制: 通道ID: {}", channel.getGbId()); try { responseAck(request, Response.FORBIDDEN, ""); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] DeviceInfo查询回复: {}", e.getMessage()); return; } return; } // 根据通道ID,获取所属设备 Device device = deviceService.getDevice(channel.getDataDeviceId()); if (device == null) { // 不存在则回复404 log.warn("[DeviceInfo] 通道所属设备不存在, 通道ID: {}", channel.getDataDeviceId()); try { responseAck(request, Response.NOT_FOUND, "device not found "); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] DeviceInfo查询回复: {}", e.getMessage()); return; } return; } try { // 回复200 OK responseAck((SIPRequest) evt.getRequest(), Response.OK); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] DeviceInfo查询回复: {}", e.getMessage()); return; } try { cmderFroPlatform.deviceInfoResponse(platform, device, sn, fromHeader.getTag()); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 国标级联 DeviceInfo查询回复: {}", e.getMessage()); } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/DeviceStatusQueryMessageHandler.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.query.cmd; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.Platform; import com.genersoft.iot.vmp.gb28181.service.IGbChannelService; import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; 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.query.QueryMessageHandler; import gov.nist.javax.sip.message.SIPRequest; import lombok.extern.slf4j.Slf4j; import org.dom4j.Element; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.sip.InvalidArgumentException; import javax.sip.RequestEvent; import javax.sip.SipException; import javax.sip.header.FromHeader; import javax.sip.message.Response; import java.text.ParseException; import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText; @Slf4j @Component public class DeviceStatusQueryMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler { private final String cmdType = "DeviceStatus"; @Autowired private QueryMessageHandler queryMessageHandler; @Autowired private IGbChannelService channelService; @Autowired private ISIPCommanderForPlatform cmderFroPlatform; @Override public void afterPropertiesSet() throws Exception { queryMessageHandler.addHandler(cmdType, this); } @Override public void handForDevice(RequestEvent evt, Device device, Element element) { } @Override public void handForPlatform(RequestEvent evt, Platform platform, Element rootElement) { log.info("[DeviceStatus Query] \n {}", rootElement.asXML()); FromHeader fromHeader = (FromHeader) evt.getRequest().getHeader(FromHeader.NAME); // 回复200 OK try { responseAck((SIPRequest) evt.getRequest(), Response.OK); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 国标级联 DeviceStatus查询回复200OK: {}", e.getMessage()); } String sn = rootElement.element("SN").getText(); String channelId = getText(rootElement, "DeviceID"); if (platform.getDeviceGBId().equals(channelId)) { // 上级平台查询本平台状态 try { cmderFroPlatform.deviceStatusResponse(platform, channelId, sn, fromHeader.getTag(), true); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 国标级联 DeviceStatus查询回复: {}", e.getMessage()); } return; } CommonGBChannel channel= channelService.queryOneWithPlatform(platform.getId(), channelId); if (channel ==null){ log.error("[平台没有该通道的使用权限]:platformId: {} deviceID:{}", platform.getServerGBId(), channelId); // 上级平台查询本平台状态 try { cmderFroPlatform.deviceStatusResponse(platform, channelId, sn, fromHeader.getTag(), null); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 国标级联 DeviceStatus查询回复: {}", e.getMessage()); } return; } try { cmderFroPlatform.deviceStatusResponse(platform, channelId, sn, fromHeader.getTag(), "ON".equalsIgnoreCase(channel.getGbStatus())); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 国标级联 DeviceStatus查询回复: {}", e.getMessage()); } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/RecordInfoQueryMessageHandler.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.query.cmd; import com.genersoft.iot.vmp.common.enums.ChannelDataType; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.event.record.RecordInfoEventListener; import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService; import com.genersoft.iot.vmp.gb28181.service.IDeviceService; import com.genersoft.iot.vmp.gb28181.service.IGbChannelPlayService; import com.genersoft.iot.vmp.gb28181.service.IGbChannelService; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderForPlatform; 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.query.QueryMessageHandler; import com.genersoft.iot.vmp.utils.DateUtil; import gov.nist.javax.sip.message.SIPRequest; import lombok.extern.slf4j.Slf4j; import org.dom4j.Element; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; 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.List; @Slf4j @Component public class RecordInfoQueryMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler { private final String cmdType = "RecordInfo"; @Autowired private QueryMessageHandler queryMessageHandler; @Autowired private IGbChannelService channelService; @Autowired private IGbChannelPlayService playService; @Autowired private IDeviceService deviceService; @Autowired private IDeviceChannelService deviceChannelService; @Autowired private SIPCommanderForPlatform cmderFroPlatform; @Autowired private SIPCommander commander; @Autowired private RecordInfoEventListener recordInfoEventListener; @Override public void afterPropertiesSet() throws Exception { queryMessageHandler.addHandler(cmdType, this); } @Override public void handForDevice(RequestEvent evt, Device device, Element element) { } @Override public void handForPlatform(RequestEvent evt, Platform platform, Element rootElement) { SIPRequest request = (SIPRequest) evt.getRequest(); Element snElement = rootElement.element("SN"); int sn = Integer.parseInt(snElement.getText()); Element deviceIDElement = rootElement.element("DeviceID"); String channelId = deviceIDElement.getText(); Element startTimeElement = rootElement.element("StartTime"); String startTime = null; if (startTimeElement != null) { startTime = startTimeElement.getText(); } Element endTimeElement = rootElement.element("EndTime"); String endTime = null; if (endTimeElement != null) { endTime = endTimeElement.getText(); } Element secrecyElement = rootElement.element("Secrecy"); int secrecy = 0; if (secrecyElement != null) { secrecy = Integer.parseInt(secrecyElement.getText().trim()); } String type = "all"; Element typeElement = rootElement.element("Type"); if (typeElement != null) { type = typeElement.getText(); } // 向国标设备请求录像数据 CommonGBChannel channel = channelService.queryOneWithPlatform(platform.getId(), channelId); if (channel == null) { log.info("[平台查询录像记录] 未找到通道 {}/{}", platform.getName(), channelId ); try { responseAck(request, Response.BAD_REQUEST); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] [平台查询录像记录] 未找到通道: {}", e.getMessage()); } return; } if (channel.getDataType() == ChannelDataType.GB28181) { Device device = deviceService.getDevice(channel.getDataDeviceId()); if (device == null) { log.warn("[平台查询录像记录] 未找到通道对应的设备 {}/{}", platform.getName(), channelId ); try { responseAck(request, Response.BAD_REQUEST); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] [平台查询录像记录] 未找到通道对应的设备: {}", e.getMessage()); } return; } // 获取通道的原始信息 DeviceChannel deviceChannel = deviceChannelService.getOneForSourceById(channel.getGbId()); // 接收录像数据 recordInfoEventListener.addEndEventHandler(device.getDeviceId(), deviceChannel.getDeviceId(), (recordInfo)->{ try { log.info("[国标级联] 录像查询收到数据, 通道: {},准备转发===", channelId); cmderFroPlatform.recordInfo(channel, platform, request.getFromTag(), recordInfo); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 国标级联 回复录像数据: {}", e.getMessage()); } }); try { commander.recordInfoQuery(device, deviceChannel.getDeviceId(), DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(startTime), DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(endTime), sn, secrecy, type, (eventResult -> { // 回复200 OK try { responseAck(request, Response.OK); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 录像查询回复: {}", e.getMessage()); } }),(eventResult -> { // 查询失败 try { responseAck(request, eventResult.statusCode, eventResult.msg); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 录像查询回复: {}", e.getMessage()); } })); } catch (InvalidArgumentException | ParseException | SipException e) { log.error("[命令发送失败] 录像查询: {}", e.getMessage()); } }else { // 回复200 OK try { responseAck(request, Response.OK); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 录像查询回复: {}", e.getMessage()); } playService.queryRecord(channel, DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(startTime), DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(endTime), (code, msg, commonRecordInfoList) -> { RecordInfo recordInfo = new RecordInfo(); recordInfo.setSumNum(commonRecordInfoList.size()); recordInfo.setChannelId(channelId); recordInfo.setSn(sn + ""); List recordList = new ArrayList<>(commonRecordInfoList.size()); for (int i = 0; i < commonRecordInfoList.size(); i++) { CommonRecordInfo commonRecordInfo = commonRecordInfoList.get(i); RecordItem recordItem = new RecordItem(); recordItem.setDeviceId(channelId); recordItem.setName(commonRecordInfo.getStartTime()); recordItem.setFilePath("/" + commonRecordInfo.getStartTime()); recordItem.setAddress("/" + commonRecordInfo.getStartTime()); recordItem.setStartTime(commonRecordInfo.getStartTime()); recordItem.setEndTime(commonRecordInfo.getEndTime()); recordItem.setSecrecy(0); recordItem.setRecorderId(""); recordItem.setType(""); recordItem.setFileSize(commonRecordInfo.getFileSize()); recordList.add(recordItem); } recordInfo.setRecordList(recordList); try { log.info("[国标级联] 录像查询收到数据, 通道: {},准备转发===", channelId); cmderFroPlatform.recordInfo(channel, platform, request.getFromTag(), recordInfo); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 国标级联 回复录像数据: {}", e.getMessage()); } }); } // // // // // // // // if (channel.getDataType() != ChannelDataType.GB28181) { // log.info("[平台查询录像记录] 只支持查询国标28181的录像数据 {}/{}", platform.getName(), channelId ); // try { // responseAck(request, Response.NOT_IMPLEMENTED); // 回复未实现 // } catch (SipException | InvalidArgumentException | ParseException e) { // log.error("[命令发送失败] 平台查询录像记录: {}", e.getMessage()); // } // return; // } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/ResponseMessageHandler.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.MessageHandlerAbstract; import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.MessageRequestProcessor; import org.dom4j.Element; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.sip.RequestEvent; /** * 命令类型: 请求动作的应答 * 命令类型: 设备控制, 报警通知, 设备目录信息查询, 目录信息查询, 目录收到, 设备信息查询, 设备状态信息查询 ...... */ @Component public class ResponseMessageHandler extends MessageHandlerAbstract implements InitializingBean { private final String messageType = "Response"; @Autowired private MessageRequestProcessor messageRequestProcessor; @Override public void afterPropertiesSet() throws Exception { messageRequestProcessor.addHandler(messageType, this); } @Override public void handForDevice(RequestEvent evt, Device device, Element element) { super.handForDevice(evt, device, element); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/AlarmResponseMessageHandler.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.Platform; import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; 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.gb28181.utils.XmlUtil; import gov.nist.javax.sip.message.SIPRequest; import lombok.extern.slf4j.Slf4j; import org.dom4j.Element; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.sip.InvalidArgumentException; import javax.sip.RequestEvent; import javax.sip.SipException; import javax.sip.message.Response; import java.text.ParseException; @Slf4j @Component public class AlarmResponseMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler { private final String cmdType = "Alarm"; @Autowired private ResponseMessageHandler responseMessageHandler; @Autowired private DeferredResultHolder deferredResultHolder; @Override public void afterPropertiesSet() throws Exception { responseMessageHandler.addHandler(cmdType, this); } @Override public void handForDevice(RequestEvent evt, Device device, Element rootElement) { // 回复200 OK try { responseAck((SIPRequest) evt.getRequest(), Response.OK); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 目录查询回复: {}", e.getMessage()); } JSONObject json = new JSONObject(); XmlUtil.node2Json(rootElement, json); if (log.isDebugEnabled()) { log.debug(json.toJSONString()); } responseMessageHandler.handMessageEvent(rootElement, null); } @Override public void handForPlatform(RequestEvent evt, Platform parentPlatform, Element element) { } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/BroadcastResponseMessageHandler.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService; import com.genersoft.iot.vmp.gb28181.service.IPlayService; import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager; 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 gov.nist.javax.sip.message.SIPRequest; import lombok.extern.slf4j.Slf4j; import org.dom4j.Element; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.sip.InvalidArgumentException; import javax.sip.RequestEvent; import javax.sip.SipException; import javax.sip.message.Response; import java.text.ParseException; import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText; @Slf4j @Component public class BroadcastResponseMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler { private final String cmdType = "Broadcast"; @Autowired private ResponseMessageHandler responseMessageHandler; @Autowired private IDeviceChannelService deviceChannelService; @Autowired private AudioBroadcastManager audioBroadcastManager; @Autowired private IPlayService playService; @Override public void afterPropertiesSet() throws Exception { responseMessageHandler.addHandler(cmdType, this); } @Override public void handForDevice(RequestEvent evt, Device device, Element rootElement) { SIPRequest request = (SIPRequest) evt.getRequest(); try { String channelId = getText(rootElement, "DeviceID"); DeviceChannel channel = null; if (!channelId.equals(device.getDeviceId())) { channel = deviceChannelService.getOneBySourceId(device.getId(), channelId); }else { channel = deviceChannelService.getBroadcastChannel(device.getId()); } if (channel == null) { log.info("[语音广播]回复: 未找到通道{}/{}", device.getDeviceId(), channelId ); // 回复410 responseAck((SIPRequest) evt.getRequest(), Response.NOT_FOUND); return; } if (!audioBroadcastManager.exit(channel.getId())) { // 回复410 responseAck((SIPRequest) evt.getRequest(), Response.BUSY_HERE); return; } String result = getText(rootElement, "Result"); Element infoElement = rootElement.element("Info"); String reason = null; if (infoElement != null) { reason = getText(infoElement, "Reason"); } log.info("[语音广播]回复:{}, {}/{}", reason == null? result : result + ": " + reason, device.getDeviceId(), channelId ); // 回复200 OK responseAck(request, Response.OK); if (result.equalsIgnoreCase("OK")) { AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(channel.getId()); audioBroadcastCatch.setStatus(AudioBroadcastCatchStatus.WaiteInvite); audioBroadcastManager.update(audioBroadcastCatch); }else { playService.stopAudioBroadcast(device, channel); } } catch (ParseException | SipException | InvalidArgumentException e) { log.error("[命令发送失败] 国标级联 语音喊话: {}", e.getMessage()); } } @Override public void handForPlatform(RequestEvent evt, Platform parentPlatform, Element element) { } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/CatalogResponseMessageHandler.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd; import com.genersoft.iot.vmp.conf.SipConfig; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService; import com.genersoft.iot.vmp.gb28181.service.IGroupService; import com.genersoft.iot.vmp.gb28181.service.IRegionService; import com.genersoft.iot.vmp.gb28181.session.CatalogDataManager; 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.Coordtransform; import gov.nist.javax.sip.message.SIPRequest; import lombok.extern.slf4j.Slf4j; import org.dom4j.DocumentException; import org.dom4j.Element; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; 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.Collections; import java.util.Iterator; import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; /** * 目录查询的回复 */ @Slf4j @Component public class CatalogResponseMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler { private final String cmdType = "Catalog"; @Autowired private ResponseMessageHandler responseMessageHandler; private final ConcurrentLinkedQueue taskQueue = new ConcurrentLinkedQueue<>(); @Autowired private IDeviceChannelService deviceChannelService; @Autowired private IRegionService regionService; @Autowired private IGroupService groupService; @Autowired private CatalogDataManager catalogDataCatch; @Autowired private SipConfig sipConfig; @Override public void afterPropertiesSet() throws Exception { responseMessageHandler.addHandler(cmdType, this); } @Override public void handForDevice(RequestEvent evt, Device device, Element element) { taskQueue.offer(new HandlerCatchData(evt, device, element)); // 回复200 OK try { responseAck((SIPRequest) evt.getRequest(), Response.OK); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 目录查询回复: {}", e.getMessage()); } } @Scheduled(fixedDelay = 50) @Transactional public void executeTaskQueue(){ if (taskQueue.isEmpty()) { return; } List handlerCatchDataList = new ArrayList<>(); int size = taskQueue.size(); for (int i = 0; i < size; i++) { HandlerCatchData poll = taskQueue.poll(); if (poll != null) { handlerCatchDataList.add(poll); } } if (handlerCatchDataList.isEmpty()) { return; } for (HandlerCatchData take : handlerCatchDataList) { if (take == null) { continue; } RequestEvent evt = take.getEvt(); int sn = 0; // 全局异常捕获,保证下一条可以得到处理 try { Element rootElement = null; try { rootElement = getRootElement(take.getEvt(), take.getDevice().getCharset()); } catch (DocumentException e) { log.error("[xml解析] 失败: ", e); continue; } if (rootElement == null) { log.warn("[ 收到通道 ] content cannot be null, {}", evt.getRequest()); continue; } Element deviceListElement = rootElement.element("DeviceList"); Element sumNumElement = rootElement.element("SumNum"); Element snElement = rootElement.element("SN"); sn = Integer.parseInt(snElement.getText()); int sumNum = Integer.parseInt(sumNumElement.getText()); if (sumNum == 0) { log.info("[收到通道]设备:{}的: 0个", take.getDevice().getDeviceId()); // 数据已经完整接收 deviceChannelService.cleanChannelsForDevice(take.getDevice().getId()); // 推送空数据,不然无法及时结束 catalogDataCatch.put(take.getDevice().getDeviceId(), sn, 0, take.getDevice(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); catalogDataCatch.setChannelSyncEnd(take.getDevice().getDeviceId(), sn, null); return; } else { Iterator deviceListIterator = deviceListElement.elementIterator(); if (deviceListIterator != null) { List channelList = new ArrayList<>(); List regionList = new ArrayList<>(); List groupList = new ArrayList<>(); // 遍历DeviceList while (deviceListIterator.hasNext()) { Element itemDevice = deviceListIterator.next(); Element channelDeviceElement = itemDevice.element("DeviceID"); if (channelDeviceElement == null) { // 总数减一, 避免最后总数不对 无法确定问题 continue; } // 从xml解析内容到 DeviceChannel 对象 DeviceChannel channel = DeviceChannel.decode(itemDevice); if (channel.getDeviceId() == null) { log.info("[收到目录订阅]:但是解析失败 {}", new String(evt.getRequest().getRawContent())); continue; } channel.setDataDeviceId(take.getDevice().getId()); if (channel.getParentId() != null && channel.getParentId().equals(sipConfig.getId())) { channel.setParentId(null); } // 解析通道类型 if (channel.getDeviceId().length() <= 8) { // 行政区划 Region region = Region.getInstance(channel); regionList.add(region); channel.setChannelType(1); }else if (channel.getDeviceId().length() == 20){ // 业务分组/虚拟组织 Group group = Group.getInstance(channel); if (group != null) { channel.setParental(1); channel.setChannelType(2); groupList.add(group); } if (channel.getLongitude() != null && channel.getLatitude() != null && channel.getLongitude() > 0 && channel.getLatitude() > 0) { Double[] wgs84Position = Coordtransform.GCJ02ToWGS84(channel.getLongitude(), channel.getLatitude()); channel.setGbLongitude(wgs84Position[0]); channel.setGbLatitude(wgs84Position[1]); } } channelList.add(channel); } catalogDataCatch.put(take.getDevice().getDeviceId(), sn, sumNum, take.getDevice(), channelList, regionList, groupList); log.info("[收到通道]设备: {} -> {}个,{}/{}", take.getDevice().getDeviceId(), channelList.size(), catalogDataCatch.size(take.getDevice().getDeviceId(), sn), sumNum); } } } catch (Exception e) { log.warn("[收到通道] 发现未处理的异常, \r\n{}", evt.getRequest()); log.error("[收到通道] 异常内容: ", e); } finally { String deviceId = take.getDevice().getDeviceId(); if (catalogDataCatch.size(deviceId, sn) > 0 && catalogDataCatch.size(deviceId, sn) == catalogDataCatch.sumNum(deviceId, sn)) { // 数据已经完整接收, 此时可能存在某个设备离线变上线的情况,但是考虑到性能,此处不做处理, // 目前支持设备通道上线通知时和设备上线时向上级通知 boolean resetChannelsResult = saveData(take.getDevice(), sn); if (!resetChannelsResult) { String errorMsg = "接收成功,写入失败,共" + catalogDataCatch.sumNum(deviceId, sn) + "条,已接收" + catalogDataCatch.getDeviceChannelList(take.getDevice().getDeviceId(), sn).size() + "条"; catalogDataCatch.setChannelSyncEnd(deviceId, sn, errorMsg); } else { catalogDataCatch.setChannelSyncEnd(deviceId, sn, null); } } } } } @Transactional public boolean saveData(Device device, int sn) { boolean result = true; List deviceChannelList = catalogDataCatch.getDeviceChannelList(device.getDeviceId(), sn); if (deviceChannelList != null && !deviceChannelList.isEmpty()) { result &= deviceChannelService.resetChannels(device.getId(), deviceChannelList); } List regionList = catalogDataCatch.getRegionList(device.getDeviceId(), sn); if ( regionList!= null && !regionList.isEmpty()) { result &= regionService.batchAdd(regionList); } List groupList = catalogDataCatch.getGroupList(device.getDeviceId(), sn); if (groupList != null && !groupList.isEmpty()) { result &= groupService.batchAdd(groupList); } return result; } @Override public void handForPlatform(RequestEvent evt, Platform parentPlatform, Element rootElement) { } public SyncStatus getChannelSyncProgress(String deviceId) { return catalogDataCatch.getSyncStatus(deviceId); } public boolean isSyncRunning(String deviceId) { return catalogDataCatch.isSyncRunning(deviceId); } public void setChannelSyncReady(Device device, int sn) { catalogDataCatch.addReady(device, sn); } public void setChannelSyncEnd(String deviceId, int sn, String errorMsg) { catalogDataCatch.setChannelSyncEnd(deviceId, sn, errorMsg); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/ConfigDownloadResponseMessageHandler.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.Platform; import com.genersoft.iot.vmp.gb28181.service.IDeviceService; import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; 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.gb28181.utils.XmlUtil; import gov.nist.javax.sip.message.SIPRequest; import lombok.extern.slf4j.Slf4j; import org.dom4j.Element; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.sip.InvalidArgumentException; import javax.sip.RequestEvent; import javax.sip.SipException; import javax.sip.message.Response; import java.text.ParseException; @Slf4j @Component public class ConfigDownloadResponseMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler { private final String cmdType = "ConfigDownload"; @Autowired private ResponseMessageHandler responseMessageHandler; @Autowired private DeferredResultHolder deferredResultHolder; @Autowired private IDeviceService deviceService; @Override public void afterPropertiesSet() throws Exception { responseMessageHandler.addHandler(cmdType, this); } @Override public void handForDevice(RequestEvent evt, Device device, Element element) { try { // 回复200 OK responseAck((SIPRequest) evt.getRequest(), Response.OK); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 设备配置查询: {}", e.getMessage()); } // 此处是对本平台发出DeviceControl指令的应答 JSONObject json = new JSONObject(); XmlUtil.node2Json(element, json); if (log.isDebugEnabled()) { log.debug(json.toJSONString()); } JSONObject jsonObject = new JSONObject(); if (json.get("BasicParam") != null) { jsonObject.put("BasicParam", json.getJSONObject("BasicParam")); } if (json.get("VideoParamOpt") != null) { jsonObject.put("VideoParamOpt", json.getJSONObject("VideoParamOpt")); } if (json.get("SVACEncodeConfig") != null) { jsonObject.put("SVACEncodeConfig", json.getJSONObject("SVACEncodeConfig")); } if (json.get("SVACDecodeConfig") != null) { jsonObject.put("SVACDecodeConfig", json.getJSONObject("SVACDecodeConfig")); } responseMessageHandler.handMessageEvent(element, jsonObject); JSONObject basicParam = json.getJSONObject("BasicParam"); if (basicParam != null) { Integer heartBeatInterval = basicParam.getInteger("HeartBeatInterval"); Integer heartBeatCount = basicParam.getInteger("HeartBeatCount"); Integer positionCapability = basicParam.getInteger("PositionCapability"); device.setHeartBeatInterval(heartBeatInterval); device.setHeartBeatCount(heartBeatCount); device.setPositionCapability(positionCapability); deviceService.updateDeviceHeartInfo(device); } } @Override public void handForPlatform(RequestEvent evt, Platform parentPlatform, Element element) { // 不会收到上级平台的心跳信息 } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/DeviceInfoResponseMessageHandler.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.Platform; import com.genersoft.iot.vmp.gb28181.service.IDeviceService; 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 gov.nist.javax.sip.message.SIPRequest; import lombok.extern.slf4j.Slf4j; import org.dom4j.DocumentException; import org.dom4j.Element; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; import javax.sip.InvalidArgumentException; import javax.sip.RequestEvent; import javax.sip.SipException; import javax.sip.message.Response; import java.text.ParseException; import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText; /** * @author lin */ @Slf4j @Component public class DeviceInfoResponseMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler { private final String cmdType = "DeviceInfo"; @Autowired private ResponseMessageHandler responseMessageHandler; @Autowired private IDeviceService deviceService; @Override public void afterPropertiesSet() throws Exception { responseMessageHandler.addHandler(cmdType, this); } @Override public void handForDevice(RequestEvent evt, Device device, Element rootElement) { log.debug("接收到DeviceInfo应答消息"); // 检查设备是否存在, 不存在则不回复 if (device == null || !device.isOnLine()) { log.warn("[接收到DeviceInfo应答消息,但是设备已经离线]:" + (device != null ? device.getDeviceId():"" )); return; } SIPRequest request = (SIPRequest) evt.getRequest(); try { rootElement = getRootElement(evt, device.getCharset()); if (rootElement == null) { log.warn("[ 接收到DeviceInfo应答消息 ] content cannot be null, {}", evt.getRequest()); try { responseAck((SIPRequest) evt.getRequest(), Response.BAD_REQUEST); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] DeviceInfo应答消息 BAD_REQUEST: {}", e.getMessage()); } return; } device.setName(getText(rootElement, "DeviceName")); device.setManufacturer(getText(rootElement, "Manufacturer")); device.setModel(getText(rootElement, "Model")); device.setFirmware(getText(rootElement, "Firmware")); if (ObjectUtils.isEmpty(device.getStreamMode())) { device.setStreamMode("TCP-PASSIVE"); } deviceService.updateDevice(device); responseMessageHandler.handMessageEvent(rootElement, device); } catch (DocumentException e) { throw new RuntimeException(e); } try { // 回复200 OK responseAck(request, Response.OK); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] DeviceInfo应答消息 200: {}", e.getMessage()); } } @Override public void handForPlatform(RequestEvent evt, Platform parentPlatform, Element rootElement) { } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/DeviceStatusResponseMessageHandler.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.Platform; import com.genersoft.iot.vmp.gb28181.service.IDeviceService; 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.gb28181.utils.XmlUtil; import gov.nist.javax.sip.message.SIPRequest; import lombok.extern.slf4j.Slf4j; import org.dom4j.Element; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.sip.InvalidArgumentException; import javax.sip.RequestEvent; import javax.sip.SipException; import javax.sip.message.Response; import java.text.ParseException; @Slf4j @Component public class DeviceStatusResponseMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler { private final String cmdType = "DeviceStatus"; @Autowired private ResponseMessageHandler responseMessageHandler; @Autowired private IDeviceService deviceService; @Override public void afterPropertiesSet() throws Exception { responseMessageHandler.addHandler(cmdType, this); } @Override public void handForDevice(RequestEvent evt, Device device, Element element) { log.info("[DeviceStatus Response] \n {}", element.asXML()); // 检查设备是否存在, 不存在则不回复 if (device == null) { return; } // 回复200 OK try { responseAck((SIPRequest) evt.getRequest(), Response.OK); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 国标级联 设备状态应答回复200OK: {}", e.getMessage()); } Element onlineElement = element.element("Online"); JSONObject json = new JSONObject(); XmlUtil.node2Json(element, json); if (log.isDebugEnabled()) { log.debug(json.toJSONString()); } String text = onlineElement.getText(); responseMessageHandler.handMessageEvent(element, text); } @Override public void handForPlatform(RequestEvent evt, Platform parentPlatform, Element rootElement) { } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/MobilePositionResponseMessageHandler.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; import com.genersoft.iot.vmp.gb28181.bean.MobilePosition; import com.genersoft.iot.vmp.gb28181.bean.Platform; 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.gb28181.utils.NumericUtil; import com.genersoft.iot.vmp.gb28181.utils.SipUtils; import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService; import com.genersoft.iot.vmp.utils.DateUtil; import gov.nist.javax.sip.message.SIPRequest; import lombok.extern.slf4j.Slf4j; import org.dom4j.DocumentException; import org.dom4j.Element; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; import javax.sip.InvalidArgumentException; import javax.sip.RequestEvent; import javax.sip.SipException; import javax.sip.message.Response; import java.text.ParseException; import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText; /** * 移动设备位置数据查询回复 * @author lin */ @Slf4j @Component public class MobilePositionResponseMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler { private final String cmdType = "MobilePosition"; @Autowired private ResponseMessageHandler responseMessageHandler; @Autowired private IDeviceChannelService deviceChannelService; @Autowired private DeferredResultHolder resultHolder; @Override public void afterPropertiesSet() throws Exception { responseMessageHandler.addHandler(cmdType, this); } @Override public void handForDevice(RequestEvent evt, Device device, Element rootElement) { SIPRequest request = (SIPRequest) evt.getRequest(); try { rootElement = getRootElement(evt, device.getCharset()); if (rootElement == null) { log.warn("[ 移动设备位置数据查询回复 ] content cannot be null, {}", evt.getRequest()); try { responseAck(request, Response.BAD_REQUEST); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 移动设备位置数据查询 BAD_REQUEST: {}", e.getMessage()); } return; } String channelId = getText(rootElement, "DeviceID"); DeviceChannel deviceChannel = deviceChannelService.getOne(device.getDeviceId(), channelId); if (deviceChannel == null) { log.warn("[解析报警消息] 未找到通道:{}/{}", device.getDeviceId(), channelId); }else { MobilePosition mobilePosition = new MobilePosition(); mobilePosition.setCreateTime(DateUtil.getNow()); if (!ObjectUtils.isEmpty(device.getName())) { mobilePosition.setDeviceName(device.getName()); } mobilePosition.setDeviceId(device.getDeviceId()); mobilePosition.setChannelId(deviceChannel.getId()); mobilePosition.setChannelDeviceId(deviceChannel.getDeviceId()); //兼容ISO 8601格式时间 String time = getText(rootElement, "Time"); if (ObjectUtils.isEmpty(time)){ mobilePosition.setTime(DateUtil.getNow()); }else { mobilePosition.setTime(SipUtils.parseTime(time)); } mobilePosition.setLongitude(Double.parseDouble(getText(rootElement, "Longitude"))); mobilePosition.setLatitude(Double.parseDouble(getText(rootElement, "Latitude"))); if (NumericUtil.isDouble(getText(rootElement, "Speed"))) { mobilePosition.setSpeed(Double.parseDouble(getText(rootElement, "Speed"))); } else { mobilePosition.setSpeed(0.0); } if (NumericUtil.isDouble(getText(rootElement, "Direction"))) { mobilePosition.setDirection(Double.parseDouble(getText(rootElement, "Direction"))); } else { mobilePosition.setDirection(0.0); } if (NumericUtil.isDouble(getText(rootElement, "Altitude"))) { mobilePosition.setAltitude(Double.parseDouble(getText(rootElement, "Altitude"))); } else { mobilePosition.setAltitude(0.0); } mobilePosition.setReportSource("Mobile Position"); // 更新device channel 的经纬度 deviceChannel.setLongitude(mobilePosition.getLongitude()); deviceChannel.setLatitude(mobilePosition.getLatitude()); deviceChannel.setGpsTime(mobilePosition.getTime()); deviceChannelService.updateChannelGPS(device, deviceChannel, mobilePosition); String key = DeferredResultHolder.CALLBACK_CMD_MOBILE_POSITION + device.getDeviceId(); RequestMessage msg = new RequestMessage(); msg.setKey(key); msg.setData(mobilePosition); resultHolder.invokeAllResult(msg); } //回复 200 OK try { responseAck(request, Response.OK); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 移动设备位置数据查询 200: {}", e.getMessage()); } } catch (DocumentException e) { log.error("未处理的异常 ", e); } } @Override public void handForPlatform(RequestEvent evt, Platform parentPlatform, Element element) { } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/PresetQueryResponseMessageHandler.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.MessageResponseTask; import com.genersoft.iot.vmp.gb28181.bean.Platform; import com.genersoft.iot.vmp.gb28181.bean.Preset; 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 gov.nist.javax.sip.message.SIPRequest; import lombok.extern.slf4j.Slf4j; import org.dom4j.DocumentException; import org.dom4j.Element; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; 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.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.DelayQueue; import java.util.concurrent.TimeUnit; import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText; /** * 设备预置位查询应答 */ @Slf4j @Component public class PresetQueryResponseMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler { private final String cmdType = "PresetQuery"; @Autowired private ResponseMessageHandler responseMessageHandler; private final Map> mesageMap = new ConcurrentHashMap<>(); private final DelayQueue> delayQueue = new DelayQueue<>(); @Override public void afterPropertiesSet() throws Exception { responseMessageHandler.addHandler(cmdType, this); } @Override public void handForDevice(RequestEvent evt, Device device, Element element) { SIPRequest request = (SIPRequest) evt.getRequest(); try { Element rootElement = getRootElement(evt, device.getCharset()); if (rootElement == null) { log.warn("[ 设备预置位查询应答 ] content cannot be null, {}", evt.getRequest()); try { responseAck(request, Response.BAD_REQUEST); } catch (InvalidArgumentException | ParseException | SipException e) { log.error("[命令发送失败] 设备预置位查询应答处理: {}", e.getMessage()); } return; } Element presetListNumElement = rootElement.element("PresetList"); Element snElement = rootElement.element("SN"); //该字段可能为通道或则设备的id if (snElement == null || presetListNumElement == null) { try { responseAck(request, Response.BAD_REQUEST, "xml error"); } catch (InvalidArgumentException | ParseException | SipException e) { log.error("[命令发送失败] 设备预置位查询应答处理: {}", e.getMessage()); } return; } int num = Integer.parseInt(presetListNumElement.attributeValue("Num")); List presetQuerySipReqList = new ArrayList<>(); if (num > 0) { for (Iterator presetIterator = presetListNumElement.elementIterator(); presetIterator.hasNext(); ) { Element itemListElement = presetIterator.next(); Preset presetQuerySipReq = new Preset(); for (Iterator itemListIterator = itemListElement.elementIterator(); itemListIterator.hasNext(); ) { // 遍历item Element itemOne = itemListIterator.next(); String name = itemOne.getName(); String textTrim = itemOne.getTextTrim(); if ("PresetID".equalsIgnoreCase(name)) { presetQuerySipReq.setPresetId(textTrim); } else { presetQuerySipReq.setPresetName(textTrim); } } presetQuerySipReqList.add(presetQuerySipReq); } } String sn = getText(element, "SN"); addCatch(cmdType + "_" + sn, num, rootElement, presetQuerySipReqList); try { responseAck(request, Response.OK); } catch (InvalidArgumentException | ParseException | SipException e) { log.error("[命令发送失败] 设备预置位查询应答处理: {}", e.getMessage()); } } catch (DocumentException e) { log.error("[解析xml]失败: ", e); } } private void addCatch(String key, int sumNum, Element rootElement, List presetQuerySipReqList) { if (presetQuerySipReqList.size() == sumNum) { responseMessageHandler.handMessageEvent(rootElement, presetQuerySipReqList); if (mesageMap.containsKey(key)) { MessageResponseTask messageResponseTask = mesageMap.get(key); mesageMap.remove(key); boolean remove = delayQueue.remove(messageResponseTask); if (!remove) { log.info("[移除预置位查询任务] 从延时队列内移除失败: {}", key); } } }else { if (mesageMap.containsKey(key)) { MessageResponseTask messageResponseTask = mesageMap.get(key); List data = messageResponseTask.getData(); data.addAll(presetQuerySipReqList); if (data.size() == sumNum) { responseMessageHandler.handMessageEvent(rootElement, data); mesageMap.remove(key); boolean remove = delayQueue.remove(messageResponseTask); if (!remove) { log.info("[移除预置位查询任务] 从延时队列内移除失败: {}", key); } return; } messageResponseTask.setDelayTime(System.currentTimeMillis() + 1000); }else { MessageResponseTask messageResponseTask = new MessageResponseTask<>(); messageResponseTask.setElement(rootElement); messageResponseTask.setData(presetQuerySipReqList); messageResponseTask.setDelayTime(System.currentTimeMillis() + 1000); messageResponseTask.setKey(key); mesageMap.put(key, messageResponseTask); delayQueue.offer(messageResponseTask); } } } // 处理过期的缓存 @Scheduled(fixedDelay = 500, timeUnit = TimeUnit.MILLISECONDS) public void expirationCheck(){ while (!delayQueue.isEmpty()) { MessageResponseTask take = null; try { take = delayQueue.take(); try { responseMessageHandler.handMessageEvent(take.getElement(), take.getData()); mesageMap.remove(take.getKey()); }catch (Exception e) { log.error("[预置位查询到期] {} 到期处理时出现异常", take.getKey()); } } catch (InterruptedException e) { log.error("[设备订阅任务] ", e); } } } @Override public void handForPlatform(RequestEvent evt, Platform parentPlatform, Element rootElement) { } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/RecordInfoResponseMessageHandler.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd; import com.genersoft.iot.vmp.common.VideoManagerConstants; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.Platform; 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.event.record.RecordInfoEndEvent; import com.genersoft.iot.vmp.gb28181.event.record.RecordInfoEvent; 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.UJson; import gov.nist.javax.sip.message.SIPRequest; import lombok.extern.slf4j.Slf4j; import org.dom4j.Element; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; import javax.sip.InvalidArgumentException; import javax.sip.RequestEvent; import javax.sip.SipException; import javax.sip.message.Response; import java.text.ParseException; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText; /** * @author lin */ @Slf4j @Component public class RecordInfoResponseMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler { private final String cmdType = "RecordInfo"; @Autowired private ResponseMessageHandler responseMessageHandler; @Autowired private ApplicationEventPublisher applicationEventPublisher; @Autowired private RedisTemplate redisTemplate; private Long recordInfoTtl = 1800L; @Override public void afterPropertiesSet() throws Exception { responseMessageHandler.addHandler(cmdType, this); } @Override public void handForDevice(RequestEvent evt, Device device, Element rootElement) { try { // 回复200 OK responseAck((SIPRequest) evt.getRequest(), Response.OK); }catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 国标级联 国标录像: {}", e.getMessage()); } try { String sn = getText(rootElement, "SN"); String channelId = getText(rootElement, "DeviceID"); RecordInfo recordInfo = new RecordInfo(); recordInfo.setChannelId(channelId); recordInfo.setDeviceId(device.getDeviceId()); recordInfo.setSn(sn); recordInfo.setName(getText(rootElement, "Name")); String sumNumStr = getText(rootElement, "SumNum"); int sumNum = 0; if (!ObjectUtils.isEmpty(sumNumStr)) { sumNum = Integer.parseInt(sumNumStr); } recordInfo.setSumNum(sumNum); Element recordListElement = rootElement.element("RecordList"); if (recordListElement == null || sumNum == 0) { log.info("无录像数据"); recordInfo.setCount(sumNum); recordInfoEventPush(recordInfo); recordInfoEndEventPush(recordInfo); } else { Iterator recordListIterator = recordListElement.elementIterator(); if (recordListIterator != null) { List recordList = new ArrayList<>(); // 遍历DeviceList while (recordListIterator.hasNext()) { Element itemRecord = recordListIterator.next(); Element recordElement = itemRecord.element("DeviceID"); if (recordElement == null) { log.info("记录为空,下一个..."); continue; } 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")); 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); } Map map = recordList.stream() .filter(record -> record.getDeviceId() != null) .collect(Collectors.toMap(record -> record.getStartTime()+ record.getEndTime(), UJson::writeJson)); // 获取任务结果数据 String resKey = VideoManagerConstants.REDIS_RECORD_INFO_RES_PRE + channelId + sn; redisTemplate.opsForHash().putAll(resKey, map); redisTemplate.expire(resKey, recordInfoTtl, TimeUnit.SECONDS); String resCountKey = VideoManagerConstants.REDIS_RECORD_INFO_RES_COUNT_PRE + channelId + sn; Long incr = redisTemplate.opsForValue().increment(resCountKey, map.size()); if (incr == null) { incr = 0L; } redisTemplate.expire(resCountKey, recordInfoTtl, TimeUnit.SECONDS); recordInfo.setRecordList(recordList); recordInfo.setCount(Math.toIntExact(incr)); recordInfoEventPush(recordInfo); if (incr < sumNum) { return; } // 已接收完成 List resList = redisTemplate.opsForHash().entries(resKey).values().stream().map(e -> UJson.readJson(e.toString(), RecordItem.class)).collect(Collectors.toList()); if (resList.size() < sumNum) { return; } recordInfo.setRecordList(resList); recordInfoEndEventPush(recordInfo); } } } catch (Exception e) { log.error("[国标录像] 发现未处理的异常, \r\n{}", evt.getRequest()); log.error("[国标录像] 异常内容: ", e); } } @Override public void handForPlatform(RequestEvent evt, Platform parentPlatform, Element element) { } private void recordInfoEventPush(RecordInfo recordInfo) { if (recordInfo == null) { return; } if(recordInfo.getRecordList() != null) { Collections.sort(recordInfo.getRecordList()); }else{ recordInfo.setRecordList(new ArrayList<>()); } RecordInfoEvent outEvent = new RecordInfoEvent(this); outEvent.setRecordInfo(recordInfo); applicationEventPublisher.publishEvent(outEvent); } private void recordInfoEndEventPush(RecordInfo recordInfo) { if (recordInfo == null) { return; } if(recordInfo.getRecordList() != null) { Collections.sort(recordInfo.getRecordList()); }else{ recordInfo.setRecordList(new ArrayList<>()); } RecordInfoEndEvent outEvent = new RecordInfoEndEvent(this); outEvent.setRecordInfo(recordInfo); applicationEventPublisher.publishEvent(outEvent); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/ISIPResponseProcessor.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.event.response; import org.springframework.scheduling.annotation.Async; import javax.sip.ResponseEvent; /** * @description:处理接收IPCamera发来的SIP协议响应消息 * @author: swwheihei * @date: 2020年5月3日 下午4:42:22 */ public interface ISIPResponseProcessor { void process(ResponseEvent evt); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/SIPResponseProcessorAbstract.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.event.response; import org.springframework.beans.factory.InitializingBean; public abstract class SIPResponseProcessorAbstract implements InitializingBean, ISIPResponseProcessor { } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/ByeResponseProcessor.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.event.response.impl; import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver; import com.genersoft.iot.vmp.gb28181.transmit.event.response.SIPResponseProcessorAbstract; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.sip.ResponseEvent; /** * @description: BYE请求响应器 * @author: swwheihei * @date: 2020年5月3日 下午5:32:05 */ @Component public class ByeResponseProcessor extends SIPResponseProcessorAbstract { private final String method = "BYE"; @Autowired private SIPProcessorObserver sipProcessorObserver; @Override public void afterPropertiesSet() throws Exception { // 添加消息处理的订阅 sipProcessorObserver.addResponseProcessor(method, this); } /** * 处理BYE响应 * * @param evt */ @Override public void process(ResponseEvent evt) { // TODO Auto-generated method stub } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/CancelResponseProcessor.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.event.response.impl; import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver; import com.genersoft.iot.vmp.gb28181.transmit.event.response.SIPResponseProcessorAbstract; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.sip.ResponseEvent; /** * @description: CANCEL响应处理器 * @author: panlinlin * @date: 2021年11月5日 16:35 */ @Component public class CancelResponseProcessor extends SIPResponseProcessorAbstract { private final String method = "CANCEL"; @Autowired private SIPProcessorObserver sipProcessorObserver; @Override public void afterPropertiesSet() throws Exception { // 添加消息处理的订阅 sipProcessorObserver.addResponseProcessor(method, this); } /** * 处理CANCEL响应 * * @param evt */ @Override public void process(ResponseEvent evt) { // TODO Auto-generated method stub } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/InviteResponseProcessor.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.event.response.impl; import com.genersoft.iot.vmp.gb28181.bean.Gb28181Sdp; import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver; import com.genersoft.iot.vmp.gb28181.transmit.SIPSender; import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderProvider; import com.genersoft.iot.vmp.gb28181.transmit.event.response.SIPResponseProcessorAbstract; import com.genersoft.iot.vmp.gb28181.utils.SipUtils; import com.genersoft.iot.vmp.utils.IpPortUtil; import gov.nist.javax.sip.ResponseEventExt; import gov.nist.javax.sip.message.SIPResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.sdp.SdpParseException; import javax.sdp.SessionDescription; import javax.sip.InvalidArgumentException; import javax.sip.ResponseEvent; import javax.sip.SipException; import javax.sip.SipFactory; import javax.sip.address.SipURI; import javax.sip.message.Request; import javax.sip.message.Response; import java.text.ParseException; /** * @description: 处理INVITE响应 * @author: panlinlin * @date: 2021年11月5日 16:40 */ @Slf4j @Component public class InviteResponseProcessor extends SIPResponseProcessorAbstract { private final String method = "INVITE"; @Autowired private SIPProcessorObserver sipProcessorObserver; @Autowired private SIPSender sipSender; @Autowired private SIPRequestHeaderProvider headerProvider; @Override public void afterPropertiesSet() throws Exception { // 添加消息处理的订阅 sipProcessorObserver.addResponseProcessor(method, this); } /** * 处理invite响应 * * @param evt 响应消息 * @throws ParseException */ @Override public void process(ResponseEvent evt ){ log.debug("接收到消息:" + evt.getResponse()); try { SIPResponse response = (SIPResponse)evt.getResponse(); int statusCode = response.getStatusCode(); // trying不会回复 if (statusCode == Response.TRYING) { } // 成功响应 // 下发ack if (statusCode == Response.OK) { ResponseEventExt event = (ResponseEventExt)evt; String contentString = new String(response.getRawContent()); Gb28181Sdp gb28181Sdp = SipUtils.parseSDP(contentString); SessionDescription sdp = gb28181Sdp.getBaseSdb(); SipURI requestUri = SipFactory.getInstance().createAddressFactory().createSipURI(sdp.getOrigin().getUsername(), IpPortUtil.concatenateIpAndPort(event.getRemoteIpAddress(), String.valueOf(event.getRemotePort()))); Request reqAck = headerProvider.createAckRequest(response.getLocalAddress().getHostAddress(), requestUri, response); log.info("[回复ack] {}-> {}:{} ", sdp.getOrigin().getUsername(), event.getRemoteIpAddress(), event.getRemotePort()); sipSender.transmitRequest( response.getLocalAddress().getHostAddress(), reqAck); } } catch (InvalidArgumentException | ParseException | SipException | SdpParseException e) { log.info("[点播回复ACK],异常:", e ); } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/RegisterResponseProcessor.java ================================================ package com.genersoft.iot.vmp.gb28181.transmit.event.response.impl; import com.genersoft.iot.vmp.gb28181.bean.Platform; import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo; import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; import com.genersoft.iot.vmp.gb28181.event.sip.SipEvent; import com.genersoft.iot.vmp.gb28181.service.IPlatformService; import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver; import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; import com.genersoft.iot.vmp.gb28181.transmit.event.response.SIPResponseProcessorAbstract; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import gov.nist.javax.sip.message.SIPResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.sip.InvalidArgumentException; import javax.sip.ResponseEvent; import javax.sip.SipException; import javax.sip.header.WWWAuthenticateHeader; import javax.sip.message.Response; import java.text.ParseException; /** * @description:Register响应处理器 * @author: swwheihei * @date: 2020年5月3日 下午5:32:23 */ @Slf4j @Component public class RegisterResponseProcessor extends SIPResponseProcessorAbstract { private final String method = "REGISTER"; @Autowired private ISIPCommanderForPlatform sipCommanderForPlatform; @Autowired private IRedisCatchStorage redisCatchStorage; @Autowired private SIPProcessorObserver sipProcessorObserver; @Autowired private IPlatformService platformService; @Autowired private SipSubscribe sipSubscribe; @Override public void afterPropertiesSet() throws Exception { // 添加消息处理的订阅 sipProcessorObserver.addResponseProcessor(method, this); } /** * 处理Register响应 * * @param evt 事件 */ @Override public void process(ResponseEvent evt) { SIPResponse response = (SIPResponse)evt.getResponse(); String callId = response.getCallIdHeader().getCallId(); long seqNumber = response.getCSeqHeader().getSeqNumber(); SipEvent subscribe = sipSubscribe.getSubscribe(callId + seqNumber); if (subscribe == null || subscribe.getSipTransactionInfo() == null || subscribe.getSipTransactionInfo().getUser() == null) { return; } String action = subscribe.getSipTransactionInfo().getExpires() > 0 ? "注册" : "注销"; String platFormServerGbId = subscribe.getSipTransactionInfo().getUser(); log.info("[国标级联]{} {}响应 {} ", action, response.getStatusCode(), platFormServerGbId); Platform platform = platformService.queryPlatformByServerGBId(platFormServerGbId); if (platform == null) { log.warn("[国标级联]收到 来自{}的 {} 回复 {}, 但是平台信息未查询到!!!", platFormServerGbId, action, response.getStatusCode()); return; } if (response.getStatusCode() == Response.UNAUTHORIZED) { WWWAuthenticateHeader www = (WWWAuthenticateHeader)response.getHeader(WWWAuthenticateHeader.NAME); SipTransactionInfo sipTransactionInfo = new SipTransactionInfo(response); try { sipCommanderForPlatform.register(platform, sipTransactionInfo, www, null, null, subscribe.getSipTransactionInfo().getExpires() > 0); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 国标级联 再次注册: {}", e.getMessage()); } }else if (response.getStatusCode() == Response.OK){ if (subscribe.getSipTransactionInfo().getExpires() > 0) { SipTransactionInfo sipTransactionInfo = new SipTransactionInfo(response); platformService.online(platform, sipTransactionInfo); }else { platformService.offline(platform); } } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/utils/Coordtransform.java ================================================ package com.genersoft.iot.vmp.gb28181.utils; /** * 坐标转换 * 一个提供了百度坐标(BD09)、国测局坐标(火星坐标,GCJ02)、和WGS84坐标系之间的转换的工具类 * 参考https://github.com/wandergis/coordtransform 写的Java版本 * @author Xinconan * @date 2016-03-18 * @url https://github.com/xinconan/coordtransform */ public class Coordtransform { private static double x_PI = 3.14159265358979324 * 3000.0 / 180.0; private static double PI = 3.1415926535897932384626; private static double a = 6378245.0; private static double ee = 0.00669342162296594323; /** * 百度坐标系 (BD-09) 与 火星坐标系 (GCJ-02)的转换 * 即 百度 转 谷歌、高德 * @param bd_lon * @param bd_lat * @return Double[lon,lat] */ public static Double[] BD09ToGCJ02(Double bd_lon,Double bd_lat){ double x = bd_lon - 0.0065; double y = bd_lat - 0.006; double z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * x_PI); double theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * x_PI); Double[] arr = new Double[2]; arr[0] = z * Math.cos(theta); arr[1] = z * Math.sin(theta); return arr; } /** * 火星坐标系 (GCJ-02) 与百度坐标系 (BD-09) 的转换 * 即谷歌、高德 转 百度 * @param gcj_lon * @param gcj_lat * @return Double[lon,lat] */ public static Double[] GCJ02ToBD09(Double gcj_lon,Double gcj_lat){ double z = Math.sqrt(gcj_lon * gcj_lon + gcj_lat * gcj_lat) + 0.00002 * Math.sin(gcj_lat * x_PI); double theta = Math.atan2(gcj_lat, gcj_lon) + 0.000003 * Math.cos(gcj_lon * x_PI); Double[] arr = new Double[2]; arr[0] = z * Math.cos(theta) + 0.0065; arr[1] = z * Math.sin(theta) + 0.006; return arr; } /** * WGS84转GCJ02 * @param wgs_lon * @param wgs_lat * @return Double[lon,lat] */ public static Double[] WGS84ToGCJ02(Double wgs_lon,Double wgs_lat){ if(outOfChina(wgs_lon, wgs_lat)){ return new Double[]{wgs_lon,wgs_lat}; } double dlat = transformlat(wgs_lon - 105.0, wgs_lat - 35.0); double dlng = transformlng(wgs_lon - 105.0, wgs_lat - 35.0); double radlat = wgs_lat / 180.0 * PI; double magic = Math.sin(radlat); magic = 1 - ee * magic * magic; double sqrtmagic = Math.sqrt(magic); dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI); dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * PI); Double[] arr = new Double[2]; arr[0] = wgs_lon + dlng; arr[1] = wgs_lat + dlat; return arr; } /** * GCJ02转WGS84 * @param gcj_lon * @param gcj_lat * @return Double[lon,lat] */ public static Double[] GCJ02ToWGS84(Double gcj_lon,Double gcj_lat){ if(outOfChina(gcj_lon, gcj_lat)){ return new Double[]{gcj_lon,gcj_lat}; } double dlat = transformlat(gcj_lon - 105.0, gcj_lat - 35.0); double dlng = transformlng(gcj_lon - 105.0, gcj_lat - 35.0); double radlat = gcj_lat / 180.0 * PI; double magic = Math.sin(radlat); magic = 1 - ee * magic * magic; double sqrtmagic = Math.sqrt(magic); dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI); dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * PI); double mglat = gcj_lat + dlat; double mglng = gcj_lon + dlng; return new Double[]{gcj_lon * 2 - mglng, gcj_lat * 2 - mglat}; } private static Double transformlat(double lng, double lat) { double ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng)); ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0; ret += (20.0 * Math.sin(lat * PI) + 40.0 * Math.sin(lat / 3.0 * PI)) * 2.0 / 3.0; ret += (160.0 * Math.sin(lat / 12.0 * PI) + 320 * Math.sin(lat * PI / 30.0)) * 2.0 / 3.0; return ret; } private static Double transformlng(double lng,double lat) { double ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng)); ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0; ret += (20.0 * Math.sin(lng * PI) + 40.0 * Math.sin(lng / 3.0 * PI)) * 2.0 / 3.0; ret += (150.0 * Math.sin(lng / 12.0 * PI) + 300.0 * Math.sin(lng / 30.0 * PI)) * 2.0 / 3.0; return ret; } /** * outOfChina * @描述: 判断是否在国内,不在国内则不做偏移 * @param lng * @param lat * @return {boolean} */ private static boolean outOfChina(Double lng,Double lat) { return (lng < 72.004 || lng > 137.8347) || ((lat < 0.8293 || lat > 55.8271) || false); }; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/utils/MessageElement.java ================================================ package com.genersoft.iot.vmp.gb28181.utils; import java.lang.annotation.*; /** * @author gaofuwang * @version 1.0 * @date 2022/6/28 14:58 */ @Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface MessageElement { String value(); String subVal() default ""; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/utils/MessageElementForCatalog.java ================================================ package com.genersoft.iot.vmp.gb28181.utils; import java.lang.annotation.*; /** * @author gaofuwang * @version 1.0 * @date 2022/6/28 14:58 */ @Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface MessageElementForCatalog { String[] value(); String subVal() default ""; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/utils/NumericUtil.java ================================================ package com.genersoft.iot.vmp.gb28181.utils; /** * 数值格式判断和处理 * @author lawrencehj * @date 2021年1月27日 */ public class NumericUtil { /** * 判断是否Double格式 * @param str * @return true/false */ public static boolean isDouble(String str) { try { Double num2 = Double.valueOf(str); // logger.debug(num2 + " is a valid numeric string!"); return true; } catch (Exception e) { // logger.debug(str + " is an invalid numeric string!"); return false; } } /** * 判断是否Double格式 * @param str * @return true/false */ public static boolean isInteger(String str) { try { int num2 = Integer.valueOf(str); // logger.debug(num2 + " is an integer!"); return true; } catch (Exception e) { // logger.debug(str + " is not an integer!"); return false; } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/utils/SipUtils.java ================================================ package com.genersoft.iot.vmp.gb28181.utils; import com.genersoft.iot.vmp.gb28181.bean.Gb28181Sdp; import com.genersoft.iot.vmp.common.RemoteAddressInfo; import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.utils.GitUtil; import gov.nist.javax.sip.address.AddressImpl; import gov.nist.javax.sip.address.SipUri; import gov.nist.javax.sip.header.Subject; import gov.nist.javax.sip.message.SIPRequest; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.RandomStringUtils; import org.springframework.util.ObjectUtils; import javax.sdp.SdpFactory; import javax.sdp.SdpParseException; import javax.sdp.SessionDescription; import javax.sip.PeerUnavailableException; import javax.sip.SipFactory; import javax.sip.header.FromHeader; import javax.sip.header.SubjectHeader; import javax.sip.header.UserAgentHeader; import javax.sip.message.Request; import java.text.ParseException; import java.time.LocalDateTime; import java.time.format.DateTimeParseException; import java.util.ArrayList; import java.util.List; import java.util.UUID; /** * @author panlinlin * @version 1.0.0 * @description JAIN SIP的工具类 * @createTime 2021年09月27日 15:12:00 */ @Slf4j public class SipUtils { public static String getUserIdFromFromHeader(Request request) { FromHeader fromHeader = (FromHeader)request.getHeader(FromHeader.NAME); return getUserIdFromFromHeader(fromHeader); } /** * 从subject读取channelId * */ public static String[] getChannelIdFromRequest(Request request) { SubjectHeader subject = (Subject)request.getHeader("subject"); if (subject == null) { // 如果缺失subject return null; } String[] result = new String[2]; String subjectStr = subject.getSubject(); if (subjectStr.indexOf(",") > 0) { String[] subjectSplit = subjectStr.split(","); result[0] = subjectSplit[0].split(":")[0]; result[1] = subjectSplit[1].split(":")[0]; }else { result[0] = subjectStr.split(":")[0]; } return result; } public static String getUserIdFromFromHeader(FromHeader fromHeader) { AddressImpl address = (AddressImpl)fromHeader.getAddress(); SipUri uri = (SipUri) address.getURI(); return uri.getUser(); } public static String getNewViaTag() { return "z9hG4bK" + RandomStringUtils.randomNumeric(10); } public static UserAgentHeader createUserAgentHeader(GitUtil gitUtil) throws PeerUnavailableException, ParseException { List agentParam = new ArrayList<>(); agentParam.add("WVP-Pro "); if (gitUtil != null ) { if (!ObjectUtils.isEmpty(gitUtil.getBuildVersion())) { agentParam.add("v"); agentParam.add(gitUtil.getBuildVersion() + "."); } if (!ObjectUtils.isEmpty(gitUtil.getCommitTime())) { agentParam.add(gitUtil.getCommitTime()); } } return SipFactory.getInstance().createHeaderFactory().createUserAgentHeader(agentParam); } public static String getNewFromTag(){ return UUID.randomUUID().toString().replace("-", ""); // return getNewTag(); } public static String getNewTag(){ return String.valueOf(System.currentTimeMillis()); } /** * 云台指令码计算 * * @param leftRight 镜头左移右移 0:停止 1:左移 2:右移 * @param upDown 镜头上移下移 0:停止 1:上移 2:下移 * @param inOut 镜头放大缩小 0:停止 1:缩小 2:放大 * @param moveSpeed 镜头移动速度 默认 0XFF (0-255) * @param zoomSpeed 镜头缩放速度 默认 0X1 (0-255) */ public static String cmdString(int leftRight, int upDown, int inOut, int moveSpeed, int zoomSpeed) { int cmdCode = 0; if (leftRight == 2) { cmdCode|=0x01; // 右移 } else if(leftRight == 1) { cmdCode|=0x02; // 左移 } if (upDown == 2) { cmdCode|=0x04; // 下移 } else if(upDown == 1) { cmdCode|=0x08; // 上移 } if (inOut == 2) { cmdCode |= 0x10; // 放大 } else if(inOut == 1) { cmdCode |= 0x20; // 缩小 } StringBuilder builder = new StringBuilder("A50F01"); String strTmp; strTmp = String.format("%02X", cmdCode); builder.append(strTmp, 0, 2); strTmp = String.format("%02X", moveSpeed); builder.append(strTmp, 0, 2); builder.append(strTmp, 0, 2); //优化zoom低倍速下的变倍速率 if ((zoomSpeed > 0) && (zoomSpeed <16)) { zoomSpeed = 16; } strTmp = String.format("%X", zoomSpeed); builder.append(strTmp, 0, 1).append("0"); //计算校验码 int checkCode = (0XA5 + 0X0F + 0X01 + cmdCode + moveSpeed + moveSpeed + (zoomSpeed /*<< 4*/ & 0XF0)) % 0X100; strTmp = String.format("%02X", checkCode); builder.append(strTmp, 0, 2); return builder.toString(); } public static String getNewCallId() { return (int) Math.floor(Math.random() * 1000000000) + ""; } public static int getTypeCodeFromGbCode(String deviceId) { if (ObjectUtils.isEmpty(deviceId)) { return 0; } return Integer.parseInt(deviceId.substring(10, 13)); } /** * 判断是否是前端外围设备 * @param deviceId * @return */ public static boolean isFrontEnd(String deviceId) { int typeCodeFromGbCode = getTypeCodeFromGbCode(deviceId); return typeCodeFromGbCode > 130 && typeCodeFromGbCode < 199; } /** * 从请求中获取设备ip地址和端口号 * @param request 请求 * @param sipUseSourceIpAsRemoteAddress false 从via中获取地址, true 直接获取远程地址 * @return 地址信息 */ public static RemoteAddressInfo getRemoteAddressFromRequest(SIPRequest request, boolean sipUseSourceIpAsRemoteAddress) { String remoteAddress; int remotePort; if (sipUseSourceIpAsRemoteAddress) { remoteAddress = request.getPeerPacketSourceAddress().getHostAddress(); remotePort = request.getPeerPacketSourcePort(); }else { // 判断RPort是否改变,改变则说明路由nat信息变化,修改设备信息 // 获取到通信地址等信息 remoteAddress = request.getTopmostViaHeader().getReceived(); remotePort = request.getTopmostViaHeader().getRPort(); // 解析本地地址替代 if (ObjectUtils.isEmpty(remoteAddress) || remotePort == -1) { if (request.getPeerPacketSourceAddress() != null) { remoteAddress = request.getPeerPacketSourceAddress().getHostAddress(); }else { remoteAddress = request.getRemoteAddress().getHostAddress(); } if( request.getPeerPacketSourcePort() > 0) { remotePort = request.getPeerPacketSourcePort(); }else { remotePort = request.getRemotePort(); } } } return new RemoteAddressInfo(remoteAddress, remotePort); } public static Gb28181Sdp parseSDP(String sdpStr) throws SdpParseException { // jainSip不支持y= f=字段, 移除以解析。 int ssrcIndex = sdpStr.indexOf("y="); int mediaDescriptionIndex = sdpStr.indexOf("f="); // 检查是否有y字段 SessionDescription sdp; String ssrc = null; String mediaDescription = null; if (mediaDescriptionIndex == 0 && ssrcIndex == 0) { sdp = SdpFactory.getInstance().createSessionDescription(sdpStr); }else { String lines[] = sdpStr.split("\\r?\\n"); StringBuilder sdpBuffer = new StringBuilder(); for (String line : lines) { if (line.trim().startsWith("y=")) { ssrc = line.substring(2); }else if (line.trim().startsWith("f=")) { mediaDescription = line.substring(2); }else { sdpBuffer.append(line.trim()).append("\r\n"); } } sdp = SdpFactory.getInstance().createSessionDescription(sdpBuffer.toString()); } return Gb28181Sdp.getInstance(sdp, ssrc, mediaDescription); } public static String getSsrcFromSdp(String sdpStr) { // jainSip不支持y= f=字段, 移除以解析。 int ssrcIndex = sdpStr.indexOf("y="); if (ssrcIndex == 0) { return null; } String lines[] = sdpStr.split("\\r?\\n"); for (String line : lines) { if (line.trim().startsWith("y=")) { return line.substring(2); } } return null; } public static String parseTime(String timeStr) { if (ObjectUtils.isEmpty(timeStr)){ return null; } LocalDateTime localDateTime; try { localDateTime = LocalDateTime.parse(timeStr); }catch (DateTimeParseException e) { try { localDateTime = LocalDateTime.parse(timeStr, DateUtil.formatterISO8601); }catch (DateTimeParseException e2) { log.error("[格式化时间] 无法格式化时间: {}", timeStr); return null; } } return localDateTime.format(DateUtil.formatter); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/utils/VectorTileCatch.java ================================================ package com.genersoft.iot.vmp.gb28181.utils; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import com.genersoft.iot.vmp.gb28181.bean.VectorTileSource; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.springframework.util.ConcurrentReferenceHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.DelayQueue; import java.util.concurrent.TimeUnit; @Slf4j @Component public class VectorTileCatch { private final Map vectorTileMap = new ConcurrentReferenceHashMap<>(); private final DelayQueue delayQueue = new DelayQueue<>(); public void addVectorTile(String id, String key, byte[] content) { VectorTileSource vectorTileSource = vectorTileMap.get(id); if (vectorTileSource == null) { vectorTileSource = new VectorTileSource(); vectorTileSource.setId(id); vectorTileMap.put(id, vectorTileSource); delayQueue.offer(vectorTileSource); } vectorTileSource.getVectorTileMap().put(key, content); } public byte[] getVectorTile(String id, String key) { if (!vectorTileMap.containsKey(id)) { return null; } return vectorTileMap.get(id).getVectorTileMap().get(key); } public void addSource(String id, List channelList) { VectorTileSource vectorTileSource = vectorTileMap.get(id); if (vectorTileSource == null) { vectorTileSource = new VectorTileSource(); vectorTileSource.setId(id); vectorTileMap.put(id, vectorTileSource); delayQueue.offer(vectorTileSource); } vectorTileMap.get(id).getChannelList().addAll(channelList); } public void remove(String id) { VectorTileSource vectorTileSource = vectorTileMap.get(id); if (vectorTileSource != null) { delayQueue.remove(vectorTileSource); } vectorTileMap.remove(id); } public List getChannelList(String id) { if (!vectorTileMap.containsKey(id)) { return null; } return vectorTileMap.get(id).getChannelList(); } public void save(String id) { if (!vectorTileMap.containsKey(id)) { return; } VectorTileSource vectorTileSource = vectorTileMap.get(id); if (vectorTileSource == null) { return; } vectorTileMap.remove(id); delayQueue.remove(vectorTileSource); vectorTileMap.put("DEFAULT", vectorTileSource); } // 缓存数据过期检查 @Scheduled(fixedDelay = 30, timeUnit = TimeUnit.MINUTES) public void expirationCheck(){ while (!delayQueue.isEmpty()) { try { VectorTileSource vectorTileSource = delayQueue.take(); vectorTileMap.remove(vectorTileSource.getId()); } catch (InterruptedException e) { log.error("[清理过期的抽稀数据] ", e); } } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/gb28181/utils/XmlUtil.java ================================================ package com.genersoft.iot.vmp.gb28181.utils; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.math.NumberUtils; import org.dom4j.Attribute; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.io.SAXReader; import org.springframework.util.ObjectUtils; import org.springframework.util.ReflectionUtils; import javax.sip.RequestEvent; import javax.sip.message.Request; import java.io.ByteArrayInputStream; import java.io.StringReader; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.*; /** * 基于dom4j的工具包 */ @Slf4j public class XmlUtil { /** * 解析XML为Document对象 */ public static Element parseXml(String xml) { Document document = null; // StringReader sr = new StringReader(xml); SAXReader saxReader = new SAXReader(); try { document = saxReader.read(sr); } catch (DocumentException e) { log.error("解析失败", e); } return null == document ? null : document.getRootElement(); } /** * 获取element对象的text的值 * * @param em 节点的对象 * @param tag 节点的tag * @return 节点 */ public static String getText(Element em, String tag) { if (null == em) { return null; } Element e = em.element(tag); // return null == e ? null : e.getText().trim(); } /** * 获取element对象的text的值 * * @param em 节点的对象 * @param tag 节点的tag * @return 节点 */ public static Double getDouble(Element em, String tag) { if (null == em) { return null; } Element e = em.element(tag); if (null == e) { return null; } String text = e.getText().trim(); if (ObjectUtils.isEmpty(text) || !NumberUtils.isParsable(text)) { return null; } return Double.parseDouble(text); } /** * 获取element对象的text的值 * * @param em 节点的对象 * @param tag 节点的tag * @return 节点 */ public static Integer getInteger(Element em, String tag) { if (null == em) { return null; } Element e = em.element(tag); if (null == e) { return null; } String text = e.getText().trim(); if (ObjectUtils.isEmpty(text) || !NumberUtils.isParsable(text)) { return null; } return Integer.parseInt(text); } /** * 递归解析xml节点,适用于 多节点数据 * * @param node node * @param nodeName nodeName * @return List> */ public static List> listNodes(Element node, String nodeName) { if (null == node) { return null; } // 初始化返回 List> listMap = new ArrayList>(); // 首先获取当前节点的所有属性节点 List list = node.attributes(); Map map = null; // 遍历属性节点 for (Attribute attribute : list) { if (nodeName.equals(node.getName())) { if (null == map) { map = new HashMap(); listMap.add(map); } // 取到的节点属性放到map中 map.put(attribute.getName(), attribute.getValue()); } } // 遍历当前节点下的所有节点 ,nodeName 要解析的节点名称 // 使用递归 Iterator iterator = node.elementIterator(); while (iterator.hasNext()) { Element e = iterator.next(); listMap.addAll(listNodes(e, nodeName)); } return listMap; } /** * xml转json * * @param element * @param json */ public static void node2Json(Element element, JSONObject json) { // 如果是属性 for (Object o : element.attributes()) { Attribute attr = (Attribute) o; if (!ObjectUtils.isEmpty(attr.getValue())) { json.put("@" + attr.getName(), attr.getValue()); } } List chdEl = element.elements(); if (chdEl.isEmpty() && !ObjectUtils.isEmpty(element.getText())) {// 如果没有子元素,只有一个值 json.put(element.getName(), element.getText()); } for (Element e : chdEl) { // 有子元素 if (!e.elements().isEmpty()) { // 子元素也有子元素 JSONObject chdjson = new JSONObject(); node2Json(e, chdjson); Object o = json.get(e.getName()); if (o != null) { JSONArray jsona = null; if (o instanceof JSONObject) { // 如果此元素已存在,则转为jsonArray JSONObject jsono = (JSONObject) o; json.remove(e.getName()); jsona = new JSONArray(); jsona.add(jsono); jsona.add(chdjson); } if (o instanceof JSONArray) { jsona = (JSONArray) o; jsona.add(chdjson); } json.put(e.getName(), jsona); } else { if (!chdjson.isEmpty()) { json.put(e.getName(), chdjson); } } } else { // 子元素没有子元素 for (Object o : element.attributes()) { Attribute attr = (Attribute) o; if (!ObjectUtils.isEmpty(attr.getValue())) { json.put("@" + attr.getName(), attr.getValue()); } } if (!e.getText().isEmpty()) { json.put(e.getName(), e.getText()); } } } } public static Element getRootElement(RequestEvent evt) throws DocumentException { return getRootElement(evt, "gb2312"); } public static Element getRootElement(RequestEvent evt, String charset) throws DocumentException { Request request = evt.getRequest(); return getRootElement(request.getRawContent(), charset); } public static Element getRootElement(byte[] content, String charset) throws DocumentException { if (charset == null) { charset = "gb2312"; } SAXReader reader = new SAXReader(); reader.setEncoding(charset); Document xml = reader.read(new ByteArrayInputStream(content)); return xml.getRootElement(); } private enum ChannelType{ CivilCode, BusinessGroup,VirtualOrganization,Other } // public static DeviceChannel channelContentHandler(Element itemDevice, Device device, String event){ // DeviceChannel deviceChannel = new DeviceChannel(); // deviceChannel.setDeviceId(device.getDeviceId()); // Element channdelIdElement = itemDevice.element("DeviceID"); // if (channdelIdElement == null) { // logger.warn("解析Catalog消息时发现缺少 DeviceID"); // return null; // } // String channelId = channdelIdElement.getTextTrim(); // if (ObjectUtils.isEmpty(channelId)) { // logger.warn("解析Catalog消息时发现缺少 DeviceID"); // return null; // } // deviceChannel.setDeviceId(channelId); // if (event != null && !event.equals(CatalogEvent.ADD) && !event.equals(CatalogEvent.UPDATE)) { // // 除了ADD和update情况下需要识别全部内容, // return deviceChannel; // } // Element nameElement = itemDevice.element("Name"); // // 当通道名称为空时,设置通道名称为通道编码,避免级联时因通道名称为空导致上级接收通道失败 // if (nameElement != null && StringUtils.isNotBlank(nameElement.getText())) { // deviceChannel.setName(nameElement.getText()); // } else { // deviceChannel.setName(channelId); // } // if(channelId.length() <= 8) { // deviceChannel.setHasAudio(false); // CivilCodePo parentCode = CivilCodeUtil.INSTANCE.getParentCode(channelId); // if (parentCode != null) { // deviceChannel.setParentId(parentCode.getCode()); // deviceChannel.setCivilCode(parentCode.getCode()); // }else { // logger.warn("[xml解析] 无法确定行政区划{}的上级行政区划", channelId); // } // deviceChannel.setStatus("ON"); // return deviceChannel; // }else { // if(channelId.length() != 20) { // logger.warn("[xml解析] 失败,编号不符合国标28181定义: {}", channelId); // return null; // } // // int code = Integer.parseInt(channelId.substring(10, 13)); // if (code == 136 || code == 137 || code == 138) { // deviceChannel.setHasAudio(true); // }else { // deviceChannel.setHasAudio(false); // } // // 设备厂商 // String manufacturer = getText(itemDevice, "Manufacturer"); // // 设备型号 // String model = getText(itemDevice, "Model"); // // 设备归属 // String owner = getText(itemDevice, "Owner"); // // 行政区域 // String civilCode = getText(itemDevice, "CivilCode"); // // 虚拟组织所属的业务分组ID,业务分组根据特定的业务需求制定,一个业务分组包含一组特定的虚拟组织 // String businessGroupID = getText(itemDevice, "BusinessGroupID"); // // 父设备/区域/系统ID // String parentID = getText(itemDevice, "ParentID"); // if (parentID != null && parentID.equalsIgnoreCase("null")) { // parentID = null; // } // // 注册方式(必选)缺省为1;1:符合IETFRFC3261标准的认证注册模式;2:基于口令的双向认证注册模式;3:基于数字证书的双向认证注册模式 // String registerWay = getText(itemDevice, "RegisterWay"); // // 保密属性(必选)缺省为0;0:不涉密,1:涉密 // String secrecy = getText(itemDevice, "Secrecy"); // // 安装地址 // String address = getText(itemDevice, "Address"); // // switch (code){ // case 200: // // 系统目录 // if (!ObjectUtils.isEmpty(manufacturer)) { // deviceChannel.setManufacture(manufacturer); // } // if (!ObjectUtils.isEmpty(model)) { // deviceChannel.setModel(model); // } // if (!ObjectUtils.isEmpty(owner)) { // deviceChannel.setOwner(owner); // } // if (!ObjectUtils.isEmpty(civilCode)) { // deviceChannel.setCivilCode(civilCode); // deviceChannel.setParentId(civilCode); // }else { // if (!ObjectUtils.isEmpty(parentID)) { // deviceChannel.setParentId(parentID); // } // } // if (!ObjectUtils.isEmpty(address)) { // deviceChannel.setAddress(address); // } // deviceChannel.setStatus(true); // if (!ObjectUtils.isEmpty(registerWay)) { // try { // deviceChannel.setRegisterWay(Integer.parseInt(registerWay)); // }catch (NumberFormatException exception) { // logger.warn("[xml解析] 从通道数据获取registerWay失败: {}", registerWay); // } // } // if (!ObjectUtils.isEmpty(secrecy)) { // deviceChannel.setSecrecy(secrecy); // } // return deviceChannel; // case 215: // // 业务分组 // deviceChannel.setStatus(true); // if (!ObjectUtils.isEmpty(parentID)) { // if (!parentID.trim().equalsIgnoreCase(device.getDeviceId())) { // deviceChannel.setParentId(parentID); // } // }else { // logger.warn("[xml解析] 业务分组数据中缺少关键信息->ParentId"); // if (!ObjectUtils.isEmpty(civilCode)) { // deviceChannel.setCivilCode(civilCode); // } // } // break; // case 216: // // 虚拟组织 // deviceChannel.setStatus(true); // if (!ObjectUtils.isEmpty(businessGroupID)) { // deviceChannel.setBusinessGroupId(businessGroupID); // } // // if (!ObjectUtils.isEmpty(parentID)) { // if (parentID.contains("/")) { // String[] parentIdArray = parentID.split("/"); // parentID = parentIdArray[parentIdArray.length - 1]; // } // deviceChannel.setParentId(parentID); // }else { // if (!ObjectUtils.isEmpty(businessGroupID)) { // deviceChannel.setParentId(businessGroupID); // } // } // break; // default: // // 设备目录 // if (!ObjectUtils.isEmpty(manufacturer)) { // deviceChannel.setManufacture(manufacturer); // } // if (!ObjectUtils.isEmpty(model)) { // deviceChannel.setModel(model); // } // if (!ObjectUtils.isEmpty(owner)) { // deviceChannel.setOwner(owner); // } // if (!ObjectUtils.isEmpty(civilCode) // && civilCode.length() <= 8 // && NumberUtils.isParsable(civilCode) // && civilCode.length()%2 == 0 // ) { // deviceChannel.setCivilCode(civilCode); // } // if (!ObjectUtils.isEmpty(businessGroupID)) { // deviceChannel.setBusinessGroupId(businessGroupID); // } // // // 警区 // String block = getText(itemDevice, "Block"); // if (!ObjectUtils.isEmpty(block)) { // deviceChannel.setBlock(block); // } // if (!ObjectUtils.isEmpty(address)) { // deviceChannel.setAddress(address); // } // // if (!ObjectUtils.isEmpty(secrecy)) { // deviceChannel.setSecrecy(secrecy); // } // // // 当为设备时,是否有子设备(必选)1有,0没有 // String parental = getText(itemDevice, "Parental"); // if (!ObjectUtils.isEmpty(parental)) { // try { // // 由于海康会错误的发送65535作为这里的取值,所以这里除非是0否则认为是1 // if (!ObjectUtils.isEmpty(parental) && parental.length() == 1 && Integer.parseInt(parental) == 0) { // deviceChannel.setParental(0); // }else { // deviceChannel.setParental(1); // } // }catch (NumberFormatException e) { // logger.warn("[xml解析] 从通道数据获取 parental失败: {}", parental); // } // } // // 父设备/区域/系统ID // // if (!ObjectUtils.isEmpty(parentID) ) { // if (parentID.contains("/")) { // String[] parentIdArray = parentID.split("/"); // deviceChannel.setParentId(parentIdArray[parentIdArray.length - 1]); // }else { // if (parentID.length()%2 == 0) { // deviceChannel.setParentId(parentID); // }else { // logger.warn("[xml解析] 不规范的parentID:{}, 已舍弃", parentID); // } // } // }else { // if (!ObjectUtils.isEmpty(businessGroupID)) { // deviceChannel.setParentId(businessGroupID); // }else { // if (!ObjectUtils.isEmpty(deviceChannel.getCivilCode())) { // deviceChannel.setParentId(deviceChannel.getCivilCode()); // } // } // } // // 注册方式 // if (!ObjectUtils.isEmpty(registerWay)) { // try { // int registerWayInt = Integer.parseInt(registerWay); // deviceChannel.setRegisterWay(registerWayInt); // }catch (NumberFormatException exception) { // logger.warn("[xml解析] 从通道数据获取registerWay失败: {}", registerWay); // deviceChannel.setRegisterWay(1); // } // }else { // deviceChannel.setRegisterWay(1); // } // // // 信令安全模式(可选)缺省为0; 0:不采用;2:S/MIME 签名方式;3:S/MIME加密签名同时采用方式;4:数字摘要方式 // String safetyWay = getText(itemDevice, "SafetyWay"); // if (!ObjectUtils.isEmpty(safetyWay)) { // try { // deviceChannel.setSafetyWay(Integer.parseInt(safetyWay)); // }catch (NumberFormatException e) { // logger.warn("[xml解析] 从通道数据获取 safetyWay失败: {}", safetyWay); // } // } // // // 证书序列号(有证书的设备必选) // String certNum = getText(itemDevice, "CertNum"); // if (!ObjectUtils.isEmpty(certNum)) { // deviceChannel.setCertNum(certNum); // } // // // 证书有效标识(有证书的设备必选)缺省为0;证书有效标识:0:无效 1:有效 // String certifiable = getText(itemDevice, "Certifiable"); // if (!ObjectUtils.isEmpty(certifiable)) { // try { // deviceChannel.setCertifiable(Integer.parseInt(certifiable)); // }catch (NumberFormatException e) { // logger.warn("[xml解析] 从通道数据获取 Certifiable失败: {}", certifiable); // } // } // // // 无效原因码(有证书且证书无效的设备必选) // String errCode = getText(itemDevice, "ErrCode"); // if (!ObjectUtils.isEmpty(errCode)) { // try { // deviceChannel.setErrCode(Integer.parseInt(errCode)); // }catch (NumberFormatException e) { // logger.warn("[xml解析] 从通道数据获取 ErrCode失败: {}", errCode); // } // } // // // 证书终止有效期(有证书的设备必选) // String endTime = getText(itemDevice, "EndTime"); // if (!ObjectUtils.isEmpty(endTime)) { // deviceChannel.setEndTime(endTime); // } // // // // 设备/区域/系统IP地址 // String ipAddress = getText(itemDevice, "IPAddress"); // if (!ObjectUtils.isEmpty(ipAddress)) { // deviceChannel.setIpAddress(ipAddress); // } // // // 设备/区域/系统端口 // String port = getText(itemDevice, "Port"); // if (!ObjectUtils.isEmpty(port)) { // try { // deviceChannel.setPort(Integer.parseInt(port)); // }catch (NumberFormatException e) { // logger.warn("[xml解析] 从通道数据获取 Port失败: {}", port); // } // } // // // 设备口令 // String password = getText(itemDevice, "Password"); // if (!ObjectUtils.isEmpty(password)) { // deviceChannel.setPassword(password); // } // // // // 设备状态 // String status = getText(itemDevice, "Status"); // if (status != null) { // // ONLINE OFFLINE HIKVISION DS-7716N-E4 NVR的兼容性处理 // if (status.equalsIgnoreCase("ON") || status.equalsIgnoreCase("On") || status.equalsIgnoreCase("ONLINE") || status.equalsIgnoreCase("OK")) { // deviceChannel.setStatus(true); // } // if (status.equalsIgnoreCase("OFF") || status.equalsIgnoreCase("Off") || status.equalsIgnoreCase("OFFLINE")) { // deviceChannel.setStatus(false); // } // }else { // deviceChannel.setStatus(true); // } //// logger.info("状态字符串: {}", status); //// logger.info("状态结果: {}", deviceChannel.isStatus()); // // 经度 // String longitude = getText(itemDevice, "Longitude"); // if (NumericUtil.isDouble(longitude)) { // deviceChannel.setLongitude(Double.parseDouble(longitude)); // } else { // deviceChannel.setLongitude(0.00); // } // // // 纬度 // String latitude = getText(itemDevice, "Latitude"); // if (NumericUtil.isDouble(latitude)) { // deviceChannel.setLatitude(Double.parseDouble(latitude)); // } else { // deviceChannel.setLatitude(0.00); // } // // deviceChannel.setGpsTime(DateUtil.getNow()); // // // -摄像机类型扩展,标识摄像机类型:1-球机;2-半球;3-固定枪机;4-遥控枪机。当目录项为摄像机时可选 // String ptzType = getText(itemDevice, "PTZType"); // if (ObjectUtils.isEmpty(ptzType)) { // //兼容INFO中的信息 // Element info = itemDevice.element("Info"); // String ptzTypeFromInfo = XmlUtil.getText(info, "PTZType"); // if(!ObjectUtils.isEmpty(ptzTypeFromInfo)){ // try { // deviceChannel.setPtzType(Integer.parseInt(ptzTypeFromInfo)); // }catch (NumberFormatException e){ // logger.warn("[xml解析] 从通道数据info中获取PTZType失败: {}", ptzTypeFromInfo); // } // } // } else { // try { // deviceChannel.setPtzType(Integer.parseInt(ptzType)); // }catch (NumberFormatException e){ // logger.warn("[xml解析] 从通道数据中获取PTZType失败: {}", ptzType); // } // } // // // TODO 摄像机位置类型扩展。 // // 1-省际检查站、 // // 2-党政机关、 // // 3-车站码头、 // // 4-中心广场、 // // 5-体育场馆、 // // 6-商业中心、 // // 7-宗教场所、 // // 8-校园周边、 // // 9-治安复杂区域、 // // 10-交通干线。 // // String positionType = getText(itemDevice, "PositionType"); // // // TODO 摄像机安装位置室外、室内属性。1-室外、2-室内。 // // String roomType = getText(itemDevice, "RoomType"); // // TODO 摄像机用途属性 // // String useType = getText(itemDevice, "UseType"); // // TODO 摄像机补光属性。1-无补光、2-红外补光、3-白光补光 // // String supplyLightType = getText(itemDevice, "SupplyLightType"); // // TODO 摄像机监视方位属性。1-东、2-西、3-南、4-北、5-东南、6-东北、7-西南、8-西北。 // // String directionType = getText(itemDevice, "DirectionType"); // // TODO 摄像机支持的分辨率,可有多个分辨率值,各个取值间以“/”分隔。分辨率取值参见附录 F中SDPf字段规定 // // String resolution = getText(itemDevice, "Resolution"); // // // TODO 下载倍速范围(可选),各可选参数以“/”分隔,如设备支持1,2,4倍速下载则应写为“1/2/4 // // String downloadSpeed = getText(itemDevice, "DownloadSpeed"); // // TODO 空域编码能力,取值0:不支持;1:1级增强(1个增强层);2:2级增强(2个增强层);3:3级增强(3个增强层) // // String svcSpaceSupportMode = getText(itemDevice, "SVCSpaceSupportMode"); // // TODO 时域编码能力,取值0:不支持;1:1级增强;2:2级增强;3:3级增强 // // String svcTimeSupportMode = getText(itemDevice, "SVCTimeSupportMode"); // // // deviceChannel.setSecrecy(secrecy); // break; // } // } // // return deviceChannel; // } /** * 新增方法支持内部嵌套 * * @param element xmlElement * @param clazz 结果类 * @param 泛型 * @return 结果对象 * @throws NoSuchMethodException * @throws InvocationTargetException * @throws InstantiationException * @throws IllegalAccessException */ public static T loadElement(Element element, Class clazz) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { Field[] fields = clazz.getDeclaredFields(); T t = clazz.getDeclaredConstructor().newInstance(); for (Field field : fields) { ReflectionUtils.makeAccessible(field); MessageElement annotation = field.getAnnotation(MessageElement.class); if (annotation == null) { continue; } String value = annotation.value(); String subVal = annotation.subVal(); Element element1 = element.element(value); if (element1 == null) { continue; } if ("".equals(subVal)) { // 无下级数据 Object fieldVal = element1.isTextOnly() ? element1.getText() : loadElement(element1, field.getType()); Object o = simpleTypeDeal(field.getType(), fieldVal); ReflectionUtils.setField(field, t, o); } else { // 存在下级数据 ArrayList list = new ArrayList<>(); Type genericType = field.getGenericType(); if (!(genericType instanceof ParameterizedType)) { continue; } Class aClass = (Class) ((ParameterizedType) genericType).getActualTypeArguments()[0]; for (Element element2 : element1.elements(subVal)) { list.add(loadElement(element2, aClass)); } ReflectionUtils.setField(field, t, list); } } return t; } public static T elementDecode(Element element, Class clazz) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { Field[] fields = clazz.getDeclaredFields(); T t = clazz.getDeclaredConstructor().newInstance(); for (Field field : fields) { ReflectionUtils.makeAccessible(field); MessageElementForCatalog annotation = field.getAnnotation(MessageElementForCatalog.class); if (annotation == null) { continue; } String[] values = annotation.value(); for (String value : values) { boolean subVal = value.contains("."); if (!subVal) { Element element1 = element.element(value); if (element1 == null) { continue; } // 无下级数据 Object fieldVal = element1.isTextOnly() ? element1.getText() : loadElement(element1, field.getType()); Object o = simpleTypeDeal(field.getType(), fieldVal); ReflectionUtils.setField(field, t, o); break; } else { String[] pathArray = value.split("\\."); Element subElement = element; for (String path : pathArray) { subElement = subElement.element(path); if (subElement == null) { break; } } if (subElement == null) { continue; } Object fieldVal = subElement.isTextOnly() ? subElement.getText() : loadElement(subElement, field.getType()); Object o = simpleTypeDeal(field.getType(), fieldVal); ReflectionUtils.setField(field, t, o); } } } return t; } /** * 简单类型处理 * * @param tClass * @param val * @return */ private static Object simpleTypeDeal(Class tClass, Object val) { try { if (val == null || val.toString().equalsIgnoreCase("null")) { return null; } if (tClass.equals(String.class)) { return val.toString(); } if (tClass.equals(Integer.class)) { return Integer.valueOf(val.toString()); } if (tClass.equals(Double.class)) { return Double.valueOf(val.toString()); } if (tClass.equals(Long.class)) { return Long.valueOf(val.toString()); } return val; }catch (Exception e) { return null; } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/annotation/MsgId.java ================================================ package com.genersoft.iot.vmp.jt1078.annotation; import java.lang.annotation.*; /** * @author QingtaiJiang * @date 2023/4/27 18:31 * @email qingtaij@163.com */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface MsgId { String id(); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/JTAlarmSign.java ================================================ package com.genersoft.iot.vmp.jt1078.bean; import com.genersoft.iot.vmp.jt1078.bean.config.JTDeviceSubConfig; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; /** * 报警标志 */ @Data @Schema(description = "报警标志") public class JTAlarmSign implements JTDeviceSubConfig { @Schema(description = "紧急报警,触动报警开关后触发") private boolean urgent; @Schema(description = "超速报警") private boolean alarmSpeeding; @Schema(description = "疲劳驾驶报警") private boolean alarmTired; @Schema(description = "危险驾驶行为报警") private boolean alarmDangerous; @Schema(description = "GNSS 模块发生故障报警") private boolean alarmGnssFault; @Schema(description = "GNSS 天线未接或被剪断报警") private boolean alarmGnssBreak; @Schema(description = "GNSS 天线短路报警") private boolean alarmGnssShortCircuited; @Schema(description = "终端主电源欠压报警") private boolean alarmUnderVoltage; @Schema(description = "终端主电源掉电报警") private boolean alarmPowerOff; @Schema(description = "终端 LCD或显示器故障报警") private boolean alarmLCD; @Schema(description = "TTS 模块故障报警") private boolean alarmTtsFault; @Schema(description = "摄像头故障报警") private boolean alarmCameraFault; @Schema(description = "道路运输证 IC卡模块故障报警") private boolean alarmIcFault; @Schema(description = "超速预警") private boolean warningSpeeding; @Schema(description = "疲劳驾驶预警") private boolean warningTired; @Schema(description = "违规行驶报警") private boolean alarmWrong; @Schema(description = "胎压预警") private boolean warningTirePressure; @Schema(description = "右转盲区异常报警") private boolean alarmBlindZone; @Schema(description = "当天累计驾驶超时报警") private boolean alarmDrivingTimeout; @Schema(description = "超时停车报警") private boolean alarmParkingTimeout; @Schema(description = "进出区域报警") private boolean alarmRegion; @Schema(description = "进出路线报警") private boolean alarmRoute; @Schema(description = "路段行驶时间不足/过长报警") private boolean alarmTravelTime; @Schema(description = "路线偏离报警") private boolean alarmRouteDeviation; @Schema(description = "车辆 VSS 故障") private boolean alarmVSS; @Schema(description = "车辆油量异常报警") private boolean alarmOil; @Schema(description = "车辆被盗报警(通过车辆防盗器)") private boolean alarmStolen; @Schema(description = "车辆非法点火报警") private boolean alarmIllegalIgnition; @Schema(description = "车辆非法位移报警") private boolean alarmIllegalDisplacement; @Schema(description = "碰撞侧翻报警") private boolean alarmRollover; @Schema(description = "侧翻预警") private boolean warningRollover; public JTAlarmSign() { } public JTAlarmSign(long alarmSignInt) { if (alarmSignInt == 0) { return; } // 解析alarm参数 this.urgent = (alarmSignInt & 1) == 1; this.alarmSpeeding = (alarmSignInt >>> 1 & 1) == 1; this.alarmTired = (alarmSignInt >>> 2 & 1) == 1; this.alarmDangerous = (alarmSignInt >>> 3 & 1) == 1; this.alarmGnssFault = (alarmSignInt >>> 4 & 1) == 1; this.alarmGnssBreak = (alarmSignInt >>> 5 & 1) == 1; this.alarmGnssShortCircuited = (alarmSignInt >>> 6 & 1) == 1; this.alarmUnderVoltage = (alarmSignInt >>> 7 & 1) == 1; this.alarmPowerOff = (alarmSignInt >>> 8 & 1) == 1; this.alarmLCD = (alarmSignInt >>> 9 & 1) == 1; this.alarmTtsFault = (alarmSignInt >>> 10 & 1) == 1; this.alarmCameraFault = (alarmSignInt >>> 11 & 1) == 1; this.alarmIcFault = (alarmSignInt >>> 12 & 1) == 1; this.warningSpeeding = (alarmSignInt >>> 13 & 1) == 1; this.warningTired = (alarmSignInt >>> 14 & 1) == 1; this.alarmWrong = (alarmSignInt >>> 15 & 1) == 1; this.warningTirePressure = (alarmSignInt >>> 16 & 1) == 1; this.alarmBlindZone = (alarmSignInt >>> 17 & 1) == 1; this.alarmDrivingTimeout = (alarmSignInt >>> 18 & 1) == 1; this.alarmParkingTimeout = (alarmSignInt >>> 19 & 1) == 1; this.alarmRegion = (alarmSignInt >>> 20 & 1) == 1; this.alarmRoute = (alarmSignInt >>> 21 & 1) == 1; this.alarmTravelTime = (alarmSignInt >>> 22 & 1) == 1; this.alarmRouteDeviation = (alarmSignInt >>> 23 & 1) == 1; this.alarmVSS = (alarmSignInt >>> 24 & 1) == 1; this.alarmOil = (alarmSignInt >>> 25 & 1) == 1; this.alarmStolen = (alarmSignInt >>> 26 & 1) == 1; this.alarmIllegalIgnition = (alarmSignInt >>> 27 & 1) == 1; this.alarmIllegalDisplacement = (alarmSignInt >>> 28 & 1) == 1; this.alarmRollover = (alarmSignInt >>> 29 & 1) == 1; this.warningRollover = (alarmSignInt >>> 30 & 1) == 1; } public static JTAlarmSign decode(ByteBuf byteBuf) { long alarmSignByte = byteBuf.readUnsignedInt(); return new JTAlarmSign(alarmSignByte); } @Override public ByteBuf encode() { // 限定容量 避免影响后续占位 ByteBuf byteBuf = Unpooled.buffer(); int alarmSignValue = 0; if (urgent) { alarmSignValue = alarmSignValue | 1; } if (alarmSpeeding) { alarmSignValue = alarmSignValue | 1 << 1; } if (alarmTired) { alarmSignValue = alarmSignValue | 1 << 2; } if (alarmDangerous) { alarmSignValue = alarmSignValue | 1 << 3; } if (alarmGnssFault) { alarmSignValue = alarmSignValue | 1 << 4; } if (alarmGnssBreak) { alarmSignValue = alarmSignValue | 1 << 5; } if (alarmGnssShortCircuited) { alarmSignValue = alarmSignValue | 1 << 6; } if (alarmUnderVoltage) { alarmSignValue = alarmSignValue | 1 << 7; } if (alarmPowerOff) { alarmSignValue = alarmSignValue | 1 << 8; } if (alarmLCD) { alarmSignValue = alarmSignValue | 1 << 9; } if (alarmTtsFault) { alarmSignValue = alarmSignValue | 1 << 10; } if (alarmCameraFault) { alarmSignValue = alarmSignValue | 1 << 11; } if (alarmIcFault) { alarmSignValue = alarmSignValue | 1 << 12; } if (warningSpeeding) { alarmSignValue = alarmSignValue | 1 << 13; } if (warningTired) { alarmSignValue = alarmSignValue | 1 << 14; } if (alarmWrong) { alarmSignValue = alarmSignValue | 1 << 15; } if (warningTirePressure) { alarmSignValue = alarmSignValue | 1 << 16; } if (alarmBlindZone) { alarmSignValue = alarmSignValue | 1 << 17; } if (alarmDrivingTimeout) { alarmSignValue = alarmSignValue | 1 << 18; } if (alarmParkingTimeout) { alarmSignValue = alarmSignValue | 1 << 19; } if (alarmRegion) { alarmSignValue = alarmSignValue | 1 << 20; } if (alarmRoute) { alarmSignValue = alarmSignValue | 1 << 21; } if (alarmTravelTime) { alarmSignValue = alarmSignValue | 1 << 22; } if (alarmRouteDeviation) { alarmSignValue = alarmSignValue | 1 << 23; } if (alarmVSS) { alarmSignValue = alarmSignValue | 1 << 24; } if (alarmOil) { alarmSignValue = alarmSignValue | 1 << 25; } if (alarmStolen) { alarmSignValue = alarmSignValue | 1 << 26; } if (alarmIllegalIgnition) { alarmSignValue = alarmSignValue | 1 << 27; } if (alarmIllegalDisplacement) { alarmSignValue = alarmSignValue | 1 << 28; } if (alarmRollover) { alarmSignValue = alarmSignValue | 1 << 29; } if (warningRollover) { alarmSignValue = alarmSignValue | 1 << 30; } byteBuf.writeInt(alarmSignValue); return byteBuf; } @Override public String toString() { return "状态报警标志位:" + "\n 紧急报警:" + (urgent?"开":"关") + "\n 超速报警:" + (alarmSpeeding?"开":"关") + "\n 疲劳驾驶报警:" + (alarmTired?"开":"关") + "\n 危险驾驶行为报警:" + (alarmDangerous?"开":"关") + "\n GNSS 模块发生故障报警:" + (alarmGnssFault?"开":"关") + "\n GNSS 天线未接或被剪断报警:" + (alarmGnssBreak?"开":"关") + "\n GNSS 天线短路报警:" + (alarmGnssShortCircuited?"开":"关") + "\n 终端主电源欠压报警:" + (alarmUnderVoltage?"开":"关") + "\n 终端主电源掉电报警:" + (alarmPowerOff?"开":"关") + "\n 终端LCD或显示器故障报警:" + (alarmLCD?"开":"关") + "\n TTS 模块故障报警:" + (alarmTtsFault?"开":"关") + "\n 摄像头故障报警:" + (alarmCameraFault?"开":"关") + "\n 道路运输证IC卡模块故障报警:" + (alarmIcFault?"开":"关") + "\n 超速预警:" + (warningSpeeding?"开":"关") + "\n 疲劳驾驶预警:" + (warningTired?"开":"关") + "\n 违规行驶报警:" + (alarmWrong ?"开":"关") + "\n 胎压预警:" + (warningTirePressure?"开":"关") + "\n 右转盲区异常报警:" + (alarmBlindZone?"开":"关") + "\n 当天累计驾驶超时报警:" + (alarmDrivingTimeout?"开":"关") + "\n 超时停车报警:" + (alarmParkingTimeout?"开":"关") + "\n 进出区域报警:" + (alarmRegion?"开":"关") + "\n 进出路线报警:" + (alarmRoute?"开":"关") + "\n 路段行驶时间不足/过长报警:" + (alarmTravelTime?"开":"关") + "\n 路线偏离报警:" + (alarmRouteDeviation?"开":"关") + "\n 车辆 VSS 故障:" + (alarmVSS?"开":"关") + "\n 车辆油量异常报警:" + (alarmOil?"开":"关") + "\n 车辆被盗报警(通过车辆防盗器):" + (alarmStolen?"开":"关") + "\n 车辆非法点火报警:" + (alarmIllegalIgnition?"开":"关") + "\n 车辆非法位移报警:" + (alarmIllegalDisplacement?"开":"关") + "\n 碰撞侧翻报警:" + (alarmRollover?"开":"关") + "\n 侧翻预警:" + (warningRollover?"开":"关") + "\n "; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/JTAreaAttribute.java ================================================ package com.genersoft.iot.vmp.jt1078.bean; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.Setter; @Setter @Getter @Schema(description = "区域属性") public class JTAreaAttribute { @Schema(description = "是否启用起始时间与结束时间的判断规则 ,false:否;true:是") private boolean ruleForTimeLimit; @Schema(description = "是否启用最高速度、超速持续时间和夜间最高速度的判断规则 ,false:否;true:是") private boolean ruleForSpeedLimit; @Schema(description = "进区域是否报警给驾驶员,false:否;true:是") private boolean ruleForAlarmToDriverWhenEnter; @Schema(description = "进区域是否报警给平台 ,false:否;true:是") private boolean ruleForAlarmToPlatformWhenEnter; @Schema(description = "出区域是否报警给驾驶员,false:否;true:是") private boolean ruleForAlarmToDriverWhenExit; @Schema(description = "出区域是否报警给平台 ,false:否;true:是") private boolean ruleForAlarmToPlatformWhenExit; @Schema(description = "false:北纬;true:南纬") private boolean southLatitude; @Schema(description = "false:东经;true:西经") private boolean westLongitude; @Schema(description = "false:允许开门;true:禁止开门") private boolean prohibitOpeningDoors; @Schema(description = "false:进区域开启通信模块;true:进区域关闭通信模块") private boolean ruleForTurnOffCommunicationWhenEnter; @Schema(description = "false:进区域不采集 GNSS 详细定位数据;true:进区域采集 GNSS 详细定位数据") private boolean ruleForGnssWhenEnter; public ByteBuf encode(){ ByteBuf byteBuf = Unpooled.buffer(); short content = 0 ; if (ruleForTimeLimit) { content |= 1; } if (ruleForSpeedLimit) { content |= (1 << 1); } if (ruleForAlarmToDriverWhenEnter) { content |= (1 << 2); } if (ruleForAlarmToPlatformWhenEnter) { content |= (1 << 3); } if (ruleForAlarmToDriverWhenExit) { content |= (1 << 4); } if (ruleForAlarmToPlatformWhenExit) { content |= (1 << 5); } if (southLatitude) { content |= (1 << 6); } if (westLongitude) { content |= (byte) (1 << 7); } if (prohibitOpeningDoors) { content |= (1 << (0 + 8)); } if (ruleForTurnOffCommunicationWhenEnter) { content |= (1 << (1 + 8)); } if (ruleForGnssWhenEnter) { content |= (1 << (2 + 8)); } byteBuf.writeShort((short)(content & 0xffff)); return byteBuf; } public static JTAreaAttribute decode(int attributeInt) { JTAreaAttribute attribute = new JTAreaAttribute(); attribute.setRuleForTimeLimit((attributeInt & 1) == 1); attribute.setRuleForSpeedLimit((attributeInt >> 1 & 1) == 1); attribute.setRuleForAlarmToDriverWhenEnter((attributeInt >> 2 & 1) == 1); attribute.setRuleForAlarmToPlatformWhenEnter((attributeInt >> 3 & 1) == 1); attribute.setRuleForAlarmToDriverWhenExit((attributeInt >> 4 & 1) == 1); attribute.setRuleForAlarmToPlatformWhenExit((attributeInt >> 5 & 1) == 1); attribute.setSouthLatitude((attributeInt >> 6 & 1) == 1); attribute.setWestLongitude((attributeInt >> 7 & 1) == 1); attribute.setProhibitOpeningDoors((attributeInt >> 8 & 1) == 1); attribute.setRuleForTurnOffCommunicationWhenEnter((attributeInt >> 9 & 1) == 1); attribute.setRuleForGnssWhenEnter((attributeInt >> 10 & 1) == 1); return attribute; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/JTAreaOrRoute.java ================================================ package com.genersoft.iot.vmp.jt1078.bean; public interface JTAreaOrRoute { } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/JTChannel.java ================================================ package com.genersoft.iot.vmp.jt1078.bean; import com.genersoft.iot.vmp.common.enums.ChannelDataType; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import org.springframework.util.ObjectUtils; /** * JT 通道 */ @Data @Schema(description = "jt808通道") @EqualsAndHashCode(callSuper = true) public class JTChannel extends CommonGBChannel { @Schema(description = "数据库自增ID") private int id; @Schema(description = "名称") private String name; @Schema(description = "设备的数据库ID") private int terminalDbId; @Schema(description = "通道ID") private Integer channelId; @Schema(description = "是否含有音频") private boolean hasAudio; @Schema(description = "创建时间") private String createTime; @Schema(description = "更新时间") private String updateTime; @Schema(description = "流信息") private String stream; private Integer dataType = ChannelDataType.JT_1078; @Override public String toString() { return "JTChannel{" + "id=" + id + ", name='" + name + '\'' + ", terminalDbId=" + terminalDbId + ", channelId=" + channelId + ", createTime='" + createTime + '\'' + ", updateTime='" + updateTime + '\'' + ", hasAudio='" + hasAudio + '\'' + '}'; } public CommonGBChannel buildCommonGBChannel() { if (ObjectUtils.isEmpty(this.getGbDeviceId())) { return null; } if (ObjectUtils.isEmpty(this.getGbName())) { this.setGbName(this.getName()); } return this; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/JTCircleArea.java ================================================ package com.genersoft.iot.vmp.jt1078.bean; import com.genersoft.iot.vmp.jt1078.util.BCDUtil; import com.genersoft.iot.vmp.utils.DateUtil; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.Setter; import java.nio.charset.Charset; import java.util.Date; @Setter @Getter @Schema(description = "圆形区域") public class JTCircleArea implements JTAreaOrRoute{ @Schema(description = "区域 ID") private long id; @Schema(description = "") private JTAreaAttribute attribute; @Schema(description = "中心点纬度") private Double latitude; @Schema(description = "中心点经度") private Double longitude; @Schema(description = "半径,单位为米(m)") private long radius; @Schema(description = "起始时间, yyyy-MM-dd HH:mm:ss") private String startTime; @Schema(description = "结束时间, yyyy-MM-dd HH:mm:ss") private String endTime; @Schema(description = "最高速度, 单位为千米每小时(km/h)") private int maxSpeed; @Schema(description = "超速持续时间, 单位为秒(s)") private int overSpeedDuration; @Schema(description = "夜间最高速度, 单位为千米每小时(km/h)") private int nighttimeMaxSpeed; @Schema(description = "区域的名称") private String name; public ByteBuf encode(){ ByteBuf byteBuf = Unpooled.buffer(); byteBuf.writeInt((int) (id & 0xffffffffL)); byteBuf.writeBytes(attribute.encode()); byteBuf.writeInt((int) (Math.round((latitude * 1000000)) & 0xffffffffL)); byteBuf.writeInt((int) (Math.round((longitude * 1000000)) & 0xffffffffL)); byteBuf.writeInt((int) (radius & 0xffffffffL)); byteBuf.writeBytes(BCDUtil.strToBcd(DateUtil.yyyy_MM_dd_HH_mm_ssTo1078(startTime))); byteBuf.writeBytes(BCDUtil.strToBcd(DateUtil.yyyy_MM_dd_HH_mm_ssTo1078(endTime))); byteBuf.writeShort((short)(maxSpeed & 0xffff)); byteBuf.writeByte(overSpeedDuration); byteBuf.writeShort((short)(nighttimeMaxSpeed & 0xffff)); byteBuf.writeShort((short)(name.getBytes(Charset.forName("GBK")).length & 0xffff)); byteBuf.writeCharSequence(name, Charset.forName("GBK")); return byteBuf; } public static JTCircleArea decode(ByteBuf buf) { JTCircleArea area = new JTCircleArea(); area.setId(buf.readUnsignedInt()); int attributeInt = buf.readUnsignedShort(); JTAreaAttribute areaAttribute = JTAreaAttribute.decode(attributeInt); area.setAttribute(areaAttribute); area.setLatitude(buf.readUnsignedInt()/1000000D); area.setLongitude(buf.readUnsignedInt()/1000000D); area.setRadius(buf.readUnsignedInt()); byte[] startTimeBytes = new byte[6]; buf.readBytes(startTimeBytes); area.setStartTime(DateUtil.jt1078Toyyyy_MM_dd_HH_mm_ss(BCDUtil.transform(startTimeBytes))); byte[] endTimeBytes = new byte[6]; buf.readBytes(endTimeBytes); area.setEndTime(DateUtil.jt1078Toyyyy_MM_dd_HH_mm_ss(BCDUtil.transform(endTimeBytes))); area.setMaxSpeed(buf.readUnsignedShort()); area.setOverSpeedDuration(buf.readUnsignedByte()); area.setNighttimeMaxSpeed(buf.readUnsignedShort()); int nameLength = buf.readUnsignedShort(); area.setName(buf.readCharSequence(nameLength, Charset.forName("GBK")).toString().trim()); return area; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/JTCommunicationModuleAttribute.java ================================================ package com.genersoft.iot.vmp.jt1078.bean; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.Setter; /** * JT 通信模块属性 */ @Setter @Getter @Schema(description = "JT通信模块属性") public class JTCommunicationModuleAttribute { private boolean gprs ; private boolean cdma ; private boolean tdScdma ; private boolean wcdma ; private boolean cdma2000 ; private boolean tdLte ; private boolean other ; public static JTCommunicationModuleAttribute getInstance(short content) { boolean gprs = (content & 1) == 1; boolean cdma = (content >>> 1 & 1) == 1; boolean tdScdma = (content >>> 2 & 1) == 1; boolean wcdma = (content >>> 3 & 1) == 1; boolean cdma2000 = (content >>> 4 & 1) == 1; boolean tdLte = (content >>> 5 & 1) == 1; boolean other = (content >>> 7 & 1) == 1; return new JTCommunicationModuleAttribute(gprs, cdma, tdScdma, wcdma, cdma2000, tdLte, other); } public JTCommunicationModuleAttribute(boolean gprs, boolean cdma, boolean tdScdma, boolean wcdma, boolean cdma2000, boolean tdLte, boolean other) { this.gprs = gprs; this.cdma = cdma; this.tdScdma = tdScdma; this.wcdma = wcdma; this.cdma2000 = cdma2000; this.tdLte = tdLte; this.other = other; } @Override public String toString() { return "JCommunicationModuleAttribute{" + "gprs=" + gprs + ", cdma=" + cdma + ", tdScdma=" + tdScdma + ", wcdma=" + wcdma + ", cdma2000=" + cdma2000 + ", tdLte=" + tdLte + ", other=" + other + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/JTConfirmationAlarmMessageType.java ================================================ package com.genersoft.iot.vmp.jt1078.bean; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.Setter; @Setter @Getter @Schema(description = "人工确认报警类型") public class JTConfirmationAlarmMessageType { @Schema(description = "确认紧急报警") private boolean urgent; @Schema(description = "确认危险预警") private boolean alarmDangerous; @Schema(description = "确认进出区域报警") private boolean alarmRegion; @Schema(description = "确认进出路线报警") private boolean alarmRoute; @Schema(description = "确认路段行驶时间不足/过长报警") private boolean alarmTravelTime; @Schema(description = "确认车辆非法点火报警") private boolean alarmIllegalIgnition; @Schema(description = "确认车辆非法位移报警") private boolean alarmIllegalDisplacement; public long encode(){ long result = 0L; if (urgent) { result |= 0x01; } if (alarmDangerous) { result |= (0x01 << 3); } if (alarmRegion) { result |= (0x01 << 20); } if (alarmRoute) { result |= (0x01 << 21); } if (alarmTravelTime) { result |= (0x01 << 22); } if (alarmIllegalIgnition) { result |= (0x01 << 27); } if (alarmIllegalDisplacement) { result |= (0x01 << 28); } return result; } @Override public String toString() { return "JConfirmationAlarmMessageType{" + "urgent=" + urgent + ", alarmDangerous=" + alarmDangerous + ", alarmRegion=" + alarmRegion + ", alarmRoute=" + alarmRoute + ", alarmTravelTime=" + alarmTravelTime + ", alarmIllegalIgnition=" + alarmIllegalIgnition + ", alarmIllegalDisplacement=" + alarmIllegalDisplacement + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/JTDevice.java ================================================ package com.genersoft.iot.vmp.jt1078.bean; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; /** * JT 设备 */ @Data @Schema(description = "jt808设备") public class JTDevice { private int id; @Schema(description = "省域ID") private String provinceId; @Schema(description = "省域文字描述") private String provinceText; @Schema(description = "市县域ID") private String cityId; @Schema(description = "市县域文字描述") private String cityText; @Schema(description = "制造商ID") private String makerId; @Schema(description = "终端型号") private String model; @Schema(description = "终端手机号") private String phoneNumber; @Schema(description = "终端ID") private String terminalId; @Schema(description = "车牌颜色") private int plateColor; @Schema(description = "车牌") private String plateNo; @Schema(description = "经度") private Double longitude; @Schema(description = "纬度") private Double latitude; @Schema(description = "注册时间") private String registerTime; @Schema(description = "创建时间") private String createTime; @Schema(description = "更新时间") private String updateTime; @Schema(description = "状态") private boolean status; @Schema(description = "设备使用的媒体id, 默认为null") private String mediaServerId; @Schema(description = "地理坐标系, 目前支持 WGS84,GCJ02") private String geoCoordSys; @Schema(description = "收流IP") private String sdpIp; @Override public String toString() { return "JTDevice{" + " 终端手机号='" + phoneNumber + '\'' + ", 省域ID='" + provinceId + '\'' + ", 省域文字描述='" + provinceText + '\'' + ", 市县域ID='" + cityId + '\'' + ", 市县域文字描述='" + cityText + '\'' + ", 制造商ID='" + makerId + '\'' + ", 终端型号='" + model + '\'' + ", 设备ID='" + terminalId + '\'' + ", 车牌颜色=" + plateColor + ", 车牌='" + plateNo + '\'' + ", 注册时间='" + registerTime + '\'' + ", status=" + status + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/JTDeviceAttribute.java ================================================ package com.genersoft.iot.vmp.jt1078.bean; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.Setter; /** * JT 终端属性 */ @Setter @Getter @Schema(description = "JT终端属性") public class JTDeviceAttribute { @Schema(description = "终端类型") private JTDeviceType type; @Schema(description = "制造商 ID") private String makerId; @Schema(description = "终端型号") private String deviceModel; @Schema(description = "终端 ID") private String terminalId; @Schema(description = "终端 SIM卡 ICCID") private String iccId; @Schema(description = "终端硬件版本号") private String hardwareVersion; @Schema(description = "固件版本号") private String firmwareVersion ; @Schema(description = "GNSS 模块属性") private JTGnssAttribute gnssAttribute ; @Schema(description = "通信模块属性") private JTCommunicationModuleAttribute communicationModuleAttribute ; @Override public String toString() { return "JTDeviceAttribute{" + "type=" + type + ", makerId='" + makerId + '\'' + ", deviceModel='" + deviceModel + '\'' + ", terminalId='" + terminalId + '\'' + ", iccId='" + iccId + '\'' + ", hardwareVersion='" + hardwareVersion + '\'' + ", firmwareVersion='" + firmwareVersion + '\'' + ", gnssAttribute=" + gnssAttribute + ", communicationModuleAttribute=" + communicationModuleAttribute + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/JTDeviceConfig.java ================================================ package com.genersoft.iot.vmp.jt1078.bean; import com.genersoft.iot.vmp.jt1078.bean.common.ConfigAttribute; import com.genersoft.iot.vmp.jt1078.bean.config.*; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; /** * JT 终端参数设置 */ @Schema(description = "JT终端参数设置") @Data public class JTDeviceConfig { @ConfigAttribute(id = 0x1, type="Long", description = "终端心跳发送间隔,单位为秒(s)") private Long keepaliveInterval; @ConfigAttribute(id = 0x2, type="Long", description = "TCP消息应答超时时间,单位为秒(s)") private Long tcpResponseTimeout; @ConfigAttribute(id = 0x3, type="Long", description = "TCP消息重传次数") private Long tcpRetransmissionCount; @ConfigAttribute(id = 0x4, type="Long", description = "UDP消息应答超时时间,单位为秒(s)") private Long udpResponseTimeout; @ConfigAttribute(id = 0x5, type="Long", description = "UDP消息重传次数") private Long udpRetransmissionCount; @ConfigAttribute(id = 0x6, type="Long", description = "SMS 消息应答超时时间,单位为秒(s)") private Long smsResponseTimeout; @ConfigAttribute(id = 0x7, type="Long", description = "SMS 消息重传次数") private Long smsRetransmissionCount; @ConfigAttribute(id = 0x10, type="String", description = "主服务器APN无线通信拨号访问点,若网络制式为 CDMA,则该处 为 PPP拨号号码") private String apnMaster; @ConfigAttribute(id = 0x11, type="String", description = "主服务器无线通信拨号用户名") private String dialingUsernameMaster; @ConfigAttribute(id = 0x12, type="String", description = "主服务器无线通信拨号密码") private String dialingPasswordMaster; @ConfigAttribute(id = 0x13, type="String", description = "主服务器地址IP或域名,以冒号分割主机和端口 多个服务器使用分号分割") private String addressMaster; @ConfigAttribute(id = 0x14, type="String", description = "备份服务器APN") private String apnBackup; @ConfigAttribute(id = 0x15, type="String", description = "备份服务器无线通信拨号用户名") private String dialingUsernameBackup; @ConfigAttribute(id = 0x16, type="String", description = "备份服务器无线通信拨号密码") private String dialingPasswordBackup; @ConfigAttribute(id = 0x17, type="String", description = "备用服务器备份地址IP或域名,以冒号分割主机和端口 多个服务器使用分号分割") private String addressBackup; @ConfigAttribute(id = 0x1a, type="String", description = "道路运输证IC卡认证主服务器IP地址或域名") private String addressIcMaster; @ConfigAttribute(id = 0x1b, type="Long", description = "道路运输证IC卡认证主服务器TCP端口") private Long tcpPortIcMaster; @ConfigAttribute(id = 0x1c, type="Long", description = "道路运输证IC卡认证主服务器UDP端口") private Long udpPortIcMaster; @ConfigAttribute(id = 0x1d, type="String", description = "道路运输证IC卡认证备份服务器IP地址或域名,端口同主服务器") private String addressIcBackup; @ConfigAttribute(id = 0x20, type="Long", description = "位置汇报策略, 0定时汇报 1定距汇报 2定时和定距汇报") private Long locationReportingStrategy; @ConfigAttribute(id = 0x21, type="Long", description = "位置汇报方案,0根据ACC状态 1根据登录状态和ACC状态,先判断登录状态,若登录再根据ACC状态") private Long locationReportingPlan; @ConfigAttribute(id = 0x22, type="Long", description = "驾驶员未登录汇报时间间隔,单位为秒,值大于零") private Long reportingIntervalOffline; @ConfigAttribute(id = 0x23, type="String", description = "从服务器 APN# 该值为空时 !终端应使用主服务器相同配置") private String apnSlave; @ConfigAttribute(id = 0x24, type="String", description = "从服务器无线通信拨号用户名 # 该值为空时 !终端应使用主服务器 相同配置") private String dialingUsernameSlave; @ConfigAttribute(id = 0x25, type="String", description = "从服务器无线通信拨号密码 # 该值为空时 !终端应使用主服务器相 同配置") private String dialingPasswordSlave; @ConfigAttribute(id = 0x26, type="String", description = "从服务器备份地址 IP或域名 !主机和端口用冒号分割 !多个服务器 使用分号分割") private String addressSlave; @ConfigAttribute(id = 0x27, type="Long", description = "休眠时汇报时间间隔 单位为秒 值大于0") private Long reportingIntervalDormancy; @ConfigAttribute(id = 0x28, type="Long", description = "紧急报警时汇报时间间隔 单位为秒 值大于0") private Long reportingIntervalEmergencyAlarm; @ConfigAttribute(id = 0x29, type="Long", description = "缺省时间汇报间隔 单位为秒 值大于0") private Long reportingIntervalDefault; @ConfigAttribute(id = 0x2c, type="Long", description = "缺省距离汇报间隔 单位为米 值大于0") private Long reportingDistanceDefault; @ConfigAttribute(id = 0x2d, type="Long", description = "驾驶员未登录汇报距离间隔 单位为米 值大于0") private Long reportingDistanceOffline; @ConfigAttribute(id = 0x2e, type="Long", description = "休眠时汇报距离间隔 单位为米 值大于0") private Long reportingDistanceDormancy; @ConfigAttribute(id = 0x2f, type="Long", description = "紧急报警时汇报距离间隔 单位为米 值大于0") private Long reportingDistanceEmergencyAlarm; @ConfigAttribute(id = 0x30, type="Long", description = "拐点补传角度 ,值小于180") private Long inflectionPointAngle; @ConfigAttribute(id = 0x31, type="Integer", description = "电子围栏半径(非法位移國值) ,单位为米(m)") private Integer fenceRadius; @ConfigAttribute(id = 0x32, type="IllegalDrivingPeriods", description = "违规行驶时段范围 ,精确到分") private JTIllegalDrivingPeriods illegalDrivingPeriods; @ConfigAttribute(id = 0x40, type="String", description = "监控平台电话号码") private String platformPhoneNumber; @ConfigAttribute(id = 0x41, type="String", description = "复位电话号码 ,可采用此电话号码拨打终端电话让终端复位") private String phoneNumberForReset; @ConfigAttribute(id = 0x42, type="String", description = "恢复出厂设置电话号码 ,可采用此电话号码拨打终端电话让终端恢 复出厂设置") private String phoneNumberForFactoryReset; @ConfigAttribute(id = 0x42, type="String", description = "监控平台 SMS 电话号码") private String phoneNumberForSms; @ConfigAttribute(id = 0x44, type="String", description = "接收终端 SMS 文本报警号码") private String phoneNumberForReceiveTextAlarm; @ConfigAttribute(id = 0x45, type="Long", description = "终端电话接听策略 。0:自动接听;1:ACC ON时自动接听 ,OFF时手动接听") private Long phoneAnsweringPolicy; @ConfigAttribute(id = 0x46, type="Long", description = "每次最长通话时间 ,单位为秒(s) ,0 为不允许通话 ,0xFFFFFFFF为不限制") private Long longestCallTimeForPerSession; @ConfigAttribute(id = 0x47, type="Long", description = "当月最长通话时间 ,单位为秒(s) ,0 为不允许通话 ,0xFFFFFFFF为 不限制") private Long longestCallTimeInMonth; @ConfigAttribute(id = 0x48, type="String", description = "监听电话号码") private String phoneNumbersForListen; @ConfigAttribute(id = 0x49, type="String", description = "监管平台特权短信号码") private String privilegedSMSNumber; @ConfigAttribute(id = 0x50, type="AlarmSign", description = "报警屏蔽字 ,与位置信息汇报消息中的报警标志相对应 ,相应位为 1 则相应报警被屏蔽") private JTAlarmSign alarmMaskingWord; @ConfigAttribute(id = 0x51, type="AlarmSign", description = "报警发送文本 SMS 开关 , 与位置信息汇报消息中的报警标志相对 应 ,相应位为1 则相应报警时发送文本 SMS") private JTAlarmSign alarmSendsTextSmsSwitch; @ConfigAttribute(id = 0x52, type="AlarmSign", description = "报警拍摄开关 ,与位置信息汇报消息中的报警标志相对应 ,相应位为 1 则相应报警时摄像头拍摄") private JTAlarmSign alarmShootingSwitch; @ConfigAttribute(id = 0x53, type="AlarmSign", description = "报警拍摄存储标志 ,与位置信息汇报消息中的报警标志相对应 ,相应 位为1 则对相应报警时拍的照片进行存储 ,否则实时上传") private JTAlarmSign alarmShootingStorageFlags; @ConfigAttribute(id = 0x54, type="AlarmSign", description = "关键标志 ,与位置信息汇报消息中的报警标志相对应 ,相应位为 1 则 对相应报警为关键报警") private JTAlarmSign KeySign; @ConfigAttribute(id = 0x55, type="Long", description = "最高速度 ,单位为千米每小时(km/h)") private Long maxSpeed; @ConfigAttribute(id = 0x56, type="Long", description = "超速持续时间 ,单位为秒(s)") private Long overSpeedDuration; @ConfigAttribute(id = 0x57, type="Long", description = "连续驾驶时间门限 单位为秒(s)") private Long continuousDrivingTimeThreshold; @ConfigAttribute(id = 0x58, type="Long", description = "当天累计驾驶时间门限 单位为秒(s)") private Long cumulativeDrivingTimeThresholdForTheDay; @ConfigAttribute(id = 0x59, type="Long", description = "最小休息时间 单位为秒(s)") private Long minimumBreakTime; @ConfigAttribute(id = 0x5a, type="Long", description = "最长停车时间 单位为秒(s)") private Long maximumParkingTime; @ConfigAttribute(id = 0x5b, type="Integer", description = "超速预警差值 单位为1/10 千米每小时(1/10km/h)") private Integer overSpeedWarningDifference; @ConfigAttribute(id = 0x5c, type="Integer", description = "疲劳驾驶预警差值 单位为秒 值大于零") private Integer drowsyDrivingWarningDifference; @ConfigAttribute(id = 0x5d, type="CollisionAlarmParams", description = "碰撞报警参数设置") private JTCollisionAlarmParams collisionAlarmParams; @ConfigAttribute(id = 0x5e, type="Integer", description = "侧翻报警参数设置:侧翻角度,单位为度,默认为30") private Integer rolloverAlarm; @ConfigAttribute(id = 0x64, type="CameraTimer", description = "定时拍照控制") private JTCameraTimer cameraTimer; @ConfigAttribute(id = 0x70, type="Long", description = "图像/视频质量 设置范围为1~10 1表示最优质量") private Long qualityForVideo; @ConfigAttribute(id = 0x71, type="Long", description = "亮度,设置范围为0 ~ 255") private Long brightness; @ConfigAttribute(id = 0x72, type="Long", description = "对比度,设置范围为0 ~ 127") private Long contrastRatio; @ConfigAttribute(id = 0x73, type="Long", description = "饱和度,设置范围为0 ~ 127") private Long saturation; @ConfigAttribute(id = 0x74, type="Long", description = "色度,设置范围为0 ~ 255") private Long chroma; @ConfigAttribute(id = 0x75, type="VideoParam", description = "音视频参数设置") private JTVideoParam videoParam; @ConfigAttribute(id = 0x76, type="ChannelListParam", description = "音视频通道列表设置") private JTChannelListParam channelListParam; @ConfigAttribute(id = 0x77, type="ChannelParam", description = "单独视频通道参数设置") private JTChannelParam channelParam; @ConfigAttribute(id = 0x79, type="AlarmRecordingParam", description = "特殊报警录像参数设置") private JTAlarmRecordingParam alarmRecordingParam; @ConfigAttribute(id = 0x7a, type="VideoAlarmBit", description = "视频相关报警屏蔽字") private JTVideoAlarmBit videoAlarmBit; @ConfigAttribute(id = 0x7b, type="AnalyzeAlarmParam", description = "图像分析报警参数设置") private JTAnalyzeAlarmParam analyzeAlarmParam; @ConfigAttribute(id = 0x7c, type="AwakenParam", description = "终端休眠唤醒模式设置") private JTAwakenParam awakenParam; @ConfigAttribute(id = 0x80, type="Long", description = "车辆里程表读数,单位'1/10km") private Long mileage; @ConfigAttribute(id = 0x81, type="Integer", description = "车辆所在的省域ID") private Integer provincialId; @ConfigAttribute(id = 0x82, type="Integer", description = "车辆所在的市域ID") private Integer cityId; @ConfigAttribute(id = 0x83, type="String", description = "公安交通管理部门颁发的机动车号牌") private String licensePlate; @ConfigAttribute(id = 0x84, type="Short", description = "车牌颜色,值按照JT/T697-7.2014中的规定,未上牌车辆填0") private Short licensePlateColor; @ConfigAttribute(id = 0x90, type="GnssPositioningMode", description = "GNSS定位模式") private JTGnssPositioningMode gnssPositioningMode; @ConfigAttribute(id = 0x91, type="Short", description = "GNSS 波特率,定义如下: 0: 4800, 1:9600, 2:19200, 3:38400, 4:57600, 5:115200") private Short gnssBaudRate; @ConfigAttribute(id = 0x92, type="Short", description = "GNSS 模块详细定位数据输出频率,定义如下: 0: 500ms, 1:1000ms(默认值), 2:2000ms, 3:3000ms, 4:4000ms") private Short gnssOutputFrequency; @ConfigAttribute(id = 0x93, type="Long", description = "GNSS 模块详细定位数据采集频率 ,单位为秒(s) ,默认为1") private Long gnssCollectionFrequency; @ConfigAttribute(id = 0x94, type="Short", description = "GNSS 模块详细定位数据上传方式:,定义如下: " + "0: 本地存储 ,不上传(默认值) , " + "1:按时间间隔上传, " + "2:按距离间隔上传, " + "11:按累计时间上传 ,达到传输时间后自动停止上传, " + "12:按累计距离上传 ,达到距离后自动停止上传, " + "13:按累计条数上传 ,达到上传条数后自动停止上传") private Short gnssDataUploadMethod; @ConfigAttribute(id = 0x95, type="Long", description = "GNSS 模块详细定位数据上传设置:,定义如下: " + "1:单位为秒(s), " + "2:单位为米(m) , " + "11:单位为 秒(s), " + "12:单位为米(m), " + "13:单位 为条") private Long gnssDataUploadMethodUnit; @ConfigAttribute(id = 0x100, type="Long", description = "CAN总线通道1 采集时间间隔 ,单位为毫秒(ms) ,0 表示不采集") private Long canCollectionTimeForChannel1; @ConfigAttribute(id = 0x101, type="Integer", description = "CAN总线通道1 上传时间间隔 ,单位为秒(s) ,0 表示不上传") private Integer canUploadIntervalForChannel1; @ConfigAttribute(id = 0x102, type="Long", description = "CAN总线通道2 采集时间间隔 ,单位为毫秒(ms) ,0 表示不采集") private Long canCollectionTimeForChannel2; @ConfigAttribute(id = 0x103, type="Integer", description = "CAN总线通道2 上传时间间隔 ,单位为秒(s) ,0 表示不上传") private Integer canUploadIntervalForChannel2; @Override public String toString() { return "JTDeviceConfig{" + "终端心跳发送间隔: " + keepaliveInterval + "秒" + ", TCP消息应答超时时间:" + tcpResponseTimeout + "秒" + ", TCP消息重传次数: " + tcpRetransmissionCount + "秒" + ", UDP消息应答超时时间: " + udpResponseTimeout + ", UDP消息重传次数: " + udpRetransmissionCount + ", SMS 消息应答超时时间: " + smsResponseTimeout + "秒" + ", SMS 消息重传次数: " + smsRetransmissionCount + ", 主服务器APN无线通信拨号访问点: " + apnMaster + '\'' + ", 主服务器无线通信拨号用户名: " + dialingUsernameMaster + ", 主服务器无线通信拨号密码: " + dialingPasswordMaster + ", 主服务器地址IP或域名: " + addressMaster + ", 备份服务器APN: " + apnBackup + ", 备份服务器无线通信拨号用户名: " + dialingUsernameBackup + ", 备份服务器无线通信拨号密码: " + dialingPasswordBackup + ", 备用服务器备份地址IP或域名: " + addressBackup + ", 道路运输证IC卡认证主服务器IP地址或域名: " + addressIcMaster + ", 道路运输证IC卡认证主服务器TCP端口: " + tcpPortIcMaster + ", 道路运输证IC卡认证主服务器UDP端口: " + udpPortIcMaster + ", 道路运输证IC卡认证备份服务器IP地址或域名: " + addressIcBackup + ", 位置汇报策略: " + locationReportingStrategy + ", 位置汇报方案: " + locationReportingPlan + ", 驾驶员未登录汇报时间间隔: " + reportingIntervalOffline + "秒" + ", 从服务器 APN: " + apnSlave + ", 从服务器无线通信拨号密码: " + dialingUsernameSlave + ", 从服务器备份地址 IP或域名: " + dialingPasswordSlave + ", 从服务器备份地址 IP或域名: " + addressSlave + ", reportingIntervalDormancy: " + reportingIntervalDormancy + ", reportingIntervalEmergencyAlarm: " + reportingIntervalEmergencyAlarm + ", reportingIntervalDefault: " + reportingIntervalDefault + ", reportingDistanceDefault: " + reportingDistanceDefault + ", reportingDistanceOffline: " + reportingDistanceOffline + ", reportingDistanceDormancy: " + reportingDistanceDormancy + ", reportingDistanceEmergencyAlarm: " + reportingDistanceEmergencyAlarm + ", inflectionPointAngle: " + inflectionPointAngle + ", fenceRadius: " + fenceRadius + ", illegalDrivingPeriods: " + illegalDrivingPeriods + ", platformPhoneNumber: " + platformPhoneNumber + ", phoneNumberForReset: " + phoneNumberForReset + ", phoneNumberForFactoryReset: " + phoneNumberForFactoryReset + ", phoneNumberForSms: " + phoneNumberForSms + ", phoneNumberForReceiveTextAlarm: " + phoneNumberForReceiveTextAlarm + ", phoneAnsweringPolicy: " + phoneAnsweringPolicy + ", longestCallTimeForPerSession: " + longestCallTimeForPerSession + ", longestCallTimeInMonth: " + longestCallTimeInMonth + ", phoneNumbersForListen: " + phoneNumbersForListen + ", privilegedSMSNumber: " + privilegedSMSNumber + ", alarmMaskingWord: " + alarmMaskingWord + ", alarmSendsTextSmsSwitch: " + alarmSendsTextSmsSwitch + ", alarmShootingSwitch: " + alarmShootingSwitch + ", alarmShootingStorageFlags: " + alarmShootingStorageFlags + ", KeySign: " + KeySign + ", topSpeed: " + maxSpeed + ", overSpeedDuration: " + overSpeedDuration + ", continuousDrivingTimeThreshold: " + continuousDrivingTimeThreshold + ", cumulativeDrivingTimeThresholdForTheDay: " + cumulativeDrivingTimeThresholdForTheDay + ", minimumBreakTime: " + minimumBreakTime + ", maximumParkingTime: " + maximumParkingTime + ", overSpeedWarningDifference: " + overSpeedWarningDifference + ", drowsyDrivingWarningDifference: " + drowsyDrivingWarningDifference + ", collisionAlarmParams: " + collisionAlarmParams + ", rolloverAlarm: " + rolloverAlarm + ", cameraTimer: " + cameraTimer + ", qualityForVideo: " + qualityForVideo + ", brightness: " + brightness + ", contrastRatio: " + contrastRatio + ", saturation: " + saturation + ", chroma: " + chroma + ", mileage: " + mileage + ", provincialId: " + provincialId + ", cityId: " + cityId + ", licensePlate: " + licensePlate + ", licensePlateColor: " + licensePlateColor + ", gnssPositioningMode: " + gnssPositioningMode + ", gnssBaudRate: " + gnssBaudRate + ", gnssOutputFrequency: " + gnssOutputFrequency + ", gnssCollectionFrequency: " + gnssCollectionFrequency + ", gnssDataUploadMethod: " + gnssDataUploadMethod + ", gnssDataUploadMethodUnit: " + gnssDataUploadMethodUnit + ", canCollectionTimeForChannel1: " + canCollectionTimeForChannel1 + ", canUploadIntervalForChannel1: " + canUploadIntervalForChannel1 + ", canCollectionTimeForChannel2: " + canCollectionTimeForChannel2 + ", canUploadIntervalForChannel2: " + canUploadIntervalForChannel2 + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/JTDeviceConnectionControl.java ================================================ package com.genersoft.iot.vmp.jt1078.bean; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; /** * JT 终端控制 */ @Data @Schema(description = "终端控制") public class JTDeviceConnectionControl { /** * false 表示切换到指定监管平台服务器 ,true 表示切换回原 缺省监控平台服务器 */ private Boolean switchOn; /** * 监管平台鉴权码 */ private String authentication; /** * 拨号点名称 */ private String name; /** * 拨号用户名 */ private String username; /** * 拨号密码 */ private String password; /** * 地址 */ private String address; /** * TCP端口 */ private Integer tcpPort; /** * UDP端口 */ private Integer udpPort; /** * 连接到指定服务器时限 */ private Long timeLimit; @Override public String toString() { return "JTDeviceConnectionControl{" + "switchOn=" + switchOn + ", authentication='" + authentication + '\'' + ", name='" + name + '\'' + ", username='" + username + '\'' + ", password='" + password + '\'' + ", address='" + address + '\'' + ", tcpPort=" + tcpPort + ", udpPort=" + udpPort + ", timeLimit=" + timeLimit + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/JTDeviceType.java ================================================ package com.genersoft.iot.vmp.jt1078.bean; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.Setter; /** * JT 终端类型 */ @Setter @Getter @Schema(description = "JT终端参数设置") public class JTDeviceType { /** * 适用客运车辆 */ private boolean passengerVehicles; /** * 适用危险品车辆 */ private boolean dangerousGoodsVehicles; /** * 普通货运车辆 */ private boolean freightVehicles; /** * 出租车辆 */ private boolean rentalVehicles; /** * 支持硬盘录像 */ private boolean hardDiskRecording; /** * false:一体机 ,true:分体机 */ private boolean splittingMachine; /** * 适用挂车 */ private boolean trailer; public static JTDeviceType getInstance(int content) { boolean passengerVehicles = (content & 1) == 1; boolean dangerousGoodsVehicles = (content >>> 1 & 1) == 1; boolean freightVehicles = (content >>> 2 & 1) == 1; boolean rentalVehicles = (content >>> 3 & 1) == 1; boolean hardDiskRecording = (content >>> 6 & 1) == 1; boolean splittingMachine = (content >>> 7 & 1) == 1; boolean trailer = (content >>> 8 & 1) == 1; return new JTDeviceType(passengerVehicles, dangerousGoodsVehicles, freightVehicles, rentalVehicles, hardDiskRecording, splittingMachine, trailer); } public JTDeviceType(boolean passengerVehicles, boolean dangerousGoodsVehicles, boolean freightVehicles, boolean rentalVehicles, boolean hardDiskRecording, boolean splittingMachine, boolean trailer) { this.passengerVehicles = passengerVehicles; this.dangerousGoodsVehicles = dangerousGoodsVehicles; this.freightVehicles = freightVehicles; this.rentalVehicles = rentalVehicles; this.hardDiskRecording = hardDiskRecording; this.splittingMachine = splittingMachine; this.trailer = trailer; } @Override public String toString() { return "JTDeviceType{" + "passengerVehicles=" + passengerVehicles + ", dangerousGoodsVehicles=" + dangerousGoodsVehicles + ", freightVehicles=" + freightVehicles + ", rentalVehicles=" + rentalVehicles + ", hardDiskRecording=" + hardDiskRecording + ", splittingMachine=" + splittingMachine + ", trailer=" + trailer + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/JTDriverInformation.java ================================================ package com.genersoft.iot.vmp.jt1078.bean; import com.genersoft.iot.vmp.jt1078.util.BCDUtil; import com.genersoft.iot.vmp.utils.DateUtil; import io.netty.buffer.ByteBuf; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.extern.slf4j.Slf4j; import java.nio.charset.Charset; @Data @Slf4j @Schema(description = "驾驶员身份信息") public class JTDriverInformation { @Schema(description = "0x01:从业资格证 IC卡插入( 驾驶员上班);0x02:从 业资格证 IC卡拔出(驾驶员下班)") private int status; @Schema(description = "插卡/拔卡时间 ,以下字段在状 态为0x01 时才有效并做填充") private String time; @Schema(description = "IC卡读取结果:" + "0x00:IC卡读卡成功;" + "0x01:读卡失败 ,原因为卡片密钥认证未通过;" + "0x02:读卡失败 ,原因为卡片已被锁定;" + "0x03:读卡失败 ,原因为卡片被拔出;" + "0x04:读卡失败 ,原因为数据校验错误。" + "以下字段在 IC卡读取结果等于0x00 时才有效") private Integer result; @Schema(description = "驾驶员姓名") private String name; @Schema(description = "从业资格证编码") private String certificateCode; @Schema(description = "发证机构名称") private String certificateIssuanceMechanismName; @Schema(description = "证件有效期") private String expire; @Schema(description = "驾驶员身份证号") private String driverIdNumber; public static JTDriverInformation decode(ByteBuf buf, boolean is2019) { JTDriverInformation jtDriverInformation = new JTDriverInformation(); jtDriverInformation.setStatus(buf.readUnsignedByte()); byte[] bytes = new byte[6]; buf.readBytes(bytes); String timeStr = BCDUtil.transform(bytes); try { jtDriverInformation.setTime(DateUtil.jt1078Toyyyy_MM_dd_HH_mm_ss(timeStr)); }catch (Exception e) { log.error("[JT-驾驶员身份信息] 解码时无法格式化时间: {}", timeStr); } if (jtDriverInformation.getStatus() == 1) { int result = (int)buf.readUnsignedByte(); jtDriverInformation.setResult(result); if (result == 0) { // IC卡读卡成功 int nameLength = buf.readUnsignedByte(); jtDriverInformation.setName(buf.readCharSequence(nameLength, Charset.forName("GBK")).toString().trim()); jtDriverInformation.setCertificateCode(buf.readCharSequence(20, Charset.forName("GBK")).toString().trim()); int certificateIssuanceMechanismNameLength = buf.readUnsignedByte(); jtDriverInformation.setCertificateIssuanceMechanismName(buf.readCharSequence( certificateIssuanceMechanismNameLength, Charset.forName("GBK")).toString().trim()); byte[] bytesForExpire = new byte[4]; buf.readBytes(bytesForExpire); String bytesForExpireStr = BCDUtil.transform(bytesForExpire); try { jtDriverInformation.setExpire(DateUtil.jt1078dateToyyyy_MM_dd(bytesForExpireStr)); }catch (Exception e) { log.error("[JT-驾驶员身份信息] 解码时无法格式化时间: {}", bytesForExpireStr); } if (is2019) { jtDriverInformation.setDriverIdNumber(buf.readCharSequence(20, Charset.forName("GBK")).toString().trim()); } } } return jtDriverInformation; } @Override public String toString() { return "JTDriverInformation{" + "status=" + status + ", time='" + time + '\'' + ", result=" + result + ", name='" + name + '\'' + ", certificateCode='" + certificateCode + '\'' + ", certificateIssuanceMechanismName='" + certificateIssuanceMechanismName + '\'' + ", expire='" + expire + '\'' + ", driverIdNumber='" + driverIdNumber + '\'' + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/JTGnssAttribute.java ================================================ package com.genersoft.iot.vmp.jt1078.bean; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.Setter; /** * JT GNSS 模块属性 */ @Setter @Getter @Schema(description = "JTGNSS 模块属性") public class JTGnssAttribute { private boolean gps; private boolean beidou; private boolean glonass ; private boolean gaLiLeo; public static JTGnssAttribute getInstance(short content) { boolean gps = (content & 1) == 1; boolean beidou = (content >>> 1 & 1) == 1; boolean glonass = (content >>> 2 & 1) == 1; boolean gaLiLeo = (content >>> 3 & 1) == 1; return new JTGnssAttribute(gps, beidou, glonass, gaLiLeo); } public JTGnssAttribute(boolean gps, boolean beidou, boolean glonass, boolean gaLiLeo) { this.gps = gps; this.beidou = beidou; this.glonass = glonass; this.gaLiLeo = gaLiLeo; } @Override public String toString() { return "JGnssAttribute{" + "gps=" + gps + ", beidou=" + beidou + ", glonass=" + glonass + ", gaLiLeo=" + gaLiLeo + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/JTMediaAttribute.java ================================================ package com.genersoft.iot.vmp.jt1078.bean; import com.genersoft.iot.vmp.jt1078.bean.config.JTDeviceSubConfig; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.Getter; import lombok.Setter; /** * 终端上传音视频属性 */ @Setter @Getter public class JTMediaAttribute implements JTDeviceSubConfig { /** * 输入音频编码方式: * 1 G. 721 * 2 G. 722 * 3 G. 723 * 4 G. 728 * 5 G. 729 * 6 G. 711A * 7 G. 711U * 8 G. 726 * 9 G. 729A * 10 DVI4_3 * 11 DVI4_4 * 12 DVI4_8K * 13 DVI4_16K * 14 LPC * 15 S16BE_STEREO * 16 S16BE_MONO * 17 MPEGAUDIO * 18 LPCM * 19 AAC * 20 WMA9STD * 21 HEAAC * 22 PCM_VOICE * 23 PCM_AUDIO * 24 AACLC * 25 MP3 * 26 ADPCMA * 27 MP4AUDIO * 28 AMR */ private int audioEncoder; /** * 输入音频声道数 */ private int audioChannels; /** * 输入音频采样率: * 0:8 kHz; * 1:22. 05 kHz; * 2:44. 1 kHz; * 3:48 kHz */ private int audioSamplingRate; /** * 输入音频采样位数: * 0:8 位; * 1:16 位; * 2:32 位 */ private int audioSamplingBits; /** * 音频帧长度: 范围 1 ~ 4 294 967 295 */ private int audioFrameLength; /** * 是否支持音频输出: * 0:不支持;1:支持 */ private int audioOutputEnable; /** * 视频编码方式: * 98 H. 264 * 99 H. 265 * 100 AVS * 101 SVAC */ private int videoEncoder; /** * 终端支持的最大音频物理通道数量: */ private int audioChannelMax; /** * 终端支持的最大视频物理通道数量: */ private int videoChannelMax; @Override public ByteBuf encode() { ByteBuf byteBuf = Unpooled.buffer(); byteBuf.writeByte(audioEncoder); byteBuf.writeByte(audioChannels); byteBuf.writeByte(audioSamplingRate); byteBuf.writeByte(audioSamplingBits); byteBuf.writeShort(audioFrameLength); byteBuf.writeByte(audioOutputEnable); byteBuf.writeByte(videoEncoder); byteBuf.writeByte(audioChannelMax); byteBuf.writeByte(videoChannelMax); return byteBuf; } public static JTMediaAttribute decode(ByteBuf byteBuf) { JTMediaAttribute jtMediaAttribute = new JTMediaAttribute(); jtMediaAttribute.setAudioEncoder(byteBuf.readUnsignedByte()); jtMediaAttribute.setAudioChannels(byteBuf.readUnsignedByte()); jtMediaAttribute.setAudioSamplingRate(byteBuf.readUnsignedByte()); jtMediaAttribute.setAudioSamplingBits(byteBuf.readUnsignedByte()); jtMediaAttribute.setAudioFrameLength(byteBuf.readUnsignedShort()); jtMediaAttribute.setAudioOutputEnable(byteBuf.readUnsignedByte()); jtMediaAttribute.setVideoEncoder(byteBuf.readUnsignedByte()); jtMediaAttribute.setAudioChannelMax(byteBuf.readUnsignedByte()); jtMediaAttribute.setVideoChannelMax(byteBuf.readUnsignedByte()); return jtMediaAttribute; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/JTMediaDataInfo.java ================================================ package com.genersoft.iot.vmp.jt1078.bean; import io.netty.buffer.ByteBuf; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.Setter; @Setter @Getter @Schema(description = "多媒体检索项数据") public class JTMediaDataInfo { @Schema(description = "多媒体数据 ID") private long id; @Schema(description = "多媒体类型, 0:图像;1:音频;2:视频") private int type; @Schema(description = "事件项编码: 0:平台下发指令;1:定时动作;2:抢劫报警触发;3:碰 撞侧翻报警触发;4:门开拍照;5:门关拍照;6:车门由开 变关 ,车速从小于20km到超过20km;7:定距拍照") private int eventCode; @Schema(description = "通道 ID") private int channelId; @Schema(description = "表示拍摄或录制的起始时刻的汇报消息") private JTPositionBaseInfo positionBaseInfo; public static JTMediaDataInfo decode(ByteBuf buf) { JTMediaDataInfo jtMediaEventInfo = new JTMediaDataInfo(); jtMediaEventInfo.setId(buf.readUnsignedInt()); jtMediaEventInfo.setType(buf.readUnsignedByte()); jtMediaEventInfo.setChannelId(buf.readUnsignedByte()); jtMediaEventInfo.setEventCode(buf.readUnsignedByte()); jtMediaEventInfo.setPositionBaseInfo(JTPositionBaseInfo.decode(buf)); return jtMediaEventInfo; } @Override public String toString() { return "JTMediaDataInfo{" + "id=" + id + ", type=" + type + ", eventCode=" + eventCode + ", channelId=" + channelId + ", positionBaseInfo=" + positionBaseInfo + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/JTMediaEventInfo.java ================================================ package com.genersoft.iot.vmp.jt1078.bean; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @Data @Schema(description = "多媒体事件信息") public class JTMediaEventInfo { @Schema(description = "多媒体数据 ID") private long id; @Schema(description = "多媒体类型, 0:图像;1:音频;2:视频") private int type; @Schema(description = "多媒体格式编码, 0:JPEG;1:TIF;2:MP3;3:WAV;4:WMV;其他保留") private int code; @Schema(description = "事件项编码: 0:平台下发指令;1:定时动作;2:抢劫报警触发;3:碰 撞侧翻报警触发;4:门开拍照;5:门关拍照;6:车门由开 变关 ,车速从小于20km到超过20km;7:定距拍照") private int eventCode; @Schema(description = "通道 ID") private int channelId; @Schema(description = "媒体数据") private byte[] mediaData; @Schema(description = "位置信息汇报") private JTPositionBaseInfo positionBaseInfo; public static JTMediaEventInfo decode(ByteBuf buf) { JTMediaEventInfo jtMediaEventInfo = new JTMediaEventInfo(); jtMediaEventInfo.setId(buf.readUnsignedInt()); jtMediaEventInfo.setType(buf.readUnsignedByte()); jtMediaEventInfo.setCode(buf.readUnsignedByte()); jtMediaEventInfo.setEventCode(buf.readUnsignedByte()); jtMediaEventInfo.setChannelId(buf.readUnsignedByte()); if (buf.readableBytes() > 28) { ByteBuf byteBuf = buf.readSlice(28); jtMediaEventInfo.setPositionBaseInfo(JTPositionBaseInfo.decode(byteBuf)); byte[] bytes = new byte[buf.readableBytes()]; buf.readBytes(bytes); jtMediaEventInfo.setMediaData(bytes); } return jtMediaEventInfo; } @Override public String toString() { return "JTMediaEventInfo{" + "id=" + id + ", type=" + type + ", code=" + code + ", eventCode=" + eventCode + ", channelId=" + channelId + ", fileSize=" + (mediaData == null ? 0 : mediaData.length) + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/JTMediaStreamType.java ================================================ package com.genersoft.iot.vmp.jt1078.bean; public enum JTMediaStreamType { PLAY,PLAYBACK,TALK } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/JTPassengerNum.java ================================================ package com.genersoft.iot.vmp.jt1078.bean; import com.genersoft.iot.vmp.jt1078.bean.config.JTDeviceSubConfig; import com.genersoft.iot.vmp.jt1078.util.BCDUtil; import com.genersoft.iot.vmp.utils.DateUtil; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.Getter; import lombok.Setter; /** * 终端上传乘客流量 */ @Setter @Getter public class JTPassengerNum implements JTDeviceSubConfig { /** * 起始时间, YY-MM-DD-HH-MM-SS( GMT + 8 时间,本标准中之后涉及的时间均采用此时区) */ private String startTime; /** * 结束时间, YY-MM-DD-HH-MM-SS( GMT + 8 时间,本标准中之后涉及的时间均采用此时区) */ private String endTime; /** * 上车人数 */ private int getIn; /** * 下车人数 */ private int getOut; @Override public ByteBuf encode() { return null; } public static JTPassengerNum decode(ByteBuf buf) { JTPassengerNum jtPassengerNum = new JTPassengerNum(); byte[] bytes = new byte[6]; buf.readBytes(bytes); jtPassengerNum.setStartTime(DateUtil.jt1078Toyyyy_MM_dd_HH_mm_ss(BCDUtil.transform(bytes))); buf.readBytes(bytes); jtPassengerNum.setEndTime(DateUtil.jt1078Toyyyy_MM_dd_HH_mm_ss(BCDUtil.transform(bytes))); jtPassengerNum.setGetIn(buf.readUnsignedShort()); jtPassengerNum.setGetOut(buf.readUnsignedShort()); return jtPassengerNum; } @Override public String toString() { return "终端上传乘客流量:" + " 时间: " + startTime + " 到 " + endTime + ", 上车:" + getIn + ", 下车:" + getOut ; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/JTPhoneBookContact.java ================================================ package com.genersoft.iot.vmp.jt1078.bean; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.Setter; import java.nio.charset.Charset; @Setter @Getter @Schema(description = "电话本联系人") public class JTPhoneBookContact { @Schema(description = "1:呼入,2:呼出,3:呼入/呼出") private int sign; @Schema(description = "电话号码") private String phoneNumber; @Schema(description = "联系人") private String contactName; public ByteBuf encode(){ ByteBuf buffer = Unpooled.buffer(); buffer.writeByte(sign); buffer.writeByte(phoneNumber.getBytes(Charset.forName("GBK")).length); buffer.writeCharSequence(phoneNumber, Charset.forName("GBK")); buffer.writeByte(contactName.getBytes(Charset.forName("GBK")).length); buffer.writeCharSequence(contactName, Charset.forName("GBK")); return buffer; } @Override public String toString() { return "JTPhoneBookContact{" + "sign=" + sign + ", phoneNumber='" + phoneNumber + '\'' + ", contactName='" + contactName + '\'' + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/JTPolygonArea.java ================================================ package com.genersoft.iot.vmp.jt1078.bean; import com.genersoft.iot.vmp.jt1078.util.BCDUtil; import com.genersoft.iot.vmp.utils.DateUtil; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.Setter; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; @Setter @Getter @Schema(description = "多边形区域") public class JTPolygonArea implements JTAreaOrRoute{ @Schema(description = "区域 ID") private long id; @Schema(description = "") private JTAreaAttribute attribute; @Schema(description = "起始时间, yyyy-MM-dd HH:mm:ss") private String startTime; @Schema(description = "结束时间, yyyy-MM-dd HH:mm:ss") private String endTime; @Schema(description = "最高速度, 单位为千米每小时(km/h)") private int maxSpeed; @Schema(description = "超速持续时间, 单位为秒(s)") private int overSpeedDuration; @Schema(description = "区域顶点") private List polygonPoints; @Schema(description = "夜间最高速度, 单位为千米每小时(km/h)") private int nighttimeMaxSpeed; @Schema(description = "区域的名称") private String name; public ByteBuf encode(){ ByteBuf byteBuf = Unpooled.buffer(); byteBuf.writeInt((int) (id & 0xffffffffL)); byteBuf.writeBytes(attribute.encode()); byteBuf.writeBytes(BCDUtil.strToBcd(DateUtil.yyyy_MM_dd_HH_mm_ssTo1078(startTime))); byteBuf.writeBytes(BCDUtil.strToBcd(DateUtil.yyyy_MM_dd_HH_mm_ssTo1078(endTime))); byteBuf.writeShort((short)(maxSpeed & 0xffff)); byteBuf.writeByte(overSpeedDuration); byteBuf.writeShort((short)(polygonPoints.size() & 0xffff)); if (!polygonPoints.isEmpty()) { for (JTPolygonPoint polygonPoint : polygonPoints) { byteBuf.writeBytes(polygonPoint.encode()); } } byteBuf.writeShort((short)(nighttimeMaxSpeed & 0xffff)); byteBuf.writeShort((short)(name.getBytes(Charset.forName("GBK")).length & 0xffff)); byteBuf.writeCharSequence(name, Charset.forName("GBK")); return byteBuf; } public static JTPolygonArea decode(ByteBuf buf) { JTPolygonArea area = new JTPolygonArea(); area.setId(buf.readUnsignedInt()); int attributeInt = buf.readUnsignedShort(); JTAreaAttribute areaAttribute = JTAreaAttribute.decode(attributeInt); area.setAttribute(areaAttribute); byte[] startTimeBytes = new byte[6]; buf.readBytes(startTimeBytes); area.setStartTime(DateUtil.jt1078Toyyyy_MM_dd_HH_mm_ss(BCDUtil.transform(startTimeBytes))); byte[] endTimeBytes = new byte[6]; buf.readBytes(endTimeBytes); area.setEndTime(DateUtil.jt1078Toyyyy_MM_dd_HH_mm_ss(BCDUtil.transform(endTimeBytes))); area.setMaxSpeed(buf.readUnsignedShort()); area.setOverSpeedDuration(buf.readUnsignedByte()); int polygonPointsSize = buf.readUnsignedShort(); List polygonPointList = new ArrayList<>(polygonPointsSize); for (int i = 0; i < polygonPointsSize; i++) { JTPolygonPoint polygonPoint = new JTPolygonPoint(); polygonPoint.setLatitude(buf.readUnsignedInt()/1000000D); polygonPoint.setLongitude(buf.readUnsignedInt()/1000000D); polygonPointList.add(polygonPoint); } area.setPolygonPoints(polygonPointList); area.setNighttimeMaxSpeed(buf.readUnsignedShort()); int nameLength = buf.readUnsignedShort(); area.setName(buf.readCharSequence(nameLength, Charset.forName("GBK")).toString().trim()); return area; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/JTPolygonPoint.java ================================================ package com.genersoft.iot.vmp.jt1078.bean; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.Setter; @Setter @Getter @Schema(description = "多边形区域的顶点") public class JTPolygonPoint { @Schema(description = "顶点纬度") private Double latitude; @Schema(description = "顶点经度") private Double longitude; public ByteBuf encode(){ ByteBuf byteBuf = Unpooled.buffer(); byteBuf.writeInt((int) (Math.round((latitude * 1000000)) & 0xffffffffL)); byteBuf.writeInt((int) (Math.round((longitude * 1000000)) & 0xffffffffL)); return byteBuf; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/JTPositionAdditionalInfo.java ================================================ package com.genersoft.iot.vmp.jt1078.bean; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.Setter; @Getter @Setter @Schema(description = "位置附加信息") public class JTPositionAdditionalInfo { @Schema(description = "里程, 单位为1/10km, 对应车上里程表读数") private int mileage; @Schema(description = "油量, 单位为1/10L, 对应车上油量表读数") private int oil; @Schema(description = "行驶记录功能获取的速度,单位为1/10km/h") private int speed; @Schema(description = "报警事件的 ID") private int alarmId; // TODO 暂不支持胎压 @Schema(description = "车厢温度 ,单位为摄氏度") private int carriageTemperature; // TODO 暂不支持胎压 } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/JTPositionBaseInfo.java ================================================ package com.genersoft.iot.vmp.jt1078.bean; import com.genersoft.iot.vmp.jt1078.util.BCDUtil; import io.netty.buffer.ByteBuf; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import java.nio.ByteBuffer; @Setter @Getter @Slf4j @Schema(description = "位置基本信息") public class JTPositionBaseInfo { /** * 报警标志 */ @Schema(description = "报警标志") private JTAlarmSign alarmSign; /** * 状态 */ @Schema(description = "状态") private JTStatus status; /** * 经度 */ @Schema(description = "经度") private Double longitude; /** * 纬度 */ @Schema(description = "纬度") private Double latitude; /** * 高程 */ @Schema(description = "高程") private Integer altitude; /** * 速度 */ @Schema(description = "速度") private Integer speed; /** * 方向 */ @Schema(description = "方向") private Integer direction; /** * 时间 */ @Schema(description = "时间") private String time; /** * 视频报警 */ @Schema(description = "视频报警") private JTVideoAlarm videoAlarm; public static JTPositionBaseInfo decode(ByteBuf buf) { JTPositionBaseInfo positionInfo = new JTPositionBaseInfo(); if (buf.readableBytes() < 17) { log.error("[位置基本信息] 解码失败,长度不足: {}", buf.readableBytes()); return positionInfo; } positionInfo.setAlarmSign(new JTAlarmSign(buf.readUnsignedInt())); positionInfo.setStatus(new JTStatus(buf.readUnsignedInt())); positionInfo.setLatitude(buf.readInt() * 0.000001D); positionInfo.setLongitude(buf.readInt() * 0.000001D); positionInfo.setAltitude(buf.readUnsignedShort()); positionInfo.setSpeed(buf.readUnsignedShort()); positionInfo.setDirection(buf.readUnsignedShort()); byte[] timeBytes = new byte[6]; buf.readBytes(timeBytes); positionInfo.setTime(BCDUtil.transform(timeBytes)); return positionInfo; } public String toSimpleString() { return "简略位置汇报信息: " + " \n 经度:" + longitude + " \n 纬度:" + latitude + " \n 高程: " + altitude + " \n 速度: " + speed + " \n 方向: " + direction + " \n 时间: " + time + " \n"; } @Override public String toString() { return "位置汇报信息: " + " \n 报警标志:" + alarmSign.toString() + " \n 状态:" + status.toString() + " \n 经度:" + longitude + " \n 纬度:" + latitude + " \n 高程: " + altitude + " \n 速度: " + speed + " \n 方向: " + direction + " \n 时间: " + time + " \n"; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/JTPositionInfo.java ================================================ package com.genersoft.iot.vmp.jt1078.bean; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; @Getter @Schema(description = "位置信息") public class JTPositionInfo { /** * 位置基本信息 */ @Schema(description = "位置基本信息") private JTPositionBaseInfo base; /** * 位置基本信息 */ @Schema(description = "位置附加信息") private JTPositionAdditionalInfo additional; public void setBase(JTPositionBaseInfo base) { this.base = base; } public void setAdditional(JTPositionAdditionalInfo additional) { this.additional = additional; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/JTQueryMediaDataCommand.java ================================================ package com.genersoft.iot.vmp.jt1078.bean; import com.genersoft.iot.vmp.jt1078.util.BCDUtil; import com.genersoft.iot.vmp.utils.DateUtil; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.Setter; @Setter @Getter @Schema(description = "存储多媒体数据") public class JTQueryMediaDataCommand { @Schema(description = "多媒体类型: 0:图像;1:音频;2:视频") private int type; @Schema(description = "通道 ID, 0 表示检索该媒体类型的所有通道") private int chanelId; @Schema(description = "事件项编码: 0:平台下发指令;1:定时动作;2:抢劫报警触发;3:碰 撞侧翻报警触发;其他保留") private int event; @Schema(description = "开始时间") private String startTime; @Schema(description = "结束时间") private String endTime; @Schema(description = "删除标志, 0:保留;1:删除, 存储多媒体数据上传命令中使用") private Integer delete; public ByteBuf decode() { ByteBuf byteBuf = Unpooled.buffer(); byteBuf.writeByte(type); byteBuf.writeByte(chanelId); byteBuf.writeByte(event); if (startTime == null) { byteBuf.writeBytes(BCDUtil.strToBcd("000000000000")); }else { byteBuf.writeBytes(BCDUtil.strToBcd(DateUtil.yyyy_MM_dd_HH_mm_ssTo1078(startTime))); } if (endTime == null) { byteBuf.writeBytes(BCDUtil.strToBcd("000000000000")); }else { byteBuf.writeBytes(BCDUtil.strToBcd(DateUtil.yyyy_MM_dd_HH_mm_ssTo1078(endTime))); } if (delete != null) { byteBuf.writeByte(delete); } return byteBuf; } @Override public String toString() { return "JTQueryMediaDataCommand{" + "type=" + type + ", chanelId=" + chanelId + ", event=" + event + ", startTime='" + startTime + '\'' + ", endTime='" + endTime + '\'' + ", delete='" + delete + '\'' + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/JTRecordDownloadCatch.java ================================================ package com.genersoft.iot.vmp.jt1078.bean; import com.genersoft.iot.vmp.jt1078.proc.response.J9206; import lombok.Getter; import lombok.Setter; import org.jetbrains.annotations.NotNull; import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit; public class JTRecordDownloadCatch implements Delayed { @Getter @Setter private String phoneNumber; @Getter @Setter private String path; @Getter @Setter private J9206 j9206; /** * 超时时间(单位: 毫秒) */ @Getter @Setter private long delayTime; @Override public long getDelay(@NotNull TimeUnit unit) { return unit.convert(delayTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS); } @Override public int compareTo(@NotNull Delayed o) { return (int) (this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS)); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/JTRectangleArea.java ================================================ package com.genersoft.iot.vmp.jt1078.bean; import com.genersoft.iot.vmp.jt1078.util.BCDUtil; import com.genersoft.iot.vmp.utils.DateUtil; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.Setter; import java.nio.charset.Charset; @Setter @Getter @Schema(description = "矩形区域") public class JTRectangleArea implements JTAreaOrRoute{ @Schema(description = "区域 ID") private long id; @Schema(description = "") private JTAreaAttribute attribute; @Schema(description = "左上点纬度") private Double latitudeForUpperLeft; @Schema(description = "左上点经度") private Double longitudeForUpperLeft; @Schema(description = "右下点纬度") private Double latitudeForLowerRight; @Schema(description = "右下点经度") private Double longitudeForLowerRight; @Schema(description = "起始时间, yyyy-MM-dd HH:mm:ss") private String startTime; @Schema(description = "结束时间, yyyy-MM-dd HH:mm:ss") private String endTime; @Schema(description = "最高速度, 单位为千米每小时(km/h)") private int maxSpeed; @Schema(description = "超速持续时间, 单位为秒(s)") private int overSpeedDuration; @Schema(description = "夜间最高速度, 单位为千米每小时(km/h)") private int nighttimeMaxSpeed; @Schema(description = "区域的名称") private String name; public ByteBuf encode(){ ByteBuf byteBuf = Unpooled.buffer(); byteBuf.writeInt((int) (id & 0xffffffffL)); byteBuf.writeBytes(attribute.encode()); byteBuf.writeInt((int) (Math.round((latitudeForUpperLeft * 1000000)) & 0xffffffffL)); byteBuf.writeInt((int) (Math.round((longitudeForUpperLeft * 1000000)) & 0xffffffffL)); byteBuf.writeInt((int) (Math.round((latitudeForLowerRight * 1000000)) & 0xffffffffL)); byteBuf.writeInt((int) (Math.round((longitudeForLowerRight * 1000000)) & 0xffffffffL)); byteBuf.writeBytes(BCDUtil.strToBcd(DateUtil.yyyy_MM_dd_HH_mm_ssTo1078(startTime))); byteBuf.writeBytes(BCDUtil.strToBcd(DateUtil.yyyy_MM_dd_HH_mm_ssTo1078(endTime))); byteBuf.writeShort((short)(maxSpeed & 0xffff)); byteBuf.writeByte(overSpeedDuration); byteBuf.writeShort((short)(nighttimeMaxSpeed & 0xffff)); byteBuf.writeShort((short)(name.getBytes(Charset.forName("GBK")).length & 0xffff)); byteBuf.writeCharSequence(name, Charset.forName("GBK")); return byteBuf; } public static JTRectangleArea decode(ByteBuf buf) { JTRectangleArea area = new JTRectangleArea(); area.setId(buf.readUnsignedInt()); int attributeInt = buf.readUnsignedShort(); JTAreaAttribute areaAttribute = JTAreaAttribute.decode(attributeInt); area.setAttribute(areaAttribute); area.setLatitudeForUpperLeft(buf.readUnsignedInt()/1000000D); area.setLongitudeForUpperLeft(buf.readUnsignedInt()/1000000D); area.setLatitudeForLowerRight(buf.readUnsignedInt()/1000000D); area.setLongitudeForLowerRight(buf.readUnsignedInt()/1000000D); byte[] startTimeBytes = new byte[6]; buf.readBytes(startTimeBytes); area.setStartTime(DateUtil.jt1078Toyyyy_MM_dd_HH_mm_ss(BCDUtil.transform(startTimeBytes))); byte[] endTimeBytes = new byte[6]; buf.readBytes(endTimeBytes); area.setEndTime(DateUtil.jt1078Toyyyy_MM_dd_HH_mm_ss(BCDUtil.transform(endTimeBytes))); area.setMaxSpeed(buf.readUnsignedShort()); area.setOverSpeedDuration(buf.readUnsignedByte()); area.setNighttimeMaxSpeed(buf.readUnsignedShort()); int nameLength = buf.readUnsignedShort(); area.setName(buf.readCharSequence(nameLength, Charset.forName("GBK")).toString().trim()); return area; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/JTRoute.java ================================================ package com.genersoft.iot.vmp.jt1078.bean; import com.genersoft.iot.vmp.jt1078.util.BCDUtil; import com.genersoft.iot.vmp.utils.DateUtil; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.Setter; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; @Setter @Getter @Schema(description = "路线") public class JTRoute implements JTAreaOrRoute{ @Schema(description = "路线 ID") private long id; @Schema(description = "路线属性") private JTRouteAttribute attribute; @Schema(description = "起始时间, yyyy-MM-dd HH:mm:ss") private String startTime; @Schema(description = "结束时间, yyyy-MM-dd HH:mm:ss") private String endTime; @Schema(description = "路线拐点") private List routePointList; @Schema(description = "区域的名称") private String name; public ByteBuf encode(){ ByteBuf byteBuf = Unpooled.buffer(); byteBuf.writeInt((int) (id & 0xffffffffL)); byteBuf.writeBytes(attribute.encode()); byteBuf.writeBytes(BCDUtil.strToBcd(DateUtil.yyyy_MM_dd_HH_mm_ssTo1078(startTime))); byteBuf.writeBytes(BCDUtil.strToBcd(DateUtil.yyyy_MM_dd_HH_mm_ssTo1078(endTime))); byteBuf.writeShort((short)(routePointList.size() & 0xffff)); if (!routePointList.isEmpty()){ for (JTRoutePoint jtRoutePoint : routePointList) { byteBuf.writeBytes(jtRoutePoint.encode()); } } byteBuf.writeShort((short)(name.getBytes(Charset.forName("GBK")).length & 0xffff)); byteBuf.writeCharSequence(name, Charset.forName("GBK")); return byteBuf; } public static JTRoute decode(ByteBuf buf) { JTRoute route = new JTRoute(); route.setId(buf.readUnsignedInt()); int attributeInt = buf.readUnsignedShort(); JTRouteAttribute routeAttribute = JTRouteAttribute.decode(attributeInt); route.setAttribute(routeAttribute); byte[] startTimeBytes = new byte[6]; buf.readBytes(startTimeBytes); route.setStartTime(DateUtil.jt1078Toyyyy_MM_dd_HH_mm_ss(BCDUtil.transform(startTimeBytes))); byte[] endTimeBytes = new byte[6]; buf.readBytes(endTimeBytes); route.setEndTime(DateUtil.jt1078Toyyyy_MM_dd_HH_mm_ss(BCDUtil.transform(endTimeBytes))); int routePointsSize = buf.readUnsignedShort(); List jtRoutePoints = new ArrayList<>(routePointsSize); for (int i = 0; i < routePointsSize; i++) { jtRoutePoints.add(JTRoutePoint.decode(buf)); } route.setRoutePointList(jtRoutePoints); int nameLength = buf.readUnsignedShort(); route.setName(buf.readCharSequence(nameLength, Charset.forName("GBK")).toString().trim()); return route; } @Override public String toString() { return "JTRoute{" + "id=" + id + ", attribute=" + attribute + ", startTime='" + startTime + '\'' + ", endTime='" + endTime + '\'' + ", routePointList=" + routePointList + ", name='" + name + '\'' + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/JTRouteAttribute.java ================================================ package com.genersoft.iot.vmp.jt1078.bean; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.Setter; @Setter @Getter @Schema(description = "路线属性") public class JTRouteAttribute { @Schema(description = "是否启用起始时间与结束时间的判断规则 ,false:否;true:是") private boolean ruleForTimeLimit; @Schema(description = "进区域是否报警给驾驶员,false:否;true:是") private boolean ruleForAlarmToDriverWhenEnter; @Schema(description = "进区域是否报警给平台 ,false:否;true:是") private boolean ruleForAlarmToPlatformWhenEnter; @Schema(description = "出区域是否报警给驾驶员,false:否;true:是") private boolean ruleForAlarmToDriverWhenExit; @Schema(description = "出区域是否报警给平台 ,false:否;true:是") private boolean ruleForAlarmToPlatformWhenExit; public ByteBuf encode(){ ByteBuf byteBuf = Unpooled.buffer(); short content = 0; if (ruleForTimeLimit) { content |= 1; } if (ruleForAlarmToDriverWhenEnter) { content |= (1 << 2); } if (ruleForAlarmToPlatformWhenEnter) { content |= (1 << 3); } if (ruleForAlarmToDriverWhenExit) { content |= (1 << 4); } if (ruleForAlarmToPlatformWhenExit) { content |= (1 << 5); } byteBuf.writeShort((short)(content & 0xffff)); return byteBuf; } public static JTRouteAttribute decode(int attributeInt) { JTRouteAttribute attribute = new JTRouteAttribute(); attribute.setRuleForTimeLimit((attributeInt & 1) == 1); attribute.setRuleForAlarmToDriverWhenEnter((attributeInt >> 2 & 1) == 1); attribute.setRuleForAlarmToPlatformWhenEnter((attributeInt >> 3 & 1) == 1); attribute.setRuleForAlarmToDriverWhenExit((attributeInt >> 4 & 1) == 1); attribute.setRuleForAlarmToPlatformWhenExit((attributeInt >> 5 & 1) == 1); return attribute; } @Override public String toString() { return "JTRouteAttribute{" + "ruleForTimeLimit=" + ruleForTimeLimit + ", ruleForAlarmToDriverWhenEnter=" + ruleForAlarmToDriverWhenEnter + ", ruleForAlarmToPlatformWhenEnter=" + ruleForAlarmToPlatformWhenEnter + ", ruleForAlarmToDriverWhenExit=" + ruleForAlarmToDriverWhenExit + ", ruleForAlarmToPlatformWhenExit=" + ruleForAlarmToPlatformWhenExit + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/JTRoutePoint.java ================================================ package com.genersoft.iot.vmp.jt1078.bean; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.Setter; @Setter @Getter @Schema(description = "路线拐点") public class JTRoutePoint { @Schema(description = "拐点 ID") private long id; @Schema(description = "路段 ID") private long routeSectionId; @Schema(description = "拐点纬度") private Double latitude; @Schema(description = "拐点经度") private Double longitude; @Schema(description = "路段宽度") private int routeSectionAttributeWidth; @Schema(description = "路段属性") private JTRouteSectionAttribute routeSectionAttribute; @Schema(description = "路段行驶过长國值") private int routeSectionMaxLength; @Schema(description = "路段行驶不足國值") private int routeSectionMinLength; @Schema(description = "路段最高速度") private int routeSectionMaxSpeed; @Schema(description = "路段超速持续时间") private int routeSectionOverSpeedDuration; @Schema(description = "路段夜间最高速度") private int routeSectionNighttimeMaxSpeed; public ByteBuf encode(){ ByteBuf byteBuf = Unpooled.buffer(); byteBuf.writeInt((int) (id & 0xffffffffL)); byteBuf.writeInt((int) (routeSectionId & 0xffffffffL)); byteBuf.writeInt((int) (Math.round((latitude * 1000000)) & 0xffffffffL)); byteBuf.writeInt((int) (Math.round((longitude * 1000000)) & 0xffffffffL)); byteBuf.writeByte(routeSectionAttributeWidth); byteBuf.writeByte(routeSectionAttribute.encode()); byteBuf.writeShort((short)(routeSectionMaxLength & 0xffff)); byteBuf.writeShort((short)(routeSectionMinLength & 0xffff)); byteBuf.writeShort((short)(routeSectionMaxSpeed & 0xffff)); byteBuf.writeByte(routeSectionOverSpeedDuration); byteBuf.writeShort((short)(routeSectionNighttimeMaxSpeed & 0xffff)); return byteBuf; } public static JTRoutePoint decode(ByteBuf buf) { JTRoutePoint point = new JTRoutePoint(); point.setId(buf.readUnsignedInt()); point.setRouteSectionId(buf.readUnsignedInt()); point.setLatitude(buf.readUnsignedInt()/1000000D); point.setLongitude(buf.readUnsignedInt()/1000000D); point.setRouteSectionAttributeWidth(buf.readUnsignedByte()); JTRouteSectionAttribute areaAttribute = JTRouteSectionAttribute.decode(buf.readUnsignedByte()); point.setRouteSectionAttribute(areaAttribute); point.setRouteSectionMaxLength(buf.readUnsignedShort()); point.setRouteSectionMinLength(buf.readUnsignedShort()); point.setRouteSectionMaxSpeed(buf.readUnsignedShort()); point.setRouteSectionOverSpeedDuration(buf.readUnsignedByte()); point.setRouteSectionNighttimeMaxSpeed(buf.readUnsignedShort()); return point; } @Override public String toString() { return "JTRoutePoint{" + "id=" + id + ", routeSectionId=" + routeSectionId + ", latitude=" + latitude + ", longitude=" + longitude + ", routeSectionAttributeWidth=" + routeSectionAttributeWidth + ", routeSectionAttribute=" + routeSectionAttribute + ", routeSectionMaxLength=" + routeSectionMaxLength + ", routeSectionMinLength=" + routeSectionMinLength + ", routeSectionMaxSpeed=" + routeSectionMaxSpeed + ", routeSectionOverSpeedDuration=" + routeSectionOverSpeedDuration + ", routeSectionNighttimeMaxSpeed=" + routeSectionNighttimeMaxSpeed + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/JTRouteSectionAttribute.java ================================================ package com.genersoft.iot.vmp.jt1078.bean; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.Setter; @Setter @Getter @Schema(description = "路段属性") public class JTRouteSectionAttribute { @Schema(description = "行驶时间 ,false:否;true:是") private boolean ruleForTimeLimit; @Schema(description = "限速 ,false:否;true:是") private boolean ruleForSpeedLimit; @Schema(description = "false:北纬;true:南纬") private boolean southLatitude; @Schema(description = "false:东经;true:西经") private boolean westLongitude; public byte encode(){ byte attributeByte = 0; if (ruleForTimeLimit) { attributeByte |= 1; } if (ruleForSpeedLimit) { attributeByte |= (1 << 1); } if (southLatitude) { attributeByte |= (1 << 2); } if (westLongitude) { attributeByte |= (1 << 3); } return attributeByte; } public static JTRouteSectionAttribute decode(short attributeShort) { JTRouteSectionAttribute attribute = new JTRouteSectionAttribute(); attribute.setRuleForTimeLimit((attributeShort & 1) == 1); attribute.setRuleForSpeedLimit((attributeShort >> 1 & 1) == 1); attribute.setSouthLatitude((attributeShort >> 2 & 1) == 1); attribute.setWestLongitude((attributeShort >> 3 & 1) == 1); return attribute; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/JTShootingCommand.java ================================================ package com.genersoft.iot.vmp.jt1078.bean; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.Getter; import lombok.Setter; @Getter @Setter @Schema(description = "拍摄命令参数") public class JTShootingCommand { @Schema(description = "通道 ID") private int chanelId; @Schema(description = "0:停止拍摄;0xFFFF:录像;其他:拍照张数") private int command; @Schema(description = "拍照间隔/录像时间, 单位为秒(s) ,0 表示按最小间隔拍照或一直录像") private int time; @Schema(description = "1:保存; 0:实时上传") private int save; @Schema(description = "分辨率: " + "0x00:最低分辨率" + "0x01:320 x240;" + "0x02:640 x480;" + "0x03:800 x600;" + "0x04:1024 x768;" + "0x05:176 x144;" + "0x06:352 x288;" + "0x07:704 x288;" + "0x08:704 x576;" + "0xff:最高分辨率") private int resolvingPower; @Schema(description = "图像/视频质量: 取值范围为 1 ~ 10 ,1 代表质量损失最小 ,10 表示压缩 比最大") private int quality; @Schema(description = "亮度, 0 ~ 255") private int brightness; @Schema(description = "对比度,0 ~ 127") private int contrastRatio; @Schema(description = "饱和度,0 ~ 127") private int saturation; @Schema(description = "色度,0 ~ 255") private int chroma; public ByteBuf decode() { ByteBuf byteBuf = Unpooled.buffer(); byteBuf.writeByte(chanelId); byteBuf.writeShort((short)(command & 0xffff)); byteBuf.writeShort((short)(time & 0xffff)); byteBuf.writeByte(save); byteBuf.writeByte(resolvingPower); byteBuf.writeByte(quality); byteBuf.writeByte(brightness); byteBuf.writeByte(contrastRatio); byteBuf.writeByte(saturation); byteBuf.writeByte(chroma); return byteBuf; } @Override public String toString() { return "JTShootingCommand{" + "chanelId=" + chanelId + ", command=" + command + ", time=" + time + ", save=" + save + ", resolvingPower=" + resolvingPower + ", quality=" + quality + ", brightness=" + brightness + ", contrastRatio=" + contrastRatio + ", saturation=" + saturation + ", chroma=" + chroma + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/JTStatus.java ================================================ package com.genersoft.iot.vmp.jt1078.bean; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @Data @Schema(description = "状态信息") public class JTStatus { @Schema(description = "false:ACC关;true: ACC开") private boolean acc; @Schema(description = "false:未定位;true: 定位") private boolean positioning; @Schema(description = "false:北纬;true: 南纬") private boolean southLatitude; @Schema(description = "false:东经;true: 西经") private boolean wesLongitude; @Schema(description = "false:运营状态;true: 停运状态") private boolean outage; @Schema(description = "false:经纬度未经保密插件加密;true: 经纬度已经保密插件加密") private boolean positionEncryption; @Schema(description = "true: 紧急刹车系统采集的前撞预警") private boolean warningFrontCrash; @Schema(description = "true: 车道偏移预警") private boolean warningShifting; @Schema(description = "00:空车;01:半载;10:保留;11:满载。可表示客车的空载状态 ,重车及货车的空载、满载状态 ,该状态可由人工输入或传感器获取") private int load; @Schema(description = "false:车辆油路正常;true: 车辆油路断开") private boolean oilWayBreak; @Schema(description = "false:车辆电路正常;true: 车辆电路断开") private boolean circuitBreak; @Schema(description = "false:车门解锁;true: 车门加锁") private boolean doorLocking; @Schema(description = "false:门1 关;true: 门1 开(前门)") private boolean door1Open; @Schema(description = "false:门2 关;true: 门2 开(中门)") private boolean door2Open; @Schema(description = "false:门3 关;true: 门3 开(后门)") private boolean door3Open; @Schema(description = "false:门4 关;true: 门4 开(驾驶席门)") private boolean door4Open; @Schema(description = "false:门5 关;true: 门5 开(自定义)") private boolean door5Open; @Schema(description = "false:未使用 GPS 卫星进行定位;true:使用 GPS 卫星进行定位") private boolean gps; @Schema(description = "false:未使用北斗卫星进行定位;true:使用北斗卫星进行定位") private boolean beidou; @Schema(description = "false:未使用GLONASS 卫星进行定位;true:使用GLONASS 卫星进行定位") private boolean glonass; @Schema(description = "false:未使用GaLiLeo 卫星进行定位;true:使用GaLiLeo 卫星进行定位") private boolean gaLiLeo; @Schema(description = "false:车辆处于停止状态;true:车辆处于行驶状态") private boolean driving; public JTStatus() { } public JTStatus(long statusInt) { if (statusInt == 0) { return; } this.acc = (statusInt & 1) == 1; this.positioning = (statusInt >>> 1 & 1) == 1; this.southLatitude = (statusInt >>> 2 & 1) == 1; this.wesLongitude = (statusInt >>> 3 & 1) == 1; this.outage = (statusInt >>> 4 & 1) == 1; this.positionEncryption = (statusInt >>> 5 & 1) == 1; this.warningFrontCrash = (statusInt >>> 6 & 1) == 1; this.warningShifting = (statusInt >>> 7 & 1) == 1; this.load = (int)(statusInt >>> 8 & 3); this.oilWayBreak = (statusInt >>> 10 & 1) == 1; this.circuitBreak = (statusInt >>> 11 & 1) == 1; this.doorLocking = (statusInt >>> 12 & 1) == 1; this.door1Open = (statusInt >>> 13 & 1) == 1; this.door2Open = (statusInt >>> 14 & 1) == 1; this.door3Open = (statusInt >>> 15 & 1) == 1; this.door4Open = (statusInt >>> 16 & 1) == 1; this.door5Open = (statusInt >>> 17 & 1) == 1; this.gps = (statusInt >>> 18 & 1) == 1; this.beidou = (statusInt >>> 19 & 1) == 1; this.glonass = (statusInt >>> 20 & 1) == 1; this.gaLiLeo = (statusInt >>> 21 & 1) == 1; this.driving = (statusInt >>> 22 & 1) == 1; } @Override public String toString() { return "状态位:" + "\n acc状态:" + (acc?"开":"关") + "\n 定位状态:" + (positioning?"定位":"未定位") + "\n 南北纬:" + (southLatitude?"南纬":"北纬") + "\n 东西经:" + (wesLongitude?"西经":"东经") + "\n 运营状态:" + (outage?"停运":"运营") + "\n 经纬度保密:" + (positionEncryption?"加密":"未加密") + "\n 前撞预警:" + (warningFrontCrash?"紧急刹车系统采集的前撞预警":"无") + "\n 车道偏移预警:" + (warningShifting?"车道偏移预警":"无") + "\n 空/半/满载状态:" + (load == 0?"空车":(load == 1?"半载":(load == 3?"满载":"未定义状态"))) + "\n 车辆油路状态:" + (oilWayBreak?"车辆油路断开":"车辆油路正常") + "\n 车辆电路状态:" + (circuitBreak?"车辆电路断开":"车辆电路正常") + "\n 门锁状态:" + (doorLocking?"车门加锁":"车门解锁") + "\n 门1(前门)状态:" + (door1Open?"开":"关") + "\n 门2(中门)状态:" + (door2Open?"开":"关") + "\n 门3(后门)状态:" + (door3Open?"开":"关") + "\n 门4(驾驶席门)状态:" + (door4Open?"开":"关") + "\n 门5(自定义)状态:" + (door5Open?"开":"关") + "\n GPS卫星定位状态: " + (gps?"使用":"未使用") + "\n 北斗卫星定位状态: " + (beidou?"使用":"未使用") + "\n GLONASS卫星定位状态: " + (glonass?"使用":"未使用") + "\n GaLiLeo卫星定位状态: " + (gaLiLeo?"使用":"未使用") + "\n GaLiLeo卫星定位状态: " + (gaLiLeo?"使用":"未使用") + "\n 车辆行驶状态: " + (driving?"车辆行驶":"车辆停止") + "\n "; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/JTTextSign.java ================================================ package com.genersoft.iot.vmp.jt1078.bean; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; /** * 文本信息标志 */ @Data @Schema(description = "文本信息标志") public class JTTextSign { @Schema(description = "1紧急,2服务,3通知") private int type; @Schema(description = "1终端显示器显示") private boolean terminalDisplay; @Schema(description = "1广告屏显示") private boolean adScreen; @Schema(description = "1终端 TTS 播读") private boolean tts; @Schema(description = "false: 中心导航信息 true CAN故障码信息") private boolean source; public byte encode(){ byte byteSign = 0; byteSign |= (byte) type; if (terminalDisplay) { byteSign |= (0x1 << 2); } if (tts) { byteSign |= (0x1 << 3); } if (adScreen) { byteSign |= (0x1 << 4); } if (source) { byteSign |= (0x1 << 5); } return byteSign; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/JTVehicleControl.java ================================================ package com.genersoft.iot.vmp.jt1078.bean; import com.genersoft.iot.vmp.jt1078.bean.common.ConfigAttribute; import lombok.Getter; import lombok.Setter; import java.util.Objects; /** * 车辆控制类型 */ @Setter @Getter public class JTVehicleControl { private int length; private void setLength(Object value) { if (Objects.isNull(value)) { length--; }else { length ++; } } @ConfigAttribute(id = 0X0001, type="Byte", description = "车门, 0:车门锁闭 1:车门开启") private Integer controlCarDoor; public void setControlCarDoor(Integer controlCarDoor) { this.controlCarDoor = controlCarDoor; setLength(controlCarDoor); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/JTVideoAlarm.java ================================================ package com.genersoft.iot.vmp.jt1078.bean; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.Setter; import java.util.ArrayList; import java.util.List; @Setter @Getter @Schema(description = "视频报警上报") public class JTVideoAlarm { @Schema(description = "视频信号丢失报警的通道") private List videoLossChannels; @Schema(description = "视频信号遮挡报警的通道") private List videoOcclusionChannels; @Schema(description = "存储器故障报警状态,第 1-12 个主存储器,12-15 分别表示第 1-4 个灾备存储装置") private List storageFaultAlarm; @Schema(description = "异常驾驶行为-疲劳") private boolean drivingForFatigue; @Schema(description = "异常驾驶行为-打电话") private boolean drivingForCall; @Schema(description = "异常驾驶行为-抽烟") private boolean drivingSmoking; @Schema(description = "其他视频设备故障") private boolean otherDeviceFailure; @Schema(description = "客车超员报警") private boolean overcrowding; @Schema(description = "特殊报警录像达到存储阈值报警") private boolean specialRecordFull; public JTVideoAlarm() { } public static JTVideoAlarm getInstance(int alarm, int loss, int occlusion, short storageFault, short driving) { JTVideoAlarm jtVideoAlarm = new JTVideoAlarm(); if (alarm == 0) { return jtVideoAlarm; } boolean lossAlarm = (alarm & 1) == 1; boolean occlusionAlarm = (alarm >>> 1 & 1) == 1; boolean storageFaultAlarm = (alarm >>> 2 & 1) == 1; jtVideoAlarm.setOtherDeviceFailure((alarm >>> 3 & 1) == 1); jtVideoAlarm.setOvercrowding((alarm >>> 4 & 1) == 1); boolean drivingAlarm = (alarm >>> 5 & 1) == 1; jtVideoAlarm.setSpecialRecordFull((alarm >>> 6 & 1) == 1); if (lossAlarm && loss > 0) { List videoLossChannels = new ArrayList<>(); for (int i = 0; i < 32; i++) { if ((loss >>> i & 1) == 1 ) { videoLossChannels.add(i); } } jtVideoAlarm.setVideoLossChannels(videoLossChannels); } if (occlusionAlarm && occlusion > 0) { List videoOcclusionChannels = new ArrayList<>(); for (int i = 0; i < 32; i++) { if ((occlusion >>> i & 1) == 1) { videoOcclusionChannels.add(i); } } jtVideoAlarm.setVideoOcclusionChannels(videoOcclusionChannels); } if (storageFaultAlarm && storageFault > 0) { List storageFaultAlarmContent = new ArrayList<>(); for (int i = 0; i < 16; i++) { if ((storageFault >>> i & 1) == 1) { storageFaultAlarmContent.add(i); } } jtVideoAlarm.setStorageFaultAlarm(storageFaultAlarmContent); } if (drivingAlarm && driving > 0) { jtVideoAlarm.setDrivingForFatigue((driving & 1) == 1 ); jtVideoAlarm.setDrivingForCall((driving >>> 1 & 1) == 1 ); jtVideoAlarm.setDrivingSmoking((driving >>> 2 & 1) == 1 ); } return jtVideoAlarm; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/common/ConfigAttribute.java ================================================ package com.genersoft.iot.vmp.jt1078.bean.common; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.RUNTIME) public @interface ConfigAttribute { long id(); String type(); String description(); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/config/JTAlarmRecordingParam.java ================================================ package com.genersoft.iot.vmp.jt1078.bean.config; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.Getter; import lombok.Setter; /** * 特殊报警录像参数 */ @Setter @Getter public class JTAlarmRecordingParam implements JTDeviceSubConfig{ /** * 特殊报警录像存储阈值, 百分比,取值 特殊报警录像占用主存储器存储阈值百 1 ~ 99,默认值为 20 */ private int storageLimit; /** * 特殊报警录像持续时间,特殊报警录像的最长持续时间,单位为分钟(min) ,默认值为 5 */ private int duration; /** * 特殊报警标识起始时间, 特殊报警发生前进行标记的录像时间, 单位为分钟( min) ,默认值为 1 */ private int startTime; @Override public ByteBuf encode() { ByteBuf byteBuf = Unpooled.buffer(); byteBuf.writeByte(storageLimit); byteBuf.writeByte(duration); byteBuf.writeByte(startTime); return byteBuf; } public static JTAlarmRecordingParam decode(ByteBuf byteBuf) { JTAlarmRecordingParam alarmRecordingParam = new JTAlarmRecordingParam(); alarmRecordingParam.setStorageLimit(byteBuf.readUnsignedByte()); alarmRecordingParam.setDuration(byteBuf.readUnsignedByte()); alarmRecordingParam.setStartTime(byteBuf.readUnsignedByte()); return alarmRecordingParam; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/config/JTAloneChanel.java ================================================ package com.genersoft.iot.vmp.jt1078.bean.config; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.Getter; import lombok.Setter; /** * 单独通道视频 */ @Setter @Getter public class JTAloneChanel implements JTDeviceSubConfig{ /** * 逻辑通道号 */ private int logicChannelId; /** * 实时流编码模式 * 0:CBR( 固定码率) ; * 1:VBR( 可变码率) ; * 2:ABR( 平均码率) ; * 100 ~ 127:自定义 */ private int liveStreamCodeRateType; /** * 实时流分辨率 * 0:QCIF; * 1:CIF; * 2:WCIF; * 3:D1; * 4:WD1; * 5:720P; * 6:1 080P; * 100 ~ 127:自定义 */ private int liveStreamResolving; /** * 实时流关键帧间隔, 范围(1 ~ 1 000) 帧 */ private int liveStreamIInterval; /** * 实时流目标帧率,范围(1 ~ 120) 帧 / s */ private int liveStreamFrameRate; /** * 实时流目标码率,单位为千位每秒( kbps) */ private long liveStreamCodeRate; /** * 存储流编码模式 * 0:CBR( 固定码率) ; * 1:VBR( 可变码率) ; * 2:ABR( 平均码率) ; * 100 ~ 127:自定义 */ private int storageStreamCodeRateType; /** * 存储流分辨率 * 0:QCIF; * 1:CIF; * 2:WCIF; * 3:D1; * 4:WD1; * 5:720P; * 6:1 080P; * 100 ~ 127:自定义 */ private int storageStreamResolving; /** * 存储流关键帧间隔, 范围(1 ~ 1 000) 帧 */ private int storageStreamIInterval; /** * 存储流目标帧率,范围(1 ~ 120) 帧 / s */ private int storageStreamFrameRate; /** * 存储流目标码率,单位为千位每秒( kbps) */ private long storageStreamCodeRate; /** * 字幕叠加设置 */ private JTOSDConfig osd; @Override public ByteBuf encode() { ByteBuf byteBuf = Unpooled.buffer(); byteBuf.writeByte(logicChannelId); byteBuf.writeByte(liveStreamCodeRateType); byteBuf.writeByte(liveStreamResolving); byteBuf.writeShort((short)(liveStreamIInterval & 0xffff)); byteBuf.writeByte(liveStreamFrameRate); byteBuf.writeInt((int) (liveStreamCodeRate & 0xffffffffL)); byteBuf.writeByte(storageStreamCodeRateType); byteBuf.writeByte(storageStreamResolving); byteBuf.writeShort((short)(storageStreamIInterval & 0xffff)); byteBuf.writeByte(storageStreamFrameRate); byteBuf.writeInt((int) (storageStreamCodeRate & 0xffffffffL)); byteBuf.writeBytes(osd.encode()); return byteBuf; } public static JTAloneChanel decode(ByteBuf buf) { JTAloneChanel jtAloneChanel = new JTAloneChanel(); jtAloneChanel.setLogicChannelId(buf.readByte()); jtAloneChanel.setLiveStreamCodeRateType(buf.readByte()); jtAloneChanel.setLiveStreamResolving(buf.readByte()); jtAloneChanel.setLiveStreamIInterval(buf.readUnsignedShort()); jtAloneChanel.setLiveStreamFrameRate(buf.readByte()); jtAloneChanel.setLiveStreamCodeRate(buf.readUnsignedInt()); jtAloneChanel.setStorageStreamCodeRateType(buf.readByte()); jtAloneChanel.setStorageStreamResolving(buf.readByte()); jtAloneChanel.setStorageStreamIInterval(buf.readUnsignedShort()); jtAloneChanel.setStorageStreamFrameRate(buf.readByte()); jtAloneChanel.setStorageStreamCodeRate(buf.readUnsignedInt()); jtAloneChanel.setOsd(JTOSDConfig.decode(buf)); return null; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/config/JTAnalyzeAlarmParam.java ================================================ package com.genersoft.iot.vmp.jt1078.bean.config; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.Getter; import lombok.Setter; /** * 视频分析报警参数 */ @Setter @Getter public class JTAnalyzeAlarmParam implements JTDeviceSubConfig{ /** * 车辆核载人数 */ private int numberForPeople; /** * 疲劳程度阈值 */ private int fatigueThreshold; @Override public ByteBuf encode() { ByteBuf byteBuf = Unpooled.buffer(); byteBuf.writeByte(numberForPeople); byteBuf.writeByte(fatigueThreshold); return byteBuf; } public static JTAnalyzeAlarmParam decode(ByteBuf byteBuf) { JTAnalyzeAlarmParam analyzeAlarmParam = new JTAnalyzeAlarmParam(); analyzeAlarmParam.setNumberForPeople(byteBuf.readUnsignedByte()); analyzeAlarmParam.setFatigueThreshold(byteBuf.readUnsignedByte()); return analyzeAlarmParam; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/config/JTAwakenParam.java ================================================ package com.genersoft.iot.vmp.jt1078.bean.config; import com.genersoft.iot.vmp.jt1078.util.BCDUtil; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.Getter; import lombok.Setter; /** * 终端休眠唤醒模式设置 */ @Setter @Getter public class JTAwakenParam implements JTDeviceSubConfig{ /** * 休眠唤醒模式-条件唤醒 */ private boolean wakeUpModeByCondition; /** * 休眠唤醒模式-定时唤醒 */ private boolean wakeUpModeByTime; /** * 休眠唤醒模式-手动唤醒 */ private boolean wakeUpModeByManual; /** * 唤醒条件类型-紧急报警 */ private boolean wakeUpConditionsByAlarm; /** * 唤醒条件类型-碰撞侧翻报警 */ private boolean wakeUpConditionsByRollover; /** * 唤醒条件类型-车辆开门 */ private boolean wakeUpConditionsByOpenTheDoor; /** * 定时唤醒日设置-周一 */ private boolean awakeningDayForMonday; /** * 定时唤醒日设置-周二 */ private boolean awakeningDayForTuesday; /** * 定时唤醒日设置-周三 */ private boolean awakeningDayForWednesday; /** * 定时唤醒日设置-周四 */ private boolean awakeningDayForThursday; /** * 定时唤醒日设置-周五 */ private boolean awakeningDayForFriday; /** * 定时唤醒日设置-周六 */ private boolean awakeningDayForSaturday; /** * 定时唤醒日设置-周日 */ private boolean awakeningDayForSunday; /** * 日定时唤醒-启用时间段1 */ private boolean time1Enable; /** * 日定时唤醒-时间段1开始时间 */ private String time1StartTime; /** * 日定时唤醒-时间段1结束时间 */ private String time1EndTime; /** * 日定时唤醒-启用时间段2 */ private boolean time2Enable; /** * 日定时唤醒-时间段2开始时间 */ private String time2StartTime; /** * 日定时唤醒-时间段2结束时间 */ private String time2EndTime; /** * 日定时唤醒-启用时间段3 */ private boolean time3Enable; /** * 日定时唤醒-时间段3开始时间 */ private String time3StartTime; /** * 日定时唤醒-时间段3结束时间 */ private String time3EndTime; /** * 日定时唤醒-启用时间段4 */ private boolean time4Enable; /** * 日定时唤醒-时间段4开始时间 */ private String time4StartTime; /** * 日定时唤醒-时间段4结束时间 */ private String time4EndTime; @Override public ByteBuf encode() { ByteBuf byteBuf = Unpooled.buffer(); byte wakeUpTypeByte = 0; byte wakeUpConditionsByte = 0; byte wakeDayByte = 0; if (wakeUpModeByCondition) { wakeUpTypeByte = (byte)(wakeUpTypeByte | 1); } if (wakeUpModeByTime) { wakeUpTypeByte = (byte)(wakeUpTypeByte | (1 << 1)); } if (wakeUpModeByManual) { wakeUpTypeByte = (byte)(wakeUpTypeByte | (1 << 2)); } byteBuf.writeByte(wakeUpTypeByte); if (wakeUpConditionsByAlarm) { wakeUpConditionsByte = (byte)(wakeUpConditionsByte | 1); } if (wakeUpConditionsByRollover) { wakeUpConditionsByte = (byte)(wakeUpConditionsByte | (1 << 1)); } if (wakeUpConditionsByOpenTheDoor) { wakeUpConditionsByte = (byte)(wakeUpConditionsByte | (1 << 2)); } byteBuf.writeByte(wakeUpConditionsByte); if (awakeningDayForMonday) { wakeDayByte = (byte)(wakeDayByte | 1); } if (awakeningDayForTuesday) { wakeDayByte = (byte)(wakeDayByte | (1 << 1)); } if (awakeningDayForWednesday) { wakeDayByte = (byte)(wakeDayByte | (1 << 2)); } if (awakeningDayForThursday) { wakeDayByte = (byte)(wakeDayByte | (1 << 3)); } if (awakeningDayForFriday) { wakeDayByte = (byte)(wakeDayByte | (1 << 4)); } if (awakeningDayForSaturday) { wakeDayByte = (byte)(wakeDayByte | (1 << 5)); } if (awakeningDayForSunday) { wakeDayByte = (byte)(wakeDayByte | (1 << 6)); } byteBuf.writeByte(wakeDayByte); byte enableByte = 0; if (time1Enable) { enableByte = (byte)(enableByte | 1); } if (time2Enable) { enableByte = (byte)(enableByte | (1 << 1)); } if (time3Enable) { enableByte = (byte)(enableByte | (1 << 2)); } if (time4Enable) { enableByte = (byte)(enableByte | (1 << 3)); } byteBuf.writeByte(enableByte); byteBuf.writeBytes(transportTime(time1StartTime)); byteBuf.writeBytes(transportTime(time1EndTime)); byteBuf.writeBytes(transportTime(time2StartTime)); byteBuf.writeBytes(transportTime(time2EndTime)); byteBuf.writeBytes(transportTime(time3StartTime)); byteBuf.writeBytes(transportTime(time3EndTime)); byteBuf.writeBytes(transportTime(time4StartTime)); byteBuf.writeBytes(transportTime(time4EndTime)); return byteBuf; } private byte[] transportTime(String time) { return BCDUtil.strToBcd(time.replace(":", "")); } public static JTAwakenParam decode(ByteBuf byteBuf) { JTAwakenParam awakenParam = new JTAwakenParam(); short wakeUpTypeByte = byteBuf.readUnsignedByte(); awakenParam.wakeUpModeByCondition = ((wakeUpTypeByte & 1) == 1); awakenParam.wakeUpModeByTime = ((wakeUpTypeByte >>> 1 & 1) == 1); awakenParam.wakeUpModeByManual = ((wakeUpTypeByte >>> 2 & 1) == 1); short wakeUpConditionsByte = byteBuf.readUnsignedByte(); awakenParam.wakeUpConditionsByAlarm = ((wakeUpConditionsByte & 1) == 1); awakenParam.wakeUpConditionsByRollover = ((wakeUpConditionsByte >>> 1 & 1) == 1); awakenParam.wakeUpConditionsByOpenTheDoor = ((wakeUpConditionsByte >>> 2 & 1) == 1); short wakeDayByte = byteBuf.readUnsignedByte(); awakenParam.awakeningDayForMonday = ((wakeDayByte & 1) == 1); awakenParam.awakeningDayForTuesday = ((wakeDayByte >>> 1 & 1) == 1); awakenParam.awakeningDayForWednesday = ((wakeDayByte >>> 2 & 1) == 1); awakenParam.awakeningDayForThursday = ((wakeDayByte >>> 3 & 1) == 1); awakenParam.awakeningDayForFriday = ((wakeDayByte >>> 4 & 1) == 1); awakenParam.awakeningDayForSaturday = ((wakeDayByte >>> 5 & 1) == 1); awakenParam.awakeningDayForSunday = ((wakeDayByte >>> 6 & 1) == 1); short enableByte = byteBuf.readUnsignedByte(); awakenParam.time1Enable = ((enableByte & 1) == 1); awakenParam.time2Enable = ((enableByte >>> 1 & 1) == 1); awakenParam.time3Enable = ((enableByte >>> 2 & 1) == 1); awakenParam.time4Enable = ((enableByte >>> 3 & 1) == 1); byte[] timeBytes = new byte[2]; byteBuf.readBytes(timeBytes); awakenParam.time1StartTime = transportTime(timeBytes); byteBuf.readBytes(timeBytes); awakenParam.time1EndTime = transportTime(timeBytes); byteBuf.readBytes(timeBytes); awakenParam.time2StartTime = transportTime(timeBytes); byteBuf.readBytes(timeBytes); awakenParam.time2EndTime = transportTime(timeBytes); byteBuf.readBytes(timeBytes); awakenParam.time3StartTime = transportTime(timeBytes); byteBuf.readBytes(timeBytes); awakenParam.time3EndTime = transportTime(timeBytes); byteBuf.readBytes(timeBytes); awakenParam.time4StartTime = transportTime(timeBytes); byteBuf.readBytes(timeBytes); awakenParam.time4EndTime = transportTime(timeBytes); return awakenParam; } private static String transportTime(byte[] timeBytes) { String time1Str = BCDUtil.transform(timeBytes); return time1Str.replace(time1Str.substring(0, 2), time1Str.substring(0, 2) + ":"); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/config/JTCameraTimer.java ================================================ package com.genersoft.iot.vmp.jt1078.bean.config; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.Getter; import lombok.Setter; /** * 定时拍照控制 */ @Setter @Getter public class JTCameraTimer implements JTDeviceSubConfig{ /** * 摄像通道1 定时拍照开关标志 */ private boolean switchForChannel1; /** * 摄像通道2 定时拍照开关标志 */ private boolean switchForChannel2; /** * 摄像通道3 定时拍照开关标志 */ private boolean switchForChannel3; /** * 摄像通道4 定时拍照开关标志 */ private boolean switchForChannel4; /** * 摄像通道5 定时拍照开关标志 */ private boolean switchForChannel5; /** * 摄像通道1 定时拍照存储标志, true: 上传, false: 存储 */ private boolean storageFlagsForChannel1; /** * 摄像通道2 定时拍照存储标志 true: 上传, false: 存储 */ private boolean storageFlagsForChannel2; /** * 摄像通道3 定时拍照存储标志 true: 上传, false: 存储 */ private boolean storageFlagsForChannel3; /** * 摄像通道4 定时拍照存储标志 true: 上传, false: 存储 */ private boolean storageFlagsForChannel4; /** * 摄像通道5 定时拍照存储标志 true: 上传, false: 存储 */ private boolean storageFlagsForChannel5; /** * 定时时间单位,true: 分, false: 秒,当数值小于5s时,终端按5s处理 */ private boolean timeUnit; /** * 定时时间间隔 */ private Integer timeInterval; @Override public ByteBuf encode() { ByteBuf byteBuf = Unpooled.buffer(); byte[] bytes = new byte[4]; bytes[0] = 0; if (switchForChannel1) { bytes[0] = (byte)(bytes[0] | 1); } if (switchForChannel2) { bytes[0] = (byte)(bytes[0] | 2); } if (switchForChannel3) { bytes[0] = (byte)(bytes[0] | 4); } if (switchForChannel4) { bytes[0] = (byte)(bytes[0] | 8); } if (switchForChannel5) { bytes[0] = (byte)(bytes[0] | 16); } bytes[1] = 0; if (storageFlagsForChannel1) { bytes[1] = (byte)(bytes[1] | 1); } if (storageFlagsForChannel2) { bytes[1] = (byte)(bytes[1] | 2); } if (storageFlagsForChannel3) { bytes[1] = (byte)(bytes[1] | 4); } if (storageFlagsForChannel4) { bytes[1] = (byte)(bytes[1] | 8); } if (storageFlagsForChannel5) { bytes[1] = (byte)(bytes[1] | 16); } bytes[3] = (byte)(timeInterval & 0xfe); if (timeUnit) { bytes[3] = (byte)(bytes[3] | 1); } byteBuf.writeBytes(bytes); return byteBuf; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/config/JTChanelConfig.java ================================================ package com.genersoft.iot.vmp.jt1078.bean.config; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.Getter; import lombok.Setter; /** * 音视频通道 */ @Setter @Getter public class JTChanelConfig implements JTDeviceSubConfig{ /** * 物理通道号 单独 */ private int physicalChannelId; /** * 逻辑通道号 */ private int logicChannelId; /** * 通道类型: * 0:音视频; * 1:音频 * 2:视频 */ private int channelType; /** * 是否连接云台: 通道类型为 0 和 2 时,此字段有效 * 0:未连接;1:连接 */ private int ptzEnable; @Override public ByteBuf encode() { ByteBuf byteBuf = Unpooled.buffer(); byteBuf.writeByte(physicalChannelId); byteBuf.writeByte(logicChannelId); byteBuf.writeByte(channelType); byteBuf.writeByte(ptzEnable); return byteBuf; } public static JTChanelConfig decode(ByteBuf byteBuf) { JTChanelConfig jtChanel = new JTChanelConfig(); jtChanel.setPhysicalChannelId(byteBuf.readUnsignedByte()); jtChanel.setLogicChannelId(byteBuf.readUnsignedByte()); jtChanel.setChannelType(byteBuf.readUnsignedByte()); jtChanel.setPtzEnable(byteBuf.readUnsignedByte()); return jtChanel; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/config/JTChannelListParam.java ================================================ package com.genersoft.iot.vmp.jt1078.bean.config; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.Getter; import lombok.Setter; import java.util.ArrayList; import java.util.List; /** * 音视频通道列表设置 */ @Setter @Getter public class JTChannelListParam implements JTDeviceSubConfig{ /** * 音视频通道总数 */ private int videoAndAudioCount; /** * 音频通道总数 */ private int audioCount; /** * 视频通道总数 */ private int videoCount; private List chanelList; @Override public ByteBuf encode() { ByteBuf byteBuf = Unpooled.buffer(); byteBuf.writeByte(videoAndAudioCount); byteBuf.writeByte(audioCount); byteBuf.writeByte(videoCount); for (JTChanelConfig jtChanel : chanelList) { byteBuf.writeBytes(jtChanel.encode()); } return byteBuf; } public static JTChannelListParam decode(ByteBuf byteBuf) { JTChannelListParam channelListParam = new JTChannelListParam(); channelListParam.setVideoAndAudioCount(byteBuf.readUnsignedByte()); channelListParam.setAudioCount(byteBuf.readUnsignedByte()); channelListParam.setVideoCount(byteBuf.readUnsignedByte()); int total = channelListParam.getVideoAndAudioCount() + channelListParam.getVideoCount() + channelListParam.getAudioCount(); List chanelList = new ArrayList<>(total); for (int i = 0; i < total; i++) { chanelList.add(JTChanelConfig.decode(byteBuf)); } channelListParam.setChanelList(chanelList); return channelListParam; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/config/JTChannelParam.java ================================================ package com.genersoft.iot.vmp.jt1078.bean.config; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.Getter; import lombok.Setter; import java.util.ArrayList; import java.util.List; /** * 单独视频通道参数设置 */ @Setter @Getter public class JTChannelParam implements JTDeviceSubConfig { /** * 单独通道视频参数设置列表 */ private List jtAloneChanelList; @Override public ByteBuf encode() { ByteBuf byteBuf = Unpooled.buffer(); byteBuf.writeByte(jtAloneChanelList.size()); for (JTAloneChanel jtAloneChanel : jtAloneChanelList) { if (jtAloneChanel == null) { continue; } byteBuf.writeBytes(jtAloneChanel.encode()); } return byteBuf; } public static JTChannelParam decode(ByteBuf byteBuf) { JTChannelParam channelParam = new JTChannelParam(); int length = byteBuf.readUnsignedByte(); List jtAloneChanelList = new ArrayList<>(length); for (int i = 0; i < length; i++) { jtAloneChanelList.add(JTAloneChanel.decode(byteBuf)); } channelParam.setJtAloneChanelList(jtAloneChanelList); return channelParam; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/config/JTCollisionAlarmParams.java ================================================ package com.genersoft.iot.vmp.jt1078.bean.config; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.Getter; import lombok.Setter; /** * 碰撞报警参数设置 */ @Setter @Getter public class JTCollisionAlarmParams implements JTDeviceSubConfig{ /** * 碰撞时间 单位为毫秒(ms) */ private int collisionAlarmTime; /** * 碰撞加速度 单位为0.1g,设置范围为0~79,默认为10 */ private int collisionAcceleration; @Override public ByteBuf encode() { ByteBuf byteBuf = Unpooled.buffer(); byte[] bytes = new byte[2]; bytes[0] = (byte) (collisionAlarmTime & 0xff); bytes[1] = (byte) (collisionAcceleration & 0xff); byteBuf.writeBytes(bytes); return byteBuf; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/config/JTDeviceSubConfig.java ================================================ package com.genersoft.iot.vmp.jt1078.bean.config; import io.netty.buffer.ByteBuf; public interface JTDeviceSubConfig { ByteBuf encode(); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/config/JTGnssPositioningMode.java ================================================ package com.genersoft.iot.vmp.jt1078.bean.config; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.Getter; import lombok.Setter; /** * GNSS 定位模式 */ @Setter @Getter public class JTGnssPositioningMode implements JTDeviceSubConfig{ /** * GPS 定位 true: 开启, false: 关闭 */ private boolean gps; /** * 北斗定位 true: 开启, false: 关闭 */ private boolean beidou; /** * GLONASS定位 true: 开启, false: 关闭 */ private boolean glonass; /** * GaLiLeo定位 true: 开启, false: 关闭 */ private boolean gaLiLeo; @Override public ByteBuf encode() { ByteBuf byteBuf = Unpooled.buffer(); byte[] bytes = new byte[1]; bytes[0] = 0; if (gps) { bytes[0] = (byte)(bytes[0] | 1); } if (beidou) { bytes[0] = (byte)(bytes[0] | 2); } if (glonass) { bytes[0] = (byte)(bytes[0] | 4); } if (gaLiLeo) { bytes[0] = (byte)(bytes[0] | 8); } byteBuf.writeBytes(bytes); return byteBuf; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/config/JTIllegalDrivingPeriods.java ================================================ package com.genersoft.iot.vmp.jt1078.bean.config; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.Getter; import lombok.Setter; /** * 违规行驶时段范围 ,精确到分 */ @Setter @Getter public class JTIllegalDrivingPeriods implements JTDeviceSubConfig{ /** * 违规行驶时段-开始时间 HH:mm */ private String startTime; /** * 违规行驶时段-结束时间 HH:mm */ private String endTime; @Override public ByteBuf encode() { ByteBuf byteBuf = Unpooled.buffer(); byte[] bytes = new byte[4]; String[] startTimeArray = startTime.split(":"); String[] endTimeArray = endTime.split(":"); bytes[0] = (byte)Integer.parseInt(startTimeArray[0]); bytes[1] = (byte)Integer.parseInt(startTimeArray[1]); bytes[2] = (byte)Integer.parseInt(endTimeArray[0]); bytes[3] = (byte)Integer.parseInt(endTimeArray[1]); byteBuf.writeBytes(bytes); return byteBuf; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/config/JTOSDConfig.java ================================================ package com.genersoft.iot.vmp.jt1078.bean.config; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.Getter; import lombok.Setter; /** * OSD字幕叠加设置 */ @Setter @Getter public class JTOSDConfig { /** * 日期和时间 */ private boolean time; /** * 车牌号码 */ private boolean licensePlate; /** * 逻辑通道号 */ private boolean channelId; /** * 经纬度 */ private boolean position; /** * 行驶记录速度 */ private boolean speed; /** * 卫星定位速度 */ private boolean speedForGPS; /** * 连续驾驶时间 */ private boolean drivingTime; public ByteBuf encode(){ ByteBuf byteBuf = Unpooled.buffer(); byte content = 0; if (time) { content = (byte)(content | 1); } if (licensePlate) { content = (byte)(content | (1 << 1)); } if (channelId) { content = (byte)(content | (1 << 2)); } if (position) { content = (byte)(content | (1 << 3)); } if (speed) { content = (byte)(content | (1 << 4)); } if (speedForGPS) { content = (byte)(content | (1 << 5)); } if (drivingTime) { content = (byte)(content | (1 << 6)); } byteBuf.writeByte(content); byteBuf.writeByte(0); return byteBuf; } public static JTOSDConfig decode(ByteBuf buf) { JTOSDConfig config = new JTOSDConfig(); int content = buf.readUnsignedShort(); config.setTime((content & 1) == 1); config.setLicensePlate((content >>> 1 & 1) == 1); config.setChannelId((content >>> 2 & 1) == 1); config.setPosition((content >>> 3 & 1) == 1); config.setSpeed((content >>> 4 & 1) == 1); config.setSpeedForGPS((content >>> 5 & 1) == 1); config.setDrivingTime((content >>> 6 & 1) == 1); return config; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/config/JTVideoAlarmBit.java ================================================ package com.genersoft.iot.vmp.jt1078.bean.config; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; /** * 视频报警标志位 */ public class JTVideoAlarmBit implements JTDeviceSubConfig{ /** * 视频信号丢失报警 */ private boolean lossSignal; /** * 视频信号遮挡报警 */ private boolean occlusionSignal; /** * 存储单元故障报警 */ private boolean storageFault; /** * 其他视频设备故障报警 */ private boolean otherDeviceFailure; /** * 客车超员报警 */ private boolean overcrowding; /** * 异常驾驶行为报警 */ private boolean abnormalDriving; /** * 特殊报警录像达到存储阈值报警 */ private boolean storageLimit; public boolean isLossSignal() { return lossSignal; } public void setLossSignal(boolean lossSignal) { this.lossSignal = lossSignal; } public boolean isOcclusionSignal() { return occlusionSignal; } public void setOcclusionSignal(boolean occlusionSignal) { this.occlusionSignal = occlusionSignal; } public boolean isStorageFault() { return storageFault; } public void setStorageFault(boolean storageFault) { this.storageFault = storageFault; } public boolean isOtherDeviceFailure() { return otherDeviceFailure; } public void setOtherDeviceFailure(boolean otherDeviceFailure) { this.otherDeviceFailure = otherDeviceFailure; } public boolean isOvercrowding() { return overcrowding; } public void setOvercrowding(boolean overcrowding) { this.overcrowding = overcrowding; } public boolean isAbnormalDriving() { return abnormalDriving; } public void setAbnormalDriving(boolean abnormalDriving) { this.abnormalDriving = abnormalDriving; } public boolean isStorageLimit() { return storageLimit; } public void setStorageLimit(boolean storageLimit) { this.storageLimit = storageLimit; } @Override public ByteBuf encode() { ByteBuf byteBuf = Unpooled.buffer(); byte content = 0; if (lossSignal) { content = content |= 1; } if (occlusionSignal) { content = content |= (1 << 1); } if (storageFault) { content = content |= (1 << 2); } if (otherDeviceFailure) { content = content |= (1 << 3); } if (overcrowding) { content = content |= (1 << 4); } if (abnormalDriving) { content = content |= (1 << 5); } if (storageLimit) { content = content |= (1 << 6); } byteBuf.writeByte(content); byteBuf.writeByte(0); byteBuf.writeByte(0); byteBuf.writeByte(0); return byteBuf; } public static JTVideoAlarmBit decode(ByteBuf byteBuf) { JTVideoAlarmBit videoAlarmBit = new JTVideoAlarmBit(); byte content = byteBuf.readByte(); videoAlarmBit.setLossSignal((content & 1) == 1); videoAlarmBit.setOcclusionSignal((content >>> 1 & 1) == 1); videoAlarmBit.setStorageFault((content >>> 2 & 1) == 1); videoAlarmBit.setOtherDeviceFailure((content >>> 3 & 1) == 1); videoAlarmBit.setOvercrowding((content >>> 4 & 1) == 1); videoAlarmBit.setAbnormalDriving((content >>> 5 & 1) == 1); videoAlarmBit.setStorageLimit((content >>> 6 & 1) == 1); byteBuf.readByte(); byteBuf.readByte(); byteBuf.readByte(); return videoAlarmBit; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/bean/config/JTVideoParam.java ================================================ package com.genersoft.iot.vmp.jt1078.bean.config; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.Data; /** * 违规行驶时段范围 ,精确到分 */ @Data public class JTVideoParam implements JTDeviceSubConfig{ /** * 实时流编码模式 * 0:CBR( 固定码率) ; * 1:VBR( 可变码率) ; * 2:ABR( 平均码率) ; * 100 ~ 127:自定义 */ private int liveStreamCodeRateType; /** * 实时流分辨率 * 0:QCIF; * 1:CIF; * 2:WCIF; * 3:D1; * 4:WD1; * 5:720P; * 6:1 080P; * 100 ~ 127:自定义 */ private int liveStreamResolving; /** * 实时流关键帧间隔, 范围(1 ~ 1 000) 帧 */ private int liveStreamIInterval; /** * 实时流目标帧率,范围(1 ~ 120) 帧 / s */ private int liveStreamFrameRate; /** * 实时流目标码率,单位为千位每秒( kbps) */ private long liveStreamCodeRate; /** * 存储流编码模式 * 0:CBR( 固定码率) * 1:VBR( 可变码率) * 2:ABR( 平均码率) * 100 ~ 127:自定义 */ private int storageStreamCodeRateType; /** * 存储流分辨率 * 0:QCIF; * 1:CIF; * 2:WCIF; * 3:D1; * 4:WD1; * 5:720P; * 6:1 080P; * 100 ~ 127:自定义 */ private int storageStreamResolving; /** * 存储流关键帧间隔, 范围(1 ~ 1 000) 帧 */ private int storageStreamIInterval; /** * 存储流目标帧率,范围(1 ~ 120) 帧 / s */ private int storageStreamFrameRate; /** * 存储流目标码率,单位为千位每秒( kbps) */ private long storageStreamCodeRate; /** * 字幕叠加设置 */ private JTOSDConfig osd; /** * 是否启用音频输出, 0:不启用;1:启用 */ private int audioEnable; @Override public ByteBuf encode() { ByteBuf byteBuf = Unpooled.buffer(); byteBuf.writeByte(liveStreamCodeRateType); byteBuf.writeByte(liveStreamResolving); byteBuf.writeShort((short)(liveStreamIInterval & 0xffff)); byteBuf.writeByte(liveStreamFrameRate); byteBuf.writeInt((int) (liveStreamCodeRate & 0xffffffffL)); byteBuf.writeByte(storageStreamCodeRateType); byteBuf.writeByte(storageStreamResolving); byteBuf.writeShort((short)(storageStreamIInterval & 0xffff)); byteBuf.writeByte(storageStreamFrameRate); byteBuf.writeInt((int) (storageStreamCodeRate & 0xffffffffL)); byteBuf.writeBytes(osd.encode()); byteBuf.writeByte(audioEnable); return byteBuf; } public static JTVideoParam decode(ByteBuf buf) { JTVideoParam videoParam = new JTVideoParam(); videoParam.setLiveStreamCodeRateType(buf.readByte()); videoParam.setLiveStreamResolving(buf.readByte()); videoParam.setLiveStreamIInterval(buf.readUnsignedShort()); videoParam.setLiveStreamFrameRate(buf.readByte()); videoParam.setLiveStreamCodeRate(buf.readUnsignedInt()); videoParam.setStorageStreamCodeRateType(buf.readByte()); videoParam.setStorageStreamResolving(buf.readByte()); videoParam.setStorageStreamIInterval(buf.readUnsignedShort()); videoParam.setStorageStreamFrameRate(buf.readByte()); videoParam.setStorageStreamCodeRate(buf.readUnsignedInt()); videoParam.setOsd(JTOSDConfig.decode(buf)); videoParam.setAudioEnable(buf.readByte()); return null; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/cmd/JT1078Template.java ================================================ package com.genersoft.iot.vmp.jt1078.cmd; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.jt1078.proc.entity.Cmd; import com.genersoft.iot.vmp.jt1078.proc.response.*; import com.genersoft.iot.vmp.jt1078.session.SessionManager; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import org.springframework.stereotype.Component; import java.util.Random; /** * @author QingtaiJiang * @date 2023/4/27 18:58 * @email qingtaij@163.com */ @Component public class JT1078Template { private final Random random = new Random(); private static final String H8103 = "8103"; private static final String H8104 = "8104"; private static final String H8105 = "8105"; private static final String H8106 = "8106"; private static final String H8107 = "8107"; private static final String H8201 = "8201"; private static final String H8202 = "8202"; private static final String H8203 = "8203"; private static final String H8204 = "8204"; private static final String H8300 = "8300"; private static final String H8400 = "8400"; private static final String H8401 = "8401"; private static final String H8500 = "8500"; private static final String H8600 = "8600"; private static final String H8601 = "8601"; private static final String H8602 = "8602"; private static final String H8603 = "8603"; private static final String H8604 = "8604"; private static final String H8605 = "8605"; private static final String H8606 = "8606"; private static final String H8607 = "8607"; private static final String H8608 = "8608"; private static final String H8702 = "8702"; private static final String H8801 = "8801"; private static final String H8802 = "8802"; private static final String H8803 = "8803"; private static final String H8804 = "8804"; private static final String H8805 = "8805"; private static final String H9003 = "9003"; private static final String H9101 = "9101"; private static final String H9102 = "9102"; private static final String H9201 = "9201"; private static final String H9202 = "9202"; private static final String H9205 = "9205"; private static final String H9206 = "9206"; private static final String H9207 = "9207"; private static final String H9301 = "9301"; private static final String H9302 = "9302"; private static final String H9303 = "9303"; private static final String H9304 = "9304"; private static final String H9305 = "9305"; private static final String H9306 = "9306"; private static final String H0001 = "0001"; private static final String H0104 = "0104"; private static final String H0107 = "0107"; private static final String H0201 = "0201"; private static final String H0500 = "0500"; private static final String H0608 = "0608"; private static final String H0702 = "0702"; private static final String H0801 = "0801"; private static final String H0802 = "0802"; private static final String H0805 = "0805"; private static final String H1003 = "1003"; private static final String H1205 = "1205"; public void checkTerminalStatus(String devId){ if (SessionManager.INSTANCE.get(devId) == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "终端不在线"); } } /** * 开启直播视频 * * @param devId 设备号 * @param j9101 开启视频参数 */ public Object startLive(String devId, J9101 j9101, Integer timeOut) { checkTerminalStatus(devId); Cmd cmd = new Cmd.Builder() .setPhoneNumber(devId) .setPackageNo(randomInt()) .setMsgId(H9101) .setRespId(H0001) .setRs(j9101) .build(); return SessionManager.INSTANCE.request(cmd, timeOut); } /** * 关闭直播视频 * * @param devId 设备号 * @param j9102 关闭视频参数 */ public Object stopLive(String devId, J9102 j9102, Integer timeOut) { checkTerminalStatus(devId); Cmd cmd = new Cmd.Builder() .setPhoneNumber(devId) .setPackageNo(randomInt()) .setMsgId(H9102) .setRespId(H0001) .setRs(j9102) .build(); return SessionManager.INSTANCE.request(cmd, timeOut); } /** * 查询音视频列表 * * @param devId 设备号 * @param j9205 查询音视频列表 */ public Object queryBackTime(String devId, J9205 j9205, Integer timeOut) { checkTerminalStatus(devId); Cmd cmd = new Cmd.Builder() .setPhoneNumber(devId) .setPackageNo(randomInt()) .setMsgId(H9205) .setRespId(H1205) .setRs(j9205) .build(); return SessionManager.INSTANCE.request(cmd, timeOut); } /** * 开启视频回放 * * @param devId 设备号 * @param j9201 视频回放参数 */ public Object startBackLive(String devId, J9201 j9201, Integer timeOut) { checkTerminalStatus(devId); Cmd cmd = new Cmd.Builder() .setPhoneNumber(devId) .setPackageNo(randomInt()) .setMsgId(H9201) .setRespId(H1205) .setRs(j9201) .build(); return SessionManager.INSTANCE.request(cmd, timeOut); } /** * 视频回放控制 * * @param devId 设备号 * @param j9202 控制视频回放参数 */ public Object controlBackLive(String devId, J9202 j9202, Integer timeOut) { checkTerminalStatus(devId); Cmd cmd = new Cmd.Builder() .setPhoneNumber(devId) .setPackageNo(randomInt()) .setMsgId(H9202) .setRespId(H0001) .setRs(j9202) .build(); return SessionManager.INSTANCE.request(cmd, timeOut); } /** * 文件上传 * * @param devId 设备号 * @param j9206 文件上传参数 */ public Object fileUpload(String devId, J9206 j9206, Integer timeOut) { checkTerminalStatus(devId); Cmd cmd = new Cmd.Builder() .setPhoneNumber(devId) .setPackageNo(randomInt()) .setMsgId(H9206) .setRespId(H0001) .setRs(j9206) .build(); return SessionManager.INSTANCE.request(cmd, timeOut); } /** * 文件上传控制 * * @param devId 设备号 * @param j9207 文件上传控制参数 */ public Object fileUploadControl(String devId, J9207 j9207, Integer timeOut) { checkTerminalStatus(devId); Cmd cmd = new Cmd.Builder() .setPhoneNumber(devId) .setPackageNo(randomInt()) .setMsgId(H9207) .setRespId(H0001) .setRs(j9207) .build(); return SessionManager.INSTANCE.request(cmd, timeOut); } /** * 云台控制指令-云台旋转 * * @param devId 设备号 * @param j9301 云台旋转参数 */ public Object ptzRotate(String devId, J9301 j9301, Integer timeOut) { checkTerminalStatus(devId); Cmd cmd = new Cmd.Builder() .setPhoneNumber(devId) .setPackageNo(randomInt()) .setMsgId(H9301) .setRespId(H0001) .setRs(j9301) .build(); return SessionManager.INSTANCE.request(cmd, timeOut); } /** * 云台控制指令-云台调整焦距控制 * * @param devId 设备号 * @param j9302 云台焦距控制参数 */ public Object ptzFocal(String devId, J9302 j9302, Integer timeOut) { checkTerminalStatus(devId); Cmd cmd = new Cmd.Builder() .setPhoneNumber(devId) .setPackageNo(randomInt()) .setMsgId(H9302) .setRespId(H0001) .setRs(j9302) .build(); return SessionManager.INSTANCE.request(cmd, timeOut); } /** * 云台控制指令-云台调整光圈控制 * * @param devId 设备号 * @param j9303 云台光圈控制参数 */ public Object ptzIris(String devId, J9303 j9303, Integer timeOut) { checkTerminalStatus(devId); Cmd cmd = new Cmd.Builder() .setPhoneNumber(devId) .setPackageNo(randomInt()) .setMsgId(H9303) .setRespId(H0001) .setRs(j9303) .build(); return SessionManager.INSTANCE.request(cmd, timeOut); } /** * 云台控制指令-云台雨刷控制 * * @param devId 设备号 * @param j9304 云台雨刷控制参数 */ public Object ptzWiper(String devId, J9304 j9304, Integer timeOut) { checkTerminalStatus(devId); Cmd cmd = new Cmd.Builder() .setPhoneNumber(devId) .setPackageNo(randomInt()) .setMsgId(H9304) .setRespId(H0001) .setRs(j9304) .build(); return SessionManager.INSTANCE.request(cmd, timeOut); } /** * 云台控制指令-红外补光控制 * * @param devId 设备号 * @param j9305 云台红外补光控制参数 */ public Object ptzSupplementaryLight(String devId, J9305 j9305, Integer timeOut) { checkTerminalStatus(devId); Cmd cmd = new Cmd.Builder() .setPhoneNumber(devId) .setPackageNo(randomInt()) .setMsgId(H9305) .setRespId(H0001) .setRs(j9305) .build(); return SessionManager.INSTANCE.request(cmd, timeOut); } /** * 云台控制指令-变倍控制 * * @param devId 设备号 * @param j9306 云台变倍控制参数 */ public Object ptzZoom(String devId, J9306 j9306, Integer timeOut) { checkTerminalStatus(devId); Cmd cmd = new Cmd.Builder() .setPhoneNumber(devId) .setPackageNo(randomInt()) .setMsgId(H9306) .setRespId(H0001) .setRs(j9306) .build(); return SessionManager.INSTANCE.request(cmd, timeOut); } /** * 查询终端参数 * * @param devId 设备号 */ public Object getDeviceConfig(String devId, J8104 j8104, Integer timeOut) { checkTerminalStatus(devId); Cmd cmd = new Cmd.Builder() .setPhoneNumber(devId) .setPackageNo(randomInt()) .setMsgId(H8104) .setRespId(H0104) .setRs(j8104) .build(); return SessionManager.INSTANCE.request(cmd, timeOut); } /** * 查询指定终端参数 * * @param devId 设备号 */ public Object getDeviceSpecifyConfig(String devId, J8106 j8106, Integer timeOut) { checkTerminalStatus(devId); Cmd cmd = new Cmd.Builder() .setPhoneNumber(devId) .setPackageNo(randomInt()) .setMsgId(H8106) .setRespId(H0104) .setRs(j8106) .build(); return SessionManager.INSTANCE.request(cmd, timeOut); } /** * 设置终端参数 * * @param devId 设备号 */ public Object setDeviceSpecifyConfig(String devId, J8103 j8103, Integer timeOut) { checkTerminalStatus(devId); Cmd cmd = new Cmd.Builder() .setPhoneNumber(devId) .setPackageNo(randomInt()) .setMsgId(H8103) .setRespId(H0001) .setRs(j8103) .build(); return SessionManager.INSTANCE.request(cmd, timeOut); } private Long randomInt() { return (long) random.nextInt(1000) + 1; } /** * 设备控制 */ public Object deviceControl(String devId, J8105 j8105, int timeOut) { checkTerminalStatus(devId); Cmd cmd = new Cmd.Builder() .setPhoneNumber(devId) .setPackageNo(randomInt()) .setMsgId(H8105) .setRespId(H0001) .setRs(j8105) .build(); return SessionManager.INSTANCE.request(cmd, timeOut); } /** * 查询终端属性 */ public Object deviceAttribute(String devId, J8107 j8107, int timeOut) { checkTerminalStatus(devId); Cmd cmd = new Cmd.Builder() .setPhoneNumber(devId) .setPackageNo(randomInt()) .setMsgId(H8107) .setRespId(H0107) .setRs(j8107) .build(); return SessionManager.INSTANCE.request(cmd, timeOut); } /** * 位置信息查询 */ public Object queryPositionInfo(String devId, J8201 j8201, int timeOut) { checkTerminalStatus(devId); Cmd cmd = new Cmd.Builder() .setPhoneNumber(devId) .setPackageNo(randomInt()) .setMsgId(H8201) .setRespId(H0201) .setRs(j8201) .build(); return SessionManager.INSTANCE.request(cmd, timeOut); } public Object tempPositionTrackingControl(String devId, J8202 j8202, int timeOut) { checkTerminalStatus(devId); Cmd cmd = new Cmd.Builder() .setPhoneNumber(devId) .setPackageNo(randomInt()) .setMsgId(H8202) .setRespId(H0001) .setRs(j8202) .build(); return SessionManager.INSTANCE.request(cmd, timeOut); } public Object confirmationAlarmMessage(String devId, J8203 j8203, int timeOut) { checkTerminalStatus(devId); Cmd cmd = new Cmd.Builder() .setPhoneNumber(devId) .setPackageNo(randomInt()) .setMsgId(H8203) .setRespId(H0001) .setRs(j8203) .build(); return SessionManager.INSTANCE.request(cmd, timeOut); } public Object linkDetection(String devId, J8204 j8204, int timeOut) { checkTerminalStatus(devId); Cmd cmd = new Cmd.Builder() .setPhoneNumber(devId) .setPackageNo(randomInt()) .setMsgId(H8204) .setRespId(H0001) .setRs(j8204) .build(); return SessionManager.INSTANCE.request(cmd, timeOut); } public Object textMessage(String devId, J8300 j8300, int timeOut) { checkTerminalStatus(devId); Cmd cmd = new Cmd.Builder() .setPhoneNumber(devId) .setPackageNo(randomInt()) .setMsgId(H8300) .setRespId(H0001) .setRs(j8300) .build(); return SessionManager.INSTANCE.request(cmd, timeOut); } public Object telephoneCallback(String devId, J8400 j8400, int timeOut) { checkTerminalStatus(devId); Cmd cmd = new Cmd.Builder() .setPhoneNumber(devId) .setPackageNo(randomInt()) .setMsgId(H8400) .setRespId(H0001) .setRs(j8400) .build(); return SessionManager.INSTANCE.request(cmd, timeOut); } public Object setPhoneBook(String devId, J8401 j8401, int timeOut) { checkTerminalStatus(devId); Cmd cmd = new Cmd.Builder() .setPhoneNumber(devId) .setPackageNo(randomInt()) .setMsgId(H8401) .setRespId(H0001) .setRs(j8401) .build(); return SessionManager.INSTANCE.request(cmd, timeOut); } public Object vehicleControl(String devId, J8500 j8500, int timeOut) { checkTerminalStatus(devId); Cmd cmd = new Cmd.Builder() .setPhoneNumber(devId) .setPackageNo(randomInt()) .setMsgId(H8500) .setRespId(H0500) .setRs(j8500) .build(); return SessionManager.INSTANCE.request(cmd, timeOut); } public Object setAreaForCircle(String devId, J8600 j8600, int timeOut) { checkTerminalStatus(devId); Cmd cmd = new Cmd.Builder() .setPhoneNumber(devId) .setPackageNo(randomInt()) .setMsgId(H8600) .setRespId(H0001) .setRs(j8600) .build(); return SessionManager.INSTANCE.request(cmd, timeOut); } public Object deleteAreaForCircle(String devId, J8601 j8601, int timeOut) { checkTerminalStatus(devId); Cmd cmd = new Cmd.Builder() .setPhoneNumber(devId) .setPackageNo(randomInt()) .setMsgId(H8601) .setRespId(H0001) .setRs(j8601) .build(); return SessionManager.INSTANCE.request(cmd, timeOut); } public Object setAreaForRectangle(String devId, J8602 j8602, int timeOut) { checkTerminalStatus(devId); Cmd cmd = new Cmd.Builder() .setPhoneNumber(devId) .setPackageNo(randomInt()) .setMsgId(H8602) .setRespId(H0001) .setRs(j8602) .build(); return SessionManager.INSTANCE.request(cmd, timeOut); } public Object deleteAreaForRectangle(String devId, J8603 j8603, int timeOut) { checkTerminalStatus(devId); Cmd cmd = new Cmd.Builder() .setPhoneNumber(devId) .setPackageNo(randomInt()) .setMsgId(H8603) .setRespId(H0001) .setRs(j8603) .build(); return SessionManager.INSTANCE.request(cmd, timeOut); } public Object setAreaForPolygon(String devId, J8604 j8604, int timeOut) { checkTerminalStatus(devId); Cmd cmd = new Cmd.Builder() .setPhoneNumber(devId) .setPackageNo(randomInt()) .setMsgId(H8604) .setRespId(H0001) .setRs(j8604) .build(); return SessionManager.INSTANCE.request(cmd, timeOut); } public Object deleteAreaForPolygon(String devId, J8605 j8605, int timeOut) { checkTerminalStatus(devId); Cmd cmd = new Cmd.Builder() .setPhoneNumber(devId) .setPackageNo(randomInt()) .setMsgId(H8605) .setRespId(H0001) .setRs(j8605) .build(); return SessionManager.INSTANCE.request(cmd, timeOut); } public Object setRoute(String devId, J8606 j8606, int timeOut) { checkTerminalStatus(devId); Cmd cmd = new Cmd.Builder() .setPhoneNumber(devId) .setPackageNo(randomInt()) .setMsgId(H8606) .setRespId(H0001) .setRs(j8606) .build(); return SessionManager.INSTANCE.request(cmd, timeOut); } public Object deleteRoute(String devId, J8607 j8607, int timeOut) { checkTerminalStatus(devId); Cmd cmd = new Cmd.Builder() .setPhoneNumber(devId) .setPackageNo(randomInt()) .setMsgId(H8607) .setRespId(H0001) .setRs(j8607) .build(); return SessionManager.INSTANCE.request(cmd, timeOut); } public Object queryAreaOrRoute(String devId, J8608 j8608, int timeOut) { checkTerminalStatus(devId); Cmd cmd = new Cmd.Builder() .setPhoneNumber(devId) .setPackageNo(randomInt()) .setMsgId(H8608) .setRespId(H0608) .setRs(j8608) .build(); return SessionManager.INSTANCE.request(cmd, timeOut); } public Object queryDriverInformation(String devId, J8702 j8702, int timeOut) { checkTerminalStatus(devId); Cmd cmd = new Cmd.Builder() .setPhoneNumber(devId) .setPackageNo(randomInt()) .setMsgId(H8702) .setRespId(H0702) .setRs(j8702) .build(); return SessionManager.INSTANCE.request(cmd, timeOut); } public Object shooting(String devId, J8801 j8801, int timeOut) { checkTerminalStatus(devId); Cmd cmd = new Cmd.Builder() .setPhoneNumber(devId) .setPackageNo(randomInt()) .setMsgId(H8801) .setRespId(H0805) .setRs(j8801) .build(); return SessionManager.INSTANCE.request(cmd, timeOut); } public Object queryMediaData(String devId, J8802 j8802, int timeOut) { checkTerminalStatus(devId); Cmd cmd = new Cmd.Builder() .setPhoneNumber(devId) .setPackageNo(randomInt()) .setMsgId(H8802) .setRespId(H0802) .setRs(j8802) .build(); return SessionManager.INSTANCE.request(cmd, timeOut); } public Object uploadMediaData(String devId, J8803 j8803, int timeOut) { checkTerminalStatus(devId); Cmd cmd = new Cmd.Builder() .setPhoneNumber(devId) .setPackageNo(randomInt()) .setMsgId(H8803) .setRespId(H0801) .setRs(j8803) .build(); return SessionManager.INSTANCE.request(cmd, timeOut); } public Object record(String devId, J8804 j8804, int timeOut) { checkTerminalStatus(devId); Cmd cmd = new Cmd.Builder() .setPhoneNumber(devId) .setPackageNo(randomInt()) .setMsgId(H8804) .setRespId(H0001) .setRs(j8804) .build(); return SessionManager.INSTANCE.request(cmd, timeOut); } public Object uploadMediaDataForSingle(String devId, J8805 j8805, int timeOut) { checkTerminalStatus(devId); Cmd cmd = new Cmd.Builder() .setPhoneNumber(devId) .setPackageNo(randomInt()) .setMsgId(H8805) .setRespId(H0801) .setRs(j8805) .build(); return SessionManager.INSTANCE.request(cmd, timeOut); } public Object queryMediaAttribute(String devId, J9003 j9003, int timeOut) { checkTerminalStatus(devId); Cmd cmd = new Cmd.Builder() .setPhoneNumber(devId) .setPackageNo(randomInt()) .setMsgId(H9003) .setRespId(H1003) .setRs(j9003) .build(); return SessionManager.INSTANCE.request(cmd, timeOut); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/codec/decode/Jt808Decoder.java ================================================ package com.genersoft.iot.vmp.jt1078.codec.decode; import com.genersoft.iot.vmp.jt1078.proc.Header; import com.genersoft.iot.vmp.jt1078.proc.factory.CodecFactory; import com.genersoft.iot.vmp.jt1078.proc.request.Re; import com.genersoft.iot.vmp.jt1078.proc.response.Rs; import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; import com.genersoft.iot.vmp.jt1078.session.Session; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.buffer.CompositeByteBuf; import io.netty.buffer.UnpooledByteBufAllocator; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.ByteToMessageDecoder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEventPublisher; import lombok.extern.slf4j.Slf4j; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * @author QingtaiJiang * @date 2023/4/27 18:10 * @email qingtaij@163.com */ @Slf4j public class Jt808Decoder extends ByteToMessageDecoder { private ApplicationEventPublisher applicationEventPublisher = null; private Ijt1078Service service = null; public Jt808Decoder(ApplicationEventPublisher applicationEventPublisher, Ijt1078Service service ) { this.applicationEventPublisher = applicationEventPublisher; this.service = service; } @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { Session session = ctx.channel().attr(Session.KEY).get(); log.info("> {} hex: 7e{}7e", session, ByteBufUtil.hexDump(in)); try { // 按照部标定义执行校验和转义 ByteBuf buf = unEscapeAndCheck(in); buf.retain(); Header header = new Header(); header.setMsgId(ByteBufUtil.hexDump(buf.readSlice(2))); header.setMsgPro(buf.readUnsignedShort()); // 从消息属性中读取是否存在分包 boolean isSubpackage = (header.getMsgPro() >>> 13 & 1) == 1; if (header.is2019Version()) { header.setVersion(buf.readUnsignedByte()); String devId = ByteBufUtil.hexDump(buf.readSlice(10)); header.setPhoneNumber(devId.replaceFirst("^0*", "")); } else { header.setPhoneNumber(ByteBufUtil.hexDump(buf.readSlice(6)).replaceFirst("^0*", "")); } header.setSn(buf.readUnsignedShort()); if (isSubpackage) { int packageCount = buf.readUnsignedShort(); int packageNumber = buf.readUnsignedShort(); log.debug("[分包消息] header: {}, 序号: {}, 总数: {}", header, packageNumber, packageCount); // 缓存带合并的分包消息 ByteBuf intactBuf = MultiPacketManager.INSTANCE.add(header, packageCount, buf); if (intactBuf == null) { return; } buf = intactBuf; } Re handler = CodecFactory.getHandler(header.getMsgId()); if (handler == null) { log.error("get msgId is null {}", header.getMsgId()); buf.release(); return; } Rs decode = handler.decode(buf, header, session, service); ApplicationEvent applicationEvent = handler.getEvent(); if (applicationEvent != null) { applicationEventPublisher.publishEvent(applicationEvent); } if (decode != null) { out.add(decode); } } finally { in.skipBytes(in.readableBytes()); } } /** * 转义与验证校验码 * * @param byteBuf 转义Buf * @return 转义好的数据 */ public ByteBuf unEscapeAndCheck(ByteBuf byteBuf) throws Exception { int low = byteBuf.readerIndex(); int high = byteBuf.writerIndex(); byte checkSum = 0; int calculationCheckSum = 0; byte aByte = byteBuf.getByte(high - 2); byte protocolEscapeFlag7d = 0x7d; //0x7d转义 byte protocolEscapeFlag01 = 0x01; //0x7e转义 byte protocolEscapeFlag02 = 0x02; if (aByte == protocolEscapeFlag7d) { byte b2 = byteBuf.getByte(high - 1); if (b2 == protocolEscapeFlag01) { checkSum = protocolEscapeFlag7d; } else if (b2 == protocolEscapeFlag02) { checkSum = 0x7e; } else { log.error("转义1异常:{}", ByteBufUtil.hexDump(byteBuf)); throw new Exception("转义错误"); } high = high - 2; } else { high = high - 1; checkSum = byteBuf.getByte(high); } List bufList = new ArrayList<>(); int index = low; while (index < high) { byte b = byteBuf.getByte(index); if (b == protocolEscapeFlag7d) { byte c = byteBuf.getByte(index + 1); if (c == protocolEscapeFlag01) { ByteBuf slice = slice0x01(byteBuf, low, index); bufList.add(slice); b = protocolEscapeFlag7d; } else if (c == protocolEscapeFlag02) { ByteBuf slice = slice0x02(byteBuf, low, index); bufList.add(slice); b = 0x7e; } else { log.error("转义2异常:{}", ByteBufUtil.hexDump(byteBuf)); throw new Exception("转义错误"); } index += 2; low = index; } else { index += 1; } calculationCheckSum = calculationCheckSum ^ b; } if (calculationCheckSum == checkSum) { if (bufList.isEmpty()) { return byteBuf.slice(low, high); } else { bufList.add(byteBuf.slice(low, high - low)); return new CompositeByteBuf(UnpooledByteBufAllocator.DEFAULT, false, bufList.size(), bufList); } } else { log.info("{} 解析校验码:{}--计算校验码:{}", ByteBufUtil.hexDump(byteBuf), checkSum, calculationCheckSum); throw new Exception("校验码错误!"); } } private ByteBuf slice0x01(ByteBuf buf, int low, int sign) { return buf.slice(low, sign - low + 1); } private ByteBuf slice0x02(ByteBuf buf, int low, int sign) { buf.setByte(sign, 0x7e); return buf.slice(low, sign - low + 1); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/codec/decode/MultiPacketManager.java ================================================ package com.genersoft.iot.vmp.jt1078.codec.decode; import com.genersoft.iot.vmp.jt1078.proc.Header; import io.netty.buffer.*; import lombok.extern.slf4j.Slf4j; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.*; import java.util.concurrent.ConcurrentHashMap; @Slf4j public enum MultiPacketManager { INSTANCE; // 用与消息的缓存 private final Map packetMap = new ConcurrentHashMap<>(); private final Map packetTimeMap = new ConcurrentHashMap<>(); MultiPacketManager() { startLister(); } /** * 增加待合并的分包,如果分包接受完毕会返回完整的数据包 */ public ByteBuf add(Header header, Integer count, ByteBuf byteBuf) { String key = header.getMsgId() + "/" + header.getPhoneNumber(); CompositeByteBuf compositeBuf = packetMap.computeIfAbsent(key, k -> new CompositeByteBuf(UnpooledByteBufAllocator.DEFAULT, false, count)); // compositeBuf.addComponent(true, byteBuf.readSlice(byteBuf.readableBytes())); compositeBuf.addComponent(true, byteBuf); packetTimeMap.put(key, System.currentTimeMillis()); if (count == compositeBuf.numComponents()) { packetMap.remove(key); packetTimeMap.remove(key); compositeBuf.retain(); return compositeBuf; } return null; } private void startLister(){ Timer timer = new Timer(); timer.schedule(new TimerTask() { @Override public void run() { long expireTime = System.currentTimeMillis() - 20 * 1000; if (!packetTimeMap.isEmpty()) { for (String key : packetTimeMap.keySet()) { if (packetTimeMap.get(key) < expireTime) { log.info("分包消息超时 key: {}", key); packetTimeMap.remove(key); packetMap.remove(key); } } } } }, 2000L, 2000L); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/codec/encode/Jt808Encoder.java ================================================ package com.genersoft.iot.vmp.jt1078.codec.encode; import com.genersoft.iot.vmp.jt1078.proc.response.Rs; import com.genersoft.iot.vmp.jt1078.session.Session; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; import lombok.extern.slf4j.Slf4j; import java.util.List; /** * @author QingtaiJiang * @date 2023/4/27 18:10 * @email qingtaij@163.com */ @Slf4j public class Jt808Encoder extends MessageToByteEncoder { @Override protected void encode(ChannelHandlerContext ctx, Rs msg, ByteBuf out) throws Exception { Session session = ctx.channel().attr(Session.KEY).get(); List encodeList = Jt808EncoderCmd.encode(msg, session, session.nextSerialNo()); if(encodeList!=null && !encodeList.isEmpty()){ for (ByteBuf byteBuf : encodeList) { log.debug("< {} hex:{}", session, ByteBufUtil.hexDump(byteBuf)); out.writeBytes(byteBuf); } } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/codec/encode/Jt808EncoderCmd.java ================================================ package com.genersoft.iot.vmp.jt1078.codec.encode; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.proc.Header; import com.genersoft.iot.vmp.jt1078.proc.entity.Cmd; import com.genersoft.iot.vmp.jt1078.proc.response.Rs; import com.genersoft.iot.vmp.jt1078.session.Session; import com.genersoft.iot.vmp.jt1078.util.Bin; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.buffer.CompositeByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; import io.netty.util.ByteProcessor; import lombok.extern.slf4j.Slf4j; import org.springframework.util.StringUtils; import java.util.LinkedList; import java.util.List; /** * @author QingtaiJiang * @date 2023/4/27 18:25 * @email qingtaij@163.com */ @Slf4j public class Jt808EncoderCmd extends MessageToByteEncoder { @Override protected void encode(ChannelHandlerContext ctx, Cmd cmd, ByteBuf out) throws Exception { Session session = ctx.channel().attr(Session.KEY).get(); Rs msg = cmd.getRs(); List encodeList = encode(msg, session, cmd.getPackageNo().intValue()); if (encodeList != null && !encodeList.isEmpty()) { for (ByteBuf byteBuf : encodeList) { log.debug("< {} hex:{}", session, ByteBufUtil.hexDump(byteBuf)); out.writeBytes(byteBuf); } } } public static List encode(Rs msg, Session session, Integer packageNo) { String id = msg.getClass().getAnnotation(MsgId.class).id(); if (!StringUtils.hasLength(id)) { log.error("Not find msgId"); return null; } ByteBuf encode = msg.encode(); Header header = msg.getHeader(); List byteBufList = new LinkedList<>(); if (encode.readableBytes() > 1000) { int index = 1; int total = encode.readableBytes()%1000 == 0 ? encode.readableBytes()/1000 : (encode.readableBytes()/1000 + 1); while (encode.isReadable()) { ByteBuf byteBuf; if (index == total) { byteBuf = buildMsgByte(header, id, session, packageNo, encode.readRetainedSlice(encode.readableBytes()), index, total); }else { byteBuf = buildMsgByte(header, id, session, packageNo, encode.readRetainedSlice(1000), index, total); } byteBufList.add(byteBuf); index ++; } }else { byteBufList.add(buildMsgByte(header, id, session, packageNo, encode, 0, 0)); } return byteBufList; } // 分包 private static ByteBuf buildMsgByte(Header header, String id, Session session, Integer packageNo, ByteBuf encode, Integer packetIndex, Integer packetTotal) { ByteBuf byteBuf = Unpooled.buffer(); byteBuf.writeBytes(ByteBufUtil.decodeHexDump(id)); if (header == null) { header = session.getHeader(); } if (header.is2019Version()) { int msgBody = encode.readableBytes() | 1 << 14; if (packetIndex > 0) { msgBody = msgBody | 1 << 13; } // 消息体属性 byteBuf.writeShort(msgBody); // 版本号 byteBuf.writeByte(header.getVersion()); // 终端手机号 byteBuf.writeBytes(ByteBufUtil.decodeHexDump(Bin.strHexPaddingLeft(header.getPhoneNumber(), 20))); } else { // 消息体属性 byteBuf.writeShort(encode.readableBytes()); byteBuf.writeBytes(ByteBufUtil.decodeHexDump(Bin.strHexPaddingLeft(header.getPhoneNumber(), 12))); } // 消息体流水号 byteBuf.writeShort(packageNo); if (packetIndex > 0) { byteBuf.writeShort(packetTotal); byteBuf.writeShort(packetIndex); } // 写入消息体 byteBuf.writeBytes(encode); // 计算校验码,并反转义 byteBuf = escapeAndCheck0(byteBuf); return byteBuf; } private static final ByteProcessor searcher = value -> !(value == 0x7d || value == 0x7e); //转义与校验 public static ByteBuf escapeAndCheck0(ByteBuf source) { sign(source); int low = source.readerIndex(); int high = source.writerIndex(); LinkedList bufList = new LinkedList<>(); int mark, len; while ((mark = source.forEachByte(low, high - low, searcher)) > 0) { len = mark + 1 - low; ByteBuf[] slice = slice(source, low, len); bufList.add(slice[0]); bufList.add(slice[1]); low += len; } if (bufList.size() > 0) { bufList.add(source.slice(low, high - low)); } else { bufList.add(source); } ByteBuf delimiter = Unpooled.buffer(1, 1).writeByte(0x7e).retain(); bufList.addFirst(delimiter); bufList.addLast(delimiter); CompositeByteBuf byteBufLs = Unpooled.compositeBuffer(bufList.size()); byteBufLs.addComponents(true, bufList); return byteBufLs; } public static void sign(ByteBuf buf) { byte checkCode = bcc(buf); buf.writeByte(checkCode); } public static byte bcc(ByteBuf byteBuf) { byte cs = 0; while (byteBuf.isReadable()) cs ^= byteBuf.readByte(); byteBuf.resetReaderIndex(); return cs; } protected static ByteBuf[] slice(ByteBuf byteBuf, int index, int length) { byte first = byteBuf.getByte(index + length - 1); ByteBuf[] byteBufList = new ByteBuf[2]; byteBufList[0] = byteBuf.retainedSlice(index, length); if (first == 0x7d) { byteBufList[1] = Unpooled.buffer(1, 1).writeByte(0x01); } else { byteBuf.setByte(index + length - 1, 0x7d); byteBufList[1] = Unpooled.buffer(1, 1).writeByte(0x02); } return byteBufList; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/codec/netty/Jt808Handler.java ================================================ package com.genersoft.iot.vmp.jt1078.codec.netty; import com.genersoft.iot.vmp.jt1078.event.ConnectChangeEvent; import com.genersoft.iot.vmp.jt1078.proc.response.Rs; import com.genersoft.iot.vmp.jt1078.session.Session; import com.genersoft.iot.vmp.jt1078.session.SessionManager; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.handler.timeout.IdleState; import io.netty.handler.timeout.IdleStateEvent; import io.netty.util.ReferenceCountUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationEventPublisher; import lombok.extern.slf4j.Slf4j; /** * @author QingtaiJiang * @date 2023/4/27 18:14 * @email qingtaij@163.com */ @Slf4j public class Jt808Handler extends ChannelInboundHandlerAdapter { private ApplicationEventPublisher applicationEventPublisher = null; public Jt808Handler(ApplicationEventPublisher applicationEventPublisher) { this.applicationEventPublisher = applicationEventPublisher; } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof Rs) { ctx.writeAndFlush(msg); } else { ctx.fireChannelRead(msg); } // 读取完成后的消息释放 ReferenceCountUtil.release(msg); } @Override public void channelActive(ChannelHandlerContext ctx) { Channel channel = ctx.channel(); Session session = SessionManager.INSTANCE.newSession(channel); channel.attr(Session.KEY).set(session); log.info("> Tcp connect {}", session); if (session.getPhoneNumber() == null) { return; } ConnectChangeEvent event = new ConnectChangeEvent(this); event.setConnected(true); event.setPhoneNumber(session.getPhoneNumber()); applicationEventPublisher.publishEvent(event); } @Override public void channelInactive(ChannelHandlerContext ctx) { Session session = ctx.channel().attr(Session.KEY).get(); log.info("< Tcp disconnect {}", session); ctx.close(); if (session.getPhoneNumber() == null) { return; } ConnectChangeEvent event = new ConnectChangeEvent(this); event.setConnected(false); event.setPhoneNumber(session.getPhoneNumber()); applicationEventPublisher.publishEvent(event); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable e) { Session session = ctx.channel().attr(Session.KEY).get(); String message = e.getMessage(); if (message.toLowerCase().contains("Connection reset by peer".toLowerCase())) { log.info("< exception{} {}", session, e.getMessage()); } else { log.info("< exception{} {}", session, e.getMessage(), e); } } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { if (evt instanceof IdleStateEvent) { IdleStateEvent event = (IdleStateEvent) evt; IdleState state = event.state(); if (state == IdleState.READER_IDLE || state == IdleState.WRITER_IDLE) { Session session = ctx.channel().attr(Session.KEY).get(); log.warn("< Proactively disconnect{}", session); ctx.close(); } } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/codec/netty/TcpServer.java ================================================ package com.genersoft.iot.vmp.jt1078.codec.netty; import com.genersoft.iot.vmp.jt1078.codec.decode.Jt808Decoder; import com.genersoft.iot.vmp.jt1078.codec.encode.Jt808Encoder; import com.genersoft.iot.vmp.jt1078.codec.encode.Jt808EncoderCmd; import com.genersoft.iot.vmp.jt1078.proc.factory.CodecFactory; import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioChannelOption; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.DelimiterBasedFrameDecoder; import io.netty.handler.timeout.IdleStateHandler; import io.netty.util.concurrent.Future; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.CommandLineRunner; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Component; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.TimeUnit; /** * @author QingtaiJiang * @date 2023/4/27 18:01 * @email qingtaij@163.com */ @Slf4j public class TcpServer { private final Integer port; private boolean isRunning = false; private EventLoopGroup bossGroup = null; private EventLoopGroup workerGroup = null; private ApplicationEventPublisher applicationEventPublisher = null; private Ijt1078Service service = null; private final ByteBuf DECODER_JT808 = Unpooled.wrappedBuffer(new byte[]{0x7e}); public TcpServer(Integer port, ApplicationEventPublisher applicationEventPublisher, Ijt1078Service service) { this.port = port; this.applicationEventPublisher = applicationEventPublisher; this.service = service; } private void startTcpServer() { try { CodecFactory.init(); this.bossGroup = new NioEventLoopGroup(); this.workerGroup = new NioEventLoopGroup(); ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.channel(NioServerSocketChannel.class); bootstrap.group(bossGroup, workerGroup); bootstrap.option(NioChannelOption.SO_BACKLOG, 1024) .option(NioChannelOption.SO_REUSEADDR, true) .childOption(NioChannelOption.TCP_NODELAY, true) .childHandler(new ChannelInitializer() { @Override public void initChannel(NioSocketChannel channel) { channel.pipeline() .addLast(new IdleStateHandler(10, 0, 0, TimeUnit.MINUTES)) .addLast(new DelimiterBasedFrameDecoder(1024 * 2, DECODER_JT808)) .addLast(new Jt808Decoder(applicationEventPublisher, service)) .addLast(new Jt808Encoder()) .addLast(new Jt808EncoderCmd()) .addLast(new Jt808Handler(applicationEventPublisher)); } }); ChannelFuture channelFuture = bootstrap.bind(port).sync(); // 监听设备TCP端口是否启动成功 channelFuture.addListener(future -> { if (!future.isSuccess()) { log.error("Binding port:{} fail! cause: {}", port, future.cause().getCause(), future.cause()); } }); log.info("服务:JT808 Server 启动成功, port:{}", port); channelFuture.channel().closeFuture().sync(); } catch (Exception e) { log.warn("服务:JT808 Server 启动异常, port:{},{}", port, e.getMessage(), e); } finally { stop(); } } /** * 开启一个新的线程,拉起来Netty */ public synchronized void start() { if (this.isRunning) { log.warn("服务:JT808 Server 已经启动, port:{}", port); return; } this.isRunning = true; new Thread(this::startTcpServer).start(); } public synchronized void stop() { if (!this.isRunning) { log.warn("服务:JT808 Server 已经停止, port:{}", port); } this.isRunning = false; Future future = this.bossGroup.shutdownGracefully(); if (!future.isSuccess()) { log.warn("bossGroup 无法正常停止", future.cause()); } future = this.workerGroup.shutdownGracefully(); if (!future.isSuccess()) { log.warn("workerGroup 无法正常停止", future.cause()); } log.warn("服务:JT808 Server 已经停止, port:{}", port); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/config/JT1078AutoConfiguration.java ================================================ package com.genersoft.iot.vmp.jt1078.config; import com.genersoft.iot.vmp.jt1078.codec.netty.TcpServer; import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; /** * @author QingtaiJiang * @date 2023/4/27 19:35 * @email qingtaij@163.com */ @Order(Integer.MIN_VALUE) @Configuration @ConditionalOnProperty(value = "jt1078.enable", havingValue = "true") public class JT1078AutoConfiguration { @Autowired private ApplicationEventPublisher applicationEventPublisher; @Autowired private Ijt1078Service service; @Bean(initMethod = "start", destroyMethod = "stop") public TcpServer jt1078Server(@Value("${jt1078.port}") Integer port) { return new TcpServer(port, applicationEventPublisher, service); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/config/JT1078Config.java ================================================ package com.genersoft.iot.vmp.jt1078.config; import com.genersoft.iot.vmp.jt1078.bean.common.ConfigAttribute; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; @Component @Data @ConfigurationProperties(prefix = "jt1078", ignoreInvalidFields = true) @Order(3) public class JT1078Config { private Integer port; private String password; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/controller/JT1078Controller.java ================================================ package com.genersoft.iot.vmp.jt1078.controller; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.conf.ftpServer.FtpSetting; import com.genersoft.iot.vmp.conf.security.JwtUtils; import com.genersoft.iot.vmp.jt1078.bean.*; import com.genersoft.iot.vmp.jt1078.controller.bean.*; import com.genersoft.iot.vmp.jt1078.proc.request.J1205; import com.genersoft.iot.vmp.jt1078.service.Ijt1078PlayService; import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; import com.genersoft.iot.vmp.service.bean.InviteErrorCode; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.StreamContent; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; import jakarta.servlet.ServletOutputStream; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.http.MediaType; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.web.bind.annotation.*; import org.springframework.web.context.request.async.DeferredResult; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLEncoder; import java.util.List; @Slf4j @ConditionalOnProperty(value = "jt1078.enable", havingValue = "true") @RestController @Tag(name = "部标设备控制") @RequestMapping("/api/jt1078") public class JT1078Controller { @Resource private Ijt1078Service service; @Resource private Ijt1078PlayService jt1078PlayService; @Autowired private UserSetting userSetting; @Autowired private FtpSetting ftpSetting; @Operation(summary = "JT-开始点播", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "phoneNumber", description = "设备手机号", required = true) @Parameter(name = "channelId", description = "通道编号, 一般为从1开始的数字", required = true) @Parameter(name = "type", description = "类型:0:音视频,1:视频,3:音频", required = true) @GetMapping("/live/start") public DeferredResult> startLive(HttpServletRequest request, @Parameter(required = true) String phoneNumber, @Parameter(required = true) Integer channelId, @Parameter(required = false) Integer type) { if (type == null || (type != 0 && type != 1 && type != 3)) { type = 0; } DeferredResult> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue()); result.onTimeout(()->{ log.info("[JT-点播等待超时] phoneNumber:{}, channelId:{}, ", phoneNumber, channelId); // 释放rtpserver WVPResult wvpResult = new WVPResult<>(); wvpResult.setCode(ErrorCode.ERROR100.getCode()); wvpResult.setMsg("超时"); result.setResult(wvpResult); jt1078PlayService.stopPlay(phoneNumber, channelId); }); jt1078PlayService.play(phoneNumber, channelId, type, wvpResult -> { WVPResult wvpResultForFinish = new WVPResult<>(); wvpResultForFinish.setCode(wvpResult.getCode()); wvpResultForFinish.setMsg(wvpResult.getMsg()); if (wvpResult.getCode() == InviteErrorCode.SUCCESS.getCode()) { StreamInfo streamInfo = wvpResult.getData(); if (streamInfo != null) { if (userSetting.getUseSourceIpAsStreamIp()) { streamInfo=streamInfo.clone();//深拷贝 String host; try { URL url=new URL(request.getRequestURL().toString()); host=url.getHost(); } catch (MalformedURLException e) { host=request.getLocalAddr(); } streamInfo.changeStreamIp(host); } wvpResultForFinish.setData(new StreamContent(streamInfo)); } } result.setResult(wvpResultForFinish); }); return result; } @Operation(summary = "JT-结束点播", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "phoneNumber", description = "设备手机号", required = true) @Parameter(name = "channelId", description = "通道国标编号, 一般为从1开始的数字", required = true) @GetMapping("/live/stop") public void stopLive(HttpServletRequest request, @Parameter(required = true) String phoneNumber, @Parameter(required = true) Integer channelId) { jt1078PlayService.stopPlay(phoneNumber, channelId); } @Operation(summary = "JT-语音对讲", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "phoneNumber", description = "设备手机号", required = true) @Parameter(name = "channelId", description = "通道国标编号, 一般为从1开始的数字", required = true) @GetMapping("/talk/start") public StreamContent startTalk(HttpServletRequest request, @Parameter(required = true) String phoneNumber, @Parameter(required = true) Integer channelId) { StreamInfo streamInfo = jt1078PlayService.startTalk(phoneNumber, channelId); if (userSetting.getUseSourceIpAsStreamIp()) { String host; try { URL url=new URL(request.getRequestURL().toString()); host=url.getHost(); } catch (MalformedURLException e) { host=request.getLocalAddr(); } streamInfo.changeStreamIp(host); } streamInfo.setIp("localhost"); return new StreamContent(streamInfo); } @Operation(summary = "JT-结束对讲", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "phoneNumber", description = "设备手机号", required = true) @Parameter(name = "channelId", description = "通道国标编号, 一般为从1开始的数字", required = true) @GetMapping("/talk/stop") public void stopTalk(HttpServletRequest request, @Parameter(required = true) String phoneNumber, @Parameter(required = true) Integer channelId) { jt1078PlayService.stopTalk(phoneNumber, channelId); } @Operation(summary = "JT-暂停点播", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "phoneNumber", description = "设备手机号", required = true) @Parameter(name = "channelId", description = "通道国标编号, 一般为从1开始的数字", required = true) @GetMapping("/live/pause") public void pauseLive(HttpServletRequest request, @Parameter(required = true) String phoneNumber, @Parameter(required = true) Integer channelId) { jt1078PlayService.pausePlay(phoneNumber, channelId); } @Operation(summary = "JT-继续点播", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "phoneNumber", description = "设备手机号", required = true) @Parameter(name = "channelId", description = "通道国标编号, 一般为从1开始的数字", required = true) @GetMapping("/live/continue") public void continueLive(HttpServletRequest request, @Parameter(required = true) String phoneNumber, @Parameter(required = true) Integer channelId) { jt1078PlayService.continueLivePlay(phoneNumber, channelId); } @Operation(summary = "JT-切换码流类型", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "phoneNumber", description = "设备手机号", required = true) @Parameter(name = "channelId", description = "通道国标编号, 一般为从1开始的数字", required = true) @Parameter(name = "streamType", description = "0:主码流; 1:子码流", required = true) @GetMapping("/live/switch") public void changeStreamType(HttpServletRequest request, @Parameter(required = true) String phoneNumber, @Parameter(required = true) Integer channelId, @Parameter(required = true) Integer streamType) { service.changeStreamType(phoneNumber, channelId, streamType); } @Operation(summary = "JT-录像-查询资源列表", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "phoneNumber", description = "设备手机号", required = true) @Parameter(name = "channelId", description = "通道国标编号, 一般为从1开始的数字", required = true) @Parameter(name = "startTime", description = "开始时间,格式: yyyy-MM-dd HH:mm:ss", required = true) @Parameter(name = "endTime", description = "结束时间,格式: yyyy-MM-dd HH:mm:ss", required = true) @GetMapping("/record/list") public WVPResult> playbackList(HttpServletRequest request, @Parameter(required = true) String phoneNumber, @Parameter(required = true) Integer channelId, @Parameter(required = true) String startTime, @Parameter(required = true) String endTime ) { List recordList = jt1078PlayService.getRecordList(phoneNumber, channelId, startTime, endTime); if (recordList == null) { return WVPResult.fail(ErrorCode.ERROR100); }else { return WVPResult.success(recordList); } } @Operation(summary = "JT-录像-开始回放", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "phoneNumber", description = "设备手机号", required = true) @Parameter(name = "channelId", description = "通道国标编号, 一般为从1开始的数字", required = true) @Parameter(name = "startTime", description = "开始时间,格式: yyyy-MM-dd HH:mm:ss", required = true) @Parameter(name = "endTime", description = "结束时间,格式: yyyy-MM-dd HH:mm:ss", required = true) @Parameter(name = "type", description = "0.音视频 1.音频 2.视频 3.视频或音视频", required = true) @Parameter(name = "rate", description = "0.所有码流 1.主码流 2.子码流(如果此通道只传输音频,此字段置0)", required = true) @Parameter(name = "playbackType", description = "0.正常回放 1.快进回放 2.关键帧快退回放 3.关键帧播放 4.单帧上传", required = true) @Parameter(name = "playbackSpeed", description = "0.无效 1.1倍 2.2倍 3.4倍 4.8倍 5.16倍 (回放控制为1和2时,此字段内容有效,否则置0)", required = true) @GetMapping("/playback/start") public DeferredResult> recordLive(HttpServletRequest request, @Parameter(required = true) String phoneNumber, @Parameter(required = true) Integer channelId, @Parameter(required = true) String startTime, @Parameter(required = true) String endTime, @Parameter(required = false) Integer type, @Parameter(required = false) Integer rate, @Parameter(required = false) Integer playbackType, @Parameter(required = false) Integer playbackSpeed ) { DeferredResult> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue()); result.onTimeout(()->{ log.info("[JT-回放-等待超时] phoneNumber:{}, channelId:{}, ", phoneNumber, channelId); // 释放rtpserver WVPResult wvpResult = new WVPResult<>(); wvpResult.setCode(ErrorCode.ERROR100.getCode()); wvpResult.setMsg("回放超时"); result.setResult(wvpResult); jt1078PlayService.stopPlay(phoneNumber, channelId); }); jt1078PlayService.playback(phoneNumber, channelId, startTime, endTime,type, rate, playbackType, playbackSpeed, wvpResult -> { WVPResult wvpResultForFinish = new WVPResult<>(); wvpResultForFinish.setCode(wvpResult.getCode()); wvpResultForFinish.setMsg(wvpResult.getMsg()); if (wvpResult.getCode() == InviteErrorCode.SUCCESS.getCode()) { StreamInfo streamInfo = wvpResult.getData(); if (streamInfo != null) { if (userSetting.getUseSourceIpAsStreamIp()) { String host; try { URL url=new URL(request.getRequestURL().toString()); host=url.getHost(); } catch (MalformedURLException e) { host=request.getLocalAddr(); } streamInfo.changeStreamIp(host); } wvpResultForFinish.setData(new StreamContent(streamInfo)); } } result.setResult(wvpResultForFinish); }); return result; } @Operation(summary = "JT-录像-回放控制", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "phoneNumber", description = "设备手机号", required = true) @Parameter(name = "channelId", description = "通道国标编号, 一般为从1开始的数字", required = true) @Parameter(name = "command", description = "0:开始回放; 1:暂停回放; 2:结束回放; 3:快进回放; 4:关键帧快退回放; 5:拖动回放; 6:关键帧播放", required = true) @Parameter(name = "playbackSpeed", description = "0.无效 1.1倍 2.2倍 3.4倍 4.8倍 5.16倍 (回放控制为3和4时,此字段内容有效,否则置0)", required = false) @Parameter(name = "time", description = "拖动回放位置(时间)", required = false) @GetMapping("/playback/control") public void recordControl(@Parameter(required = true) String phoneNumber, @Parameter(required = true) Integer channelId, @Parameter(required = false) Integer command, @Parameter(required = false) String time, @Parameter(required = false) Integer playbackSpeed ) { jt1078PlayService.playbackControl(phoneNumber, channelId, command, playbackSpeed, time); } @Operation(summary = "JT-录像-结束回放", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "phoneNumber", description = "设备手机号", required = true) @Parameter(name = "channelId", description = "通道国标编号, 一般为从1开始的数字", required = true) @GetMapping("/playback/stop") public void stopPlayback(HttpServletRequest request, @Parameter(required = true) String phoneNumber, @Parameter(required = true) Integer channelId) { jt1078PlayService.stopPlayback(phoneNumber, channelId); } @Operation(summary = "JT-录像-获取下载地址", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "phoneNumber", description = "设备手机号", required = true) @Parameter(name = "channelId", description = "通道国标编号, 一般为从1开始的数字", required = true) @Parameter(name = "startTime", description = "开始时间,格式: yyyy-MM-dd HH:mm:ss", required = true) @Parameter(name = "endTime", description = "结束时间,格式: yyyy-MM-dd HH:mm:ss", required = true) @Parameter(name = "alarmSign", description = "报警标志", required = true) @Parameter(name = "mediaType", description = "音视频资源类型: 0.音视频 1.音频 2.视频 3.视频或音视频", required = true) @Parameter(name = "streamType", description = "码流类型:0.所有码流 1.主码流 2.子码流(如果此通道只传输音频,此字段置0)", required = true) @Parameter(name = "storageType", description = "存储器类型", required = true) @GetMapping("/playback/downloadUrl") public String getRecordTempUrl(HttpServletRequest request, @Parameter(required = true) String phoneNumber, @Parameter(required = true) Integer channelId, @Parameter(required = true) String startTime, @Parameter(required = true) String endTime, @Parameter(required = false) Integer alarmSign, @Parameter(required = false) Integer mediaType, @Parameter(required = false) Integer streamType, @Parameter(required = false) Integer storageType ){ log.info("[JT-录像] 下载,设备:{}, 通道: {}, 开始时间: {}, 结束时间: {},报警标志: {}, 音视频类型: {}, 码流类型: {},存储器类型: {}, ", phoneNumber, channelId, startTime, endTime, alarmSign, mediaType, streamType, storageType); if (!ftpSetting.getEnable()) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "未启用ftp服务,无法下载录像"); } return service.getRecordTempUrl(phoneNumber, channelId, startTime, endTime, alarmSign, mediaType, streamType, storageType); } @Operation(summary = "JT-录像-下载", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "path", description = "临时下载路径", required = true) @GetMapping("/playback/download") public void download(HttpServletRequest request, HttpServletResponse response, @Parameter(required = true) String path) throws IOException { if (!ftpSetting.getEnable()) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "未启用ftp服务,无法下载录像"); } DeferredResult result = new DeferredResult<>(); ServletOutputStream outputStream = response.getOutputStream(); response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(path + ".mp4", "UTF-8")); // response.setContentLength(394983300); response.setStatus(HttpServletResponse.SC_OK); service.recordDownload(path, outputStream); } @Operation(summary = "JT-云台控制", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "phoneNumber", description = "设备手机号", required = true) @Parameter(name = "channelId", description = "通道国标编号, 一般为从1开始的数字", required = true) @Parameter(name = "command", description = "控制指令,允许值: left, right, up, down, zoomin, zoomout, irisin, irisout, focusnear, focusfar, stop", required = true) @Parameter(name = "speed", description = "速度(0-255), command,值 left, right, up, down时有效", required = true) @GetMapping("/ptz") public void ptz(String phoneNumber, Integer channelId, String command, int speed){ log.info("[JT-云台控制] phoneNumber:{}, channelId:{}, command: {}, speed: {}", phoneNumber, channelId, command, speed); service.ptzControl(phoneNumber, channelId, command, speed); } @Operation(summary = "JT-补光灯开关", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "phoneNumber", description = "设备手机号", required = true) @Parameter(name = "channelId", description = "通道国标编号, 一般为从1开始的数字", required = true) @Parameter(name = "command", description = "控制指令,允许值: on off", required = true) @GetMapping("/fill-light") public void fillLight(String phoneNumber, Integer channelId, String command){ log.info("[JT-补光灯开关] phoneNumber:{}, channelId:{}, command: {}", phoneNumber, channelId, command); service.supplementaryLight(phoneNumber, channelId, command); } @Operation(summary = "JT-雨刷开关", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "phoneNumber", description = "设备手机号", required = true) @Parameter(name = "channelId", description = "通道国标编号, 一般为从1开始的数字", required = true) @Parameter(name = "command", description = "控制指令,允许值: on off", required = true) @GetMapping("/wiper") public void wiper(String phoneNumber, Integer channelId, String command){ log.info("[JT-雨刷开关] phoneNumber:{}, channelId:{}, command: {}", phoneNumber, channelId, command); service.wiper(phoneNumber, channelId, command); } @Operation(summary = "JT-查询终端参数", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "phoneNumber", description = "设备手机号", required = true) @GetMapping("/config/get") public JTDeviceConfig config(String phoneNumber, String[] params){ log.info("[JT-查询终端参数] phoneNumber:{}", phoneNumber); return service.queryConfig(phoneNumber, params); } @Operation(summary = "JT-设置终端参数", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "phoneNumber", description = "设备编号", required = true) @Parameter(name = "config", description = "终端参数", required = true) @PostMapping("/config/set") public void setConfig(@RequestBody SetConfigParam config){ log.info("[JT-设置终端参数] 参数: {}", config.toString()); service.setConfig(config.getPhoneNumber(), config.getConfig()); } @Operation(summary = "终端控制-连接指定的服务器", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "control", description = "终端控制参数", required = true) @PostMapping("/control/connection") public void connectionControl(@RequestBody ConnectionControlParam control){ log.info("[JT-终端控制] 参数: {}", control.toString()); service.connectionControl(control.getPhoneNumber(), control.getControl()); } @Operation(summary = "JT-终端控制-复位", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "phoneNumber", description = "设备编号", required = true) @PostMapping("/control/reset") public void resetControl(String phoneNumber){ log.info("[JT-复位] phoneNumber: {}", phoneNumber); service.resetControl(phoneNumber); } @Operation(summary = "JT-终端控制-恢复出厂设置", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "phoneNumber", description = "设备编号", required = true) @PostMapping("/control/factory-reset") public void factoryResetControl(String phoneNumber){ log.info("[JT-恢复出厂设置] phoneNumber: {}", phoneNumber); service.factoryResetControl(phoneNumber); } @Operation(summary = "JT-查询终端属性", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "phoneNumber", description = "设备编号", required = true) @GetMapping("/attribute") public JTDeviceAttribute attribute(String phoneNumber){ log.info("[JT-查询终端属性] phoneNumber: {}", phoneNumber); return service.attribute(phoneNumber); } @Operation(summary = "JT-查询位置信息", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "phoneNumber", description = "设备编号", required = true) @GetMapping("/position-info") public JTPositionBaseInfo queryPositionInfo(String phoneNumber){ log.info("[JT-查询位置信息] phoneNumber: {}", phoneNumber); return service.queryPositionInfo(phoneNumber); } @Operation(summary = "JT-临时位置跟踪控制", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "phoneNumber", description = "设备编号", required = true) @Parameter(name = "timeInterval", description = "时间间隔,单位为秒,时间间隔为0 时停止跟踪,停止跟踪无需带后继字段", required = true) @Parameter(name = "validityPeriod", description = "位置跟踪有效期, 单位为秒,终端在接收到位置跟踪控制消息后,在有效期截止时间之前依据消息中的时间间隔发送位置汇报", required = true) @GetMapping("/control/temp-position-tracking") public void tempPositionTrackingControl(String phoneNumber, Integer timeInterval, Long validityPeriod){ log.info("[JT-临时位置跟踪控制] phoneNumber: {}, 时间间隔 {}秒, 位置跟踪有效期 {}秒", phoneNumber, timeInterval, validityPeriod); service.tempPositionTrackingControl(phoneNumber, timeInterval, validityPeriod); } @Operation(summary = "JT-人工确认报警消息", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "phoneNumber", description = "设备编号", required = true) @Parameter(name = "timeInterval", description = "时间间隔,单位为秒,时间间隔为0 时停止跟踪,停止跟踪无需带后继字段", required = true) @Parameter(name = "validityPeriod", description = "位置跟踪有效期, 单位为秒,终端在接收到位置跟踪控制消息后,在有效期截止时间之前依据消息中的时间间隔发送位置汇报", required = true) @PostMapping("/confirmation-alarm-message") public void confirmationAlarmMessage(@RequestBody ConfirmationAlarmMessageParam param){ log.info("[JT-人工确认报警消息] 参数: {}", param); service.confirmationAlarmMessage(param.getPhoneNumber(), param.getAlarmPackageNo(), param.getAlarmMessageType()); } @Operation(summary = "JT-链路检测", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "phoneNumber", description = "设备编号", required = true) @GetMapping("/link-detection") public Integer linkDetection(String phoneNumber){ log.info("[JT-链路检测] phoneNumber: {}", phoneNumber); return service.linkDetection(phoneNumber); } @Operation(summary = "JT-文本信息下发", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "textMessageParam", description = "文本信息下发参数", required = true) @PostMapping("/text-msg") public WVPResult textMessage(@RequestBody TextMessageParam textMessageParam){ log.info("[JT-文本信息下发] textMessageParam: {}", textMessageParam); int result = service.textMessage(textMessageParam.getPhoneNumber(), textMessageParam.getSign(), textMessageParam.getTextType(), textMessageParam.getContent()); if (result == 0) { return WVPResult.success(result); }else { WVPResult fail = WVPResult.fail(ErrorCode.ERROR100); fail.setData(result); return fail; } } @Operation(summary = "JT-电话回拨", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "phoneNumber", description = "设备编号", required = true) @Parameter(name = "sign", description = "标志: 0:普通通话,1:监听", required = true) @Parameter(name = "destPhoneNumber", description = "回拨电话号码", required = true) @GetMapping("/telephone-callback") public WVPResult telephoneCallback(String phoneNumber, Integer sign, String destPhoneNumber){ log.info("[JT-电话回拨] phoneNumber: {}, sign: {}, phoneNumber: {},", phoneNumber, sign, phoneNumber); int result = service.telephoneCallback(phoneNumber, sign, destPhoneNumber); if (result == 0) { return WVPResult.success(result); }else { WVPResult fail = WVPResult.fail(ErrorCode.ERROR100); fail.setData(result); return fail; } } @Operation(summary = "JT-设置电话本", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "setPhoneBookParam", description = "设置电话本参数", required = true) @PostMapping("/set-phone-book") public WVPResult setPhoneBook(@RequestBody SetPhoneBookParam setPhoneBookParam){ log.info("[JT-设置电话本] setPhoneBookParam: {}", setPhoneBookParam); int result = service.setPhoneBook(setPhoneBookParam.getPhoneNumber(), setPhoneBookParam.getType(), setPhoneBookParam.getPhoneBookContactList()); if (result == 0) { return WVPResult.success(result); }else { WVPResult fail = WVPResult.fail(ErrorCode.ERROR100); fail.setData(result); return fail; } } @Operation(summary = "JT-车门控制", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "phoneNumber", description = "设备编号", required = true) @Parameter(name = "open", description = "开启车门", required = true) @GetMapping("/control/door") public WVPResult controlDoor(String phoneNumber, Boolean open){ log.info("[JT-车门控制] phoneNumber: {}, open: {},", phoneNumber, open); JTPositionBaseInfo positionBaseInfo = service.controlDoor(phoneNumber, open); if (positionBaseInfo == null || positionBaseInfo.getStatus() == null) { return WVPResult.fail(ErrorCode.ERROR100.getCode(), "控制失败"); } if (open == !positionBaseInfo.getStatus().isDoorLocking()) { return WVPResult.success(null); }else { return WVPResult.fail(ErrorCode.ERROR100.getCode(), "控制失败"); } } @Operation(summary = "JT-更新圆形区域", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "areaParam", description = "设置区域参数", required = true) @PostMapping("/area/circle/update") public WVPResult updateAreaForCircle(@RequestBody SetAreaParam areaParam){ log.info("[JT-更新圆形区域] areaParam: {},", areaParam); int result = service.setAreaForCircle(0, areaParam.getPhoneNumber(), areaParam.getCircleAreaList()); if (result == 0) { return WVPResult.success(result); }else { WVPResult fail = WVPResult.fail(ErrorCode.ERROR100); fail.setData(result); return fail; } } @Operation(summary = "JT-追加圆形区域", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "areaParam", description = "设置区域参数", required = true) @PostMapping("/area/circle/add") public WVPResult addAreaForCircle(@RequestBody SetAreaParam areaParam){ log.info("[JT-追加圆形区域] areaParam: {},", areaParam); int result = service.setAreaForCircle(1, areaParam.getPhoneNumber(), areaParam.getCircleAreaList()); if (result == 0) { return WVPResult.success(result); }else { WVPResult fail = WVPResult.fail(ErrorCode.ERROR100); fail.setData(result); return fail; } } @Operation(summary = "JT-修改圆形区域", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "areaParam", description = "设置区域参数", required = true) @PostMapping("/area/circle/edit") public WVPResult editAreaForCircle(@RequestBody SetAreaParam areaParam){ log.info("[JT-修改圆形区域] areaParam: {},", areaParam); int result = service.setAreaForCircle(2, areaParam.getPhoneNumber(), areaParam.getCircleAreaList()); if (result == 0) { return WVPResult.success(result); }else { WVPResult fail = WVPResult.fail(ErrorCode.ERROR100); fail.setData(result); return fail; } } @Operation(summary = "JT-删除圆形区域", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "phoneNumber", description = "设备编号", required = true) @Parameter(name = "ids", description = "待删除圆形区域的id,例如1,2,3", required = true) @GetMapping("/area/circle/delete") public WVPResult deleteAreaForCircle(String phoneNumber, @RequestParam(value = "ids", required = false) List ids){ log.info("[JT-删除圆形区域] phoneNumber: {}, ids:{}", phoneNumber, ids); int result = service.deleteAreaForCircle(phoneNumber, ids); if (result == 0) { return WVPResult.success(result); }else { WVPResult fail = WVPResult.fail(ErrorCode.ERROR100); fail.setData(result); return fail; } } @Operation(summary = "JT-查询圆形区域", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "phoneNumber", description = "设备编号", required = true) @GetMapping("/area/circle/query") public WVPResult> queryAreaForCircle(String phoneNumber, @RequestParam(value = "ids", required = false) List ids){ log.info("[JT-查询圆形区域] phoneNumber: {}, ids:{}", phoneNumber, ids); List result = service.queryAreaForCircle(phoneNumber, ids); if (result != null) { return WVPResult.success(result); }else { return WVPResult.fail(ErrorCode.ERROR100); } } @Operation(summary = "JT-更新矩形区域", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "areaParam", description = "设置区域参数", required = true) @PostMapping("/area/rectangle/update") public WVPResult updateAreaForRectangle(@RequestBody SetAreaParam areaParam){ log.info("[JT-更新矩形区域] areaParam: {},", areaParam); int result = service.setAreaForRectangle(0, areaParam.getPhoneNumber(), areaParam.getRectangleAreas()); if (result == 0) { return WVPResult.success(result); }else { WVPResult fail = WVPResult.fail(ErrorCode.ERROR100); fail.setData(result); return fail; } } @Operation(summary = "JT-追加矩形区域", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "areaParam", description = "设置区域参数", required = true) @PostMapping("/area/rectangle/add") public WVPResult addAreaForRectangle(@RequestBody SetAreaParam areaParam){ log.info("[JT-追加矩形区域] areaParam: {},", areaParam); int result = service.setAreaForRectangle(1, areaParam.getPhoneNumber(), areaParam.getRectangleAreas()); if (result == 0) { return WVPResult.success(result); }else { WVPResult fail = WVPResult.fail(ErrorCode.ERROR100); fail.setData(result); return fail; } } @Operation(summary = "JT-修改矩形区域", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "areaParam", description = "设置区域参数", required = true) @PostMapping("/area/rectangle/edit") public WVPResult editAreaForRectangle(@RequestBody SetAreaParam areaParam){ log.info("[JT-修改矩形区域] areaParam: {},", areaParam); int result = service.setAreaForRectangle(2, areaParam.getPhoneNumber(), areaParam.getRectangleAreas()); if (result == 0) { return WVPResult.success(result); }else { WVPResult fail = WVPResult.fail(ErrorCode.ERROR100); fail.setData(result); return fail; } } @Operation(summary = "JT-删除矩形区域", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "phoneNumber", description = "设备编号", required = true) @Parameter(name = "ids", description = "待删除圆形区域的id,例如1,2,3", required = true) @GetMapping("/area/rectangle/delete") public WVPResult deleteAreaForRectangle(String phoneNumber, @RequestParam(value = "ids", required = false) List ids){ log.info("[JT-删除矩形区域] phoneNumber: {}, ids:{}", phoneNumber, ids); int result = service.deleteAreaForRectangle(phoneNumber, ids); if (result == 0) { return WVPResult.success(result); }else { WVPResult fail = WVPResult.fail(ErrorCode.ERROR100); fail.setData(result); return fail; } } @Operation(summary = "JT-查询矩形区域", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "phoneNumber", description = "设备编号", required = true) @GetMapping("/area/rectangle/query") public WVPResult> queryAreaForRectangle(String phoneNumber, @RequestParam(value = "ids", required = false) List ids){ log.info("[JT-查询矩形区域] phoneNumber: {}, ids:{}", phoneNumber, ids); List result = service.queryAreaForRectangle(phoneNumber, ids); if (result != null) { return WVPResult.success(result); }else { return WVPResult.fail(ErrorCode.ERROR100); } } @Operation(summary = "JT-设置多边形区域", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "areaParam", description = "设置区域参数", required = true) @PostMapping("/area/polygon/set") public WVPResult setAreaForPolygon(@RequestBody SetAreaParam areaParam){ log.info("[JT-设置多边形区域] areaParam: {},", areaParam); int result = service.setAreaForPolygon(areaParam.getPhoneNumber(), areaParam.getPolygonArea()); if (result == 0) { return WVPResult.success(result); }else { WVPResult fail = WVPResult.fail(ErrorCode.ERROR100); fail.setData(result); return fail; } } @Operation(summary = "JT-删除多边形区域", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "phoneNumber", description = "设备编号", required = true) @Parameter(name = "ids", description = "待删除圆形区域的id,例如1,2,3", required = true) @GetMapping("/area/polygon/delete") public WVPResult deleteAreaForPolygon(String phoneNumber, @RequestParam(value = "ids", required = false) List ids){ log.info("[JT-删除多边形区域] phoneNumber: {}, ids:{}", phoneNumber, ids); int result = service.deleteAreaForPolygon(phoneNumber, ids); if (result == 0) { return WVPResult.success(result); }else { WVPResult fail = WVPResult.fail(ErrorCode.ERROR100); fail.setData(result); return fail; } } @Operation(summary = "JT-查询多边形区域", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "phoneNumber", description = "设备编号", required = true) @GetMapping("/area/polygon/query") public WVPResult> queryAreaForPolygon(String phoneNumber, @RequestParam(value = "ids", required = false) List ids){ log.info("[JT-查询多边形区域] phoneNumber: {}, ids:{}", phoneNumber, ids); List result = service.queryAreaForPolygon(phoneNumber, ids); if (result != null) { return WVPResult.success(result); }else { return WVPResult.fail(ErrorCode.ERROR100); } } @Operation(summary = "JT-设置路线", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "areaParam", description = "设置区域参数", required = true) @PostMapping("/route/set") public WVPResult setRoute(@RequestBody SetAreaParam areaParam){ log.info("[JT-设置路线] areaParam: {},", areaParam); int result = service.setRoute(areaParam.getPhoneNumber(), areaParam.getRoute()); if (result == 0) { return WVPResult.success(result); }else { WVPResult fail = WVPResult.fail(ErrorCode.ERROR100); fail.setData(result); return fail; } } @Operation(summary = "JT-删除路线", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "phoneNumber", description = "设备编号", required = true) @Parameter(name = "ids", description = "待删除圆形区域的id,例如1,2,3", required = true) @GetMapping("/route/delete") public WVPResult deleteRoute(String phoneNumber, @RequestParam(value = "ids", required = false) List ids){ log.info("[JT-删除路线] phoneNumber: {}, ids:{}", phoneNumber, ids); int result = service.deleteRoute(phoneNumber, ids); if (result == 0) { return WVPResult.success(result); }else { WVPResult fail = WVPResult.fail(ErrorCode.ERROR100); fail.setData(result); return fail; } } @Operation(summary = "JT-查询路线", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "phoneNumber", description = "设备编号", required = true) @GetMapping("/route/query") public WVPResult> queryRoute(String phoneNumber, @RequestParam(value = "ids", required = false) List ids){ log.info("[JT-查询路线] phoneNumber: {}, ids:{}", phoneNumber, ids); List result = service.queryRoute(phoneNumber, ids); if (result != null) { return WVPResult.success(result); }else { return WVPResult.fail(ErrorCode.ERROR100); } } // TODO 待实现 行驶记录数据采集命令 行驶记录数据上传 行驶记录参数下传命令 电子运单上报 CAN总线数据上传 @Operation(summary = "JT-上报驾驶员身份信息请求", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "phoneNumber", description = "设备编号", required = true) @GetMapping("/driver-information") public WVPResult queryDriverInformation(String phoneNumber){ log.info("[JT-上报驾驶员身份信息请求] phoneNumber: {}", phoneNumber); JTDriverInformation jtDriverInformation = service.queryDriverInformation(phoneNumber); if (jtDriverInformation != null) { return WVPResult.success(jtDriverInformation); }else { return WVPResult.fail(ErrorCode.ERROR100); } } @Operation(summary = "JT-摄像头立即拍摄命令", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "phoneNumber", description = "设备编号", required = true) @PostMapping("/shooting") public WVPResult> shooting(@RequestBody ShootingParam param){ log.info("[JT-摄像头立即拍摄命令] param: {}", param ); List ids = service.shooting(param.getPhoneNumber(), param.getShootingCommand()); if (ids != null) { return WVPResult.success(ids); }else { return WVPResult.fail(ErrorCode.ERROR100); } } @Operation(summary = "JT-抓图", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "phoneNumber", description = "设备编号", required = true) @Parameter(name = "channelId", description = "通道编号", required = true) @GetMapping("/snap") public void snap(HttpServletResponse response, String phoneNumber, Integer channelId){ log.info("[JT-抓图] 设备编号: {}, 通道编号: {}", phoneNumber, channelId ); Assert.notNull(channelId, "缺少通道编号"); try { ServletOutputStream outputStream = response.getOutputStream(); response.setContentType(MediaType.IMAGE_JPEG_VALUE); // response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(phoneNumber + "_" + channelId + ".jpg", "UTF-8")); byte[] data = service.snap(phoneNumber, channelId); outputStream.write(data); outputStream.flush(); }catch (Exception e) { throw new ControllerException(ErrorCode.ERROR100.getCode(), e.getMessage()); } } @Operation(summary = "JT-存储多媒体数据检索", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "param", description = "存储多媒体数据参数", required = true) @PostMapping("/media/list") public WVPResult> queryMediaData(@RequestBody QueryMediaDataParam param){ log.info("[JT-存储多媒体数据检索] param: {}", param ); List ids = service.queryMediaData(param.getPhoneNumber(), param.getQueryMediaDataCommand()); if (ids != null) { return WVPResult.success(ids); }else { return WVPResult.fail(ErrorCode.ERROR100); } } @Operation(summary = "JT-单条存储多媒体数据上传", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "phoneNumber", description = "设备编号", required = true) @Parameter(name = "mediaId", description = "多媒体ID", required = true) @GetMapping("/media/upload/one/upload") public void uploadOneMedia(HttpServletResponse response, String phoneNumber, Long mediaId){ log.info("[JT-单条存储多媒体数据上传] 设备编号: {}, 多媒体ID: {}", phoneNumber, mediaId ); Assert.notNull(mediaId, "缺少通道编号"); try { ServletOutputStream outputStream = response.getOutputStream(); response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); service.uploadOneMedia(phoneNumber, mediaId, outputStream, false); }catch (Exception e) { throw new ControllerException(ErrorCode.ERROR100.getCode(), e.getMessage()); } } @Operation(summary = "JT-单条存储多媒体数据删除", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "phoneNumber", description = "设备编号", required = true) @Parameter(name = "mediaId", description = "多媒体ID", required = true) @GetMapping("/media/upload/one/delete") public void deleteOneMedia(HttpServletResponse response, String phoneNumber, Long mediaId){ log.info("[JT-单条存储多媒体数据上传] 设备编号: {}, 多媒体ID: {}", phoneNumber, mediaId ); Assert.notNull(mediaId, "缺少通道编号"); try { ServletOutputStream outputStream = response.getOutputStream(); response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); service.uploadOneMedia(phoneNumber, mediaId, outputStream, true); }catch (Exception e) { throw new ControllerException(ErrorCode.ERROR100.getCode(), e.getMessage()); } } // @Operation(summary = "JT-存储多媒体数据上传命令", security = @SecurityRequirement(name = JwtUtils.HEADER)) // @Parameter(name = "param", description = "存储多媒体数据参数", required = true) // @PostMapping("/media-data-upload") // public DeferredResult>> updateMediaData(@RequestBody QueryMediaDataParam param){ // // log.info("[JT-存储多媒体数据上传命令] param: {}", param ); // DeferredResult>> deferredResult = new DeferredResult<>(30000L); // List resultList = new ArrayList<>(); // // deferredResult.onTimeout(()->{ // log.info("[JT-存储多媒体数据上传命令超时] param: {}", param ); // WVPResult> fail = WVPResult.fail(ErrorCode.ERROR100); // fail.setMsg("超时"); // fail.setData(resultList); // deferredResult.setResult(fail); // }); // List ids; // if (param.getMediaId() != null) { // ids = new ArrayList<>(); // JTMediaDataInfo mediaDataInfo = new JTMediaDataInfo(); // mediaDataInfo.setId(param.getMediaId()); // ids.add(mediaDataInfo); // }else { // ids = service.queryMediaData(param.getPhoneNumber(), param.getQueryMediaDataCommand()); // } // if (ids.isEmpty()) { // deferredResult.setResult(WVPResult.fail(ErrorCode.ERROR100)); // return deferredResult; // } // Map idMap= new HashMap<>(); // for (JTMediaDataInfo mediaDataInfo : ids) { // idMap.put(mediaDataInfo.getId() + ".jpg", mediaDataInfo); // } // // 开启文件监听 // FileAlterationObserver observer = new FileAlterationObserver(new File("mediaEvent")); // observer.addListener(new FileAlterationListenerAdaptor() { // @Override // public void onFileCreate(File file) { // if (idMap.containsKey(file.getName())) { // idMap.remove(file.getName()); // resultList.add("mediaEvent" + File.separator + file.getName()); // if (idMap.isEmpty()) { // deferredResult.setResult(WVPResult.success(resultList)); // } // } // } // }); // FileAlterationMonitor monitor = new FileAlterationMonitor(5, observer); // try { // monitor.start(); // } catch (Exception e) { // log.info("[JT-存储多媒体数据上传命令监听文件失败] param: {}", param ); // deferredResult.setResult(null); // return deferredResult; // } // taskExecutor.execute(()->{ // if (param.getMediaId() != null) { // service.uploadMediaDataForSingle(param.getPhoneNumber(), param.getMediaId(), param.getDelete()); // }else { // service.uploadMediaData(param.getPhoneNumber(), param.getQueryMediaDataCommand()); // } // // }); // return deferredResult; // } @Operation(summary = "JT-开始录音", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "phoneNumber", description = "设备手机号", required = true) @Parameter(name = "time", description = "录音时间,单位为秒(s) ,0 表示一直录音", required = false) @Parameter(name = "save", description = "0:实时上传;1:保存", required = false) @Parameter(name = "samplingRate", description = "音频采样率, 0:8K;1:11K;2:23K;3:32K", required = false) @GetMapping("/record/start") public void startRecord(HttpServletRequest request, @Parameter(required = true) String phoneNumber, @Parameter(required = false) Integer time, @Parameter(required = false) Integer save, @Parameter(required = false) Integer samplingRate ) { if (ObjectUtils.isEmpty(time)) { time = 0; } if (ObjectUtils.isEmpty(save)) { save = 0; } if (ObjectUtils.isEmpty(samplingRate)) { samplingRate = 0; } service.record(phoneNumber, 1, time, save, samplingRate); } @Operation(summary = "JT-停止录音", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "phoneNumber", description = "设备手机号", required = true) @Parameter(name = "time", description = "录音时间,单位为秒(s) ,0 表示一直录音", required = false) @Parameter(name = "save", description = "0:实时上传;1:保存", required = false) @Parameter(name = "samplingRate", description = "音频采样率, 0:8K;1:11K;2:23K;3:32K", required = false) @GetMapping("/record/stop") public void stopRecord(HttpServletRequest request, @Parameter(required = true) String phoneNumber, @Parameter(required = false) Integer time, @Parameter(required = false) Integer save, @Parameter(required = false) Integer samplingRate ) { if (ObjectUtils.isEmpty(time)) { time = 0; } if (ObjectUtils.isEmpty(save)) { save = 0; } if (ObjectUtils.isEmpty(samplingRate)) { samplingRate = 0; } service.record(phoneNumber, 0, time, save, samplingRate); } @Operation(summary = "JT-查询终端音视频属性", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "phoneNumber", description = "设备手机号", required = true) @GetMapping("/media/attribute") public JTMediaAttribute queryMediaAttribute( @Parameter(required = true) String phoneNumber ) { return service.queryMediaAttribute(phoneNumber); } // TODO 视频报警上报 } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/controller/JT1078TerminalController.java ================================================ package com.genersoft.iot.vmp.jt1078.controller; import com.genersoft.iot.vmp.conf.security.JwtUtils; import com.genersoft.iot.vmp.jt1078.bean.JTChannel; import com.genersoft.iot.vmp.jt1078.bean.JTDevice; import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; import com.github.pagehelper.PageInfo; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.web.bind.annotation.*; @Slf4j @ConditionalOnProperty(value = "jt1078.enable", havingValue = "true") @RestController @Tag(name = "部标终端以及通道管理") @RequestMapping("/api/jt1078/terminal") public class JT1078TerminalController { @Resource Ijt1078Service service; @Operation(summary = "JT-分页查询部标设备", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "page", description = "当前页", required = true) @Parameter(name = "count", description = "每页查询数量", required = true) @Parameter(name = "query", description = "查询内容") @Parameter(name = "online", description = "是否在线") @GetMapping("/list") public PageInfo getDevices(int page, int count, @RequestParam(required = false) String query, @RequestParam(required = false) Boolean online) { return service.getDeviceList(page, count, query, online); } @Operation(summary = "更新设备", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "device", description = "设备", required = true) @PostMapping("/update") public void updateDevice(JTDevice device){ assert device.getId() > 0; assert device.getPhoneNumber() != null; service.updateDevice(device); } @Operation(summary = "JT-新增设备", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "device", description = "设备", required = true) @PostMapping("/add") public void addDevice(JTDevice device){ assert device.getPhoneNumber() != null; String phoneNumber = device.getPhoneNumber().replaceFirst("^0*", ""); device.setPhoneNumber(phoneNumber); service.addDevice(device); } @Operation(summary = "删除设备", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "phoneNumber", description = "设备手机号", required = true) @DeleteMapping("/delete") public void addDevice(String phoneNumber){ assert phoneNumber != null; service.deleteDeviceByPhoneNumber(phoneNumber); } @Operation(summary = "查询设备", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "phoneNumber", description = "设备手机号", required = true) @GetMapping("/query") public JTDevice getDevice(Integer deviceId){ return service.getDeviceById(deviceId); } @Operation(summary = "JT-查询部标通道", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "page", description = "当前页", required = true) @Parameter(name = "count", description = "每页查询数量", required = true) @Parameter(name = "deviceId", description = "设备ID", required = true) @Parameter(name = "query", description = "查询内容") @GetMapping("/channel/list") public PageInfo getChannels(int page, int count, @RequestParam(required = true) Integer deviceId, @RequestParam(required = false) String query) { assert deviceId != null; return service.getChannelList(page, count, deviceId, query); } @Operation(summary = "JT-查询单个部标通道", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "id", description = "通道数据库ID", required = true) @GetMapping("/channel/one") public JTChannel getChannel(Integer id) { assert id != null; return service.getChannelByDbId(id); } @Operation(summary = "JT-更新通道", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "channel", description = "通道", required = true) @PostMapping("/channel/update") public void updateChannel(@RequestBody JTChannel channel){ assert channel.getId() > 0; assert channel.getChannelId() != null; service.updateChannel(channel); } @Operation(summary = "JT-新增通道", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "channel", description = "通道", required = true) @PostMapping("/channel/add") public JTChannel addChannel(@RequestBody JTChannel channel){ assert channel.getChannelId() != null; assert channel.getTerminalDbId() != 0; service.addChannel(channel); return channel; } @Operation(summary = "JT-删除通道", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "id", description = "通道的数据库ID", required = true) @DeleteMapping("/channel/delete") public void deleteChannel(Integer id){ service.deleteChannelById(id); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/controller/bean/ConfirmationAlarmMessageParam.java ================================================ package com.genersoft.iot.vmp.jt1078.controller.bean; import com.genersoft.iot.vmp.jt1078.bean.JTConfirmationAlarmMessageType; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.Setter; /** * 人工确认报警消息参数 */ @Setter @Getter @Schema(description = "人工确认报警消息参数") public class ConfirmationAlarmMessageParam { @Schema(description = "终端手机号") private String phoneNumber; @Schema(description = "报警消息流水号") private int alarmPackageNo; @Schema(description = "人工确认报警类型") private JTConfirmationAlarmMessageType alarmMessageType; @Override public String toString() { return "ConfirmationAlarmMessageParam{" + "PhoneNumber='" + phoneNumber + '\'' + ", alarmPackageNo=" + alarmPackageNo + ", alarmMessageType=" + alarmMessageType + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/controller/bean/ConnectionControlParam.java ================================================ package com.genersoft.iot.vmp.jt1078.controller.bean; import com.genersoft.iot.vmp.jt1078.bean.JTDeviceConnectionControl; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.Setter; @Setter @Getter public class ConnectionControlParam { @Schema(description = "终端手机号") private String phoneNumber; private JTDeviceConnectionControl control; @Override public String toString() { return "ConnectionControlParam{" + "deviceId='" + phoneNumber + '\'' + ", control=" + control + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/controller/bean/QueryMediaDataParam.java ================================================ package com.genersoft.iot.vmp.jt1078.controller.bean; import com.genersoft.iot.vmp.jt1078.bean.JTQueryMediaDataCommand; import com.genersoft.iot.vmp.jt1078.bean.JTShootingCommand; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.Setter; @Setter @Getter @Schema(description = "存储多媒体数据参数") public class QueryMediaDataParam { @Schema(description = "终端手机号") private String phoneNumber; @Schema(description = "多媒体 ID, 单条存储多媒体数据检索上传时有效") private Long mediaId; @Schema(description = "删除标志, 单条存储多媒体数据检索上传时有效") private int delete; @Schema(description = "存储多媒体数据参数") private JTQueryMediaDataCommand queryMediaDataCommand; @Override public String toString() { return "QueryMediaDataParam{" + "设备手机号='" + phoneNumber + '\'' + ", mediaId=" + mediaId + ", queryMediaDataCommand=" + queryMediaDataCommand + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/controller/bean/SetAreaParam.java ================================================ package com.genersoft.iot.vmp.jt1078.controller.bean; import com.genersoft.iot.vmp.jt1078.bean.*; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.Setter; import java.util.List; @Setter @Getter @Schema(description = "设置区域参数") public class SetAreaParam { @Schema(description = "终端手机号") private String phoneNumber; @Schema(description = "圆形区域项") private List circleAreaList; @Schema(description = "矩形区域项") private List rectangleAreas; @Schema(description = "多边形区域") private JTPolygonArea polygonArea; @Schema(description = "路线") private JTRoute route; @Override public String toString() { return "SetAreaParam{" + "设备手机号='" + phoneNumber + '\'' + ", circleAreaList=" + circleAreaList + ", rectangleAreas=" + rectangleAreas + ", polygonArea=" + polygonArea + ", route=" + route + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/controller/bean/SetConfigParam.java ================================================ package com.genersoft.iot.vmp.jt1078.controller.bean; import com.genersoft.iot.vmp.jt1078.bean.JTDeviceConfig; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.Setter; @Setter @Getter @Schema(description = "终端参数设置") public class SetConfigParam { @Schema(description = "终端手机号") private String phoneNumber; @Schema(description = "终端参数设置") private JTDeviceConfig config; @Override public String toString() { return "SetConfigParam{" + "phoneNumber='" + phoneNumber + '\'' + ", config=" + config + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/controller/bean/SetPhoneBookParam.java ================================================ package com.genersoft.iot.vmp.jt1078.controller.bean; import com.genersoft.iot.vmp.jt1078.bean.JTPhoneBookContact; import com.genersoft.iot.vmp.jt1078.bean.JTTextSign; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.Setter; import java.util.List; @Setter @Getter @Schema(description = "设置电话本") public class SetPhoneBookParam { @Schema(description = "终端手机号") private String phoneNumber; @Schema(description = "设置类型:\n" + "0: 删除终端上所有存储的联系人,\n" + "1: 表示更新电话本, 删除终端中已有全部联系人并追加消息中的联系人,\n" + "2: 表示追加电话本,\n" + "3: 表示修改电话本$以联系人为索引") private int type; @Schema(description = "联系人") private List phoneBookContactList; @Override public String toString() { return "SetPhoneBookParam{" + "设备手机号='" + phoneNumber + '\'' + ", type=" + type + ", phoneBookContactList=" + phoneBookContactList + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/controller/bean/ShootingParam.java ================================================ package com.genersoft.iot.vmp.jt1078.controller.bean; import com.genersoft.iot.vmp.jt1078.bean.JTShootingCommand; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.Setter; @Setter @Getter @Schema(description = "摄像头立即拍摄命令参数") public class ShootingParam { @Schema(description = "终端手机号") private String phoneNumber; @Schema(description = "拍摄命令参数") private JTShootingCommand shootingCommand; @Override public String toString() { return "ShootingParam{" + "设备手机号='" + phoneNumber + '\'' + ", shootingCommand=" + shootingCommand + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/controller/bean/TextMessageParam.java ================================================ package com.genersoft.iot.vmp.jt1078.controller.bean; import com.genersoft.iot.vmp.jt1078.bean.JTTextSign; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.Setter; /** * 文本信息下发参数 */ @Setter @Getter @Schema(description = "人工确认报警消息参数") public class TextMessageParam { @Schema(description = "终端手机号") private String phoneNumber; @Schema(description = "标志") private JTTextSign sign; @Schema(description = "文本类型,1 = 通知 ,2 = 服务") private int textType; @Schema(description = "消息内容,最长为1024字节") private String content; @Override public String toString() { return "TextMessageParam{" + "phoneNumber='" + phoneNumber + '\'' + ", sign=" + sign + ", textType=" + textType + ", content='" + content + '\'' + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/dao/JTChannelMapper.java ================================================ package com.genersoft.iot.vmp.jt1078.dao; import com.genersoft.iot.vmp.jt1078.bean.JTChannel; import com.genersoft.iot.vmp.jt1078.dao.provider.JTChannelProvider; import org.apache.ibatis.annotations.*; import java.util.List; @Mapper public interface JTChannelMapper { @SelectProvider(type = JTChannelProvider.class, method = "selectAll") List selectAll(@Param("terminalDbId") int terminalDbId, @Param("query") String query); @Update(value = {" "}) void update(JTChannel channel); @Insert(value = {" "}) @Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id") void add(JTChannel channel); @Delete("delete from wvp_jt_channel where id = #{id}") void delete(@Param("id") int id); @SelectProvider(type = JTChannelProvider.class, method = "selectChannelByChannelId") JTChannel selectChannelByChannelId(@Param("terminalDbId") int terminalDbId, @Param("channelId") Integer channelId); @SelectProvider(type = JTChannelProvider.class, method = "selectChannelById") JTChannel selectChannelById(@Param("id") Integer id); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/dao/JTTerminalMapper.java ================================================ package com.genersoft.iot.vmp.jt1078.dao; import com.genersoft.iot.vmp.jt1078.bean.JTDevice; import org.apache.ibatis.annotations.*; import java.util.List; @Mapper public interface JTTerminalMapper { @Select("SELECT * FROM wvp_jt_terminal where phone_number=#{phoneNumber}") JTDevice getDevice(@Param("phoneNumber") String phoneNumber); @Update(value = {" "}) void updateDevice(JTDevice device); @Select(value = {" "}) List getDeviceList(@Param("query") String query, @Param("online") Boolean online); @Insert("INSERT INTO wvp_jt_terminal (" + "terminal_id,"+ "province_id,"+ "province_text,"+ "city_id,"+ "city_text,"+ "maker_id,"+ "phone_number,"+ "model,"+ "plate_color,"+ "plate_no,"+ "longitude,"+ "latitude,"+ "create_time,"+ "register_time,"+ "update_time"+ ") VALUES (" + "#{terminalId}," + "#{provinceId}," + "#{provinceText}," + "#{cityId}," + "#{cityText}," + "#{makerId}," + "#{phoneNumber}," + "#{model}," + "#{plateColor}," + "#{plateNo}," + "#{longitude}," + "#{latitude}," + "#{createTime}," + "#{registerTime}," + "#{updateTime}" + ")") void addDevice(JTDevice device); @Delete("delete from wvp_jt_terminal where phone_number = #{phoneNumber}") void deleteDeviceByPhoneNumber(@Param("phoneNumber") String phoneNumber); @Update(value = {" "}) void updateDeviceStatus(@Param("connected") boolean connected, @Param("phoneNumber") String phoneNumber); @Select("SELECT * FROM wvp_jt_terminal where id=#{deviceId}") JTDevice getDeviceById(@Param("deviceId") Integer deviceId); @Update({""}) void batchUpdateDevicePosition(List devices); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/dao/provider/JTChannelProvider.java ================================================ package com.genersoft.iot.vmp.jt1078.dao.provider; import java.util.Map; public class JTChannelProvider { public final static String BASE_SQL = "SELECT jc.*, jc.id as data_device_id, wdc.*, wdc.id as gb_id " + " from wvp_jt_channel jc " + " LEFT join wvp_device_channel wdc " + " on jc.id = wdc.data_device_id and wdc.data_type = 200 "; public String selectChannelByChannelId(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(BASE_SQL); sqlBuild.append(" WHERE jc.terminal_db_id = #{terminalDbId} and jc.channel_id = #{channelId} "); return sqlBuild.toString(); } public String selectChannelById(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(BASE_SQL); sqlBuild.append(" WHERE jc.id = #{id}"); return sqlBuild.toString(); } public String selectAll(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(BASE_SQL); sqlBuild.append(" WHERE jc.terminal_db_id = #{terminalDbId} "); if (params.get("query") != null) { sqlBuild.append(" AND ") .append(" jc.name LIKE ").append("'%").append(params.get("query")).append("%'") ; } sqlBuild.append(" ORDER BY jc.channel_id "); return sqlBuild.toString(); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/event/ConnectChangeEvent.java ================================================ package com.genersoft.iot.vmp.jt1078.event; import com.genersoft.iot.vmp.jt1078.bean.JTDevice; import lombok.Getter; import lombok.Setter; import org.springframework.context.ApplicationEvent; import java.io.Serial; import java.time.Clock; /** * 链接断或者连接的事件 */ @Setter @Getter public class ConnectChangeEvent extends ApplicationEvent { @Serial private static final long serialVersionUID = 1L; public ConnectChangeEvent(Object source) { super(source); } private boolean connected; private String phoneNumber; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/event/DeviceUpdateEvent.java ================================================ package com.genersoft.iot.vmp.jt1078.event; import com.genersoft.iot.vmp.jt1078.bean.JTDevice; import lombok.Getter; import lombok.Setter; import org.springframework.context.ApplicationEvent; import java.io.Serial; /** * 设备更新事件 */ public class DeviceUpdateEvent extends ApplicationEvent { @Serial private static final long serialVersionUID = 1L; public DeviceUpdateEvent(Object source) { super(source); } @Getter @Setter private JTDevice device; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/event/FtpUploadEvent.java ================================================ package com.genersoft.iot.vmp.jt1078.event; import lombok.Getter; import lombok.Setter; import org.springframework.context.ApplicationEvent; @Setter @Getter public class FtpUploadEvent extends ApplicationEvent { public FtpUploadEvent(Object source) { super(source); } private String fileName; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/event/JTPositionEvent.java ================================================ package com.genersoft.iot.vmp.jt1078.event; import com.genersoft.iot.vmp.jt1078.bean.JTDevice; import com.genersoft.iot.vmp.jt1078.bean.JTPositionBaseInfo; import lombok.Getter; import lombok.Setter; import org.springframework.context.ApplicationEvent; import java.io.Serial; /** * 设备更新事件 */ public class JTPositionEvent extends ApplicationEvent { @Serial private static final long serialVersionUID = 1L; public JTPositionEvent(Object source) { super(source); } @Getter @Setter private String phoneNumber; @Getter @Setter private JTPositionBaseInfo positionInfo; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/event/eventListener/ConnectChangeEventListener.java ================================================ package com.genersoft.iot.vmp.jt1078.event.eventListener; import com.genersoft.iot.vmp.jt1078.bean.JTDevice; import com.genersoft.iot.vmp.jt1078.event.ConnectChangeEvent; import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; import com.genersoft.iot.vmp.jt1078.session.SessionManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationListener; import org.springframework.stereotype.Component; @Component public class ConnectChangeEventListener implements ApplicationListener { private final static Logger log = LoggerFactory.getLogger(ConnectChangeEventListener.class); @Autowired private Ijt1078Service service; @Override public void onApplicationEvent(ConnectChangeEvent event) { if (event.isConnected()) { log.info("[JT-设备已连接] 终端ID: {}", event.getPhoneNumber()); }else{ log.info("[JT-设备连接已断开] 终端ID: {}", event.getPhoneNumber()); if(SessionManager.INSTANCE.get(event.getPhoneNumber()) != null) { SessionManager.INSTANCE.get(event.getPhoneNumber()).unregister(); } } JTDevice device = service.getDevice(event.getPhoneNumber()); if (device != null) { service.updateDeviceStatus(event.isConnected(), event.getPhoneNumber()); } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/Header.java ================================================ package com.genersoft.iot.vmp.jt1078.proc; import com.genersoft.iot.vmp.jt1078.util.Bin; import lombok.Data; /** * @author QingtaiJiang * @date 2023/4/27 18:22 * @email qingtaij@163.com */ @Data public class Header { // 消息ID String msgId; // 消息体属性 Integer msgPro; // 终端手机号 String phoneNumber; // 消息体流水号 Integer sn; // 协议版本号 Short version = -1; /** * 判断是否是2019的版本 * * @return true 2019后的版本。false 2013 */ public boolean is2019Version() { return Bin.get(msgPro, 14); } @Override public String toString() { return "Header{" + "消息ID='" + msgId + '\'' + ", 消息体属性=" + msgPro + ", 终端手机号='" + phoneNumber + '\'' + ", 消息体流水号=" + sn + ", 协议版本号=" + version + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/entity/Cmd.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.entity; import com.genersoft.iot.vmp.jt1078.proc.response.Rs; import lombok.Data; /** * @author QingtaiJiang * @date 2023/4/27 18:23 * @email qingtaij@163.com */ @Data public class Cmd { String phoneNumber; Long packageNo; String msgId; String respId; Rs rs; public Cmd() { } public Cmd(Builder builder) { this.phoneNumber = builder.phoneNumber; this.packageNo = builder.packageNo; this.msgId = builder.msgId; this.respId = builder.respId; this.rs = builder.rs; } public static class Builder { String phoneNumber; Long packageNo; String msgId; String respId; Rs rs; public Builder setPhoneNumber(String phoneNumber) { this.phoneNumber = phoneNumber.replaceFirst("^0*", ""); return this; } public Builder setPackageNo(Long packageNo) { this.packageNo = packageNo; return this; } public Builder setMsgId(String msgId) { this.msgId = msgId; return this; } public Builder setRespId(String respId) { this.respId = respId; return this; } public Builder setRs(Rs re) { this.rs = re; return this; } public Cmd build() { return new Cmd(this); } } @Override public String toString() { return "Cmd{" + "devId='" + phoneNumber + '\'' + ", packageNo=" + packageNo + ", msgId='" + msgId + '\'' + ", respId='" + respId + '\'' + ", rs=" + rs + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/factory/CodecFactory.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.factory; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.proc.request.Re; import com.genersoft.iot.vmp.jt1078.util.ClassUtil; import lombok.extern.slf4j.Slf4j; import java.util.HashMap; import java.util.List; import java.util.Map; /** * @author QingtaiJiang * @date 2023/4/27 18:29 * @email qingtaij@163.com */ @Slf4j public class CodecFactory { private static Map> protocolHash; public static void init() { protocolHash = new HashMap<>(); List> classList = ClassUtil.getClassList("com.genersoft.iot.vmp.jt1078.proc", MsgId.class); for (Class handlerClass : classList) { String id = handlerClass.getAnnotation(MsgId.class).id(); protocolHash.put(id, handlerClass); } if (log.isDebugEnabled()) { log.debug("消息ID缓存表 protocolHash:{}", protocolHash); } } public static Re getHandler(String msgId) { Class aClass = protocolHash.get(msgId); Object bean = ClassUtil.getBean(aClass); if (bean instanceof Re) { return (Re) bean; } return null; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0001.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.request; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.proc.Header; import com.genersoft.iot.vmp.jt1078.proc.response.Rs; import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; import com.genersoft.iot.vmp.jt1078.session.Session; import com.genersoft.iot.vmp.jt1078.session.SessionManager; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import lombok.Getter; import org.springframework.context.ApplicationEvent; /** * 终端通用应答 * * @author QingtaiJiang * @date 2023/4/27 18:04 * @email qingtaij@163.com */ @Getter @MsgId(id = "0001") public class J0001 extends Re { int respNo; String respId; /** * 0:成功/确认;1:失败;2:消息有误;3:不支持 */ int result; @Override protected Rs decode0(ByteBuf buf, Header header, Session session) { respNo = buf.readUnsignedShort(); respId = ByteBufUtil.hexDump(buf.readSlice(2)); result = buf.readUnsignedByte(); return null; } @Override protected Rs handler(Header header, Session session, Ijt1078Service service) { SessionManager.INSTANCE.response(header.getPhoneNumber(), "0001", (long) respNo, result); return null; } @Override public ApplicationEvent getEvent() { return null; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0002.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.request; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.proc.Header; import com.genersoft.iot.vmp.jt1078.proc.response.J8001; import com.genersoft.iot.vmp.jt1078.proc.response.Rs; import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; import com.genersoft.iot.vmp.jt1078.session.Session; import io.netty.buffer.ByteBuf; import lombok.extern.slf4j.Slf4j; import org.springframework.context.ApplicationEvent; /** * 终端心跳 * * @author QingtaiJiang * @date 2023/4/27 18:04 * @email qingtaij@163.com */ @Slf4j @MsgId(id = "0002") public class J0002 extends Re { @Override protected Rs decode0(ByteBuf buf, Header header, Session session) { return null; } @Override protected Rs handler(Header header, Session session, Ijt1078Service service) { log.info("[终端心跳] {}", header.getPhoneNumber()); J8001 j8001 = new J8001(); j8001.setRespNo(header.getSn()); j8001.setRespId(header.getMsgId()); j8001.setResult(J8001.SUCCESS); return j8001; } @Override public ApplicationEvent getEvent() { return null; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0003.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.request; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.proc.Header; import com.genersoft.iot.vmp.jt1078.proc.response.Rs; import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; import com.genersoft.iot.vmp.jt1078.session.Session; import com.genersoft.iot.vmp.jt1078.session.SessionManager; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.springframework.context.ApplicationEvent; /** * 终端注销 */ @Slf4j @Getter @MsgId(id = "0003") public class J0003 extends Re { int respNo; String respId; int result; @Override protected Rs decode0(ByteBuf buf, Header header, Session session) { respNo = buf.readUnsignedShort(); respId = ByteBufUtil.hexDump(buf.readSlice(2)); result = buf.readUnsignedByte(); log.info("[JT-注销] 设备: {}", header.getPhoneNumber()); return null; } @Override protected Rs handler(Header header, Session session, Ijt1078Service service) { SessionManager.INSTANCE.response(header.getPhoneNumber(), "0001", (long) respNo, result); return null; } @Override public ApplicationEvent getEvent() { return null; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0004.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.request; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.proc.Header; import com.genersoft.iot.vmp.jt1078.proc.response.Rs; import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; import com.genersoft.iot.vmp.jt1078.session.Session; import io.netty.buffer.ByteBuf; import org.springframework.context.ApplicationEvent; /** * 查询服务器时间 * * @author QingtaiJiang * @date 2023/4/27 18:06 * @email qingtaij@163.com */ @MsgId(id = "0004") public class J0004 extends Re { @Override protected Rs decode0(ByteBuf buf, Header header, Session session) { return null; } @Override protected Rs handler(Header header, Session session, Ijt1078Service service) { return null; } @Override public ApplicationEvent getEvent() { return null; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0100.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.request; import com.genersoft.iot.vmp.common.CivilCodePo; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.bean.JTDevice; import com.genersoft.iot.vmp.jt1078.event.DeviceUpdateEvent; import com.genersoft.iot.vmp.jt1078.proc.Header; import com.genersoft.iot.vmp.jt1078.proc.response.J8100; import com.genersoft.iot.vmp.jt1078.proc.response.Rs; import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; import com.genersoft.iot.vmp.jt1078.session.Session; import com.genersoft.iot.vmp.utils.CivilCodeUtil; import com.genersoft.iot.vmp.utils.DateUtil; import io.netty.buffer.ByteBuf; import lombok.extern.slf4j.Slf4j; import org.springframework.context.ApplicationEvent; import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; import java.util.UUID; /** * 终端注册 * * @author QingtaiJiang * @date 2023/4/27 18:06 * @email qingtaij@163.com */ @Slf4j @MsgId(id = "0100") public class J0100 extends Re { private JTDevice device; private JTDevice deviceForUpdate; @Override protected Rs decode0(ByteBuf buf, Header header, Session session) { Short version = header.getVersion(); device = new JTDevice(); device.setProvinceId(buf.readUnsignedShort() + ""); if (version >= 1) { device.setCityId(buf.readUnsignedShort() + ""); // decode as 2019 device.setMakerId(buf.readCharSequence(11, Charset.forName("GBK")) .toString().trim()); device.setModel(buf.readCharSequence(30, Charset.forName("GBK")) .toString().trim()); device.setTerminalId(buf.readCharSequence(30, Charset.forName("GBK")) .toString().trim()); device.setPlateColor(buf.readByte()); device.setPlateNo(buf.readCharSequence(buf.readableBytes(), Charset.forName("GBK")) .toString().trim()); } else { // decode as 2013 device.setCityId(buf.readUnsignedShort() + ""); // decode as 2019 byte[] bytes5 = new byte[5]; buf.readBytes(bytes5); device.setMakerId(new String(bytes5).trim()); byte[] bytes20 = new byte[20]; buf.readBytes(bytes20); device.setModel(new String(bytes20).trim()); byte[] bytes7 = new byte[7]; buf.readBytes(bytes7); device.setTerminalId(new String(bytes7).trim()); device.setPlateColor(buf.readByte()); byte[] plateColorBytes = new byte[buf.readableBytes()]; buf.readBytes(plateColorBytes); try { device.setPlateNo(new String(plateColorBytes, "GBK").trim()); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } return null; } @Override protected Rs handler(Header header, Session session, Ijt1078Service service) { J8100 j8100 = new J8100(); j8100.setRespNo(header.getSn()); // 从数据库判断这个设备是否合法 deviceForUpdate = service.getDevice(header.getPhoneNumber()); if (deviceForUpdate != null) { j8100.setResult(J8100.SUCCESS); String authenticationCode = UUID.randomUUID().toString(); j8100.setCode(authenticationCode); session.setAuthenticationCode(authenticationCode); deviceForUpdate.setStatus(true); deviceForUpdate.setProvinceId(device.getProvinceId()); deviceForUpdate.setRegisterTime(DateUtil.getNow()); CivilCodePo provinceCivilCodePo = CivilCodeUtil.INSTANCE.get(device.getProvinceId()); if (provinceCivilCodePo != null) { deviceForUpdate.setProvinceText(provinceCivilCodePo.getName()); } deviceForUpdate.setCityId(device.getCityId()); CivilCodePo cityCivilCodePo = CivilCodeUtil.INSTANCE.get(device.getProvinceId() + String.format("%04d", Integer.parseInt(device.getCityId()))); if (cityCivilCodePo != null) { deviceForUpdate.setCityText(cityCivilCodePo.getName()); } deviceForUpdate.setModel(device.getModel()); deviceForUpdate.setMakerId(device.getMakerId()); deviceForUpdate.setTerminalId(device.getTerminalId()); // TODO 支持直接展示车牌颜色的描述 deviceForUpdate.setPlateColor(device.getPlateColor()); deviceForUpdate.setPlateNo(device.getPlateNo()); log.info("[JT-注册成功] 设备: {}", deviceForUpdate); }else { log.info("[JT-注册失败] 未授权设备: {}", header.getPhoneNumber()); j8100.setResult(J8100.FAIL); // 断开连接,清理资源 if (session.isRegistered()) { session.unregister(); } } return j8100; } @Override public ApplicationEvent getEvent() { DeviceUpdateEvent registerEvent = new DeviceUpdateEvent(this); registerEvent.setDevice(deviceForUpdate); return registerEvent; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0102.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.request; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.bean.JTDevice; import com.genersoft.iot.vmp.jt1078.event.DeviceUpdateEvent; import com.genersoft.iot.vmp.jt1078.proc.Header; import com.genersoft.iot.vmp.jt1078.proc.response.J8001; import com.genersoft.iot.vmp.jt1078.proc.response.Rs; import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; import com.genersoft.iot.vmp.jt1078.session.Session; import io.netty.buffer.ByteBuf; import lombok.extern.slf4j.Slf4j; import org.springframework.context.ApplicationEvent; import java.nio.charset.Charset; /** * 终端鉴权 * * @author QingtaiJiang * @date 2023/4/27 18:06 * @email qingtaij@163.com */ @Slf4j @MsgId(id = "0102") public class J0102 extends Re { private String authenticationCode; private JTDevice deviceForUpdate; @Override protected Rs decode0(ByteBuf buf, Header header, Session session) { if (header.is2019Version()) { int lenCode = buf.readUnsignedByte(); authenticationCode = buf.readCharSequence(lenCode, Charset.forName("GBK")).toString(); }else { authenticationCode = buf.readCharSequence(buf.readableBytes(), Charset.forName("GBK")).toString(); } log.info("设备鉴权: authenticationCode: " + authenticationCode); return null; } @Override protected Rs handler(Header header, Session session, Ijt1078Service service) { J8001 j8001 = new J8001(); j8001.setRespNo(header.getSn()); j8001.setRespId(header.getMsgId()); if (session.getAuthenticationCode() == null || !session.getAuthenticationCode().equals(authenticationCode)) { j8001.setResult(J8001.FAIL); }else { j8001.setResult(J8001.SUCCESS); JTDevice device = service.getDevice(header.getPhoneNumber()); if (device != null && !device.isStatus()) { deviceForUpdate = device; deviceForUpdate.setStatus(true); service.updateDevice(device); } } return j8001; } @Override public ApplicationEvent getEvent() { DeviceUpdateEvent registerEvent = new DeviceUpdateEvent(this); registerEvent.setDevice(deviceForUpdate); return registerEvent; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0104.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.request; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.bean.JTAlarmSign; import com.genersoft.iot.vmp.jt1078.bean.JTDeviceConfig; import com.genersoft.iot.vmp.jt1078.bean.common.ConfigAttribute; import com.genersoft.iot.vmp.jt1078.bean.config.*; import com.genersoft.iot.vmp.jt1078.proc.Header; import com.genersoft.iot.vmp.jt1078.proc.response.J8001; import com.genersoft.iot.vmp.jt1078.proc.response.Rs; import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; import com.genersoft.iot.vmp.jt1078.session.Session; import com.genersoft.iot.vmp.jt1078.session.SessionManager; import io.netty.buffer.ByteBuf; import org.apache.commons.lang3.StringUtils; import org.springframework.context.ApplicationEvent; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.nio.charset.Charset; import java.util.HashMap; import java.util.Map; /** * 查询终端参数应答 * */ @MsgId(id = "0104") public class J0104 extends Re { Integer respNo; Integer paramLength; @Override protected Rs decode0(ByteBuf buf, Header header, Session session) { respNo = buf.readUnsignedShort(); paramLength = (int) buf.readUnsignedByte(); if (paramLength <= 0) { return null; } JTDeviceConfig deviceConfig = new JTDeviceConfig(); Field[] fields = deviceConfig.getClass().getDeclaredFields(); Map allFieldMap = new HashMap<>(); Map allConfigAttributeMap = new HashMap<>(); for (Field field : fields) { ConfigAttribute configAttribute = field.getAnnotation(ConfigAttribute.class); if (configAttribute != null) { allFieldMap.put(configAttribute.id(), field); allConfigAttributeMap.put(configAttribute.id(), configAttribute); } } for (int i = 0; i < paramLength; i++) { long id = buf.readUnsignedInt(); if (!allFieldMap.containsKey(id)) { continue; } short length = buf.readUnsignedByte(); Field field = allFieldMap.get(id); try { switch (allConfigAttributeMap.get(id).type()) { case "Long": Method methodForLong = deviceConfig.getClass().getDeclaredMethod("set" + StringUtils.capitalize(field.getName()), Long.class); methodForLong.invoke(deviceConfig, buf.readUnsignedInt()); continue; case "String": Method methodForString = deviceConfig.getClass().getDeclaredMethod("set" + StringUtils.capitalize(field.getName()), String.class); String val = buf.readCharSequence(length, Charset.forName("GBK")).toString().trim(); methodForString.invoke(deviceConfig, val); continue; case "Integer": Method methodForInteger = deviceConfig.getClass().getDeclaredMethod("set" + StringUtils.capitalize(field.getName()), Integer.class); methodForInteger.invoke(deviceConfig, buf.readUnsignedShort()); continue; case "Short": Method methodForShort = deviceConfig.getClass().getDeclaredMethod("set" + StringUtils.capitalize(field.getName()), Short.class); methodForShort.invoke(deviceConfig, buf.readUnsignedByte()); continue; case "IllegalDrivingPeriods": JTIllegalDrivingPeriods illegalDrivingPeriods = new JTIllegalDrivingPeriods(); int startHour = buf.readUnsignedByte(); int startMinute = buf.readUnsignedByte(); int stopHour = buf.readUnsignedByte(); int stopMinute = buf.readUnsignedByte(); illegalDrivingPeriods.setStartTime(startHour + ":" + startMinute); illegalDrivingPeriods.setEndTime(stopHour + ":" + stopMinute); Method methodForIllegalDrivingPeriods = deviceConfig.getClass().getDeclaredMethod("set" + StringUtils.capitalize(field.getName()), JTIllegalDrivingPeriods.class); methodForIllegalDrivingPeriods.invoke(deviceConfig, illegalDrivingPeriods); continue; case "CollisionAlarmParams": JTCollisionAlarmParams collisionAlarmParams = new JTCollisionAlarmParams(); collisionAlarmParams.setCollisionAlarmTime(buf.readUnsignedByte()); collisionAlarmParams.setCollisionAcceleration(buf.readUnsignedByte()); Method methodForCollisionAlarmParams = deviceConfig.getClass().getDeclaredMethod("set" + StringUtils.capitalize(field.getName()), JTCollisionAlarmParams.class); methodForCollisionAlarmParams.invoke(deviceConfig, collisionAlarmParams); continue; case "CameraTimer": JTCameraTimer cameraTimer = new JTCameraTimer(); long cameraTimerContent = buf.readUnsignedInt(); cameraTimer.setSwitchForChannel1((cameraTimerContent & 1) == 1); cameraTimer.setSwitchForChannel2((cameraTimerContent >>> 1 & 1) == 1); cameraTimer.setSwitchForChannel3((cameraTimerContent >>> 2 & 1) == 1); cameraTimer.setSwitchForChannel4((cameraTimerContent >>> 3 & 1) == 1); cameraTimer.setSwitchForChannel5((cameraTimerContent >>> 4 & 1) == 1); cameraTimer.setStorageFlagsForChannel1((cameraTimerContent >>> 7 & 1) == 1); cameraTimer.setStorageFlagsForChannel2((cameraTimerContent >>> 8 & 1) == 1); cameraTimer.setStorageFlagsForChannel3((cameraTimerContent >>> 9 & 1) == 1); cameraTimer.setStorageFlagsForChannel4((cameraTimerContent >>> 10 & 1) == 1); cameraTimer.setStorageFlagsForChannel5((cameraTimerContent >>> 11 & 1) == 1); cameraTimer.setTimeUnit((cameraTimerContent >>> 15 & 1) == 1); cameraTimer.setTimeInterval((int)cameraTimerContent >>> 16); Method methodForCameraTimer = deviceConfig.getClass().getDeclaredMethod("set" + StringUtils.capitalize(field.getName()), JTCameraTimer.class); methodForCameraTimer.invoke(deviceConfig, cameraTimer); continue; case "GnssPositioningMode": JTGnssPositioningMode gnssPositioningMode = new JTGnssPositioningMode(); short gnssPositioningModeContent = buf.readUnsignedByte(); gnssPositioningMode.setGps((gnssPositioningModeContent& 1) == 1); gnssPositioningMode.setBeidou((gnssPositioningModeContent >>> 1 & 1) == 1); gnssPositioningMode.setGlonass((gnssPositioningModeContent >>> 2 & 1) == 1); gnssPositioningMode.setGaLiLeo((gnssPositioningModeContent >>> 3 & 1) == 1); Method methodForGnssPositioningMode = deviceConfig.getClass().getDeclaredMethod("set" + StringUtils.capitalize(field.getName()), JTGnssPositioningMode.class); methodForGnssPositioningMode.invoke(deviceConfig, gnssPositioningMode); continue; case "VideoParam": JTVideoParam videoParam = JTVideoParam.decode(buf); Method methodForVideoParam = deviceConfig.getClass().getDeclaredMethod("set" + StringUtils.capitalize(field.getName()), JTVideoParam.class); methodForVideoParam.invoke(deviceConfig, videoParam); continue; case "ChannelListParam": JTChannelListParam channelListParam = JTChannelListParam.decode(buf); Method methodForChannelListParam = deviceConfig.getClass().getDeclaredMethod("set" + StringUtils.capitalize(field.getName()), JTChannelListParam.class); methodForChannelListParam.invoke(deviceConfig, channelListParam); continue; case "ChannelParam": JTChannelParam channelParam = JTChannelParam.decode(buf); Method methodForChannelParam = deviceConfig.getClass().getDeclaredMethod("set" + StringUtils.capitalize(field.getName()), JTChannelParam.class); methodForChannelParam.invoke(deviceConfig, channelParam); continue; case "AlarmRecordingParam": JTAlarmRecordingParam alarmRecordingParam = JTAlarmRecordingParam.decode(buf); Method methodForAlarmRecordingParam = deviceConfig.getClass().getDeclaredMethod("set" + StringUtils.capitalize(field.getName()), JTAlarmRecordingParam.class); methodForAlarmRecordingParam.invoke(deviceConfig, alarmRecordingParam); continue; case "VideoAlarmBit": JTVideoAlarmBit videoAlarmBit = JTVideoAlarmBit.decode(buf); Method methodForVideoAlarmBit = deviceConfig.getClass().getDeclaredMethod("set" + StringUtils.capitalize(field.getName()), JTVideoAlarmBit.class); methodForVideoAlarmBit.invoke(deviceConfig, videoAlarmBit); continue; case "AnalyzeAlarmParam": JTAnalyzeAlarmParam analyzeAlarmParam = JTAnalyzeAlarmParam.decode(buf); Method methodForAnalyzeAlarmParam = deviceConfig.getClass().getDeclaredMethod("set" + StringUtils.capitalize(field.getName()), JTAnalyzeAlarmParam.class); methodForAnalyzeAlarmParam.invoke(deviceConfig, analyzeAlarmParam); continue; case "AwakenParam": JTAwakenParam awakenParamParam = JTAwakenParam.decode(buf); Method methodForAwakenParam = deviceConfig.getClass().getDeclaredMethod("set" + StringUtils.capitalize(field.getName()), JTAwakenParam.class); methodForAwakenParam.invoke(deviceConfig, awakenParamParam); continue; case "AlarmSign": JTAlarmSign alarmSign = JTAlarmSign.decode(buf); Method methodForAlarmSign = deviceConfig.getClass().getDeclaredMethod("set" + StringUtils.capitalize(field.getName()), JTAlarmSign.class); methodForAlarmSign.invoke(deviceConfig, alarmSign); continue; default: System.err.println(field.getGenericType().getTypeName()); continue; } } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { throw new RuntimeException(e); } } SessionManager.INSTANCE.response(header.getPhoneNumber(), "0104", (long) respNo, deviceConfig); return null; } @Override protected Rs handler(Header header, Session session, Ijt1078Service service) { J8001 j8001 = new J8001(); j8001.setRespNo(header.getSn()); j8001.setRespId(header.getMsgId()); j8001.setResult(J8001.SUCCESS); return j8001; } @Override public ApplicationEvent getEvent() { return null; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0107.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.request; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.bean.*; import com.genersoft.iot.vmp.jt1078.proc.Header; import com.genersoft.iot.vmp.jt1078.proc.response.J8001; import com.genersoft.iot.vmp.jt1078.proc.response.Rs; import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; import com.genersoft.iot.vmp.jt1078.session.Session; import com.genersoft.iot.vmp.jt1078.session.SessionManager; import com.genersoft.iot.vmp.jt1078.util.BCDUtil; import io.netty.buffer.ByteBuf; import lombok.extern.slf4j.Slf4j; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationEvent; import java.nio.charset.Charset; /** * 查询终端属性应答 * */ @Slf4j @MsgId(id = "0107") public class J0107 extends Re { @Override protected Rs decode0(ByteBuf buf, Header header, Session session) { JTDeviceAttribute deviceAttribute = new JTDeviceAttribute(); deviceAttribute.setType(JTDeviceType.getInstance(buf.readUnsignedShort())); deviceAttribute.setMakerId(buf.readCharSequence(5, Charset.forName("GBK")).toString().trim()); deviceAttribute.setDeviceModel(buf.readCharSequence(20, Charset.forName("GBK")).toString().trim()); if (header.is2019Version()) { buf.readCharSequence(10, Charset.forName("GBK")); } deviceAttribute.setTerminalId(buf.readCharSequence(7, Charset.forName("GBK")).toString().trim()); if (header.is2019Version()) { buf.readCharSequence(23, Charset.forName("GBK")); } byte[] bytes = new byte[10]; buf.readBytes(bytes); deviceAttribute.setIccId(BCDUtil.transform(bytes)); int hardwareVersionLength = buf.readUnsignedByte(); deviceAttribute.setHardwareVersion(buf.readCharSequence(hardwareVersionLength, Charset.forName("GBK")).toString().trim()); int firmwareVersionLength = buf.readUnsignedByte(); deviceAttribute.setFirmwareVersion(buf.readCharSequence(firmwareVersionLength, Charset.forName("GBK")).toString().trim()); deviceAttribute.setGnssAttribute(JTGnssAttribute.getInstance(buf.readUnsignedByte())); deviceAttribute.setCommunicationModuleAttribute(JTCommunicationModuleAttribute.getInstance(buf.readUnsignedByte())); log.info("[查询终端属性应答] {}, {}", header.getPhoneNumber(), deviceAttribute); SessionManager.INSTANCE.response(header.getPhoneNumber(), "0107", null, deviceAttribute); return null; } @Override protected Rs handler(Header header, Session session, Ijt1078Service service) { J8001 j8001 = new J8001(); j8001.setRespNo(header.getSn()); j8001.setRespId(header.getMsgId()); j8001.setResult(J8001.SUCCESS); return j8001; } @Override public ApplicationEvent getEvent() { return null; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0200.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.request; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.bean.*; import com.genersoft.iot.vmp.jt1078.proc.Header; import com.genersoft.iot.vmp.jt1078.proc.response.J8001; import com.genersoft.iot.vmp.jt1078.proc.response.Rs; import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; import com.genersoft.iot.vmp.jt1078.session.Session; import io.netty.buffer.ByteBuf; import lombok.extern.slf4j.Slf4j; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationEvent; /** * 位置信息汇报 * */ @Slf4j @MsgId(id = "0200") public class J0200 extends Re { private JTPositionBaseInfo positionInfo; @Override protected Rs decode0(ByteBuf buf, Header header, Session session) { positionInfo = JTPositionBaseInfo.decode(buf); log.debug("[JT-位置汇报]: phoneNumber={} {}", header.getPhoneNumber(), positionInfo.toSimpleString()); // 读取附加信息 // JTPositionAdditionalInfo positionAdditionalInfo = new JTPositionAdditionalInfo(); // Map additionalMsg = new HashMap<>(); // getAdditionalMsg(buf, positionAdditionalInfo); // log.info("[JT-位置汇报]: phoneNumber={} {}", header.getPhoneNumber(), positionInfo.toSimpleString()); return null; } private void getAdditionalMsg(ByteBuf buf, JTPositionAdditionalInfo additionalInfo) { if (buf.isReadable()) { int msgId = buf.readUnsignedByte(); int length = buf.readUnsignedByte(); ByteBuf byteBuf = buf.readBytes(length); switch (msgId) { case 1: // 里程 long mileage = byteBuf.readUnsignedInt(); log.info("[JT-位置汇报]: 里程: {} km", (double)mileage/10); break; case 2: // 油量 int oil = byteBuf.readUnsignedShort(); log.info("[JT-位置汇报]: 油量: {} L", (double)oil/10); break; case 3: // 速度 int speed = byteBuf.readUnsignedShort(); log.info("[JT-位置汇报]: 速度: {} km/h", (double)speed/10); break; case 4: // 需要人工确认报警事件的 ID int alarmId = byteBuf.readUnsignedShort(); log.info("[JT-位置汇报]: 需要人工确认报警事件的 ID: {}", alarmId); break; case 5: byte[] tirePressureBytes = new byte[30]; // 胎压 byteBuf.readBytes(tirePressureBytes); log.info("[JT-位置汇报]: 胎压 {}", tirePressureBytes); break; case 6: // 车厢温度 short carriageTemperature = byteBuf.readShort(); log.info("[JT-位置汇报]: 车厢温度 {}摄氏度", carriageTemperature); break; case 11: // 超速报警 short positionType = byteBuf.readUnsignedByte(); long positionId = byteBuf.readUnsignedInt(); log.info("[JT-位置汇报]: 超速报警, 位置类型: {}, 区域或路段 ID: {}", positionType, positionId); break; default: log.info("[JT-位置汇报]: 附加消息ID: {}, 消息长度: {}", msgId, length); break; } getAdditionalMsg(buf, additionalInfo); } } @Override protected Rs handler(Header header, Session session, Ijt1078Service service) { J8001 j8001 = new J8001(); j8001.setRespNo(header.getSn()); j8001.setRespId(header.getMsgId()); j8001.setResult(J8001.SUCCESS); service.updateDevicePosition(header.getPhoneNumber(), positionInfo.getLongitude(), positionInfo.getLatitude()); return j8001; } @Override public ApplicationEvent getEvent() { return null; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0201.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.request; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.bean.*; import com.genersoft.iot.vmp.jt1078.event.DeviceUpdateEvent; import com.genersoft.iot.vmp.jt1078.event.JTPositionEvent; import com.genersoft.iot.vmp.jt1078.proc.Header; import com.genersoft.iot.vmp.jt1078.proc.response.J8001; import com.genersoft.iot.vmp.jt1078.proc.response.Rs; import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; import com.genersoft.iot.vmp.jt1078.session.Session; import com.genersoft.iot.vmp.jt1078.session.SessionManager; import io.netty.buffer.ByteBuf; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationEvent; /** * 位置信息查询应答 * * @author QingtaiJiang * @date 2023/4/27 18:06 * @email qingtaij@163.com */ @MsgId(id = "0201") public class J0201 extends Re { private final static Logger log = LoggerFactory.getLogger(J0100.class); private JTPositionBaseInfo positionInfo; private String phoneNumber; @Override protected Rs decode0(ByteBuf buf, Header header, Session session) { phoneNumber = header.getPhoneNumber(); int respNo = buf.readUnsignedShort(); positionInfo = JTPositionBaseInfo.decode(buf); log.info("[JT-位置信息查询应答]: {}", positionInfo); SessionManager.INSTANCE.response(header.getPhoneNumber(), "0201", (long) respNo, positionInfo); return null; } @Override protected Rs handler(Header header, Session session, Ijt1078Service service) { J8001 j8001 = new J8001(); j8001.setRespNo(header.getSn()); j8001.setRespId(header.getMsgId()); j8001.setResult(J8001.SUCCESS); return j8001; } @Override public ApplicationEvent getEvent() { if (positionInfo == null || phoneNumber == null ) { return null; } JTPositionEvent registerEvent = new JTPositionEvent(this); registerEvent.setPhoneNumber(phoneNumber); registerEvent.setPositionInfo(positionInfo); return registerEvent; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0500.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.request; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.bean.*; import com.genersoft.iot.vmp.jt1078.event.JTPositionEvent; import com.genersoft.iot.vmp.jt1078.proc.Header; import com.genersoft.iot.vmp.jt1078.proc.response.J8001; import com.genersoft.iot.vmp.jt1078.proc.response.Rs; import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; import com.genersoft.iot.vmp.jt1078.session.Session; import com.genersoft.iot.vmp.jt1078.session.SessionManager; import io.netty.buffer.ByteBuf; import lombok.extern.slf4j.Slf4j; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationEvent; /** * 车辆控制应答 * */ @Slf4j @MsgId(id = "0500") public class J0500 extends Re { private JTPositionBaseInfo positionInfo; private String phoneNumber; @Override protected Rs decode0(ByteBuf buf, Header header, Session session) { phoneNumber = header.getPhoneNumber(); int respNo = buf.readUnsignedShort(); positionInfo = JTPositionBaseInfo.decode(buf); log.info("[车辆控制应答] {}", header.getPhoneNumber()); SessionManager.INSTANCE.response(header.getPhoneNumber(), "0500", (long) respNo, positionInfo); return null; } @Override protected Rs handler(Header header, Session session, Ijt1078Service service) { J8001 j8001 = new J8001(); j8001.setRespNo(header.getSn()); j8001.setRespId(header.getMsgId()); j8001.setResult(J8001.SUCCESS); return j8001; } @Override public ApplicationEvent getEvent() { if (positionInfo == null || phoneNumber == null ) { return null; } JTPositionEvent registerEvent = new JTPositionEvent(this); registerEvent.setPhoneNumber(phoneNumber); registerEvent.setPositionInfo(positionInfo); return registerEvent; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0608.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.request; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.bean.*; import com.genersoft.iot.vmp.jt1078.proc.Header; import com.genersoft.iot.vmp.jt1078.proc.response.J8001; import com.genersoft.iot.vmp.jt1078.proc.response.Rs; import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; import com.genersoft.iot.vmp.jt1078.session.Session; import com.genersoft.iot.vmp.jt1078.session.SessionManager; import io.netty.buffer.ByteBuf; import lombok.extern.slf4j.Slf4j; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationEvent; import java.util.ArrayList; import java.util.List; /** * 查询区域或线路数据应答 */ @Slf4j @MsgId(id = "0608") public class J0608 extends Re { @Override protected Rs decode0(ByteBuf buf, Header header, Session session) { int type = buf.readByte(); long dataLength = buf.readUnsignedInt(); log.info("[JT-查询区域或线路数据应答]: 类型: {}, 数量: {}", type, dataLength); List areaOrRoutes = new ArrayList<>(); if (dataLength == 0) { SessionManager.INSTANCE.response(header.getPhoneNumber(), "0608", null, areaOrRoutes); return null; } switch (type) { case 1: buf.readUnsignedByte(); int areaLengthForCircleArea = buf.readUnsignedByte(); List jtCircleAreas = new ArrayList<>(); for (int i = 0; i < areaLengthForCircleArea; i++) { // 查询圆形区域数据 JTCircleArea jtCircleArea = JTCircleArea.decode(buf); jtCircleAreas.add(jtCircleArea); } SessionManager.INSTANCE.response(header.getPhoneNumber(), "0608", null, jtCircleAreas); break; case 2: buf.readUnsignedByte(); int areaLengthForRectangleArea = buf.readUnsignedByte(); // 查询矩形区域数据 List jtRectangleAreas = new ArrayList<>(); for (int i = 0; i < areaLengthForRectangleArea; i++) { // 查询圆形区域数据 JTRectangleArea jtRectangleArea = JTRectangleArea.decode(buf); jtRectangleAreas.add(jtRectangleArea); } SessionManager.INSTANCE.response(header.getPhoneNumber(), "0608", null, jtRectangleAreas); break; case 3: // 查询多 边形区域数据 List jtPolygonAreas = new ArrayList<>(); for (int i = 0; i < dataLength; i++) { // 查询圆形区域数据 JTPolygonArea jtRectangleArea = JTPolygonArea.decode(buf); jtPolygonAreas.add(jtRectangleArea); } SessionManager.INSTANCE.response(header.getPhoneNumber(), "0608", null, jtPolygonAreas); break; case 4: // 查询线路数据 List jtRoutes = new ArrayList<>(); for (int i = 0; i < dataLength; i++) { // 查询圆形区域数据 JTRoute jtRoute = JTRoute.decode(buf); jtRoutes.add(jtRoute); } SessionManager.INSTANCE.response(header.getPhoneNumber(), "0608", null, jtRoutes); break; default: break; } return null; } @Override protected Rs handler(Header header, Session session, Ijt1078Service service) { J8001 j8001 = new J8001(); j8001.setRespNo(header.getSn()); j8001.setRespId(header.getMsgId()); return j8001; } @Override public ApplicationEvent getEvent() { return null; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0702.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.request; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.bean.JTDriverInformation; import com.genersoft.iot.vmp.jt1078.proc.Header; import com.genersoft.iot.vmp.jt1078.proc.response.J8001; import com.genersoft.iot.vmp.jt1078.proc.response.Rs; import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; import com.genersoft.iot.vmp.jt1078.session.Session; import com.genersoft.iot.vmp.jt1078.session.SessionManager; import io.netty.buffer.ByteBuf; import lombok.extern.slf4j.Slf4j; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationEvent; /** * 驾驶员身份信息采集上报 * */ @Slf4j @MsgId(id = "0702") public class J0702 extends Re { @Override protected Rs decode0(ByteBuf buf, Header header, Session session) { JTDriverInformation driverInformation = JTDriverInformation.decode(buf, header.is2019Version()); log.info("[JT-驾驶员身份信息采集上报]: {}", driverInformation.toString()); SessionManager.INSTANCE.response(header.getPhoneNumber(), "0702", null, driverInformation); return null; } @Override protected Rs handler(Header header, Session session, Ijt1078Service service) { J8001 j8001 = new J8001(); j8001.setRespNo(header.getSn()); j8001.setRespId(header.getMsgId()); return j8001; } @Override public ApplicationEvent getEvent() { return null; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0704.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.request; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.bean.JTDriverInformation; import com.genersoft.iot.vmp.jt1078.bean.JTPositionBaseInfo; import com.genersoft.iot.vmp.jt1078.proc.Header; import com.genersoft.iot.vmp.jt1078.proc.response.J8001; import com.genersoft.iot.vmp.jt1078.proc.response.Rs; import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; import com.genersoft.iot.vmp.jt1078.session.Session; import com.genersoft.iot.vmp.jt1078.session.SessionManager; import io.netty.buffer.ByteBuf; import lombok.extern.slf4j.Slf4j; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationEvent; import java.util.ArrayList; import java.util.List; /** * 定位数据批量上传 */ @Slf4j @MsgId(id = "0704") public class J0704 extends Re { private final List positionBaseInfoList = new ArrayList<>(); @Override protected Rs decode0(ByteBuf buf, Header header, Session session) { int length = buf.readUnsignedShort(); int type = buf.readUnsignedByte(); for (int i = 0; i < length; i++) { int dateLength = buf.readUnsignedShort(); ByteBuf byteBuf = buf.readBytes(dateLength); JTPositionBaseInfo positionInfo = JTPositionBaseInfo.decode(byteBuf); byteBuf.release(); positionBaseInfoList.add(positionInfo); } log.info("[JT-定位数据批量上传]: 共{}条", positionBaseInfoList.size()); return null; } @Override protected Rs handler(Header header, Session session, Ijt1078Service service) { J8001 j8001 = new J8001(); j8001.setRespNo(header.getSn()); j8001.setRespId(header.getMsgId()); return j8001; } @Override public ApplicationEvent getEvent() { return null; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0800.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.request; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.bean.JTMediaEventInfo; import com.genersoft.iot.vmp.jt1078.bean.JTPositionBaseInfo; import com.genersoft.iot.vmp.jt1078.proc.Header; import com.genersoft.iot.vmp.jt1078.proc.response.J8001; import com.genersoft.iot.vmp.jt1078.proc.response.Rs; import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; import com.genersoft.iot.vmp.jt1078.session.Session; import io.netty.buffer.ByteBuf; import lombok.extern.slf4j.Slf4j; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationEvent; import java.util.ArrayList; import java.util.List; /** * 多媒体事件信息上传 * */ @Slf4j @MsgId(id = "0800") public class J0800 extends Re { @Override protected Rs decode0(ByteBuf buf, Header header, Session session) { JTMediaEventInfo mediaEventInfo = JTMediaEventInfo.decode(buf); log.info("[JT-多媒体事件信息上传]: {}", mediaEventInfo); return null; } @Override protected Rs handler(Header header, Session session, Ijt1078Service service) { J8001 j8001 = new J8001(); j8001.setRespNo(header.getSn()); j8001.setRespId(header.getMsgId()); return j8001; } @Override public ApplicationEvent getEvent() { return null; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0801.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.request; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.bean.JTMediaEventInfo; import com.genersoft.iot.vmp.jt1078.proc.Header; import com.genersoft.iot.vmp.jt1078.proc.response.J8001; import com.genersoft.iot.vmp.jt1078.proc.response.Rs; import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; import com.genersoft.iot.vmp.jt1078.session.Session; import com.genersoft.iot.vmp.jt1078.session.SessionManager; import io.netty.buffer.ByteBuf; import lombok.extern.slf4j.Slf4j; import org.springframework.context.ApplicationEvent; /** * 多媒体数据上传 */ @Slf4j @MsgId(id = "0801") public class J0801 extends Re { @Override protected Rs decode0(ByteBuf buf, Header header, Session session) { JTMediaEventInfo mediaEventInfo = JTMediaEventInfo.decode(buf); log.info("[JT-多媒体数据上传]: {}", mediaEventInfo); // try { // if (mediaEventInfo.getMediaData() != null) { // File file = new File("./source.jpg"); // if (file.exists()) { // file.delete(); // } // FileOutputStream fileOutputStream = new FileOutputStream(file); // fileOutputStream.write(mediaEventInfo.getMediaData()); // fileOutputStream.flush(); // fileOutputStream.close(); // } // }catch (Exception e) { // log.error("[JT-多媒体数据上传] 写入文件异常", e); // } SessionManager.INSTANCE.response(header.getPhoneNumber(), "0801", null, mediaEventInfo); return null; } @Override protected Rs handler(Header header, Session session, Ijt1078Service service) { J8001 j8001 = new J8001(); j8001.setRespNo(header.getSn()); j8001.setRespId(header.getMsgId()); return j8001; } @Override public ApplicationEvent getEvent() { return null; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0802.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.request; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.bean.JTMediaDataInfo; import com.genersoft.iot.vmp.jt1078.proc.Header; import com.genersoft.iot.vmp.jt1078.proc.response.J8001; import com.genersoft.iot.vmp.jt1078.proc.response.Rs; import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; import com.genersoft.iot.vmp.jt1078.session.Session; import com.genersoft.iot.vmp.jt1078.session.SessionManager; import io.netty.buffer.ByteBuf; import lombok.extern.slf4j.Slf4j; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationEvent; import java.util.ArrayList; import java.util.List; /** * 存储多媒体数据检索应答 * */ @Slf4j @MsgId(id = "0802") public class J0802 extends Re { @Override protected Rs decode0(ByteBuf buf, Header header, Session session) { int respNo = buf.readUnsignedShort(); int length = buf.readUnsignedShort(); if (length == 0) { log.info("[JT-存储多媒体数据检索应答]: {}", length); SessionManager.INSTANCE.response(header.getPhoneNumber(), "0802", (long) respNo, new ArrayList<>()); return null; } List mediaDataInfoList = new ArrayList<>(length); for (int i = 0; i < length; i++) { mediaDataInfoList.add(JTMediaDataInfo.decode(buf)); } log.info("[JT-存储多媒体数据检索应答]: {}", mediaDataInfoList.size()); SessionManager.INSTANCE.response(header.getPhoneNumber(), "0802", (long) respNo, mediaDataInfoList); return null; } @Override protected Rs handler(Header header, Session session, Ijt1078Service service) { J8001 j8001 = new J8001(); j8001.setRespNo(header.getSn()); j8001.setRespId(header.getMsgId()); return j8001; } @Override public ApplicationEvent getEvent() { return null; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0805.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.request; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.proc.Header; import com.genersoft.iot.vmp.jt1078.proc.response.Rs; import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; import com.genersoft.iot.vmp.jt1078.session.Session; import com.genersoft.iot.vmp.jt1078.session.SessionManager; import io.netty.buffer.ByteBuf; import lombok.Getter; import lombok.Setter; import org.springframework.context.ApplicationEvent; import java.util.ArrayList; import java.util.List; /** * 摄像头立即拍摄命令应答 */ @Setter @Getter @MsgId(id = "0805") public class J0805 extends Re { private int respNo; /** * 0:成功/确认;1:失败;2:消息有误;3:不支持 */ private int result; /** * 表示拍摄成功的多媒体个数 */ private List ids = new ArrayList<>(); @Override protected Rs decode0(ByteBuf buf, Header header, Session session) { respNo = buf.readUnsignedShort(); result = buf.readUnsignedByte(); if (result == 0) { int length = buf.readUnsignedShort(); for (int i = 0; i < length; i++) { ids.add(buf.readUnsignedInt()); } } SessionManager.INSTANCE.response(header.getPhoneNumber(), "0805", null, ids); return null; } @Override protected Rs handler(Header header, Session session, Ijt1078Service service) { SessionManager.INSTANCE.response(header.getPhoneNumber(), "0001", (long) respNo, result); return null; } @Override public ApplicationEvent getEvent() { return null; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0900.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.request; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.proc.Header; import com.genersoft.iot.vmp.jt1078.proc.response.J8001; import com.genersoft.iot.vmp.jt1078.proc.response.Rs; import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; import com.genersoft.iot.vmp.jt1078.session.Session; import com.genersoft.iot.vmp.jt1078.session.SessionManager; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.Getter; import lombok.Setter; import org.springframework.context.ApplicationEvent; /** * 数据上行透传 */ @Setter @Getter @MsgId(id = "0900") public class J0900 extends Re { /** * 透传消息类型, 0x00: GNSS 模块详细定位数据, 0X0B: 道路运输证 IC卡信息, 0X41: 串口1 透传, 0X42: 串口2 透传, 0XF0 ~ 0XFF: 用户自定义透传 */ private Integer type; /** * 透传消息内容 */ private byte[] content; @Override protected Rs decode0(ByteBuf buf, Header header, Session session) { type = (int)buf.readUnsignedByte(); byte[] content = new byte[buf.readableBytes()]; buf.readBytes(content); return null; } @Override protected Rs handler(Header header, Session session, Ijt1078Service service) { J8001 j8001 = new J8001(); j8001.setRespNo(header.getSn()); j8001.setRespId(header.getMsgId()); return null; } @Override public ApplicationEvent getEvent() { return null; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0901.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.request; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.proc.Header; import com.genersoft.iot.vmp.jt1078.proc.response.J8001; import com.genersoft.iot.vmp.jt1078.proc.response.Rs; import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; import com.genersoft.iot.vmp.jt1078.session.Session; import io.netty.buffer.ByteBuf; import lombok.Getter; import lombok.Setter; import org.springframework.context.ApplicationEvent; /** * 数据压缩上报 */ @Setter @Getter @MsgId(id = "0901") public class J0901 extends Re { /** * 平台 RSA公钥{e ,n}中的 e */ private Long e; /** * RSA公钥{e ,n}中的 n */ private byte[] n; @Override protected Rs decode0(ByteBuf buf, Header header, Session session) { e = buf.readUnsignedInt(); byte[] content = new byte[buf.readableBytes()]; buf.readBytes(content); return null; } @Override protected Rs handler(Header header, Session session, Ijt1078Service service) { J8001 j8001 = new J8001(); j8001.setRespNo(header.getSn()); j8001.setRespId(header.getMsgId()); return j8001; } @Override public ApplicationEvent getEvent() { return null; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0A00.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.request; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.proc.Header; import com.genersoft.iot.vmp.jt1078.proc.response.J8001; import com.genersoft.iot.vmp.jt1078.proc.response.Rs; import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; import com.genersoft.iot.vmp.jt1078.session.Session; import io.netty.buffer.ByteBuf; import lombok.Getter; import lombok.Setter; import org.springframework.context.ApplicationEvent; /** * 终端 RSA公钥 */ @Setter @Getter @MsgId(id = "0900") public class J0A00 extends Re { /** * 透传消息类型, 0x00: GNSS 模块详细定位数据, 0X0B: 道路运输证 IC卡信息, 0X41: 串口1 透传, 0X42: 串口2 透传, 0XF0 ~ 0XFF: 用户自定义透传 */ private Integer type; /** * 透传消息内容 */ private byte[] content; @Override protected Rs decode0(ByteBuf buf, Header header, Session session) { type = (int)buf.readUnsignedByte(); byte[] content = new byte[buf.readableBytes()]; buf.readBytes(content); return null; } @Override protected Rs handler(Header header, Session session, Ijt1078Service service) { J8001 j8001 = new J8001(); j8001.setRespNo(header.getSn()); j8001.setRespId(header.getMsgId()); return null; } @Override public ApplicationEvent getEvent() { return null; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J1003.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.request; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.bean.JTMediaAttribute; import com.genersoft.iot.vmp.jt1078.proc.Header; import com.genersoft.iot.vmp.jt1078.proc.response.J8001; import com.genersoft.iot.vmp.jt1078.proc.response.Rs; import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; import com.genersoft.iot.vmp.jt1078.session.Session; import com.genersoft.iot.vmp.jt1078.session.SessionManager; import io.netty.buffer.ByteBuf; import org.springframework.context.ApplicationEvent; /** * 终端上传音视频属性 * */ @MsgId(id = "1003") public class J1003 extends Re { JTMediaAttribute mediaAttribute; @Override protected Rs decode0(ByteBuf buf, Header header, Session session) { mediaAttribute = JTMediaAttribute.decode(buf); SessionManager.INSTANCE.response(header.getPhoneNumber(), "1003", null, mediaAttribute); return null; } @Override protected Rs handler(Header header, Session session, Ijt1078Service service) { J8001 j8001 = new J8001(); j8001.setRespNo(header.getSn()); j8001.setRespId(header.getMsgId()); j8001.setResult(J8001.SUCCESS); return j8001; } @Override public ApplicationEvent getEvent() { return null; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J1005.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.request; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.bean.JTMediaAttribute; import com.genersoft.iot.vmp.jt1078.bean.JTPassengerNum; import com.genersoft.iot.vmp.jt1078.proc.Header; import com.genersoft.iot.vmp.jt1078.proc.response.J8001; import com.genersoft.iot.vmp.jt1078.proc.response.Rs; import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; import com.genersoft.iot.vmp.jt1078.session.Session; import com.genersoft.iot.vmp.jt1078.session.SessionManager; import io.netty.buffer.ByteBuf; import lombok.extern.slf4j.Slf4j; import org.springframework.context.ApplicationEvent; /** * 终端上传乘客流量 * */ @Slf4j @MsgId(id = "1005") public class J1005 extends Re { JTPassengerNum passengerNum; @Override protected Rs decode0(ByteBuf buf, Header header, Session session) { passengerNum = JTPassengerNum.decode(buf); log.info("[终端上传乘客流量] {}", passengerNum); return null; } @Override protected Rs handler(Header header, Session session, Ijt1078Service service) { J8001 j8001 = new J8001(); j8001.setRespNo(header.getSn()); j8001.setRespId(header.getMsgId()); j8001.setResult(J8001.SUCCESS); return j8001; } @Override public ApplicationEvent getEvent() { return null; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J1205.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.request; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.proc.Header; import com.genersoft.iot.vmp.jt1078.proc.response.J8001; import com.genersoft.iot.vmp.jt1078.proc.response.Rs; import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; import com.genersoft.iot.vmp.jt1078.session.Session; import com.genersoft.iot.vmp.jt1078.session.SessionManager; import com.genersoft.iot.vmp.utils.DateUtil; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import lombok.Getter; import lombok.Setter; import org.springframework.context.ApplicationEvent; import java.util.ArrayList; import java.util.List; /** * 终端上传音视频资源列表 * * @author QingtaiJiang * @date 2023/4/28 10:36 * @email qingtaij@163.com */ @Setter @Getter @MsgId(id = "1205") public class J1205 extends Re { Integer respNo; private List recordList = new ArrayList<>(); @Override protected Rs decode0(ByteBuf buf, Header header, Session session) { respNo = buf.readUnsignedShort(); long size = buf.readUnsignedInt(); for (int i = 0; i < size; i++) { JRecordItem item = new JRecordItem(); item.setChannelId(buf.readUnsignedByte()); String startTime = ByteBufUtil.hexDump(buf.readSlice(6)); item.setStartTime(DateUtil.jt1078Toyyyy_MM_dd_HH_mm_ss(startTime)); String endTime = ByteBufUtil.hexDump(buf.readSlice(6)); item.setEndTime(DateUtil.jt1078Toyyyy_MM_dd_HH_mm_ss(endTime)); item.setAlarmSign(buf.readLong()); item.setMediaType(buf.readUnsignedByte()); item.setStreamType(buf.readUnsignedByte()); item.setStorageType(buf.readUnsignedByte()); item.setSize(buf.readUnsignedInt()); recordList.add(item); } return null; } @Override protected Rs handler(Header header, Session session, Ijt1078Service service) { SessionManager.INSTANCE.response(header.getPhoneNumber(), "1205", (long) respNo, recordList); J8001 j8001 = new J8001(); j8001.setRespNo(header.getSn()); j8001.setRespId(header.getMsgId()); j8001.setResult(J8001.SUCCESS); return j8001; } @Setter @Getter public static class JRecordItem { // 逻辑通道号 private int channelId; // 开始时间 private String startTime; // 结束时间 private String endTime; // 报警标志 private long alarmSign; // 音视频资源类型 private int mediaType; // 码流类型 private int streamType = 1; // 存储器类型 private int storageType; // 文件大小 private long size; @Override public String toString() { return "JRecordItem{" + "channelId=" + channelId + ", startTime='" + startTime + '\'' + ", endTime='" + endTime + '\'' + ", warn=" + alarmSign + ", mediaType=" + mediaType + ", streamType=" + streamType + ", storageType=" + storageType + ", size=" + size + '}'; } } @Override public String toString() { return "J1205{" + "respNo=" + respNo + ", recordList=" + recordList + '}'; } @Override public ApplicationEvent getEvent() { return null; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J1206.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.request; import com.alibaba.fastjson2.JSON; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.proc.Header; import com.genersoft.iot.vmp.jt1078.proc.response.J8001; import com.genersoft.iot.vmp.jt1078.proc.response.Rs; import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; import com.genersoft.iot.vmp.jt1078.session.Session; import com.genersoft.iot.vmp.jt1078.session.SessionManager; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import lombok.Getter; import lombok.Setter; import org.springframework.context.ApplicationEvent; import java.util.ArrayList; import java.util.List; /** * 文件上传完成通知 * */ @Setter @Getter @MsgId(id = "1206") public class J1206 extends Re { Integer respNo; // 0:成功; 1:失败 private int result; @Override protected Rs decode0(ByteBuf buf, Header header, Session session) { respNo = buf.readUnsignedShort(); result = buf.readUnsignedByte(); return null; } @Override protected Rs handler(Header header, Session session, Ijt1078Service service) { J8001 j8001 = new J8001(); j8001.setRespNo(header.getSn()); j8001.setRespId(header.getMsgId()); j8001.setResult(J8001.SUCCESS); return j8001; } @Override public String toString() { return "J1206{" + "respNo=" + respNo + ", result=" + result + '}'; } @Override public ApplicationEvent getEvent() { return null; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/Re.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.request; import com.genersoft.iot.vmp.jt1078.proc.Header; import com.genersoft.iot.vmp.jt1078.proc.response.Rs; import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; import com.genersoft.iot.vmp.jt1078.session.Session; import io.netty.buffer.ByteBuf; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationEvent; import lombok.extern.slf4j.Slf4j; import org.springframework.util.StringUtils; /** * @author QingtaiJiang * @date 2023/4/27 18:50 * @email qingtaij@163.com */ @Slf4j public abstract class Re { protected abstract Rs decode0(ByteBuf buf, Header header, Session session); protected abstract Rs handler(Header header, Session session, Ijt1078Service service); public Rs decode(ByteBuf buf, Header header, Session session, Ijt1078Service service) { if (session != null && !StringUtils.hasLength(session.getPhoneNumber())) { session.register(header.getPhoneNumber(), (int) header.getVersion(), header); } Rs rs = decode0(buf, header, session); buf.release(); Rs rsHand = handler(header, session, service); if (rs == null && rsHand != null) { rs = rsHand; } else if (rs != null && rsHand != null) { log.warn("decode0:{} 与 handler:{} 返回值冲突,采用decode0返回值", rs, rsHand); } if (rs != null) { rs.setHeader(header); } return rs; } public abstract ApplicationEvent getEvent(); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8001.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import lombok.Setter; /** * @author QingtaiJiang * @date 2023/4/27 18:48 * @email qingtaij@163.com */ @Setter @MsgId(id = "8001") public class J8001 extends Rs { public static final Integer SUCCESS = 0; public static final Integer FAIL = 1; public static final Integer ERROR = 2; public static final Integer NOT_SUPPORTED = 3; public static final Integer ALARM_ACK = 3; Integer respNo; String respId; Integer result; @Override public ByteBuf encode() { ByteBuf buffer = Unpooled.buffer(); buffer.writeShort(respNo); buffer.writeBytes(ByteBufUtil.decodeHexDump(respId)); buffer.writeByte(result); return buffer; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8100.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.util.CharsetUtil; import lombok.Setter; /** * @author QingtaiJiang * @date 2023/4/27 18:40 * @email qingtaij@163.com */ @Setter @MsgId(id = "8100") public class J8100 extends Rs { /** * 0 成功 * 1 车辆已被注册 * 2 数据库中无该车辆 * 3 终端已被注册 * 4 数据库中无该终端 */ public static final Integer SUCCESS = 0; public static final Integer FAIL = 4; Integer respNo; Integer result; String code; @Override public ByteBuf encode() { ByteBuf buffer = Unpooled.buffer(); buffer.writeShort(respNo); buffer.writeByte(result); buffer.writeCharSequence(code, CharsetUtil.UTF_8); return buffer; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8103.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.bean.JTDeviceConfig; import com.genersoft.iot.vmp.jt1078.bean.common.ConfigAttribute; import com.genersoft.iot.vmp.jt1078.bean.config.*; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import lombok.Getter; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.util.Arrays; import java.util.HashMap; import java.util.Map; /** * 设置终端参数 */ @Getter @MsgId(id = "8103") public class J8103 extends Rs { private final static Logger log = LoggerFactory.getLogger(J8103.class); private JTDeviceConfig config; @Override public ByteBuf encode() { ByteBuf buffer = Unpooled.buffer(); Class configClass = config.getClass(); Field[] declaredFields = configClass.getDeclaredFields(); Map fieldConfigAttributeMap = new HashMap<>(); for (Field field : declaredFields) { try{ Method method = configClass.getDeclaredMethod("get" + StringUtils.capitalize(field.getName())); Object invoke = method.invoke(config); if (invoke == null) { continue; } ConfigAttribute configAttribute = field.getAnnotation(ConfigAttribute.class); if (configAttribute != null) { fieldConfigAttributeMap.put(field, configAttribute); } }catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException e) { log.error("[设置终端参数 ] 编码失败", e ); } } buffer.writeByte(fieldConfigAttributeMap.size()); if (!fieldConfigAttributeMap.isEmpty()) { for (Field field : fieldConfigAttributeMap.keySet()) { try{ ConfigAttribute configAttribute = fieldConfigAttributeMap.get(field); buffer.writeInt((int) (configAttribute.id() & 0xffff)); switch (configAttribute.type()) { case "Long": buffer.writeByte(4); field.setAccessible(true); long longVal = (long)field.get(config); buffer.writeInt((int) (longVal & 0xffffffffL)); continue; case "String": field.setAccessible(true); String stringVal = (String)field.get(config); buffer.writeByte(stringVal.getBytes(Charset.forName("GBK")).length); buffer.writeCharSequence(stringVal, Charset.forName("GBK")); continue; case "Integer": buffer.writeByte(2); field.setAccessible(true); Integer integerVal = (Integer)field.get(config); buffer.writeShort((short)(integerVal & 0xffff)); continue; case "Short": buffer.writeByte(1); field.setAccessible(true); Short shortVal = (Short)field.get(config); buffer.writeByte((int) (shortVal & 0xff)); continue; case "IllegalDrivingPeriods": case "CollisionAlarmParams": case "CameraTimer": case "GnssPositioningMode": case "VideoParam": case "ChannelListParam": case "ChannelParam": case "AlarmRecordingParam": case "AlarmShielding": case "VideoAlarmBit": case "AnalyzeAlarmParam": case "AwakenParam": case "AlarmSign": field.setAccessible(true); JTDeviceSubConfig subConfig = (JTDeviceSubConfig)field.get(config); ByteBuf byteBuf = subConfig.encode(); buffer.writeByte(byteBuf.readableBytes()); buffer.writeBytes(byteBuf); continue; } }catch (Exception e) { log.error("[设置终端参数 ] 编码失败", e ); } } } return buffer; } public void setConfig(JTDeviceConfig config) { this.config = config; } @Override public String toString() { return "J8103{" + "config=" + config + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8104.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import java.util.Arrays; /** * 查询终端参数 */ @MsgId(id = "8104") public class J8104 extends Rs { @Override public ByteBuf encode() { return Unpooled.buffer(); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8105.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.bean.JTDeviceConnectionControl; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.Getter; import java.nio.charset.Charset; import java.util.Arrays; /** * 终端控制 */ @Getter @MsgId(id = "8105") public class J8105 extends Rs { private JTDeviceConnectionControl connectionControl; /** * 终端复位 */ private Boolean reset; /** * 终端恢复出厂设置 */ private Boolean factoryReset; @Override public ByteBuf encode() { ByteBuf byteBuf = Unpooled.buffer(); if (reset != null) { byteBuf.writeByte(4); }else if (factoryReset != null) { byteBuf.writeByte(5); }else if (connectionControl != null) { byteBuf.writeByte(2); StringBuffer stringBuffer = new StringBuffer(); if (connectionControl.getSwitchOn() != null) { if (connectionControl.getSwitchOn()) { stringBuffer.append("1"); }else { stringBuffer.append("0"); stringBuffer.append(";" + connectionControl.getAuthentication()) .append(";" + connectionControl.getName()) .append(";" + connectionControl.getUsername()) .append(";" + connectionControl.getPassword()) .append(";" + connectionControl.getAddress()) .append(";" + connectionControl.getTcpPort()) .append(";" + connectionControl.getUdpPort()) .append(";" + connectionControl.getTimeLimit()); } } byteBuf.writeCharSequence(stringBuffer.toString(), Charset.forName("GBK")); } return byteBuf; } public void setConnectionControl(JTDeviceConnectionControl connectionControl) { this.connectionControl = connectionControl; } public void setReset(Boolean reset) { this.reset = reset; } public void setFactoryReset(Boolean factoryReset) { this.factoryReset = factoryReset; } @Override public String toString() { return "J8105{" + "connectionControl=" + connectionControl + ", reset=" + reset + ", factoryReset=" + factoryReset + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8106.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.util.CharsetUtil; import lombok.Getter; import java.util.Arrays; /** * 查询指定终端参数 */ @Getter @MsgId(id = "8106") public class J8106 extends Rs { private long[] params; @Override public ByteBuf encode() { ByteBuf buffer = Unpooled.buffer(); buffer.writeByte(params.length); for (long param : params) { buffer.writeInt((int) param); } return buffer; } public void setParams(long[] params) { this.params = params; } @Override public String toString() { return "J8106{" + "params=" + Arrays.toString(params) + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8107.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; /** * 查询终端属性 */ @MsgId(id = "8107") public class J8107 extends Rs { @Override public ByteBuf encode() { return Unpooled.buffer(); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8201.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; /** * 位置信息查询 */ @MsgId(id = "8201") public class J8201 extends Rs { @Override public ByteBuf encode() { return Unpooled.buffer(); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8202.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.Getter; import lombok.Setter; /** * 临时位置跟踪控制 */ @Setter @Getter @MsgId(id = "8202") public class J8202 extends Rs { /** * 时间间隔,单位为秒,时间间隔为0 时停止跟踪,停止跟踪无需带后继字段 */ private int timeInterval; /** * 位置跟踪有效期, 单位为秒,终端在接收到位置跟踪控制消息后,在有效期截止时间之前依据消息中的时间间隔发送位置汇报 */ private long validityPeriod; @Override public ByteBuf encode() { ByteBuf buffer = Unpooled.buffer(); buffer.writeShort((short)(timeInterval & 0xffff)); if (timeInterval > 0) { buffer.writeInt((int) (validityPeriod & 0xffffffffL)); } return buffer; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8203.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.bean.JTConfirmationAlarmMessageType; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.Getter; import lombok.Setter; /** * 人工确认报警消息 */ @Setter @Getter @MsgId(id = "8203") public class J8203 extends Rs { /** * 报警消息流水号 */ private int alarmPackageNo; /** * 人工确认报警类型 */ private JTConfirmationAlarmMessageType alarmMessageType; @Override public ByteBuf encode() { ByteBuf buffer = Unpooled.buffer(); buffer.writeShort((short)(alarmPackageNo & 0xffff)); if (alarmMessageType != null) { buffer.writeInt((int) (alarmMessageType.encode() & 0xffffffffL)); } return buffer; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8204.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; /** * 链路检测 */ @MsgId(id = "8204") public class J8204 extends Rs { @Override public ByteBuf encode() { return Unpooled.buffer(); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8300.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.bean.JTTextSign; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.Data; import lombok.EqualsAndHashCode; import java.nio.charset.Charset; /** * 文本信息下发 */ @EqualsAndHashCode(callSuper = true) @Data @MsgId(id = "8300") public class J8300 extends Rs { /** * 标志 */ private JTTextSign sign; /** * 文本类型1 = 通知 ,2 = 服务 */ private int textType; /** * 文本信息 */ private String content; @Override public ByteBuf encode() { ByteBuf buffer = Unpooled.buffer(); buffer.writeByte(sign.encode()); buffer.writeByte(textType); buffer.writeCharSequence(content, Charset.forName("GBK")); return buffer; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8400.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.bean.JTTextSign; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.Getter; import lombok.Setter; import java.nio.charset.Charset; /** * 电话回拨 */ @Setter @Getter @MsgId(id = "8400") public class J8400 extends Rs { /** * 标志, 0'普通通话,1'监听 */ private int sign; /** * 电话号码 */ private String phoneNumber; @Override public ByteBuf encode() { ByteBuf buffer = Unpooled.buffer(); buffer.writeByte(sign); buffer.writeCharSequence(phoneNumber, Charset.forName("GBK")); return buffer; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8401.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.bean.JTPhoneBookContact; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.Getter; import lombok.Setter; import java.nio.charset.Charset; import java.util.List; /** * 设置电话本 */ @Setter @Getter @MsgId(id = "8401") public class J8401 extends Rs { /** * 设置类型: * 0: 删除终端上所有存储的联系人, * 1: 表示更新电话本$ 删除终端中已有全部联系人并追加消 息中的联系人, * 2: 表示追加电话本, * 3: 表示修改电话本$以联系人为索引 */ private int type; /** * 联系人 */ private List phoneBookContactList; @Override public ByteBuf encode() { ByteBuf buffer = Unpooled.buffer(); buffer.writeByte(type); if (phoneBookContactList != null && !phoneBookContactList.isEmpty()) { buffer.writeByte(phoneBookContactList.size()); for (JTPhoneBookContact jtPhoneBookContact : phoneBookContactList) { buffer.writeBytes(jtPhoneBookContact.encode()); } }else { buffer.writeByte(0); } return buffer; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8500.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.bean.JTPhoneBookContact; import com.genersoft.iot.vmp.jt1078.bean.JTVehicleControl; import com.genersoft.iot.vmp.jt1078.bean.common.ConfigAttribute; import com.genersoft.iot.vmp.jt1078.bean.config.JTDeviceSubConfig; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.Getter; import lombok.Setter; import java.lang.reflect.Field; import java.nio.charset.Charset; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 车辆控制 */ @Setter @Getter @MsgId(id = "8500") public class J8500 extends Rs { /** * 控制类型 */ private JTVehicleControl vehicleControl; @Override public ByteBuf encode() { ByteBuf buffer = Unpooled.buffer(); buffer.writeShort((short)(vehicleControl.getLength() & 0xffff)); Field[] fields = vehicleControl.getClass().getDeclaredFields(); for (Field field : fields) { field.setAccessible(true); Object value = null; try { value = field.get(vehicleControl); if (value == null) { continue; } } catch (IllegalAccessException e) { throw new RuntimeException(e); } ConfigAttribute configAttribute = field.getAnnotation(ConfigAttribute.class); if (configAttribute == null) { continue; } buffer.writeShort((short)(configAttribute.id() & 0xffff)); switch (configAttribute.type()) { case "Byte": field.setAccessible(true); buffer.writeByte((int)value); continue; } } return buffer; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8600.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.bean.JTCircleArea; import com.genersoft.iot.vmp.jt1078.bean.JTVehicleControl; import com.genersoft.iot.vmp.jt1078.bean.common.ConfigAttribute; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.Getter; import lombok.Setter; import java.lang.reflect.Field; import java.util.List; /** * 设置圆形区域 */ @Setter @Getter @MsgId(id = "8600") public class J8600 extends Rs { /** * 设置属性, 0:更新区域; 1:追加区域; 2:修改区域 */ private int attribute; /** * 区域项 */ private List circleAreaList; @Override public ByteBuf encode() { ByteBuf buffer = Unpooled.buffer(); buffer.writeByte(attribute); buffer.writeByte(circleAreaList.size()); if (circleAreaList.isEmpty()) { return buffer; } for (JTCircleArea circleArea : circleAreaList) { buffer.writeBytes(circleArea.encode()); } return buffer; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8601.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.bean.JTCircleArea; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.Getter; import lombok.Setter; import java.util.List; /** * 删除圆形区域 */ @Setter @Getter @MsgId(id = "8601") public class J8601 extends Rs { /** * 待删除的区域ID */ private List idList; @Override public ByteBuf encode() { ByteBuf buffer = Unpooled.buffer(); if (idList == null || idList.isEmpty()) { buffer.writeByte(0); return buffer; }else { buffer.writeByte(idList.size()); } for (Long id : idList) { buffer.writeInt((int) (id & 0xffffffffL)); } return buffer; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8602.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.bean.JTCircleArea; import com.genersoft.iot.vmp.jt1078.bean.JTRectangleArea; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.Getter; import lombok.Setter; import java.util.List; /** * 设置矩形区域 */ @Setter @Getter @MsgId(id = "8602") public class J8602 extends Rs { /** * 设置属性, 0:更新区域; 1:追加区域; 2:修改区域 */ private int attribute; /** * 区域项 */ private List rectangleAreas; @Override public ByteBuf encode() { ByteBuf buffer = Unpooled.buffer(); buffer.writeByte(attribute); buffer.writeByte(rectangleAreas.size()); if (rectangleAreas.isEmpty()) { return buffer; } for (JTRectangleArea area : rectangleAreas) { buffer.writeBytes(area.encode()); } return buffer; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8603.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.Getter; import lombok.Setter; import java.util.List; /** * 删除矩形区域 */ @Setter @Getter @MsgId(id = "8603") public class J8603 extends Rs { /** * 待删除的区域ID */ private List idList; @Override public ByteBuf encode() { ByteBuf buffer = Unpooled.buffer(); if (idList == null || idList.isEmpty()) { buffer.writeByte(0); return buffer; }else { buffer.writeByte(idList.size()); } for (Long id : idList) { buffer.writeInt((int) (id & 0xffffffffL)); } return buffer; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8604.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.bean.JTPolygonArea; import com.genersoft.iot.vmp.jt1078.bean.JTRectangleArea; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.Getter; import lombok.Setter; import java.util.List; /** * 设置多边形区域 */ @Setter @Getter @MsgId(id = "8604") public class J8604 extends Rs { /** * 多边形区域 */ private JTPolygonArea polygonArea; @Override public ByteBuf encode() { ByteBuf buffer = Unpooled.buffer(); buffer.writeBytes(polygonArea.encode()); return buffer; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8605.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.Getter; import lombok.Setter; import java.util.List; /** * 删除多边形区域 */ @Setter @Getter @MsgId(id = "8605") public class J8605 extends Rs { /** * 待删除的区域ID */ private List idList; @Override public ByteBuf encode() { ByteBuf buffer = Unpooled.buffer(); if (idList == null || idList.isEmpty()) { buffer.writeByte(0); return buffer; }else { buffer.writeByte(idList.size()); } for (Long id : idList) { buffer.writeInt((int) (id & 0xffffffffL)); } return buffer; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8606.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.bean.JTPolygonArea; import com.genersoft.iot.vmp.jt1078.bean.JTRoute; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.Getter; import lombok.Setter; /** * 设置路线 */ @Setter @Getter @MsgId(id = "8606") public class J8606 extends Rs { /** * 路线 */ private JTRoute route; @Override public ByteBuf encode() { ByteBuf buffer = Unpooled.buffer(); buffer.writeBytes(route.encode()); return buffer; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8607.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.Getter; import lombok.Setter; import java.util.List; /** * 删除路线 */ @Setter @Getter @MsgId(id = "8607") public class J8607 extends Rs { /** * 待删除的路线ID */ private List idList; @Override public ByteBuf encode() { ByteBuf buffer = Unpooled.buffer(); if (idList == null || idList.isEmpty()) { buffer.writeByte(0); return buffer; }else { buffer.writeByte(idList.size()); } for (Long id : idList) { buffer.writeInt((int) (id & 0xffffffffL)); } return buffer; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8608.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.Getter; import lombok.Setter; import java.util.List; /** * 查询区域或线路数据 */ @Setter @Getter @MsgId(id = "8608") public class J8608 extends Rs { /** * 查询类型, 1 = 查询圆形区域数据 ,2 = 查询矩形区域数据 ,3 = 查询多 边形区域数据 ,4 = 查询线路数据 */ private int type; /** * 要查询的区域或线路的 ID */ private List idList; @Override public ByteBuf encode() { ByteBuf buffer = Unpooled.buffer(); buffer.writeByte(type); if (idList == null || idList.isEmpty()) { buffer.writeInt(0); return buffer; }else { buffer.writeInt(idList.size()); } for (Long id : idList) { buffer.writeInt((int) (id & 0xffffffffL)); } return buffer; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8702.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; /** * 上报驾驶员身份信息请求 */ @MsgId(id = "8702") public class J8702 extends Rs { @Override public ByteBuf encode() { return Unpooled.buffer(); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8801.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.bean.JTShootingCommand; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import lombok.Getter; import lombok.Setter; import java.util.List; /** * 摄像头立即拍摄命令 */ @Setter @Getter @MsgId(id = "8801") public class J8801 extends Rs { JTShootingCommand command; @Override public ByteBuf encode() { return command.decode(); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8802.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.bean.JTQueryMediaDataCommand; import com.genersoft.iot.vmp.jt1078.bean.JTShootingCommand; import io.netty.buffer.ByteBuf; import lombok.Getter; import lombok.Setter; /** * 存储多媒体数据检索 */ @Setter @Getter @MsgId(id = "8802") public class J8802 extends Rs { JTQueryMediaDataCommand command; @Override public ByteBuf encode() { return command.decode(); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8803.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.bean.JTQueryMediaDataCommand; import io.netty.buffer.ByteBuf; import lombok.Getter; import lombok.Setter; /** * 存储多媒体数据上传命令 */ @Setter @Getter @MsgId(id = "8803") public class J8803 extends Rs { JTQueryMediaDataCommand command; @Override public ByteBuf encode() { return command.decode(); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8804.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.bean.JTQueryMediaDataCommand; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.Getter; import lombok.Setter; /** * 录音开始/停止命令 */ @Setter @Getter @MsgId(id = "8804") public class J8804 extends Rs { /** * 录音命令, 0:停止录音;0X01:开始录音 */ private int commond; /** * 录音时长,单位为秒(s) ,0 表示一直录音 */ private int duration; /** * 保存标志, 0:实时上传;1:保存 */ private int save; /** * 音频采样率, 0:8K;1:11K;2:23K;3:32K;其他保留 */ private int samplingRate; @Override public ByteBuf encode() { ByteBuf byteBuf = Unpooled.buffer(); byteBuf.writeByte(commond); byteBuf.writeShort((short)(duration & 0xffff)); byteBuf.writeByte(save); byteBuf.writeByte(samplingRate); return byteBuf; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8805.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.bean.JTQueryMediaDataCommand; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.Setter; /** * 单条存储多媒体数据检索上传命令 */ @Setter @Getter @MsgId(id = "8805") public class J8805 extends Rs { /** * 多媒体 ID */ private Long mediaId; /** * 删除标志, 0:保留;1:删除, 存储多媒体数据上传命令中使用 */ private Integer delete; @Override public ByteBuf encode() { ByteBuf byteBuf = Unpooled.buffer(); byteBuf.writeInt((int) (mediaId & 0xffffffffL)); byteBuf.writeByte(delete); return byteBuf; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8900.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.Getter; import lombok.Setter; /** * 数据下行透传 */ @Setter @Getter @MsgId(id = "8900") public class J8900 extends Rs { /** * 透传消息类型, 0x00: GNSS 模块详细定位数据, 0X0B: 道路运输证 IC卡信息, 0X41: 串口1 透传, 0X42: 串口2 透传, 0XF0 ~ 0XFF: 用户自定义透传 */ private Integer type; /** * 透传消息内容 */ private byte[] content; @Override public ByteBuf encode() { ByteBuf byteBuf = Unpooled.buffer(); byteBuf.writeByte(type); byteBuf.writeBytes(content); return byteBuf; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8A00.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.Getter; import lombok.Setter; /** * 平台 RSA公钥 */ @Setter @Getter @MsgId(id = "8A00") public class J8A00 extends Rs { /** * 平台 RSA公钥{e ,n}中的 e */ private Long e; /** * RSA公钥{e ,n}中的 n */ private byte[] n; @Override public ByteBuf encode() { ByteBuf byteBuf = Unpooled.buffer(); byteBuf.writeInt((int) (e & 0xffffffffL)); byteBuf.writeBytes(n); return byteBuf; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9003.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; /** * 查询终端音视频属性 */ @MsgId(id = "9003") public class J9003 extends Rs { @Override public ByteBuf encode() { return Unpooled.buffer(); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9101.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.util.CharsetUtil; import lombok.Getter; import lombok.Setter; import java.nio.charset.Charset; /** * 实时音视频传输请求 * * @author QingtaiJiang * @date 2023/4/27 18:25 * @email qingtaij@163.com */ @Setter @Getter @MsgId(id = "9101") public class J9101 extends Rs { String ip; // TCP端口 Integer tcpPort; // UDP端口 Integer udpPort; // 逻辑通道号 Integer channel; // 数据类型 /** * 0:音视频,1:视频,2:双向对讲,3:监听,4:中心广播,5:透传 */ Integer type; // 码流类型 /** * 0:主码流,1:子码流 */ Integer rate; @Override public ByteBuf encode() { ByteBuf buffer = Unpooled.buffer(); buffer.writeByte(ip.getBytes(Charset.forName("GBK")).length); buffer.writeCharSequence(ip, Charset.forName("GBK")); buffer.writeShort(tcpPort); buffer.writeShort(udpPort); buffer.writeByte(channel); buffer.writeByte(type); buffer.writeByte(rate); return buffer; } @Override public String toString() { return "J9101{" + "ip='" + ip + '\'' + ", tcpPort=" + tcpPort + ", udpPort=" + udpPort + ", channel=" + channel + ", type=" + type + ", rate=" + rate + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9102.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.Getter; import lombok.Setter; /** * 音视频实时传输控制 * * @author QingtaiJiang * @date 2023/4/27 18:49 * @email qingtaij@163.com */ @Setter @Getter @MsgId(id = "9102") public class J9102 extends Rs { // 通道号 Integer channel; // 控制指令 /** * 0:关闭音视频传输指令; * 1:切换码流(增加暂停和继续); * 2:暂停该通道所有流的发送; * 3:恢复暂停前流的发送,与暂停前的流类型一致; * 4:关闭双向对讲 */ Integer command; // 数据类型 /** * 0:关闭该通道有关的音视频数据; * 1:只关闭该通道有关的音频,保留该通道 * 有关的视频; * 2:只关闭该通道有关的视频,保留该通道 * 有关的音频 */ Integer closeType; // 数据类型 /** * 0:主码流; * 1:子码流 */ Integer streamType; @Override public ByteBuf encode() { ByteBuf buffer = Unpooled.buffer(); buffer.writeByte(channel); buffer.writeByte(command); buffer.writeByte(closeType); buffer.writeByte(streamType); return buffer; } @Override public String toString() { return "J9102{" + "channel=" + channel + ", command=" + command + ", closeType=" + closeType + ", streamType=" + streamType + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9201.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.util.CharsetUtil; import lombok.Getter; import lombok.Setter; import java.nio.charset.Charset; /** * 回放请求 * * @author QingtaiJiang * @date 2023/4/28 10:37 * @email qingtaij@163.com */ @Setter @Getter @MsgId(id = "9201") public class J9201 extends Rs { // 服务器IP地址 private String ip; // 实时视频服务器TCP端口号 private int tcpPort; // 实时视频服务器UDP端口号 private int udpPort; // 逻辑通道号 private int channel; // 音视频资源类型:0.音视频 1.音频 2.视频 3.视频或音视频 private int type; // 码流类型:0.所有码流 1.主码流 2.子码流(如果此通道只传输音频,此字段置0) private int rate; // 存储器类型:0.所有存储器 1.主存储器 2.灾备存储器" private int storageType; // 回放方式:0.正常回放 1.快进回放 2.关键帧快退回放 3.关键帧播放 4.单帧上传 private int playbackType; // 快进或快退倍数:0.无效 1.1倍 2.2倍 3.4倍 4.8倍 5.16倍 (回放控制为1和2时,此字段内容有效,否则置0) private int playbackSpeed; // 开始时间YYMMDDHHMMSS,回放方式为4时,该字段表示单帧上传时间 private String startTime; // 结束时间YYMMDDHHMMSS,回放方式为4时,该字段无效,为0表示一直回放 private String endTime; @Override public ByteBuf encode() { ByteBuf buffer = Unpooled.buffer(); buffer.writeByte(ip.getBytes(Charset.forName("GBK")).length); buffer.writeCharSequence(ip, Charset.forName("GBK")); buffer.writeShort(tcpPort); buffer.writeShort(udpPort); buffer.writeByte(channel); buffer.writeByte(type); buffer.writeByte(rate); buffer.writeByte(storageType); buffer.writeByte(playbackType); buffer.writeByte(playbackSpeed); buffer.writeBytes(ByteBufUtil.decodeHexDump(startTime)); buffer.writeBytes(ByteBufUtil.decodeHexDump(endTime)); return buffer; } @Override public String toString() { return "J9201{" + "ip='" + ip + '\'' + ", tcpPort=" + tcpPort + ", udpPort=" + udpPort + ", channel=" + channel + ", type=" + type + ", rate=" + rate + ", storageType=" + storageType + ", playbackType=" + playbackType + ", playbackSpeed=" + playbackSpeed + ", startTime='" + startTime + '\'' + ", endTime='" + endTime + '\'' + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9202.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import lombok.Getter; import lombok.Setter; /** * 平台下发远程录像回放控制 * * @author QingtaiJiang * @date 2023/4/28 10:37 * @email qingtaij@163.com */ @Setter @Getter @MsgId(id = "9202") public class J9202 extends Rs { // 逻辑通道号 private int channel; // 回放控制:0.开始回放 1.暂停回放 2.结束回放 3.快进回放 4.关键帧快退回放 5.拖动回放 6.关键帧播放 private int playbackType; // 快进或快退倍数:0.无效 1.1倍 2.2倍 3.4倍 4.8倍 5.16倍 (回放控制为3和4时,此字段内容有效,否则置0) private int playbackSpeed; // 拖动回放位置(YYMMDDHHMMSS,回放控制为5时,此字段有效) private String playbackTime; @Override public ByteBuf encode() { ByteBuf buffer = Unpooled.buffer(); buffer.writeByte(channel); buffer.writeByte(playbackType); buffer.writeByte(playbackSpeed); if (playbackType == 5) { buffer.writeBytes(ByteBufUtil.decodeHexDump(playbackTime)); } return buffer; } @Override public String toString() { return "J9202{" + "channel=" + channel + ", playbackType=" + playbackType + ", playbackSpeed=" + playbackSpeed + ", playbackTime='" + playbackTime + '\'' + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9205.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; /** * 查询资源列表 * * @author QingtaiJiang * @date 2023/4/28 10:36 * @email qingtaij@163.com */ @MsgId(id = "9205") public class J9205 extends Rs { // 逻辑通道号 private int channelId; // 开始时间YYMMDDHHMMSS,全0表示无起始时间 private String startTime; // 结束时间YYMMDDHHMMSS,全0表示无终止时间 private String endTime; // 报警标志 private final int warnType = 0; // 音视频资源类型:0.音视频 1.音频 2.视频 3.视频或音视频 private int mediaType; // 码流类型:0.所有码流 1.主码流 2.子码流 private int streamType = 0; // 存储器类型:0.所有存储器 1.主存储器 2.灾备存储器 private int storageType = 0; @Override public ByteBuf encode() { ByteBuf buffer = Unpooled.buffer(); buffer.writeByte(channelId); buffer.writeBytes(ByteBufUtil.decodeHexDump(startTime)); buffer.writeBytes(ByteBufUtil.decodeHexDump(endTime)); buffer.writeLong(warnType); buffer.writeByte(mediaType); buffer.writeByte(streamType); buffer.writeByte(storageType); return buffer; } public void setChannelId(int channelId) { this.channelId = channelId; } public void setStartTime(String startTime) { this.startTime = startTime; } public void setEndTime(String endTime) { this.endTime = endTime; } public void setMediaType(int mediaType) { this.mediaType = mediaType; } public void setStreamType(int streamType) { this.streamType = streamType; } public void setStorageType(int storageType) { this.storageType = storageType; } public int getWarnType() { return warnType; } @Override public String toString() { return "J9205{" + "channelId=" + channelId + ", startTime='" + startTime + '\'' + ", endTime='" + endTime + '\'' + ", warnType=" + warnType + ", mediaType=" + mediaType + ", streamType=" + streamType + ", storageType=" + storageType + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9206.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import lombok.Getter; import lombok.Setter; import java.nio.charset.Charset; /** * 文件上传指令 * */ @Setter @Getter @MsgId(id = "9206") public class J9206 extends Rs { // 服务器地址 private String serverIp; // 服务器端口 private int port; // 用户名 private String username; // 密码 private String password; // 文件上传路径 private String path; // 逻辑通道号 private int channelId; // 开始时间YYMMDDHHMMSS,全0表示无起始时间 private String startTime; // 结束时间YYMMDDHHMMSS,全0表示无终止时间 private String endTime; // 报警标志 private int alarmSign = 0; // 音视频资源类型:0.音视频 1.音频 2.视频 3.视频或音视频 private int mediaType; // 码流类型:0.所有码流 1.主码流 2.子码流 private int streamType = 0; // 存储器类型:0.所有存储器 1.主存储器 2.灾备存储器 private int storageType = 0; // 任务执行条件, // 1:仅WI-FI 下可下载, // 2: 仅LAN 连接时可下载; // 3: WI-FI + LAN 连接时可下载; // 4: 仅3G/ 4G 连接时可下载 // 5: WI-FI + 3G/ 4G 连接时可下载 // 6: WI-FI + LAN 连接时可下载 // 7: WI-FI + LAN + 3G/ 4G 连接时可下载 private int taskConditions = 7; @Override public ByteBuf encode() { ByteBuf buffer = Unpooled.buffer(); buffer.writeByte(serverIp.getBytes(Charset.forName("GBK")).length); buffer.writeCharSequence(serverIp, Charset.forName("GBK")); buffer.writeShort(port); buffer.writeByte(username.getBytes(Charset.forName("GBK")).length); buffer.writeCharSequence(username, Charset.forName("GBK")); buffer.writeByte(password.getBytes(Charset.forName("GBK")).length); buffer.writeCharSequence(password, Charset.forName("GBK")); buffer.writeByte(path.getBytes(Charset.forName("GBK")).length); buffer.writeCharSequence(path, Charset.forName("GBK")); buffer.writeByte(channelId); buffer.writeBytes(ByteBufUtil.decodeHexDump(startTime)); buffer.writeBytes(ByteBufUtil.decodeHexDump(endTime)); buffer.writeLong(alarmSign); buffer.writeByte(mediaType); buffer.writeByte(streamType); buffer.writeByte(storageType); buffer.writeByte(taskConditions); return buffer; } @Override public String toString() { return "J9206{" + "serverIp='" + serverIp + '\'' + ", port=" + port + ", user='" + username + '\'' + ", password='" + password + '\'' + ", path='" + path + '\'' + ", channelId=" + channelId + ", startTime='" + startTime + '\'' + ", endTime='" + endTime + '\'' + ", warnType=" + alarmSign + ", mediaType=" + mediaType + ", streamType=" + streamType + ", storageType=" + storageType + ", taskConditions=" + taskConditions + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9207.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.util.CharsetUtil; import lombok.Getter; import lombok.Setter; /** * 文件上传控制 * */ @Setter @Getter @MsgId(id = "9207") public class J9207 extends Rs { // 对应平台文件上传消息的流水号 Integer respNo; // 控制: 0:暂停; 1:继续; 2:取消 private int control; @Override public ByteBuf encode() { ByteBuf buffer = Unpooled.buffer(); buffer.writeShort(respNo); buffer.writeByte(control); return buffer; } @Override public String toString() { return "J9207{" + "respNo=" + respNo + ", control=" + control + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9301.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import lombok.Getter; import lombok.Setter; /** * 云台控制指令-云台旋转 * */ @Setter @Getter @MsgId(id = "9301") public class J9301 extends Rs { // 逻辑通道号 private int channel; // 方向: 0:停止; 1:上; 2:下; 3:左; 4:右 private int direction; // 速度:0 ~ 255 private int speed; @Override public ByteBuf encode() { ByteBuf buffer = Unpooled.buffer(); buffer.writeByte(channel); buffer.writeByte(direction); buffer.writeByte(speed); return buffer; } @Override public String toString() { return "J9301{" + "channel=" + channel + ", direction=" + direction + ", speed=" + speed + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9302.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.Getter; import lombok.Setter; /** * 云台控制指令-焦距控制 * */ @Setter @Getter @MsgId(id = "9302") public class J9302 extends Rs { // 逻辑通道号 private int channel; // 方向: 0:焦距调大; 1:焦距调小 private int focalDirection; @Override public ByteBuf encode() { ByteBuf buffer = Unpooled.buffer(); buffer.writeByte(channel); buffer.writeByte(focalDirection); return buffer; } @Override public String toString() { return "J9302{" + "channel=" + channel + ", zoomDirection=" + focalDirection + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9303.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.Getter; import lombok.Setter; /** * 云台控制指令-光圈控制 * */ @Setter @Getter @MsgId(id = "9303") public class J9303 extends Rs { // 逻辑通道号 private int channel; // 调整方式: 0:调大; 1:调小 private int iris; @Override public ByteBuf encode() { ByteBuf buffer = Unpooled.buffer(); buffer.writeByte(channel); buffer.writeByte(iris); return buffer; } @Override public String toString() { return "J9303{" + "channel=" + channel + ", iris=" + iris + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9304.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.Getter; import lombok.Setter; /** * 云台控制指令-云台雨刷控制 * */ @Setter @Getter @MsgId(id = "9304") public class J9304 extends Rs { // 逻辑通道号 private int channel; // 启停标识: 0:停止; 1:启动 private int on; @Override public ByteBuf encode() { ByteBuf buffer = Unpooled.buffer(); buffer.writeByte(channel); buffer.writeByte(on); return buffer; } @Override public String toString() { return "J9304{" + "channel=" + channel + ", on=" + on + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9305.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.Getter; import lombok.Setter; /** * 云台控制指令-红外补光控制 * */ @Setter @Getter @MsgId(id = "9305") public class J9305 extends Rs { // 逻辑通道号 private int channel; // 启停标识: 0:停止; 1:启动 private int on; @Override public ByteBuf encode() { ByteBuf buffer = Unpooled.buffer(); buffer.writeByte(channel); buffer.writeByte(on); return buffer; } @Override public String toString() { return "J9305{" + "channel=" + channel + ", on=" + on + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9306.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.Getter; import lombok.Setter; /** * 云台控制指令-云台变倍控制 * */ @Setter @Getter @MsgId(id = "9306") public class J9306 extends Rs { // 逻辑通道号 private int channel; // 0:调大; 1:调小 private int zoom; @Override public ByteBuf encode() { ByteBuf buffer = Unpooled.buffer(); buffer.writeByte(channel); buffer.writeByte(zoom); return buffer; } @Override public String toString() { return "J9306{" + "channel=" + channel + ", zoom=" + zoom + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/Rs.java ================================================ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.proc.Header; import io.netty.buffer.ByteBuf; /** * @author QingtaiJiang * @date 2021/8/30 18:54 * @email qingtaij@163.com */ public abstract class Rs { private Header header; public abstract ByteBuf encode(); public Header getHeader() { return header; } public void setHeader(Header header) { this.header = header; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/service/Ijt1078PlayService.java ================================================ package com.genersoft.iot.vmp.jt1078.service; import com.genersoft.iot.vmp.common.CommonCallback; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import com.genersoft.iot.vmp.jt1078.bean.*; import com.genersoft.iot.vmp.jt1078.proc.request.J1205; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import java.util.List; public interface Ijt1078PlayService { JTMediaStreamType checkStreamFromJt(String stream); void play(String phoneNumber, Integer channelId, int type, CommonCallback> callback); void playback(String phoneNumber, Integer channelId, String startTime, String endTime, Integer type, Integer rate, Integer playbackType, Integer playbackSpeed, CommonCallback> callback); void stopPlay(String phoneNumber, Integer channelId); void pausePlay(String phoneNumber, Integer channelId); void continueLivePlay(String phoneNumber, Integer channelId); List getRecordList(String phoneNumber, Integer channelId, String startTime, String endTime); void stopPlayback(String phoneNumber, Integer channelId); StreamInfo startTalk(String phoneNumber, Integer channelId); void stopTalk(String phoneNumber, Integer channelId); void playbackControl(String phoneNumber, Integer channelId, Integer command, Integer playbackSpeed, String time); void start(Integer channelId, Boolean record, ErrorCallback callback); void stop(Integer channelId); void playBack(Integer channelId, Long startTime, Long stopTime, ErrorCallback callback); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/service/Ijt1078Service.java ================================================ package com.genersoft.iot.vmp.jt1078.service; import com.genersoft.iot.vmp.common.CommonCallback; import com.genersoft.iot.vmp.jt1078.bean.*; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import com.github.pagehelper.PageInfo; import jakarta.servlet.ServletOutputStream; import java.io.OutputStream; import java.util.List; public interface Ijt1078Service { JTMediaStreamType checkStreamFromJt(String stream); JTDevice getDevice(String phoneNumber); JTChannel getChannel(Integer terminalDbId, Integer channelId); void updateDevice(JTDevice deviceInDb); PageInfo getDeviceList(int page, int count, String query, Boolean online); void addDevice(JTDevice device); void deleteDeviceByPhoneNumber(String phoneNumber); void updateDeviceStatus(boolean connected, String phoneNumber); void ptzControl(String phoneNumber, Integer channelId, String command, int speed); void supplementaryLight(String phoneNumber, Integer channelId, String command); void wiper(String phoneNumber, Integer channelId, String command); JTDeviceConfig queryConfig(String phoneNumber, String[] params); void setConfig(String phoneNumber, JTDeviceConfig config); void connectionControl(String phoneNumber, JTDeviceConnectionControl control); void resetControl(String phoneNumber); void factoryResetControl(String phoneNumber); JTDeviceAttribute attribute(String phoneNumber); JTPositionBaseInfo queryPositionInfo(String phoneNumber); void tempPositionTrackingControl(String phoneNumber, Integer timeInterval, Long validityPeriod); void confirmationAlarmMessage(String phoneNumber, int alarmPackageNo, JTConfirmationAlarmMessageType alarmMessageType); int linkDetection(String phoneNumber); int textMessage(String phoneNumber,JTTextSign sign, int textType, String content); int telephoneCallback(String phoneNumber, Integer sign, String destPhoneNumber); int setPhoneBook(String phoneNumber, int type, List phoneBookContactList); JTPositionBaseInfo controlDoor(String phoneNumber, Boolean open); int setAreaForCircle(int attribute, String phoneNumber, List circleAreaList); int deleteAreaForCircle(String phoneNumber, List ids); List queryAreaForCircle(String phoneNumber, List ids); int setAreaForRectangle(int i, String phoneNumber, List rectangleAreas); int deleteAreaForRectangle(String phoneNumber, List ids); List queryAreaForRectangle(String phoneNumber, List ids); int setAreaForPolygon(String phoneNumber, JTPolygonArea polygonArea); int deleteAreaForPolygon(String phoneNumber, List ids); List queryAreaForPolygon(String phoneNumber, List ids); int setRoute(String phoneNumber, JTRoute route); int deleteRoute(String phoneNumber, List ids); List queryRoute(String phoneNumber, List ids); JTDriverInformation queryDriverInformation(String phoneNumber); List shooting(String phoneNumber, JTShootingCommand shootingCommand); List queryMediaData(String phoneNumber, JTQueryMediaDataCommand queryMediaDataCommand); void uploadMediaData(String phoneNumber, JTQueryMediaDataCommand queryMediaDataCommand); void record(String phoneNumber, int command, Integer time, Integer save, Integer samplingRate); void uploadMediaDataForSingle(String phoneNumber, Long mediaId, Integer delete); JTMediaAttribute queryMediaAttribute(String phoneNumber); void changeStreamType(String phoneNumber, Integer channelId, Integer streamType); void recordDownload(String phoneNumber, Integer channelId, String startTime, String endTime, Integer alarmSign, Integer mediaType, Integer streamType, Integer storageType, OutputStream outputStream, CommonCallback> fileCallback); PageInfo getChannelList(int page, int count, int deviceId, String query); void updateChannel(JTChannel channel); void addChannel(JTChannel channel); void deleteChannelById(Integer id); JTDevice getDeviceById(Integer deviceId); void updateDevicePosition(String phoneNumber, Double longitude, Double latitude); JTChannel getChannelByDbId(Integer id); String getRecordTempUrl(String phoneNumber, Integer channelId, String startTime, String endTime, Integer alarmSign, Integer mediaType, Integer streamType, Integer storageType); void recordDownload(String filePath, ServletOutputStream outputStream); byte[] snap(String phoneNumber, int channelId); void uploadOneMedia(String phoneNumber, Long mediaId, ServletOutputStream outputStream, boolean delete); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/service/impl/SourcePTZServiceForJTImpl.java ================================================ package com.genersoft.iot.vmp.jt1078.service.impl; import com.genersoft.iot.vmp.common.enums.ChannelDataType; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.service.IPTZService; import com.genersoft.iot.vmp.gb28181.service.ISourcePTZService; import com.genersoft.iot.vmp.jt1078.bean.JTChannel; import com.genersoft.iot.vmp.jt1078.bean.JTDevice; import com.genersoft.iot.vmp.jt1078.cmd.JT1078Template; import com.genersoft.iot.vmp.jt1078.proc.response.*; import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import lombok.extern.slf4j.Slf4j; import org.checkerframework.checker.units.qual.A; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.Assert; import java.util.List; @Slf4j @Service(ChannelDataType.PTZ_SERVICE + ChannelDataType.JT_1078) public class SourcePTZServiceForJTImpl implements ISourcePTZService { @Autowired private Ijt1078Service service; @Autowired private JT1078Template jt1078Template; @Override public void ptz(CommonGBChannel channel, FrontEndControlCodeForPTZ frontEndControlCode, ErrorCallback callback) { JTChannel jtChannel = service.getChannelByDbId(channel.getDataDeviceId()); if (jtChannel == null) { callback.run(ErrorCode.ERROR404.getCode(), "通道不存在", null); return; } JTDevice jtDevice = service.getDeviceById(jtChannel.getTerminalDbId()); if (jtDevice == null) { callback.run(ErrorCode.ERROR404.getCode(), "设备不存在", null); return; } if (frontEndControlCode.getPan() == null && frontEndControlCode.getTilt() == null && frontEndControlCode.getZoom() == null) { J9301 j9301 = new J9301(); j9301.setChannel(jtChannel.getChannelId()); j9301.setDirection(0); j9301.setSpeed(0); jt1078Template.ptzRotate(jtDevice.getPhoneNumber(), j9301, 6); callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), null); return; } if (frontEndControlCode.getPan() != null || frontEndControlCode.getTilt() != null) { J9301 j9301 = new J9301(); j9301.setChannel(jtChannel.getChannelId()); if (frontEndControlCode.getTilt() != null) { if (frontEndControlCode.getTilt() == 0) { j9301.setDirection(1); }else if (frontEndControlCode.getTilt() == 1) { j9301.setDirection(2); } j9301.setSpeed((int)(frontEndControlCode.getTilt()/100D * 255)); } if (frontEndControlCode.getPan() != null) { if (frontEndControlCode.getPan() == 0) { j9301.setDirection(3); }else if (frontEndControlCode.getPan() == 1) { j9301.setDirection(4); } j9301.setSpeed((int)(frontEndControlCode.getPanSpeed()/100D * 255)); } jt1078Template.ptzRotate(jtDevice.getPhoneNumber(), j9301, 6); } if (frontEndControlCode.getZoom() != null) { J9306 j9306 = new J9306(); j9306.setChannel(jtChannel.getChannelId()); j9306.setZoom(1 - frontEndControlCode.getZoom()); jt1078Template.ptzZoom(jtDevice.getPhoneNumber(), j9306, 6); } callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), null); } @Override public void preset(CommonGBChannel channel, FrontEndControlCodeForPreset frontEndControlCode, ErrorCallback callback) { callback.run(ErrorCode.ERROR486.getCode(), ErrorCode.ERROR486.getMsg(), null); } @Override public void fi(CommonGBChannel channel, FrontEndControlCodeForFI frontEndControlCode, ErrorCallback callback) { JTChannel jtChannel = service.getChannelByDbId(channel.getDataDeviceId()); if (jtChannel == null) { callback.run(ErrorCode.ERROR404.getCode(), "通道不存在", null); return; } JTDevice jtDevice = service.getDeviceById(jtChannel.getTerminalDbId()); if (jtDevice == null) { callback.run(ErrorCode.ERROR404.getCode(), "设备不存在", null); return; } if (frontEndControlCode.getIris() != null) { J9303 j9303 = new J9303(); j9303.setChannel(jtChannel.getChannelId()); j9303.setIris(1 - frontEndControlCode.getIris()); jt1078Template.ptzIris(jtDevice.getPhoneNumber(), j9303, 6); } if (frontEndControlCode.getFocus() != null) { J9302 j9302 = new J9302(); j9302.setChannel(jtChannel.getChannelId()); j9302.setFocalDirection(1 - frontEndControlCode.getFocus()); jt1078Template.ptzFocal(jtDevice.getPhoneNumber(), j9302, 6); } } @Override public void tour(CommonGBChannel channel, FrontEndControlCodeForTour frontEndControlCode, ErrorCallback callback) { callback.run(ErrorCode.ERROR486.getCode(), ErrorCode.ERROR486.getMsg(), null); } @Override public void scan(CommonGBChannel channel, FrontEndControlCodeForScan frontEndControlCode, ErrorCallback callback) { callback.run(ErrorCode.ERROR486.getCode(), ErrorCode.ERROR486.getMsg(), null); } @Override public void auxiliary(CommonGBChannel channel, FrontEndControlCodeForAuxiliary frontEndControlCode, ErrorCallback callback) { callback.run(ErrorCode.ERROR486.getCode(), ErrorCode.ERROR486.getMsg(), null); } @Override public void wiper(CommonGBChannel channel, FrontEndControlCodeForWiper frontEndControlCode, ErrorCallback callback) { JTChannel jtChannel = service.getChannelByDbId(channel.getDataDeviceId()); if (jtChannel == null) { callback.run(ErrorCode.ERROR404.getCode(), "通道不存在", null); return; } JTDevice jtDevice = service.getDeviceById(jtChannel.getTerminalDbId()); if (jtDevice == null) { callback.run(ErrorCode.ERROR404.getCode(), "设备不存在", null); return; } J9304 j9304 = new J9304(); j9304.setChannel(jtChannel.getChannelId()); if (frontEndControlCode.getCode() == 1) { j9304.setOn(1); }else if (frontEndControlCode.getCode() == 0){ j9304.setOn(0); } jt1078Template.ptzWiper(jtDevice.getPhoneNumber(), j9304, 6); } @Override public void queryPreset(CommonGBChannel channel, ErrorCallback> callback) { callback.run(ErrorCode.ERROR486.getCode(), ErrorCode.ERROR486.getMsg(), null); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/service/impl/SourcePlayServiceForJTImpl.java ================================================ package com.genersoft.iot.vmp.jt1078.service.impl; import com.genersoft.iot.vmp.common.InviteSessionType; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.common.enums.ChannelDataType; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import com.genersoft.iot.vmp.gb28181.bean.Platform; import com.genersoft.iot.vmp.gb28181.bean.PlayException; import com.genersoft.iot.vmp.gb28181.service.IPlayService; import com.genersoft.iot.vmp.gb28181.service.ISourcePlayService; import com.genersoft.iot.vmp.jt1078.service.Ijt1078PlayService; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import javax.sip.message.Response; @Slf4j @Service(ChannelDataType.PLAY_SERVICE + ChannelDataType.JT_1078) public class SourcePlayServiceForJTImpl implements ISourcePlayService { @Autowired private Ijt1078PlayService playService; @Override public void play(CommonGBChannel channel, Platform platform, Boolean record, ErrorCallback callback) { // 部标设备通道 try { playService.start(channel.getDataDeviceId(), record, callback); }catch (Exception e) { log.info("[通用通道] 部标设备点播异常 {}", e.getMessage()); callback.run(Response.BUSY_HERE, "busy here", null); } } @Override public void stopPlay(CommonGBChannel channel) { // 推流 try { playService.stop(channel.getDataDeviceId()); }catch (Exception e) { log.error("[停止点播失败] {}({})", channel.getGbName(), channel.getGbDeviceId(), e); } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/service/impl/SourcePlaybackServiceForJTImpl.java ================================================ package com.genersoft.iot.vmp.jt1078.service.impl; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.common.enums.ChannelDataType; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import com.genersoft.iot.vmp.gb28181.bean.CommonRecordInfo; import com.genersoft.iot.vmp.gb28181.service.ISourcePlaybackService; import com.genersoft.iot.vmp.jt1078.bean.JTChannel; import com.genersoft.iot.vmp.jt1078.bean.JTDevice; import com.genersoft.iot.vmp.jt1078.cmd.JT1078Template; import com.genersoft.iot.vmp.jt1078.dao.JTChannelMapper; import com.genersoft.iot.vmp.jt1078.dao.JTTerminalMapper; import com.genersoft.iot.vmp.jt1078.proc.request.J1205; import com.genersoft.iot.vmp.jt1078.service.Ijt1078PlayService; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import lombok.extern.slf4j.Slf4j; import org.checkerframework.checker.units.qual.A; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.Assert; import java.util.ArrayList; import java.util.List; @Slf4j @Service(ChannelDataType.PLAYBACK_SERVICE + ChannelDataType.JT_1078) public class SourcePlaybackServiceForJTImpl implements ISourcePlaybackService { @Autowired private JTTerminalMapper jtDeviceMapper; @Autowired private JTChannelMapper jtChannelMapper; @Autowired private JT1078Template jt1078Template; @Autowired private Ijt1078PlayService playService; @Override public void playback(CommonGBChannel channel, Long startTime, Long stopTime, ErrorCallback callback) { JTChannel jtChannel = jtChannelMapper.selectChannelById(channel.getDataDeviceId()); Assert.notNull(channel, "通道不存在"); JTDevice device = jtDeviceMapper.getDeviceById(jtChannel.getDataDeviceId()); Assert.notNull(device, "设备不存在"); jt1078Template.checkTerminalStatus(device.getPhoneNumber()); playService.playback(device.getPhoneNumber(), jtChannel.getChannelId(), DateUtil.timestampTo_yyyy_MM_dd_HH_mm_ss(startTime), DateUtil.timestampTo_yyyy_MM_dd_HH_mm_ss(stopTime), 0, 0, 0, 0, result -> { callback.run(result.getCode(), result.getMsg(), result.getData()); }); } @Override public void stopPlayback(CommonGBChannel channel, String stream) { JTChannel jtChannel = jtChannelMapper.selectChannelById(channel.getDataDeviceId()); Assert.notNull(channel, "通道不存在"); JTDevice device = jtDeviceMapper.getDeviceById(jtChannel.getDataDeviceId()); Assert.notNull(device, "设备不存在"); jt1078Template.checkTerminalStatus(device.getPhoneNumber()); playService.stopPlayback(device.getPhoneNumber(), jtChannel.getChannelId()); } @Override public void playbackPause(CommonGBChannel channel, String stream) { JTChannel jtChannel = jtChannelMapper.selectChannelById(channel.getDataDeviceId()); Assert.notNull(channel, "通道不存在"); JTDevice device = jtDeviceMapper.getDeviceById(jtChannel.getDataDeviceId()); Assert.notNull(device, "设备不存在"); jt1078Template.checkTerminalStatus(device.getPhoneNumber()); playService.playbackControl(device.getPhoneNumber(), jtChannel.getChannelId(), 1, 0, null); } @Override public void playbackResume(CommonGBChannel channel, String stream) { JTChannel jtChannel = jtChannelMapper.selectChannelById(channel.getDataDeviceId()); Assert.notNull(channel, "通道不存在"); JTDevice device = jtDeviceMapper.getDeviceById(jtChannel.getDataDeviceId()); Assert.notNull(device, "设备不存在"); jt1078Template.checkTerminalStatus(device.getPhoneNumber()); playService.playbackControl(device.getPhoneNumber(), jtChannel.getChannelId(), 0, 0, null); } @Override public void playbackSeek(CommonGBChannel channel, String stream, long seekTime) { // 因为seek是增量,比如15s处, 1078是具体的时间点,比如2025-10-10 23:21:12 这个一个绝对时间点。无法转换,故此处不做支持 log.warn("[JT-通用通道] 回放seek, 尚不支持"); } @Override public void playbackSpeed(CommonGBChannel channel, String stream, Double speed) { JTChannel jtChannel = jtChannelMapper.selectChannelById(channel.getDataDeviceId()); Assert.notNull(channel, "通道不存在"); JTDevice device = jtDeviceMapper.getDeviceById(jtChannel.getDataDeviceId()); Assert.notNull(device, "设备不存在"); jt1078Template.checkTerminalStatus(device.getPhoneNumber()); playService.playbackControl(device.getPhoneNumber(), jtChannel.getChannelId(), 0, (int)Math.floor(speed), null); } @Override public void queryRecord(CommonGBChannel channel, String startTime, String endTime, ErrorCallback> callback) { JTChannel jtChannel = jtChannelMapper.selectChannelById(channel.getDataDeviceId()); Assert.notNull(channel, "通道不存在"); JTDevice device = jtDeviceMapper.getDeviceById(jtChannel.getDataDeviceId()); Assert.notNull(device, "设备不存在"); jt1078Template.checkTerminalStatus(device.getPhoneNumber()); List recordList = playService.getRecordList(device.getPhoneNumber(), jtChannel.getChannelId(), startTime, endTime); if (recordList.isEmpty()) { callback.run(ErrorCode.ERROR100.getCode(), ErrorCode.ERROR100.getMsg(), null); return; } List recordInfoList = new ArrayList<>(); for (J1205.JRecordItem jRecordItem : recordList) { CommonRecordInfo commonRecordInfo = new CommonRecordInfo(); commonRecordInfo.setStartTime(jRecordItem.getStartTime()); commonRecordInfo.setEndTime(jRecordItem.getEndTime()); commonRecordInfo.setFileSize(jRecordItem.getSize() + ""); recordInfoList.add(commonRecordInfo); } callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), recordInfoList); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/service/impl/jt1078PlayServiceImpl.java ================================================ package com.genersoft.iot.vmp.jt1078.service.impl; import com.genersoft.iot.vmp.common.CommonCallback; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.common.VideoManagerConstants; import com.genersoft.iot.vmp.common.enums.MediaApp; import com.genersoft.iot.vmp.conf.DynamicTask; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.jt1078.bean.*; import com.genersoft.iot.vmp.jt1078.cmd.JT1078Template; import com.genersoft.iot.vmp.jt1078.proc.request.J1205; import com.genersoft.iot.vmp.jt1078.proc.response.*; import com.genersoft.iot.vmp.jt1078.service.Ijt1078PlayService; import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; import com.genersoft.iot.vmp.media.bean.MediaInfo; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.event.hook.Hook; import com.genersoft.iot.vmp.media.event.hook.HookData; import com.genersoft.iot.vmp.media.event.hook.HookSubscribe; import com.genersoft.iot.vmp.media.event.hook.HookType; import com.genersoft.iot.vmp.media.event.media.MediaArrivalEvent; import com.genersoft.iot.vmp.media.event.media.MediaDepartureEvent; import com.genersoft.iot.vmp.media.event.media.MediaNotFoundEvent; import com.genersoft.iot.vmp.media.event.mediaServer.MediaSendRtpStoppedEvent; import com.genersoft.iot.vmp.media.service.IMediaServerService; import com.genersoft.iot.vmp.service.IReceiveRtpServerService; import com.genersoft.iot.vmp.service.ISendRtpServerService; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.service.bean.InviteErrorCode; import com.genersoft.iot.vmp.service.bean.RTPServerParam; import com.genersoft.iot.vmp.service.bean.SSRCInfo; import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.utils.MediaServerUtils; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.ObjectUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.event.EventListener; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.util.Assert; import javax.sip.message.Response; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @Service @Slf4j public class jt1078PlayServiceImpl implements Ijt1078PlayService { public static final String talkApp = "jt_talk"; @Autowired private ISendRtpServerService sendRtpServerService; @Autowired private Ijt1078Service jt1078Service; @Autowired private JT1078Template jt1078Template; @Autowired private RedisTemplate redisTemplate; @Autowired private HookSubscribe subscribe; @Autowired private IMediaServerService mediaServerService; @Autowired private IReceiveRtpServerService receiveRtpServerService; @Autowired private DynamicTask dynamicTask; @Autowired private UserSetting userSetting; /** * 流到来的处理 */ @Async("taskExecutor") @EventListener public void onApplicationEvent(MediaArrivalEvent event) { if (event.getApp().equals(talkApp) && event.getStream().endsWith("_talk")) { // 收到对JT讲的流 if (event.getStream().indexOf("_") <= 0) { log.info("[JT-对讲流到来] 流格式有误,stream应该为jt_[phoneNumber]_[channelId]_talk"); return; } String[] streamArray = event.getStream().split("_"); if (streamArray.length != 4) { log.info("[JT-对讲流到来] 流格式有误,stream应该为jt_[phoneNumber]_[channelId]_talk"); return; } String phoneNumber = streamArray[1]; String channelId = streamArray[2]; JTDevice device = jt1078Service.getDevice(phoneNumber); if (device == null) { log.info("[JT-对讲流到来] 未找到设备{}", phoneNumber); return; } sendTalk(device, Integer.valueOf(channelId), event.getMediaServer(), event.getApp(), event.getStream()); } } /** * 流离开的处理 */ @Async("taskExecutor") @EventListener public void onApplicationEvent(MediaDepartureEvent event) { } /** * 流未找到的处理 */ @Async("taskExecutor") @EventListener public void onApplicationEvent(MediaNotFoundEvent event) { if (!userSetting.getAutoApplyPlay()) { return; } JTMediaStreamType jtMediaStreamType = checkStreamFromJt(event.getStream()); if (jtMediaStreamType == null){ return; } String[] streamParamArray = event.getStream().split("_"); String phoneNumber = streamParamArray[1]; int channelId = Integer.parseInt(streamParamArray[2]); String params = event.getParams(); Map paramMap = MediaServerUtils.urlParamToMap(params); int type = 0; try { type = Integer.parseInt(paramMap.get("type")); }catch (NumberFormatException ignored) {} if (jtMediaStreamType.equals(JTMediaStreamType.PLAY)) { play(phoneNumber, channelId, 0, null); }else if (jtMediaStreamType.equals(JTMediaStreamType.PLAYBACK)) { String startTimeParam = DateUtil.jt1078Toyyyy_MM_dd_HH_mm_ss(streamParamArray[3]); String endTimeParam = DateUtil.jt1078Toyyyy_MM_dd_HH_mm_ss(streamParamArray[4]); int rate = 0; int playbackType = 0; int playbackSpeed = 0; try { rate = Integer.parseInt(paramMap.get("rate")); playbackType = Integer.parseInt(paramMap.get("playbackType")); playbackSpeed = Integer.parseInt(paramMap.get("playbackSpeed")); }catch (NumberFormatException ignored) {} playback(phoneNumber, channelId, startTimeParam, endTimeParam, type, rate, playbackType, playbackSpeed, null); } } /** * 校验流是否是属于部标的 */ @Override public JTMediaStreamType checkStreamFromJt(String stream) { if (!stream.startsWith("jt_")) { return null; } String[] streamParamArray = stream.split("_"); if (streamParamArray.length == 3) { return JTMediaStreamType.PLAY; }else if (streamParamArray.length == 5) { return JTMediaStreamType.PLAYBACK; }else if (streamParamArray.length == 4) { return JTMediaStreamType.TALK; }else { return null; } } private final Map>>> inviteErrorCallbackMap = new ConcurrentHashMap<>(); @Override public void play(String phoneNumber, Integer channelId, int type, CommonCallback> callback) { JTDevice device = jt1078Service.getDevice(phoneNumber); if (device == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "设备不存在"); } jt1078Template.checkTerminalStatus(phoneNumber); JTChannel channel = jt1078Service.getChannel(device.getId(), channelId); if (channel == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "通道不存在"); } play(device, channel, type, callback); } private void play(JTDevice device, JTChannel channel, int type, CommonCallback> callback) { String phoneNumber = device.getPhoneNumber(); int channelId = channel.getChannelId(); String stream = phoneNumber + "_" + channelId; // 检查流是否已经存在,存在则返回 String playKey = VideoManagerConstants.INVITE_INFO_1078_PLAY + phoneNumber + ":" + channelId; List>> errorCallbacks = inviteErrorCallbackMap.computeIfAbsent(playKey, k -> new ArrayList<>()); errorCallbacks.add(callback); StreamInfo streamInfo = (StreamInfo) redisTemplate.opsForValue().get(playKey); if (streamInfo != null) { MediaServer mediaServer = streamInfo.getMediaServer(); if (mediaServer != null) { // 查询流是否存在,不存在则删除缓存数据 MediaInfo mediaInfo = mediaServerService.getMediaInfo(mediaServer, MediaApp.JT1078, streamInfo.getStream()); if (mediaInfo != null) { log.info("[JT-点播] 点播已经存在,直接返回, phoneNumber: {}, channelId: {}", phoneNumber, channelId); for (CommonCallback> errorCallback : errorCallbacks) { errorCallback.run(new WVPResult<>(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), streamInfo)); } return; } } // 清理数据 redisTemplate.delete(playKey); } MediaServer mediaServer; if (org.springframework.util.ObjectUtils.isEmpty(device.getMediaServerId()) || "auto".equals(device.getMediaServerId())) { mediaServer = mediaServerService.getMediaServerForMinimumLoad(null); } else { mediaServer = mediaServerService.getOne(device.getMediaServerId()); } if (mediaServer == null) { for (CommonCallback> errorCallback : errorCallbacks) { errorCallback.run(new WVPResult<>(InviteErrorCode.FAIL.getCode(), "未找到可用的媒体节点", streamInfo)); } return; } // 开启收流端口 RTPServerParam rtpServerParam = new RTPServerParam(); rtpServerParam.setMediaServer(mediaServer); rtpServerParam.setApp(MediaApp.JT1078); rtpServerParam.setStreamId(stream); rtpServerParam.setPort(0); rtpServerParam.setTcpMode(1); // 1 表示tcp被动 rtpServerParam.setOnlyAuto(false); rtpServerParam.setDisableAudio(!channel.isHasAudio()); int port = receiveRtpServerService.openRTPServer(rtpServerParam, (code, msg, hookData) -> { if (code == InviteErrorCode.SUCCESS.getCode() && hookData != null ) { // hook响应 log.info("[JT-点播] 点播成功, 手机号: {}, 通道: {}", phoneNumber, channelId); // TODO 发送9105 实时音视频传输状态通知, 通知丢包率 StreamInfo info = onPublishHandler(mediaServer, hookData, phoneNumber, channelId); for (CommonCallback> errorCallback : errorCallbacks) { if (errorCallback == null) { continue; } errorCallback.run(new WVPResult<>(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), info)); } redisTemplate.opsForValue().set(playKey, info); // 截图 String path = "snap"; String fileName = phoneNumber + "_" + channelId + ".jpg"; // 请求截图 log.info("[请求截图]: " + fileName); mediaServerService.getSnap(mediaServer, MediaApp.JT1078, stream, 15, 1, path, fileName); }else { if (callback != null) { callback.run(WVPResult.fail(code, msg)); } log.info("[JT-点播] 超时, phoneNumber: {}, channelId: {}", phoneNumber, channelId); for (CommonCallback> errorCallback : errorCallbacks) { errorCallback.run(new WVPResult<>(InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getCode(), InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getMsg(), null)); } stopPlay(phoneNumber, channelId); } }); if (port <= 0) { stopPlay(phoneNumber, channelId); return; } log.info("[JT-点播] phoneNumber: {}, channelId: {},IP: {}, 端口: {}", phoneNumber, channelId, mediaServer.getSdpIp(), port); J9101 j9101 = new J9101(); j9101.setChannel(channelId); j9101.setIp(mediaServer.getSdpIp()); j9101.setRate(1); j9101.setTcpPort(port); j9101.setUdpPort(port); j9101.setType(type); jt1078Template.startLive(phoneNumber, j9101, 6); } public StreamInfo onPublishHandler(MediaServer mediaServerItem, HookData hookData, String phoneNumber, Integer channelId) { StreamInfo streamInfo = mediaServerService.getStreamInfoByAppAndStream(mediaServerItem, MediaApp.JT1078, hookData.getStream(), hookData.getMediaInfo(), null); streamInfo.setDeviceId(phoneNumber); streamInfo.setChannelId(channelId); return streamInfo; } @Override public void stopPlay(String phoneNumber, Integer channelId) { String playKey = VideoManagerConstants.INVITE_INFO_1078_PLAY + phoneNumber + ":" + channelId; dynamicTask.stop(playKey); // 清理回调 List>> generalCallbacks = inviteErrorCallbackMap.get(playKey); if (generalCallbacks != null && !generalCallbacks.isEmpty()) { for (CommonCallback> callback : generalCallbacks) { callback.run(new WVPResult<>(InviteErrorCode.ERROR_FOR_FINISH.getCode(), InviteErrorCode.ERROR_FOR_FINISH.getMsg(), null)); } } jt1078Template.checkTerminalStatus(phoneNumber); StreamInfo streamInfo = (StreamInfo) redisTemplate.opsForValue().get(playKey); // 发送停止命令 J9102 j9102 = new J9102(); j9102.setChannel(channelId); j9102.setCommand(0); j9102.setCloseType(0); j9102.setStreamType(1); jt1078Template.stopLive(phoneNumber, j9102, 6); log.info("[JT-停止点播] phoneNumber: {}, channelId: {}", phoneNumber, channelId); // 删除缓存数据 if (streamInfo != null) { // 关闭rtpServer receiveRtpServerService.closeRTPServer(streamInfo.getMediaServer(), streamInfo.getApp(), streamInfo.getStream()); redisTemplate.delete(playKey); } } @Override public void pausePlay(String phoneNumber, Integer channelId) { String playKey = VideoManagerConstants.INVITE_INFO_1078_PLAY + phoneNumber + ":" + channelId; dynamicTask.stop(playKey); StreamInfo streamInfo = (StreamInfo) redisTemplate.opsForValue().get(playKey); if (streamInfo == null) { log.info("[JT-暂停点播] 未找到点播信息 phoneNumber: {}, channelId: {}", phoneNumber, channelId); } log.info("[JT-暂停点播] phoneNumber: {}, channelId: {}", phoneNumber, channelId); // 发送暂停命令 J9102 j9102 = new J9102(); j9102.setChannel(channelId); j9102.setCommand(2); j9102.setCloseType(0); j9102.setStreamType(1); jt1078Template.stopLive(phoneNumber, j9102, 6); } @Override public void continueLivePlay(String phoneNumber, Integer channelId) { String playKey = VideoManagerConstants.INVITE_INFO_1078_PLAY + phoneNumber + ":" + channelId; dynamicTask.stop(playKey); StreamInfo streamInfo = (StreamInfo) redisTemplate.opsForValue().get(playKey); if (streamInfo == null) { log.info("[JT-继续点播] 未找到点播信息 phoneNumber: {}, channelId: {}", phoneNumber, channelId); } log.info("[JT-继续点播] phoneNumber: {}, channelId: {}", phoneNumber, channelId); // 发送暂停命令 J9102 j9102 = new J9102(); j9102.setChannel(channelId); j9102.setCommand(2); j9102.setCloseType(0); j9102.setStreamType(1); jt1078Template.stopLive(phoneNumber, j9102, 6); } @Override public List getRecordList(String phoneNumber, Integer channelId, String startTime, String endTime) { log.info("[JT-查询录像列表] phoneNumber: {}, channelId: {}, startTime: {}, endTime: {}" , phoneNumber, channelId, startTime, endTime); // 发送请求录像列表命令 J9205 j9205 = new J9205(); j9205.setChannelId(channelId); j9205.setStartTime(DateUtil.yyyy_MM_dd_HH_mm_ssTo1078(startTime)); j9205.setEndTime(DateUtil.yyyy_MM_dd_HH_mm_ssTo1078(endTime)); j9205.setMediaType(0); j9205.setStreamType(0); j9205.setStorageType(0); List JRecordItemList = (List) jt1078Template.queryBackTime(phoneNumber, j9205, 20); if (JRecordItemList == null || JRecordItemList.isEmpty()) { return null; } log.info("[JT-查询录像列表] phoneNumber: {}, channelId: {}, startTime: {}, endTime: {}, 结果: {}条" , phoneNumber, channelId, startTime, endTime, JRecordItemList.size()); return JRecordItemList; } @Override public void playback(String phoneNumber, Integer channelId, String startTime, String endTime, Integer type, Integer rate, Integer playbackType, Integer playbackSpeed, CommonCallback> callback) { JTDevice device = jt1078Service.getDevice(phoneNumber); if (device == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "设备不存在"); } jt1078Template.checkTerminalStatus(phoneNumber); JTChannel channel = jt1078Service.getChannel(device.getId(), channelId); if (channel == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "通道不存在"); } playback(device, channel, startTime, endTime, type, rate, playbackType, playbackSpeed, callback); } /** * 回放 * @param device 设备 * @param channel 通道 * @param startTime 开始时间 * @param endTime 结束时间 * @param type 音视频资源类型:0.音视频 1.音频 2.视频 3.视频或音视频 * @param rate 码流类型:0.所有码流 1.主码流 2.子码流(如果此通道只传输音频,此字段置0) * @param playbackType 回放方式:0.正常回放 1.快进回放 2.关键帧快退回放 3.关键帧播放 4.单帧上传 * @param playbackSpeed 快进或快退倍数:0.无效 1.1倍 2.2倍 3.4倍 4.8倍 5.16倍 (回放控制为1和2时,此字段内容有效,否则置0) * @param callback 结束回调 */ private void playback(JTDevice device, JTChannel channel, String startTime, String endTime, Integer type, Integer rate, Integer playbackType, Integer playbackSpeed, CommonCallback> callback) { String phoneNumber = device.getPhoneNumber(); Integer channelId = channel.getChannelId(); log.info("[JT-回放] 回放,设备:{}, 通道: {}, 开始时间: {}, 结束时间: {}, 音视频类型: {}, 码流类型: {}, " + "回放方式: {}, 快进或快退倍数: {}", phoneNumber, channelId, startTime, endTime, type, rate, playbackType, playbackSpeed); // 检查流是否已经存在,存在则返回 String playbackKey = VideoManagerConstants.INVITE_INFO_1078_PLAYBACK + phoneNumber + ":" + channelId; List>> errorCallbacks = inviteErrorCallbackMap.computeIfAbsent(playbackKey, k -> new ArrayList<>()); errorCallbacks.add(callback); String logInfo = String.format("phoneNumber:%s, channelId:%s, startTime:%s, endTime:%s", phoneNumber, channelId, startTime, endTime); StreamInfo streamInfo = (StreamInfo) redisTemplate.opsForValue().get(playbackKey); if (streamInfo != null) { receiveRtpServerService.closeRTPServer(streamInfo.getMediaServer(), streamInfo.getApp(), streamInfo.getStream()); // 清理数据 redisTemplate.delete(playbackKey); } String app = MediaApp.JT1078; String stream = String.format("%s_%s_%s_%s", phoneNumber, channelId, DateUtil.yyyy_MM_dd_HH_mm_ssToUrl(startTime), DateUtil.yyyy_MM_dd_HH_mm_ssToUrl(endTime)); MediaServer mediaServer; if (org.springframework.util.ObjectUtils.isEmpty(device.getMediaServerId()) || "auto".equals(device.getMediaServerId())) { mediaServer = mediaServerService.getMediaServerForMinimumLoad(null); } else { mediaServer = mediaServerService.getOne(device.getMediaServerId()); } if (mediaServer == null) { for (CommonCallback> errorCallback : errorCallbacks) { errorCallback.run(new WVPResult<>(InviteErrorCode.FAIL.getCode(), "未找到可用的媒体节点", streamInfo)); } return; } // 开启收流端口 RTPServerParam rtpServerParam = new RTPServerParam(); rtpServerParam.setMediaServer(mediaServer); rtpServerParam.setApp(MediaApp.JT1078); rtpServerParam.setStreamId(stream); rtpServerParam.setPort(0); rtpServerParam.setTcpMode(1); // 1 表示tcp被动 rtpServerParam.setOnlyAuto(false); rtpServerParam.setDisableAudio(!channel.isHasAudio()); int port = receiveRtpServerService.openRTPServer(rtpServerParam, (code, msg, hookData) -> { if (code == InviteErrorCode.SUCCESS.getCode() && hookData != null ) { // hook 响应 log.info("[JT-回放] 回放成功, logInfo: {}", logInfo); StreamInfo info = onPublishHandler(mediaServer, hookData, phoneNumber, channelId); for (CommonCallback> errorCallback : errorCallbacks) { if (errorCallback == null) { continue; } errorCallback.run(new WVPResult<>(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), info)); } redisTemplate.opsForValue().set(playbackKey, info); }else { log.info("[JT-回放] 回放超时, logInfo: {}", logInfo); for (CommonCallback> errorCallback : errorCallbacks) { errorCallback.run(new WVPResult<>(InviteErrorCode.ERROR_FOR_SIGNALLING_TIMEOUT.getCode(), InviteErrorCode.ERROR_FOR_SIGNALLING_TIMEOUT.getMsg(), null)); } receiveRtpServerService.closeRTPServer(mediaServer, app, stream); } }); log.info("[JT-回放] logInfo: {}, 端口: {}", logInfo, port); J9201 j9201 = new J9201(); j9201.setChannel(channelId); j9201.setIp(mediaServer.getSdpIp()); if (rate != null) { j9201.setRate(rate); } if (playbackType != null) { j9201.setPlaybackType(playbackType); } if (playbackSpeed != null) { j9201.setPlaybackSpeed(playbackSpeed); } j9201.setTcpPort(port); j9201.setUdpPort(port); j9201.setType(type); j9201.setStartTime(DateUtil.yyyy_MM_dd_HH_mm_ssTo1078(startTime)); j9201.setEndTime(DateUtil.yyyy_MM_dd_HH_mm_ssTo1078(endTime)); jt1078Template.startBackLive(phoneNumber, j9201, 20); } @Override public void playbackControl(String phoneNumber, Integer channelId, Integer command, Integer playbackSpeed, String time) { String playKey = VideoManagerConstants.INVITE_INFO_1078_PLAYBACK + phoneNumber + ":" + channelId; dynamicTask.stop(playKey); if (command == 2) { log.info("[JT-停止回放] phoneNumber: {}, channelId: {}, command: {}, playbackSpeed: {}, time: {}", phoneNumber, channelId, command, playbackSpeed, time); // 结束回放 StreamInfo streamInfo = (StreamInfo) redisTemplate.opsForValue().get(playKey); // 删除缓存数据 if (streamInfo != null) { // 关闭rtpServer receiveRtpServerService.closeRTPServer(streamInfo.getMediaServer(), streamInfo.getApp(), streamInfo.getStream()); } // 清理回调 List>> generalCallbacks = inviteErrorCallbackMap.get(playKey); if (generalCallbacks != null && !generalCallbacks.isEmpty()) { for (CommonCallback> callback : generalCallbacks) { if (callback == null) { continue; } callback.run(new WVPResult<>(InviteErrorCode.ERROR_FOR_FINISH.getCode(), InviteErrorCode.ERROR_FOR_FINISH.getMsg(), null)); } } }else { log.info("[JT-回放控制] phoneNumber: {}, channelId: {}, command: {}, playbackSpeed: {}, time: {}", phoneNumber, channelId, command, playbackSpeed, time); } // 发送停止命令 J9202 j9202 = new J9202(); j9202.setChannel(channelId); j9202.setPlaybackType(command); if (playbackSpeed != null) { j9202.setPlaybackSpeed(playbackSpeed); } if (!ObjectUtils.isEmpty(time)) { j9202.setPlaybackTime(DateUtil.yyyy_MM_dd_HH_mm_ssTo1078(time)); } jt1078Template.controlBackLive(phoneNumber, j9202, 4); } @Override public void stopPlayback(String phoneNumber, Integer channelId) { playbackControl(phoneNumber, channelId, 2, null, null); } /** * 监听发流停止 */ @EventListener public void onApplicationEvent(MediaSendRtpStoppedEvent event) { List sendRtpInfos = sendRtpServerService.queryByStream(event.getStream()); if (sendRtpInfos.isEmpty()) { return; } for (SendRtpInfo sendRtpInfo : sendRtpInfos) { if (!sendRtpInfo.isOnlyAudio() || ObjectUtils.isEmpty(sendRtpInfo.getChannelId())) { continue; } if (!sendRtpInfo.getSsrc().contains("_")) { continue; } sendRtpServerService.delete(sendRtpInfo); String playKey = VideoManagerConstants.INVITE_INFO_1078_TALK + sendRtpInfo.getApp() + ":" + sendRtpInfo.getStream(); redisTemplate.delete(playKey); } } @Override public StreamInfo startTalk(String phoneNumber, Integer channelId) { // 检查流是否已经存在,存在则返回 String playKey = VideoManagerConstants.INVITE_INFO_1078_TALK + phoneNumber + ":" + channelId; StreamInfo streamInfo = (StreamInfo) redisTemplate.opsForValue().get(playKey); if (streamInfo != null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "对讲进行中"); } JTDevice device = jt1078Service.getDevice(phoneNumber); Assert.notNull(device, "部标设备不存在"); String stream = "jt_" + phoneNumber + "_" + channelId + "_talk"; MediaServer mediaServer; if (org.springframework.util.ObjectUtils.isEmpty(device.getMediaServerId()) || "auto".equals(device.getMediaServerId())) { mediaServer = mediaServerService.getMediaServerForMinimumLoad(null); } else { mediaServer = mediaServerService.getOne(device.getMediaServerId()); } // 检查待发送的流是否存在, MediaInfo mediaInfo = mediaServerService.getMediaInfo(mediaServer, talkApp, stream); Assert.isNull(mediaInfo, "对讲已经存在"); return mediaServerService.getStreamInfoByAppAndStream(mediaServer, talkApp, stream, null, null, null, false); } private void sendTalk(JTDevice device, Integer channelId, MediaServer mediaServer, String app, String stream) { // 检查待发送的流是否存在, MediaInfo mediaInfo = mediaServerService.getMediaInfo(mediaServer, app, stream); if (mediaInfo == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), app + "/" + stream + "流不存在"); } String phoneNumber = device.getPhoneNumber(); // 开启收流端口, zlm发送1078的rtp流需要将ssrc字段设置为 imei_channel格式 String ssrc = device.getPhoneNumber() + "_" + channelId; SendRtpInfo sendRtpInfo = sendRtpServerService.createSendRtpInfo(mediaServer, null, null, ssrc, phoneNumber, talkApp, stream, channelId, true, false); sendRtpInfo.setTcpActive(true); sendRtpInfo.setUsePs(false); sendRtpInfo.setOnlyAudio(true); sendRtpInfo.setReceiveStream(stream + "_talk"); // 设置hook监听 Hook hook = Hook.getInstance(HookType.on_media_arrival, MediaApp.JT1078, sendRtpInfo.getReceiveStream(), mediaServer.getId()); subscribe.addSubscribe(hook, (hookData) -> { log.info("[JT-对讲] 对讲连接建立, phoneNumber: {}, channelId: {}", phoneNumber, channelId); subscribe.removeSubscribe(hook); // 存储发流信息 sendRtpServerService.update(sendRtpInfo); }); Hook hookForDeparture = Hook.getInstance(HookType.on_media_departure, app, stream, mediaServer.getId()); subscribe.addSubscribe(hookForDeparture, (hookData) -> { log.info("[JT-对讲] 对讲时源流注销, app: {}. stream: {}, phoneNumber: {}, channelId: {}", app, stream, phoneNumber, channelId); stopTalk(phoneNumber, channelId); }); Integer localPort = mediaServerService.startSendRtpPassive(mediaServer, sendRtpInfo, userSetting.getPlayTimeout()); log.info("[JT-对讲] phoneNumber: {}, channelId: {}, 收发端口: {}, app: {}, stream: {}", phoneNumber, channelId, localPort, app, stream); J9101 j9101 = new J9101(); j9101.setChannel(channelId); j9101.setIp(mediaServer.getSdpIp()); j9101.setRate(1); j9101.setTcpPort(sendRtpInfo.getLocalPort()); j9101.setUdpPort(sendRtpInfo.getLocalPort()); j9101.setType(4); jt1078Template.startLive(phoneNumber, j9101, 6); log.info("[JT-对讲] 对讲消息下发成功, phoneNumber: {}, channelId: {}", phoneNumber, channelId); // 存储发流信息 // sendRtpServerService.update(sendRtpInfo); } @Override public void stopTalk(String phoneNumber, Integer channelId) { String playKey = VideoManagerConstants.INVITE_INFO_1078_TALK + phoneNumber + ":" + channelId; dynamicTask.stop(playKey); StreamInfo streamInfo = (StreamInfo) redisTemplate.opsForValue().get(playKey); // 发送停止命令 J9102 j9102 = new J9102(); j9102.setChannel(channelId); j9102.setCommand(4); j9102.setCloseType(0); j9102.setStreamType(1); jt1078Template.stopLive(phoneNumber, j9102, 6); log.info("[JT-停止对讲] phoneNumber: {}, channelId: {}", phoneNumber, channelId); // 删除缓存数据 if (streamInfo != null) { redisTemplate.delete(playKey); // 关闭 rtpServer receiveRtpServerService.closeRTPServer(streamInfo.getMediaServer(), streamInfo.getApp(), streamInfo.getStream()); } // 清理回调 List>> generalCallbacks = inviteErrorCallbackMap.get(playKey); if (generalCallbacks != null && !generalCallbacks.isEmpty()) { for (CommonCallback> callback : generalCallbacks) { callback.run(new WVPResult<>(InviteErrorCode.ERROR_FOR_FINISH.getCode(), InviteErrorCode.ERROR_FOR_FINISH.getMsg(), null)); } } } @Override public void start(Integer channelId, Boolean record, ErrorCallback callback) { JTChannel channel = jt1078Service.getChannelByDbId(channelId); Assert.notNull(channel, "通道不存在"); JTDevice device = jt1078Service.getDeviceById(channel.getTerminalDbId()); Assert.notNull(device, "设备不存在"); jt1078Template.checkTerminalStatus(device.getPhoneNumber()); play(device, channel, 0, result -> callback.run(result.getCode(), result.getMsg(), result.getData())); } @Override public void stop(Integer channelId) { JTChannel channel = jt1078Service.getChannelByDbId(channelId); Assert.notNull(channel, "通道不存在"); JTDevice device = jt1078Service.getDeviceById(channel.getTerminalDbId()); Assert.notNull(device, "设备不存在"); stopPlay(device.getPhoneNumber(), channel.getChannelId()); } @Override public void playBack(Integer channelId, Long startTime, Long stopTime, ErrorCallback callback) { if (startTime == null || stopTime == null) { throw new PlayException(Response.BAD_REQUEST, "bad request"); } JTChannel channel = jt1078Service.getChannelByDbId(channelId); Assert.notNull(channel, "通道不存在"); JTDevice device = jt1078Service.getDeviceById(channel.getTerminalDbId()); Assert.notNull(device, "设备不存在"); jt1078Template.checkTerminalStatus(device.getPhoneNumber()); String startTimeStr = DateUtil.timestampTo_yyyy_MM_dd_HH_mm_ss(startTime); String stopTimeStr = DateUtil.timestampTo_yyyy_MM_dd_HH_mm_ss(stopTime); playback(device, channel, startTimeStr, stopTimeStr, 0, 1, 0, 0, result -> callback.run(result.getCode(), result.getMsg(), result.getData())); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/service/impl/jt1078ServiceImpl.java ================================================ package com.genersoft.iot.vmp.jt1078.service.impl; import com.genersoft.iot.vmp.common.CommonCallback; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.common.VideoManagerConstants; import com.genersoft.iot.vmp.conf.DynamicTask; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.conf.ftpServer.FtpFileSystemFactory; import com.genersoft.iot.vmp.conf.ftpServer.FtpSetting; import com.genersoft.iot.vmp.conf.ftpServer.UserManager; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import com.genersoft.iot.vmp.gb28181.service.IGbChannelService; import com.genersoft.iot.vmp.jt1078.bean.*; import com.genersoft.iot.vmp.jt1078.bean.common.ConfigAttribute; import com.genersoft.iot.vmp.jt1078.cmd.JT1078Template; import com.genersoft.iot.vmp.jt1078.dao.JTChannelMapper; import com.genersoft.iot.vmp.jt1078.dao.JTTerminalMapper; import com.genersoft.iot.vmp.jt1078.event.DeviceUpdateEvent; import com.genersoft.iot.vmp.jt1078.event.JTPositionEvent; import com.genersoft.iot.vmp.jt1078.proc.response.*; import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; import com.genersoft.iot.vmp.jt1078.session.FtpDownloadManager; import com.genersoft.iot.vmp.jt1078.session.Session; import com.genersoft.iot.vmp.jt1078.session.SessionManager; import com.genersoft.iot.vmp.media.event.media.MediaArrivalEvent; import com.genersoft.iot.vmp.media.event.media.MediaDepartureEvent; import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import jakarta.annotation.PostConstruct; import jakarta.servlet.ServletOutputStream; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.ObjectUtils; import org.apache.ftpserver.usermanager.impl.BaseUser; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.event.EventListener; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; import java.io.IOException; import java.io.OutputStream; import java.lang.reflect.Field; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.List; import java.util.UUID; @Service @Slf4j public class jt1078ServiceImpl implements Ijt1078Service { @Autowired private JTTerminalMapper jtDeviceMapper; @Autowired private JTChannelMapper jtChannelMapper; @Autowired private JT1078Template jt1078Template; @Autowired private RedisTemplate redisTemplate; @Autowired private IGbChannelService channelService; @Autowired private DynamicTask dynamicTask; @Autowired private UserSetting userSetting; @Autowired private FtpSetting ftpSetting; @Autowired private UserManager ftpUserManager; @Autowired private FtpFileSystemFactory fileSystemFactory; @Autowired private FtpDownloadManager downloadManager; // 服务启动后五分钟内没有尽量连接的设备设置为离线 @PostConstruct public void init(){ // 检查session与在线终端是是否对应 不对应则设置终端离线 List deviceList = jtDeviceMapper.getDeviceList(null, true); if (deviceList.isEmpty()) { return; } for (JTDevice device : deviceList) { Session session = SessionManager.INSTANCE.get(device.getPhoneNumber()); if (session == null) { device.setStatus(false); // 通道发送状态变化通知 List jtChannels = jtChannelMapper.selectAll(device.getId(), null); List channelList = new ArrayList<>(); for (JTChannel jtChannel : jtChannels) { if (jtChannel.getGbId() > 0) { jtChannel.setGbStatus("OFF"); channelList.add(jtChannel); } } channelService.updateStatus(channelList); updateDevice(device); } } } /** * 流到来的处理 */ @Async("taskExecutor") @org.springframework.context.event.EventListener public void onApplicationEvent(MediaArrivalEvent event) { } /** * 流离开的处理 */ @Async("taskExecutor") @EventListener public void onApplicationEvent(MediaDepartureEvent event) { } /** * 设备更新的通知 */ @Async("taskExecutor") @EventListener public void onApplicationEvent(DeviceUpdateEvent event) { JTDevice device = event.getDevice(); if (device == null || device.getPhoneNumber() == null) { return; } JTDevice deviceInDb = getDevice(event.getDevice().getPhoneNumber()); if (deviceInDb.isStatus() != device.isStatus()) { // 通道发送状态变化通知 List jtChannels = jtChannelMapper.selectAll(deviceInDb.getId(), null); List channelList = new ArrayList<>(); for (JTChannel jtChannel : jtChannels) { if (jtChannel.getGbId() > 0) { jtChannel.setGbStatus("OFF"); channelList.add(jtChannel); } } channelService.updateStatus(channelList); } updateDevice(event.getDevice()); } /** * 位置更新的通知 */ @Async("taskExecutor") @EventListener public void onApplicationEvent(JTPositionEvent event) { if (event.getPhoneNumber() == null || event.getPositionInfo() == null || event.getPositionInfo().getLongitude() == null || event.getPositionInfo().getLatitude() == null) { return; } JTDevice device = getDevice(event.getPhoneNumber()); if (device == null) { return; } device.setLongitude(event.getPositionInfo().getLongitude()); device.setLatitude(event.getPositionInfo().getLatitude()); updateDevice(device); // 通道发送状态变化通知 List jtChannels = jtChannelMapper.selectAll(device.getId(), null); List channelList = new ArrayList<>(); for (JTChannel jtChannel : jtChannels) { if (jtChannel.getGbId() > 0) { jtChannel.setGbLongitude(event.getPositionInfo().getLongitude()); jtChannel.setGbLatitude(event.getPositionInfo().getLatitude()); if (event.getPositionInfo().getAltitude() != null) { jtChannel.setGpsAltitude((double) event.getPositionInfo().getAltitude()); }else { jtChannel.setGpsAltitude(0d); } if (event.getPositionInfo().getDirection() != null) { jtChannel.setGpsDirection((double) event.getPositionInfo().getDirection()); }else { jtChannel.setGpsDirection(0d); } if (event.getPositionInfo().getTime() != null) { jtChannel.setGpsTime(event.getPositionInfo().getTime()); }else { jtChannel.setGpsTime(DateUtil.getNow()); } channelList.add(jtChannel); } } channelService.updateGPS(channelList); } /** * 校验流是否是属于部标的 */ @Override public JTMediaStreamType checkStreamFromJt(String stream) { String[] streamParamArray = stream.split("_"); if (streamParamArray.length == 2) { return JTMediaStreamType.PLAY; }else if (streamParamArray.length == 4) { return JTMediaStreamType.PLAYBACK; }else if (streamParamArray.length == 5) { return JTMediaStreamType.TALK; }else { return null; } } @Override public JTDevice getDevice(String phoneNumber) { return jtDeviceMapper.getDevice(phoneNumber); } @Override public JTChannel getChannel(Integer terminalDbId, Integer channelId) { return jtChannelMapper.selectChannelByChannelId(terminalDbId, channelId); } @Override public void updateDevice(JTDevice device) { device.setUpdateTime(DateUtil.getNow()); jtDeviceMapper.updateDevice(device); } @Override public PageInfo getDeviceList(int page, int count, String query, Boolean online) { PageHelper.startPage(page, count); if (query != null) { query = query.replaceAll("/", "//") .replaceAll("%", "/%") .replaceAll("_", "/_"); } List all = jtDeviceMapper.getDeviceList(query, online); return new PageInfo<>(all); } @Override public void addDevice(JTDevice device) { JTDevice deviceInDb = jtDeviceMapper.getDevice(device.getPhoneNumber()); if (deviceInDb != null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "设备" + device.getPhoneNumber() + "已存在"); } device.setCreateTime(DateUtil.getNow()); device.setUpdateTime(DateUtil.getNow()); jtDeviceMapper.addDevice(device); } @Override public void deleteDeviceByPhoneNumber(String phoneNumber) { jtDeviceMapper.deleteDeviceByPhoneNumber(phoneNumber); } @Override public void updateDeviceStatus(boolean connected, String phoneNumber) { jtDeviceMapper.updateDeviceStatus(connected, phoneNumber); } @Override public void recordDownload(String phoneNumber, Integer channelId, String startTime, String endTime, Integer alarmSign, Integer mediaType, Integer streamType, Integer storageType, OutputStream outputStream, CommonCallback> fileCallback) { String filePath = UUID.randomUUID().toString(); fileSystemFactory.addOutputStream(filePath, outputStream); dynamicTask.startDelay(filePath, ()->{ fileSystemFactory.removeOutputStream(filePath); }, 2*60*60*1000); Session session = SessionManager.INSTANCE.get(phoneNumber); Assert.notNull(session, "连接不存在"); InetSocketAddress socketAddress = session.getLoadAddress(); String hostName = socketAddress.getHostName(); BaseUser randomUser = ftpUserManager.getRandomUser(); log.info("[JT-录像] 下载,设备:{}, 通道: {}, 开始时间: {}, 结束时间: {} 上传IP: {} 等待上传文件路径: {} 用户名: {}, 密码: {} ", phoneNumber, channelId, startTime, endTime, hostName, filePath, randomUser.getName(), randomUser.getPassword()); // 发送停止命令 J9206 j92026 = new J9206(); j92026.setChannelId(channelId); j92026.setStartTime(DateUtil.yyyy_MM_dd_HH_mm_ssTo1078(startTime)); j92026.setEndTime(DateUtil.yyyy_MM_dd_HH_mm_ssTo1078(endTime)); j92026.setServerIp(hostName); j92026.setPort(ftpSetting.getPort()); j92026.setUsername(randomUser.getName()); j92026.setPassword(randomUser.getPassword()); j92026.setPath(filePath); if (mediaType != null) { j92026.setMediaType(mediaType); } if (streamType != null) { j92026.setStreamType(streamType); } if (storageType != null) { j92026.setStorageType(storageType); } if (alarmSign != null) { j92026.setAlarmSign(alarmSign); } jt1078Template.fileUpload(phoneNumber, j92026, 7200); } @Override public void ptzControl(String phoneNumber, Integer channelId, String command, int speed) { // 发送停止命令 switch (command) { case "left": case "right": case "up": case "down": case "stop": J9301 j9301 = new J9301(); j9301.setChannel(channelId); switch (command) { case "left": j9301.setDirection(3); j9301.setSpeed(speed); break; case "right": j9301.setDirection(4); j9301.setSpeed(speed); break; case "up": j9301.setDirection(1); j9301.setSpeed(speed); break; case "down": j9301.setDirection(2); j9301.setSpeed(speed); break; case "stop": j9301.setDirection(0); j9301.setSpeed(0); break; } jt1078Template.ptzRotate(phoneNumber, j9301, 6); break; case "zoomin": case "zoomout": J9306 j9306 = new J9306(); j9306.setChannel(channelId); if (command.equals("zoomin")) { j9306.setZoom(0); } else { j9306.setZoom(1); } jt1078Template.ptzZoom(phoneNumber, j9306, 6); break; case "irisin": case "irisout": J9303 j9303 = new J9303(); j9303.setChannel(channelId); if (command.equals("irisin")) { j9303.setIris(0); } else { j9303.setIris(1); } jt1078Template.ptzIris(phoneNumber, j9303, 6); break; case "focusnear": case "focusfar": J9302 j9302 = new J9302(); j9302.setChannel(channelId); if (command.equals("focusfar")) { j9302.setFocalDirection(0); } else { j9302.setFocalDirection(1); } jt1078Template.ptzFocal(phoneNumber, j9302, 6); break; } } @Override public void supplementaryLight(String phoneNumber, Integer channelId, String command) { J9305 j9305 = new J9305(); j9305.setChannel(channelId); if (command.equalsIgnoreCase("on")) { j9305.setOn(1); } else { j9305.setOn(0); } jt1078Template.ptzSupplementaryLight(phoneNumber, j9305, 6); } @Override public void wiper(String phoneNumber, Integer channelId, String command) { J9304 j9304 = new J9304(); j9304.setChannel(channelId); if (command.equalsIgnoreCase("on")) { j9304.setOn(1); } else { j9304.setOn(0); } jt1078Template.ptzWiper(phoneNumber, j9304, 6); } @Override public JTDeviceConfig queryConfig(String phoneNumber, String[] params) { if (phoneNumber == null) { return null; } if (params == null || params.length == 0) { J8104 j8104 = new J8104(); return (JTDeviceConfig) jt1078Template.getDeviceConfig(phoneNumber, j8104, 20); } else { long[] paramBytes = new long[params.length]; for (int i = 0; i < params.length; i++) { try { Field field = JTDeviceConfig.class.getDeclaredField(params[i]); if (field.isAnnotationPresent(ConfigAttribute.class)) { ConfigAttribute configAttribute = field.getAnnotation(ConfigAttribute.class); if (configAttribute == null) { log.warn("[查询设备配置] 获取 ConfigAttribute 失败"); continue; } paramBytes[i] = configAttribute.id(); } } catch (NoSuchFieldException e) { throw new RuntimeException(e); } } J8106 j8106 = new J8106(); j8106.setParams(paramBytes); return (JTDeviceConfig) jt1078Template.getDeviceSpecifyConfig(phoneNumber, j8106, 20); } } @Override public void setConfig(String phoneNumber, JTDeviceConfig config) { J8103 j8103 = new J8103(); j8103.setConfig(config); jt1078Template.setDeviceSpecifyConfig(phoneNumber, j8103, 6); } @Override public void connectionControl(String phoneNumber, JTDeviceConnectionControl control) { J8105 j8105 = new J8105(); j8105.setConnectionControl(control); jt1078Template.deviceControl(phoneNumber, j8105, 6); } @Override public void resetControl(String phoneNumber) { J8105 j8105 = new J8105(); j8105.setReset(true); jt1078Template.deviceControl(phoneNumber, j8105, 6); } @Override public void factoryResetControl(String phoneNumber) { J8105 j8105 = new J8105(); j8105.setFactoryReset(true); jt1078Template.deviceControl(phoneNumber, j8105, 6); } @Override public JTDeviceAttribute attribute(String phoneNumber) { J8107 j8107 = new J8107(); return (JTDeviceAttribute) jt1078Template.deviceAttribute(phoneNumber, j8107, 20); } @Override public JTPositionBaseInfo queryPositionInfo(String phoneNumber) { J8201 j8201 = new J8201(); return (JTPositionBaseInfo) jt1078Template.queryPositionInfo(phoneNumber, j8201, 20); } @Override public void tempPositionTrackingControl(String phoneNumber, Integer timeInterval, Long validityPeriod) { J8202 j8202 = new J8202(); j8202.setTimeInterval(timeInterval); j8202.setValidityPeriod(validityPeriod); jt1078Template.tempPositionTrackingControl(phoneNumber, j8202, 20); } @Override public void confirmationAlarmMessage(String phoneNumber, int alarmPackageNo, JTConfirmationAlarmMessageType alarmMessageType) { J8203 j8203 = new J8203(); j8203.setAlarmMessageType(alarmMessageType); j8203.setAlarmPackageNo(alarmPackageNo); jt1078Template.confirmationAlarmMessage(phoneNumber, j8203, 6); } @Override public int linkDetection(String phoneNumber) { J8204 j8204 = new J8204(); Object result = jt1078Template.linkDetection(phoneNumber, j8204, 6); if (result == null) { return 1; }else { return (int) result; } } @Override public int textMessage(String phoneNumber, JTTextSign sign, int textType, String content) { J8300 j8300 = new J8300(); j8300.setSign(sign); j8300.setTextType(textType); j8300.setContent(content); return (int) jt1078Template.textMessage(phoneNumber, j8300, 6); } @Override public int telephoneCallback(String phoneNumber, Integer sign, String destPhoneNumber) { J8400 j8400 = new J8400(); j8400.setSign(sign); j8400.setPhoneNumber(destPhoneNumber); return (int) jt1078Template.telephoneCallback(phoneNumber, j8400, 6); } @Override public int setPhoneBook(String phoneNumber, int type, List phoneBookContactList) { J8401 j8401 = new J8401(); j8401.setType(type); if (phoneBookContactList != null) { j8401.setPhoneBookContactList(phoneBookContactList); } return (int) jt1078Template.setPhoneBook(phoneNumber, j8401, 6); } @Override public JTPositionBaseInfo controlDoor(String phoneNumber, Boolean open) { J8500 j8500 = new J8500(); JTVehicleControl jtVehicleControl = new JTVehicleControl(); jtVehicleControl.setControlCarDoor(open ? 1 : 0); j8500.setVehicleControl(jtVehicleControl); return (JTPositionBaseInfo) jt1078Template.vehicleControl(phoneNumber, j8500, 20); } @Override public int setAreaForCircle(int attribute, String phoneNumber, List circleAreaList) { J8600 j8600 = new J8600(); j8600.setAttribute(attribute); j8600.setCircleAreaList(circleAreaList); return (int) jt1078Template.setAreaForCircle(phoneNumber, j8600, 20); } @Override public int deleteAreaForCircle(String phoneNumber, List ids) { J8601 j8601 = new J8601(); j8601.setIdList(ids); return (int) jt1078Template.deleteAreaForCircle(phoneNumber, j8601, 20); } @Override public List queryAreaForCircle(String phoneNumber, List ids) { J8608 j8608 = new J8608(); j8608.setType(1); j8608.setIdList(ids); return (List) jt1078Template.queryAreaOrRoute(phoneNumber, j8608, 20); } @Override public int setAreaForRectangle(int attribute, String phoneNumber, List rectangleAreas) { J8602 j8602 = new J8602(); j8602.setAttribute(attribute); j8602.setRectangleAreas(rectangleAreas); return (int) jt1078Template.setAreaForRectangle(phoneNumber, j8602, 20); } @Override public int deleteAreaForRectangle(String phoneNumber, List ids) { J8603 j8603 = new J8603(); j8603.setIdList(ids); return (int) jt1078Template.deleteAreaForRectangle(phoneNumber, j8603, 20); } @Override public List queryAreaForRectangle(String phoneNumber, List ids) { J8608 j8608 = new J8608(); j8608.setType(2); j8608.setIdList(ids); return (List) jt1078Template.queryAreaOrRoute(phoneNumber, j8608, 20); } @Override public int setAreaForPolygon(String phoneNumber, JTPolygonArea polygonArea) { J8604 j8604 = new J8604(); j8604.setPolygonArea(polygonArea); return (int) jt1078Template.setAreaForPolygon(phoneNumber, j8604, 20); } @Override public int deleteAreaForPolygon(String phoneNumber, List ids) { J8605 j8605 = new J8605(); j8605.setIdList(ids); return (int) jt1078Template.deleteAreaForPolygon(phoneNumber, j8605, 20); } @Override public List queryAreaForPolygon(String phoneNumber, List ids) { J8608 j8608 = new J8608(); j8608.setType(3); j8608.setIdList(ids); return (List) jt1078Template.queryAreaOrRoute(phoneNumber, j8608, 20); } @Override public int setRoute(String phoneNumber, JTRoute route) { J8606 j8606 = new J8606(); j8606.setRoute(route); return (int) jt1078Template.setRoute(phoneNumber, j8606, 20); } @Override public int deleteRoute(String phoneNumber, List ids) { J8607 j8607 = new J8607(); j8607.setIdList(ids); return (int) jt1078Template.deleteRoute(phoneNumber, j8607, 20); } @Override public List queryRoute(String phoneNumber, List ids) { J8608 j8608 = new J8608(); j8608.setType(4); j8608.setIdList(ids); return (List) jt1078Template.queryAreaOrRoute(phoneNumber, j8608, 20); } @Override public JTDriverInformation queryDriverInformation(String phoneNumber) { J8702 j8702 = new J8702(); return (JTDriverInformation) jt1078Template.queryDriverInformation(phoneNumber, j8702, 20); } @Override public List shooting(String phoneNumber, JTShootingCommand shootingCommand) { J8801 j8801 = new J8801(); j8801.setCommand(shootingCommand); return (List) jt1078Template.shooting(phoneNumber, j8801, 300); } @Override public List queryMediaData(String phoneNumber, JTQueryMediaDataCommand queryMediaDataCommand) { J8802 j8802 = new J8802(); j8802.setCommand(queryMediaDataCommand); return (List) jt1078Template.queryMediaData(phoneNumber, j8802, 300); } @Override public void uploadMediaData(String phoneNumber, JTQueryMediaDataCommand queryMediaDataCommand) { J8803 j8803 = new J8803(); j8803.setCommand(queryMediaDataCommand); jt1078Template.uploadMediaData(phoneNumber, j8803, 10); } @Override public void record(String phoneNumber, int command, Integer time, Integer save, Integer samplingRate) { J8804 j8804 = new J8804(); j8804.setCommond(command); j8804.setDuration(time); j8804.setSave(save); j8804.setSamplingRate(samplingRate); jt1078Template.record(phoneNumber, j8804, 10); } @Override public void uploadMediaDataForSingle(String phoneNumber, Long mediaId, Integer delete) { J8805 j8805 = new J8805(); j8805.setMediaId(mediaId); j8805.setDelete(delete); jt1078Template.uploadMediaDataForSingle(phoneNumber, j8805, 10); } @Override public JTMediaAttribute queryMediaAttribute(String phoneNumber) { J9003 j9003 = new J9003(); return (JTMediaAttribute) jt1078Template.queryMediaAttribute(phoneNumber, j9003, 300); } @Override public void changeStreamType(String phoneNumber, Integer channelId, Integer streamType) { String playKey = VideoManagerConstants.INVITE_INFO_1078_PLAY + phoneNumber + ":" + channelId; dynamicTask.stop(playKey); StreamInfo streamInfo = (StreamInfo) redisTemplate.opsForValue().get(playKey); if (streamInfo == null) { log.info("[JT-切换码流类型] 未找到点播信息 phoneNumber: {}, channelId: {}, streamType: {}", phoneNumber, channelId, streamType); } log.info("[JT-切换码流类型] phoneNumber: {}, channelId: {}, streamType: {}", phoneNumber, channelId, streamType); // 发送暂停命令 J9102 j9102 = new J9102(); j9102.setChannel(channelId); j9102.setCommand(1); j9102.setCloseType(0); j9102.setStreamType(streamType); jt1078Template.stopLive(phoneNumber, j9102, 6); } @Override public PageInfo getChannelList(int page, int count, int deviceId, String query) { JTDevice device = getDeviceById(deviceId); if (device == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "设备不存在"); } PageHelper.startPage(page, count); List all = jtChannelMapper.selectAll(deviceId, query); PageInfo jtChannelPageInfo = new PageInfo<>(all); for (JTChannel jtChannel : jtChannelPageInfo.getList()) { String playKey = VideoManagerConstants.INVITE_INFO_1078_PLAY + device.getPhoneNumber() + ":" + jtChannel.getChannelId(); StreamInfo streamInfo = (StreamInfo) redisTemplate.opsForValue().get(playKey); if (streamInfo != null) { jtChannel.setStream(streamInfo.getStream()); } } return new PageInfo<>(all); } @Override @Transactional public void updateChannel(JTChannel channel) { channel.setUpdateTime(DateUtil.getNow()); jtChannelMapper.update(channel); if (!ObjectUtils.isEmpty(channel.getGbDeviceId())) { if (channel.getGbId() > 0) { channelService.update(channel.buildCommonGBChannel()); }else { channelService.add(channel.buildCommonGBChannel()); } } } @Override @Transactional public void addChannel(JTChannel channel) { JTChannel channelInDb = jtChannelMapper.selectChannelByChannelId(channel.getTerminalDbId(), channel.getChannelId()); if (channelInDb != null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "通道已存在"); } channel.setCreateTime(DateUtil.getNow()); channel.setUpdateTime(DateUtil.getNow()); jtChannelMapper.add(channel); if (!ObjectUtils.isEmpty(channel.getGbDeviceId())) { channelService.add(channel.buildCommonGBChannel()); } } @Override @Transactional public void deleteChannelById(Integer id) { JTChannel jtChannel = jtChannelMapper.selectChannelById(id); if (jtChannel == null) { return; } if (jtChannel.getGbId() > 0) { channelService.delete(jtChannel.getGbId()); } jtChannelMapper.delete(id); } @Override public JTDevice getDeviceById(Integer deviceId) { return jtDeviceMapper.getDeviceById(deviceId); } @Override public void updateDevicePosition(String phoneNumber, Double longitude, Double latitude) { JTDevice device = new JTDevice(); device.setPhoneNumber(phoneNumber); device.setLongitude(longitude); device.setLatitude(latitude); device.setUpdateTime(DateUtil.getNow()); String key = VideoManagerConstants.INVITE_INFO_1078_POSITION + userSetting.getServerId(); redisTemplate.opsForList().leftPush(key, device); } @Scheduled(fixedDelay = 1000) public void positionTask(){ String key = VideoManagerConstants.INVITE_INFO_1078_POSITION + userSetting.getServerId(); int count = 1000; List devices = new ArrayList<>(count); Long size = redisTemplate.opsForList().size(key); if (size == null || size == 0) { return; } long readCount = Math.min(count, size); for (long i = 0L; i < readCount; i++) { devices.add((JTDevice)redisTemplate.opsForList().rightPop(key)); } jtDeviceMapper.batchUpdateDevicePosition(devices); } @Override public JTChannel getChannelByDbId(Integer id) { return jtChannelMapper.selectChannelById(id); } @Override public String getRecordTempUrl(String phoneNumber, Integer channelId, String startTime, String endTime, Integer alarmSign, Integer mediaType, Integer streamType, Integer storageType) { String filePath = UUID.randomUUID().toString(); Session session = SessionManager.INSTANCE.get(phoneNumber); Assert.notNull(session, "连接不存在"); InetSocketAddress socketAddress = session.getLoadAddress(); String hostName = socketAddress.getHostName(); BaseUser randomUser = ftpUserManager.getRandomUser(); log.info("[JT-录像] 下载,设备:{}, 通道: {}, 开始时间: {}, 结束时间: {} 上传IP: {} 等待上传文件路径: {} 用户名: {}, 密码: {} ", phoneNumber, channelId, startTime, endTime, hostName, filePath, randomUser.getName(), randomUser.getPassword()); // 文件上传指令 J9206 j9206 = new J9206(); j9206.setChannelId(channelId); j9206.setStartTime(DateUtil.yyyy_MM_dd_HH_mm_ssTo1078(startTime)); j9206.setEndTime(DateUtil.yyyy_MM_dd_HH_mm_ssTo1078(endTime)); j9206.setServerIp(hostName); j9206.setPort(ftpSetting.getPort()); j9206.setUsername(randomUser.getName()); j9206.setPassword(randomUser.getPassword()); j9206.setPath(filePath); if (mediaType != null) { j9206.setMediaType(mediaType); } if (streamType != null) { j9206.setStreamType(streamType); } if (storageType != null) { j9206.setStorageType(storageType); } if (alarmSign != null) { j9206.setAlarmSign(alarmSign); } downloadManager.addCatch(filePath, phoneNumber, j9206); return filePath; } @Override public void recordDownload(String filePath, ServletOutputStream outputStream) { JTRecordDownloadCatch downloadCatch = downloadManager.getCatch(filePath); Assert.notNull(downloadCatch, "地址不存在"); fileSystemFactory.addOutputStream(filePath, outputStream); jt1078Template.fileUpload(downloadCatch.getPhoneNumber(), downloadCatch.getJ9206(), 7200); downloadManager.runDownload(filePath, 2 * 60 * 60); fileSystemFactory.removeOutputStream(filePath); } @Override public byte[] snap(String phoneNumber, int channelId) { J8801 j8801 = new J8801(); // 设置抓图默认参数 JTShootingCommand shootingCommand = new JTShootingCommand(); shootingCommand.setChanelId(channelId); shootingCommand.setCommand(1); shootingCommand.setTime(0); shootingCommand.setSave(0); shootingCommand.setResolvingPower(0xFF); shootingCommand.setQuality(1); shootingCommand.setBrightness(125); shootingCommand.setContrastRatio(60); shootingCommand.setSaturation(60); shootingCommand.setChroma(125); j8801.setCommand(shootingCommand); log.info("[JT-抓图] 设备编号: {}, 通道编号: {}", phoneNumber, channelId); // 监听文件上传, 存在设备不回复抓图请求或者回复通用回复,导致缺少抓图编号,但是直接上传文件的,此处通过监听文件上传直接获取文件 @SuppressWarnings("unchecked") List ids = (List) jt1078Template.shooting(phoneNumber, j8801, 300); log.info("[JT-抓图] 抓图编号: {}, 设备编号: {}, 通道编号: {}", ids.get(0), phoneNumber, channelId); log.info("[JT-抓图] 请求上传图片,抓图编号: {}, 设备编号: {}, 通道编号: {}", ids.get(0), phoneNumber, channelId); J8805 j8805 = new J8805(); j8805.setMediaId(ids.get(0)); j8805.setDelete(1); JTMediaEventInfo mediaEventInfo = (JTMediaEventInfo)jt1078Template.uploadMediaDataForSingle(phoneNumber, j8805, 600); if (mediaEventInfo == null) { log.info("[]"); throw new ControllerException(ErrorCode.ERROR100.getCode(), ErrorCode.ERROR100.getMsg()); } log.info("[JT-抓图] 图片上传完成,抓图编号: {}, 设备编号: {}, 通道编号: {}", ids.get(0), phoneNumber, channelId); return mediaEventInfo.getMediaData(); } @Override public void uploadOneMedia(String phoneNumber, Long mediaId, ServletOutputStream outputStream, boolean delete) { log.info("[JT-单条存储多媒体数据上传] 媒体编号: {}, 设备编号: {}", mediaId, phoneNumber); J8805 j8805 = new J8805(); j8805.setMediaId(mediaId); j8805.setDelete(delete ? 1 : 0); log.info("[JT-单条存储多媒体数据上传] 请求上传,媒体编号: {}, 设备编号: {}", mediaId, phoneNumber); JTMediaEventInfo mediaEventInfo = (JTMediaEventInfo)jt1078Template.uploadMediaDataForSingle(phoneNumber, j8805, 600); if (mediaEventInfo == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), ErrorCode.ERROR100.getMsg()); } log.info("[JT-单条存储多媒体数据上传] 图片上传完成,媒体编号: {}, 设备编号: {}", mediaId, phoneNumber); try { outputStream.write(mediaEventInfo.getMediaData()); outputStream.flush(); } catch (IOException e) { log.info("[JT-单条存储多媒体数据上传] 数据写入异常,抓图编号: {}, 设备编号: {}", mediaId, phoneNumber, e); throw new ControllerException(ErrorCode.ERROR100.getCode(), "数据写入异常"); } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/session/FtpDownloadManager.java ================================================ package com.genersoft.iot.vmp.jt1078.session; import com.genersoft.iot.vmp.jt1078.bean.JTRecordDownloadCatch; import com.genersoft.iot.vmp.jt1078.event.FtpUploadEvent; import com.genersoft.iot.vmp.jt1078.proc.response.J9206; import lombok.extern.slf4j.Slf4j; import org.springframework.context.event.EventListener; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.DelayQueue; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.TimeUnit; @Slf4j @Component public class FtpDownloadManager { private final Map downloadCatchMap = new ConcurrentHashMap<>(); private final DelayQueue downloadCatchQueue = new DelayQueue<>(); private final Map> topicSubscribers = new ConcurrentHashMap<>(); // 下载过期检查 @Scheduled(fixedDelay = 1, timeUnit = TimeUnit.SECONDS) public void downloadCatchCheck(){ while (!downloadCatchQueue.isEmpty()) { try { JTRecordDownloadCatch take = downloadCatchQueue.take(); downloadCatchMap.remove(take.getPath()); } catch (InterruptedException e) { log.error("[下载过期] ", e); } } } public void addCatch(String path, String phoneNumber, J9206 j9206) { JTRecordDownloadCatch downloadCatch = new JTRecordDownloadCatch(); downloadCatch.setPhoneNumber(phoneNumber); downloadCatch.setPath(path); downloadCatch.setJ9206(j9206); // 10分钟临时地址无法访问则删除 downloadCatch.setDelayTime(System.currentTimeMillis() + 10 * 60 * 1000L); downloadCatchMap.put(path, downloadCatch); downloadCatchQueue.add(downloadCatch); } public JTRecordDownloadCatch getCatch(String path) { return downloadCatchMap.get(path); } @EventListener public void onApplicationEvent(FtpUploadEvent event) { if (topicSubscribers.isEmpty()) { return; } topicSubscribers.keySet().forEach(key -> { if (!event.getFileName().contains(key)) { return; } SynchronousQueue synchronousQueue = topicSubscribers.get(key); if (synchronousQueue != null) { synchronousQueue.offer(null); } }); } public Object runDownload(String path, long timeOut) { SynchronousQueue subscribe = subscribe(path); if (subscribe == null) { log.error("[JT-下载] 暂停进程失败"); return null; } try { return subscribe.poll(timeOut, TimeUnit.SECONDS); } catch (InterruptedException e) { log.warn("[JT-下载] 暂停进程超时", e); } finally { this.unsubscribe(path); JTRecordDownloadCatch downloadCatch = getCatch(path); if (downloadCatch != null) { downloadCatchMap.remove(path); downloadCatchQueue.remove(downloadCatch); } } return null; } private SynchronousQueue subscribe(String key) { SynchronousQueue queue = null; if (!topicSubscribers.containsKey(key)) topicSubscribers.put(key, queue = new SynchronousQueue<>()); return queue; } private void unsubscribe(String key) { topicSubscribers.remove(key); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/session/Session.java ================================================ package com.genersoft.iot.vmp.jt1078.session; import com.genersoft.iot.vmp.jt1078.proc.Header; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.util.AttributeKey; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.concurrent.atomic.AtomicInteger; /** * @author QingtaiJiang * @date 2023/4/27 18:54 * @email qingtaij@163.com */ @Slf4j public class Session { public static final AttributeKey KEY = AttributeKey.newInstance(Session.class.getName()); // Netty的channel protected final Channel channel; // 原子类的自增ID private final AtomicInteger serialNo = new AtomicInteger(0); // 是否注册成功 @Getter private boolean registered = false; // 设备手机号 @Getter private String phoneNumber; // 设备手机号 @Setter @Getter private String authenticationCode; // 创建时间 @Getter private final long creationTime; // 协议版本号 @Getter private Integer protocolVersion; @Getter private Header header; protected Session(Channel channel) { this.channel = channel; this.creationTime = System.currentTimeMillis(); } public void writeObject(Object message) { log.info("<<<<<<<<<< cmd{},{}", this, message); channel.writeAndFlush(message); } /** * 获得下一个流水号 * * @return 流水号 */ public int nextSerialNo() { int current; int next; do { current = serialNo.get(); next = current > 0xffff ? 0 : current; } while (!serialNo.compareAndSet(current, next + 1)); return next; } /** * 注册session * * @param devId 设备ID */ public void register(String devId, Integer version, Header header) { this.phoneNumber = devId; this.registered = true; this.protocolVersion = version; this.header = header; SessionManager.INSTANCE.put(devId, this); } @Override public String toString() { return "[" + "phoneNumber=" + phoneNumber + ", reg=" + registered + ", version=" + protocolVersion + ",ip=" + channel.remoteAddress() + ']'; } public void unregister() { channel.close(); SessionManager.INSTANCE.remove(this.phoneNumber); } public InetSocketAddress getLoadAddress() { return (InetSocketAddress)channel.localAddress(); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/session/SessionManager.java ================================================ package com.genersoft.iot.vmp.jt1078.session; import com.genersoft.iot.vmp.jt1078.proc.entity.Cmd; import io.netty.channel.Channel; import lombok.extern.slf4j.Slf4j; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.TimeUnit; /** * @author QingtaiJiang * @date 2023/4/27 19:54 * @email qingtaij@163.com */ @Slf4j public enum SessionManager { INSTANCE; // 用与消息的缓存 private final Map> topicSubscribers = new ConcurrentHashMap<>(); // session的缓存 private final Map sessionMap; SessionManager() { this.sessionMap = new ConcurrentHashMap<>(); } /** * 创建新的Session * * @param channel netty通道 * @return 创建的session对象 */ public Session newSession(Channel channel) { return new Session(channel); } /** * 获取指定设备的Session * * @param clientId 设备Id * @return Session */ public Session get(Object clientId) { return sessionMap.get(clientId); } /** * 放入新设备连接的session * * @param clientId 设备ID * @param newSession session */ void put(Object clientId, Session newSession) { sessionMap.put(clientId, newSession); } /** * 发送同步消息,接收响应 * 默认超时时间6秒 */ public Object request(Cmd cmd) { // 默认6秒 int timeOut = 6000; return request(cmd, timeOut); } public Object request(Cmd cmd, Integer timeOut) { Session session = this.get(cmd.getPhoneNumber()); if (session == null) { log.error("DevId: {} not online!", cmd.getPhoneNumber()); return null; } String requestKey = requestKey(cmd.getPhoneNumber(), cmd.getRespId(), cmd.getPackageNo()); SynchronousQueue subscribe = subscribe(requestKey); if (subscribe == null) { log.error("DevId: {} key:{} send repaid", cmd.getPhoneNumber(), requestKey); return null; } session.writeObject(cmd); try { return subscribe.poll(timeOut, TimeUnit.SECONDS); } catch (InterruptedException e) { log.warn("<<<<<<<<<< timeout" + session, e); } finally { this.unsubscribe(requestKey); } return null; } public Boolean response(String devId, String respId, Long responseNo, Object data) { String requestKey = requestKey(devId, respId, responseNo); boolean result = false; if (responseNo == null) { for (String key : topicSubscribers.keySet()) { if (key.startsWith(requestKey)) { SynchronousQueue queue = topicSubscribers.get(key); if (queue != null) { result = true; try { queue.offer(data, 2, TimeUnit.SECONDS); } catch (InterruptedException e) { log.error("{}", e.getMessage(), e); } } } } }else { SynchronousQueue queue = topicSubscribers.get(requestKey); if (queue != null) { result = true; try { queue.offer(data, 2, TimeUnit.SECONDS); } catch (InterruptedException e) { log.error("{}", e.getMessage(), e); } } } if (!result) { log.warn("Not find response,key:{} data:{} ", requestKey, data); } return result; } private void unsubscribe(String key) { topicSubscribers.remove(key); } private SynchronousQueue subscribe(String key) { SynchronousQueue queue = null; if (!topicSubscribers.containsKey(key)) topicSubscribers.put(key, queue = new SynchronousQueue<>()); return queue; } private String requestKey(String devId, String respId, Long requestNo) { return String.join("_", devId.replaceFirst("^0*", ""), respId, requestNo == null?"":requestNo.toString()); } public void remove(String devId) { sessionMap.remove(devId); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/util/BCDUtil.java ================================================ package com.genersoft.iot.vmp.jt1078.util; /** * BCD码转换 */ public class BCDUtil { public static String transform(byte[] bytes) { if (bytes.length == 0) { return null; } // BCD StringBuilder stringBuffer = new StringBuilder(bytes.length * 2); for (int i = 0; i < bytes.length; i++) { // 每次取出四位的值,一个byte是八位,第一取出高四位,第二次取出低四位, // 这里也可以先 & 0xf0再右移4位,0xf0二进制为11110000,与运算后,可以得到高4位是值,低四位清零的结果 stringBuffer.append((byte) ((bytes[i] >>> 4 & 0xf))); stringBuffer.append((byte) (bytes[i] & 0x0f)); } return stringBuffer.toString(); } /** * 字符串转BCD码 * 来自: https://www.cnblogs.com/ranandrun/p/BCD.html * @param asc ASCII字符串 * @return BCD */ public static byte[] strToBcd(String asc) { int len = asc.length(); int mod = len % 2; if (mod != 0) { asc = "0" + asc; len = asc.length(); } byte abt[] = new byte[len]; if (len >= 2) { len >>= 1; } byte bbt[] = new byte[len]; abt = asc.getBytes(); int j, k; for (int p = 0; p < asc.length() / 2; p++) { if ((abt[2 * p] >= '0') && (abt[2 * p] <= '9')) { j = abt[2 * p] - '0'; } else if ((abt[2 * p] >= 'a') && (abt[2 * p] <= 'z')) { j = abt[2 * p] - 'a' + 0x0a; } else { j = abt[2 * p] - 'A' + 0x0a; } if ((abt[2 * p + 1] >= '0') && (abt[2 * p + 1] <= '9')) { k = abt[2 * p + 1] - '0'; } else if ((abt[2 * p + 1] >= 'a') && (abt[2 * p + 1] <= 'z')) { k = abt[2 * p + 1] - 'a' + 0x0a; } else { k = abt[2 * p + 1] - 'A' + 0x0a; } int a = (j << 4) + k; byte b = (byte) a; bbt[p] = b; } return bbt; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/util/Bin.java ================================================ package com.genersoft.iot.vmp.jt1078.util; /** * 32位整型的二进制读写 */ public class Bin { private static final int[] bits = new int[32]; static { bits[0] = 1; for (int i = 1; i < bits.length; i++) { bits[i] = bits[i - 1] << 1; } } /** * 读取n的第i位 * * @param n int32 * @param i 取值范围0-31 */ public static boolean get(int n, int i) { return (n & bits[i]) == bits[i]; } /** * 不足位数从左边加0 */ public static String strHexPaddingLeft(String data, int length) { int dataLength = data.length(); if (dataLength < length) { StringBuilder dataBuilder = new StringBuilder(data); for (int i = dataLength; i < length; i++) { dataBuilder.insert(0, "0"); } data = dataBuilder.toString(); } return data; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/jt1078/util/ClassUtil.java ================================================ package com.genersoft.iot.vmp.jt1078.util; import lombok.extern.slf4j.Slf4j; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver; import java.lang.annotation.Annotation; import java.util.LinkedList; import java.util.List; @Slf4j public class ClassUtil { public static ConfigurableApplicationContext context; public static T getBean(String beanName, Class clazz) { return context.getBean(beanName, clazz); } public static Object getBean(Class clazz) { if (clazz != null) { try { return clazz.getDeclaredConstructor().newInstance(); } catch (Exception ex) { log.error("ClassUtil:找不到指定的类", ex); } } return null; } public static Object getBean(String className) { Class clazz = null; try { clazz = Class.forName(className); } catch (Exception ex) { log.error("ClassUtil:找不到指定的类"); } if (clazz != null) { try { //获取声明的构造器--》创建实例 return clazz.getDeclaredConstructor().newInstance(); } catch (Exception ex) { log.error("ClassUtil:找不到指定的类", ex); } } return null; } /** * 获取包下所有带注解的class * * @param packageName 包名 * @param annotationClass 注解类型 * @return list */ public static List> getClassList(String packageName, Class annotationClass) { List> classList = getClassList(packageName); classList.removeIf(next -> !next.isAnnotationPresent(annotationClass)); return classList; } public static List> getClassList(String... packageName) { List> classList = new LinkedList<>(); for (String s : packageName) { List> c = getClassList(s); classList.addAll(c); } return classList; } public static List> getClassList(String packageName) { List> classList = new LinkedList<>(); try { ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); Resource[] resources = resourcePatternResolver.getResources(packageName.replace(".", "/") + "/**/*.class"); for (Resource resource : resources) { String url = resource.getURL().toString(); String[] split = url.split(packageName.replace(".", "/")); String s = split[split.length - 1]; String className = s.replace("/", "."); className = className.substring(0, className.lastIndexOf(".")); doAddClass(classList, packageName + className); } } catch (Exception e) { throw new RuntimeException(e); } return classList; } private static void doAddClass(List> classList, String className) { Class cls = loadClass(className, false); classList.add(cls); } public static Class loadClass(String className, boolean isInitialized) { Class cls; try { cls = Class.forName(className, isInitialized, getClassLoader()); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } return cls; } public static ClassLoader getClassLoader() { return Thread.currentThread().getContextClassLoader(); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/MediaServerConfig.java ================================================ package com.genersoft.iot.vmp.media; import com.genersoft.iot.vmp.conf.MediaConfig; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.event.mediaServer.MediaServerChangeEvent; import com.genersoft.iot.vmp.media.service.IMediaServerService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.context.ApplicationEventPublisher; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import java.util.List; /** * 启动是从配置文件加载节点信息,以及发送个节点状态管理去控制节点状态 */ @Slf4j @Component @Order(value=12) public class MediaServerConfig implements CommandLineRunner { @Autowired private ApplicationEventPublisher applicationEventPublisher; @Autowired private IMediaServerService mediaServerService; @Autowired private MediaConfig mediaConfig; @Autowired private UserSetting userSetting; @Override public void run(String... strings) throws Exception { // 清理所有在线节点的缓存信息 mediaServerService.clearMediaServerForOnline(); MediaServer mediaSerItemInConfig = mediaConfig.buildMediaSer(); mediaSerItemInConfig.setServerId(userSetting.getServerId()); mediaServerService.deleteDefault(); // 发送媒体节点变化事件 mediaServerService.syncCatchFromDatabase(); // 获取所有的zlm, 并开启主动连接 List all = mediaServerService.getAllFromDatabaseWithOutDefault(); all.add(mediaSerItemInConfig); log.info("[媒体节点] 加载节点列表, 共{}个节点", all.size()); MediaServerChangeEvent event = new MediaServerChangeEvent(this); event.setMediaServerItemList(all); applicationEventPublisher.publishEvent(event); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/abl/ABLHttpHookListener.java ================================================ package com.genersoft.iot.vmp.media.abl; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.common.enums.MediaApp; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.gb28181.service.IDeviceService; import com.genersoft.iot.vmp.gb28181.service.IInviteStreamService; import com.genersoft.iot.vmp.gb28181.service.IPlayService; import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager; import com.genersoft.iot.vmp.gb28181.session.SSRCFactory; import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; import com.genersoft.iot.vmp.media.abl.bean.hook.*; import com.genersoft.iot.vmp.media.abl.event.HookAblServerKeepaliveEvent; import com.genersoft.iot.vmp.media.abl.event.HookAblServerStartEvent; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.bean.ResultForOnPublish; import com.genersoft.iot.vmp.media.event.media.*; import com.genersoft.iot.vmp.media.service.IMediaServerService; import com.genersoft.iot.vmp.media.zlm.dto.hook.HookResult; import com.genersoft.iot.vmp.media.zlm.dto.hook.HookResultForOnPublish; import com.genersoft.iot.vmp.service.IMediaService; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import io.swagger.v3.oas.annotations.Hidden; import jakarta.servlet.http.HttpServletRequest; 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.context.ApplicationEventPublisher; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.util.ObjectUtils; import org.springframework.web.bind.annotation.*; import java.util.HashMap; import java.util.Map; /** * ABL 的hook事件监听 */ @RestController @RequestMapping("/index/hook/abl") @Hidden public class ABLHttpHookListener { private final static Logger logger = LoggerFactory.getLogger(ABLHttpHookListener.class); @Autowired private ABLRESTfulUtils ablresTfulUtils; @Autowired private ISIPCommanderForPlatform commanderFroPlatform; @Autowired private AudioBroadcastManager audioBroadcastManager; @Autowired private IPlayService playService; @Autowired private IRedisCatchStorage redisCatchStorage; @Autowired private IInviteStreamService inviteStreamService; @Autowired private IDeviceService deviceService; @Autowired private IMediaServerService mediaServerService; @Autowired private IMediaService mediaService; @Autowired private UserSetting userSetting; @Autowired private SSRCFactory ssrcFactory; @Qualifier("taskExecutor") @Autowired private ThreadPoolTaskExecutor taskExecutor; @Autowired private RedisTemplate redisTemplate; @Autowired private ApplicationEventPublisher applicationEventPublisher; /** * 服务器定时上报时间,上报间隔可配置,默认10s上报一次 */ @ResponseBody @PostMapping(value = "/on_server_keepalive", produces = "application/json;charset=UTF-8") public HookResult onServerKeepalive(@RequestBody OnServerKeepaliveABLHookParam param) { try { HookAblServerKeepaliveEvent event = new HookAblServerKeepaliveEvent(this); MediaServer mediaServerItem = mediaServerService.getOne(param.getMediaServerId()); if (mediaServerItem != null) { event.setMediaServerItem(mediaServerItem); applicationEventPublisher.publishEvent(event); } }catch (Exception e) { logger.info("[ZLM-HOOK-心跳] 发送通知失败 ", e); } return HookResult.SUCCESS(); } /** * 播放器鉴权事件,rtsp/rtmp/http-flv/ws-flv/hls的播放都将触发此鉴权事件。 */ @ResponseBody @PostMapping(value = "/on_play", produces = "application/json;charset=UTF-8") public HookResult onPlay(@RequestBody OnPlayABLHookParam param) { MediaServer mediaServer = mediaServerService.getOne(param.getMediaServerId()); if (mediaServer == null) { return new HookResultForOnPublish(0, "success"); } Map paramMap = urlParamToMap(param.getParams()); // 对于播放流进行鉴权 boolean authenticateResult = mediaService.authenticatePlay(param.getApp(), param.getStream(), paramMap.get("callId")); if (!authenticateResult) { logger.info("[ABL HOOK] 播放鉴权 失败:{}->{}", param.getMediaServerId(), param); ablresTfulUtils.closeStreams(mediaServer, param.getApp(), param.getStream()); } logger.info("[ABL HOOK] 播放鉴权成功:{}->{}", param.getMediaServerId(), param); return HookResult.SUCCESS(); } /** * rtsp/rtmp/rtp推流鉴权事件。 */ @ResponseBody @PostMapping(value = "/on_publish", produces = "application/json;charset=UTF-8") public HookResult onPublish(@RequestBody OnPublishABLHookParam param) { logger.info("[ABL HOOK] 推流鉴权:{}->{}/{}?{}", param.getMediaServerId(), param.getApp(), param.getStream(), param.getParams()); // TODO 加快处理速度 MediaServer mediaServer = mediaServerService.getOne(param.getMediaServerId()); if (mediaServer == null) { return new HookResultForOnPublish(0, "success"); } try { ResultForOnPublish resultForOnPublish = mediaService.authenticatePublish(mediaServer, param.getApp(), param.getStream(), param.getParams()); if (resultForOnPublish == null) { logger.info("[ABL HOOK]推流鉴权 拒绝 响应:{}->{}", param.getMediaServerId(), param); ablresTfulUtils.closeStreams(mediaServer, param.getApp(), param.getStream()); } }catch (ControllerException e) { ablresTfulUtils.closeStreams(mediaServer, param.getApp(), param.getStream()); } return HookResult.SUCCESS(); } /** * 如果某一个码流进行MP4录像(enable_mp4=1),会触发录像进度通知事件 */ @ResponseBody @PostMapping(value = "/on_record_progress", produces = "application/json;charset=UTF-8") public HookResult onRecordProgress(@RequestBody OnRecordProgressABLHookParam param) { logger.info("[ABL HOOK] 录像进度通知:{}->{}/{}->{}/{}", param.getMediaServerId(), param.getApp(), param.getStream(), param.getCurrentFileDuration(), param.getTotalVideoDuration()); try { MediaServer mediaServerItem = mediaServerService.getOne(param.getMediaServerId()); if (mediaServerItem != null) { MediaRecordProcessEvent event = MediaRecordProcessEvent.getInstance(this, param, mediaServerItem); event.setMediaServer(mediaServerItem); applicationEventPublisher.publishEvent(event); } }catch (Exception e) { logger.info("[ZLM-HOOK-录像进度通知] 发送通知失败 ", e); } return HookResult.SUCCESS(); } /** * 当代理拉流、国标接入等等 码流不到达时会发出 码流不到达的事件通知 */ @ResponseBody @PostMapping(value = "/on_stream_not_arrive", produces = "application/json;charset=UTF-8") public HookResult onStreamNotArrive(@RequestBody ABLHookParam param) { logger.info("[ABL HOOK] 码流不到达通知:{}->{}/{}", param.getMediaServerId(), param.getApp(), param.getStream()); try { if (MediaApp.GB28181.equals(param.getApp())) { return HookResult.SUCCESS(); } MediaRtpServerTimeoutEvent event = new MediaRtpServerTimeoutEvent(this); MediaServer mediaServerItem = mediaServerService.getOne(param.getMediaServerId()); if (mediaServerItem != null) { event.setMediaServer(mediaServerItem); event.setApp(MediaApp.GB28181); applicationEventPublisher.publishEvent(event); } }catch (Exception e) { logger.info("[ABL-HOOK-码流不到达通知] 发送通知失败 ", e); } return HookResult.SUCCESS(); } /** * 如果某一个码流进行MP4录像(enable_mp4=1),当某个MP4文件被删除会触发该事件通知 */ @ResponseBody @PostMapping(value = "/on_delete_record_mp4", produces = "application/json;charset=UTF-8") public HookResult onDeleteRecordMp4(@RequestBody OnRecordMp4ABLHookParam param) { logger.info("[ABL HOOK] MP4文件被删除通知:{}->{}/{}", param.getMediaServerId(), param.getApp(), param.getStream()); return HookResult.SUCCESS(); } /** * rtsp/rtmp流注册或注销时触发此事件;此事件对回复不敏感。 */ @ResponseBody @PostMapping(value = "/on_stream_arrive", produces = "application/json;charset=UTF-8") public HookResult onStreamArrive(@RequestBody OnStreamArriveABLHookParam param) { MediaServer mediaServer = mediaServerService.getOne(param.getMediaServerId()); if (mediaServer == null) { return HookResult.SUCCESS(); } logger.info("[ABL HOOK] 码流到达, {}->{}/{}", param.getMediaServerId(), param.getApp(), param.getStream()); MediaArrivalEvent mediaArrivalEvent = MediaArrivalEvent.getInstance(this, param, mediaServer); applicationEventPublisher.publishEvent(mediaArrivalEvent); return HookResult.SUCCESS(); } /** * 流无人观看时事件,用户可以通过此事件选择是否关闭无人看的流。 */ @ResponseBody @PostMapping(value = "/on_stream_none_reader", produces = "application/json;charset=UTF-8") public JSONObject onStreamNoneReader(@RequestBody ABLHookParam param) { logger.info("[ABL HOOK]流无人观看:{}->{}/{}", param.getMediaServerId(), param.getApp(), param.getStream()); JSONObject ret = new JSONObject(); boolean close = mediaService.closeStreamOnNoneReader(param.getMediaServerId(), param.getApp(), param.getStream(), null); ret.put("code", close); return ret; } /** * 当播放一个url,如果不存在时,会发出一个消息通知 */ @ResponseBody @PostMapping(value = "/on_stream_not_found", produces = "application/json;charset=UTF-8") public HookResult onStreamNotFound(@RequestBody ABLHookParam param) { logger.info("[ABL HOOK] 流未找到:{}->{}/{}", param.getMediaServerId(), param.getApp(), param.getStream()); MediaServer mediaServer = mediaServerService.getOne(param.getMediaServerId()); if (!userSetting.getAutoApplyPlay() || mediaServer == null) { return HookResult.SUCCESS(); } MediaNotFoundEvent mediaNotFoundEvent = MediaNotFoundEvent.getInstance(this, param, mediaServer); applicationEventPublisher.publishEvent(mediaNotFoundEvent); return HookResult.SUCCESS(); } /** * ABLMediaServer启动时会发送上线通知 */ @ResponseBody @PostMapping(value = "/on_server_started", produces = "application/json;charset=UTF-8") public HookResult onServerStarted(HttpServletRequest request, @RequestBody OnServerStaredABLHookParam param) { logger.info("[ABL HOOK] 启动 " + param.getMediaServerId()); try { HookAblServerStartEvent event = new HookAblServerStartEvent(this); MediaServer mediaServerItem = mediaServerService.getOne(param.getMediaServerId()); if (mediaServerItem != null) { event.setMediaServerItem(mediaServerItem); applicationEventPublisher.publishEvent(event); } }catch (Exception e) { logger.info("[ABL-HOOK-启动] 发送通知失败 ", e); } return HookResult.SUCCESS(); } /** * TODO 发送rtp(startSendRtp)被动关闭时回调 */ // @ResponseBody // @PostMapping(value = "/on_send_rtp_stopped", produces = "application/json;charset=UTF-8") // public HookResult onSendRtpStopped(HttpServletRequest request, @RequestBody OnSendRtpStoppedHookParam param) { // // logger.info("[ZLM HOOK] rtp发送关闭:{}->{}/{}", param.getMediaServerId(), param.getApp(), param.getStream()); // // // 查找对应的上级推流,发送停止 // if (!"rtp".equals(param.getApp())) { // return HookResult.SUCCESS(); // } // try { // MediaSendRtpStoppedEvent event = new MediaSendRtpStoppedEvent(this); // MediaServer mediaServerItem = mediaServerService.getOne(param.getMediaServerId()); // if (mediaServerItem != null) { // event.setMediaServer(mediaServerItem); // applicationEventPublisher.publishEvent(event); // } // }catch (Exception e) { // logger.info("[ZLM-HOOK-rtp发送关闭] 发送通知失败 ", e); // } // // return HookResult.SUCCESS(); // } /** * TODO 录像完成事件 */ @ResponseBody @PostMapping(value = "/on_record_mp4", produces = "application/json;charset=UTF-8") public HookResult onRecordMp4(HttpServletRequest request, @RequestBody OnRecordMp4ABLHookParam param) { logger.info("[ABL HOOK] 录像完成事件:{}->{}", param.getMediaServerId(), param.getFileName()); try { MediaServer mediaServerItem = mediaServerService.getOne(param.getMediaServerId()); if (mediaServerItem != null) { MediaRecordMp4Event event = MediaRecordMp4Event.getInstance(this, param, mediaServerItem); event.setMediaServer(mediaServerItem); applicationEventPublisher.publishEvent(event); } }catch (Exception e) { logger.info("[ZLM-HOOK-rtpServer收流超时] 发送通知失败 ", e); } return HookResult.SUCCESS(); } /** * 当某一路码流断开时会发送通知 */ @ResponseBody @PostMapping(value = "/on_stream_disconnect", produces = "application/json;charset=UTF-8") public HookResult onRecordMp4(HttpServletRequest request, @RequestBody ABLHookParam param) { logger.info("[ABL HOOK] 码流断开事件, {}->{}/{}", param.getMediaServerId(), param.getApp(), param.getStream()); MediaServer mediaServer = mediaServerService.getOne(param.getMediaServerId()); if (mediaServer == null) { return HookResult.SUCCESS(); } MediaDepartureEvent mediaDepartureEvent = MediaDepartureEvent.getInstance(this, param, mediaServer); applicationEventPublisher.publishEvent(mediaDepartureEvent); return HookResult.SUCCESS(); } private Map urlParamToMap(String params) { HashMap map = new HashMap<>(); if (ObjectUtils.isEmpty(params)) { return map; } String[] paramsArray = params.split("&"); if (paramsArray.length == 0) { return map; } for (String param : paramsArray) { String[] paramArray = param.split("="); if (paramArray.length == 2) { map.put(paramArray[0], paramArray[1]); } } return map; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/abl/ABLMediaNodeServerService.java ================================================ package com.genersoft.iot.vmp.media.abl; import com.alibaba.fastjson2.JSONArray; import com.genersoft.iot.vmp.common.CommonCallback; import com.genersoft.iot.vmp.common.InviteInfo; import com.genersoft.iot.vmp.common.InviteSessionType; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.common.enums.MediaApp; import com.genersoft.iot.vmp.conf.SipConfig; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.gb28181.bean.SendRtpInfo; import com.genersoft.iot.vmp.gb28181.bean.TalkRtpInfo; import com.genersoft.iot.vmp.gb28181.service.IInviteStreamService; import com.genersoft.iot.vmp.media.abl.bean.ABLMedia; import com.genersoft.iot.vmp.media.abl.bean.ABLResult; import com.genersoft.iot.vmp.media.abl.bean.AblServerConfig; import com.genersoft.iot.vmp.media.bean.MediaInfo; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.bean.RecordInfo; import com.genersoft.iot.vmp.media.event.media.MediaRecordMp4Event; import com.genersoft.iot.vmp.media.service.IMediaNodeServerService; import com.genersoft.iot.vmp.service.bean.CloudRecordItem; import com.genersoft.iot.vmp.service.bean.DownloadFileInfo; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.storager.dao.CloudRecordServiceMapper; import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy; import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import lombok.extern.slf4j.Slf4j; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; import org.springframework.util.ObjectUtils; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @Slf4j @Service("abl") public class ABLMediaNodeServerService implements IMediaNodeServerService { private final static Logger logger = LoggerFactory.getLogger(ABLMediaNodeServerService.class); @Autowired private ABLRESTfulUtils ablresTfulUtils; @Autowired private SipConfig sipConfig; @Autowired private UserSetting userSetting; @Autowired private CloudRecordServiceMapper cloudRecordServiceMapper; @Autowired private IInviteStreamService inviteStreamService; @Override public boolean initStopSendRtp(MediaServer mediaInfo, String app, String stream, String ssrc) { return false; } @Override public int createRTPServer(MediaServer mediaServer, String app, String stream, long ssrc, Integer port, Boolean onlyAuto, Boolean disableAudio, Boolean reUsePort, Integer tcpMode) { Boolean recordSip = userSetting.getRecordSip(); return ablresTfulUtils.openRtpServer(mediaServer, app, stream, 96, port, tcpMode, disableAudio?1:0, recordSip, false); } @Override public void closeRtpServer(MediaServer mediaServer, String app, String stream, CommonCallback callback) { if (mediaServer == null) { return; } ABLResult result = ablresTfulUtils.closeStreams(mediaServer, app, stream); logger.info("关闭RTP Server " + result); if (result.getCode() != 0) { logger.error("[closeRtpServer] 失败: {}", result.getMemo()); } } // @Override // public int createJTTServer(MediaServer mediaServer, String stream, Integer port, Boolean disableVideo, Boolean disableAudio, Integer tcpMode) { // Boolean recordSip = userSetting.getRecordSip(); // return ablresTfulUtils.openRtpServer(mediaServer, MediaApp.JT1078, stream, 96, port, tcpMode, disableAudio?1:0, recordSip, true); // } // // @Override // public void closeJTTServer(MediaServer mediaServer, String streamId, CommonCallback callback) { // if (mediaServer == null) { // return; // } // ABLResult result = ablresTfulUtils.closeStreams(mediaServer, MediaApp.JT1078, streamId); // logger.info("关闭JT-RTP Server " + result); // if (result.getCode() != 0) { // logger.error("[JT-closeRtpServer] 失败: {}", result.getMemo()); // } // } @Override public void closeStreams(MediaServer mediaServer, String app, String streamId) { ABLResult result = ablresTfulUtils.closeStreams(mediaServer, app, streamId); if (result.getCode() != 0) { logger.error("[closeStreams] 失败: {}", result.getMemo()); } } @Override public Boolean updateRtpServerSSRC(MediaServer mediaServerItem, String app, String streamId, String ssrc) { return null; } @Override public boolean checkNodeId(MediaServer mediaServerItem) { logger.warn("[abl-checkNodeId] 未实现"); return false; } @Override public void online(MediaServer mediaServerItem) { logger.warn("[abl-online] 未实现"); } @Override public MediaServer checkMediaServer(String ip, int port, String secret) { MediaServer mediaServer = new MediaServer(); mediaServer.setIp(ip); mediaServer.setHttpPort(port); mediaServer.setSecret(secret); ABLResult result = ablresTfulUtils.getServerConfig(mediaServer); JSONArray data = result.getParams(); if (data != null && !data.isEmpty()) { AblServerConfig config = AblServerConfig.getInstance(data); config.setServerIp(ip); config.setHttpServerPort(port); return new MediaServer(config, sipConfig.getIp()); } return null; } @Override public boolean stopSendRtp(MediaServer mediaInfo, String app, String stream, String ssrc) { // TODO 需要记录开始发流返回的KEY,暂不做实现 logger.warn("[abl-stopSendRtp] 未实现"); // ablresTfulUtils.stopSendRtp() return false; } @Override public boolean deleteRecordDirectory(MediaServer mediaServerItem, String app, String stream, String date, String fileName) { logger.warn("[abl-deleteRecordDirectory] 未实现"); return false; } @Override public List getMediaList(MediaServer mediaServer, String app, String stream, String callId) { ABLResult result = ablresTfulUtils.getMediaList(mediaServer, app, stream); if (result.getCode() != 0) { return null; } if (result.getMediaList() == null || result.getMediaList().isEmpty()) { return new ArrayList<>(); } List streamInfoList = new ArrayList<>(); for (int i = 0; i < result.getMediaList().size(); i++) { ABLMedia ablMedia = result.getMediaList().get(i); MediaInfo mediaInfo = MediaInfo.getInstance(ablMedia, mediaServer); StreamInfo streamInfo = getStreamInfoByAppAndStream(mediaServer, app, stream, mediaInfo, null, callId, true); if (streamInfo != null) { streamInfoList.add(streamInfo); } } return streamInfoList; } @Override public StreamInfo getStreamInfoByAppAndStream(MediaServer mediaServer, String app, String stream, MediaInfo mediaInfo, String addr, String callId, boolean isPlay) { StreamInfo streamInfoResult = new StreamInfo(); streamInfoResult.setStream(stream); streamInfoResult.setApp(app); if (addr == null) { addr = mediaServer.getStreamIp(); } streamInfoResult.setIp(addr); if (mediaInfo != null) { streamInfoResult.setServerId(mediaInfo.getServerId()); }else { streamInfoResult.setServerId(userSetting.getServerId()); } streamInfoResult.setMediaServer(mediaServer); Map param = new HashMap<>(); if (!ObjectUtils.isEmpty(callId)) { param.put("callId", callId); } if (mediaInfo != null && !ObjectUtils.isEmpty(mediaInfo.getOriginTypeStr())) { param.put("originTypeStr", mediaInfo.getOriginTypeStr()); } StringBuilder callIdParamBuilder = new StringBuilder(); if (!param.isEmpty()) { callIdParamBuilder.append("?"); for (Map.Entry entry : param.entrySet()) { callIdParamBuilder.append(entry.getKey()).append("=").append(entry.getValue()); callIdParamBuilder.append("&"); } callIdParamBuilder.deleteCharAt(callIdParamBuilder.length() - 1); } String callIdParam = callIdParamBuilder.toString(); streamInfoResult.setRtmp(addr, mediaServer.getRtmpPort(),mediaServer.getRtmpSSlPort(), app, stream, callIdParam); streamInfoResult.setRtsp(addr, mediaServer.getRtspPort(),mediaServer.getRtspSSLPort(), app, stream, callIdParam); String flvFile = String.format("%s/%s.flv%s", app, stream, callIdParam); if ((mediaServer.getFlvPort() & 1) == 1) { // 奇数端口 默认ssl端口 streamInfoResult.setFlv(addr, null, mediaServer.getFlvPort(), flvFile); }else { streamInfoResult.setFlv(addr, mediaServer.getFlvPort(),null, flvFile); } if ((mediaServer.getWsFlvPort() & 1) == 1) { // 奇数端口 默认ssl端口 streamInfoResult.setWsFlv(addr, null, mediaServer.getWsFlvPort(), flvFile); }else { streamInfoResult.setWsFlv(addr, mediaServer.getWsFlvPort(),null, flvFile); } String mp4File = String.format("%s/%s.mp4%s", app, stream, callIdParam); if ((mediaServer.getMp4Port() & 1) == 1) { // 奇数端口 默认ssl端口 streamInfoResult.setFmp4(addr, null, mediaServer.getMp4Port(), mp4File); }else { streamInfoResult.setFmp4(addr, mediaServer.getMp4Port(), null, mp4File); } streamInfoResult.setHls(addr, mediaServer.getHttpPort(), mediaServer.getHttpSSlPort(), app, stream, callIdParam); streamInfoResult.setTs(addr, mediaServer.getHttpPort(), mediaServer.getHttpSSlPort(), app, stream, callIdParam); streamInfoResult.setRtc(addr, mediaServer.getHttpPort(), mediaServer.getHttpSSlPort(), app, stream, callIdParam, isPlay); streamInfoResult.setMediaInfo(mediaInfo); if (!MediaApp.GB28181_BROADCAST.equalsIgnoreCase(app) && !ObjectUtils.isEmpty(mediaServer.getTranscodeSuffix()) && !"null".equalsIgnoreCase(mediaServer.getTranscodeSuffix())) { String newStream = stream + "_" + mediaServer.getTranscodeSuffix(); mediaServer.setTranscodeSuffix(null); StreamInfo transcodeStreamInfo = getStreamInfoByAppAndStream(mediaServer, app, newStream, null, addr, callId, isPlay); streamInfoResult.setTranscodeStream(transcodeStreamInfo); } return streamInfoResult; } @Override public Boolean connectRtpServer(MediaServer mediaServerItem, String address, int port, String app, String stream) { logger.warn("[abl-connectRtpServer] 未实现"); return null; } @Override public void getSnap(MediaServer mediaServer, String app, String stream, int timeoutSec, int expireSec, String path, String fileName) { ablresTfulUtils.getSnap(mediaServer, app, stream, timeoutSec, path, fileName); } @Override public MediaInfo getMediaInfo(MediaServer mediaServer, String app, String stream) { ABLResult ablResult = ablresTfulUtils.getMediaList(mediaServer, app, stream); if (ablResult.getCode() != 0) { return null; } if (ablResult.getMediaList() == null || ablResult.getMediaList().isEmpty()) { return null; } return MediaInfo.getInstance(ablResult.getMediaList().get(0), mediaServer); } @Override public Boolean pauseRtpCheck(MediaServer mediaServer, String streamKey) { ABLResult ablResult = ablresTfulUtils.pauseRtpServer(mediaServer, streamKey); return ablResult.getCode() == 0; } @Override public Boolean resumeRtpCheck(MediaServer mediaServer, String streamKey) { ABLResult ablResult = ablresTfulUtils.resumeRtpServer(mediaServer, streamKey); return ablResult.getCode() == 0; } @Override public String getFfmpegCmd(MediaServer mediaServer, String cmdKey) { return ""; } @Override public Boolean delFFmpegSource(MediaServer mediaServer, String streamKey) { ABLResult ablResult = ablresTfulUtils.delFFmpegProxy(mediaServer, streamKey); return ablResult.getCode() == 0; } @Override public Boolean delStreamProxy(MediaServer mediaServer, String streamKey) { ABLResult ablResult = ablresTfulUtils.delStreamProxy(mediaServer, streamKey); return ablResult.getCode() == 0; } @Override public Map getFFmpegCMDs(MediaServer mediaServer) { return new HashMap<>(); } // 接受进度通知 // @EventListener // public void onApplicationEvent(MediaRecordProcessEvent event) { // CloudRecordItem cloudRecordItem = cloudRecordServiceMapper.getListByFileName(event.getApp(), event.getStream(), event.getFileName()); // if (cloudRecordItem == null) { // cloudRecordItem = CloudRecordItem.getInstance(event); // cloudRecordItem.setStartTime(event.getStartTime()); // cloudRecordItem.setEndTime(event.getEndTime()); // cloudRecordServiceMapper.add(cloudRecordItem); // }else { // cloudRecordServiceMapper.updateTimeLen(cloudRecordItem.getId(), (long)event.getCurrentFileDuration() * 1000, System.currentTimeMillis()); // } // } @EventListener public void onApplicationEvent(MediaRecordMp4Event event) { InviteInfo inviteInfo = inviteStreamService.getInviteInfo(InviteSessionType.DOWNLOAD, null, event.getStream()); if (inviteInfo == null || inviteInfo.getStreamInfo() == null) { return; } List cloudRecordItemList = cloudRecordServiceMapper.getList(null, event.getApp(), event.getStream(), null, null, null, null, null, null); if (cloudRecordItemList.isEmpty()) { return; } long startTime = cloudRecordItemList.get(cloudRecordItemList.size() - 1).getStartTime(); long endTime = cloudRecordItemList.get(0).getEndTime(); ABLResult ablResult = ablresTfulUtils.queryRecordList(event.getMediaServer(), event.getApp(), event.getStream(), DateUtil.timestampMsToUrlToyyyy_MM_dd_HH_mm_ss(startTime), DateUtil.timestampMsToUrlToyyyy_MM_dd_HH_mm_ss(endTime)); if (ablResult.getCode() != 0) { return; } if (ablResult.getUrl() == null) { return; } String download = ablResult.getUrl().getDownload(); DownloadFileInfo downloadFileInfo = new DownloadFileInfo(); downloadFileInfo.setHttpPath(download); downloadFileInfo.setHttpsPath(download); inviteInfo.getStreamInfo().setDownLoadFilePath(downloadFileInfo); inviteStreamService.updateInviteInfo(inviteInfo); } @Override public Long updateDownloadProcess(MediaServer mediaServer, String app, String stream) { List list = cloudRecordServiceMapper.getList(null, app, stream, null, null, null, null, null, null); if (list.isEmpty()) { return null; } long downloadProcess = 0L; for (CloudRecordItem cloudRecordItem : list) { downloadProcess += (long) cloudRecordItem.getTimeLen(); } return downloadProcess; } @Override public WVPResult addStreamProxy(MediaServer mediaServer, String app, String stream, String url, boolean enableAudio, boolean enableMp4, String rtpType, Integer timeout) { ABLResult result = ablresTfulUtils.addStreamProxy(mediaServer, app, stream, url, !enableAudio, enableMp4, rtpType, timeout); if (result.getCode() != 0) { return WVPResult.fail(ErrorCode.ERROR100.getCode(), result.getMemo()); }else { return WVPResult.success(result.getKey()); } } @Override public Integer startSendRtpPassive(MediaServer mediaServer, SendRtpInfo sendRtpItem, Integer timeout) { logger.warn("[abl-startSendRtpPassive] 未实现"); return 0; } @Override public Integer startSendRtpTalk(MediaServer mediaServer, TalkRtpInfo talkRtpInfo, Integer timeout) { logger.warn("[abl-startSendRtpTalk] 未实现"); return 0; } @Override public void startSendRtpStream(MediaServer mediaServer, SendRtpInfo sendRtpItem) { logger.warn("[abl-startSendRtpStream] 未实现"); } @Override public String startProxy(MediaServer mediaServer, StreamProxy streamProxy) { MediaInfo mediaInfo = getMediaInfo(mediaServer, streamProxy.getApp(), streamProxy.getStream()); if (mediaInfo != null) { closeStreams(mediaServer, streamProxy.getApp(), streamProxy.getStream()); } ABLResult ablResult = null; if ("ffmpeg".equalsIgnoreCase(streamProxy.getType())){ if (streamProxy.getTimeout() == 0) { streamProxy.setTimeout(15); } ablResult = ablresTfulUtils.addFFmpegProxy(mediaServer, streamProxy.getApp(), streamProxy.getStream(), streamProxy.getSrcUrl().trim(), !streamProxy.isEnableAudio(), streamProxy.isEnableMp4(), streamProxy.getRtspType(), streamProxy.getTimeout()); }else { ablResult = ablresTfulUtils.addStreamProxy(mediaServer, streamProxy.getApp(), streamProxy.getStream(), streamProxy.getSrcUrl().trim(), streamProxy.isEnableAudio(), streamProxy.isEnableMp4(), streamProxy.getRtspType(), streamProxy.getTimeout()); } if (ablResult.getCode() != 0) { throw new ControllerException(ablResult.getCode(), ablResult.getMemo()); }else { String key = ablResult.getKey(); if (key == null) { throw new ControllerException(ablResult.getCode(), "代理结果异常: " + ablResult); }else { return key; } } } @Override public void stopProxy(MediaServer mediaServer, String streamKey, String type) { ABLResult ablResult = null; if ("ffmpeg".equalsIgnoreCase(type)){ ablResult = ablresTfulUtils.delFFmpegProxy(mediaServer, streamKey); }else { ablResult = ablresTfulUtils.delStreamProxy(mediaServer, streamKey); } if (ablResult.getCode() != 0) { throw new ControllerException(ablResult.getCode(), ablResult.getMemo()); } } @Override public List listRtpServer(MediaServer mediaServer) { ABLResult ablResult = ablresTfulUtils.getMediaList(mediaServer, null, null); if (ablResult.getCode() != 0) { return null; } if (ablResult.getMediaList() == null || ablResult.getMediaList().isEmpty()) { return new ArrayList<>(); } List result = new ArrayList<>(); for (int i = 0; i < ablResult.getMediaList().size(); i++) { ABLMedia ablMedia = ablResult.getMediaList().get(i); result.add(ablMedia.getStream()); } return result; } @Override public void loadMP4File(MediaServer mediaServer, String app, String stream, String filePath, String fileName, ErrorCallback callback) { String buildStream = String.format("%s__ReplayFMP4RecordFile__%s", stream, fileName); StreamInfo streamInfo = getStreamInfoByAppAndStream(mediaServer, app, buildStream, null, null, null, true); callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo); } @Override public void loadMP4FileForDate(MediaServer mediaServer, String app, String stream, String date, String dateDir, ErrorCallback callback) { // 解析为 LocalDate LocalDate localDate = LocalDate.parse(date, DateUtil.DateFormatter); LocalDateTime startOfDay = localDate.atStartOfDay(); LocalDateTime endOfDay = localDate.atTime(23, 59,59, 999); String startTime = DateUtil.urlFormatter.format(startOfDay); String endTime = DateUtil.urlFormatter.format(endOfDay); ABLResult ablResult = ablresTfulUtils.queryRecordList(mediaServer, app, stream, startTime, endTime); if (ablResult.getCode() != 0) { throw new ControllerException(ErrorCode.ERROR100.getCode(), ablResult.getMemo()); } String resultApp = ablResult.getApp(); String resultStream = ablResult.getStream(); StreamInfo streamInfo = getStreamInfoByAppAndStream(mediaServer, resultApp, resultStream, null, null,null, true); streamInfo.setKey(ablResult.getKey()); if (callback != null) { callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo); } } @Override public void seekRecordStamp(MediaServer mediaServer, String app, String stream, Double stamp, String schema) { ABLResult ablResult = ablresTfulUtils.controlRecordPlay(mediaServer, app, stream, "seek", stamp/1000 + ""); if (ablResult.getCode() != 0) { log.warn("[abl-seek] 失败:{}", ablResult.getMemo()); } } @Override public void setRecordSpeed(MediaServer mediaServer, String app, String stream, Integer speed, String schema) { ABLResult ablResult = ablresTfulUtils.controlRecordPlay(mediaServer, app, stream, "scale", speed + ""); if (ablResult.getCode() != 0) { log.warn("[abl-倍速] 失败:{}", ablResult.getMemo()); } } @Override public DownloadFileInfo getDownloadFilePath(MediaServer mediaServer, RecordInfo recordInfo) { // 将filePath作为独立参数传入,避免%符号解析问题 String pathTemplate = "%s://%s:%s/%s/%s__ReplayFMP4RecordFile__%s?download_speed=16"; DownloadFileInfo info = new DownloadFileInfo(); if ((mediaServer.getMp4Port() & 1) == 1) { info.setHttpsPath( String.format( pathTemplate, "https", mediaServer.getStreamIp(), mediaServer.getMp4Port(), recordInfo.getApp(), recordInfo.getStream(), recordInfo.getFileName() ) ); }else { info.setHttpPath( String.format( pathTemplate, "http", mediaServer.getStreamIp(), mediaServer.getMp4Port(), recordInfo.getApp(), recordInfo.getStream(), recordInfo.getFileName() ) ); } return info; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/abl/ABLMediaServerStatusManger.java ================================================ package com.genersoft.iot.vmp.media.abl; import com.alibaba.fastjson2.JSONArray; import com.genersoft.iot.vmp.conf.DynamicTask; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.gb28181.event.EventPublisher; import com.genersoft.iot.vmp.media.abl.bean.ABLResult; import com.genersoft.iot.vmp.media.abl.bean.AblServerConfig; import com.genersoft.iot.vmp.media.abl.bean.ConfigKeyId; import com.genersoft.iot.vmp.media.abl.event.HookAblServerKeepaliveEvent; import com.genersoft.iot.vmp.media.abl.event.HookAblServerStartEvent; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.event.mediaServer.MediaServerChangeEvent; import com.genersoft.iot.vmp.media.event.mediaServer.MediaServerDeleteEvent; import com.genersoft.iot.vmp.media.service.IMediaServerService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.event.EventListener; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.lang.reflect.Field; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * 管理zlm流媒体节点的状态 */ @Component @Slf4j public class ABLMediaServerStatusManger { private final Map offlineABLPrimaryMap = new ConcurrentHashMap<>(); private final Map offlineAblsecondaryMap = new ConcurrentHashMap<>(); private final Map offlineAblTimeMap = new ConcurrentHashMap<>(); @Autowired private ABLRESTfulUtils ablResTfulUtils; @Autowired private IMediaServerService mediaServerService; @Autowired private DynamicTask dynamicTask; @Value("${server.ssl.enabled:false}") private boolean sslEnabled; @Value("${server.port}") private Integer serverPort; @Autowired private UserSetting userSetting; @Autowired private EventPublisher eventPublisher; private final String type = "abl"; @Async("taskExecutor") @EventListener public void onApplicationEvent(MediaServerChangeEvent event) { if (event.getMediaServerItemList() == null || event.getMediaServerItemList().isEmpty()) { return; } for (MediaServer mediaServer : event.getMediaServerItemList()) { if (!type.equals(mediaServer.getType())) { continue; } log.info("[ABL-添加待上线节点] ID:" + mediaServer.getId()); offlineABLPrimaryMap.put(mediaServer.getId(), mediaServer); offlineAblTimeMap.put(mediaServer.getId(), System.currentTimeMillis()); } execute(); } @Async("taskExecutor") @EventListener public void onApplicationEvent(HookAblServerStartEvent event) { if (event.getMediaServerItem() == null || !type.equals(event.getMediaServerItem().getType()) || event.getMediaServerItem().isStatus()) { return; } MediaServer serverItem = mediaServerService.getOne(event.getMediaServerItem().getId()); if (serverItem == null) { return; } log.info("[ABL-HOOK事件-服务启动] ID:" + event.getMediaServerItem().getId()); online(serverItem, null); } @Async("taskExecutor") @EventListener public void onApplicationEvent(HookAblServerKeepaliveEvent event) { if (event.getMediaServerItem() == null) { return; } MediaServer serverItem = mediaServerService.getOne(event.getMediaServerItem().getId()); if (serverItem == null) { return; } log.info("[ABL-HOOK事件-心跳] ID:" + event.getMediaServerItem().getId()); online(serverItem, null); } @Async("taskExecutor") @EventListener public void onApplicationEvent(MediaServerDeleteEvent event) { if (event.getMediaServer() == null) { return; } log.info("[ABL-节点被移除] ID:" + event.getMediaServer().getServerId()); offlineABLPrimaryMap.remove(event.getMediaServer().getServerId()); offlineAblsecondaryMap.remove(event.getMediaServer().getServerId()); offlineAblTimeMap.remove(event.getMediaServer().getServerId()); } @Scheduled(fixedDelay = 10*1000) //每隔10秒检查一次 public void execute(){ // 初次加入的离线节点会在30分钟内,每间隔十秒尝试一次,30分钟后如果仍然没有上线,则每隔30分钟尝试一次连接 if (offlineABLPrimaryMap.isEmpty() && offlineAblsecondaryMap.isEmpty()) { return; } if (!offlineABLPrimaryMap.isEmpty()) { for (MediaServer mediaServerItem : offlineABLPrimaryMap.values()) { if (offlineAblTimeMap.get(mediaServerItem.getId()) < System.currentTimeMillis() - 30*60*1000) { offlineAblsecondaryMap.put(mediaServerItem.getId(), mediaServerItem); offlineABLPrimaryMap.remove(mediaServerItem.getId()); continue; } log.info("[ABL-尝试连接] ID:{}, 地址: {}:{}", mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort()); ABLResult ablResult = ablResTfulUtils.getServerConfig(mediaServerItem); AblServerConfig ablServerConfig = null; if (ablResult.getCode() != 0) { log.info("[ABL-尝试连接]失败, ID:{}, 地址: {}:{}", mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort()); continue; } JSONArray params = ablResult.getParams(); if (params == null || params.isEmpty()) { log.info("[ABL-尝试连接]失败, ID:{}, 地址: {}:{}", mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort()); }else { ablServerConfig = AblServerConfig.getInstance(params); initPort(mediaServerItem, ablServerConfig); online(mediaServerItem, ablServerConfig); } } } if (!offlineAblsecondaryMap.isEmpty()) { for (MediaServer mediaServerItem : offlineAblsecondaryMap.values()) { if (offlineAblTimeMap.get(mediaServerItem.getId()) < System.currentTimeMillis() - 30*60*1000) { continue; } log.info("[ABL-尝试连接] ID:{}, 地址: {}:{}", mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort()); ABLResult ablResult = ablResTfulUtils.getServerConfig(mediaServerItem); AblServerConfig ablServerConfig = null; if (ablResult.getCode() != 0) { log.info("[ABL-尝试连接]失败, ID:{}, 地址: {}:{}", mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort()); offlineAblTimeMap.put(mediaServerItem.getId(), System.currentTimeMillis()); continue; } JSONArray params = ablResult.getParams(); if (params == null || params.isEmpty()) { log.info("[ABL-尝试连接]失败, ID:{}, 地址: {}:{}", mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort()); offlineAblTimeMap.put(mediaServerItem.getId(), System.currentTimeMillis()); }else { ablServerConfig = AblServerConfig.getInstance(params); initPort(mediaServerItem, ablServerConfig); online(mediaServerItem, ablServerConfig); } } } } private void online(MediaServer mediaServer, AblServerConfig config) { if (config == null) { ABLResult ablResult = ablResTfulUtils.getServerConfig(mediaServer); JSONArray data = ablResult.getParams(); if (data != null && !data.isEmpty()) { config = AblServerConfig.getInstance(data); }else { log.info("[ABL-连接成功] 读取流媒体配置失败 ID:{}, 地址: {}:{}", mediaServer.getId(), mediaServer.getIp(), mediaServer.getHttpPort()); return; } } offlineABLPrimaryMap.remove(mediaServer.getId()); offlineAblsecondaryMap.remove(mediaServer.getId()); offlineAblTimeMap.remove(mediaServer.getId()); log.info("[ABL-连接成功] ID:{}, 地址: {}:{}", mediaServer.getId(), mediaServer.getIp(), mediaServer.getHttpPort()); mediaServer.setStatus(true); mediaServer.setHookAliveInterval(10F); initPort(mediaServer, config); // 发送上线通知 eventPublisher.mediaServerOnlineEventPublish(mediaServer); mediaServerService.update(mediaServer); // 设置两次心跳未收到则认为zlm离线 String key = "ABL-keepalive-" + mediaServer.getId(); dynamicTask.startDelay(key, ()->{ log.warn("[ABL-心跳超时] ID:{}", mediaServer.getId()); mediaServer.setStatus(false); offlineABLPrimaryMap.put(mediaServer.getId(), mediaServer); offlineAblTimeMap.put(mediaServer.getId(), System.currentTimeMillis()); // TODO 发送离线通知 mediaServerService.update(mediaServer); }, (int)(mediaServer.getHookAliveInterval() * 2 * 1000)); } private void initPort(MediaServer mediaServer, AblServerConfig ablServerConfig) { // 端口只会从配置中读取一次,一旦自己配置或者读取过了将不在配置 if (ablServerConfig.getRtmpPort() != null && mediaServer.getRtmpPort() != ablServerConfig.getRtmpPort()) { mediaServer.setRtmpPort(ablServerConfig.getRtmpPort()); } if (ablServerConfig.getRtspPort() != null && mediaServer.getRtspPort() != ablServerConfig.getRtspPort()) { mediaServer.setRtspPort(ablServerConfig.getRtspPort()); } if (ablServerConfig.getHttpFlvPort() != null && mediaServer.getFlvPort() != ablServerConfig.getHttpFlvPort()) { mediaServer.setFlvPort(ablServerConfig.getHttpFlvPort()); } if (ablServerConfig.getHttpMp4Port() != null && mediaServer.getMp4Port() != ablServerConfig.getHttpMp4Port()) { mediaServer.setMp4Port(ablServerConfig.getHttpMp4Port()); } if (ablServerConfig.getWsFlvPort() != null && mediaServer.getWsFlvPort() != ablServerConfig.getWsFlvPort()) { mediaServer.setWsFlvPort(ablServerConfig.getWsFlvPort()); } if (ablServerConfig.getPsTsRecvPort() != null && mediaServer.getRtpProxyPort() != ablServerConfig.getPsTsRecvPort()) { mediaServer.setRtpProxyPort(ablServerConfig.getPsTsRecvPort()); } if (ablServerConfig.getJtt1078RecvPort() != null && mediaServer.getRtpProxyPort() != ablServerConfig.getJtt1078RecvPort()) { mediaServer.setJttProxyPort(ablServerConfig.getJtt1078RecvPort()); } mediaServer.setHookAliveInterval(10F); } public void setAblConfig(MediaServer mediaServerItem, boolean restart, AblServerConfig config) { try { if (config.getHookEnable() == 0) { log.info("[媒体服务节点-ABL] 开启HOOK功能 :{}", mediaServerItem.getId()); ABLResult ablResult = ablResTfulUtils.setConfigParamValue(mediaServerItem, "hook_enable", "1"); if (ablResult.getCode() == 0) { log.info("[媒体服务节点-ABL] 开启HOOK功能成功 :{}", mediaServerItem.getId()); }else { log.info("[媒体服务节点-ABL] 开启HOOK功能失败 :{}->{}", mediaServerItem.getId(), ablResult.getMemo()); } } }catch (Exception e) { log.info("[媒体服务节点-ABL] 开启HOOK功能失败 :{}", mediaServerItem.getId(), e); } // 设置相关的HOOK String[] hookUrlArray = { "on_stream_arrive", "on_stream_none_reader", "on_record_mp4", "on_stream_disconnect", "on_stream_not_found", "on_server_started", "on_publish", "on_play", "on_record_progress", "on_server_keepalive", "on_stream_not_arrive", "on_delete_record_mp4", }; String protocol = sslEnabled ? "https" : "http"; String hookPrefix = String.format("%s://%s:%s/index/hook/abl", protocol, mediaServerItem.getHookIp(), serverPort); Field[] fields = AblServerConfig.class.getDeclaredFields(); for (Field field : fields) { try { if (field.isAnnotationPresent(ConfigKeyId.class)) { ConfigKeyId configKeyId = field.getAnnotation(ConfigKeyId.class); for (String hook : hookUrlArray) { if (configKeyId.value().equals(hook)) { String hookUrl = String.format("%s/%s", hookPrefix, hook); field.setAccessible(true); // 利用反射获取值后对比是否与配置中相同,不同则进行设置 if (!hookUrl.equals(field.get(config))) { ABLResult ablResult = ablResTfulUtils.setConfigParamValue(mediaServerItem, hook, hookUrl); if (ablResult.getCode() == 0) { log.info("[媒体服务节点-ABL] 设置HOOK {} 成功 :{}", hook, mediaServerItem.getId()); }else { log.info("[媒体服务节点-ABL] 设置HOOK {} 失败 :{}->{}", hook, mediaServerItem.getId(), ablResult.getMemo()); } } } } } }catch (Exception e) { log.info("[媒体服务节点-ABL] 设置HOOK 失败 :{}", mediaServerItem.getId(), e); } } // Map param = new HashMap<>(); // param.put("api.secret",mediaServerItem.getSecret()); // -profile:v Baseline // if (mediaServerItem.getRtspPort() != 0) { // param.put("ffmpeg.snap", "%s -rtsp_transport tcp -i %s -y -f mjpeg -frames:v 1 %s"); // } // param.put("hook.enable","1"); // param.put("hook.on_flow_report",""); // param.put("hook.on_play",String.format("%s/on_play", hookPrefix)); // param.put("hook.on_http_access",""); // param.put("hook.on_publish", String.format("%s/on_publish", hookPrefix)); // param.put("hook.on_record_ts",""); // param.put("hook.on_rtsp_auth",""); // param.put("hook.on_rtsp_realm",""); // param.put("hook.on_server_started",String.format("%s/on_server_started", hookPrefix)); // param.put("hook.on_shell_login",""); // param.put("hook.on_stream_changed",String.format("%s/on_stream_changed", hookPrefix)); // param.put("hook.on_stream_none_reader",String.format("%s/on_stream_none_reader", hookPrefix)); // param.put("hook.on_stream_not_found",String.format("%s/on_stream_not_found", hookPrefix)); // param.put("hook.on_server_keepalive",String.format("%s/on_server_keepalive", hookPrefix)); // param.put("hook.on_send_rtp_stopped",String.format("%s/on_send_rtp_stopped", hookPrefix)); // param.put("hook.on_rtp_server_timeout",String.format("%s/on_rtp_server_timeout", hookPrefix)); // param.put("hook.on_record_mp4",String.format("%s/on_record_mp4", hookPrefix)); // param.put("hook.timeoutSec","30"); // param.put("hook.alive_interval", mediaServerItem.getHookAliveInterval()); // // 推流断开后可以在超时时间内重新连接上继续推流,这样播放器会接着播放。 // // 置0关闭此特性(推流断开会导致立即断开播放器) // // 此参数不应大于播放器超时时间 // // 优化此消息以更快的收到流注销事件 // param.put("protocol.continue_push_ms", "3000" ); // // 最多等待未初始化的Track时间,单位毫秒,超时之后会忽略未初始化的Track, 设置此选项优化那些音频错误的不规范流, // // 等zlm支持给每个rtpServer设置关闭音频的时候可以不设置此选项 // if (mediaServerItem.isRtpEnable() && !ObjectUtils.isEmpty(mediaServerItem.getRtpPortRange())) { // param.put("rtp_proxy.port_range", mediaServerItem.getRtpPortRange().replace(",", "-")); // } // // if (!ObjectUtils.isEmpty(mediaServerItem.getRecordPath())) { // File recordPathFile = new File(mediaServerItem.getRecordPath()); // param.put("protocol.mp4_save_path", recordPathFile.getParentFile().getPath()); // param.put("protocol.downloadRoot", recordPathFile.getParentFile().getPath()); // param.put("record.appName", recordPathFile.getName()); // } // // JSONObject responseJSON = ablResTfulUtils.setConfigParamValue(mediaServerItem, param); // // if (responseJSON != null && responseJSON.getInteger("code") == 0) { // if (restart) { // log.info("[媒体服务节点] 设置成功,开始重启以保证配置生效 {} -> {}:{}", // mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort()); // ablResTfulUtils.restartServer(mediaServerItem); // }else { // log.info("[媒体服务节点] 设置成功 {} -> {}:{}", // mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort()); // } // }else { // log.info("[媒体服务节点] 设置媒体服务节点失败 {} -> {}:{}", // mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort()); // } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/abl/ABLRESTfulUtils.java ================================================ package com.genersoft.iot.vmp.media.abl; import com.alibaba.fastjson2.JSON; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.media.abl.bean.ABLResult; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import okhttp3.*; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.ConnectException; import java.net.SocketTimeoutException; import java.net.URLEncoder; import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.concurrent.TimeUnit; @Component public class ABLRESTfulUtils { private final static Logger logger = LoggerFactory.getLogger(ABLRESTfulUtils.class); private OkHttpClient client; public interface RequestCallback{ void run(String response); } public interface ResultCallback{ void run(ABLResult response); } private OkHttpClient getClient(){ return getClient(null); } private OkHttpClient getClient(Integer readTimeOut){ if (client == null) { if (readTimeOut == null) { readTimeOut = 10; } OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder(); //todo 暂时写死超时时间 均为5s // 设置连接超时时间 httpClientBuilder.connectTimeout(8,TimeUnit.SECONDS); // 设置读取超时时间 httpClientBuilder.readTimeout(readTimeOut,TimeUnit.SECONDS); // 设置连接池 httpClientBuilder.connectionPool(new ConnectionPool(16, 5, TimeUnit.MINUTES)); client = httpClientBuilder.build(); } return client; } public String sendPost(MediaServer mediaServerItem, String api, Map param, RequestCallback callback) { return sendPost(mediaServerItem, api, param, callback, null); } public String sendPost(MediaServer mediaServerItem, String api, Map param, RequestCallback callback, Integer readTimeOut) { OkHttpClient client = getClient(readTimeOut); if (mediaServerItem == null) { return null; } String url = String.format("http://%s:%s/index/api/%s", mediaServerItem.getIp(), mediaServerItem.getHttpPort(), api); String result = null; FormBody.Builder builder = new FormBody.Builder(); builder.add("secret",mediaServerItem.getSecret()); if (param != null && !param.isEmpty()) { for (String key : param.keySet()){ if (param.get(key) != null) { builder.add(key, param.get(key).toString()); } } } FormBody body = builder.build(); Request request = new Request.Builder() .post(body) .url(url) .build(); if (callback == null) { try { Response response = client.newCall(request).execute(); if (response.isSuccessful()) { ResponseBody responseBody = response.body(); if (responseBody != null) { result = responseBody.string(); } }else { response.close(); Objects.requireNonNull(response.body()).close(); } }catch (IOException e) { logger.error(String.format("[ %s ]请求失败: %s", url, e.getMessage())); if(e instanceof SocketTimeoutException){ //读取超时超时异常 logger.error(String.format("读取ABL数据超时失败: %s, %s", url, e.getMessage())); } if(e instanceof ConnectException){ //判断连接异常,我这里是报Failed to connect to 10.7.5.144 logger.error(String.format("连接ABL连接失败: %s, %s", url, e.getMessage())); } }catch (Exception e){ logger.error(String.format("访问ABL失败: %s, %s", url, e.getMessage())); } }else { client.newCall(request).enqueue(new Callback(){ @Override public void onResponse(@NotNull Call call, @NotNull Response response){ if (response.isSuccessful()) { try { String responseStr = Objects.requireNonNull(response.body()).string(); callback.run(responseStr); } catch (IOException e) { logger.error(String.format("[ %s ]请求失败: %s", url, e.getMessage())); } }else { response.close(); Objects.requireNonNull(response.body()).close(); } } @Override public void onFailure(@NotNull Call call, @NotNull IOException e) { logger.error(String.format("连接ABL失败: %s, %s", call.request().toString(), e.getMessage())); if(e instanceof SocketTimeoutException){ //读取超时超时异常 logger.error(String.format("读取ABL数据失败: %s, %s", call.request().toString(), e.getMessage())); } if(e instanceof ConnectException){ //判断连接异常,我这里是报Failed to connect to 10.7.5.144 logger.error(String.format("连接ABL失败: %s, %s", call.request().toString(), e.getMessage())); } } }); } return result; } public String sendGet(MediaServer mediaServerItem, String api, Map param) { OkHttpClient client = getClient(); if (mediaServerItem == null) { return null; } String result = null; StringBuilder stringBuffer = new StringBuilder(); stringBuffer.append(String.format("http://%s:%s/index/api/%s", mediaServerItem.getIp(), mediaServerItem.getHttpPort(), api)); if (param != null && !param.keySet().isEmpty()) { stringBuffer.append("?secret=").append(mediaServerItem.getSecret()).append("&"); int index = 1; for (String key : param.keySet()){ if (param.get(key) != null) { stringBuffer.append(key + "=" + param.get(key)); if (index < param.size()) { stringBuffer.append("&"); } } index++; } } String url = stringBuffer.toString(); logger.info("[访问ABL]: {}", url); Request request = new Request.Builder() .get() .url(url) .build(); try { Response response = client.newCall(request).execute(); if (response.isSuccessful()) { ResponseBody responseBody = response.body(); if (responseBody != null) { result = responseBody.string(); } }else { response.close(); Objects.requireNonNull(response.body()).close(); } } catch (ConnectException e) { logger.error(String.format("连接ABL失败: %s, %s", e.getCause().getMessage(), e.getMessage())); logger.info("请检查media配置并确认ABL已启动..."); }catch (IOException e) { logger.error(String.format("[ %s ]请求失败: %s", url, e.getMessage())); } return result; } public void sendGetForImg(MediaServer mediaServerItem, String api, Map params, String targetPath, String fileName) { String url = String.format("http://%s:%s/index/api/%s", mediaServerItem.getIp(), mediaServerItem.getHttpPort(), api); HttpUrl parseUrl = HttpUrl.parse(url); if (parseUrl == null) { return; } HttpUrl.Builder httpBuilder = parseUrl.newBuilder(); httpBuilder.addQueryParameter("secret", mediaServerItem.getSecret()); if (params != null) { for (Map.Entry param : params.entrySet()) { httpBuilder.addQueryParameter(param.getKey(), param.getValue().toString()); } } Request request = new Request.Builder() .url(httpBuilder.build()) .build(); logger.info(request.toString()); try { OkHttpClient client = getClient(); Response response = client.newCall(request).execute(); if (response.isSuccessful()) { if (targetPath != null) { File snapFolder = new File(targetPath); if (!snapFolder.exists()) { if (!snapFolder.mkdirs()) { logger.warn("{}路径创建失败", snapFolder.getAbsolutePath()); } } File snapFile = new File(targetPath + File.separator + fileName); FileOutputStream outStream = new FileOutputStream(snapFile); outStream.write(Objects.requireNonNull(response.body()).bytes()); outStream.flush(); outStream.close(); } else { logger.error(String.format("[ %s ]请求失败: %s %s", url, response.code(), response.message())); } } else { logger.error(String.format("[ %s ]请求失败: %s %s", url, response.code(), response.message())); } Objects.requireNonNull(response.body()).close(); } catch (ConnectException e) { logger.error(String.format("连接ABL失败: %s, %s", e.getCause().getMessage(), e.getMessage())); logger.info("请检查media配置并确认ABL已启动..."); } catch (IOException e) { logger.error(String.format("[ %s ]请求失败: %s", url, e.getMessage())); } } public void sendGetForImgForUrl(String url, String targetPath, String fileName) { HttpUrl parseUrl = HttpUrl.parse(url); if (parseUrl == null) { return; } HttpUrl.Builder httpBuilder = parseUrl.newBuilder(); Request request = new Request.Builder() .url(httpBuilder.build()) .build(); logger.info(request.toString()); try { OkHttpClient client = getClient(); Response response = client.newCall(request).execute(); if (response.isSuccessful()) { if (targetPath != null) { File snapFolder = new File(targetPath); if (!snapFolder.exists()) { if (!snapFolder.mkdirs()) { logger.warn("{}路径创建失败", snapFolder.getAbsolutePath()); } } File snapFile = new File(targetPath + File.separator + fileName); FileOutputStream outStream = new FileOutputStream(snapFile); outStream.write(Objects.requireNonNull(response.body()).bytes()); outStream.flush(); outStream.close(); } else { logger.error(String.format("[ %s ]请求失败: %s %s", url, response.code(), response.message())); } } else { logger.error(String.format("[ %s ]请求失败: %s %s", url, response.code(), response.message())); } Objects.requireNonNull(response.body()).close(); } catch (ConnectException e) { logger.error(String.format("连接ABL失败: %s, %s", e.getCause().getMessage(), e.getMessage())); logger.info("请检查media配置并确认ABL已启动..."); } catch (IOException e) { logger.error(String.format("[ %s ]请求失败: %s", url, e.getMessage())); } } public Integer openRtpServer(MediaServer mediaServer, String app, String stream, int payload, Integer port, Integer tcpMode, Integer disableAudio, Boolean record, Boolean isJtt) { Map param = new HashMap<>(); param.put("vhost", "_defaultVhost_"); param.put("app", app); param.put("stream_id", stream); param.put("payload", payload); if (isJtt) { // 1 PS 国标gb28181, 默认为1、 // 2 ES 视频支持 H246\H265,音频只支持G711A、G711U 、AAC // 3 XHB (一家公司的打包格式) 只支持视频,音频不能加入打包 // 4 、Jt1078(2016版本)码流接入 param.put("RtpPayloadDataType", 4); param.put("jtt1078_version", "2019"); } if (port != null) { param.put("port", port); }else { param.put("port", 0); } if (tcpMode != null) { param.put("enable_tcp", tcpMode); } if (disableAudio != null) { param.put("disableAudio", disableAudio); } if (record != null && record) { param.put("enable_mp4", 1); } String response = sendPost(mediaServer, "openRtpServer", param, null); if (response == null) { return 0; }else { ABLResult ablResult = JSON.parseObject(response, ABLResult.class); if (ablResult.getCode() == 0) { return ablResult.getPort(); }else { return 0; } } } public ABLResult closeStreams(MediaServer mediaServerItem, String app, String stream) { Map param = new HashMap<>(); param.put("vhost", "__defaultVhost__"); param.put("app", app); param.put("stream", stream); param.put("force", 1); String response = sendPost(mediaServerItem, "close_streams", param, null); ABLResult ablResult = JSON.parseObject(response, ABLResult.class); if (ablResult == null) { return ABLResult.getFailForMediaServer(); }else { return ablResult; } } public ABLResult getServerConfig(MediaServer mediaServerItem){ String response = sendPost(mediaServerItem, "getServerConfig", null, null); ABLResult ablResult = JSON.parseObject(response, ABLResult.class); if (ablResult == null) { return ABLResult.getFailForMediaServer(); }else { return ablResult; } } public ABLResult setConfigParamValue(MediaServer mediaServerItem, String key, Object value){ Map param = new HashMap<>(); param.put("key", key); param.put("value", value); String response = sendGet(mediaServerItem, "setConfigParamValue", param); ABLResult ablResult = JSON.parseObject(response, ABLResult.class); if (ablResult == null) { return ABLResult.getFailForMediaServer(); }else { return ablResult; } } public void stopSendRtp(MediaServer mediaServer,String key) { Map param = new HashMap<>(); param.put("key", key); sendPost(mediaServer,"stopSendRtp", param, null); } public ABLResult getMediaList(MediaServer mediaServer, String app, String stream) { Map param = new HashMap<>(); param.put("app", app); if (stream != null) { param.put("stream", stream); } String response = sendGet(mediaServer, "getMediaList", param); ABLResult ablResult = JSON.parseObject(response, ABLResult.class); if (ablResult == null) { return ABLResult.getFailForMediaServer(); }else { return ablResult; } } public ABLResult queryRecordList(MediaServer mediaServer, String app, String stream, String startTime, String endTime) { Map param = new HashMap<>(); param.put("app", app); param.put("stream", stream); param.put("starttime", startTime); param.put("endtime", endTime); String response = sendGet(mediaServer, "queryRecordList", param); ABLResult ablResult = JSON.parseObject(response, ABLResult.class); if (ablResult == null) { return ABLResult.getFailForMediaServer(); }else { return ablResult; } } public void getSnap(MediaServer mediaServer, String app, String stream, int timeoutSec, String path, String fileName) { Map param = new HashMap<>(); param.put("app", app); param.put("stream", stream); param.put("timeout_sec", timeoutSec); param.put("vhost", "_defaultVhost_"); // JSONObject jsonObject = sendPost(mediaServer, "getSnap", param, null); // if (jsonObject != null && jsonObject.getInteger("code") == 0) { // String url = jsonObject.getString("url"); // sendGetForImgForUrl(url, path, fileName); // } sendGetForImg(mediaServer, "getSnap", param, path, fileName); } public ABLResult addStreamProxy(MediaServer mediaServer, String app, String stream, String url, boolean disableAudio, boolean enableMp4, String rtpType, Integer timeout) { try { url = URLEncoder.encode(url, "UTF-8"); } catch (UnsupportedEncodingException e) { throw new ControllerException(ErrorCode.ERROR100.getCode(),"url编码失败"); } Map param = new HashMap<>(); param.put("app", app); param.put("stream", stream); param.put("url", url); param.put("disableAudio", disableAudio? "1" : "0"); param.put("enable_mp4", enableMp4 ? "1" : "0"); // TODO rtpType timeout 尚不支持 String response = sendGet(mediaServer, "addStreamProxy", param); ABLResult ablResult = JSON.parseObject(response, ABLResult.class); if (ablResult == null) { return ABLResult.getFailForMediaServer(); }else { return ablResult; } } public ABLResult addFFmpegProxy(MediaServer mediaServer, String app, String stream, String url, boolean disableAudio, boolean enableMp4, String rtpType, Integer timeout) { try { url = URLEncoder.encode(url, "UTF-8"); } catch (UnsupportedEncodingException e) { throw new ControllerException(ErrorCode.ERROR100.getCode(),"url编码失败"); } Map param = new HashMap<>(); param.put("app", app); param.put("stream", stream); param.put("url", url); param.put("disableAudio", disableAudio); param.put("enable_mp4", enableMp4); // TODO rtpType timeout 尚不支持 String response = sendGet(mediaServer, "addFFmpegProxy", param); ABLResult ablResult = JSON.parseObject(response, ABLResult.class); if (ablResult == null) { return ABLResult.getFailForMediaServer(); }else { return ablResult; } } public ABLResult delStreamProxy(MediaServer mediaServer, String streamKey) { Map param = new HashMap<>(); param.put("key", streamKey); String response = sendGet(mediaServer, "delStreamProxy", param); ABLResult ablResult = JSON.parseObject(response, ABLResult.class); if (ablResult == null) { return ABLResult.getFailForMediaServer(); }else { return ablResult; } } public ABLResult delFFmpegProxy(MediaServer mediaServer, String streamKey) { Map param = new HashMap<>(); param.put("key", streamKey); String response = sendGet(mediaServer, "delFFmpegProxy", param); ABLResult ablResult = JSON.parseObject(response, ABLResult.class); if (ablResult == null) { return ABLResult.getFailForMediaServer(); }else { return ablResult; } } public ABLResult pauseRtpServer(MediaServer mediaServer, String streamKey) { Map param = new HashMap<>(); param.put("key", streamKey); String response = sendGet(mediaServer, "pauseRtpServer", param); ABLResult ablResult = JSON.parseObject(response, ABLResult.class); if (ablResult == null) { return ABLResult.getFailForMediaServer(); }else { return ablResult; } } public ABLResult resumeRtpServer(MediaServer mediaServer, String streamKey) { Map param = new HashMap<>(); param.put("key", streamKey); String response = sendGet(mediaServer, "resumeRtpServer", param); ABLResult ablResult = JSON.parseObject(response, ABLResult.class); if (ablResult == null) { return ABLResult.getFailForMediaServer(); }else { return ablResult; } } public ABLResult controlRecordPlay(MediaServer mediaServer, String app, String stream, String command, String value) { Map param = new HashMap<>(); param.put("app", app); param.put("stream", stream); param.put("command", command); param.put("value", value); String response = sendGet(mediaServer, "controlRecordPlay", param); ABLResult ablResult = JSON.parseObject(response, ABLResult.class); if (ablResult == null) { return ABLResult.getFailForMediaServer(); }else { return ablResult; } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/abl/bean/ABLMedia.java ================================================ package com.genersoft.iot.vmp.media.abl.bean; import lombok.Data; @Data public class ABLMedia { private String key; private String app; private String stream; private Integer sourceType; private Long duration; private String sim; private Boolean status; private Boolean enable_hls; private Boolean transcodingStatus; private String sourceURL; private Integer networkType; private Integer readerCount; private String videoCodec; private Integer width; private Integer height; private String audioCodec; private Integer audioChannels; private Integer audioSampleRate; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/abl/bean/ABLRecordFile.java ================================================ package com.genersoft.iot.vmp.media.abl.bean; import lombok.Data; @Data public class ABLRecordFile { private String file; private Long duration; private ABLUrls url; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/abl/bean/ABLResult.java ================================================ package com.genersoft.iot.vmp.media.abl.bean; import com.alibaba.fastjson2.JSONArray; import lombok.Data; import java.util.List; @Data public class ABLResult { private int code; private String memo; private String key; private Integer port; private JSONArray params; private List mediaList; private String app; private String stream; private String starttime; private String endtime; private Long duration; private ABLUrls url; private List recordFileList; public static ABLResult getFailForMediaServer() { ABLResult zlmResult = new ABLResult(); zlmResult.setCode(-2); zlmResult.setMemo("流媒体调用失败"); return zlmResult; } public static ABLResult getMediaServer(int code, String msg) { ABLResult zlmResult = new ABLResult(); zlmResult.setCode(code); zlmResult.setMemo(msg); return zlmResult; } @Override public String toString() { return "ZLMResult{" + "code=" + code + ", memo='" + memo + '\'' + (key != null ? (", key=" + key) : "") + (port != null ? (", port=" + port) : "") + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/abl/bean/ABLUrls.java ================================================ package com.genersoft.iot.vmp.media.abl.bean; import com.alibaba.fastjson2.annotation.JSONField; import lombok.Data; @Data public class ABLUrls { private String rtsp; private String rtmp; @JSONField(name = "http-flv") private String httpFlv; @JSONField(name = "ws-flv") private String wsFlv; @JSONField(name = "http-mp4") private String httpMp4; @JSONField(name = "http-hls") private String httpHls; private String download; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/abl/bean/AblServerConfig.java ================================================ package com.genersoft.iot.vmp.media.abl.bean; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import lombok.Data; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; @Data public class AblServerConfig { @ConfigKeyId("secret") private String secret; @ConfigKeyId("ServerIP") private String serverIp; @ConfigKeyId("mediaServerID") private String mediaServerId; @ConfigKeyId("hook_enable") private Integer hookEnable; @ConfigKeyId("enable_audio") private Integer enableAudio; @ConfigKeyId("httpServerPort") private Integer httpServerPort; @ConfigKeyId("rtspPort") private Integer rtspPort; @ConfigKeyId("rtmpPort") private Integer rtmpPort; @ConfigKeyId("httpFlvPort") private Integer httpFlvPort; @ConfigKeyId("hls_enable") private Integer hlsEnable; @ConfigKeyId("hlsPort") private Integer hlsPort; @ConfigKeyId("wsFlvPort") private Integer wsFlvPort; @ConfigKeyId("httpMp4Port") private Integer httpMp4Port; @ConfigKeyId("ps_tsRecvPort") private Integer psTsRecvPort; @ConfigKeyId("1078Port") private Integer jtt1078RecvPort; @ConfigKeyId("hlsCutType") private Integer hlsCutType; @ConfigKeyId("h265CutType") private Integer h265CutType; @ConfigKeyId("RecvThreadCount") private Integer RecvThreadCount; @ConfigKeyId("SendThreadCount") private Integer SendThreadCount; @ConfigKeyId("GB28181RtpTCPHeadType") private Integer GB28181RtpTCPHeadType; @ConfigKeyId("ReConnectingCount") private Integer ReConnectingCount; @ConfigKeyId("maxTimeNoOneWatch") private Integer maxTimeNoOneWatch; @ConfigKeyId("pushEnable_mp4") private Integer pushEnableMp4; @ConfigKeyId("fileSecond") private Integer fileSecond; @ConfigKeyId("fileKeepMaxTime") private Integer fileKeepMaxTime; @ConfigKeyId("httpDownloadSpeed") private Integer httpDownloadSpeed; @ConfigKeyId("RecordReplayThread") private Integer RecordReplayThread; @ConfigKeyId("convertMaxObject") private Integer convertMaxObject; @ConfigKeyId("version") private String version; @ConfigKeyId("recordPath") private String recordPath; @ConfigKeyId("picturePath") private String picturePath; @ConfigKeyId("noneReaderDuration") private Integer noneReaderDuration; @ConfigKeyId("on_server_started") private String onServerStarted; @ConfigKeyId("on_server_keepalive") private String onServerKeepalive; @ConfigKeyId("on_play") private String onPlay; @ConfigKeyId("on_publish") private String onPublish; @ConfigKeyId("on_stream_arrive") private String onStreamArrive; @ConfigKeyId("on_stream_not_arrive") private String onStreamNotArrive; @ConfigKeyId("on_stream_none_reader") private String onStreamNoneReader; @ConfigKeyId("on_stream_disconnect") private String onStreamDisconnect; @ConfigKeyId("on_stream_not_found") private String onStreamNotFound; @ConfigKeyId("on_record_mp4") private String onRecordMp4; @ConfigKeyId("on_delete_record_mp4") private String onDeleteRecordMp4; @ConfigKeyId("on_record_progress") private String onRecordProgress; @ConfigKeyId("on_record_ts") private String onRecordTs; @ConfigKeyId("enable_GetFileDuration") private Integer enableGetFileDuration; @ConfigKeyId("keepaliveDuration") private Integer keepaliveDuration; @ConfigKeyId("captureReplayType") private Integer captureReplayType; @ConfigKeyId("pictureMaxCount") private Integer pictureMaxCount; @ConfigKeyId("videoFileFormat") private Integer videoFileFormat; @ConfigKeyId("MaxDiconnectTimeoutSecond") private Integer maxDiconnectTimeoutSecond; @ConfigKeyId("G711ConvertAAC") private Integer g711ConvertAAC; @ConfigKeyId("filterVideo_enable") private Integer filterVideoEnable; @ConfigKeyId("filterVideo_text") private String filterVideoText; @ConfigKeyId("FilterFontSize") private Integer filterFontSize; @ConfigKeyId("FilterFontColor") private String filterFontColor; @ConfigKeyId("FilterFontLeft") private Integer filterFontLeft; @ConfigKeyId("FilterFontTop") private Integer filterFontTop; @ConfigKeyId("FilterFontAlpha") private Double filterFontAlpha; @ConfigKeyId("convertOutWidth") private Integer convertOutWidth; @ConfigKeyId("convertOutHeight") private Integer convertOutHeight; @ConfigKeyId("convertOutBitrate") private Integer convertOutBitrate; @ConfigKeyId("flvPlayAddMute") private Integer flvPlayAddMute; @ConfigKeyId("gb28181LibraryUse") private Integer gb28181LibraryUse; @ConfigKeyId("rtc.listening-ip") private String rtcListeningIp; @ConfigKeyId("rtc.listening-port") private Integer rtcListeningIpPort; @ConfigKeyId("rtc.external-ip") private String rtcExternalIp; @ConfigKeyId("rtc.realm") private String rtcRealm; @ConfigKeyId("rtc.user") private String rtcUser; @ConfigKeyId("rtc.min-port") private Integer rtcMinPort; @ConfigKeyId("rtc.max-port") private Integer rtcMaxPort; public static AblServerConfig getInstance(JSONArray jsonArray) { if (jsonArray == null || jsonArray.isEmpty()) { return null; } AblServerConfig ablServerConfig = new AblServerConfig(); Field[] fields = AblServerConfig.class.getDeclaredFields(); Map fieldMap = new HashMap<>(); for (Field field : fields) { if (field.isAnnotationPresent(ConfigKeyId.class)) { ConfigKeyId configKeyId = field.getAnnotation(ConfigKeyId.class); fieldMap.put(configKeyId.value(), field); } } for (int i = 0; i < jsonArray.size(); i++) { JSONObject jsonObject = jsonArray.getJSONObject(i); if (jsonObject == null) { continue; } for (String key : fieldMap.keySet()) { if (jsonObject.containsKey(key)) { Field field = fieldMap.get(key); field.setAccessible(true); try { field.set(ablServerConfig, jsonObject.getObject(key, fieldMap.get(key).getType())); } catch (IllegalAccessException e) {} } } } return ablServerConfig; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/abl/bean/ConfigKeyId.java ================================================ package com.genersoft.iot.vmp.media.abl.bean; import java.lang.annotation.*; @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ConfigKeyId { String value(); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/abl/bean/hook/ABLHookParam.java ================================================ package com.genersoft.iot.vmp.media.abl.bean.hook; public class ABLHookParam { private String mediaServerId; /** * 应用名 */ private String app; /** * 流id */ private String stream; /** * 媒体流来源编号,可以根据这个key进行关闭流媒体 可以调用delMediaStream或close_streams 函数进行关闭 */ private String key; /** * 媒体流来源网络编号,可参考附表 */ private Integer networkType; public String getMediaServerId() { return mediaServerId; } public void setMediaServerId(String mediaServerId) { this.mediaServerId = mediaServerId; } public String getApp() { return app; } public void setApp(String app) { this.app = app; } public String getStream() { return stream; } public void setStream(String stream) { this.stream = stream; } public String getKey() { return key; } public void setKey(String key) { this.key = key; } public Integer getNetworkType() { return networkType; } public void setNetworkType(Integer networkType) { this.networkType = networkType; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/abl/bean/hook/OnPlayABLHookParam.java ================================================ package com.genersoft.iot.vmp.media.abl.bean.hook; import lombok.Data; import lombok.EqualsAndHashCode; @EqualsAndHashCode(callSuper = true) @Data public class OnPlayABLHookParam extends ABLHookParam{ private String ip; private Integer port; private String params; @Override public String toString() { return "OnPlayABLHookParam{" + "ip='" + ip + '\'' + ", port=" + port + ", params='" + params + '\'' + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/abl/bean/hook/OnPublishABLHookParam.java ================================================ package com.genersoft.iot.vmp.media.abl.bean.hook; public class OnPublishABLHookParam extends ABLHookParam{ private String ip; private Integer port; private String params; public String getIp() { return ip; } public void setIp(String ip) { this.ip = ip; } public Integer getPort() { return port; } public void setPort(Integer port) { this.port = port; } public String getParams() { return params; } public void setParams(String params) { this.params = params; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/abl/bean/hook/OnRecordMp4ABLHookParam.java ================================================ package com.genersoft.iot.vmp.media.abl.bean.hook; import lombok.Getter; import lombok.Setter; @Getter @Setter public class OnRecordMp4ABLHookParam extends ABLHookParam{ private String fileName; private String startTime; private String endTime; private long fileSize; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/abl/bean/hook/OnRecordProgressABLHookParam.java ================================================ package com.genersoft.iot.vmp.media.abl.bean.hook; public class OnRecordProgressABLHookParam extends OnRecordMp4ABLHookParam{ private Integer currentFileDuration; private Integer TotalVideoDuration; private String startTime; private String endTime; public Integer getCurrentFileDuration() { return currentFileDuration; } public void setCurrentFileDuration(Integer currentFileDuration) { this.currentFileDuration = currentFileDuration; } public Integer getTotalVideoDuration() { return TotalVideoDuration; } public void setTotalVideoDuration(Integer totalVideoDuration) { TotalVideoDuration = totalVideoDuration; } public String getStartTime() { return startTime; } public void setStartTime(String startTime) { this.startTime = startTime; } public String getEndTime() { return endTime; } public void setEndTime(String endTime) { this.endTime = endTime; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/abl/bean/hook/OnServerKeepaliveABLHookParam.java ================================================ package com.genersoft.iot.vmp.media.abl.bean.hook; public class OnServerKeepaliveABLHookParam { private String localipAddress; private String mediaServerId; private String datetime; public String getLocalipAddress() { return localipAddress; } public void setLocalipAddress(String localipAddress) { this.localipAddress = localipAddress; } public String getMediaServerId() { return mediaServerId; } public void setMediaServerId(String mediaServerId) { this.mediaServerId = mediaServerId; } public String getDatetime() { return datetime; } public void setDatetime(String datetime) { this.datetime = datetime; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/abl/bean/hook/OnServerStaredABLHookParam.java ================================================ package com.genersoft.iot.vmp.media.abl.bean.hook; public class OnServerStaredABLHookParam { private String localipAddress; private String mediaServerId; private String datetime; public String getLocalipAddress() { return localipAddress; } public void setLocalipAddress(String localipAddress) { this.localipAddress = localipAddress; } public String getMediaServerId() { return mediaServerId; } public void setMediaServerId(String mediaServerId) { this.mediaServerId = mediaServerId; } public String getDatetime() { return datetime; } public void setDatetime(String datetime) { this.datetime = datetime; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/abl/bean/hook/OnStreamArriveABLHookParam.java ================================================ package com.genersoft.iot.vmp.media.abl.bean.hook; import com.genersoft.iot.vmp.media.abl.bean.ABLUrls; import lombok.Getter; import lombok.Setter; /** * 流到来的事件 */ @Getter @Setter public class OnStreamArriveABLHookParam extends ABLHookParam{ /** * 推流鉴权Id */ private String callId; /** * 状态 */ private Boolean status; /** * */ private Boolean enableHls; /** * */ private Boolean transcodingStatus; /** * */ private String sourceURL; /** * */ private Integer readerCount; /** * */ private Integer noneReaderDuration; /** * */ private String videoCodec; /** * */ private Integer videoFrameSpeed; /** * */ private Integer width; /** * */ private Integer height; /** * */ private Integer videoBitrate; /** * */ private String audioCodec; /** * */ private Integer audioChannels; /** * */ private Integer audioSampleRate; /** * */ private Integer audioBitrate; private ABLUrls url; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/abl/event/HookAblServerKeepaliveEvent.java ================================================ package com.genersoft.iot.vmp.media.abl.event; import com.genersoft.iot.vmp.media.bean.MediaServer; import org.springframework.context.ApplicationEvent; /** * zlm 心跳事件 */ public class HookAblServerKeepaliveEvent extends ApplicationEvent { public HookAblServerKeepaliveEvent(Object source) { super(source); } private MediaServer mediaServerItem; public MediaServer getMediaServerItem() { return mediaServerItem; } public void setMediaServerItem(MediaServer mediaServerItem) { this.mediaServerItem = mediaServerItem; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/abl/event/HookAblServerStartEvent.java ================================================ package com.genersoft.iot.vmp.media.abl.event; import com.genersoft.iot.vmp.media.bean.MediaServer; import org.springframework.context.ApplicationEvent; /** * zlm server_start事件 */ public class HookAblServerStartEvent extends ApplicationEvent { public HookAblServerStartEvent(Object source) { super(source); } private MediaServer mediaServerItem; public MediaServer getMediaServerItem() { return mediaServerItem; } public void setMediaServerItem(MediaServer mediaServerItem) { this.mediaServerItem = mediaServerItem; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/bean/MediaInfo.java ================================================ package com.genersoft.iot.vmp.media.bean; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.media.abl.bean.ABLMedia; import com.genersoft.iot.vmp.media.abl.bean.hook.OnStreamArriveABLHookParam; import com.genersoft.iot.vmp.media.zlm.dto.hook.OnStreamChangedHookParam; import com.genersoft.iot.vmp.media.zlm.dto.hook.OriginType; import com.genersoft.iot.vmp.utils.MediaServerUtils; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import org.springframework.util.ObjectUtils; import java.util.List; import java.util.Map; /** * 视频信息 */ @Data @Schema(description = "视频信息") public class MediaInfo { @Schema(description = "应用名") private String app; @Schema(description = "流ID") private String stream; @Schema(description = "流媒体节点") private MediaServer mediaServer; @Schema(description = "协议") private String schema; @Schema(description = "观看人数") private Integer readerCount; @Schema(description = "视频编码类型") private String videoCodec; @Schema(description = "视频宽度") private Integer width; @Schema(description = "视频高度") private Integer height; @Schema(description = "FPS") private Integer fps; @Schema(description = "丢包率") private Integer loss; @Schema(description = "音频编码类型") private String audioCodec; @Schema(description = "音频通道数") private Integer audioChannels; @Schema(description = "音频采样率") private Integer audioSampleRate; @Schema(description = "时长") private Long duration; @Schema(description = "在线") private Boolean online; @Schema(description = "unknown = 0,rtmp_push=1,rtsp_push=2,rtp_push=3,pull=4,ffmpeg_pull=5,mp4_vod=6,device_chn=7,rtc_push=8") private Integer originType; @Schema(description = "originType的文本描述") private String originTypeStr; @Schema(description = "产生流的源流地址") private String originUrl; @Schema(description = "存活时间,单位秒") private Long aliveSecond; @Schema(description = "数据产生速度,单位byte/s") private Long bytesSpeed; @Schema(description = "鉴权参数") private String callId; @Schema(description = "额外参数") private Map paramMap; @Schema(description = "服务ID") private String serverId; public static MediaInfo getInstance(JSONObject jsonObject, MediaServer mediaServer, String serverId) { MediaInfo mediaInfo = new MediaInfo(); mediaInfo.setMediaServer(mediaServer); mediaInfo.setServerId(serverId); String app = jsonObject.getString("app"); mediaInfo.setApp(app); String stream = jsonObject.getString("stream"); mediaInfo.setStream(stream); String schema = jsonObject.getString("schema"); mediaInfo.setSchema(schema); Integer totalReaderCount = jsonObject.getInteger("totalReaderCount"); Boolean online = jsonObject.getBoolean("online"); Integer originType = jsonObject.getInteger("originType"); String originUrl = jsonObject.getString("originUrl"); String originTypeStr = jsonObject.getString("originTypeStr"); Long aliveSecond = jsonObject.getLong("aliveSecond"); String params = jsonObject.getString("params"); Long bytesSpeed = jsonObject.getLong("bytesSpeed"); if (totalReaderCount != null) { mediaInfo.setReaderCount(totalReaderCount); } else { mediaInfo.setReaderCount(0); } if (online != null) { mediaInfo.setOnline(online); } if (originType != null) { mediaInfo.setOriginType(originType); } if (originTypeStr != null) { mediaInfo.setOriginTypeStr(originTypeStr); } if (aliveSecond != null) { mediaInfo.setAliveSecond(aliveSecond); } if (bytesSpeed != null) { mediaInfo.setBytesSpeed(bytesSpeed); } if (params != null) { mediaInfo.setParamMap(MediaServerUtils.urlParamToMap(params)); if(mediaInfo.getCallId() == null) { mediaInfo.setCallId(mediaInfo.getParamMap().get("callId")); } } JSONArray jsonArray = jsonObject.getJSONArray("tracks"); if (!ObjectUtils.isEmpty(jsonArray)) { for (int i = 0; i < jsonArray.size(); i++) { JSONObject trackJson = jsonArray.getJSONObject(i); Integer channels = trackJson.getInteger("channels"); Integer codecId = trackJson.getInteger("codec_id"); Integer codecType = trackJson.getInteger("codec_type"); Integer sampleRate = trackJson.getInteger("sample_rate"); Integer height = trackJson.getInteger("height"); Integer width = trackJson.getInteger("width"); Integer fps = trackJson.getInteger("fps"); Integer loss = trackJson.getInteger("loss"); Integer frames = trackJson.getInteger("frames"); Long keyFrames = trackJson.getLongValue("key_frames"); Integer gop_interval_ms = trackJson.getInteger("gop_interval_ms"); Long gop_size = trackJson.getLongValue("gop_size"); Long duration = trackJson.getLongValue("duration"); if (channels != null) { mediaInfo.setAudioChannels(channels); } if (sampleRate != null) { mediaInfo.setAudioSampleRate(sampleRate); } if (height != null) { mediaInfo.setHeight(height); } if (width != null) { mediaInfo.setWidth(width); } if (fps != null) { mediaInfo.setFps(fps); } if (loss != null) { mediaInfo.setLoss(loss); } if (duration > 0L) { mediaInfo.setDuration(duration); } if (codecId != null) { switch (codecId) { case 0: mediaInfo.setVideoCodec("H264"); break; case 1: mediaInfo.setVideoCodec("H265"); break; case 2: mediaInfo.setAudioCodec("AAC"); break; case 3: mediaInfo.setAudioCodec("G711A"); break; case 4: mediaInfo.setAudioCodec("G711U"); break; } } } } return mediaInfo; } public static MediaInfo getInstance(OnStreamChangedHookParam param, MediaServer mediaServer, String serverId) { MediaInfo mediaInfo = new MediaInfo(); mediaInfo.setApp(param.getApp()); mediaInfo.setStream(param.getStream()); mediaInfo.setSchema(param.getSchema()); mediaInfo.setMediaServer(mediaServer); mediaInfo.setReaderCount(param.getTotalReaderCount()); mediaInfo.setOnline(param.isRegist()); mediaInfo.setOriginType(param.getOriginType()); mediaInfo.setOriginTypeStr(param.getOriginTypeStr()); mediaInfo.setOriginUrl(param.getOriginUrl()); mediaInfo.setOriginUrl(param.getOriginUrl()); mediaInfo.setAliveSecond(param.getAliveSecond()); mediaInfo.setBytesSpeed(param.getBytesSpeed()); mediaInfo.setParamMap(param.getParamMap()); if(mediaInfo.getCallId() == null) { mediaInfo.setCallId(param.getParamMap().get("callId")); } mediaInfo.setServerId(serverId); List tracks = param.getTracks(); if (tracks == null || tracks.isEmpty()) { return mediaInfo; } for (OnStreamChangedHookParam.MediaTrack mediaTrack : tracks) { switch (mediaTrack.getCodec_id()) { case 0: mediaInfo.setVideoCodec("H264"); break; case 1: mediaInfo.setVideoCodec("H265"); break; case 2: mediaInfo.setAudioCodec("AAC"); break; case 3: mediaInfo.setAudioCodec("G711A"); break; case 4: mediaInfo.setAudioCodec("G711U"); break; } if (mediaTrack.getSample_rate() > 0) { mediaInfo.setAudioSampleRate(mediaTrack.getSample_rate()); } if (mediaTrack.getChannels() > 0) { mediaInfo.setAudioChannels(mediaTrack.getChannels()); } if (mediaTrack.getHeight() > 0) { mediaInfo.setHeight(mediaTrack.getHeight()); } if (mediaTrack.getWidth() > 0) { mediaInfo.setWidth(mediaTrack.getWidth()); } } return mediaInfo; } public static MediaInfo getInstance(OnStreamArriveABLHookParam param, MediaServer mediaServer) { MediaInfo mediaInfo = new MediaInfo(); mediaInfo.setApp(param.getApp()); mediaInfo.setStream(param.getStream()); mediaInfo.setMediaServer(mediaServer); mediaInfo.setReaderCount(param.getReaderCount()); mediaInfo.setOnline(true); mediaInfo.setVideoCodec(param.getVideoCodec()); switch (param.getNetworkType()) { case 21: mediaInfo.setOriginType(OriginType.RTMP_PUSH.ordinal()); break; case 23: mediaInfo.setOriginType(OriginType.RTSP_PUSH.ordinal()); break; case 30: case 31: case 32: case 33: mediaInfo.setOriginType(OriginType.PULL.ordinal()); break; default: mediaInfo.setOriginType(OriginType.UNKNOWN.ordinal()); break; } mediaInfo.setWidth(param.getWidth()); mediaInfo.setHeight(param.getHeight()); mediaInfo.setAudioCodec(param.getAudioCodec()); mediaInfo.setAudioChannels(param.getAudioChannels()); mediaInfo.setAudioSampleRate(param.getAudioSampleRate()); return mediaInfo; } public static MediaInfo getInstance(ABLMedia ablMedia, MediaServer mediaServer) { MediaInfo mediaInfo = new MediaInfo(); mediaInfo.setApp(ablMedia.getApp()); mediaInfo.setStream(ablMedia.getStream()); mediaInfo.setMediaServer(mediaServer); mediaInfo.setReaderCount(ablMedia.getReaderCount()); mediaInfo.setOnline(true); mediaInfo.setVideoCodec(ablMedia.getVideoCodec()); switch (ablMedia.getNetworkType()) { case 21: mediaInfo.setOriginType(OriginType.RTMP_PUSH.ordinal()); break; case 23: mediaInfo.setOriginType(OriginType.RTSP_PUSH.ordinal()); break; case 30: case 31: case 32: case 33: mediaInfo.setOriginType(OriginType.PULL.ordinal()); break; default: mediaInfo.setOriginType(OriginType.UNKNOWN.ordinal()); break; } mediaInfo.setWidth(ablMedia.getWidth()); mediaInfo.setHeight(ablMedia.getHeight()); mediaInfo.setAudioCodec(ablMedia.getAudioCodec()); mediaInfo.setAudioChannels(ablMedia.getAudioChannels()); mediaInfo.setAudioSampleRate(ablMedia.getAudioSampleRate()); return mediaInfo; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/bean/MediaServer.java ================================================ package com.genersoft.iot.vmp.media.bean; import com.genersoft.iot.vmp.media.abl.bean.AblServerConfig; import com.genersoft.iot.vmp.media.zlm.dto.ZLMServerConfig; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import org.springframework.util.ObjectUtils; @Schema(description = "流媒体服务信息") @Data public class MediaServer { @Schema(description = "ID") private String id; @Schema(description = "IP") private String ip; @Schema(description = "hook使用的IP(zlm访问WVP使用的IP)") private String hookIp = "127.0.0.1"; @Schema(description = "SDP IP") private String sdpIp; @Schema(description = "流IP") private String streamIp; @Schema(description = "HTTP端口") private int httpPort; @Schema(description = "HTTPS端口") private int httpSSlPort; @Schema(description = "RTMP端口") private int rtmpPort; @Schema(description = "flv端口") private int flvPort; @Schema(description = "https-flv端口") private int flvSSLPort; @Schema(description = "mp4端口") private int mp4Port; @Schema(description = "ws-flv端口") private int wsFlvPort; @Schema(description = "wss-flv端口") private int wsFlvSSLPort; @Schema(description = "RTMPS端口") private int rtmpSSlPort; @Schema(description = "RTP收流端口(单端口模式有用)") private int rtpProxyPort; @Schema(description = "1078收流端口(单端口模式有用)") private int jttProxyPort; @Schema(description = "RTSP端口") private int rtspPort; @Schema(description = "RTSPS端口") private int rtspSSLPort; @Schema(description = "是否开启自动配置ZLM") private boolean autoConfig; @Schema(description = "ZLM鉴权参数") private String secret; @Schema(description = "keepalive hook触发间隔,单位秒") private Float hookAliveInterval; @Schema(description = "是否使用多端口模式") private boolean rtpEnable; @Schema(description = "状态") private boolean status; @Schema(description = "多端口RTP收流端口范围") private String rtpPortRange; @Schema(description = "RTP发流端口范围") private String sendRtpPortRange; @Schema(description = "assist服务端口") private int recordAssistPort; @Schema(description = "创建时间") private String createTime; @Schema(description = "更新时间") private String updateTime; @Schema(description = "上次心跳时间") private String lastKeepaliveTime; @Schema(description = "是否是默认ZLM") private boolean defaultServer; @Schema(description = "录像存储时长") private int recordDay; @Schema(description = "录像存储路径") private String recordPath; @Schema(description = "类型: zlm/abl") private String type; @Schema(description = "转码的前缀") private String transcodeSuffix; @Schema(description = "服务Id") private String serverId; public MediaServer() { } public MediaServer(ZLMServerConfig zlmServerConfig, String sipIp) { id = zlmServerConfig.getGeneralMediaServerId(); ip = zlmServerConfig.getIp(); hookIp = ObjectUtils.isEmpty(zlmServerConfig.getHookIp())? sipIp: zlmServerConfig.getHookIp(); sdpIp = ObjectUtils.isEmpty(zlmServerConfig.getSdpIp())? zlmServerConfig.getIp(): zlmServerConfig.getSdpIp(); streamIp = ObjectUtils.isEmpty(zlmServerConfig.getStreamIp())? zlmServerConfig.getIp(): zlmServerConfig.getStreamIp(); httpPort = zlmServerConfig.getHttpPort(); httpSSlPort = zlmServerConfig.getHttpSSLport(); rtmpPort = zlmServerConfig.getRtmpPort(); rtmpSSlPort = zlmServerConfig.getRtmpSslPort(); rtpProxyPort = zlmServerConfig.getRtpProxyPort(); rtspPort = zlmServerConfig.getRtspPort(); rtspSSLPort = zlmServerConfig.getRtspSSlport(); autoConfig = true; // 默认值true; secret = zlmServerConfig.getApiSecret(); hookAliveInterval = zlmServerConfig.getHookAliveInterval(); rtpEnable = false; // 默认使用单端口;直到用户自己设置开启多端口 rtpPortRange = zlmServerConfig.getPortRange().replace("_",","); // 默认使用30000,30500作为级联时发送流的端口号 recordAssistPort = 0; // 默认关闭 transcodeSuffix = zlmServerConfig.getTranscodeSuffix(); } public MediaServer(AblServerConfig config, String sipIp) { id = config.getMediaServerId(); ip = config.getServerIp(); hookIp = sipIp; sdpIp = config.getServerIp(); streamIp = config.getServerIp(); httpPort = config.getHttpServerPort(); flvPort = config.getHttpFlvPort(); mp4Port = config.getHttpMp4Port(); wsFlvPort = config.getWsFlvPort(); rtmpPort = config.getRtmpPort(); rtpProxyPort = config.getJtt1078RecvPort(); rtspPort = config.getRtspPort(); autoConfig = true; // 默认值true; secret = config.getSecret(); rtpEnable = false; // 默认使用单端口;直到用户自己设置开启多端口 rtpPortRange = "30000,30500"; // 默认使用30000,30500作为级联时发送流的端口号 recordAssistPort = 0; // 默认关闭 } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/bean/RecordInfo.java ================================================ package com.genersoft.iot.vmp.media.bean; import com.genersoft.iot.vmp.media.abl.bean.hook.OnRecordMp4ABLHookParam; import com.genersoft.iot.vmp.media.zlm.dto.hook.OnRecordMp4HookParam; import com.genersoft.iot.vmp.service.bean.CloudRecordItem; import com.genersoft.iot.vmp.utils.DateUtil; import lombok.Data; @Data public class RecordInfo { private String app; private String stream; private String fileName; private String filePath; private long fileSize; private String folder; private String url; /** * 单位毫秒 */ private long startTime; /** * 单位毫秒 */ private double timeLen; private String params; public static RecordInfo getInstance(OnRecordMp4HookParam hookParam) { RecordInfo recordInfo = new RecordInfo(); recordInfo.setApp(hookParam.getApp()); recordInfo.setStream(hookParam.getStream()); recordInfo.setFileName(hookParam.getFile_name()); recordInfo.setUrl(hookParam.getUrl()); recordInfo.setFolder(hookParam.getFolder()); recordInfo.setFilePath(hookParam.getFile_path()); recordInfo.setFileSize(hookParam.getFile_size()); recordInfo.setStartTime(hookParam.getStart_time() * 1000); recordInfo.setTimeLen(hookParam.getTime_len() * 1000); return recordInfo; } public static RecordInfo getInstance(OnRecordMp4ABLHookParam hookParam) { RecordInfo recordInfo = new RecordInfo(); recordInfo.setApp(hookParam.getApp()); recordInfo.setStream(hookParam.getStream()); recordInfo.setFileName(hookParam.getFileName()); recordInfo.setStartTime(DateUtil.urlToTimestampMs(hookParam.getStartTime())); recordInfo.setTimeLen(DateUtil.urlToTimestampMs(hookParam.getEndTime()) - recordInfo.getStartTime()); recordInfo.setFileSize(hookParam.getFileSize()); return recordInfo; } public static RecordInfo getInstance(CloudRecordItem cloudRecordItem) { RecordInfo recordInfo = new RecordInfo(); recordInfo.setApp(cloudRecordItem.getApp()); recordInfo.setStream(cloudRecordItem.getStream()); recordInfo.setFileName(cloudRecordItem.getFileName()); recordInfo.setStartTime(cloudRecordItem.getStartTime()); recordInfo.setTimeLen(cloudRecordItem.getTimeLen()); recordInfo.setFileSize(cloudRecordItem.getFileSize()); recordInfo.setFilePath(cloudRecordItem.getFilePath()); return recordInfo; } @Override public String toString() { return "RecordInfo{" + "文件名称='" + fileName + '\'' + ", 文件路径='" + filePath + '\'' + ", 文件大小=" + fileSize + ", 开始时间=" + startTime + ", 时长=" + timeLen + ", params=" + params + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/bean/ResultForOnPublish.java ================================================ package com.genersoft.iot.vmp.media.bean; public class ResultForOnPublish { private boolean enable_audio; private boolean enable_mp4; private int mp4_max_second; private String mp4_save_path; private String stream_replace; private Integer modify_stamp; public boolean isEnable_audio() { return enable_audio; } public void setEnable_audio(boolean enable_audio) { this.enable_audio = enable_audio; } public boolean isEnable_mp4() { return enable_mp4; } public void setEnable_mp4(boolean enable_mp4) { this.enable_mp4 = enable_mp4; } public int getMp4_max_second() { return mp4_max_second; } public void setMp4_max_second(int mp4_max_second) { this.mp4_max_second = mp4_max_second; } public String getMp4_save_path() { return mp4_save_path; } public void setMp4_save_path(String mp4_save_path) { this.mp4_save_path = mp4_save_path; } public String getStream_replace() { return stream_replace; } public void setStream_replace(String stream_replace) { this.stream_replace = stream_replace; } public Integer getModify_stamp() { return modify_stamp; } public void setModify_stamp(Integer modify_stamp) { this.modify_stamp = modify_stamp; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/event/hook/Hook.java ================================================ package com.genersoft.iot.vmp.media.event.hook; import lombok.Data; /** * zlm hook事件的参数 * @author lin */ @Data public class Hook { private HookType hookType; private String app; private String stream; private Long expireTime; public static Hook getInstance(HookType hookType, String app, String stream) { Hook hookSubscribe = new Hook(); hookSubscribe.setApp(app); hookSubscribe.setStream(stream); hookSubscribe.setHookType(hookType); hookSubscribe.setExpireTime(System.currentTimeMillis() + 5 * 60 * 1000); return hookSubscribe; } public static Hook getInstance(HookType hookType, String app, String stream, String mediaServer) { // TODO 后续修改所有方法 return Hook.getInstance(hookType, app, stream); } @Override public boolean equals(Object obj) { if (obj instanceof Hook) { Hook param = (Hook) obj; return param.getHookType().equals(this.hookType) && param.getApp().equals(this.app) && param.getStream().equals(this.stream); }else { return false; } } @Override public String toString() { return this.getHookType() + this.getApp() + this.getStream(); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/event/hook/HookData.java ================================================ package com.genersoft.iot.vmp.media.event.hook; import com.genersoft.iot.vmp.media.bean.MediaInfo; import com.genersoft.iot.vmp.media.bean.RecordInfo; import com.genersoft.iot.vmp.media.event.media.MediaArrivalEvent; import com.genersoft.iot.vmp.media.event.media.MediaEvent; import com.genersoft.iot.vmp.media.event.media.MediaPublishEvent; import com.genersoft.iot.vmp.media.event.media.MediaRecordMp4Event; import com.genersoft.iot.vmp.media.bean.MediaServer; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; /** * Hook返回的内容 */ @Data public class HookData { /** * 应用名 */ private String app; /** * 流ID */ private String stream; /** * 流媒体节点 */ private MediaServer mediaServer; /** * 协议 */ private String schema; /** * 流信息 */ private MediaInfo mediaInfo; /** * 录像信息 */ private RecordInfo recordInfo; @Schema(description = "推流的额外参数") private String params; public static HookData getInstance(MediaEvent mediaEvent) { HookData hookData = new HookData(); if (mediaEvent instanceof MediaPublishEvent) { MediaPublishEvent event = (MediaPublishEvent) mediaEvent; hookData.setApp(event.getApp()); hookData.setStream(event.getStream()); hookData.setSchema(event.getSchema()); hookData.setMediaServer(event.getMediaServer()); hookData.setParams(event.getParams()); }else if (mediaEvent instanceof MediaArrivalEvent) { MediaArrivalEvent event = (MediaArrivalEvent) mediaEvent; hookData.setApp(event.getApp()); hookData.setStream(event.getStream()); hookData.setSchema(event.getSchema()); hookData.setMediaServer(event.getMediaServer()); hookData.setMediaInfo(event.getMediaInfo()); }else if (mediaEvent instanceof MediaRecordMp4Event) { MediaRecordMp4Event event = (MediaRecordMp4Event) mediaEvent; hookData.setApp(event.getApp()); hookData.setStream(event.getStream()); hookData.setSchema(event.getSchema()); hookData.setMediaServer(event.getMediaServer()); hookData.setRecordInfo(event.getRecordInfo()); }else { hookData.setApp(mediaEvent.getApp()); hookData.setStream(mediaEvent.getStream()); hookData.setSchema(mediaEvent.getSchema()); hookData.setMediaServer(mediaEvent.getMediaServer()); } return hookData; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/event/hook/HookSubscribe.java ================================================ package com.genersoft.iot.vmp.media.event.hook; import com.genersoft.iot.vmp.media.event.media.*; import org.springframework.context.event.EventListener; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * zlm hook事件的参数 * @author lin */ @Component public class HookSubscribe { /** * 订阅数据过期时间 */ private final long subscribeExpire = 5 * 60 * 1000; @FunctionalInterface public interface Event{ void response(HookData data); } /** * 流到来的处理 */ @Async("taskExecutor") @EventListener public void onApplicationEvent(MediaArrivalEvent event) { if (event.getSchema() == null || "rtsp".equals(event.getSchema())) { sendNotify(HookType.on_media_arrival, event); } } /** * 流结束事件 */ @Async("taskExecutor") @EventListener public void onApplicationEvent(MediaDepartureEvent event) { if (event.getSchema() == null || "rtsp".equals(event.getSchema())) { sendNotify(HookType.on_media_departure, event); } } /** * 推流鉴权事件 */ @Async("taskExecutor") @EventListener public void onApplicationEvent(MediaPublishEvent event) { sendNotify(HookType.on_publish, event); } /** * 生成录像文件事件 */ @Async("taskExecutor") @EventListener public void onApplicationEvent(MediaRecordMp4Event event) { sendNotify(HookType.on_record_mp4, event); } private final Map allSubscribes = new ConcurrentHashMap<>(); private final Map allHook = new ConcurrentHashMap<>(); private void sendNotify(HookType hookType, MediaEvent event) { Hook paramHook = Hook.getInstance(hookType, event.getApp(), event.getStream()); Event hookSubscribeEvent = allSubscribes.get(paramHook.toString()); if (hookSubscribeEvent != null) { HookData data = HookData.getInstance(event); hookSubscribeEvent.response(data); } } public void addSubscribe(Hook hook, HookSubscribe.Event event) { if (hook.getExpireTime() == null) { hook.setExpireTime(System.currentTimeMillis() + subscribeExpire); } allSubscribes.put(hook.toString(), event); allHook.put(hook.toString(), hook); } public void removeSubscribe(Hook hook) { allSubscribes.remove(hook.toString()); allHook.remove(hook.toString()); } /** * 对订阅数据进行过期清理 */ @Scheduled(fixedRate=subscribeExpire) //每5分钟执行一次 public void execute(){ long expireTime = System.currentTimeMillis(); for (Hook hook : allHook.values()) { if (hook.getExpireTime() < expireTime) { allSubscribes.remove(hook.toString()); allHook.remove(hook.toString()); } } } public List getAll() { return new ArrayList<>(allHook.values()); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/event/hook/HookType.java ================================================ package com.genersoft.iot.vmp.media.event.hook; /** * hook类型 * @author lin */ public enum HookType { on_publish, on_record_mp4, on_media_arrival, on_media_departure, on_rtp_server_timeout, } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/event/media/MediaArrivalEvent.java ================================================ package com.genersoft.iot.vmp.media.event.media; import com.genersoft.iot.vmp.media.abl.bean.hook.OnStreamArriveABLHookParam; import com.genersoft.iot.vmp.media.bean.MediaInfo; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.zlm.dto.hook.OnStreamChangedHookParam; import com.genersoft.iot.vmp.vmanager.bean.StreamContent; import lombok.Getter; import lombok.Setter; import java.util.Map; /** * 流到来事件 */ public class MediaArrivalEvent extends MediaEvent { public MediaArrivalEvent(Object source) { super(source); } public static MediaArrivalEvent getInstance(Object source, OnStreamChangedHookParam hookParam, MediaServer mediaServer, String serverId){ MediaArrivalEvent mediaArrivalEvent = new MediaArrivalEvent(source); mediaArrivalEvent.setMediaInfo(MediaInfo.getInstance(hookParam, mediaServer, serverId)); mediaArrivalEvent.setApp(hookParam.getApp()); mediaArrivalEvent.setStream(hookParam.getStream()); mediaArrivalEvent.setMediaServer(mediaServer); mediaArrivalEvent.setSchema(hookParam.getSchema()); mediaArrivalEvent.setSchema(hookParam.getSchema()); mediaArrivalEvent.setParamMap(hookParam.getParamMap()); return mediaArrivalEvent; } public static MediaArrivalEvent getInstance(Object source, OnStreamArriveABLHookParam hookParam, MediaServer mediaServer){ MediaArrivalEvent mediaArrivalEvent = new MediaArrivalEvent(source); mediaArrivalEvent.setMediaInfo(MediaInfo.getInstance(hookParam, mediaServer)); mediaArrivalEvent.setApp(hookParam.getApp()); mediaArrivalEvent.setStream(hookParam.getStream()); mediaArrivalEvent.setMediaServer(mediaServer); mediaArrivalEvent.setCallId(hookParam.getCallId()); return mediaArrivalEvent; } @Getter @Setter private MediaInfo mediaInfo; @Getter @Setter private String callId; @Getter @Setter private StreamContent streamInfo; @Getter @Setter private Map paramMap; @Getter @Setter private String serverId; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/event/media/MediaDepartureEvent.java ================================================ package com.genersoft.iot.vmp.media.event.media; import com.genersoft.iot.vmp.media.abl.bean.hook.ABLHookParam; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.zlm.dto.hook.OnStreamChangedHookParam; /** * 流离开事件 */ public class MediaDepartureEvent extends MediaEvent { public MediaDepartureEvent(Object source) { super(source); } public static MediaDepartureEvent getInstance(Object source, OnStreamChangedHookParam hookParam, MediaServer mediaServer){ MediaDepartureEvent mediaDepartureEven = new MediaDepartureEvent(source); mediaDepartureEven.setApp(hookParam.getApp()); mediaDepartureEven.setStream(hookParam.getStream()); mediaDepartureEven.setSchema(hookParam.getSchema()); mediaDepartureEven.setMediaServer(mediaServer); return mediaDepartureEven; } public static MediaDepartureEvent getInstance(Object source, ABLHookParam hookParam, MediaServer mediaServer){ MediaDepartureEvent mediaDepartureEven = new MediaDepartureEvent(source); mediaDepartureEven.setApp(hookParam.getApp()); mediaDepartureEven.setStream(hookParam.getStream()); mediaDepartureEven.setMediaServer(mediaServer); return mediaDepartureEven; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/event/media/MediaEvent.java ================================================ package com.genersoft.iot.vmp.media.event.media; import com.genersoft.iot.vmp.media.bean.MediaServer; import org.springframework.context.ApplicationEvent; /** * 流到来事件 */ public class MediaEvent extends ApplicationEvent { public MediaEvent(Object source) { super(source); } private String app; private String stream; private MediaServer mediaServer; private String schema; private String params; public String getParams() { return params; } public void setParams(String params) { this.params = params; } public String getApp() { return app; } public void setApp(String app) { this.app = app; } public String getStream() { return stream; } public void setStream(String stream) { this.stream = stream; } public MediaServer getMediaServer() { return mediaServer; } public void setMediaServer(MediaServer mediaServer) { this.mediaServer = mediaServer; } public String getSchema() { return schema; } public void setSchema(String schema) { this.schema = schema; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/event/media/MediaNotFoundEvent.java ================================================ package com.genersoft.iot.vmp.media.event.media; import com.genersoft.iot.vmp.media.abl.bean.hook.ABLHookParam; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.zlm.dto.hook.OnStreamNotFoundHookParam; /** * 流未找到 */ public class MediaNotFoundEvent extends MediaEvent { public MediaNotFoundEvent(Object source) { super(source); } public static MediaNotFoundEvent getInstance(Object source, OnStreamNotFoundHookParam hookParam, MediaServer mediaServer){ MediaNotFoundEvent mediaDepartureEven = new MediaNotFoundEvent(source); mediaDepartureEven.setApp(hookParam.getApp()); mediaDepartureEven.setStream(hookParam.getStream()); mediaDepartureEven.setSchema(hookParam.getSchema()); mediaDepartureEven.setMediaServer(mediaServer); mediaDepartureEven.setParams(hookParam.getParams()); return mediaDepartureEven; } public static MediaNotFoundEvent getInstance(Object source, ABLHookParam hookParam, MediaServer mediaServer){ MediaNotFoundEvent mediaDepartureEven = new MediaNotFoundEvent(source); mediaDepartureEven.setApp(hookParam.getApp()); mediaDepartureEven.setStream(hookParam.getStream()); mediaDepartureEven.setMediaServer(mediaServer); return mediaDepartureEven; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/event/media/MediaPublishEvent.java ================================================ package com.genersoft.iot.vmp.media.event.media; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.zlm.dto.hook.OnPublishHookParam; /** * 推流鉴权事件 */ public class MediaPublishEvent extends MediaEvent { public MediaPublishEvent(Object source) { super(source); } public static MediaPublishEvent getInstance(Object source, OnPublishHookParam hookParam, MediaServer mediaServer){ MediaPublishEvent mediaPublishEvent = new MediaPublishEvent(source); mediaPublishEvent.setApp(hookParam.getApp()); mediaPublishEvent.setStream(hookParam.getStream()); mediaPublishEvent.setMediaServer(mediaServer); mediaPublishEvent.setSchema(hookParam.getSchema()); mediaPublishEvent.setParams(hookParam.getParams()); return mediaPublishEvent; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/event/media/MediaRecordMp4Event.java ================================================ package com.genersoft.iot.vmp.media.event.media; import com.genersoft.iot.vmp.media.abl.ABLHttpHookListener; import com.genersoft.iot.vmp.media.abl.bean.hook.OnRecordMp4ABLHookParam; import com.genersoft.iot.vmp.media.bean.RecordInfo; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.zlm.dto.hook.OnRecordMp4HookParam; /** * 录像文件生成事件 */ public class MediaRecordMp4Event extends MediaEvent { public MediaRecordMp4Event(Object source) { super(source); } private RecordInfo recordInfo; public static MediaRecordMp4Event getInstance(Object source, OnRecordMp4HookParam hookParam, MediaServer mediaServer){ MediaRecordMp4Event mediaRecordMp4Event = new MediaRecordMp4Event(source); mediaRecordMp4Event.setApp(hookParam.getApp()); mediaRecordMp4Event.setStream(hookParam.getStream()); RecordInfo recordInfo = RecordInfo.getInstance(hookParam); mediaRecordMp4Event.setRecordInfo(recordInfo); mediaRecordMp4Event.setMediaServer(mediaServer); return mediaRecordMp4Event; } public static MediaRecordMp4Event getInstance(ABLHttpHookListener source, OnRecordMp4ABLHookParam hookParam, MediaServer mediaServer) { MediaRecordMp4Event mediaRecordMp4Event = new MediaRecordMp4Event(source); mediaRecordMp4Event.setApp(hookParam.getApp()); mediaRecordMp4Event.setStream(hookParam.getStream()); RecordInfo recordInfo = RecordInfo.getInstance(hookParam); mediaRecordMp4Event.setRecordInfo(recordInfo); mediaRecordMp4Event.setMediaServer(mediaServer); return mediaRecordMp4Event; } public RecordInfo getRecordInfo() { return recordInfo; } public void setRecordInfo(RecordInfo recordInfo) { this.recordInfo = recordInfo; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/event/media/MediaRecordProcessEvent.java ================================================ package com.genersoft.iot.vmp.media.event.media; import com.genersoft.iot.vmp.media.abl.ABLHttpHookListener; import com.genersoft.iot.vmp.media.abl.bean.hook.OnRecordProgressABLHookParam; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.utils.DateUtil; import java.util.Date; /** * 录像文件进度通知事件 */ public class MediaRecordProcessEvent extends MediaEvent { private Integer currentFileDuration; private Integer TotalVideoDuration; private String fileName; private long startTime; private long endTime; public MediaRecordProcessEvent(Object source) { super(source); } public static MediaRecordProcessEvent getInstance(ABLHttpHookListener source, OnRecordProgressABLHookParam hookParam, MediaServer mediaServer) { MediaRecordProcessEvent mediaRecordMp4Event = new MediaRecordProcessEvent(source); mediaRecordMp4Event.setApp(hookParam.getApp()); mediaRecordMp4Event.setStream(hookParam.getStream()); mediaRecordMp4Event.setCurrentFileDuration(hookParam.getCurrentFileDuration()); mediaRecordMp4Event.setTotalVideoDuration(hookParam.getTotalVideoDuration()); mediaRecordMp4Event.setMediaServer(mediaServer); mediaRecordMp4Event.setFileName(hookParam.getFileName()); mediaRecordMp4Event.setStartTime(DateUtil.urlToTimestampMs(hookParam.getStartTime())); mediaRecordMp4Event.setEndTime(DateUtil.urlToTimestampMs(hookParam.getEndTime())); return mediaRecordMp4Event; } public Integer getCurrentFileDuration() { return currentFileDuration; } public void setCurrentFileDuration(Integer currentFileDuration) { this.currentFileDuration = currentFileDuration; } public Integer getTotalVideoDuration() { return TotalVideoDuration; } public void setTotalVideoDuration(Integer totalVideoDuration) { TotalVideoDuration = totalVideoDuration; } public String getFileName() { return fileName; } public void setFileName(String fileName) { this.fileName = fileName; } public long getStartTime() { return startTime; } public void setStartTime(long startTime) { this.startTime = startTime; } public long getEndTime() { return endTime; } public void setEndTime(long endTime) { this.endTime = endTime; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/event/media/MediaRtpServerTimeoutEvent.java ================================================ package com.genersoft.iot.vmp.media.event.media; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.zlm.dto.hook.OnStreamChangedHookParam; /** * RtpServer收流超时事件 */ public class MediaRtpServerTimeoutEvent extends MediaEvent { public MediaRtpServerTimeoutEvent(Object source) { super(source); } public static MediaRtpServerTimeoutEvent getInstance(Object source, OnStreamChangedHookParam hookParam, MediaServer mediaServer){ MediaRtpServerTimeoutEvent mediaDepartureEven = new MediaRtpServerTimeoutEvent(source); mediaDepartureEven.setApp(hookParam.getApp()); mediaDepartureEven.setStream(hookParam.getStream()); mediaDepartureEven.setSchema(hookParam.getSchema()); mediaDepartureEven.setMediaServer(mediaServer); return mediaDepartureEven; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/event/mediaServer/MediaSendRtpStoppedEvent.java ================================================ package com.genersoft.iot.vmp.media.event.mediaServer; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.zlm.dto.hook.OnStreamNotFoundHookParam; import org.springframework.context.ApplicationEvent; /** * 发送流停止事件 */ public class MediaSendRtpStoppedEvent extends ApplicationEvent { public MediaSendRtpStoppedEvent(Object source) { super(source); } private String app; private String stream; private MediaServer mediaServer; public static MediaSendRtpStoppedEvent getInstance(Object source, OnStreamNotFoundHookParam hookParam, MediaServer mediaServer){ MediaSendRtpStoppedEvent mediaDepartureEven = new MediaSendRtpStoppedEvent(source); mediaDepartureEven.setApp(hookParam.getApp()); mediaDepartureEven.setStream(hookParam.getStream()); mediaDepartureEven.setMediaServer(mediaServer); return mediaDepartureEven; } public String getApp() { return app; } public void setApp(String app) { this.app = app; } public String getStream() { return stream; } public void setStream(String stream) { this.stream = stream; } public MediaServer getMediaServer() { return mediaServer; } public void setMediaServer(MediaServer mediaServer) { this.mediaServer = mediaServer; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/event/mediaServer/MediaServerChangeEvent.java ================================================ package com.genersoft.iot.vmp.media.event.mediaServer; import com.genersoft.iot.vmp.media.bean.MediaServer; import org.springframework.context.ApplicationEvent; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class MediaServerChangeEvent extends ApplicationEvent { public MediaServerChangeEvent(Object source) { super(source); } private List mediaServerItemList; public List getMediaServerItemList() { return mediaServerItemList; } public void setMediaServerItemList(List mediaServerItemList) { this.mediaServerItemList = mediaServerItemList; } public void setMediaServerItemList(MediaServer... mediaServerItemArray) { this.mediaServerItemList = new ArrayList<>(); this.mediaServerItemList.addAll(Arrays.asList(mediaServerItemArray)); } public void setMediaServerItem(List mediaServerItemList) { this.mediaServerItemList = mediaServerItemList; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/event/mediaServer/MediaServerDeleteEvent.java ================================================ package com.genersoft.iot.vmp.media.event.mediaServer; /** * zlm在线事件 */ public class MediaServerDeleteEvent extends MediaServerEventAbstract { public MediaServerDeleteEvent(Object source) { super(source); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/event/mediaServer/MediaServerEventAbstract.java ================================================ package com.genersoft.iot.vmp.media.event.mediaServer; import com.genersoft.iot.vmp.media.bean.MediaServer; import lombok.Getter; import lombok.Setter; import org.springframework.context.ApplicationEvent; import java.io.Serial; public abstract class MediaServerEventAbstract extends ApplicationEvent { @Serial private static final long serialVersionUID = 1L; @Getter @Setter private MediaServer mediaServer; public MediaServerEventAbstract(Object source) { super(source); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/event/mediaServer/MediaServerOfflineEvent.java ================================================ package com.genersoft.iot.vmp.media.event.mediaServer; /** * zlm离线事件类 */ public class MediaServerOfflineEvent extends MediaServerEventAbstract { public MediaServerOfflineEvent(Object source) { super(source); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/event/mediaServer/MediaServerOnlineEvent.java ================================================ package com.genersoft.iot.vmp.media.event.mediaServer; /** * zlm在线事件 */ public class MediaServerOnlineEvent extends MediaServerEventAbstract { public MediaServerOnlineEvent(Object source) { super(source); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/event/mediaServer/MediaServerStatusEventListener.java ================================================ package com.genersoft.iot.vmp.media.event.mediaServer; import com.genersoft.iot.vmp.gb28181.service.IPlayService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.event.EventListener; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; /** * @description: 在线事件监听器,监听到离线后,修改设备离在线状态。 设备在线有两个来源: * 1、设备主动注销,发送注销指令 * 2、设备未知原因离线,心跳超时 * @author: swwheihei * @date: 2020年5月6日 下午1:51:23 */ @Slf4j @Component public class MediaServerStatusEventListener { @Autowired private IPlayService playService; @Async("taskExecutor") @EventListener public void onApplicationEvent(MediaServerOnlineEvent event) { log.info("[媒体节点] 上线 ID:" + event.getMediaServer().getId()); playService.zlmServerOnline(event.getMediaServer()); } @Async("taskExecutor") @EventListener public void onApplicationEvent(MediaServerOfflineEvent event) { log.info("[媒体节点] 离线,ID:" + event.getMediaServer().getId()); // 处理ZLM离线 playService.zlmServerOffline(event.getMediaServer()); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/service/IMediaNodeServerService.java ================================================ package com.genersoft.iot.vmp.media.service; import com.genersoft.iot.vmp.common.CommonCallback; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.gb28181.bean.SendRtpInfo; import com.genersoft.iot.vmp.gb28181.bean.TalkRtpInfo; import com.genersoft.iot.vmp.media.bean.MediaInfo; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.bean.RecordInfo; import com.genersoft.iot.vmp.service.bean.DownloadFileInfo; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import java.util.List; import java.util.Map; public interface IMediaNodeServerService { int createRTPServer(MediaServer mediaServer, String app, String stream, long ssrc, Integer port, Boolean onlyAuto, Boolean disableAudio, Boolean reUsePort, Integer tcpMode); void closeRtpServer(MediaServer mediaServer, String app, String stream, CommonCallback callback); // int createJTTServer(MediaServer mediaServer, String streamId, Integer port, Boolean disableVideo, Boolean disableAudio, Integer tcpMode); // // void closeJTTServer(MediaServer mediaServer, String streamId, CommonCallback callback); void closeStreams(MediaServer mediaServer, String app, String stream); Boolean updateRtpServerSSRC(MediaServer mediaServer, String app, String stream, String ssrc); boolean checkNodeId(MediaServer mediaServer); void online(MediaServer mediaServer); MediaServer checkMediaServer(String ip, int port, String secret); boolean stopSendRtp(MediaServer mediaInfo, String app, String stream, String ssrc); boolean initStopSendRtp(MediaServer mediaInfo, String app, String stream, String ssrc); boolean deleteRecordDirectory(MediaServer mediaServer, String app, String stream, String date, String fileName); List getMediaList(MediaServer mediaServer, String app, String stream, String callId); Boolean connectRtpServer(MediaServer mediaServer, String address, int port, String app, String stream); void getSnap(MediaServer mediaServer, String app, String stream, int timeoutSec, int expireSec, String path, String fileName); MediaInfo getMediaInfo(MediaServer mediaServer, String app, String stream); Boolean pauseRtpCheck(MediaServer mediaServer, String streamKey); Boolean resumeRtpCheck(MediaServer mediaServer, String streamKey); String getFfmpegCmd(MediaServer mediaServer, String cmdKey); WVPResult addStreamProxy(MediaServer mediaServer, String app, String stream, String url, boolean enableAudio, boolean enableMp4, String rtpType, Integer timeout); Boolean delFFmpegSource(MediaServer mediaServer, String streamKey); Boolean delStreamProxy(MediaServer mediaServer, String streamKey); Map getFFmpegCMDs(MediaServer mediaServer); Integer startSendRtpPassive(MediaServer mediaServer, SendRtpInfo sendRtpItem, Integer timeout); void startSendRtpStream(MediaServer mediaServer, SendRtpInfo sendRtpItem); Integer startSendRtpTalk(MediaServer mediaServer, TalkRtpInfo talkRtpInfo, Integer timeout); Long updateDownloadProcess(MediaServer mediaServer, String app, String stream); String startProxy(MediaServer mediaServer, StreamProxy streamProxy); void stopProxy(MediaServer mediaServer, String streamKey, String type); List listRtpServer(MediaServer mediaServer); void loadMP4FileForDate(MediaServer mediaServer, String app, String stream, String datePath, String dateDir, ErrorCallback callback); void seekRecordStamp(MediaServer mediaServer, String app, String stream, Double stamp, String schema); void setRecordSpeed(MediaServer mediaServer, String app, String stream, Integer speed, String schema); DownloadFileInfo getDownloadFilePath(MediaServer mediaServer, RecordInfo recordInfo); StreamInfo getStreamInfoByAppAndStream(MediaServer mediaServer, String app, String stream, MediaInfo mediaInfo, String addr, String callId, boolean isPlay); void loadMP4File(MediaServer mediaServer, String app, String stream, String filePath, String fileName, ErrorCallback callback); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/service/IMediaServerService.java ================================================ package com.genersoft.iot.vmp.media.service; import com.genersoft.iot.vmp.common.CommonCallback; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.gb28181.bean.SendRtpInfo; import com.genersoft.iot.vmp.gb28181.bean.TalkRtpInfo; import com.genersoft.iot.vmp.media.bean.MediaInfo; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.bean.RecordInfo; import com.genersoft.iot.vmp.service.bean.*; import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import java.util.List; import java.util.Map; /** * 媒体服务节点 */ public interface IMediaServerService { List getAllOnlineList(); List getAll(); List getAllFromDatabaseWithOutDefault(); List getAllOnline(); MediaServer getOne(String generalMediaServerId); void syncCatchFromDatabase(); MediaServer getMediaServerForMinimumLoad(Boolean hasAssist); void updateVmServer(List mediaServerItemList); void closeRTPServer(MediaServer mediaServerItem, String app, String streamId); void closeRTPServer(MediaServer mediaServerItem, String app, String streamId, CommonCallback callback); Boolean updateRtpServerSSRC(MediaServer mediaServerItem, String app, String streamId, String ssrc); void clearRTPServer(MediaServer mediaServerItem); void update(MediaServer mediaSerItem); void addCount(String mediaServerId); void removeCount(String mediaServerId); void releaseSsrc(String mediaServerItemId, String ssrc); void clearMediaServerForOnline(); void add(MediaServer mediaSerItem); void resetOnlineServerItem(MediaServer serverItem); MediaServer checkMediaServer(String ip, int port, String secret, String type); boolean checkMediaRecordServer(String ip, int port); void delete(MediaServer mediaServer); MediaServer getOneFromCluster(String mediaServerId); MediaServer getDefaultMediaServer(); MediaServerLoad getLoad(MediaServer mediaServerItem); List getAllWithAssistPort(); MediaServer getOneFromDatabase(String id); boolean stopSendRtp(MediaServer mediaInfo, String app, String stream, String ssrc); boolean initStopSendRtp(MediaServer mediaInfo, String app, String stream, String ssrc); boolean deleteRecordDirectory(MediaServer mediaServerItem, String app, String stream, String date, String fileName); List getMediaList(MediaServer mediaInfo, String app, String stream, String callId); Boolean connectRtpServer(MediaServer mediaServerItem, String address, int port, String app, String stream); void getSnap(MediaServer mediaServer, String app, String stream, int timeoutSec, int expireSec, String path, String fileName); MediaInfo getMediaInfo(MediaServer mediaServerItem, String app, String stream); Boolean pauseRtpCheck(MediaServer mediaServerItem, String streamKey); boolean resumeRtpCheck(MediaServer mediaServerItem, String streamKey); String getFfmpegCmd(MediaServer mediaServer, String cmdKey); void closeStreams(MediaServer mediaServerItem, String app, String stream); WVPResult addStreamProxy(MediaServer mediaServerItem, String app, String stream, String url, boolean enableAudio, boolean enableMp4, String rtpType, Integer timeout); Boolean delFFmpegSource(MediaServer mediaServerItem, String streamKey); Boolean delStreamProxy(MediaServer mediaServerItem, String streamKey); Map getFFmpegCMDs(MediaServer mediaServer); /** * 根据应用名和流ID获取播放地址, 通过zlm接口检查是否存在 * @param app * @param stream * @return */ StreamInfo getStreamInfoByAppAndStreamWithCheck(String app, String stream, String mediaServerId,String addr, boolean authority); /** * 根据应用名和流ID获取播放地址, 通过zlm接口检查是否存在, 返回的ip使用远程访问ip,适用与zlm与wvp在一台主机的情况 * @param app * @param stream * @return */ StreamInfo getStreamInfoByAppAndStreamWithCheck(String app, String stream, String mediaServerId, boolean authority); /** * 根据应用名和流ID获取播放地址, 只是地址拼接 * @param app * @param stream * @return */ StreamInfo getStreamInfoByAppAndStream(MediaServer mediaServerItem, String app, String stream, MediaInfo mediaInfo, String callId); /** * 根据应用名和流ID获取播放地址, 只是地址拼接,返回的ip使用远程访问ip,适用与zlm与wvp在一台主机的情况 * @param app * @param stream * @return */ StreamInfo getStreamInfoByAppAndStream(MediaServer mediaServer, String app, String stream, MediaInfo mediaInfo, String addr, String callId, boolean isPlay); Boolean isStreamReady(MediaServer mediaServer, String rtp, String streamId); Integer startSendRtpPassive(MediaServer mediaServer, SendRtpInfo sendRtpItem, Integer timeout); Integer startSendRtpTalk(MediaServer mediaServer, TalkRtpInfo talkRtpInfo, Integer timeout); void startSendRtp(MediaServer mediaServer, SendRtpInfo sendRtpItem); MediaServer getMediaServerByAppAndStream(String app, String stream); Long updateDownloadProcess(MediaServer mediaServerItem, String app, String stream); String startProxy(MediaServer mediaServer, StreamProxy streamProxy); void stopProxy(MediaServer mediaServer, String streamKey, String type); StreamInfo getMediaByAppAndStream(String app, String stream); int createRTPServer(MediaServer mediaServerItem, String app, String streamId, long ssrc, Integer port, boolean onlyAuto, boolean disableAudio, boolean reUsePort, Integer tcpMode); List listRtpServer(MediaServer mediaServer); void loadMP4FileForDate(MediaServer mediaServer, String app, String stream, String datePath, String dateDir, ErrorCallback callback); void seekRecordStamp(MediaServer mediaServer, String app, String stream, Double stamp, String schema); void setRecordSpeed(MediaServer mediaServer, String app, String stream, Integer speed, String schema); DownloadFileInfo getDownloadFilePath(MediaServer mediaServer, RecordInfo recordInfo); void loadMP4File(MediaServer mediaServer, String app, String stream, String filePath, String fileName, ErrorCallback callback); void deleteDefault(); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/service/impl/MediaServerServiceImpl.java ================================================ package com.genersoft.iot.vmp.media.service.impl; import com.genersoft.iot.vmp.common.CommonCallback; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.common.VideoManagerConstants; import com.genersoft.iot.vmp.conf.MediaConfig; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.gb28181.bean.SendRtpInfo; import com.genersoft.iot.vmp.gb28181.bean.TalkRtpInfo; import com.genersoft.iot.vmp.gb28181.service.IInviteStreamService; import com.genersoft.iot.vmp.gb28181.session.SSRCFactory; import com.genersoft.iot.vmp.media.bean.MediaInfo; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.bean.RecordInfo; import com.genersoft.iot.vmp.media.event.media.MediaArrivalEvent; import com.genersoft.iot.vmp.media.event.media.MediaDepartureEvent; import com.genersoft.iot.vmp.media.event.mediaServer.MediaServerDeleteEvent; import com.genersoft.iot.vmp.media.event.mediaServer.MediaServerOfflineEvent; import com.genersoft.iot.vmp.media.event.mediaServer.MediaServerOnlineEvent; import com.genersoft.iot.vmp.media.service.IMediaNodeServerService; import com.genersoft.iot.vmp.media.service.IMediaServerService; import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo; import com.genersoft.iot.vmp.media.zlm.dto.hook.OriginType; import com.genersoft.iot.vmp.service.bean.DownloadFileInfo; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.service.bean.MediaServerLoad; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.dao.MediaServerMapper; import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy; import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.utils.redis.RedisUtil; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import lombok.extern.slf4j.Slf4j; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.event.EventListener; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.ObjectUtils; import java.time.LocalDateTime; import java.util.*; /** * 媒体服务器节点管理 */ @Slf4j @Service public class MediaServerServiceImpl implements IMediaServerService { @Autowired private SSRCFactory ssrcFactory; @Autowired private UserSetting userSetting; @Autowired private MediaServerMapper mediaServerMapper; @Autowired private IRedisCatchStorage redisCatchStorage; @Autowired private IInviteStreamService inviteStreamService; @Autowired private RedisTemplate redisTemplate; @Autowired private Map nodeServerServiceMap; @Autowired private ApplicationEventPublisher applicationEventPublisher; @Autowired private MediaConfig mediaConfig; /** * 流到来的处理 */ @Async("taskExecutor") @org.springframework.context.event.EventListener public void onApplicationEvent(MediaArrivalEvent event) { if ("rtsp".equals(event.getSchema())) { log.info("流变化:注册 app->{}, stream->{}", event.getApp(), event.getStream()); addCount(event.getMediaServer().getId()); String type = OriginType.values()[event.getMediaInfo().getOriginType()].getType(); redisCatchStorage.addStream(event.getMediaServer(), type, event.getApp(), event.getStream(), event.getMediaInfo()); } } /** * 流离开的处理 */ @Async("taskExecutor") @EventListener public void onApplicationEvent(MediaDepartureEvent event) { if ("rtsp".equals(event.getSchema())) { log.info("流变化:注销, app->{}, stream->{}", event.getApp(), event.getStream()); removeCount(event.getMediaServer().getId()); MediaInfo mediaInfo = redisCatchStorage.getStreamInfo( event.getApp(), event.getStream(), event.getMediaServer().getId()); if (mediaInfo == null) { return; } String type = OriginType.values()[mediaInfo.getOriginType()].getType(); redisCatchStorage.removeStream(mediaInfo.getMediaServer().getId(), type, event.getApp(), event.getStream()); } } /** * 流媒体节点上线 */ @Async("taskExecutor") @EventListener @Transactional public void onApplicationEvent(MediaServerOnlineEvent event) { // 查看是否有未处理的RTP流 } /** * 流媒体节点离线 */ @Async("taskExecutor") @EventListener @Transactional public void onApplicationEvent(MediaServerOfflineEvent event) { } /** * 初始化 */ @Override public void updateVmServer(List mediaServerList) { log.info("[媒体服务节点] 缓存初始化 "); for (MediaServer mediaServer : mediaServerList) { if (ObjectUtils.isEmpty(mediaServer.getId())) { continue; } // 更新 if (!ssrcFactory.hasMediaServerSSRC(mediaServer.getId())) { ssrcFactory.initMediaServerSSRC(mediaServer.getId(), null); } // 查询redis是否存在此mediaServer String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + userSetting.getServerId(); Boolean hasKey = redisTemplate.hasKey(key); if (hasKey != null && !hasKey) { redisTemplate.opsForHash().put(key, mediaServer.getId(), mediaServer); } } } @Override public int createRTPServer(MediaServer mediaServer, String app, String streamId, long ssrc, Integer port, boolean onlyAuto, boolean disableAudio, boolean reUsePort, Integer tcpMode) { int rtpServerPort; if (mediaServer.isRtpEnable()) { IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); if (mediaNodeServerService == null) { log.info("[openRTPServer] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); return 0; } rtpServerPort = mediaNodeServerService.createRTPServer(mediaServer, app, streamId, ssrc, port, onlyAuto, disableAudio, reUsePort, tcpMode); } else { rtpServerPort = mediaServer.getRtpProxyPort(); } return rtpServerPort; } @Override public List listRtpServer(MediaServer mediaServer) { IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); if (mediaNodeServerService == null) { log.info("[openRTPServer] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); return new ArrayList<>(); } return mediaNodeServerService.listRtpServer(mediaServer); } @Override public void closeRTPServer(MediaServer mediaServer, String app, String streamId) { if (mediaServer == null) { return; } IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); if (mediaNodeServerService == null) { log.info("[closeRTPServer] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); return; } mediaNodeServerService.closeRtpServer(mediaServer, app, streamId, null); } @Override public void closeRTPServer(MediaServer mediaServer, String app, String streamId, CommonCallback callback) { if (mediaServer == null) { callback.run(false); return; } IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); if (mediaNodeServerService == null) { log.info("[closeRTPServer] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); return; } mediaNodeServerService.closeRtpServer(mediaServer, app, streamId, callback); } @Override public Boolean updateRtpServerSSRC(MediaServer mediaServer, String app, String streamId, String ssrc) { if (mediaServer == null) { return false; } IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); if (mediaNodeServerService == null) { log.info("[updateRtpServerSSRC] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); return false; } return mediaNodeServerService.updateRtpServerSSRC(mediaServer, app, streamId, ssrc); } @Override public void releaseSsrc(String mediaServerId, String ssrc) { MediaServer mediaServer = getOne(mediaServerId); if (mediaServer == null || ssrc == null) { return; } ssrcFactory.releaseSsrc(mediaServerId, ssrc); } /** * 媒体服务节点 重启后重置他的推流信息, TODO 给正在使用的设备发送停止命令 */ @Override public void clearRTPServer(MediaServer mediaServer) { ssrcFactory.reset(mediaServer.getId()); } @Override public void update(MediaServer mediaServer) { if (mediaServerMapper.queryOne(mediaServer.getId()) != null) { mediaServerMapper.update(mediaServer); }else { mediaServerMapper.add(mediaServer); } MediaServer mediaServerInRedis = getOne(mediaServer.getId()); if (mediaServerInRedis == null || !ssrcFactory.hasMediaServerSSRC(mediaServer.getId())) { ssrcFactory.initMediaServerSSRC(mediaServer.getId(),null); } String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + userSetting.getServerId(); redisTemplate.opsForHash().put(key, mediaServer.getId(), mediaServer); if (mediaServer.isStatus()) { resetOnlineServerItem(mediaServer); } } @Override public List getAllOnlineList() { List result = new ArrayList<>(); String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + userSetting.getServerId(); String onlineKey = VideoManagerConstants.ONLINE_MEDIA_SERVERS_PREFIX + userSetting.getServerId(); List values = redisTemplate.opsForHash().values(key); for (Object value : values) { if (Objects.isNull(value)) { continue; } MediaServer mediaServer = (MediaServer) value; // 检查状态 Double aDouble = redisTemplate.opsForZSet().score(onlineKey, mediaServer.getId()); if (aDouble != null) { mediaServer.setStatus(true); } result.add(mediaServer); } result.sort((serverItem1, serverItem2)->{ int sortResult = 0; LocalDateTime localDateTime1 = LocalDateTime.parse(serverItem1.getCreateTime(), DateUtil.formatter); LocalDateTime localDateTime2 = LocalDateTime.parse(serverItem2.getCreateTime(), DateUtil.formatter); sortResult = localDateTime1.compareTo(localDateTime2); return sortResult; }); return result; } @Override public List getAll() { List mediaServerList = mediaServerMapper.queryAll(userSetting.getServerId()); if (mediaServerList.isEmpty()) { return new ArrayList<>(); } for (MediaServer mediaServer : mediaServerList) { MediaServer mediaServerInRedis = getOne(mediaServer.getId()); if (mediaServerInRedis != null) { mediaServer.setStatus(mediaServerInRedis.isStatus()); } } return mediaServerList; } @Override public List getAllFromDatabaseWithOutDefault() { return mediaServerMapper.queryAllWithOutDefault(userSetting.getServerId()); } @Override public List getAllOnline() { String key = VideoManagerConstants.ONLINE_MEDIA_SERVERS_PREFIX + userSetting.getServerId(); Set mediaServerIdSet = redisTemplate.opsForZSet().reverseRange(key, 0, -1); List result = new ArrayList<>(); if (mediaServerIdSet != null && !mediaServerIdSet.isEmpty()) { for (Object mediaServerId : mediaServerIdSet) { String mediaServerIdStr = (String) mediaServerId; String serverKey = VideoManagerConstants.MEDIA_SERVER_PREFIX + userSetting.getServerId(); result.add((MediaServer) redisTemplate.opsForHash().get(serverKey, mediaServerIdStr)); } } Collections.reverse(result); return result; } /** * 获取单个媒体服务节点服务器 * @param mediaServerId 服务id * @return mediaServer */ @Override public MediaServer getOne(String mediaServerId) { if (mediaServerId == null) { return null; } String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + userSetting.getServerId(); MediaServer mediaServer = (MediaServer) redisTemplate.opsForHash().get(key, mediaServerId); if (mediaServer == null) { // 尝试从数据库获取 mediaServer = mediaServerMapper.queryOneWithServerId(mediaServerId, userSetting.getServerId()); if (mediaServer != null) { redisTemplate.opsForHash().put(key, mediaServer.getId(), mediaServer); } } return mediaServer; } /** * 获取集群中的节点信息,不区分所属的wvp */ @Override public MediaServer getOneFromCluster(String mediaServerId) { if (mediaServerId == null) { return null; } String scanKey = String.format("%s*", VideoManagerConstants.MEDIA_SERVER_PREFIX); List values = RedisUtil.scan(redisTemplate, scanKey); if (values.isEmpty()) { return null; } return (MediaServer) redisTemplate.opsForHash().get((String) values.get(0), mediaServerId); } @Override public MediaServer getDefaultMediaServer() { return mediaServerMapper.queryDefault(userSetting.getServerId()); } @Override public void clearMediaServerForOnline() { String key = VideoManagerConstants.ONLINE_MEDIA_SERVERS_PREFIX + userSetting.getServerId(); redisTemplate.delete(key); } @Override public void add(MediaServer mediaServer) { mediaServer.setCreateTime(DateUtil.getNow()); mediaServer.setUpdateTime(DateUtil.getNow()); if (mediaServer.getHookAliveInterval() == null || mediaServer.getHookAliveInterval() == 0F) { mediaServer.setHookAliveInterval(10F); } if (mediaServer.getType() == null) { log.info("[添加媒体节点] 失败, mediaServer的类型:为空"); return; } if (mediaServerMapper.queryOne(mediaServer.getId()) != null) { log.info("[添加媒体节点] 失败, 媒体服务ID已存在,请修改媒体服务器配置, {}", mediaServer.getId()); throw new ControllerException(ErrorCode.ERROR100.getCode(),"保存失败,媒体服务ID [ " + mediaServer.getId() + " ] 已存在,请修改媒体服务器配置"); } IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); if (mediaNodeServerService == null) { log.info("[添加媒体节点] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); return; } mediaServerMapper.add(mediaServer); if (mediaServer.isStatus()) { mediaNodeServerService.online(mediaServer); } } @Override public void resetOnlineServerItem(MediaServer serverItem) { // 更新缓存 String key = VideoManagerConstants.ONLINE_MEDIA_SERVERS_PREFIX + userSetting.getServerId(); // 使用zset的分数作为当前并发量, 默认值设置为0 if (redisTemplate.opsForZSet().score(key, serverItem.getId()) == null) { // 不存在则设置默认值 已存在则重置 redisTemplate.opsForZSet().add(key, serverItem.getId(), 0L); // 查询服务流数量 int count = getMediaList(serverItem); redisTemplate.opsForZSet().add(key, serverItem.getId(), count); }else { clearRTPServer(serverItem); } } private int getMediaList(MediaServer serverItem) { return 0; } @Override public void addCount(String mediaServerId) { if (mediaServerId == null) { return; } String key = VideoManagerConstants.ONLINE_MEDIA_SERVERS_PREFIX + userSetting.getServerId(); redisTemplate.opsForZSet().incrementScore(key, mediaServerId, 1); } @Override public void removeCount(String mediaServerId) { String key = VideoManagerConstants.ONLINE_MEDIA_SERVERS_PREFIX + userSetting.getServerId(); redisTemplate.opsForZSet().incrementScore(key, mediaServerId, - 1); } /** * 获取负载最低的节点 * @return mediaServer */ @Override public MediaServer getMediaServerForMinimumLoad(Boolean hasAssist) { String key = VideoManagerConstants.ONLINE_MEDIA_SERVERS_PREFIX + userSetting.getServerId(); Long size = redisTemplate.opsForZSet().zCard(key); if (size == null || size == 0) { log.info("获取负载最低的节点时无在线节点"); return null; } // 获取分数最低的,及并发最低的 Set objects = redisTemplate.opsForZSet().range(key, 0, -1); ArrayList mediaServerObjectS = new ArrayList<>(objects); MediaServer mediaServer = null; if (hasAssist == null) { String mediaServerId = (String)mediaServerObjectS.get(0); mediaServer = getOne(mediaServerId); }else if (hasAssist) { for (Object mediaServerObject : mediaServerObjectS) { String mediaServerId = (String)mediaServerObject; MediaServer serverItem = getOne(mediaServerId); if (serverItem.getRecordAssistPort() > 0) { mediaServer = serverItem; break; } } }else if (!hasAssist) { for (Object mediaServerObject : mediaServerObjectS) { String mediaServerId = (String)mediaServerObject; MediaServer serverItem = getOne(mediaServerId); if (serverItem.getRecordAssistPort() == 0) { mediaServer = serverItem; break; } } } return mediaServer; } @Override public MediaServer checkMediaServer(String ip, int port, String secret, String type) { if (mediaServerMapper.queryOneByHostAndPort(ip, port, userSetting.getServerId()) != null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "此连接已存在"); } IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(type); if (mediaNodeServerService == null) { log.info("[closeRTPServer] 失败, mediaServer的类型: {},未找到对应的实现类", type); return null; } MediaServer mediaServer = mediaNodeServerService.checkMediaServer(ip, port, secret); if (mediaServer != null) { if (mediaServerMapper.queryOne(mediaServer.getId()) != null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "媒体服务ID [" + mediaServer.getId() + " ] 已存在,请修改媒体服务器配置"); } } return mediaServer; } @Override public boolean checkMediaRecordServer(String ip, int port) { boolean result = false; OkHttpClient client = new OkHttpClient(); String url = String.format("http://%s:%s/index/api/record", ip, port); Request request = new Request.Builder() .get() .url(url) .build(); try { Response response = client.newCall(request).execute(); if (response != null) { result = true; } } catch (Exception e) {} return result; } @Override public void delete(MediaServer mediaServer) { mediaServerMapper.delOne(mediaServer.getId(), userSetting.getServerId()); redisTemplate.opsForZSet().remove(VideoManagerConstants.ONLINE_MEDIA_SERVERS_PREFIX + userSetting.getServerId(), mediaServer.getId()); String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + userSetting.getServerId(); redisTemplate.delete(key); // 发送节点移除通知 MediaServerDeleteEvent event = new MediaServerDeleteEvent(this); event.setMediaServer(mediaServer); applicationEventPublisher.publishEvent(event); } @Override public MediaServer getOneFromDatabase(String mediaServerId) { return mediaServerMapper.queryOne(mediaServerId); } @Override public void syncCatchFromDatabase() { List allInCatch = getAllOnlineList(); List allInDatabase = mediaServerMapper.queryAll(userSetting.getServerId()); Map mediaServerMap = new HashMap<>(); for (MediaServer mediaServer : allInDatabase) { mediaServerMap.put(mediaServer.getId(), mediaServer); } for (MediaServer mediaServer : allInCatch) { // 清除数据中不存在但redis缓存数据 if (!mediaServerMap.containsKey(mediaServer.getId())) { delete(mediaServer); } } } @Override public MediaServerLoad getLoad(MediaServer mediaServer) { MediaServerLoad result = new MediaServerLoad(); result.setId(mediaServer.getId()); result.setPush(redisCatchStorage.getPushStreamCount(mediaServer.getId())); result.setProxy(redisCatchStorage.getProxyStreamCount(mediaServer.getId())); result.setGbReceive(inviteStreamService.getStreamInfoCount(mediaServer.getId())); result.setGbSend(redisCatchStorage.getGbSendCount(mediaServer.getId())); return result; } @Override public List getAllWithAssistPort() { return mediaServerMapper.queryAllWithAssistPort(userSetting.getServerId()); } @Override public boolean stopSendRtp(MediaServer mediaServer, String app, String stream, String ssrc) { IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); if (mediaNodeServerService == null) { log.info("[stopSendRtp] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); return false; } return mediaNodeServerService.stopSendRtp(mediaServer, app, stream, ssrc); } @Override public boolean initStopSendRtp(MediaServer mediaInfo, String app, String stream, String ssrc) { IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaInfo.getType()); if (mediaNodeServerService == null) { log.info("[stopSendRtp] 失败, mediaServer的类型: {},未找到对应的实现类", mediaInfo.getType()); return false; } return mediaNodeServerService.initStopSendRtp(mediaInfo, app, stream, ssrc); } @Override public boolean deleteRecordDirectory(MediaServer mediaServer, String app, String stream, String date, String fileName) { IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); if (mediaNodeServerService == null) { log.info("[stopSendRtp] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); return false; } return mediaNodeServerService.deleteRecordDirectory(mediaServer, app, stream, date, fileName); } @Override public List getMediaList(MediaServer mediaServer, String app, String stream, String callId) { IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); if (mediaNodeServerService == null) { log.info("[getMediaList] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); return new ArrayList<>(); } return mediaNodeServerService.getMediaList(mediaServer, app, stream, callId); } @Override public Boolean connectRtpServer(MediaServer mediaServer, String address, int port, String app, String stream) { IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); if (mediaNodeServerService == null) { log.info("[connectRtpServer] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); return false; } return mediaNodeServerService.connectRtpServer(mediaServer, address, port, app, stream); } @Override public void getSnap(MediaServer mediaServer, String app, String stream, int timeoutSec, int expireSec, String path, String fileName) { IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); if (mediaNodeServerService == null) { log.info("[getSnap] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); return; } mediaNodeServerService.getSnap(mediaServer, app, stream, timeoutSec, expireSec, path, fileName); } @Override public MediaInfo getMediaInfo(MediaServer mediaServer, String app, String stream) { IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); if (mediaNodeServerService == null) { log.info("[getMediaInfo] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); return null; } return mediaNodeServerService.getMediaInfo(mediaServer, app, stream); } @Override public Boolean pauseRtpCheck(MediaServer mediaServer, String streamKey) { IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); if (mediaNodeServerService == null) { log.info("[pauseRtpCheck] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); return false; } return mediaNodeServerService.pauseRtpCheck(mediaServer, streamKey); } @Override public boolean resumeRtpCheck(MediaServer mediaServer, String streamKey) { IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); if (mediaNodeServerService == null) { log.info("[pauseRtpCheck] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); return false; } return mediaNodeServerService.resumeRtpCheck(mediaServer, streamKey); } @Override public String getFfmpegCmd(MediaServer mediaServer, String cmdKey) { IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); if (mediaNodeServerService == null) { log.info("[getFfmpegCmd] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); return null; } return mediaNodeServerService.getFfmpegCmd(mediaServer, cmdKey); } @Override public void closeStreams(MediaServer mediaServer, String app, String stream) { IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); if (mediaNodeServerService == null) { log.info("[closeStreams] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); return; } mediaNodeServerService.closeStreams(mediaServer, app, stream); } @Override public WVPResult addStreamProxy(MediaServer mediaServer, String app, String stream, String url, boolean enableAudio, boolean enableMp4, String rtpType, Integer timeout) { IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); if (mediaNodeServerService == null) { log.info("[addStreamProxy] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); return WVPResult.fail(ErrorCode.ERROR400); } return mediaNodeServerService.addStreamProxy(mediaServer, app, stream, url, enableAudio, enableMp4, rtpType, timeout); } @Override public Boolean delFFmpegSource(MediaServer mediaServer, String streamKey) { IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); if (mediaNodeServerService == null) { log.info("[delFFmpegSource] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); return false; } return mediaNodeServerService.delFFmpegSource(mediaServer, streamKey); } @Override public Boolean delStreamProxy(MediaServer mediaServerItem, String streamKey) { IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServerItem.getType()); if (mediaNodeServerService == null) { log.info("[delStreamProxy] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServerItem.getType()); return false; } return mediaNodeServerService.delStreamProxy(mediaServerItem, streamKey); } @Override public Map getFFmpegCMDs(MediaServer mediaServer) { IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); if (mediaNodeServerService == null) { log.info("[getFFmpegCMDs] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); return new HashMap<>(); } return mediaNodeServerService.getFFmpegCMDs(mediaServer); } @Override public StreamInfo getStreamInfoByAppAndStream(MediaServer mediaServerItem, String app, String stream, MediaInfo mediaInfo, String callId) { return getStreamInfoByAppAndStream(mediaServerItem, app, stream, mediaInfo, null, callId, true); } @Override public StreamInfo getStreamInfoByAppAndStreamWithCheck(String app, String stream, String mediaServerId, String addr, boolean authority) { if (mediaServerId == null) { mediaServerId = mediaConfig.getId(); } MediaServer mediaInfo = getOne(mediaServerId); if (mediaInfo == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到使用的媒体节点"); } String calld = null; StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(app, stream); if (streamAuthorityInfo != null) { calld = streamAuthorityInfo.getCallId(); } List streamInfoList = getMediaList(mediaInfo, app, stream, calld); if (streamInfoList == null || streamInfoList.isEmpty()) { return null; }else { StreamInfo streamInfo = streamInfoList.get(0); if (addr != null && !addr.isEmpty()) { streamInfo.changeStreamIp(addr); } return streamInfo; } } @Override public StreamInfo getStreamInfoByAppAndStreamWithCheck(String app, String stream, String mediaServerId, boolean authority) { return getStreamInfoByAppAndStreamWithCheck(app, stream, mediaServerId, null, authority); } @Override public StreamInfo getStreamInfoByAppAndStream(MediaServer mediaServer, String app, String stream, MediaInfo mediaInfo, String addr, String callId, boolean isPlay) { IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); if (mediaNodeServerService == null) { log.info("[getStreamInfoByAppAndStream] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); return null; } return mediaNodeServerService.getStreamInfoByAppAndStream(mediaServer, app, stream, mediaInfo, addr, callId, isPlay); } @Override public Boolean isStreamReady(MediaServer mediaServer, String app, String streamId) { IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); if (mediaNodeServerService == null) { log.info("[isStreamReady] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); return false; } MediaInfo mediaInfo = mediaNodeServerService.getMediaInfo(mediaServer, app, streamId); return mediaInfo != null; } @Override public Integer startSendRtpPassive(MediaServer mediaServer, SendRtpInfo sendRtpItem, Integer timeout) { IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); if (mediaNodeServerService == null) { log.info("[startSendRtpPassive] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到mediaServer对应的实现类"); } return mediaNodeServerService.startSendRtpPassive(mediaServer, sendRtpItem, timeout); } @Override public Integer startSendRtpTalk(MediaServer mediaServer, TalkRtpInfo talkRtpInfo, Integer timeout) { IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); if (mediaNodeServerService == null) { log.info("[startSendRtpPassive] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到mediaServer对应的实现类"); } return mediaNodeServerService.startSendRtpTalk(mediaServer, talkRtpInfo, timeout); } @Override public void startSendRtp(MediaServer mediaServer, SendRtpInfo sendRtpItem) { IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); if (mediaNodeServerService == null) { log.info("[startSendRtpStream] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到mediaServer对应的实现类"); } sendRtpItem.setRtcp(true); log.info("[开始推流] {}/{}, 目标={}:{},SSRC={}, RTCP={}", sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc(), sendRtpItem.isRtcp()); mediaNodeServerService.startSendRtpStream(mediaServer, sendRtpItem); } @Override public MediaServer getMediaServerByAppAndStream(String app, String stream) { List mediaServerList = getAll(); for (MediaServer mediaServer : mediaServerList) { MediaInfo mediaInfo = getMediaInfo(mediaServer, app, stream); if (mediaInfo != null) { return mediaServer; } } return null; } @Override public StreamInfo getMediaByAppAndStream(String app, String stream) { List mediaServerList = getAll(); for (MediaServer mediaServer : mediaServerList) { MediaInfo mediaInfo = getMediaInfo(mediaServer, app, stream); if (mediaInfo != null) { return getStreamInfoByAppAndStream(mediaServer, app, stream, mediaInfo, mediaInfo.getCallId()); } } return null; } @Override public Long updateDownloadProcess(MediaServer mediaServer, String app, String stream) { IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); if (mediaNodeServerService == null) { log.info("[updateDownloadProcess] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到mediaServer对应的实现类"); } return mediaNodeServerService.updateDownloadProcess(mediaServer, app, stream); } @Override public String startProxy(MediaServer mediaServer, StreamProxy streamProxy) { IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); if (mediaNodeServerService == null) { log.info("[startProxy] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到mediaServer对应的实现类"); } return mediaNodeServerService.startProxy(mediaServer, streamProxy); } @Override public void stopProxy(MediaServer mediaServer, String streamKey, String type) { IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); if (mediaNodeServerService == null) { log.info("[stopProxy] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到mediaServer对应的实现类"); } mediaNodeServerService.stopProxy(mediaServer, streamKey, type); } @Override public void loadMP4FileForDate(MediaServer mediaServer, String app, String stream, String date, String dateDir, ErrorCallback callback) { IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); if (mediaNodeServerService == null) { log.info("[loadMP4FileForDate] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到mediaServer对应的实现类"); } mediaNodeServerService.loadMP4FileForDate(mediaServer, app, stream, date, dateDir, callback); } @Override public void loadMP4File(MediaServer mediaServer, String app, String stream, String filePath, String fileName, ErrorCallback callback) { IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); if (mediaNodeServerService == null) { log.info("[loadMP4File] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到mediaServer对应的实现类"); } mediaNodeServerService.loadMP4File(mediaServer, app, stream, filePath, fileName, callback); } @Override public void deleteDefault() { mediaServerMapper.deleteDefault(userSetting.getServerId()); } @Override public void seekRecordStamp(MediaServer mediaServer, String app, String stream, Double stamp, String schema) { IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); if (mediaNodeServerService == null) { log.info("[seekRecordStamp] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到mediaServer对应的实现类"); } mediaNodeServerService.seekRecordStamp(mediaServer, app, stream, stamp, schema); } @Override public void setRecordSpeed(MediaServer mediaServer, String app, String stream, Integer speed, String schema) { IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); if (mediaNodeServerService == null) { log.info("[setRecordSpeed] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到mediaServer对应的实现类"); } mediaNodeServerService.setRecordSpeed(mediaServer, app, stream, speed, schema); } @Override public DownloadFileInfo getDownloadFilePath(MediaServer mediaServer, RecordInfo recordInfo) { IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); if (mediaNodeServerService == null) { log.info("[setRecordSpeed] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到mediaServer对应的实现类"); } return mediaNodeServerService.getDownloadFilePath(mediaServer, recordInfo); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/zlm/AssistRESTfulUtils.java ================================================ package com.genersoft.iot.vmp.media.zlm; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.utils.SSLSocketClientUtil; import lombok.extern.slf4j.Slf4j; import okhttp3.*; import okhttp3.logging.HttpLoggingInterceptor; import org.jetbrains.annotations.NotNull; import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; import javax.net.ssl.X509TrustManager; import java.io.IOException; import java.net.ConnectException; import java.net.SocketTimeoutException; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.TimeUnit; @Slf4j @Component public class AssistRESTfulUtils { private OkHttpClient client; public interface RequestCallback{ void run(JSONObject response); } private OkHttpClient getClient(){ return getClient(null); } private OkHttpClient getClient(Integer readTimeOut){ if (client == null) { if (readTimeOut == null) { readTimeOut = 10; } OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder(); // 设置连接超时时间 httpClientBuilder.connectTimeout(8, TimeUnit.SECONDS); // 设置读取超时时间 httpClientBuilder.readTimeout(readTimeOut,TimeUnit.SECONDS); // 设置连接池 httpClientBuilder.connectionPool(new ConnectionPool(16, 5, TimeUnit.MINUTES)); if (log.isDebugEnabled()) { HttpLoggingInterceptor logging = new HttpLoggingInterceptor(message -> { log.debug("http请求参数:" + message); }); logging.setLevel(HttpLoggingInterceptor.Level.BASIC); // OkHttp進行添加攔截器loggingInterceptor httpClientBuilder.addInterceptor(logging); } X509TrustManager manager = SSLSocketClientUtil.getX509TrustManager(); // 设置ssl httpClientBuilder.sslSocketFactory(SSLSocketClientUtil.getSocketFactory(manager), manager); httpClientBuilder.hostnameVerifier(SSLSocketClientUtil.getHostnameVerifier());//忽略校验 client = httpClientBuilder.build(); } return client; } public JSONObject sendGet(MediaServer mediaServerItem, String api, Map param, RequestCallback callback) { OkHttpClient client = getClient(); if (mediaServerItem == null) { return null; } if (mediaServerItem.getRecordAssistPort() <= 0) { log.warn("未启用Assist服务"); return null; } StringBuilder stringBuffer = new StringBuilder(); stringBuffer.append(api); JSONObject responseJSON = null; if (param != null && !param.keySet().isEmpty()) { stringBuffer.append("?"); int index = 1; for (String key : param.keySet()){ if (param.get(key) != null) { stringBuffer.append(key + "=" + param.get(key)); if (index < param.size()) { stringBuffer.append("&"); } } index++; } } String url = stringBuffer.toString(); log.info("[访问assist]: {}", url); Request request = new Request.Builder() .get() .url(url) .build(); if (callback == null) { try { Response response = client.newCall(request).execute(); if (response.isSuccessful()) { ResponseBody responseBody = response.body(); if (responseBody != null) { String responseStr = responseBody.string(); responseJSON = JSON.parseObject(responseStr); } }else { response.close(); Objects.requireNonNull(response.body()).close(); } } catch (ConnectException e) { log.error(String.format("连接Assist失败: %s, %s", e.getCause().getMessage(), e.getMessage())); log.info("请检查media配置并确认Assist已启动..."); }catch (IOException e) { log.error(String.format("[ %s ]请求失败: %s", url, e.getMessage())); } }else { client.newCall(request).enqueue(new Callback(){ @Override public void onResponse(@NotNull Call call, @NotNull Response response){ if (response.isSuccessful()) { try { String responseStr = Objects.requireNonNull(response.body()).string(); callback.run(JSON.parseObject(responseStr)); } catch (IOException e) { log.error(String.format("[ %s ]请求失败: %s", url, e.getMessage())); } }else { response.close(); Objects.requireNonNull(response.body()).close(); } } @Override public void onFailure(@NotNull Call call, @NotNull IOException e) { log.error(String.format("连接Assist失败: %s, %s", e.getCause().getMessage(), e.getMessage())); log.info("请检查media配置并确认Assist已启动..."); } }); } return responseJSON; } public JSONObject sendPost(MediaServer mediaServerItem, String url, JSONObject param, ZLMRESTfulUtils.RequestCallback callback, Integer readTimeOut) { OkHttpClient client = getClient(readTimeOut); if (mediaServerItem == null) { return null; } log.info("[访问assist]: {}, 参数: {}", url, param); JSONObject responseJSON = new JSONObject(); //-2自定义流媒体 调用错误码 responseJSON.put("code",-2); responseJSON.put("msg","ASSIST调用失败"); RequestBody requestBodyJson = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), param.toString()); Request request = new Request.Builder() .post(requestBodyJson) .url(url) .addHeader("Content-Type", "application/json") .build(); if (callback == null) { try { Response response = client.newCall(request).execute(); if (response.isSuccessful()) { ResponseBody responseBody = response.body(); if (responseBody != null) { String responseStr = responseBody.string(); responseJSON = JSON.parseObject(responseStr); } }else { response.close(); Objects.requireNonNull(response.body()).close(); } }catch (IOException e) { log.error(String.format("[ %s ]ASSIST请求失败: %s", url, e.getMessage())); if(e instanceof SocketTimeoutException){ //读取超时超时异常 log.error(String.format("读取ASSIST数据失败: %s, %s", url, e.getMessage())); } if(e instanceof ConnectException){ //判断连接异常,我这里是报Failed to connect to 10.7.5.144 log.error(String.format("连接ASSIST失败: %s, %s", url, e.getMessage())); } }catch (Exception e){ log.error(String.format("访问ASSIST失败: %s, %s", url, e.getMessage())); } }else { client.newCall(request).enqueue(new Callback(){ @Override public void onResponse(@NotNull Call call, @NotNull Response response){ if (response.isSuccessful()) { try { String responseStr = Objects.requireNonNull(response.body()).string(); callback.run(responseStr); } catch (IOException e) { log.error(String.format("[ %s ]请求失败: %s", url, e.getMessage())); } }else { response.close(); Objects.requireNonNull(response.body()).close(); } } @Override public void onFailure(@NotNull Call call, @NotNull IOException e) { log.error(String.format("连接ZLM失败: %s, %s", call.request().toString(), e.getMessage())); if(e instanceof SocketTimeoutException){ //读取超时超时异常 log.error(String.format("读取ZLM数据失败: %s, %s", call.request().toString(), e.getMessage())); } if(e instanceof ConnectException){ //判断连接异常,我这里是报Failed to connect to 10.7.5.144 log.error(String.format("连接ZLM失败: %s, %s", call.request().toString(), e.getMessage())); } } }); } return responseJSON; } public JSONObject getInfo(MediaServer mediaServerItem, RequestCallback callback){ Map param = new HashMap<>(); return sendGet(mediaServerItem, "api/record/info",param, callback); } public JSONObject addTask(MediaServer mediaServerItem, String app, String stream, String startTime, String endTime, String callId, List filePathList, String remoteHost) { JSONObject videoTaskInfoJSON = new JSONObject(); videoTaskInfoJSON.put("app", app); videoTaskInfoJSON.put("stream", stream); videoTaskInfoJSON.put("startTime", startTime); videoTaskInfoJSON.put("endTime", endTime); videoTaskInfoJSON.put("callId", callId); videoTaskInfoJSON.put("filePathList", filePathList); if (!ObjectUtils.isEmpty(remoteHost)) { videoTaskInfoJSON.put("remoteHost", remoteHost); } String urlStr = String.format("%s/api/record/file/download/task/add", remoteHost);; return sendPost(mediaServerItem, urlStr, videoTaskInfoJSON, null, 30); } public JSONObject queryTaskList(MediaServer mediaServerItem, String app, String stream, String callId, String taskId, Boolean isEnd, String scheme) { Map param = new HashMap<>(); if (!ObjectUtils.isEmpty(app)) { param.put("app", app); } if (!ObjectUtils.isEmpty(stream)) { param.put("stream", stream); } if (!ObjectUtils.isEmpty(callId)) { param.put("callId", callId); } if (!ObjectUtils.isEmpty(taskId)) { param.put("taskId", taskId); } if (!ObjectUtils.isEmpty(isEnd)) { param.put("isEnd", isEnd); } String urlStr = String.format("%s://%s:%s/api/record/file/download/task/list", scheme, mediaServerItem.getIp(), mediaServerItem.getRecordAssistPort());; return sendGet(mediaServerItem, urlStr, param, null); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java ================================================ package com.genersoft.iot.vmp.media.zlm; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.common.enums.MediaApp; import com.genersoft.iot.vmp.conf.MediaConfig; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.bean.ResultForOnPublish; import com.genersoft.iot.vmp.media.event.media.*; import com.genersoft.iot.vmp.media.event.mediaServer.MediaSendRtpStoppedEvent; import com.genersoft.iot.vmp.media.service.IMediaServerService; import com.genersoft.iot.vmp.media.zlm.dto.ZLMServerConfig; import com.genersoft.iot.vmp.media.zlm.dto.hook.*; import com.genersoft.iot.vmp.media.zlm.event.HookZlmServerKeepaliveEvent; import com.genersoft.iot.vmp.media.zlm.event.HookZlmServerStartEvent; import com.genersoft.iot.vmp.service.IMediaService; import com.genersoft.iot.vmp.utils.MediaServerUtils; import io.swagger.v3.oas.annotations.Hidden; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationEventPublisher; import org.springframework.util.ObjectUtils; import org.springframework.web.bind.annotation.*; import jakarta.servlet.http.HttpServletRequest; import java.net.MalformedURLException; import java.net.URL; import java.util.HashMap; import java.util.Map; import java.util.Objects; /** * @description:针对 ZLMediaServer的hook事件监听 * @author: swwheihei * @date: 2020年5月8日 上午10:46:48 */ @Slf4j @RestController @RequestMapping("/index/hook") @Hidden public class ZLMHttpHookListener { @Autowired private IMediaServerService mediaServerService; @Autowired private IMediaService mediaService; @Autowired private MediaConfig mediaConfig; @Autowired private UserSetting userSetting; @Autowired private ApplicationEventPublisher applicationEventPublisher; /** * 服务器定时上报时间,上报间隔可配置,默认10s上报一次 */ @ResponseBody @PostMapping(value = "/on_server_keepalive", produces = "application/json;charset=UTF-8") public HookResult onServerKeepalive(@RequestBody OnServerKeepaliveHookParam param) { try { HookZlmServerKeepaliveEvent event = new HookZlmServerKeepaliveEvent(this); MediaServer mediaServerItem = mediaServerService.getOne(param.getMediaServerId()); if (mediaServerItem != null) { event.setMediaServerItem(mediaServerItem); applicationEventPublisher.publishEvent(event); } }catch (Exception e) { log.info("[ZLM-HOOK-心跳] 发送通知失败 ", e); } return HookResult.SUCCESS(); } /** * 播放器鉴权事件,rtsp/rtmp/http-flv/ws-flv/hls的播放都将触发此鉴权事件。 */ @ResponseBody @PostMapping(value = "/on_play", produces = "application/json;charset=UTF-8") public HookResult onPlay(@RequestBody OnPlayHookParam param) { Map paramMap = MediaServerUtils.urlParamToMap(param.getParams()); // 对于播放流进行鉴权 boolean authenticateResult = mediaService.authenticatePlay(param.getApp(), param.getStream(), paramMap.get("callId")); if (!authenticateResult) { log.info("[ZLM HOOK] 播放鉴权 失败:{}->{}", param.getMediaServerId(), param); return new HookResult(401, "Unauthorized"); } if (log.isDebugEnabled()){ log.debug("[ZLM HOOK] 播放鉴权成功:{}->{}", param.getMediaServerId(), param); } return HookResult.SUCCESS(); } /** * rtsp/rtmp/rtp推流鉴权事件。 */ @ResponseBody @PostMapping(value = "/on_publish", produces = "application/json;charset=UTF-8") public HookResultForOnPublish onPublish(@RequestBody OnPublishHookParam param) { JSONObject json = (JSONObject) JSON.toJSON(param); log.info("[ZLM HOOK]推流鉴权:{}->{}", param.getMediaServerId(), param); // TODO 加快处理速度 String mediaServerId = json.getString("mediaServerId"); MediaServer mediaServer = mediaServerService.getOne(mediaServerId); if (mediaServer == null) { HookResultForOnPublish fail = HookResultForOnPublish.Fail(); log.warn("[ZLM HOOK]推流鉴权 响应:{}->找不到对应的mediaServer", param.getMediaServerId()); return fail; } ResultForOnPublish resultForOnPublish = mediaService.authenticatePublish(mediaServer, param.getApp(), param.getStream(), param.getParams()); if (resultForOnPublish != null) { HookResultForOnPublish successResult = HookResultForOnPublish.getInstance(resultForOnPublish); log.info("[ZLM HOOK]推流鉴权 响应:{}->{}->>>>{}", param.getMediaServerId(), param, successResult); return successResult; }else { HookResultForOnPublish fail = HookResultForOnPublish.Fail(); log.info("[ZLM HOOK]推流鉴权 响应:{}->{}->>>>{}", param.getMediaServerId(), param, fail); return fail; } } /** * rtsp/rtmp流注册或注销时触发此事件;此事件对回复不敏感。 */ @ResponseBody @PostMapping(value = "/on_stream_changed", produces = "application/json;charset=UTF-8") public HookResult onStreamChanged(@RequestBody OnStreamChangedHookParam param) { MediaServer mediaServer = mediaServerService.getOne(param.getMediaServerId()); if (mediaServer == null) { return HookResult.SUCCESS(); } if (!ObjectUtils.isEmpty(mediaServer.getTranscodeSuffix()) && !"null".equalsIgnoreCase(mediaServer.getTranscodeSuffix()) && param.getStream().endsWith(mediaServer.getTranscodeSuffix()) ) { return HookResult.SUCCESS(); } if (param.getSchema().equalsIgnoreCase("rtsp")) { if (param.isRegist()) { log.info("[ZLM HOOK] 流注册, {}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream()); String queryParams = param.getParams(); if (queryParams == null) { try { URL url = new URL("http" + param.getOriginUrl().substring(4)); queryParams = url.getQuery(); }catch (MalformedURLException ignored) {} } if (queryParams != null) { param.setParamMap(MediaServerUtils.urlParamToMap(queryParams)); }else { param.setParamMap(new HashMap<>()); } MediaArrivalEvent mediaArrivalEvent = MediaArrivalEvent.getInstance(this, param, mediaServer, userSetting.getServerId()); applicationEventPublisher.publishEvent(mediaArrivalEvent); } else { log.info("[ZLM HOOK] 流注销, {}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream()); MediaDepartureEvent mediaDepartureEvent = MediaDepartureEvent.getInstance(this, param, mediaServer); applicationEventPublisher.publishEvent(mediaDepartureEvent); } } return HookResult.SUCCESS(); } /** * 流无人观看时事件,用户可以通过此事件选择是否关闭无人看的流。 */ @ResponseBody @PostMapping(value = "/on_stream_none_reader", produces = "application/json;charset=UTF-8") public JSONObject onStreamNoneReader(@RequestBody OnStreamNoneReaderHookParam param) { log.info("[ZLM HOOK]流无人观看:{}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream()); MediaServer mediaInfo = mediaServerService.getOne(param.getMediaServerId()); if (mediaInfo == null) { JSONObject ret = new JSONObject(); ret.put("code", 0); return ret; } if (mediaInfo.getTranscodeSuffix() != null && param.getStream().endsWith(mediaInfo.getTranscodeSuffix())) { param.setStream(param.getStream().substring(0, param.getStream().lastIndexOf(mediaInfo.getTranscodeSuffix()) - 1)); } if (!ObjectUtils.isEmpty(mediaInfo.getTranscodeSuffix()) && !"null".equalsIgnoreCase(mediaInfo.getTranscodeSuffix()) && param.getStream().endsWith(mediaInfo.getTranscodeSuffix()) ) { param.setStream(param.getStream().substring(0, param.getStream().lastIndexOf(mediaInfo.getTranscodeSuffix()) -1 )); } JSONObject ret = new JSONObject(); boolean close = mediaService.closeStreamOnNoneReader(param.getMediaServerId(), param.getApp(), param.getStream(), param.getSchema()); log.info("[ZLM HOOK]流无人观看是否触发关闭:{}, {}->{}->{}/{}", close, param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream()); ret.put("code", 0); ret.put("close", close); return ret; } /** * 流未找到事件,用户可以在此事件触发时,立即去拉流,这样可以实现按需拉流;此事件对回复不敏感。 */ @ResponseBody @PostMapping(value = "/on_stream_not_found", produces = "application/json;charset=UTF-8") public HookResult onStreamNotFound(@RequestBody OnStreamNotFoundHookParam param) { log.info("[ZLM HOOK] 流未找到:{}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream()); MediaServer mediaServer = mediaServerService.getOne(param.getMediaServerId()); if (!userSetting.getAutoApplyPlay() || mediaServer == null) { return HookResult.SUCCESS(); } MediaNotFoundEvent mediaNotFoundEvent = MediaNotFoundEvent.getInstance(this, param, mediaServer); applicationEventPublisher.publishEvent(mediaNotFoundEvent); return HookResult.SUCCESS(); } /** * 服务器启动事件,可以用于监听服务器崩溃重启;此事件对回复不敏感。 */ @ResponseBody @PostMapping(value = "/on_server_started", produces = "application/json;charset=UTF-8") public HookResult onServerStarted(HttpServletRequest request, @RequestBody JSONObject jsonObject) { jsonObject.put("ip", request.getRemoteAddr()); ZLMServerConfig zlmServerConfig = JSON.to(ZLMServerConfig.class, jsonObject); zlmServerConfig.setIp(request.getRemoteAddr()); log.info("[ZLM HOOK] zlm 启动 {}", zlmServerConfig.getGeneralMediaServerId()); try { HookZlmServerStartEvent event = new HookZlmServerStartEvent(this); MediaServer mediaServer = mediaServerService.getOne(zlmServerConfig.getMediaServerId()); if (mediaServer == null && Objects.equals(mediaConfig.getId(), zlmServerConfig.getGeneralMediaServerId())) { mediaServer = mediaConfig.buildMediaSer(); } if (mediaServer != null) { event.setMediaServer(mediaServer); event.setConfig(zlmServerConfig); applicationEventPublisher.publishEvent(event); }else { log.info("[ZLM HOOK] 此zlm未接入 {}", zlmServerConfig.getGeneralMediaServerId()); } }catch (Exception e) { log.info("[ZLM-HOOK-ZLM启动] 发送通知失败 ", e); } return HookResult.SUCCESS(); } /** * 发送rtp(startSendRtp)被动关闭时回调 */ @ResponseBody @PostMapping(value = "/on_send_rtp_stopped", produces = "application/json;charset=UTF-8") public HookResult onSendRtpStopped(HttpServletRequest request, @RequestBody OnSendRtpStoppedHookParam param) { log.info("[ZLM HOOK] rtp发送关闭:{}->{}/{}", param.getMediaServerId(), param.getApp(), param.getStream()); // 查找对应的上级推流,发送停止 if (!MediaApp.GB28181.equals(param.getApp())) { return HookResult.SUCCESS(); } try { MediaSendRtpStoppedEvent event = new MediaSendRtpStoppedEvent(this); MediaServer mediaServerItem = mediaServerService.getOne(param.getMediaServerId()); if (mediaServerItem != null) { event.setMediaServer(mediaServerItem); applicationEventPublisher.publishEvent(event); } }catch (Exception e) { log.info("[ZLM-HOOK-rtp发送关闭] 发送通知失败 ", e); } return HookResult.SUCCESS(); } /** * rtpServer收流超时 */ @ResponseBody @PostMapping(value = "/on_rtp_server_timeout", produces = "application/json;charset=UTF-8") public HookResult onRtpServerTimeout(@RequestBody OnRtpServerTimeoutHookParam param) { log.info("[ZLM HOOK] rtpServer收流超时:{}->{}({})", param.getMediaServerId(), param.getStream_id(), param.getSsrc()); try { MediaRtpServerTimeoutEvent event = new MediaRtpServerTimeoutEvent(this); MediaServer mediaServerItem = mediaServerService.getOne(param.getMediaServerId()); if (mediaServerItem != null) { event.setMediaServer(mediaServerItem); event.setApp(MediaApp.GB28181); applicationEventPublisher.publishEvent(event); } }catch (Exception e) { log.info("[ZLM-HOOK-rtpServer收流超时] 发送通知失败 ", e); } return HookResult.SUCCESS(); } /** * 录像完成事件 */ @ResponseBody @PostMapping(value = "/on_record_mp4", produces = "application/json;charset=UTF-8") public HookResult onRecordMp4(HttpServletRequest request, @RequestBody OnRecordMp4HookParam param) { log.info("[ZLM HOOK] 录像完成:时长: {}, {}->{}",param.getTime_len(), param.getMediaServerId(), param.getFile_path()); try { MediaServer mediaServerItem = mediaServerService.getOne(param.getMediaServerId()); if (mediaServerItem != null) { MediaRecordMp4Event event = MediaRecordMp4Event.getInstance(this, param, mediaServerItem); event.setMediaServer(mediaServerItem); applicationEventPublisher.publishEvent(event); } }catch (Exception e) { log.info("[ZLM-HOOK-rtpServer收流超时] 发送通知失败 ", e); } return HookResult.SUCCESS(); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaNodeServerService.java ================================================ package com.genersoft.iot.vmp.media.zlm; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.common.CommonCallback; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.common.enums.MediaApp; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.gb28181.bean.SendRtpInfo; import com.genersoft.iot.vmp.gb28181.bean.TalkRtpInfo; import com.genersoft.iot.vmp.media.bean.MediaInfo; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.bean.RecordInfo; import com.genersoft.iot.vmp.media.event.hook.Hook; import com.genersoft.iot.vmp.media.event.hook.HookSubscribe; import com.genersoft.iot.vmp.media.event.hook.HookType; import com.genersoft.iot.vmp.media.service.IMediaNodeServerService; import com.genersoft.iot.vmp.media.zlm.dto.*; import com.genersoft.iot.vmp.service.bean.DownloadFileInfo; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.RandomStringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.ObjectUtils; import java.util.*; @Slf4j @Service("zlm") public class ZLMMediaNodeServerService implements IMediaNodeServerService { @Autowired private ZLMRESTfulUtils zlmresTfulUtils; @Autowired private ZLMServerFactory zlmServerFactory; @Autowired private UserSetting userSetting; @Autowired private HookSubscribe subscribe; @Override public int createRTPServer(MediaServer mediaServer, String app, String stream, long ssrc, Integer port, Boolean onlyAuto, Boolean disableAudio, Boolean reUsePort, Integer tcpMode) { return zlmServerFactory.createRTPServer(mediaServer, app, stream, ssrc, port, onlyAuto, disableAudio, reUsePort, tcpMode); } @Override public void closeRtpServer(MediaServer mediaServer, String app, String stream, CommonCallback callback) { zlmServerFactory.closeRtpServer(mediaServer, app, stream, callback); } // @Override // public int createJTTServer(MediaServer mediaServer, String streamId, Integer port, Boolean disableVideo, Boolean disableAudio, Integer tcpMode) { // return zlmServerFactory.createRTPServer(mediaServer, "1078", streamId, 0, port, disableVideo, disableAudio, false, tcpMode); // } // @Override // public void closeJTTServer(MediaServer mediaServer, String streamId, CommonCallback callback) { // zlmServerFactory.closeRtpServer(mediaServer, streamId, callback); // } @Override public void closeStreams(MediaServer mediaServer, String app, String stream) { zlmresTfulUtils.closeStreams(mediaServer, app, stream); } @Override public Boolean updateRtpServerSSRC(MediaServer mediaServer, String app, String streamId, String ssrc) { return zlmServerFactory.updateRtpServerSSRC(mediaServer, app, streamId, ssrc); } @Override public boolean checkNodeId(MediaServer mediaServer) { if (mediaServer == null) { return false; } ZLMResult> mediaServerConfig = zlmresTfulUtils.getMediaServerConfig(mediaServer); if (mediaServerConfig != null) { List data = mediaServerConfig.getData(); if (data != null && !data.isEmpty()) { ZLMServerConfig zlmServerConfig= JSON.parseObject(JSON.toJSONString(data.get(0)), ZLMServerConfig.class); return zlmServerConfig.getGeneralMediaServerId().equals(mediaServer.getId()); }else { return false; } }else { return false; } } @Override public void online(MediaServer mediaServer) { } @Override public MediaServer checkMediaServer(String ip, int port, String secret) { MediaServer mediaServer = new MediaServer(); mediaServer.setServerId(userSetting.getServerId()); mediaServer.setIp(ip); mediaServer.setHttpPort(port); mediaServer.setSecret(secret); ZLMResult> mediaServerConfigResult = zlmresTfulUtils.getMediaServerConfig(mediaServer); if (mediaServerConfigResult == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "连接失败"); } List configList = mediaServerConfigResult.getData(); if (configList == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "读取配置失败"); } ZLMServerConfig zlmServerConfig = JSON.parseObject(JSON.toJSONString(configList.get(0)), ZLMServerConfig.class); if (zlmServerConfig == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "读取配置失败"); } mediaServer.setId(zlmServerConfig.getGeneralMediaServerId()); mediaServer.setHttpSSlPort(zlmServerConfig.getHttpSSLport()); mediaServer.setRtmpPort(zlmServerConfig.getRtmpPort()); mediaServer.setRtmpSSlPort(zlmServerConfig.getRtmpSslPort()); mediaServer.setRtspPort(zlmServerConfig.getRtspPort()); mediaServer.setRtspSSLPort(zlmServerConfig.getRtspSSlport()); mediaServer.setRtpProxyPort(zlmServerConfig.getRtpProxyPort()); mediaServer.setStreamIp(ip); mediaServer.setHookIp("127.0.0.1"); mediaServer.setSdpIp(ip); mediaServer.setType("zlm"); return mediaServer; } @Override public boolean stopSendRtp(MediaServer mediaInfo, String app, String stream, String ssrc) { Map param = new HashMap<>(); param.put("vhost", "__defaultVhost__"); param.put("app", app); param.put("stream", stream); if (!ObjectUtils.isEmpty(ssrc)) { param.put("ssrc", ssrc); } ZLMResult zlmResult = zlmresTfulUtils.stopSendRtp(mediaInfo, param); if (zlmResult.getCode() == 0) { log.info("[停止发流] 成功: 参数:{}", JSON.toJSONString(param)); return true; }else { log.info("停止发流结果: {}, 参数:{}", zlmResult.getMsg(), JSON.toJSONString(param)); return false; } } @Override public boolean initStopSendRtp(MediaServer mediaInfo, String app, String stream, String ssrc) { Map param = new HashMap<>(); param.put("vhost", "__defaultVhost__"); param.put("app", app); param.put("stream", stream); if (!ObjectUtils.isEmpty(ssrc)) { param.put("ssrc", ssrc); } ZLMResult zlmResult = zlmresTfulUtils.stopSendRtp(mediaInfo, param); if (zlmResult.getCode() != 0 ) { log.error("停止发流失败: {}, 参数:{}", zlmResult.getMsg(), JSON.toJSONString(param)); return false; } return true; } @Override public boolean deleteRecordDirectory(MediaServer mediaServer, String app, String stream, String date, String fileName) { log.info("[zlm-deleteRecordDirectory] 删除磁盘文件, server: {} {}:{}->{}/{}", mediaServer.getId(), app, stream, date, fileName); ZLMResult zlmResult = zlmresTfulUtils.deleteRecordDirectory(mediaServer, app, stream, date, fileName); if (zlmResult.getCode() == 0) { return true; }else { log.info("[zlm-deleteRecordDirectory] 删除磁盘文件错误, server: {} {}:{}->{}/{}, 结果: {}", mediaServer.getId(), app, stream, date, fileName, zlmResult); throw new ControllerException(ErrorCode.ERROR100.getCode(), "删除磁盘文件失败"); } } @Override public List getMediaList(MediaServer mediaServer, String app, String stream, String callId) { List streamInfoList = new ArrayList<>(); ZLMResult zlmResult = zlmresTfulUtils.getMediaList(mediaServer, app, stream); if (zlmResult != null) { if (zlmResult.getCode() == 0) { if (zlmResult.getData() == null) { return streamInfoList; } for (int i = 0; i < zlmResult.getData().size(); i++) { JSONObject mediaJSON = zlmResult.getData().getJSONObject(0); MediaInfo mediaInfo = MediaInfo.getInstance(mediaJSON, mediaServer, userSetting.getServerId()); StreamInfo streamInfo = getStreamInfoByAppAndStream(mediaServer, mediaInfo.getApp(), mediaInfo.getStream(), mediaInfo, null, callId, true); if (streamInfo != null) { streamInfoList.add(streamInfo); } } } } return streamInfoList; } @Override public Boolean connectRtpServer(MediaServer mediaServer, String address, int port, String app, String stream) { ZLMResult zlmResult = zlmresTfulUtils.connectRtpServer(mediaServer, address, port, app, stream); log.info("[TCP主动连接对方] 结果: {}", zlmResult); return zlmResult.getCode() == 0; } @Override public void getSnap(MediaServer mediaServer, String app, String stream, int timeoutSec, int expireSec, String path, String fileName) { String streamUrl; if (mediaServer.getRtspPort() != 0) { streamUrl = String.format("rtsp://127.0.0.1:%s/%s/%s", mediaServer.getRtspPort(), app, stream); } else { streamUrl = String.format("http://127.0.0.1:%s/%s/%s.live.mp4", mediaServer.getHttpPort(), app, stream); } zlmresTfulUtils.getSnap(mediaServer, streamUrl, timeoutSec, expireSec, path, fileName); } @Override public MediaInfo getMediaInfo(MediaServer mediaServer, String app, String stream) { ZLMResult zlmResult = zlmresTfulUtils.getMediaInfo(mediaServer, app, "rtsp", stream); if (zlmResult.getCode() != 0 || zlmResult.getData() == null || zlmResult.getData().getString("app") == null ) { return null; } return MediaInfo.getInstance(zlmResult.getData(), mediaServer, userSetting.getServerId()); } @Override public Boolean pauseRtpCheck(MediaServer mediaServer, String streamKey) { ZLMResult zlmResult = zlmresTfulUtils.pauseRtpCheck(mediaServer, streamKey); return zlmResult.getCode() == 0; } @Override public Boolean resumeRtpCheck(MediaServer mediaServer, String streamKey) { ZLMResult zlmResult = zlmresTfulUtils.resumeRtpCheck(mediaServer, streamKey); return zlmResult.getCode() == 0; } @Override public String getFfmpegCmd(MediaServer mediaServer, String cmdKey) { ZLMResult> mediaServerConfigResult = zlmresTfulUtils.getMediaServerConfig(mediaServer); if (mediaServerConfigResult == null || mediaServerConfigResult.getCode() != 0) { log.warn("[getFfmpegCmd] 获取流媒体配置失败"); throw new ControllerException(ErrorCode.ERROR100.getCode(), "获取流媒体配置失败"); } List data = mediaServerConfigResult.getData(); JSONObject mediaServerConfig = data.get(0); if (ObjectUtils.isEmpty(cmdKey)) { cmdKey = "ffmpeg.cmd"; } return mediaServerConfig.getString(cmdKey); } @Override public WVPResult addStreamProxy(MediaServer mediaServer, String app, String stream, String url, boolean enableAudio, boolean enableMp4, String rtpType, Integer timeout) { ZLMResult zlmResult = zlmresTfulUtils.addStreamProxy(mediaServer, app, stream, url, enableAudio, enableMp4, rtpType, timeout); if (zlmResult.getCode() != 0) { return WVPResult.fail(ErrorCode.ERROR100.getCode(), "添加代理失败"); }else { StreamProxyResult data = zlmResult.getData(); if (data == null) { return WVPResult.fail(ErrorCode.ERROR100.getCode(), "代理结果异常"); }else { return WVPResult.success(data.getKey()); } } } @Override public Boolean delFFmpegSource(MediaServer mediaServer, String streamKey) { ZLMResult flagDataZLMResult = zlmresTfulUtils.delFFmpegSource(mediaServer, streamKey); return flagDataZLMResult != null && flagDataZLMResult.getCode() == 0; } @Override public Boolean delStreamProxy(MediaServer mediaServer, String streamKey) { ZLMResult flagDataZLMResult = zlmresTfulUtils.delStreamProxy(mediaServer, streamKey); return flagDataZLMResult != null && flagDataZLMResult.getCode() == 0; } @Override public Map getFFmpegCMDs(MediaServer mediaServer) { Map result = new HashMap<>(); ZLMResult> mediaServerConfigResult = zlmresTfulUtils.getMediaServerConfig(mediaServer); if (mediaServerConfigResult != null && mediaServerConfigResult.getCode() == 0 && !mediaServerConfigResult.getData().isEmpty()){ JSONObject jsonObject = mediaServerConfigResult.getData().get(0); for (String key : jsonObject.keySet()) { if (key.startsWith("ffmpeg.cmd")){ result.put(key, jsonObject.getString(key)); } } } return result; } @Override public Integer startSendRtpPassive(MediaServer mediaServer, SendRtpInfo sendRtpItem, Integer timeout) { Map param = new HashMap<>(12); param.put("vhost","__defaultVhost__"); param.put("app", sendRtpItem.getApp()); param.put("stream", sendRtpItem.getStream()); param.put("ssrc", sendRtpItem.getSsrc()); param.put("src_port", sendRtpItem.getLocalPort()); param.put("pt", sendRtpItem.getPt()); param.put("use_ps", sendRtpItem.isUsePs() ? "1" : "0"); param.put("only_audio", sendRtpItem.isOnlyAudio() ? "1" : "0"); param.put("is_udp", sendRtpItem.isTcp() ? "0" : "1"); param.put("recv_stream_id", sendRtpItem.getReceiveStream()); param.put("enable_origin_recv_limit", "1"); if (timeout != null) { param.put("close_delay_ms", timeout); } if (!sendRtpItem.isTcp()) { // 开启rtcp保活 param.put("udp_rtcp_timeout", sendRtpItem.isRtcp()? "1":"0"); } if (!sendRtpItem.isTcpActive()) { param.put("dst_url",sendRtpItem.getIp()); param.put("dst_port", sendRtpItem.getPort()); } ZLMResult zlmResult = zlmServerFactory.startSendRtpPassive(mediaServer, param, null); if (zlmResult.getCode() != 0 ) { log.error("启动监听TCP被动推流失败: {}, 参数:{}", zlmResult.getMsg(), JSON.toJSONString(param)); throw new ControllerException(zlmResult.getCode(), zlmResult.getMsg()); } log.info("调用ZLM-TCP被动推流接口成功: 本地端口: {}", zlmResult.getLocal_port()); return zlmResult.getLocal_port(); } @Override public void startSendRtpStream(MediaServer mediaServer, SendRtpInfo sendRtpItem) { Map param = new HashMap<>(12); param.put("vhost", "__defaultVhost__"); param.put("app", sendRtpItem.getApp()); param.put("stream", sendRtpItem.getStream()); param.put("ssrc", sendRtpItem.getSsrc()); param.put("src_port", sendRtpItem.getLocalPort()); param.put("pt", sendRtpItem.getPt()); param.put("use_ps", sendRtpItem.isUsePs() ? "1" : "0"); param.put("only_audio", sendRtpItem.isOnlyAudio() ? "1" : "0"); param.put("is_udp", sendRtpItem.isTcp() ? "0" : "1"); param.put("enable_origin_recv_limit", "1"); if (!sendRtpItem.isTcp()) { // udp模式下开启rtcp保活 param.put("udp_rtcp_timeout", sendRtpItem.isRtcp() ? "500" : "0"); } param.put("dst_url", sendRtpItem.getIp()); param.put("dst_port", sendRtpItem.getPort()); ZLMResult zlmResult = zlmresTfulUtils.startSendRtp(mediaServer, param); if (zlmResult == null ) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "连接zlm失败"); }else if (zlmResult.getCode() != 0) { throw new ControllerException(zlmResult.getCode(), zlmResult.getMsg()); } log.info("[推流结果]:{} ,参数: {}", zlmResult, JSONObject.toJSONString(param)); } @Override public Integer startSendRtpTalk(MediaServer mediaServer, TalkRtpInfo talkRtpInfo, Integer timeout) { Map param = new HashMap<>(12); param.put("vhost","__defaultVhost__"); param.put("app", talkRtpInfo.getApp()); param.put("stream", talkRtpInfo.getStream()); param.put("ssrc", talkRtpInfo.getSsrc()); param.put("pt", talkRtpInfo.getPt()); param.put("type", talkRtpInfo.getType()); param.put("only_audio", talkRtpInfo.getOnlyAudio()); param.put("recv_stream_id", talkRtpInfo.getReceiveStreamId()); param.put("enable_origin_recv_limit", talkRtpInfo.getEnableOriginReceiveLimit() != null && talkRtpInfo.getEnableOriginReceiveLimit() == 1 ? "1" : "0"); ZLMResult zlmResult = zlmServerFactory.startSendRtpTalk(mediaServer, param, null); if (zlmResult.getCode() != 0 ) { log.error("启动监听TCP被动推流失败: {}, 参数:{}", zlmResult.getMsg(), JSON.toJSONString(param)); throw new ControllerException(zlmResult.getCode(), zlmResult.getMsg()); } log.info("调用ZLM-TCP被动推流接口, 成功 本地端口: {}", zlmResult.getLocal_port()); return zlmResult.getLocal_port(); } @Override public Long updateDownloadProcess(MediaServer mediaServer, String app, String stream) { MediaInfo mediaInfo = getMediaInfo(mediaServer, app, stream); if (mediaInfo == null) { log.warn("[获取下载进度] 查询进度失败, 节点Id: {}, {}/{}", mediaServer.getId(), app, stream); return null; } return mediaInfo.getDuration(); } @Override public String startProxy(MediaServer mediaServer, StreamProxy streamProxy) { String dstUrl; if ("ffmpeg".equalsIgnoreCase(streamProxy.getType())) { String ffmpegCmd = getFfmpegCmd(mediaServer, streamProxy.getFfmpegCmdKey()); if (ffmpegCmd == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "ffmpeg拉流代理无法获取ffmpeg cmd"); } String schema = getSchemaFromFFmpegCmd(ffmpegCmd); if (schema == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "ffmpeg拉流代理无法从ffmpeg cmd中获取到输出格式"); } int port; String schemaForUri; if (schema.equalsIgnoreCase("rtsp")) { port = mediaServer.getRtspPort(); schemaForUri = schema; }else if (schema.equalsIgnoreCase("flv")) { if (mediaServer.getRtmpPort() == 0) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "ffmpeg拉流代理播放时发现未设置rtmp端口"); } port = mediaServer.getRtmpPort(); schemaForUri = "rtmp"; }else { port = mediaServer.getRtmpPort(); schemaForUri = schema; } dstUrl = String.format("%s://%s:%s/%s/%s", schemaForUri, "127.0.0.1", port, streamProxy.getApp(), streamProxy.getStream()); }else { dstUrl = String.format("rtsp://%s:%s/%s/%s", "127.0.0.1", mediaServer.getRtspPort(), streamProxy.getApp(), streamProxy.getStream()); } MediaInfo mediaInfo = getMediaInfo(mediaServer, streamProxy.getApp(), streamProxy.getStream()); if (mediaInfo != null) { closeStreams(mediaServer, streamProxy.getApp(), streamProxy.getStream()); } ZLMResult zlmResult = null; if ("ffmpeg".equalsIgnoreCase(streamProxy.getType())){ if (streamProxy.getTimeout() == 0) { streamProxy.setTimeout(15); } zlmResult = zlmresTfulUtils.addFFmpegSource(mediaServer, streamProxy.getSrcUrl().trim(), dstUrl, streamProxy.getTimeout(), streamProxy.isEnableAudio(), streamProxy.isEnableMp4(), streamProxy.getFfmpegCmdKey()); }else { zlmResult = zlmresTfulUtils.addStreamProxy(mediaServer, streamProxy.getApp(), streamProxy.getStream(), streamProxy.getSrcUrl().trim(), streamProxy.isEnableAudio(), streamProxy.isEnableMp4(), streamProxy.getRtspType(), streamProxy.getTimeout()); } if (zlmResult.getCode() != 0) { throw new ControllerException(zlmResult.getCode(), zlmResult.getMsg()); }else { StreamProxyResult data = zlmResult.getData(); if (data == null) { throw new ControllerException(zlmResult.getCode(), "代理结果异常: " + zlmResult); }else { return data.getKey(); } } } private String getSchemaFromFFmpegCmd(String ffmpegCmd) { ffmpegCmd = ffmpegCmd.replaceAll(" + ", " "); String[] paramArray = ffmpegCmd.split(" "); if (paramArray.length == 0) { return null; } for (int i = 0; i < paramArray.length; i++) { if (paramArray[i].equalsIgnoreCase("-f")) { if (i + 1 < paramArray.length - 1) { return paramArray[i+1]; }else { return null; } } } return null; } @Override public void stopProxy(MediaServer mediaServer, String streamKey, String type) { ZLMResult zlmResult = zlmresTfulUtils.delStreamProxy(mediaServer, streamKey); if (zlmResult == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "请求失败"); }else if (zlmResult.getCode() != 0) { throw new ControllerException(zlmResult.getCode(), zlmResult.getMsg()); } } @Override public List listRtpServer(MediaServer mediaServer) { ZLMResult> zlmResult = zlmresTfulUtils.listRtpServer(mediaServer); List result = new ArrayList<>(); if (zlmResult.getCode() != 0) { return result; } List data = zlmResult.getData(); if (data == null || data.isEmpty()) { return result; } for (RtpServerResult datum : data) { result.add(datum.getStream_id()); } return result; } @Override public void loadMP4File(MediaServer mediaServer, String app, String stream, String filePath, String fileName, ErrorCallback callback) { String buildApp = "mp4_record"; String buildStream = app + "_" + stream + "_" + fileName + "_" + RandomStringUtils.randomAlphabetic(6).toLowerCase(); Hook hook = Hook.getInstance(HookType.on_media_arrival, buildApp, buildStream, mediaServer.getServerId()); subscribe.addSubscribe(hook, (hookData) -> { StreamInfo streamInfo = getStreamInfoByAppAndStream(mediaServer, buildApp, buildStream, hookData.getMediaInfo(), null, null, true); if (callback != null) { callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo); } }); ZLMResult zlmResult = zlmresTfulUtils.loadMP4File(mediaServer, buildApp, buildStream, filePath); if (zlmResult == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "请求失败"); } if (zlmResult.getCode() != 0) { throw new ControllerException(zlmResult.getCode(), zlmResult.getMsg()); } } @Override public void loadMP4FileForDate(MediaServer mediaServer, String app, String stream, String date, String dateDir, ErrorCallback callback) { String buildApp = "mp4_record"; String buildStream = app + "_" + stream + "_" + date; MediaInfo mediaInfo = getMediaInfo(mediaServer, buildApp, buildStream); if (mediaInfo != null) { if (callback != null) { StreamInfo streamInfo = getStreamInfoByAppAndStream(mediaServer, buildApp, buildStream, mediaInfo, null, null, true); callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo); } return; } Hook hook = Hook.getInstance(HookType.on_media_arrival, buildApp, buildStream, mediaServer.getServerId()); subscribe.addSubscribe(hook, (hookData) -> { StreamInfo streamInfo = getStreamInfoByAppAndStream(mediaServer, buildApp, buildStream, hookData.getMediaInfo(), null, null, true); if (callback != null) { callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo); } }); ZLMResult zlmResult = zlmresTfulUtils.loadMP4File(mediaServer, buildApp, buildStream, dateDir); if (zlmResult == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "请求失败"); } if (zlmResult.getCode() != 0) { throw new ControllerException(zlmResult.getCode(), zlmResult.getMsg()); } } @Override public void seekRecordStamp(MediaServer mediaServer, String app, String stream, Double stamp, String schema) { ZLMResult zlmResult = zlmresTfulUtils.seekRecordStamp(mediaServer, app, stream, stamp, schema); if (zlmResult == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "请求失败"); } if (zlmResult.getCode() != 0) { throw new ControllerException(zlmResult.getCode(), zlmResult.getMsg()); } } @Override public void setRecordSpeed(MediaServer mediaServer, String app, String stream, Integer speed, String schema) { ZLMResult zlmResult = zlmresTfulUtils.setRecordSpeed(mediaServer, app, stream, speed, schema); if (zlmResult == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "请求失败"); } if (zlmResult.getCode() != 0) { throw new ControllerException(zlmResult.getCode(), zlmResult.getMsg()); } } @Override public DownloadFileInfo getDownloadFilePath(MediaServer mediaServerItem, RecordInfo recordInfo) { // 将filePath作为独立参数传入,避免%符号解析问题 String pathTemplate = "%s://%s:%s/index/api/downloadFile?file_path=%s"; DownloadFileInfo info = new DownloadFileInfo(); // filePath作为第4个参数 info.setHttpPath(String.format(pathTemplate, "http", mediaServerItem.getStreamIp(), mediaServerItem.getHttpPort(), recordInfo.getFilePath())); // 同样作为第4个参数 if (mediaServerItem.getHttpSSlPort() > 0) { info.setHttpsPath(String.format(pathTemplate, "https", mediaServerItem.getStreamIp(), mediaServerItem.getHttpSSlPort(), recordInfo.getFilePath())); } return info; } @Override public StreamInfo getStreamInfoByAppAndStream(MediaServer mediaServer, String app, String stream, MediaInfo mediaInfo, String addr, String callId, boolean isPlay) { StreamInfo streamInfoResult = new StreamInfo(); streamInfoResult.setStream(stream); streamInfoResult.setApp(app); if (addr == null) { addr = mediaServer.getStreamIp(); } streamInfoResult.setIp(addr); if (mediaInfo != null) { streamInfoResult.setServerId(mediaInfo.getServerId()); }else { streamInfoResult.setServerId(userSetting.getServerId()); } streamInfoResult.setMediaServer(mediaServer); Map param = new HashMap<>(); if (!ObjectUtils.isEmpty(callId)) { param.put("callId", callId); } if (mediaInfo != null && !ObjectUtils.isEmpty(mediaInfo.getOriginTypeStr())) { if (!ObjectUtils.isEmpty(mediaInfo.getOriginTypeStr())) { param.put("originTypeStr", mediaInfo.getOriginTypeStr()); } if (!ObjectUtils.isEmpty(mediaInfo.getVideoCodec())) { param.put("videoCodec", mediaInfo.getVideoCodec()); } if (!ObjectUtils.isEmpty(mediaInfo.getAudioCodec())) { param.put("audioCodec", mediaInfo.getAudioCodec()); } } StringBuilder callIdParamBuilder = new StringBuilder(); if (!param.isEmpty()) { callIdParamBuilder.append("?"); for (Map.Entry entry : param.entrySet()) { callIdParamBuilder.append(entry.getKey()).append("=").append(entry.getValue()); callIdParamBuilder.append("&"); } callIdParamBuilder.deleteCharAt(callIdParamBuilder.length() - 1); } String callIdParam = callIdParamBuilder.toString(); streamInfoResult.setRtmp(addr, mediaServer.getRtmpPort(),mediaServer.getRtmpSSlPort(), app, stream, callIdParam); streamInfoResult.setRtsp(addr, mediaServer.getRtspPort(),mediaServer.getRtspSSLPort(), app, stream, callIdParam); String flvFile = String.format("%s/%s.live.flv%s", app, stream, callIdParam); streamInfoResult.setFlv(addr, mediaServer.getHttpPort(),mediaServer.getHttpSSlPort(), flvFile); streamInfoResult.setWsFlv(addr, mediaServer.getHttpPort(),mediaServer.getHttpSSlPort(), flvFile); String mp4File = String.format("%s/%s.live.mp4%s", app, stream, callIdParam); streamInfoResult.setFmp4(addr, mediaServer.getHttpPort(),mediaServer.getHttpSSlPort(), mp4File); streamInfoResult.setWsMp4(addr, mediaServer.getHttpPort(),mediaServer.getHttpSSlPort(), mp4File); streamInfoResult.setHls(addr, mediaServer.getHttpPort(), mediaServer.getHttpSSlPort(), app, stream, callIdParam); streamInfoResult.setWsHls(addr, mediaServer.getHttpPort(), mediaServer.getHttpSSlPort(), app, stream, callIdParam); streamInfoResult.setTs(addr, mediaServer.getHttpPort(), mediaServer.getHttpSSlPort(), app, stream, callIdParam); streamInfoResult.setWsTs(addr, mediaServer.getHttpPort(), mediaServer.getHttpSSlPort(), app, stream, callIdParam); streamInfoResult.setRtc(addr, mediaServer.getHttpPort(), mediaServer.getHttpSSlPort(), app, stream, callIdParam, isPlay); streamInfoResult.setMediaInfo(mediaInfo); if (!MediaApp.GB28181_BROADCAST.equalsIgnoreCase(app) && !ObjectUtils.isEmpty(mediaServer.getTranscodeSuffix()) && !"null".equalsIgnoreCase(mediaServer.getTranscodeSuffix())) { String newStream = stream + "_" + mediaServer.getTranscodeSuffix(); mediaServer.setTranscodeSuffix(null); StreamInfo transcodeStreamInfo = getStreamInfoByAppAndStream(mediaServer, app, newStream, null, addr, callId, isPlay); streamInfoResult.setTranscodeStream(transcodeStreamInfo); } return streamInfoResult; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaServerStatusManager.java ================================================ package com.genersoft.iot.vmp.media.zlm; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.conf.DynamicTask; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.gb28181.event.EventPublisher; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.event.mediaServer.MediaServerChangeEvent; import com.genersoft.iot.vmp.media.event.mediaServer.MediaServerDeleteEvent; import com.genersoft.iot.vmp.media.service.IMediaServerService; import com.genersoft.iot.vmp.media.zlm.dto.ZLMResult; import com.genersoft.iot.vmp.media.zlm.dto.ZLMServerConfig; import com.genersoft.iot.vmp.media.zlm.event.HookZlmServerKeepaliveEvent; import com.genersoft.iot.vmp.media.zlm.event.HookZlmServerStartEvent; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.event.EventListener; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; import java.io.File; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; /** * 管理zlm流媒体节点的状态 */ @Slf4j @Component public class ZLMMediaServerStatusManager { private final Map offlineZlmPrimaryMap = new ConcurrentHashMap<>(); private final Map offlineZlmsecondaryMap = new ConcurrentHashMap<>(); private final Map offlineZlmTimeMap = new ConcurrentHashMap<>(); @Autowired private ZLMRESTfulUtils zlmresTfulUtils; @Autowired private IMediaServerService mediaServerService; @Autowired private DynamicTask dynamicTask; @Autowired private UserSetting userSetting; @Value("${server.ssl.enabled:false}") private boolean sslEnabled; @Value("${server.port}") private Integer serverPort; @Value("${server.servlet.context-path:}") private String serverServletContextPath; @Autowired private EventPublisher eventPublisher; private final String type = "zlm"; @Async("taskExecutor") @EventListener public void onApplicationEvent(MediaServerChangeEvent event) { if (event.getMediaServerItemList() == null || event.getMediaServerItemList().isEmpty()) { return; } for (MediaServer mediaServerItem : event.getMediaServerItemList()) { if (!type.equals(mediaServerItem.getType())) { continue; } log.info("[ZLM-添加待上线节点] ID:{}", mediaServerItem.getId()); offlineZlmPrimaryMap.put(mediaServerItem.getId(), mediaServerItem); offlineZlmTimeMap.put(mediaServerItem.getId(), System.currentTimeMillis()); execute(); } } @Async("taskExecutor") @EventListener public void onApplicationEvent(HookZlmServerStartEvent event) { if (event.getMediaServer() == null || !type.equals(event.getMediaServer().getType()) || event.getMediaServer().isStatus()) { return; } log.info("[ZLM-HOOK事件-服务启动] ID:{}", event.getMediaServer().getId()); online(event.getMediaServer(), event.getConfig()); } @Async("taskExecutor") @EventListener public void onApplicationEvent(HookZlmServerKeepaliveEvent event) { if (event.getMediaServerItem() == null) { return; } MediaServer mediaServer = mediaServerService.getOne(event.getMediaServerItem().getId()); if (mediaServer == null) { return; } log.debug("[ZLM-HOOK事件-心跳] ID:{}", event.getMediaServerItem().getId()); online(mediaServer, null); } @Async("taskExecutor") @EventListener public void onApplicationEvent(MediaServerDeleteEvent event) { if (event.getMediaServer() == null) { return; } log.info("[ZLM-节点被移除] ID:" + event.getMediaServer().getId()); offlineZlmPrimaryMap.remove(event.getMediaServer().getId()); offlineZlmsecondaryMap.remove(event.getMediaServer().getId()); offlineZlmTimeMap.remove(event.getMediaServer().getId()); } @Scheduled(fixedDelay = 10*1000) //每隔10秒检查一次 public void execute(){ // 初次加入的离线节点会在30分钟内,每间隔十秒尝试一次,30分钟后如果仍然没有上线,则每隔30分钟尝试一次连接 if (offlineZlmPrimaryMap.isEmpty() && offlineZlmsecondaryMap.isEmpty()) { return; } if (!offlineZlmPrimaryMap.isEmpty()) { for (MediaServer mediaServerItem : offlineZlmPrimaryMap.values()) { if (offlineZlmTimeMap.get(mediaServerItem.getId()) != null && offlineZlmTimeMap.get(mediaServerItem.getId()) < System.currentTimeMillis() - 30*60*1000) { offlineZlmsecondaryMap.put(mediaServerItem.getId(), mediaServerItem); offlineZlmPrimaryMap.remove(mediaServerItem.getId()); continue; } log.info("[ZLM-尝试连接] ID:{}, 地址: {}:{}", mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort()); ZLMResult> mediaServerConfigResult = zlmresTfulUtils.getMediaServerConfig(mediaServerItem); ZLMServerConfig zlmServerConfig = null; if (mediaServerConfigResult == null) { log.info("[ZLM-尝试连接]失败, ID:{}, 地址: {}:{}", mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort()); continue; } List data = mediaServerConfigResult.getData(); if (data == null || data.isEmpty()) { log.info("[ZLM-尝试连接]失败, ID:{}, 地址: {}:{}", mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort()); }else { zlmServerConfig = JSON.parseObject(JSON.toJSONString(data.get(0)), ZLMServerConfig.class); initPort(mediaServerItem, zlmServerConfig); online(mediaServerItem, zlmServerConfig); } } } if (!offlineZlmsecondaryMap.isEmpty()) { for (MediaServer mediaServerItem : offlineZlmsecondaryMap.values()) { if (offlineZlmTimeMap.get(mediaServerItem.getId()) < System.currentTimeMillis() - 30*60*1000) { continue; } log.info("[ZLM-尝试连接] ID:{}, 地址: {}:{}", mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort()); ZLMResult> mediaServerConfig = zlmresTfulUtils.getMediaServerConfig(mediaServerItem); ZLMServerConfig zlmServerConfig = null; if (mediaServerConfig == null) { log.info("[ZLM-尝试连接]失败, ID:{}, 地址: {}:{}", mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort()); offlineZlmTimeMap.put(mediaServerItem.getId(), System.currentTimeMillis()); continue; } List data = mediaServerConfig.getData(); if (data == null || data.isEmpty()) { log.info("[ZLM-尝试连接]失败, ID:{}, 地址: {}:{}", mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort()); offlineZlmTimeMap.put(mediaServerItem.getId(), System.currentTimeMillis()); }else { zlmServerConfig = JSON.parseObject(JSON.toJSONString(data.get(0)), ZLMServerConfig.class); initPort(mediaServerItem, zlmServerConfig); online(mediaServerItem, zlmServerConfig); } } } } private void online(MediaServer mediaServer, ZLMServerConfig config) { MediaServer mediaServerInDb = mediaServerService.getOne(mediaServer.getId()); if (mediaServerInDb == null || !mediaServerInDb.isStatus()) { log.info("[ZLM-连接成功] ID:{}, 地址: {}:{}", mediaServer.getId(), mediaServer.getIp(), mediaServer.getHttpPort()); if (config == null) { ZLMResult> mediaServerConfig = zlmresTfulUtils.getMediaServerConfig(mediaServer); List data = mediaServerConfig.getData(); if (data != null && !data.isEmpty()) { config = JSON.parseObject(JSON.toJSONString(data.get(0)), ZLMServerConfig.class); }else { log.info("[ZLM-连接成功] 读取流媒体配置失败 ID:{}, 地址: {}:{}", mediaServer.getId(), mediaServer.getIp(), mediaServer.getHttpPort()); return; } } // 发送上线通知 eventPublisher.mediaServerOnlineEventPublish(mediaServer); mediaServer.setStatus(true); mediaServer.setServerId(userSetting.getServerId()); mediaServer.setHookAliveInterval(config.getHookAliveInterval()); initPort(mediaServer, config); mediaServerService.update(mediaServer); setZLMConfig(mediaServer, false); } offlineZlmPrimaryMap.remove(mediaServer.getId()); offlineZlmsecondaryMap.remove(mediaServer.getId()); offlineZlmTimeMap.remove(mediaServer.getId()); // 设置两次心跳未收到则认为zlm离线 String key = "zlm-keepalive-" + mediaServer.getId(); dynamicTask.startDelay(key, ()->{ log.warn("[ZLM-心跳超时] ID:{}", mediaServer.getId()); mediaServer.setStatus(false); offlineZlmPrimaryMap.put(mediaServer.getId(), mediaServer); offlineZlmTimeMap.put(mediaServer.getId(), System.currentTimeMillis()); // 发送离线通知 eventPublisher.mediaServerOfflineEventPublish(mediaServer); mediaServerService.update(mediaServer); }, (int)(mediaServer.getHookAliveInterval() * 2 * 1000)); } private void initPort(MediaServer mediaServerItem, ZLMServerConfig zlmServerConfig) { // 端口只会从配置中读取一次,一旦自己配置或者读取过了将不在配置 mediaServerItem.setHttpSSlPort(zlmServerConfig.getHttpSSLport()); mediaServerItem.setRtmpPort(zlmServerConfig.getRtmpPort()); mediaServerItem.setRtmpSSlPort(zlmServerConfig.getRtmpSslPort()); mediaServerItem.setRtspPort(zlmServerConfig.getRtspPort()); mediaServerItem.setRtspSSLPort(zlmServerConfig.getRtspSSlport()); mediaServerItem.setRtpProxyPort(zlmServerConfig.getRtpProxyPort()); mediaServerItem.setFlvSSLPort(zlmServerConfig.getHttpSSLport()); mediaServerItem.setWsFlvSSLPort(zlmServerConfig.getHttpSSLport()); if (Objects.isNull(zlmServerConfig.getTranscodeSuffix())) { mediaServerItem.setTranscodeSuffix(null); }else { mediaServerItem.setTranscodeSuffix(zlmServerConfig.getTranscodeSuffix()); } mediaServerItem.setRtpProxyPort(zlmServerConfig.getRtpProxyPort()); mediaServerItem.setHookAliveInterval(zlmServerConfig.getHookAliveInterval()); } public void setZLMConfig(MediaServer mediaServerItem, boolean restart) { log.info("[媒体服务节点] 正在设置 :{} -> {}:{}", mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort()); String protocol = sslEnabled ? "https" : "http"; String hookPrefix = String.format("%s://%s:%s%s/index/hook", protocol, mediaServerItem.getHookIp(), serverPort, (serverServletContextPath == null || "/".equals(serverServletContextPath)) ? "" : serverServletContextPath); Map param = new HashMap<>(); if (mediaServerItem.getRtspPort() != 0) { param.put("ffmpeg.snap", "%s -rtsp_transport tcp -i %s -y -f mjpeg -frames:v 1 %s"); } param.put("hook.enable","1"); param.put("hook.on_flow_report",""); param.put("hook.on_play",String.format("%s/on_play", hookPrefix)); param.put("hook.on_http_access",""); param.put("hook.on_publish", String.format("%s/on_publish", hookPrefix)); param.put("hook.on_record_ts",""); param.put("hook.on_rtsp_auth",""); param.put("hook.on_rtsp_realm",""); param.put("hook.on_server_started",String.format("%s/on_server_started", hookPrefix)); param.put("hook.on_shell_login",""); param.put("hook.on_stream_changed",String.format("%s/on_stream_changed", hookPrefix)); param.put("hook.on_stream_none_reader",String.format("%s/on_stream_none_reader", hookPrefix)); param.put("hook.on_stream_not_found",String.format("%s/on_stream_not_found", hookPrefix)); param.put("hook.on_server_keepalive",String.format("%s/on_server_keepalive", hookPrefix)); param.put("hook.on_send_rtp_stopped",String.format("%s/on_send_rtp_stopped", hookPrefix)); param.put("hook.on_rtp_server_timeout",String.format("%s/on_rtp_server_timeout", hookPrefix)); param.put("hook.on_record_mp4",String.format("%s/on_record_mp4", hookPrefix)); param.put("hook.timeoutSec","30"); // 推流断开后可以在超时时间内重新连接上继续推流,这样播放器会接着播放。 // 置0关闭此特性(推流断开会导致立即断开播放器) // 此参数不应大于播放器超时时间 // 优化此消息以更快的收到流注销事件 param.put("protocol.continue_push_ms", "3000" ); // 最多等待未初始化的Track时间,单位毫秒,超时之后会忽略未初始化的Track, 设置此选项优化那些音频错误的不规范流, // 等zlm支持给每个rtpServer设置关闭音频的时候可以不设置此选项 if (mediaServerItem.isRtpEnable() && !ObjectUtils.isEmpty(mediaServerItem.getRtpPortRange())) { param.put("rtp_proxy.port_range", mediaServerItem.getRtpPortRange().replace(",", "-")); } if (!ObjectUtils.isEmpty(mediaServerItem.getRecordPath())) { File recordPathFile = new File(mediaServerItem.getRecordPath()); param.put("protocol.mp4_save_path", recordPathFile.getParentFile().getPath()); param.put("protocol.downloadRoot", recordPathFile.getParentFile().getPath()); param.put("record.appName", recordPathFile.getName()); } ZLMResult zlmResult = zlmresTfulUtils.setServerConfig(mediaServerItem, param); if (zlmResult != null && zlmResult.getCode() == 0) { if (restart) { log.info("[媒体服务节点] 设置成功,开始重启以保证配置生效 {} -> {}:{}", mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort()); zlmresTfulUtils.restartServer(mediaServerItem); }else { log.info("[媒体服务节点] 设置成功 {} -> {}:{}", mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort()); } }else { log.info("[媒体服务节点] 设置媒体服务节点失败 {} -> {}:{}", mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort()); } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java ================================================ package com.genersoft.iot.vmp.media.zlm; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.TypeReference; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.zlm.dto.*; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import lombok.extern.slf4j.Slf4j; import okhttp3.*; import okhttp3.logging.HttpLoggingInterceptor; import org.jetbrains.annotations.NotNull; import org.springframework.stereotype.Component; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.math.BigDecimal; import java.net.ConnectException; import java.net.SocketTimeoutException; import java.net.URLEncoder; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.TimeUnit; @Slf4j @Component public class ZLMRESTfulUtils { private OkHttpClient client; public interface RequestCallback{ void run(String response); } public interface ResultCallback{ void run(ZLMResult response); } private OkHttpClient getClient(){ return getClient(null); } private OkHttpClient getClient(Integer readTimeOut){ if (client == null) { if (readTimeOut == null) { readTimeOut = 10; } OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder(); //todo 暂时写死超时时间 均为5s // 设置连接超时时间 httpClientBuilder.connectTimeout(8,TimeUnit.SECONDS); // 设置读取超时时间 httpClientBuilder.readTimeout(readTimeOut,TimeUnit.SECONDS); // 设置连接池 httpClientBuilder.connectionPool(new ConnectionPool(16, 5, TimeUnit.MINUTES)); if (log.isDebugEnabled()) { HttpLoggingInterceptor logging = new HttpLoggingInterceptor(message -> { log.debug("http请求参数:" + message); }); logging.setLevel(HttpLoggingInterceptor.Level.BASIC); // OkHttp進行添加攔截器loggingInterceptor httpClientBuilder.addInterceptor(logging); } client = httpClientBuilder.build(); } return client; } public String sendPost(MediaServer mediaServer, String api, Map param, RequestCallback callback) { return sendPost(mediaServer, api, param, callback, null); } public String sendPost(MediaServer mediaServer, String api, Map param, RequestCallback callback, Integer readTimeOut) { OkHttpClient client = getClient(readTimeOut); if (mediaServer == null) { return null; } String url = String.format("http://%s:%s/index/api/%s", mediaServer.getIp(), mediaServer.getHttpPort(), api); String result = null; FormBody.Builder builder = new FormBody.Builder(); builder.add("secret",mediaServer.getSecret()); if (param != null && !param.isEmpty()) { for (String key : param.keySet()){ if (param.get(key) != null) { builder.add(key, param.get(key).toString()); } } } FormBody body = builder.build(); Request request = new Request.Builder() .post(body) .url(url) .build(); if (callback == null) { try { Response response = client.newCall(request).execute(); if (response.isSuccessful()) { ResponseBody responseBody = response.body(); if (responseBody != null) { result = responseBody.string(); } }else { response.close(); Objects.requireNonNull(response.body()).close(); } }catch (IOException e) { log.error(String.format("[ %s ]请求失败: %s", url, e.getMessage())); if(e instanceof SocketTimeoutException){ //读取超时超时异常 log.error(String.format("读取ZLM数据超时失败: %s, %s", url, e.getMessage())); } if(e instanceof ConnectException){ //判断连接异常,我这里是报Failed to connect to 10.7.5.144 log.error(String.format("连接ZLM连接失败: %s, %s", url, e.getMessage())); } }catch (Exception e){ log.error(String.format("访问ZLM失败: %s, %s", url, e.getMessage())); } }else { client.newCall(request).enqueue(new Callback(){ @Override public void onResponse(@NotNull Call call, @NotNull Response response){ if (response.isSuccessful()) { try { String responseStr = Objects.requireNonNull(response.body()).string(); callback.run(responseStr); } catch (IOException e) { log.error(String.format("[ %s ]请求失败: %s", url, e.getMessage())); } }else { response.close(); Objects.requireNonNull(response.body()).close(); } } @Override public void onFailure(@NotNull Call call, @NotNull IOException e) { log.error(String.format("连接ZLM失败: %s, %s", call.request().toString(), e.getMessage())); if(e instanceof SocketTimeoutException){ //读取超时超时异常 log.error(String.format("读取ZLM数据失败: %s, %s", call.request().toString(), e.getMessage())); } if(e instanceof ConnectException){ //判断连接异常,我这里是报Failed to connect to 10.7.5.144 log.error(String.format("连接ZLM失败: %s, %s", call.request().toString(), e.getMessage())); } } }); } return result; } public void sendGetForImg(MediaServer mediaServer, String api, Map params, String targetPath, String fileName) { String url = String.format("http://%s:%s/index/api/%s", mediaServer.getIp(), mediaServer.getHttpPort(), api); HttpUrl parseUrl = HttpUrl.parse(url); if (parseUrl == null) { return; } HttpUrl.Builder httpBuilder = parseUrl.newBuilder(); httpBuilder.addQueryParameter("secret", mediaServer.getSecret()); if (params != null) { for (Map.Entry param : params.entrySet()) { httpBuilder.addQueryParameter(param.getKey(), param.getValue().toString()); } } Request request = new Request.Builder() .url(httpBuilder.build()) .build(); if (log.isDebugEnabled()){ log.debug(request.toString()); } try { OkHttpClient client = getClient(); Response response = client.newCall(request).execute(); if (response.isSuccessful()) { if (targetPath != null) { File snapFolder = new File(targetPath); if (!snapFolder.exists()) { if (!snapFolder.mkdirs()) { log.warn("{}路径创建失败", snapFolder.getAbsolutePath()); } } File snapFile = new File(targetPath + File.separator + fileName); FileOutputStream outStream = new FileOutputStream(snapFile); outStream.write(Objects.requireNonNull(response.body()).bytes()); outStream.flush(); outStream.close(); } else { log.error(String.format("[ %s ]请求失败: %s %s", url, response.code(), response.message())); } } else { log.error(String.format("[ %s ]请求失败: %s %s", url, response.code(), response.message())); } Objects.requireNonNull(response.body()).close(); } catch (ConnectException e) { log.error(String.format("连接ZLM失败: %s, %s", e.getCause().getMessage(), e.getMessage())); log.info("请检查media配置并确认ZLM已启动..."); } catch (IOException e) { log.error(String.format("[ %s ]请求失败: %s", url, e.getMessage())); } } public ZLMResult isMediaOnline(MediaServer mediaServer, String app, String stream, String schema){ Map param = new HashMap<>(); if (app != null) { param.put("app",app); } if (stream != null) { param.put("stream",stream); } if (schema != null) { param.put("schema",schema); } param.put("vhost","__defaultVhost__"); String response = sendPost(mediaServer, "isMediaOnline", param, null); if (response == null) { return ZLMResult.getFailForMediaServer(); }else { ZLMResult zlmResult = JSON.parseObject(response, ZLMResult.class); if (zlmResult == null) { return ZLMResult.getFailForMediaServer(); }else { return zlmResult; } } } public ZLMResult getMediaList(MediaServer mediaServer, String app, String stream, String schema, ResultCallback callback){ Map param = new HashMap<>(); if (app != null) { param.put("app",app); } if (stream != null) { param.put("stream",stream); } if (schema != null) { param.put("schema",schema); } param.put("vhost","__defaultVhost__"); RequestCallback requestCallback = null; if (callback != null) { requestCallback = (responseStr -> { if (callback == null) { return; } if (responseStr == null) { callback.run(ZLMResult.getFailForMediaServer()); }else { ZLMResult zlmResult = JSON.parseObject(responseStr, new TypeReference>() {}); if (zlmResult == null) { callback.run(ZLMResult.getFailForMediaServer()); }else { callback.run(zlmResult); } } }); } String response = sendPost(mediaServer, "getMediaList",param, requestCallback); if (response == null) { return ZLMResult.getFailForMediaServer(); }else { ZLMResult zlmResult = JSON.parseObject(response, new TypeReference>() {}); if (zlmResult == null) { return ZLMResult.getFailForMediaServer(); }else { return zlmResult; } } } public ZLMResult getMediaList(MediaServer mediaServer, String app, String stream){ return getMediaList(mediaServer, app, stream,null, null); } public ZLMResult getMediaInfo(MediaServer mediaServer, String app, String schema, String stream){ Map param = new HashMap<>(); param.put("app",app); param.put("schema",schema); param.put("stream",stream); param.put("vhost","__defaultVhost__"); String response = sendPost(mediaServer, "getMediaInfo",param, null); if (response == null) { return ZLMResult.getFailForMediaServer(); }else { JSONObject jsonObject = JSON.parseObject(response); if (jsonObject == null) { return ZLMResult.getFailForMediaServer(); }else { ZLMResult zlmResult = new ZLMResult<>(); zlmResult.setCode(0); zlmResult.setData(jsonObject); return zlmResult; } } } public ZLMResult getRtpInfo(MediaServer mediaServer, String stream_id){ Map param = new HashMap<>(); param.put("stream_id",stream_id); String response = sendPost(mediaServer, "getRtpInfo",param, null); if (response == null) { return ZLMResult.getFailForMediaServer(); }else { ZLMResult zlmResult = JSON.parseObject(response, ZLMResult.class); if (zlmResult == null) { return ZLMResult.getFailForMediaServer(); }else { return zlmResult; } } } public ZLMResult addFFmpegSource(MediaServer mediaServer, String src_url, String dst_url, Integer timeout_sec, boolean enable_audio, boolean enable_mp4, String ffmpeg_cmd_key){ try { src_url = URLEncoder.encode(src_url, "UTF-8"); } catch (UnsupportedEncodingException e) { throw new ControllerException(ErrorCode.ERROR100.getCode(),"url编码失败"); } Map param = new HashMap<>(); param.put("src_url", src_url); param.put("dst_url", dst_url); param.put("timeout_ms", timeout_sec*1000); param.put("enable_mp4", enable_mp4); param.put("ffmpeg_cmd_key", ffmpeg_cmd_key); String response = sendPost(mediaServer, "addFFmpegSource",param, null); if (response == null) { return ZLMResult.getFailForMediaServer(); }else { ZLMResult zlmResult = JSON.parseObject(response, new TypeReference>() {}); if (zlmResult == null) { return ZLMResult.getFailForMediaServer(); }else { return zlmResult; } } } public ZLMResult delFFmpegSource(MediaServer mediaServer, String key){ Map param = new HashMap<>(); param.put("key", key); String response = sendPost(mediaServer, "delFFmpegSource",param, null); if (response == null) { return ZLMResult.getFailForMediaServer(); }else { ZLMResult zlmResult = JSON.parseObject(response, new TypeReference>() {}); if (zlmResult == null) { return ZLMResult.getFailForMediaServer(); }else { return zlmResult; } } } public ZLMResult delStreamProxy(MediaServer mediaServer, String key){ Map param = new HashMap<>(); param.put("key", key); String response = sendPost(mediaServer, "delStreamProxy",param, null); if (response == null) { return ZLMResult.getFailForMediaServer(); }else { ZLMResult zlmResult = JSON.parseObject(response, new TypeReference>() {}); if (zlmResult == null) { return ZLMResult.getFailForMediaServer(); }else { return zlmResult; } } } public ZLMResult> getMediaServerConfig(MediaServer mediaServer ){ String response = sendPost(mediaServer, "getServerConfig",null, null); if (response == null) { return ZLMResult.getFailForMediaServer(); }else { ZLMResult> zlmResult = JSON.parseObject(response, new TypeReference>>() {}); if (zlmResult == null) { return ZLMResult.getFailForMediaServer(); }else { return zlmResult; } } } public ZLMResult setServerConfig(MediaServer mediaServer, Map param){ String response = sendPost(mediaServer, "setServerConfig",param, null); if (response == null) { return ZLMResult.getFailForMediaServer(); }else { ZLMResult zlmResult = JSON.parseObject(response, ZLMResult.class); if (zlmResult == null) { return ZLMResult.getFailForMediaServer(); }else { return zlmResult; } } } public ZLMResult openRtpServer(MediaServer mediaServer, Map param){ String response = sendPost(mediaServer, "openRtpServer",param, null); if (response == null) { return ZLMResult.getFailForMediaServer(); }else { ZLMResult zlmResult = JSON.parseObject(response, ZLMResult.class); if (zlmResult == null) { return ZLMResult.getFailForMediaServer(); }else { return zlmResult; } } } public ZLMResult closeRtpServer(MediaServer mediaServer, Map param) { String response = sendPost(mediaServer, "closeRtpServer",param, null); if (response == null) { return ZLMResult.getFailForMediaServer(); }else { ZLMResult zlmResult = JSON.parseObject(response, ZLMResult.class); if (zlmResult == null) { return ZLMResult.getFailForMediaServer(); }else { return zlmResult; } } } public void closeRtpServer(MediaServer mediaServer, Map param, ResultCallback callback) { sendPost(mediaServer, "closeRtpServer",param, (response -> { if (callback == null) { return; } if (response == null) { callback.run(ZLMResult.getFailForMediaServer()); }else { callback.run(JSON.parseObject(response, ZLMResult.class)); } })); } public ZLMResult> listRtpServer(MediaServer mediaServer) { String response = sendPost(mediaServer, "listRtpServer",null, null); if (response == null) { return ZLMResult.getFailForMediaServer(); }else { ZLMResult> zlmResult = JSON.parseObject(response, new TypeReference>>() {}); if (zlmResult == null) { return ZLMResult.getFailForMediaServer(); }else { return zlmResult; } } } public ZLMResult startSendRtp(MediaServer mediaServer, Map param) { String response = sendPost(mediaServer, "startSendRtp",param, null); if (response == null) { return ZLMResult.getFailForMediaServer(); }else { ZLMResult zlmResult = JSON.parseObject(response, ZLMResult.class); if (zlmResult == null) { return ZLMResult.getFailForMediaServer(); }else { return zlmResult; } } } public ZLMResult startSendRtpPassive(MediaServer mediaServer, Map param) { String response = sendPost(mediaServer, "startSendRtpPassive",param, null); if (response == null) { return ZLMResult.getFailForMediaServer(); }else { ZLMResult zlmResult = JSON.parseObject(response, ZLMResult.class); if (zlmResult == null) { return ZLMResult.getFailForMediaServer(); }else { return zlmResult; } } } public ZLMResult startSendRtpPassive(MediaServer mediaServer, Map param, ResultCallback callback) { RequestCallback requestCallback = null; if (callback != null) { requestCallback = (responseStr -> { if (callback == null) { return; } if (responseStr == null) { callback.run(ZLMResult.getFailForMediaServer()); }else { ZLMResult zlmResult = JSON.parseObject(responseStr, ZLMResult.class); if (zlmResult == null) { callback.run(ZLMResult.getFailForMediaServer()); }else { callback.run(zlmResult); } } }); } String response = sendPost(mediaServer, "startSendRtpPassive",param, requestCallback); if (response == null) { return ZLMResult.getFailForMediaServer(); }else { ZLMResult zlmResult = JSON.parseObject(response, ZLMResult.class); if (zlmResult == null) { return ZLMResult.getFailForMediaServer(); }else { return zlmResult; } } } public ZLMResult startSendRtpTalk(MediaServer mediaServer, Map param, ResultCallback callback) { String response = sendPost(mediaServer, "startSendRtpTalk",param, (responseStr -> { if (callback == null) { return; } if (responseStr == null) { callback.run(ZLMResult.getFailForMediaServer()); }else { callback.run(JSON.parseObject(responseStr, ZLMResult.class)); } })); if (response == null) { return ZLMResult.getFailForMediaServer(); }else { ZLMResult zlmResult = JSON.parseObject(response, ZLMResult.class); if (zlmResult == null) { return ZLMResult.getFailForMediaServer(); }else { return zlmResult; } } } public ZLMResult stopSendRtp(MediaServer mediaServer, Map param) { String response = sendPost(mediaServer, "stopSendRtp",param, null); if (response == null) { return ZLMResult.getFailForMediaServer(); }else { ZLMResult zlmResult = JSON.parseObject(response, ZLMResult.class); if (zlmResult == null) { return ZLMResult.getFailForMediaServer(); }else { return zlmResult; } } } public ZLMResult restartServer(MediaServer mediaServer) { String response = sendPost(mediaServer, "restartServer",null, null); if (response == null) { return ZLMResult.getFailForMediaServer(); }else { ZLMResult zlmResult = JSON.parseObject(response, ZLMResult.class); if (zlmResult == null) { return ZLMResult.getFailForMediaServer(); }else { return zlmResult; } } } public ZLMResult addStreamProxy(MediaServer mediaServer, String app, String stream, String url, boolean enable_audio, boolean enable_mp4, String rtp_type, Integer timeOut) { Map param = new HashMap<>(); param.put("vhost", "__defaultVhost__"); param.put("app", app); param.put("stream", stream); param.put("url", url); param.put("enable_mp4", enable_mp4?1:0); param.put("enable_audio", enable_audio?1:0); param.put("rtp_type", rtp_type); param.put("timeout_sec", timeOut); // 拉流重试次数,默认为3 param.put("retry_count", 3); String response = sendPost(mediaServer, "addStreamProxy",param, null); if (response == null) { return ZLMResult.getFailForMediaServer(); }else { ZLMResult zlmResult = JSON.parseObject(response, new TypeReference>() {}); if (zlmResult == null) { return ZLMResult.getFailForMediaServer(); }else { return zlmResult; } } } public ZLMResult closeStreams(MediaServer mediaServer, String app, String stream) { Map param = new HashMap<>(); param.put("vhost", "__defaultVhost__"); param.put("app", app); param.put("stream", stream); param.put("force", 1); String response = sendPost(mediaServer, "close_streams",param, null); if (response == null) { return ZLMResult.getFailForMediaServer(); }else { ZLMResult zlmResult = JSON.parseObject(response, new TypeReference>() {}); if (zlmResult == null) { return ZLMResult.getFailForMediaServer(); }else { return zlmResult; } } } public ZLMResult> getAllSession(MediaServer mediaServer) { String response = sendPost(mediaServer, "getAllSession",null, null); if (response == null) { return ZLMResult.getFailForMediaServer(); }else { ZLMResult> zlmResult = JSON.parseObject(response, new TypeReference>>() {}); if (zlmResult == null) { return ZLMResult.getFailForMediaServer(); }else { return zlmResult; } } } public void kickSessions(MediaServer mediaServer, String localPortSStr) { Map param = new HashMap<>(); param.put("local_port", localPortSStr); sendPost(mediaServer, "kick_sessions",param, null); } public void getSnap(MediaServer mediaServer, String streamUrl, int timeout_sec, int expire_sec, String targetPath, String fileName) { Map param = new HashMap<>(3); param.put("url", streamUrl); param.put("timeout_sec", timeout_sec); param.put("expire_sec", expire_sec); param.put("async", 1); sendGetForImg(mediaServer, "getSnap", param, targetPath, fileName); } public ZLMResult pauseRtpCheck(MediaServer mediaServer, String streamId) { Map param = new HashMap<>(1); param.put("stream_id", streamId); String response = sendPost(mediaServer, "pauseRtpCheck", param, null); if (response == null) { return ZLMResult.getFailForMediaServer(); }else { ZLMResult zlmResult = JSON.parseObject(response, ZLMResult.class); if (zlmResult == null) { return ZLMResult.getFailForMediaServer(); }else { return zlmResult; } } } public ZLMResult resumeRtpCheck(MediaServer mediaServer, String streamId) { Map param = new HashMap<>(1); param.put("stream_id", streamId); String response = sendPost(mediaServer, "resumeRtpCheck", param, null); if (response == null) { return ZLMResult.getFailForMediaServer(); }else { ZLMResult zlmResult = JSON.parseObject(response, ZLMResult.class); if (zlmResult == null) { return ZLMResult.getFailForMediaServer(); }else { return zlmResult; } } } public ZLMResult connectRtpServer(MediaServer mediaServer, String dst_url, int dst_port, String app, String stream_id) { Map param = new HashMap<>(1); param.put("dst_url", dst_url); param.put("dst_port", dst_port); param.put("app", app); param.put("stream_id", stream_id); String response = sendPost(mediaServer, "connectRtpServer", param, null); if (response == null) { return ZLMResult.getFailForMediaServer(); }else { ZLMResult zlmResult = JSON.parseObject(response, ZLMResult.class); if (zlmResult == null) { return ZLMResult.getFailForMediaServer(); }else { return zlmResult; } } } public ZLMResult updateRtpServerSSRC(MediaServer mediaServer, String app, String streamId, String ssrc) { Map param = new HashMap<>(1); param.put("ssrc", ssrc); param.put("app", app); param.put("stream_id", streamId); String response = sendPost(mediaServer, "updateRtpServerSSRC", param, null); if (response == null) { return ZLMResult.getFailForMediaServer(); }else { ZLMResult zlmResult = JSON.parseObject(response, ZLMResult.class); if (zlmResult == null) { return ZLMResult.getFailForMediaServer(); }else { return zlmResult; } } } public ZLMResult deleteRecordDirectory(MediaServer mediaServer, String app, String stream, String date, String fileName) { Map param = new HashMap<>(1); param.put("vhost", "__defaultVhost__"); param.put("app", app); param.put("stream", stream); param.put("period", date); param.put("name", fileName); String response = sendPost(mediaServer, "deleteRecordDirectory", param, null); if (response == null) { return ZLMResult.getFailForMediaServer(); }else { ZLMResult zlmResult = JSON.parseObject(response, ZLMResult.class); if (zlmResult == null) { return ZLMResult.getFailForMediaServer(); }else { return zlmResult; } } } public ZLMResult loadMP4File(MediaServer mediaServer, String app, String stream, String datePath) { Map param = new HashMap<>(1); param.put("vhost", "__defaultVhost__"); param.put("app", app); param.put("stream", stream); param.put("file_path", datePath); param.put("file_repeat", "0"); String response = sendPost(mediaServer, "loadMP4File", param, null); if (response == null) { return ZLMResult.getFailForMediaServer(); }else { ZLMResult zlmResult = JSON.parseObject(response, ZLMResult.class); if (zlmResult == null) { return ZLMResult.getFailForMediaServer(); }else { return zlmResult; } } } public ZLMResult setRecordSpeed(MediaServer mediaServer, String app, String stream, int speed, String schema) { Map param = new HashMap<>(1); param.put("vhost", "__defaultVhost__"); param.put("app", app); param.put("stream", stream); param.put("speed", speed); param.put("schema", schema); String response = sendPost(mediaServer, "setRecordSpeed", param, null); if (response == null) { return ZLMResult.getFailForMediaServer(); }else { ZLMResult zlmResult = JSON.parseObject(response, ZLMResult.class); if (zlmResult == null) { return ZLMResult.getFailForMediaServer(); }else { return zlmResult; } } } public ZLMResult seekRecordStamp(MediaServer mediaServer, String app, String stream, Double stamp, String schema) { Map param = new HashMap<>(1); param.put("vhost", "__defaultVhost__"); param.put("app", app); param.put("stream", stream); BigDecimal bigDecimal = new BigDecimal(stamp); param.put("stamp", bigDecimal); param.put("schema", schema); String response = sendPost(mediaServer, "seekRecordStamp", param, null); if (response == null) { return ZLMResult.getFailForMediaServer(); }else { ZLMResult zlmResult = JSON.parseObject(response, ZLMResult.class); if (zlmResult == null) { return ZLMResult.getFailForMediaServer(); }else { return zlmResult; } } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMServerFactory.java ================================================ package com.genersoft.iot.vmp.media.zlm; import com.alibaba.fastjson2.JSONArray; import com.genersoft.iot.vmp.common.CommonCallback; import com.genersoft.iot.vmp.gb28181.bean.SendRtpInfo; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.zlm.dto.ZLMResult; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.HashMap; import java.util.Map; @Slf4j @Component public class ZLMServerFactory { @Autowired private ZLMRESTfulUtils zlmresTfulUtils; /** * 开启rtpServer * @param mediaServerItem zlm服务实例 * @param streamId 流Id * @param ssrc ssrc * @param port 端口, 0/null为使用随机 * @param reUsePort 是否重用端口 * @param tcpMode 0/null udp 模式,1 tcp 被动模式, 2 tcp 主动模式。 * @return */ public int createRTPServer(MediaServer mediaServerItem, String app, String streamId, long ssrc, Integer port, Boolean onlyAuto, Boolean disableAudio, Boolean reUsePort, Integer tcpMode) { int result = -1; // 查询此rtp server 是否已经存在 ZLMResult rtpInfoResult = zlmresTfulUtils.getRtpInfo(mediaServerItem, streamId); if(rtpInfoResult.getCode() == 0){ if (rtpInfoResult.getExist() != null && rtpInfoResult.getExist()) { result = rtpInfoResult.getLocal_port(); if (result == 0) { // 此时说明rtpServer已经创建但是流还没有推上来 // 此时重新打开rtpServer Map param = new HashMap<>(); param.put("stream_id", streamId); ZLMResult zlmResult = zlmresTfulUtils.closeRtpServer(mediaServerItem, param); if (zlmResult != null ) { if (zlmResult.getCode() == 0) { return createRTPServer(mediaServerItem, streamId, app, ssrc, port,onlyAuto, reUsePort,disableAudio, tcpMode); }else { log.warn("[开启rtpServer], 重启RtpServer错误"); } } } return result; } }else if(rtpInfoResult.getCode() == -2){ return result; } Map param = new HashMap<>(); if (tcpMode == null) { tcpMode = 0; } param.put("tcp_mode", tcpMode); param.put("app", app); param.put("stream_id", streamId); if (disableAudio != null) { param.put("only_track", disableAudio?2:0); } if (reUsePort != null) { param.put("re_use_port", reUsePort?"1":"0"); } // 推流端口设置0则使用随机端口 if (port == null) { param.put("port", 0); }else { param.put("port", port); } if (onlyAuto != null) { param.put("only_audio", onlyAuto?"1":"0"); } if (ssrc != 0) { param.put("ssrc", ssrc); } ZLMResult zlmResult = zlmresTfulUtils.openRtpServer(mediaServerItem, param); if (zlmResult != null) { if (zlmResult.getCode() == 0) { result= zlmResult.getPort(); }else { log.error("创建RTP Server 失败 {}: ", zlmResult.getMsg()); } }else { // 检查ZLM状态 log.error("创建RTP Server 失败 {}: 请检查ZLM服务", param.get("port")); } return result; } public boolean closeRtpServer(MediaServer serverItem, String app, String streamId) { boolean result = false; if (serverItem !=null){ Map param = new HashMap<>(); param.put("app", app); param.put("stream_id", streamId); ZLMResult zlmResult = zlmresTfulUtils.closeRtpServer(serverItem, param); if (zlmResult != null ) { if (zlmResult.getCode() == 0) { result = zlmResult.getHit() >= 1; }else { log.error("关闭RTP Server 失败: " + zlmResult.getMsg()); } }else { // 检查ZLM状态 log.error("关闭RTP Server 失败: 请检查ZLM服务"); } } return result; } public void closeRtpServer(MediaServer serverItem, String app, String streamId, CommonCallback callback) { if (serverItem == null) { if (callback != null) { callback.run(false); } return; } Map param = new HashMap<>(); param.put("app", app); param.put("stream_id", streamId); zlmresTfulUtils.closeRtpServer(serverItem, param, zlmResult -> { if (zlmResult.getCode() == 0) { if (callback != null) { callback.run(zlmResult.getHit() >= 1); } return; }else { log.error("关闭RTP Server 失败: " + zlmResult.getMsg()); } if (callback != null) { callback.run(false); } }); } /** * 调用zlm RESTFUL API —— startSendRtp */ public ZLMResult startSendRtpStream(MediaServer mediaServerItem, Mapparam) { return zlmresTfulUtils.startSendRtp(mediaServerItem, param); } /** * 调用zlm RESTFUL API —— startSendRtpPassive */ public ZLMResult startSendRtpPassive(MediaServer mediaServerItem, Mapparam) { return zlmresTfulUtils.startSendRtpPassive(mediaServerItem, param); } public ZLMResult startSendRtpPassive(MediaServer mediaServerItem, Map param, ZLMRESTfulUtils.ResultCallback callback) { return zlmresTfulUtils.startSendRtpPassive(mediaServerItem, param, callback); } public ZLMResult startSendRtpTalk(MediaServer mediaServer, Map param, ZLMRESTfulUtils.ResultCallback callback) { return zlmresTfulUtils.startSendRtpTalk(mediaServer, param, callback); } /** * 查询待转推的流是否就绪 */ public Boolean isStreamReady(MediaServer mediaServerItem, String app, String streamId) { ZLMResult zlmResult = zlmresTfulUtils.getMediaList(mediaServerItem, app, streamId); if (zlmResult == null || zlmResult.getCode() == -2) { return null; } ZLMResult result = (ZLMResult) zlmResult; return (result.getCode() == 0 && result.getData() != null && !result.getData().isEmpty()); } public ZLMResult startSendRtp(MediaServer mediaInfo, SendRtpInfo sendRtpItem) { String is_Udp = sendRtpItem.isTcp() ? "0" : "1"; log.info("rtp/{}开始推流, 目标={}:{},SSRC={}", sendRtpItem.getStream(), sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc()); Map param = new HashMap<>(12); param.put("vhost","__defaultVhost__"); param.put("app",sendRtpItem.getApp()); param.put("stream",sendRtpItem.getStream()); param.put("ssrc", sendRtpItem.getSsrc()); param.put("src_port", sendRtpItem.getLocalPort()); param.put("pt", sendRtpItem.getPt()); param.put("use_ps", sendRtpItem.isUsePs() ? "1" : "0"); param.put("only_audio", sendRtpItem.isOnlyAudio() ? "1" : "0"); if (!sendRtpItem.isTcp()) { // udp模式下开启rtcp保活 param.put("udp_rtcp_timeout", sendRtpItem.isRtcp()? "1":"0"); } if (mediaInfo == null) { return null; } // 如果是非严格模式,需要关闭端口占用 ZLMResult zlmResult = null; if (sendRtpItem.getLocalPort() != 0) { if (sendRtpItem.isTcpActive()) { zlmResult = startSendRtpPassive(mediaInfo, param); }else { param.put("is_udp", is_Udp); param.put("dst_url", sendRtpItem.getIp()); param.put("dst_port", sendRtpItem.getPort()); zlmResult = startSendRtpStream(mediaInfo, param); } }else { if (sendRtpItem.isTcpActive()) { zlmResult = startSendRtpPassive(mediaInfo, param); }else { param.put("is_udp", is_Udp); param.put("dst_url", sendRtpItem.getIp()); param.put("dst_port", sendRtpItem.getPort()); zlmResult = startSendRtpStream(mediaInfo, param); } } return zlmResult; } public Boolean updateRtpServerSSRC(MediaServer mediaServerItem, String app, String streamId, String ssrc) { boolean result = false; ZLMResult zlmResult = zlmresTfulUtils.updateRtpServerSSRC(mediaServerItem, app, streamId, ssrc); if (zlmResult.getCode() == 0) { result= true; log.info("[更新RTPServer] 成功"); } else { log.error("[更新RTPServer] 失败: {}, streamId:{},ssrc:{}", zlmResult.getMsg(), streamId, ssrc); } return result; } public ZLMResult stopSendRtpStream(MediaServer mediaServerItem, SendRtpInfo sendRtpItem) { Map param = new HashMap<>(); param.put("vhost", "__defaultVhost__"); param.put("app", sendRtpItem.getApp()); param.put("stream", sendRtpItem.getStream()); param.put("ssrc", sendRtpItem.getSsrc()); return zlmresTfulUtils.stopSendRtp(mediaServerItem, param); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/zlm/dto/ChannelOnlineEvent.java ================================================ package com.genersoft.iot.vmp.media.zlm.dto; import com.genersoft.iot.vmp.gb28181.bean.SendRtpInfo; import java.text.ParseException; /** * @author lin */ public interface ChannelOnlineEvent { void run(SendRtpInfo sendRtpItem) throws ParseException; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/zlm/dto/FlagData.java ================================================ package com.genersoft.iot.vmp.media.zlm.dto; import lombok.Data; @Data public class FlagData { private boolean flag; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/zlm/dto/RtpServerResult.java ================================================ package com.genersoft.iot.vmp.media.zlm.dto; import lombok.Data; @Data public class RtpServerResult { private Integer port; private String stream_id; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/zlm/dto/ServerKeepaliveData.java ================================================ package com.genersoft.iot.vmp.media.zlm.dto; public class ServerKeepaliveData { } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/zlm/dto/SessionData.java ================================================ package com.genersoft.iot.vmp.media.zlm.dto; import lombok.Data; @Data public class SessionData { private String id; private String local_ip; private Integer local_port; private String peer_ip; private Integer peer_port; private String typeid; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/zlm/dto/StreamAuthorityInfo.java ================================================ package com.genersoft.iot.vmp.media.zlm.dto; import com.genersoft.iot.vmp.media.event.media.MediaArrivalEvent; /** * 流的鉴权信息 * @author lin */ public class StreamAuthorityInfo { private String id; private String app; private String stream; /** * 产生源类型, * unknown = 0, * rtmp_push=1, * rtsp_push=2, * rtp_push=3, * pull=4, * ffmpeg_pull=5, * mp4_vod=6, * device_chn=7 */ private int originType; /** * 产生源类型的字符串描述 */ private String originTypeStr; /** * 推流时自定义的播放鉴权ID */ private String callId; /** * 推流的鉴权签名 */ private String sign; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getApp() { return app; } public void setApp(String app) { this.app = app; } public String getStream() { return stream; } public void setStream(String stream) { this.stream = stream; } public int getOriginType() { return originType; } public void setOriginType(int originType) { this.originType = originType; } public String getOriginTypeStr() { return originTypeStr; } public void setOriginTypeStr(String originTypeStr) { this.originTypeStr = originTypeStr; } public String getCallId() { return callId; } public void setCallId(String callId) { this.callId = callId; } public String getSign() { return sign; } public void setSign(String sign) { this.sign = sign; } public static StreamAuthorityInfo getInstanceByHook(String app, String stream, String id) { StreamAuthorityInfo streamAuthorityInfo = new StreamAuthorityInfo(); streamAuthorityInfo.setApp(app); streamAuthorityInfo.setStream(stream); streamAuthorityInfo.setId(id); return streamAuthorityInfo; } public static StreamAuthorityInfo getInstanceByHook(MediaArrivalEvent event) { StreamAuthorityInfo streamAuthorityInfo = new StreamAuthorityInfo(); streamAuthorityInfo.setApp(event.getApp()); streamAuthorityInfo.setStream(event.getStream()); streamAuthorityInfo.setId(event.getMediaServer().getId()); if (event.getMediaInfo() != null) { streamAuthorityInfo.setOriginType(event.getMediaInfo().getOriginType()); } return streamAuthorityInfo; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/zlm/dto/StreamProxyResult.java ================================================ package com.genersoft.iot.vmp.media.zlm.dto; import lombok.Data; @Data public class StreamProxyResult { private String key; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/zlm/dto/ZLMResult.java ================================================ package com.genersoft.iot.vmp.media.zlm.dto; import lombok.Data; @Data public class ZLMResult { private int code; private String msg; private T data; private Boolean online; private Boolean exist; private String peer_ip; private Integer peer_port; private String local_ip; private Integer local_port; private Integer changed; private Integer port; private Integer hit; public static ZLMResult getFailForMediaServer() { ZLMResult zlmResult = new ZLMResult<>(); zlmResult.setCode(-2); zlmResult.setMsg("流媒体调用失败"); return zlmResult; } public static ZLMResult getMediaServer(int code, String msg) { return getMediaServer(code, msg, null); } public static ZLMResult getMediaServer(int code, String msg, T data) { ZLMResult zlmResult = new ZLMResult<>(); zlmResult.setCode(code); zlmResult.setMsg(msg); zlmResult.setData(data); return zlmResult; } @Override public String toString() { return "ZLMResult{" + "code=" + code + ", msg='" + msg + '\'' + ", data=" + data + (online != null ? (", online=" + online) : "") + (exist != null ? (", exist=" + exist) : "") + (peer_ip != null ? (", peer_ip=" + peer_ip) : "") + (peer_port != null ? (", peer_port=" + peer_port) : "") + (local_ip != null ? (", local_ip=" + local_ip) : "") + (local_port != null ? (", local_port=" + local_port) : "") + (changed != null ? (", changed=" + changed) : "") + (port != null ? (", port=" + port) : "") + (hit != null ? (", hit=" + hit) : "") + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/zlm/dto/ZLMRunInfo.java ================================================ package com.genersoft.iot.vmp.media.zlm.dto; /** * 记录zlm运行中一些参数 */ public class ZLMRunInfo { /** * zlm当前流数量 */ private int mediaCount; /** * 在线状态 */ private boolean online; public int getMediaCount() { return mediaCount; } public void setMediaCount(int mediaCount) { this.mediaCount = mediaCount; } public boolean isOnline() { return online; } public void setOnline(boolean online) { this.online = online; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/zlm/dto/ZLMServerConfig.java ================================================ package com.genersoft.iot.vmp.media.zlm.dto; import com.alibaba.fastjson2.annotation.JSONField; import com.genersoft.iot.vmp.media.zlm.dto.hook.HookParam; import lombok.Data; import lombok.EqualsAndHashCode; @EqualsAndHashCode(callSuper = true) @Data public class ZLMServerConfig extends HookParam { @JSONField(name = "api.apiDebug") private String apiDebug; @JSONField(name = "api.secret") private String apiSecret; @JSONField(name = "api.snapRoot") private String apiSnapRoot; @JSONField(name = "api.defaultSnap") private String apiDefaultSnap; @JSONField(name = "ffmpeg.bin") private String ffmpegBin; @JSONField(name = "ffmpeg.cmd") private String ffmpegCmd; @JSONField(name = "ffmpeg.snap") private String ffmpegSnap; @JSONField(name = "ffmpeg.log") private String ffmpegLog; @JSONField(name = "ffmpeg.restart_sec") private String ffmpegRestartSec; @JSONField(name = "protocol.modify_stamp") private String protocolModifyStamp; @JSONField(name = "protocol.enable_audio") private String protocolEnableAudio; @JSONField(name = "protocol.add_mute_audio") private String protocolAddMuteAudio; @JSONField(name = "protocol.continue_push_ms") private String protocolContinuePushMs; @JSONField(name = "protocol.enable_hls") private String protocolEnableHls; @JSONField(name = "protocol.enable_mp4") private String protocolEnableMp4; @JSONField(name = "protocol.enable_rtsp") private String protocolEnableRtsp; @JSONField(name = "protocol.enable_rtmp") private String protocolEnableRtmp; @JSONField(name = "protocol.enable_ts") private String protocolEnableTs; @JSONField(name = "protocol.enable_fmp4") private String protocolEnableFmp4; @JSONField(name = "protocol.mp4_as_player") private String protocolMp4AsPlayer; @JSONField(name = "protocol.mp4_max_second") private String protocolMp4MaxSecond; @JSONField(name = "protocol.mp4_save_path") private String protocolMp4SavePath; @JSONField(name = "protocol.hls_save_path") private String protocolHlsSavePath; @JSONField(name = "protocol.hls_demand") private String protocolHlsDemand; @JSONField(name = "protocol.rtsp_demand") private String protocolRtspDemand; @JSONField(name = "protocol.rtmp_demand") private String protocolRtmpDemand; @JSONField(name = "protocol.ts_demand") private String protocolTsDemand; @JSONField(name = "protocol.fmp4_demand") private String protocolFmp4Demand; @JSONField(name = "general.enableVhost") private String generalEnableVhost; @JSONField(name = "general.flowThreshold") private String generalFlowThreshold; @JSONField(name = "general.maxStreamWaitMS") private String generalMaxStreamWaitMS; @JSONField(name = "general.streamNoneReaderDelayMS") private int generalStreamNoneReaderDelayMS; @JSONField(name = "general.resetWhenRePlay") private String generalResetWhenRePlay; @JSONField(name = "general.mergeWriteMS") private String generalMergeWriteMS; @JSONField(name = "general.mediaServerId") private String generalMediaServerId; @JSONField(name = "general.wait_track_ready_ms") private String generalWaitTrackReadyMs; @JSONField(name = "general.wait_add_track_ms") private String generalWaitAddTrackMs; @JSONField(name = "general.unready_frame_cache") private String generalUnreadyFrameCache; @JSONField(name = "ip") private String ip; private String sdpIp; private String streamIp; private String hookIp; private String updateTime; private String createTime; @JSONField(name = "hls.fileBufSize") private String hlsFileBufSize; @JSONField(name = "hls.filePath") private String hlsFilePath; @JSONField(name = "hls.segDur") private String hlsSegDur; @JSONField(name = "hls.segNum") private String hlsSegNum; @JSONField(name = "hls.segRetain") private String hlsSegRetain; @JSONField(name = "hls.broadcastRecordTs") private String hlsBroadcastRecordTs; @JSONField(name = "hls.deleteDelaySec") private String hlsDeleteDelaySec; @JSONField(name = "hls.segKeep") private String hlsSegKeep; @JSONField(name = "hook.access_file_except_hls") private String hookAccessFileExceptHLS; @JSONField(name = "hook.admin_params") private String hookAdminParams; @JSONField(name = "hook.alive_interval") private Float hookAliveInterval; @JSONField(name = "hook.enable") private String hookEnable; @JSONField(name = "hook.on_flow_report") private String hookOnFlowReport; @JSONField(name = "hook.on_http_access") private String hookOnHttpAccess; @JSONField(name = "hook.on_play") private String hookOnPlay; @JSONField(name = "hook.on_publish") private String hookOnPublish; @JSONField(name = "hook.on_record_mp4") private String hookOnRecordMp4; @JSONField(name = "hook.on_rtsp_auth") private String hookOnRtspAuth; @JSONField(name = "hook.on_rtsp_realm") private String hookOnRtspRealm; @JSONField(name = "hook.on_shell_login") private String hookOnShellLogin; @JSONField(name = "hook.on_stream_changed") private String hookOnStreamChanged; @JSONField(name = "hook.on_stream_none_reader") private String hookOnStreamNoneReader; @JSONField(name = "hook.on_stream_not_found") private String hookOnStreamNotFound; @JSONField(name = "hook.on_server_started") private String hookOnServerStarted; @JSONField(name = "hook.on_server_keepalive") private String hookOnServerKeepalive; @JSONField(name = "hook.on_send_rtp_stopped") private String hookOnSendRtpStopped; @JSONField(name = "hook.on_rtp_server_timeout") private String hookOnRtpServerTimeout; @JSONField(name = "hook.timeoutSec") private String hookTimeoutSec; @JSONField(name = "http.charSet") private String httpCharSet; @JSONField(name = "http.keepAliveSecond") private String httpKeepAliveSecond; @JSONField(name = "http.maxReqCount") private String httpMaxReqCount; @JSONField(name = "http.maxReqSize") private String httpMaxReqSize; @JSONField(name = "http.notFound") private String httpNotFound; @JSONField(name = "http.port") private int httpPort; @JSONField(name = "http.rootPath") private String httpRootPath; @JSONField(name = "http.sendBufSize") private String httpSendBufSize; @JSONField(name = "http.sslport") private int httpSSLport; @JSONField(name = "multicast.addrMax") private String multicastAddrMax; @JSONField(name = "multicast.addrMin") private String multicastAddrMin; @JSONField(name = "multicast.udpTTL") private String multicastUdpTTL; @JSONField(name = "record.appName") private String recordAppName; @JSONField(name = "record.filePath") private String recordFilePath; @JSONField(name = "record.fileSecond") private String recordFileSecond; @JSONField(name = "record.sampleMS") private String recordFileSampleMS; @JSONField(name = "rtmp.handshakeSecond") private String rtmpHandshakeSecond; @JSONField(name = "rtmp.keepAliveSecond") private String rtmpKeepAliveSecond; @JSONField(name = "rtmp.modifyStamp") private String rtmpModifyStamp; @JSONField(name = "rtmp.port") private int rtmpPort; @JSONField(name = "rtmp.sslport") private int rtmpSslPort; @JSONField(name = "rtp.audioMtuSize") private String rtpAudioMtuSize; @JSONField(name = "rtp.clearCount") private String rtpClearCount; @JSONField(name = "rtp.cycleMS") private String rtpCycleMS; @JSONField(name = "rtp.maxRtpCount") private String rtpMaxRtpCount; @JSONField(name = "rtp.videoMtuSize") private String rtpVideoMtuSize; @JSONField(name = "rtp_proxy.checkSource") private String rtpProxyCheckSource; @JSONField(name = "rtp_proxy.dumpDir") private String rtpProxyDumpDir; @JSONField(name = "rtp_proxy.port") private int rtpProxyPort; @JSONField(name = "rtp_proxy.port_range") private String portRange; @JSONField(name = "rtp_proxy.timeoutSec") private String rtpProxyTimeoutSec; @JSONField(name = "rtsp.authBasic") private String rtspAuthBasic; @JSONField(name = "rtsp.handshakeSecond") private String rtspHandshakeSecond; @JSONField(name = "rtsp.keepAliveSecond") private String rtspKeepAliveSecond; @JSONField(name = "rtsp.port") private int rtspPort; @JSONField(name = "rtsp.sslport") private int rtspSSlport; @JSONField(name = "shell.maxReqSize") private String shellMaxReqSize; @JSONField(name = "shell.shell") private String shellPhell; @JSONField(name = "transcode.suffix") private String transcodeSuffix; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/HookParam.java ================================================ package com.genersoft.iot.vmp.media.zlm.dto.hook; import lombok.Data; /** * zlm hook事件的参数 * @author lin */ @Data public class HookParam { private String mediaServerId; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/HookResult.java ================================================ package com.genersoft.iot.vmp.media.zlm.dto.hook; public class HookResult { private int code; private String msg; public HookResult() { } public HookResult(int code, String msg) { this.code = code; this.msg = msg; } public static HookResult SUCCESS(){ return new HookResult(0, "success"); } public static HookResultForOnPublish Fail(){ return new HookResultForOnPublish(-1, "fail"); } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/HookResultForOnPublish.java ================================================ package com.genersoft.iot.vmp.media.zlm.dto.hook; import com.genersoft.iot.vmp.media.bean.ResultForOnPublish; import lombok.Getter; import lombok.Setter; @Setter @Getter public class HookResultForOnPublish extends HookResult{ private boolean enable_audio; private boolean enable_mp4; private int mp4_max_second; private String mp4_save_path; private String stream_replace; private Integer modify_stamp; public HookResultForOnPublish() { } public static HookResultForOnPublish SUCCESS(){ return new HookResultForOnPublish(0, "success"); } public static HookResultForOnPublish getInstance(ResultForOnPublish resultForOnPublish){ HookResultForOnPublish successResult = new HookResultForOnPublish(0, "success"); successResult.setEnable_audio(resultForOnPublish.isEnable_audio()); successResult.setEnable_mp4(resultForOnPublish.isEnable_mp4()); successResult.setModify_stamp(resultForOnPublish.getModify_stamp()); successResult.setStream_replace(resultForOnPublish.getStream_replace()); successResult.setMp4_max_second(resultForOnPublish.getMp4_max_second()); successResult.setMp4_save_path(resultForOnPublish.getMp4_save_path()); return successResult; } public HookResultForOnPublish(int code, String msg) { setCode(code); setMsg(msg); } @Override public String toString() { return "HookResultForOnPublish{" + "enable_audio=" + enable_audio + ", enable_mp4=" + enable_mp4 + ", mp4_max_second=" + mp4_max_second + ", mp4_save_path='" + mp4_save_path + '\'' + ", stream_replace='" + stream_replace + '\'' + ", modify_stamp='" + modify_stamp + '\'' + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/OnPlayHookParam.java ================================================ package com.genersoft.iot.vmp.media.zlm.dto.hook; /** * zlm hook事件中的on_play事件的参数 * @author lin */ public class OnPlayHookParam extends HookParam{ private String id; private String app; private String stream; private String ip; private String params; private int port; private String schema; private String vhost; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getApp() { return app; } public void setApp(String app) { this.app = app; } public String getStream() { return stream; } public void setStream(String stream) { this.stream = stream; } public String getIp() { return ip; } public void setIp(String ip) { this.ip = ip; } public String getParams() { return params; } public void setParams(String params) { this.params = params; } public int getPort() { return port; } public void setPort(int port) { this.port = port; } public String getSchema() { return schema; } public void setSchema(String schema) { this.schema = schema; } public String getVhost() { return vhost; } public void setVhost(String vhost) { this.vhost = vhost; } @Override public String toString() { return "OnPlayHookParam{" + "id='" + id + '\'' + ", app='" + app + '\'' + ", stream='" + stream + '\'' + ", ip='" + ip + '\'' + ", params='" + params + '\'' + ", port=" + port + ", schema='" + schema + '\'' + ", vhost='" + vhost + '\'' + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/OnPublishHookParam.java ================================================ package com.genersoft.iot.vmp.media.zlm.dto.hook; import lombok.Getter; import lombok.Setter; /** * zlm hook事件中的on_publish事件的参数 * @author lin */ public class OnPublishHookParam extends HookParam{ @Getter @Setter private String id; @Getter @Setter private String app; @Getter @Setter private String stream; @Getter @Setter private String ip; @Getter @Setter private String params; @Getter @Setter private int port; @Getter @Setter private String schema; @Getter @Setter private String vhost; @Override public String toString() { return "OnPublishHookParam{" + "id='" + id + '\'' + ", app='" + app + '\'' + ", stream='" + stream + '\'' + ", ip='" + ip + '\'' + ", params='" + params + '\'' + ", port=" + port + ", schema='" + schema + '\'' + ", vhost='" + vhost + '\'' + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/OnRecordMp4HookParam.java ================================================ package com.genersoft.iot.vmp.media.zlm.dto.hook; /** * zlm hook事件中的on_rtp_server_timeout事件的参数 * @author lin */ public class OnRecordMp4HookParam extends HookParam{ private String app; private String stream; private String file_name; private String file_path; private long file_size; private String folder; private String url; private String vhost; private long start_time; private double time_len; private String params; public String getApp() { return app; } public void setApp(String app) { this.app = app; } public String getStream() { return stream; } public void setStream(String stream) { this.stream = stream; } public String getFile_name() { return file_name; } public void setFile_name(String file_name) { this.file_name = file_name; } public String getFile_path() { return file_path; } public void setFile_path(String file_path) { this.file_path = file_path; } public long getFile_size() { return file_size; } public void setFile_size(long file_size) { this.file_size = file_size; } public String getFolder() { return folder; } public void setFolder(String folder) { this.folder = folder; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getVhost() { return vhost; } public void setVhost(String vhost) { this.vhost = vhost; } public long getStart_time() { return start_time; } public void setStart_time(long start_time) { this.start_time = start_time; } public double getTime_len() { return time_len; } public void setTime_len(double time_len) { this.time_len = time_len; } public String getParams() { return params; } public void setParams(String params) { this.params = params; } @Override public String toString() { return "OnRecordMp4HookParam{" + "app='" + app + '\'' + ", stream='" + stream + '\'' + ", file_name='" + file_name + '\'' + ", file_path='" + file_path + '\'' + ", file_size='" + file_size + '\'' + ", folder='" + folder + '\'' + ", url='" + url + '\'' + ", vhost='" + vhost + '\'' + ", start_time=" + start_time + ", time_len=" + time_len + ", params=" + params + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/OnRtpServerTimeoutHookParam.java ================================================ package com.genersoft.iot.vmp.media.zlm.dto.hook; /** * zlm hook事件中的on_rtp_server_timeout事件的参数 * @author lin */ public class OnRtpServerTimeoutHookParam extends HookParam{ private int local_port; private String stream_id; private int tcpMode; private boolean re_use_port; private String ssrc; public int getLocal_port() { return local_port; } public void setLocal_port(int local_port) { this.local_port = local_port; } public String getStream_id() { return stream_id; } public void setStream_id(String stream_id) { this.stream_id = stream_id; } public int getTcpMode() { return tcpMode; } public void setTcpMode(int tcpMode) { this.tcpMode = tcpMode; } public boolean isRe_use_port() { return re_use_port; } public void setRe_use_port(boolean re_use_port) { this.re_use_port = re_use_port; } public String getSsrc() { return ssrc; } public void setSsrc(String ssrc) { this.ssrc = ssrc; } @Override public String toString() { return "OnRtpServerTimeoutHookParam{" + "local_port=" + local_port + ", stream_id='" + stream_id + '\'' + ", tcpMode=" + tcpMode + ", re_use_port=" + re_use_port + ", ssrc='" + ssrc + '\'' + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/OnSendRtpStoppedHookParam.java ================================================ package com.genersoft.iot.vmp.media.zlm.dto.hook; /** * zlm hook事件中的on_send_rtp_stopped事件的参数 * @author lin */ public class OnSendRtpStoppedHookParam extends HookParam{ private String app; private String stream; public String getApp() { return app; } public void setApp(String app) { this.app = app; } public String getStream() { return stream; } public void setStream(String stream) { this.stream = stream; } @Override public String toString() { return "OnSendRtpStoppedHookParam{" + "app='" + app + '\'' + ", stream='" + stream + '\'' + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/OnServerKeepaliveHookParam.java ================================================ package com.genersoft.iot.vmp.media.zlm.dto.hook; import com.genersoft.iot.vmp.media.zlm.dto.ServerKeepaliveData; /** * zlm hook事件中的on_play事件的参数 * @author lin */ public class OnServerKeepaliveHookParam extends HookParam{ private ServerKeepaliveData data; public ServerKeepaliveData getData() { return data; } public void setData(ServerKeepaliveData data) { this.data = data; } @Override public String toString() { return "OnServerKeepaliveHookParam{" + "data=" + data + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/OnStreamChangedHookParam.java ================================================ package com.genersoft.iot.vmp.media.zlm.dto.hook; import com.genersoft.iot.vmp.vmanager.bean.StreamContent; import lombok.Data; import lombok.EqualsAndHashCode; import java.util.List; import java.util.Map; /** * @author lin */ @EqualsAndHashCode(callSuper = true) @Data public class OnStreamChangedHookParam extends HookParam{ /** * 注册/注销 */ private boolean regist; /** * 应用名 */ private String app; /** * 流id */ private String stream; /** * 推流鉴权Id */ private String callId; /** * 观看总人数,包括hls/rtsp/rtmp/http-flv/ws-flv */ private int totalReaderCount; /** * 协议 包括hls/rtsp/rtmp/http-flv/ws-flv */ private String schema; /** * 产生源类型, * unknown = 0, * rtmp_push=1, * rtsp_push=2, * rtp_push=3, * pull=4, * ffmpeg_pull=5, * mp4_vod=6, * device_chn=7 */ private int originType; /** * 客户端和服务器网络信息,可能为null类型 */ private OriginSock originSock; /** * 产生源类型的字符串描述 */ private String originTypeStr; /** * 产生源的url */ private String originUrl; /** * 服务器id */ private String severId; /** * GMT unix系统时间戳,单位秒 */ private Long createStamp; /** * 存活时间,单位秒 */ private Long aliveSecond; /** * 数据产生速度,单位byte/s */ private Long bytesSpeed; /** * 音视频轨道 */ private List tracks; /** * 音视频轨道 */ private String vhost; /** * 额外的参数字符串 */ private String params; /** * 额外的参数 */ private Map paramMap; /** * 是否是docker部署, docker部署不会自动更新zlm使用的端口,需要自己手动修改 */ private boolean docker; @Data public static class MediaTrack { /** * 音频通道数 */ private int channels; /** * H264 = 0, H265 = 1, AAC = 2, G711A = 3, G711U = 4 */ private int codec_id; /** * 编码类型名称 CodecAAC CodecH264 */ private String codec_id_name; /** * Video = 0, Audio = 1 */ private int codec_type; /** * 轨道是否准备就绪 */ private boolean ready; /** * 音频采样位数 */ private int sample_bit; /** * 音频采样率 */ private int sample_rate; /** * 视频fps */ private float fps; /** * 视频高 */ private int height; /** * 视频宽 */ private int width; /** * 帧数 */ private int frames; /** * 关键帧数 */ private int key_frames; /** * GOP大小 */ private int gop_size; /** * GOP间隔时长(ms) */ private int gop_interval_ms; /** * 丢帧率 */ private float loss; } @Data public static class OriginSock{ private String identifier; private String local_ip; private int local_port; private String peer_ip; private int peer_port; } private StreamContent streamInfo; @Override public String toString() { return "OnStreamChangedHookParam{" + "regist=" + regist + ", app='" + app + '\'' + ", stream='" + stream + '\'' + ", severId='" + severId + '\'' + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/OnStreamNoneReaderHookParam.java ================================================ package com.genersoft.iot.vmp.media.zlm.dto.hook; public class OnStreamNoneReaderHookParam extends HookParam{ private String schema; private String app; private String stream; private String vhost; public String getSchema() { return schema; } public void setSchema(String schema) { this.schema = schema; } public String getApp() { return app; } public void setApp(String app) { this.app = app; } public String getStream() { return stream; } public void setStream(String stream) { this.stream = stream; } public String getVhost() { return vhost; } public void setVhost(String vhost) { this.vhost = vhost; } @Override public String toString() { return "OnStreamNoneReaderHookParam{" + "schema='" + schema + '\'' + ", app='" + app + '\'' + ", stream='" + stream + '\'' + ", vhost='" + vhost + '\'' + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/OnStreamNotFoundHookParam.java ================================================ package com.genersoft.iot.vmp.media.zlm.dto.hook; /** * zlm hook事件中的on_stream_not_found事件的参数 * @author lin */ public class OnStreamNotFoundHookParam extends HookParam{ private String id; private String app; private String stream; private String ip; private String params; private int port; private String schema; private String vhost; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getApp() { return app; } public void setApp(String app) { this.app = app; } public String getStream() { return stream; } public void setStream(String stream) { this.stream = stream; } public String getIp() { return ip; } public void setIp(String ip) { this.ip = ip; } public String getParams() { return params; } public void setParams(String params) { this.params = params; } public int getPort() { return port; } public void setPort(int port) { this.port = port; } public String getSchema() { return schema; } public void setSchema(String schema) { this.schema = schema; } public String getVhost() { return vhost; } public void setVhost(String vhost) { this.vhost = vhost; } @Override public String toString() { return "OnStreamNotFoundHookParam{" + "id='" + id + '\'' + ", app='" + app + '\'' + ", stream='" + stream + '\'' + ", ip='" + ip + '\'' + ", params='" + params + '\'' + ", port=" + port + ", schema='" + schema + '\'' + ", vhost='" + vhost + '\'' + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/OriginType.java ================================================ package com.genersoft.iot.vmp.media.zlm.dto.hook; public enum OriginType { // 不可调整顺序 UNKNOWN("UNKNOWN"), RTMP_PUSH("PUSH"), RTSP_PUSH("PUSH"), RTP_PUSH("RTP"), PULL("PULL"), FFMPEG_PULL("PULL"), MP4_VOD("MP4_VOD"), DEVICE_CHN("DEVICE_CHN"), RTC_PUSH("PUSH"); private final String type; OriginType(String type) { this.type = type; } public String getType() { return type; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/zlm/event/HookZlmServerKeepaliveEvent.java ================================================ package com.genersoft.iot.vmp.media.zlm.event; import com.genersoft.iot.vmp.media.bean.MediaServer; import org.springframework.context.ApplicationEvent; /** * zlm 心跳事件 */ public class HookZlmServerKeepaliveEvent extends ApplicationEvent { public HookZlmServerKeepaliveEvent(Object source) { super(source); } private MediaServer mediaServerItem; public MediaServer getMediaServerItem() { return mediaServerItem; } public void setMediaServerItem(MediaServer mediaServerItem) { this.mediaServerItem = mediaServerItem; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/media/zlm/event/HookZlmServerStartEvent.java ================================================ package com.genersoft.iot.vmp.media.zlm.event; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.zlm.dto.ZLMServerConfig; import lombok.Getter; import lombok.Setter; import org.springframework.context.ApplicationEvent; /** * zlm server_start事件 */ @Setter @Getter public class HookZlmServerStartEvent extends ApplicationEvent { public HookZlmServerStartEvent(Object source) { super(source); } private MediaServer mediaServer; private ZLMServerConfig config; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/ICloudRecordService.java ================================================ package com.genersoft.iot.vmp.service; import com.alibaba.fastjson2.JSONArray; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.service.bean.CloudRecordItem; import com.genersoft.iot.vmp.service.bean.DownloadFileInfo; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.vmanager.cloudRecord.bean.CloudRecordUrl; import com.github.pagehelper.PageInfo; import java.util.List; import java.util.Set; /** * 云端录像管理 * @author lin */ public interface ICloudRecordService { /** * 分页回去云端录像列表 */ PageInfo getList(int page, int count, String query, String app, String stream, String startTime, String endTime, List mediaServerItems, String callId, Boolean ascOrder); /** * 获取所有的日期 */ List getDateList(String app, String stream, int year, int month, List mediaServerItems); /** * 添加合并任务 */ String addTask(String app, String stream, MediaServer mediaServerItem, String startTime, String endTime, String callId, String remoteHost, boolean filterMediaServer); /** * 查询合并任务列表 */ JSONArray queryTask(String app, String stream, String callId, String taskId, String mediaServerId, Boolean isEnd, String scheme); /** * 收藏视频,收藏的视频过期不会删除 */ int changeCollect(boolean result, String app, String stream, String mediaServerId, String startTime, String endTime, String callId); /** * 添加指定录像收藏 */ int changeCollectById(Integer recordId, boolean result); /** * 获取播放地址 */ DownloadFileInfo getPlayUrlPath(Integer recordId); List getAllList(String query, String app, String stream, String startTime, String endTime, List mediaServerItems, String callId, List ids); /** * 加载录像文件,形成录像流 */ void loadMP4FileForDate(String app, String stream, String date, ErrorCallback callback); void seekRecord(String mediaServerId,String app, String stream, Double seek, String schema); void setRecordSpeed(String mediaServerId, String app, String stream, Integer speed, String schema); void deleteFileByIds(Set ids); void loadMP4File(String app, String stream, int cloudRecordId, ErrorCallback callback); List getUrlListByIds(List ids); List getUrlList(String app, String stream, String callId); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/ILogService.java ================================================ package com.genersoft.iot.vmp.service; import com.genersoft.iot.vmp.service.bean.LogFileInfo; import java.io.File; import java.util.List; public interface ILogService { List queryList(String query, String startTime, String endTime); File getFileByName(String fileName); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/IMapService.java ================================================ package com.genersoft.iot.vmp.service; import com.genersoft.iot.vmp.vmanager.bean.MapConfig; import com.genersoft.iot.vmp.vmanager.bean.MapModelIcon; import java.util.List; public interface IMapService { List getConfig(); List getModelList(); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/IMediaService.java ================================================ package com.genersoft.iot.vmp.service; import com.genersoft.iot.vmp.media.bean.ResultForOnPublish; import com.genersoft.iot.vmp.media.bean.MediaServer; /** * 媒体信息业务 */ public interface IMediaService { /** * 播放鉴权 */ boolean authenticatePlay(String app, String stream, String callId); ResultForOnPublish authenticatePublish(MediaServer mediaServer, String app, String stream, String params); boolean closeStreamOnNoneReader(String mediaServerId, String app, String stream, String schema); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/IMobilePositionService.java ================================================ package com.genersoft.iot.vmp.service; import com.genersoft.iot.vmp.gb28181.bean.MobilePosition; import com.genersoft.iot.vmp.gb28181.bean.Platform; import java.util.List; public interface IMobilePositionService { void add(List mobilePositionList); void add(MobilePosition mobilePosition); List queryMobilePositions(String deviceId, String channelId, String startTime, String endTime); List queryEnablePlatformListWithAsMessageChannel(); MobilePosition queryLatestPosition(String deviceId); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/IReceiveRtpServerService.java ================================================ package com.genersoft.iot.vmp.service; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.gb28181.bean.OpenRTPServerResult; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.event.hook.HookData; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.service.bean.RTPServerParam; import com.genersoft.iot.vmp.service.bean.SSRCInfo; public interface IReceiveRtpServerService { SSRCInfo openGbRTPServer(MediaServer mediaServer, String streamId, String presetSSRC, int tcpMode, boolean playback, boolean ssrcCheck, boolean onlyAuto, boolean disableAuto, ErrorCallback callback); int openRTPServer(RTPServerParam rtpServerParam, ErrorCallback callback); void closeRTPServer(MediaServer mediaServer, String app, String stream); void closeRTPServerByMediaServerId(String mediaServerId, String app, String stream); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/IRecordPlanService.java ================================================ package com.genersoft.iot.vmp.service; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import com.genersoft.iot.vmp.service.bean.RecordPlan; import com.github.pagehelper.PageInfo; import java.util.List; public interface IRecordPlanService { RecordPlan get(Integer planId); void update(RecordPlan plan); void delete(Integer planId); PageInfo query(Integer page, Integer count, String query); void add(RecordPlan plan); void link(List channelIds, Integer planId); PageInfo queryChannelList(int page, int count, String query, Integer channelType, Boolean online, Integer planId, Boolean hasLink); void linkAll(Integer planId); void cleanAll(Integer planId); Integer recording(String app, String stream); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/IRoleService.java ================================================ package com.genersoft.iot.vmp.service; import com.genersoft.iot.vmp.storager.dao.dto.Role; import java.util.List; public interface IRoleService { Role getRoleById(int id); int add(Role role); int delete(int id); List getAll(); int update(Role role); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/ISendRtpServerService.java ================================================ package com.genersoft.iot.vmp.service; import com.genersoft.iot.vmp.gb28181.bean.SendRtpInfo; import com.genersoft.iot.vmp.media.bean.MediaServer; import java.util.List; public interface ISendRtpServerService { SendRtpInfo createSendRtpInfo(MediaServer mediaServer, String ip, Integer port, String ssrc, String requesterId, String deviceId, Integer channelId, Boolean isTcp, Boolean rtcp); SendRtpInfo createSendRtpInfo(MediaServer mediaServer, String ip, Integer port, String ssrc, String platformId, String app, String stream, Integer channelId, Boolean tcp, Boolean rtcp); void update(SendRtpInfo sendRtpItem); SendRtpInfo queryByChannelId(Integer channelId, String targetId); SendRtpInfo queryByCallId(String callId); List queryByStream(String stream); SendRtpInfo queryByStream(String stream, String targetId); void delete(SendRtpInfo sendRtpInfo); void deleteByCallId(String callId); void deleteByStream(String Stream, String targetId); void deleteByChannel(Integer channelId, String targetId); List queryAll(); boolean isChannelSendingRTP(Integer channelId); List queryForPlatform(String platformId); List queryByChannelId(int id); void deleteByStream(String stream); int getNextPort(MediaServer mediaServer); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/IUserApiKeyService.java ================================================ package com.genersoft.iot.vmp.service; import com.genersoft.iot.vmp.storager.dao.dto.UserApiKey; import com.github.pagehelper.PageInfo; public interface IUserApiKeyService { int addApiKey(UserApiKey userApiKey); boolean isApiKeyExists(String apiKey); PageInfo getUserApiKeys(int page, int count); int enable(Integer id); int disable(Integer id); int remark(Integer id, String remark); int delete(Integer id); UserApiKey getUserApiKeyById(Integer id); int reset(Integer id, String apiKey); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/IUserService.java ================================================ package com.genersoft.iot.vmp.service; import com.genersoft.iot.vmp.storager.dao.dto.User; import com.github.pagehelper.PageInfo; import java.util.List; public interface IUserService { User getUser(String username, String password); boolean changePassword(int id, String password); User getUserById(int id); User getUserByUsername(String username); int addUser(User user); int deleteUser(int id); List getAllUsers(); int updateUsers(User user); boolean checkPushAuthority(String callId, String sign); PageInfo getUsers(int page, int count); int changePushKey(int id, String pushKey); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/bean/CloudRecordItem.java ================================================ package com.genersoft.iot.vmp.service.bean; import com.genersoft.iot.vmp.media.event.media.MediaRecordMp4Event; import com.genersoft.iot.vmp.media.event.media.MediaRecordProcessEvent; import com.genersoft.iot.vmp.utils.MediaServerUtils; import lombok.Data; import java.util.Map; /** * 云端录像数据 */ @Data public class CloudRecordItem { /** * 主键 */ private int id; /** * 应用名 */ private String app; /** * 流 */ private String stream; /** * 健全ID */ private String callId; /** * 开始时间 */ private long startTime; /** * 结束时间 */ private long endTime; /** * ZLM Id */ private String mediaServerId; /** * 文件名称 */ private String fileName; /** * 文件路径 */ private String filePath; /** * 文件夹 */ private String folder; /** * 收藏,收藏的文件不移除 */ private Boolean collect; /** * 保留,收藏的文件不移除 */ private Boolean reserve; /** * 文件大小 */ private long fileSize; /** * 文件时长 */ private double timeLen; /** * 所属服务ID */ private String serverId; public static CloudRecordItem getInstance(MediaRecordMp4Event param) { CloudRecordItem cloudRecordItem = new CloudRecordItem(); cloudRecordItem.setApp(param.getApp()); cloudRecordItem.setStream(param.getStream()); cloudRecordItem.setStartTime(param.getRecordInfo().getStartTime()); cloudRecordItem.setFileName(param.getRecordInfo().getFileName()); cloudRecordItem.setFolder(param.getRecordInfo().getFolder()); cloudRecordItem.setFileSize(param.getRecordInfo().getFileSize()); cloudRecordItem.setFilePath(param.getRecordInfo().getFilePath()); cloudRecordItem.setMediaServerId(param.getMediaServer().getId()); cloudRecordItem.setTimeLen(param.getRecordInfo().getTimeLen()); cloudRecordItem.setEndTime((param.getRecordInfo().getStartTime() + (long)param.getRecordInfo().getTimeLen())); Map paramsMap = MediaServerUtils.urlParamToMap(param.getRecordInfo().getParams()); if (paramsMap.get("callId") != null) { cloudRecordItem.setCallId(paramsMap.get("callId")); } return cloudRecordItem; } public static CloudRecordItem getInstance(MediaRecordProcessEvent event) { CloudRecordItem cloudRecordItem = new CloudRecordItem(); cloudRecordItem.setApp(event.getApp()); cloudRecordItem.setStream(event.getStream()); cloudRecordItem.setFileName(event.getFileName()); cloudRecordItem.setMediaServerId(event.getMediaServer().getId()); cloudRecordItem.setTimeLen(event.getCurrentFileDuration() * 1000); return cloudRecordItem; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/bean/DownloadFileInfo.java ================================================ package com.genersoft.iot.vmp.service.bean; import lombok.Data; @Data public class DownloadFileInfo { private String httpPath; private String httpsPath; private String httpDomainPath; private String httpsDomainPath; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/bean/ErrorCallback.java ================================================ package com.genersoft.iot.vmp.service.bean; public interface ErrorCallback { void run(int code, String msg, T data); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/bean/GPSMsgInfo.java ================================================ package com.genersoft.iot.vmp.service.bean; import com.genersoft.iot.vmp.gb28181.bean.MobilePosition; import lombok.Data; @Data public class GPSMsgInfo { /** * 通道国标ID */ private String id; /** * 通道ID */ private Integer channelId; /** * */ private String app; /** * 经度 (必选) */ private double lng; /** * 纬度 (必选) */ private double lat; /** * 速度,单位:km/h (可选) */ private Double speed; /** * 产生通知时间, 时间格式: 2020-01-14T14:32:12 */ private String time; /** * 方向,取值为当前摄像头方向与正北方的顺时针夹角,取值范围0°~360°,单位:(°)(可选) */ private Double direction; /** * 海拔高度,单位:m(可选) */ private Double altitude; private boolean stored; public static GPSMsgInfo getInstance(MobilePosition mobilePosition) { GPSMsgInfo gpsMsgInfo = new GPSMsgInfo(); gpsMsgInfo.setChannelId(mobilePosition.getChannelId()); gpsMsgInfo.setAltitude(mobilePosition.getAltitude()); gpsMsgInfo.setLng(mobilePosition.getLongitude()); gpsMsgInfo.setLat(mobilePosition.getLatitude()); gpsMsgInfo.setSpeed(mobilePosition.getSpeed()); gpsMsgInfo.setDirection(mobilePosition.getDirection()); gpsMsgInfo.setTime(mobilePosition.getTime()); return gpsMsgInfo; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/bean/InviteErrorCode.java ================================================ package com.genersoft.iot.vmp.service.bean; /** * 全局错误码 */ public enum InviteErrorCode { SUCCESS(0, "成功"), FAIL(-100, "失败"), ERROR_FOR_SIGNALLING_TIMEOUT(-1, "信令超时"), ERROR_FOR_STREAM_TIMEOUT(-2, "收流超时"), ERROR_FOR_RESOURCE_EXHAUSTION(-3, "资源耗尽"), ERROR_FOR_CATCH_DATA(-4, "缓存数据异常"), ERROR_FOR_SIGNALLING_ERROR(-5, "收到信令错误"), ERROR_FOR_STREAM_PARSING_EXCEPTIONS(-6, "流地址解析错误"), ERROR_FOR_SDP_PARSING_EXCEPTIONS(-7, "SDP信息解析失败"), ERROR_FOR_SSRC_UNAVAILABLE(-8, "SSRC不可用"), ERROR_FOR_RESET_SSRC(-9, "重新设置收流信息失败"), ERROR_FOR_SIP_SENDING_FAILED(-10, "命令发送失败"), ERROR_FOR_ASSIST_NOT_READY(-11, "没有可用的assist服务"), ERROR_FOR_PARAMETER_ERROR(-13, "参数异常"), ERROR_FOR_TCP_ACTIVE_CONNECTION_REFUSED_ERROR(-14, "TCP主动连接失败"), ERROR_FOR_FINISH(-20, "已结束"), ; private final int code; private final String msg; InviteErrorCode(int code, String msg) { this.code = code; this.msg = msg; } public int getCode() { return code; } public String getMsg() { return msg; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/bean/InviteTimeOutCallback.java ================================================ package com.genersoft.iot.vmp.service.bean; public interface InviteTimeOutCallback { void run(int code, String msg); // code: 0 sip超时, 1 收流超时 } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/bean/LogFileInfo.java ================================================ package com.genersoft.iot.vmp.service.bean; import lombok.Data; @Data public class LogFileInfo { private String fileName; private Long fileSize; private Long startTime; private Long endTime; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/bean/MediaServerLoad.java ================================================ package com.genersoft.iot.vmp.service.bean; public class MediaServerLoad { private String id; private int push; private int proxy; private int gbReceive; private int gbSend; public String getId() { return id; } public void setId(String id) { this.id = id; } public int getPush() { return push; } public void setPush(int push) { this.push = push; } public int getProxy() { return proxy; } public void setProxy(int proxy) { this.proxy = proxy; } public int getGbReceive() { return gbReceive; } public void setGbReceive(int gbReceive) { this.gbReceive = gbReceive; } public int getGbSend() { return gbSend; } public void setGbSend(int gbSend) { this.gbSend = gbSend; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/bean/MessageForPushChannel.java ================================================ package com.genersoft.iot.vmp.service.bean; import lombok.Data; /** * 当上级平台 * @author lin */ @Data public class MessageForPushChannel { /** * 消息类型 * 0 流注销 1 流注册 */ private int type; /** * 流应用名 */ private String app; /** * 流Id */ private String stream; /** * 国标ID */ private String gbId; /** * 请求的平台国标编号 */ private String platFormId; /** * 请求的平台自增ID */ private int platFormIndex; /** * 请求平台名称 */ private String platFormName; /** * WVP服务ID */ private String serverId; /** * 目标流媒体节点ID */ private String mediaServerId; public static MessageForPushChannel getInstance(int type, String app, String stream, String gbId, String platFormId, String platFormName, String serverId, String mediaServerId){ MessageForPushChannel messageForPushChannel = new MessageForPushChannel(); messageForPushChannel.setType(type); messageForPushChannel.setGbId(gbId); messageForPushChannel.setApp(app); messageForPushChannel.setStream(stream); messageForPushChannel.setServerId(serverId); messageForPushChannel.setMediaServerId(mediaServerId); messageForPushChannel.setPlatFormId(platFormId); messageForPushChannel.setPlatFormName(platFormName); return messageForPushChannel; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/bean/MessageForPushChannelResponse.java ================================================ package com.genersoft.iot.vmp.service.bean; /** * 当redis回复推流结果上级平台 * @author lin */ public class MessageForPushChannelResponse { /** * 错误玛 * 0 成功 1 失败 */ private int code; /** * 错误内容 */ private String msg; /** * 流应用名 */ private String app; /** * 流Id */ private String stream; public static MessageForPushChannelResponse getInstance(int code, String msg, String app, String stream){ MessageForPushChannelResponse messageForPushChannel = new MessageForPushChannelResponse(); messageForPushChannel.setCode(code); messageForPushChannel.setMsg(msg); messageForPushChannel.setApp(app); messageForPushChannel.setStream(stream); return messageForPushChannel; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getApp() { return app; } public void setApp(String app) { this.app = app; } public String getStream() { return stream; } public void setStream(String stream) { this.stream = stream; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/bean/PlayBackCallback.java ================================================ package com.genersoft.iot.vmp.service.bean; public interface PlayBackCallback { void call(PlayBackResult msg); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/bean/PlayBackResult.java ================================================ package com.genersoft.iot.vmp.service.bean; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; import com.genersoft.iot.vmp.media.bean.MediaServer; import java.util.EventObject; /** * @author lin */ public class PlayBackResult { private int code; private String msg; private T data; private MediaServer mediaServerItem; private JSONObject response; private SipSubscribe.EventResult event; public int getCode() { return code; } public void setCode(int code) { this.code = code; } public T getData() { return data; } public void setData(T data) { this.data = data; } public MediaServer getMediaServerItem() { return mediaServerItem; } public void setMediaServerItem(MediaServer mediaServerItem) { this.mediaServerItem = mediaServerItem; } public JSONObject getResponse() { return response; } public void setResponse(JSONObject response) { this.response = response; } public SipSubscribe.EventResult getEvent() { return event; } public void setEvent(SipSubscribe.EventResult event) { this.event = event; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/bean/PushStreamStatusChangeFromRedisDto.java ================================================ package com.genersoft.iot.vmp.service.bean; import lombok.Data; import java.util.List; /** * 收到redis通知修改推流通道状态 * @author lin */ @Data public class PushStreamStatusChangeFromRedisDto { private boolean setAllOffline; private List onlineStreams; private List offlineStreams; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/bean/RTPServerParam.java ================================================ package com.genersoft.iot.vmp.service.bean; import com.genersoft.iot.vmp.media.bean.MediaServer; import lombok.Data; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Getter @Setter @NoArgsConstructor public class RTPServerParam { /** * 使用的流媒体 */ private MediaServer mediaServer; private String app; private String streamId; /** * 开启rtpServer时使用的ssrc,开启rtpServer时会根据这个ssrc进行校验,如果不填则不校验 */ private Long ssrc; private Integer port; private boolean onlyAuto; private boolean disableAudio; private boolean reUsePort; /** * tcp模式,0时为不启用tcp监听,1时为启用tcp监听,2时为tcp主动连接模式 */ private Integer tcpMode; public RTPServerParam(MediaServer mediaServer, String app, String streamId, Long ssrc, Integer port, boolean onlyAuto, boolean disableAudio, boolean reUsePort, Integer tcpMode) { this.mediaServer = mediaServer; this.app = app; this.streamId = streamId; this.ssrc = ssrc; this.port = port; this.onlyAuto = onlyAuto; this.disableAudio = disableAudio; this.reUsePort = reUsePort; this.tcpMode = tcpMode; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/bean/RecordPlan.java ================================================ package com.genersoft.iot.vmp.service.bean; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.util.List; @Data @Schema(description = "录制计划") public class RecordPlan { @Schema(description = "计划数据库ID") private int id; @Schema(description = "计划名称") private String name; @Schema(description = "计划关联通道数量") private int channelCount; @Schema(description = "是否开启定时截图") private Boolean snap; @Schema(description = "创建时间") private String createTime; @Schema(description = "更新时间") private String updateTime; @Schema(description = "计划内容") private List planItemList; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/bean/RecordPlanItem.java ================================================ package com.genersoft.iot.vmp.service.bean; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @Data @Schema(description = "录制计划项") public class RecordPlanItem { @Schema(description = "计划项数据库ID") private int id; @Schema(description = "计划开始时间的序号, 从0点开始,每半个小时增加1") private Integer start; @Schema(description = "计划结束时间的序号, 从0点开始,每半个小时增加1") private Integer stop; @Schema(description = "计划周几执行") private Integer weekDay; @Schema(description = "所属计划ID") private Integer planId; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/bean/RequestPushStreamMsg.java ================================================ package com.genersoft.iot.vmp.service.bean; import com.genersoft.iot.vmp.gb28181.bean.SendRtpInfo; /** * redis消息:请求下级推送流信息 * @author lin */ public class RequestPushStreamMsg { /** * 下级服务ID */ private String mediaServerId; /** * 流ID */ private String app; /** * 应用名 */ private String stream; /** * 目标IP */ private String ip; /** * 目标端口 */ private int port; /** * ssrc */ private String ssrc; /** * 是否使用TCP方式 */ private boolean tcp; /** * 本地使用的端口 */ private int srcPort; /** * 发送时,rtp的pt(uint8_t),不传时默认为96 */ private int pt; /** * 发送时,rtp的负载类型。为true时,负载为ps;为false时,为es; */ private boolean ps; /** * 是否只有音频 */ private boolean onlyAudio; public static RequestPushStreamMsg getInstance(String mediaServerId, String app, String stream, String ip, int port, String ssrc, boolean tcp, int srcPort, int pt, boolean ps, boolean onlyAudio) { RequestPushStreamMsg requestPushStreamMsg = new RequestPushStreamMsg(); requestPushStreamMsg.setMediaServerId(mediaServerId); requestPushStreamMsg.setApp(app); requestPushStreamMsg.setStream(stream); requestPushStreamMsg.setIp(ip); requestPushStreamMsg.setPort(port); requestPushStreamMsg.setSsrc(ssrc); requestPushStreamMsg.setTcp(tcp); requestPushStreamMsg.setSrcPort(srcPort); requestPushStreamMsg.setPt(pt); requestPushStreamMsg.setPs(ps); requestPushStreamMsg.setOnlyAudio(onlyAudio); return requestPushStreamMsg; } public static RequestPushStreamMsg getInstance(SendRtpInfo sendRtpItem) { RequestPushStreamMsg requestPushStreamMsg = new RequestPushStreamMsg(); requestPushStreamMsg.setMediaServerId(sendRtpItem.getMediaServerId()); requestPushStreamMsg.setApp(sendRtpItem.getApp()); requestPushStreamMsg.setStream(sendRtpItem.getStream()); requestPushStreamMsg.setIp(sendRtpItem.getIp()); requestPushStreamMsg.setPort(sendRtpItem.getPort()); requestPushStreamMsg.setSsrc(sendRtpItem.getSsrc()); requestPushStreamMsg.setTcp(sendRtpItem.isTcp()); requestPushStreamMsg.setSrcPort(sendRtpItem.getLocalPort()); requestPushStreamMsg.setPt(sendRtpItem.getPt()); requestPushStreamMsg.setPs(sendRtpItem.isUsePs()); requestPushStreamMsg.setOnlyAudio(sendRtpItem.isOnlyAudio()); return requestPushStreamMsg; } public String getMediaServerId() { return mediaServerId; } public void setMediaServerId(String mediaServerId) { this.mediaServerId = mediaServerId; } public String getApp() { return app; } public void setApp(String app) { this.app = app; } public String getStream() { return stream; } public void setStream(String stream) { this.stream = stream; } public String getIp() { return ip; } public void setIp(String ip) { this.ip = ip; } public int getPort() { return port; } public void setPort(int port) { this.port = port; } public String getSsrc() { return ssrc; } public void setSsrc(String ssrc) { this.ssrc = ssrc; } public boolean isTcp() { return tcp; } public void setTcp(boolean tcp) { this.tcp = tcp; } public int getSrcPort() { return srcPort; } public void setSrcPort(int srcPort) { this.srcPort = srcPort; } public int getPt() { return pt; } public void setPt(int pt) { this.pt = pt; } public boolean isPs() { return ps; } public void setPs(boolean ps) { this.ps = ps; } public boolean isOnlyAudio() { return onlyAudio; } public void setOnlyAudio(boolean onlyAudio) { this.onlyAudio = onlyAudio; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/bean/RequestSendItemMsg.java ================================================ package com.genersoft.iot.vmp.service.bean; /** * redis消息:请求下级回复推送信息 * @author lin */ public class RequestSendItemMsg { /** * 下级服务ID */ private String serverId; /** * 下级服务ID */ private String mediaServerId; /** * 流ID */ private String app; /** * 应用名 */ private String stream; /** * 目标IP */ private String ip; /** * 目标端口 */ private int port; /** * ssrc */ private String ssrc; /** * 平台国标编号 */ private String platformId; /** * 平台名称 */ private String platformName; /** * 通道ID */ private String channelId; /** * 是否使用TCP */ private Boolean isTcp; /** * 是否使用TCP */ private Boolean rtcp; public static RequestSendItemMsg getInstance(String serverId, String mediaServerId, String app, String stream, String ip, int port, String ssrc, String platformId, String channelId, Boolean isTcp, Boolean rtcp, String platformName) { RequestSendItemMsg requestSendItemMsg = new RequestSendItemMsg(); requestSendItemMsg.setServerId(serverId); requestSendItemMsg.setMediaServerId(mediaServerId); requestSendItemMsg.setApp(app); requestSendItemMsg.setStream(stream); requestSendItemMsg.setIp(ip); requestSendItemMsg.setPort(port); requestSendItemMsg.setSsrc(ssrc); requestSendItemMsg.setPlatformId(platformId); requestSendItemMsg.setPlatformName(platformName); requestSendItemMsg.setChannelId(channelId); requestSendItemMsg.setTcp(isTcp); requestSendItemMsg.setRtcp(rtcp); return requestSendItemMsg; } public String getServerId() { return serverId; } public void setServerId(String serverId) { this.serverId = serverId; } public String getMediaServerId() { return mediaServerId; } public void setMediaServerId(String mediaServerId) { this.mediaServerId = mediaServerId; } public String getApp() { return app; } public void setApp(String app) { this.app = app; } public String getStream() { return stream; } public void setStream(String stream) { this.stream = stream; } public String getIp() { return ip; } public void setIp(String ip) { this.ip = ip; } public int getPort() { return port; } public void setPort(int port) { this.port = port; } public String getSsrc() { return ssrc; } public void setSsrc(String ssrc) { this.ssrc = ssrc; } public String getPlatformId() { return platformId; } public void setPlatformId(String platformId) { this.platformId = platformId; } public String getPlatformName() { return platformName; } public void setPlatformName(String platformName) { this.platformName = platformName; } public String getChannelId() { return channelId; } public void setChannelId(String channelId) { this.channelId = channelId; } public Boolean getTcp() { return isTcp; } public void setTcp(Boolean tcp) { isTcp = tcp; } public Boolean getRtcp() { return rtcp; } public void setRtcp(Boolean rtcp) { this.rtcp = rtcp; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/bean/RequestStopPushStreamMsg.java ================================================ package com.genersoft.iot.vmp.service.bean; import com.genersoft.iot.vmp.gb28181.bean.SendRtpInfo; public class RequestStopPushStreamMsg { private SendRtpInfo sendRtpItem; private String platformName; private int platFormIndex; public SendRtpInfo getSendRtpItem() { return sendRtpItem; } public void setSendRtpItem(SendRtpInfo sendRtpItem) { this.sendRtpItem = sendRtpItem; } public String getPlatformName() { return platformName; } public void setPlatformName(String platformName) { this.platformName = platformName; } public int getPlatFormIndex() { return platFormIndex; } public void setPlatFormIndex(int platFormIndex) { this.platFormIndex = platFormIndex; } public static RequestStopPushStreamMsg getInstance(SendRtpInfo sendRtpItem, String platformName, int platFormIndex) { RequestStopPushStreamMsg streamMsg = new RequestStopPushStreamMsg(); streamMsg.setSendRtpItem(sendRtpItem); streamMsg.setPlatformName(platformName); streamMsg.setPlatFormIndex(platFormIndex); return streamMsg; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/bean/ResponseSendItemMsg.java ================================================ package com.genersoft.iot.vmp.service.bean; import com.genersoft.iot.vmp.gb28181.bean.SendRtpInfo; import com.genersoft.iot.vmp.media.bean.MediaServer; /** * redis消息:下级回复推送信息 * @author lin */ public class ResponseSendItemMsg { private SendRtpInfo sendRtpItem; private MediaServer mediaServerItem; public SendRtpInfo getSendRtpItem() { return sendRtpItem; } public void setSendRtpItem(SendRtpInfo sendRtpItem) { this.sendRtpItem = sendRtpItem; } public MediaServer getMediaServerItem() { return mediaServerItem; } public void setMediaServerItem(MediaServer mediaServerItem) { this.mediaServerItem = mediaServerItem; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/bean/SSRCInfo.java ================================================ package com.genersoft.iot.vmp.service.bean; import lombok.Data; @Data public class SSRCInfo { private int port; private String ssrc; private String app; private String Stream; public SSRCInfo(int port, String ssrc, String app, String stream) { this.port = port; this.ssrc = ssrc; this.app = app; this.Stream = stream; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/bean/StreamPushItemFromRedis.java ================================================ package com.genersoft.iot.vmp.service.bean; public class StreamPushItemFromRedis { private String app; private String stream; private long timeStamp; public String getApp() { return app; } public void setApp(String app) { this.app = app; } public String getStream() { return stream; } public void setStream(String stream) { this.stream = stream; } public long getTimeStamp() { return timeStamp; } public void setTimeStamp(long timeStamp) { this.timeStamp = timeStamp; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/bean/ThirdPartyGB.java ================================================ package com.genersoft.iot.vmp.service.bean; public class ThirdPartyGB { private String name; private String nationalStandardNo; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getNationalStandardNo() { return nationalStandardNo; } public void setNationalStandardNo(String nationalStandardNo) { this.nationalStandardNo = nationalStandardNo; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/bean/WvpRedisMsg.java ================================================ package com.genersoft.iot.vmp.service.bean; /** * @author lin */ public class WvpRedisMsg { public static WvpRedisMsg getInstance(String fromId, String toId, String type, String cmd, String serial, String content){ WvpRedisMsg wvpRedisMsg = new WvpRedisMsg(); wvpRedisMsg.setFromId(fromId); wvpRedisMsg.setToId(toId); wvpRedisMsg.setType(type); wvpRedisMsg.setCmd(cmd); wvpRedisMsg.setSerial(serial); wvpRedisMsg.setContent(content); return wvpRedisMsg; } private String fromId; private String toId; /** * req 请求, res 回复 */ private String type; private String cmd; /** * 消息的ID */ private String serial; private String content; private final static String requestTag = "req"; private final static String responseTag = "res"; public static WvpRedisMsg getRequestInstance(String fromId, String toId, String cmd, String serial, String content) { WvpRedisMsg wvpRedisMsg = new WvpRedisMsg(); wvpRedisMsg.setType(requestTag); wvpRedisMsg.setFromId(fromId); wvpRedisMsg.setToId(toId); wvpRedisMsg.setCmd(cmd); wvpRedisMsg.setSerial(serial); wvpRedisMsg.setContent(content); return wvpRedisMsg; } public static WvpRedisMsg getResponseInstance() { WvpRedisMsg wvpRedisMsg = new WvpRedisMsg(); wvpRedisMsg.setType(responseTag); return wvpRedisMsg; } public static WvpRedisMsg getResponseInstance(String fromId, String toId, String cmd, String serial, String content) { WvpRedisMsg wvpRedisMsg = new WvpRedisMsg(); wvpRedisMsg.setType(responseTag); wvpRedisMsg.setFromId(fromId); wvpRedisMsg.setToId(toId); wvpRedisMsg.setCmd(cmd); wvpRedisMsg.setSerial(serial); wvpRedisMsg.setContent(content); return wvpRedisMsg; } public static boolean isRequest(WvpRedisMsg wvpRedisMsg) { return requestTag.equals(wvpRedisMsg.getType()); } public String getSerial() { return serial; } public void setSerial(String serial) { this.serial = serial; } public String getFromId() { return fromId; } public void setFromId(String fromId) { this.fromId = fromId; } public String getToId() { return toId; } public void setToId(String toId) { this.toId = toId; } public String getType() { return type; } public void setType(String type) { this.type = type; } public String getCmd() { return cmd; } public void setCmd(String cmd) { this.cmd = cmd; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/bean/WvpRedisMsgCmd.java ================================================ package com.genersoft.iot.vmp.service.bean; /** * @author lin */ public class WvpRedisMsgCmd { /** * 请求获取推流信息 */ public static final String GET_SEND_ITEM = "GetSendItem"; /** * 请求推流的请求 */ public static final String REQUEST_PUSH_STREAM = "RequestPushStream"; /** * 停止推流的请求 */ public static final String REQUEST_STOP_PUSH_STREAM = "RequestStopPushStream"; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/impl/CloudRecordServiceImpl.java ================================================ package com.genersoft.iot.vmp.service.impl; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.bean.RecordInfo; import com.genersoft.iot.vmp.media.event.media.MediaRecordMp4Event; import com.genersoft.iot.vmp.media.service.IMediaServerService; import com.genersoft.iot.vmp.media.zlm.AssistRESTfulUtils; import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo; import com.genersoft.iot.vmp.service.ICloudRecordService; import com.genersoft.iot.vmp.service.bean.CloudRecordItem; import com.genersoft.iot.vmp.service.bean.DownloadFileInfo; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcPlayService; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.dao.CloudRecordServiceMapper; import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.cloudRecord.bean.CloudRecordUrl; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.ObjectUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.event.EventListener; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.util.Assert; import java.io.File; import java.time.LocalDate; import java.time.ZoneOffset; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; @Slf4j @Service public class CloudRecordServiceImpl implements ICloudRecordService { @Autowired private CloudRecordServiceMapper cloudRecordServiceMapper; @Autowired private IMediaServerService mediaServerService; @Autowired private IRedisCatchStorage redisCatchStorage; @Autowired private AssistRESTfulUtils assistRESTfulUtils; @Autowired private UserSetting userSetting; @Autowired private IRedisRpcPlayService redisRpcPlayService; @Override public PageInfo getList(int page, int count, String query, String app, String stream, String startTime, String endTime, List mediaServerItems, String callId, Boolean ascOrder) { // 开始时间和结束时间在数据库中都是以秒为单位的 Long startTimeStamp = null; Long endTimeStamp = null; if (startTime != null ) { if (!DateUtil.verification(startTime, DateUtil.formatter)) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "开始时间格式错误,正确格式为: " + DateUtil.formatter); } startTimeStamp = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestampMs(startTime); } if (endTime != null ) { if (!DateUtil.verification(endTime, DateUtil.formatter)) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "结束时间格式错误,正确格式为: " + DateUtil.formatter); } endTimeStamp = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestampMs(endTime); } PageHelper.startPage(page, count); if (query != null) { query = query.replaceAll("/", "//") .replaceAll("%", "/%") .replaceAll("_", "/_"); } List all = cloudRecordServiceMapper.getList(query, app, stream, startTimeStamp, endTimeStamp, callId, mediaServerItems, null, ascOrder); return new PageInfo<>(all); } @Override public List getDateList(String app, String stream, int year, int month, List mediaServerItems) { LocalDate startDate = LocalDate.of(year, month, 1); LocalDate endDate; if (month == 12) { endDate = LocalDate.of(year + 1, 1, 1); }else { endDate = LocalDate.of(year, month + 1, 1); } long startTimeStamp = startDate.atStartOfDay().toInstant(ZoneOffset.ofHours(8)).toEpochMilli(); long endTimeStamp = endDate.atStartOfDay().toInstant(ZoneOffset.ofHours(8)).toEpochMilli(); List cloudRecordItemList = cloudRecordServiceMapper.getList(null, app, stream, startTimeStamp, endTimeStamp, null, mediaServerItems, null, null); if (cloudRecordItemList.isEmpty()) { return new ArrayList<>(); } Set resultSet = new HashSet<>(); cloudRecordItemList.stream().forEach(cloudRecordItem -> { String date = DateUtil.timestampTo_yyyy_MM_dd(cloudRecordItem.getStartTime()); resultSet.add(date); }); return new ArrayList<>(resultSet); } @Async("taskExecutor") @EventListener public void onApplicationEvent(MediaRecordMp4Event event) { CloudRecordItem cloudRecordItem = CloudRecordItem.getInstance(event); cloudRecordItem.setServerId(userSetting.getServerId()); if (ObjectUtils.isEmpty(cloudRecordItem.getCallId())) { StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(event.getApp(), event.getStream()); if (streamAuthorityInfo != null) { cloudRecordItem.setCallId(streamAuthorityInfo.getCallId()); } } log.info("[添加录像记录] {}/{}, callId: {}, 内容:{}", event.getApp(), event.getStream(), cloudRecordItem.getCallId(), event.getRecordInfo()); cloudRecordServiceMapper.add(cloudRecordItem); } @Override public String addTask(String app, String stream, MediaServer mediaServerItem, String startTime, String endTime, String callId, String remoteHost, boolean filterMediaServer) { // 参数校验 Assert.notNull(app,"应用名为NULL"); Assert.notNull(stream,"流ID为NULL"); if (mediaServerItem.getRecordAssistPort() == 0) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "为配置Assist服务"); } Long startTimeStamp = null; Long endTimeStamp = null; if (startTime != null) { startTimeStamp = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime); } if (endTime != null) { endTimeStamp = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime); } List mediaServers = new ArrayList<>(); mediaServers.add(mediaServerItem); // 检索相关的录像文件 List filePathList = cloudRecordServiceMapper.queryRecordFilePathList(app, stream, startTimeStamp, endTimeStamp, callId, filterMediaServer ? mediaServers : null); if (filePathList == null || filePathList.isEmpty()) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "未检索到视频文件"); } JSONObject result = assistRESTfulUtils.addTask(mediaServerItem, app, stream, startTime, endTime, callId, filePathList, remoteHost); if (result.getInteger("code") != 0) { throw new ControllerException(result.getInteger("code"), result.getString("msg")); } return result.getString("data"); } @Override public JSONArray queryTask(String app, String stream, String callId, String taskId, String mediaServerId, Boolean isEnd, String scheme) { MediaServer mediaServerItem = null; if (mediaServerId == null) { mediaServerItem = mediaServerService.getDefaultMediaServer(); }else { mediaServerItem = mediaServerService.getOne(mediaServerId); } if (mediaServerItem == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到可用的流媒体"); } JSONObject result = assistRESTfulUtils.queryTaskList(mediaServerItem, app, stream, callId, taskId, isEnd, scheme); if (result == null || result.getInteger("code") != 0) { throw new ControllerException(ErrorCode.ERROR100.getCode(), result == null ? "查询任务列表失败" : result.getString("msg")); } return result.getJSONArray("data"); } @Override public int changeCollect(boolean result, String app, String stream, String mediaServerId, String startTime, String endTime, String callId) { // 开始时间和结束时间在数据库中都是以秒为单位的 Long startTimeStamp = null; Long endTimeStamp = null; if (startTime != null ) { if (!DateUtil.verification(startTime, DateUtil.formatter)) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "开始时间格式错误,正确格式为: " + DateUtil.formatter); } startTimeStamp = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime); } if (endTime != null ) { if (!DateUtil.verification(endTime, DateUtil.formatter)) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "结束时间格式错误,正确格式为: " + DateUtil.formatter); } endTimeStamp = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime); } List mediaServerItems; if (!ObjectUtils.isEmpty(mediaServerId)) { mediaServerItems = new ArrayList<>(); MediaServer mediaServerItem = mediaServerService.getOne(mediaServerId); if (mediaServerItem == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到流媒体: " + mediaServerId); } mediaServerItems.add(mediaServerItem); } else { mediaServerItems = null; } List all = cloudRecordServiceMapper.getList(null, app, stream, startTimeStamp, endTimeStamp, callId, mediaServerItems, null, null); if (all.isEmpty()) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到待收藏的视频"); } int limitCount = 50; int resultCount = 0; if (all.size() > limitCount) { for (int i = 0; i < all.size(); i += limitCount) { int toIndex = i + limitCount; if (i + limitCount > all.size()) { toIndex = all.size(); } resultCount += cloudRecordServiceMapper.updateCollectList(result, all.subList(i, toIndex)); } }else { resultCount = cloudRecordServiceMapper.updateCollectList(result, all); } return resultCount; } @Override public int changeCollectById(Integer recordId, boolean result) { return cloudRecordServiceMapper.changeCollectById(result, recordId); } @Override public DownloadFileInfo getPlayUrlPath(Integer recordId) { CloudRecordItem recordItem = cloudRecordServiceMapper.queryOne(recordId); if (recordItem == null) { throw new ControllerException(ErrorCode.ERROR400.getCode(), "资源不存在"); } if (!userSetting.getServerId().equals(recordItem.getServerId())) { return redisRpcPlayService.getRecordPlayUrl(recordItem.getServerId(), recordId); } MediaServer mediaServer = mediaServerService.getOne(recordItem.getMediaServerId()); return mediaServerService.getDownloadFilePath(mediaServer, RecordInfo.getInstance(recordItem)); } @Override public List getAllList(String query, String app, String stream, String startTime, String endTime, List mediaServerItems, String callId, List ids) { // 开始时间和结束时间在数据库中都是以秒为单位的 Long startTimeStamp = null; Long endTimeStamp = null; if (startTime != null ) { if (!DateUtil.verification(startTime, DateUtil.formatter)) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "开始时间格式错误,正确格式为: " + DateUtil.formatter); } startTimeStamp = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestampMs(startTime); } if (endTime != null ) { if (!DateUtil.verification(endTime, DateUtil.formatter)) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "结束时间格式错误,正确格式为: " + DateUtil.formatter); } endTimeStamp = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestampMs(endTime); } return cloudRecordServiceMapper.getList(query, app, stream, startTimeStamp, endTimeStamp, callId, mediaServerItems, ids, null); } @Override public void loadMP4File(String app, String stream, int cloudRecordId, ErrorCallback callback) { CloudRecordItem recordItem = cloudRecordServiceMapper.queryOne(cloudRecordId); if (recordItem == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "无录像"); } String mediaServerId = recordItem.getMediaServerId(); MediaServer mediaServer = mediaServerService.getOne(mediaServerId); if (mediaServer == null) { log.warn("[云端录像] 播放 未找到录制的流媒体,将自动选择低负载流媒体使用"); mediaServer = mediaServerService.getMediaServerForMinimumLoad(null); } if (mediaServer == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "无可用流媒体"); } String fileName = recordItem.getFileName().substring(0 , recordItem.getFileName().indexOf(".")); String filePath = recordItem.getFilePath(); // if (filePath != null) { // fileName = filePath.substring(0, filePath.lastIndexOf("/")); // } mediaServerService.loadMP4File(mediaServer, app, stream, filePath, fileName, ((code, msg, streamInfo) -> { if (code == ErrorCode.SUCCESS.getCode()) { streamInfo.setDuration(recordItem.getTimeLen()); } callback.run(code, msg, streamInfo); })); } @Override public void loadMP4FileForDate(String app, String stream, String date, ErrorCallback callback) { long startTimestamp = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestampMs(date + " 00:00:00"); long endTimestamp = startTimestamp + 24 * 60 * 60 * 1000; List recordItemList = cloudRecordServiceMapper.getList(null, app, stream, startTimestamp, endTimestamp, null, null, null, false); if (recordItemList.isEmpty()) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "此时间无录像"); } String mediaServerId = recordItemList.get(0).getMediaServerId(); MediaServer mediaServer = mediaServerService.getOne(mediaServerId); if (mediaServer == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "媒体节点不存在: " + mediaServerId); } String dateDir = null; String filePath = recordItemList.get(0).getFilePath(); if (filePath != null) { dateDir = filePath.substring(0, filePath.lastIndexOf("/")); } mediaServerService.loadMP4FileForDate(mediaServer, app, stream, date, dateDir, callback); } @Override public void seekRecord(String mediaServerId,String app, String stream, Double seek, String schema) { MediaServer mediaServer = mediaServerService.getOne(mediaServerId); if (mediaServer == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "媒体节点不存在: " + mediaServerId); } mediaServerService.seekRecordStamp(mediaServer, app, stream, seek, schema); } @Override public void setRecordSpeed(String mediaServerId, String app, String stream, Integer speed, String schema) { MediaServer mediaServer = mediaServerService.getOne(mediaServerId); if (mediaServer == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "媒体节点不存在: " + mediaServerId); } mediaServerService.setRecordSpeed(mediaServer, app, stream, speed, schema); } @Override public void deleteFileByIds(Set ids) { log.info("[删除录像文件] ids: {}", ids.toArray()); List cloudRecordItemList = cloudRecordServiceMapper.queryRecordByIds(ids); if (cloudRecordItemList.isEmpty()) { return; } List cloudRecordItemIdListForDelete = new ArrayList<>(); StringBuilder stringBuilder = new StringBuilder(); for (CloudRecordItem cloudRecordItem : cloudRecordItemList) { String date = new File(cloudRecordItem.getFilePath()).getParentFile().getName(); MediaServer mediaServer = mediaServerService.getOne(cloudRecordItem.getMediaServerId()); try { boolean deleteResult = mediaServerService.deleteRecordDirectory(mediaServer, cloudRecordItem.getApp(), cloudRecordItem.getStream(), date, cloudRecordItem.getFileName()); if (deleteResult) { log.warn("[录像文件] 删除磁盘文件成功: {}", cloudRecordItem.getFilePath()); cloudRecordItemIdListForDelete.add(cloudRecordItem); } }catch (ControllerException e) { if (stringBuilder.length() > 0) { stringBuilder.append(", "); } stringBuilder.append(cloudRecordItem.getFileName()); } } if (!cloudRecordItemIdListForDelete.isEmpty()) { cloudRecordServiceMapper.deleteList(cloudRecordItemIdListForDelete); } if (stringBuilder.length() > 0) { stringBuilder.append(" 删除失败"); throw new ControllerException(ErrorCode.ERROR100.getCode(), stringBuilder.toString()); } } @Override public List getUrlListByIds(List ids) { List cloudRecordItems = cloudRecordServiceMapper.queryRecordByIds(ids); if (cloudRecordItems.isEmpty()) { return List.of(); } return getCloudRecordUrl(cloudRecordItems); } @Override public List getUrlList(String app, String stream, String callId) { List cloudRecordItems = cloudRecordServiceMapper.queryRecordByAppStreamAndCallId(app, stream, callId); if (cloudRecordItems.isEmpty()) { return List.of(); } return getCloudRecordUrl(cloudRecordItems); } private List getCloudRecordUrl(List cloudRecordItems) { if (cloudRecordItems.isEmpty()) { return List.of(); } List resultList = new ArrayList<>(); for (CloudRecordItem cloudRecordItem : cloudRecordItems) { CloudRecordUrl cloudRecordUrl = new CloudRecordUrl(); cloudRecordUrl.setId(cloudRecordItem.getId()); cloudRecordUrl.setFileName(cloudRecordItem.getStartTime() + ".mp4"); cloudRecordUrl.setFilePath(cloudRecordItem.getFilePath()); if (!userSetting.getServerId().equals(cloudRecordItem.getServerId())) { cloudRecordUrl.setDownloadUrl(redisRpcPlayService.getRecordPlayUrl(cloudRecordItem.getServerId(), cloudRecordItem.getId()).getHttpPath()); }else { MediaServer mediaServer = mediaServerService.getOne(cloudRecordItem.getMediaServerId()); mediaServer.setStreamIp(mediaServer.getIp()); DownloadFileInfo downloadFilePath = mediaServerService.getDownloadFilePath(mediaServer, RecordInfo.getInstance(cloudRecordItem)); cloudRecordUrl.setDownloadUrl(downloadFilePath.getHttpPath()); } resultList.add(cloudRecordUrl); } return resultList; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/impl/LogServiceImpl.java ================================================ package com.genersoft.iot.vmp.service.impl; import ch.qos.logback.classic.Logger; import ch.qos.logback.core.rolling.RollingFileAppender; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.service.ILogService; import com.genersoft.iot.vmp.service.bean.LogFileInfo; import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.input.ReversedLinesFileReader; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import java.io.*; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; @Service @Slf4j public class LogServiceImpl implements ILogService { @Override public List queryList(String query, String startTime, String endTime) { File logFile = getLogDir(); if (logFile == null || !logFile.exists()) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "获取日志文件目录失败"); } File[] files = logFile.listFiles(); List result = new ArrayList<>(); if (files == null || files.length == 0) { return result; } // 读取文件创建时间作为开始时间,修改时间为结束时间 Long startTimestamp = null; if (startTime != null) { startTimestamp = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestampMs(startTime); } Long endTimestamp = null; if (endTime != null) { endTimestamp = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestampMs(endTime); } for (File file : files) { LogFileInfo logFileInfo = new LogFileInfo(); logFileInfo.setFileName(file.getName()); logFileInfo.setFileSize(file.length()); if (query != null && !file.getName().contains(query)) { continue; } try { Long[] fileAttributes = getFileAttributes(file); if (fileAttributes == null) { continue; } long startTimestampForFile = fileAttributes[0]; long endTimestampForFile = fileAttributes[1]; logFileInfo.setStartTime(startTimestampForFile); logFileInfo.setEndTime(endTimestampForFile); if (startTimestamp != null && startTimestamp > startTimestampForFile) { continue; } if (endTimestamp != null && endTimestamp < endTimestampForFile) { continue; } } catch (IOException e) { log.error("[读取日志文件列表] 获取创建时间和修改时间失败", e); continue; } result.add(logFileInfo); } result.sort((o1, o2) -> o2.getStartTime().compareTo(o1.getStartTime())); return result; } private File getLogDir() { Logger logger = (Logger) LoggerFactory.getLogger("root"); RollingFileAppender rollingFileAppender = (RollingFileAppender) logger.getAppender("RollingFile"); File rollingFile = new File(rollingFileAppender.getFile()); return rollingFile.getParentFile(); } Long[] getFileAttributes(File file) throws IOException { BufferedReader bufferedReader = new BufferedReader(new FileReader(file)); String startLine = bufferedReader.readLine(); if (startLine== null) { return null; } String startTime = startLine.substring(0, 19); // 最后一行的开头不一定是时间 // String lastLine = ""; // try (ReversedLinesFileReader reversedLinesReader = new ReversedLinesFileReader(file, Charset.defaultCharset())) { // lastLine = reversedLinesReader.readLine(); // } catch (Exception e) { // log.error("file read error, msg:{}", e.getMessage(), e); // } // String endTime = lastLine.substring(0, 19); return new Long[]{DateUtil.yyyy_MM_dd_HH_mm_ssToTimestampMs(startTime), file.lastModified()}; } @Override public File getFileByName(String fileName) { File logDir = getLogDir(); return new File(logDir, fileName); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java ================================================ package com.genersoft.iot.vmp.service.impl; import com.genersoft.iot.vmp.common.InviteInfo; import com.genersoft.iot.vmp.common.InviteSessionStatus; import com.genersoft.iot.vmp.common.InviteSessionType; import com.genersoft.iot.vmp.common.VideoManagerConstants; import com.genersoft.iot.vmp.common.enums.MediaApp; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction; import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService; import com.genersoft.iot.vmp.gb28181.service.IInviteStreamService; import com.genersoft.iot.vmp.gb28181.session.SipInviteSessionManager; import com.genersoft.iot.vmp.jt1078.bean.JTMediaStreamType; import com.genersoft.iot.vmp.jt1078.service.Ijt1078PlayService; import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.bean.ResultForOnPublish; import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo; import com.genersoft.iot.vmp.service.IMediaService; import com.genersoft.iot.vmp.service.IRecordPlanService; import com.genersoft.iot.vmp.service.ISendRtpServerService; import com.genersoft.iot.vmp.service.IUserService; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy; import com.genersoft.iot.vmp.streamProxy.service.IStreamProxyService; import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.utils.MediaServerUtils; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.OtherPsSendInfo; import com.genersoft.iot.vmp.vmanager.bean.OtherRtpSendInfo; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import java.util.Map; @Slf4j @Service public class MediaServiceImpl implements IMediaService { @Autowired private IRedisCatchStorage redisCatchStorage; @Autowired private IStreamProxyService streamProxyService; @Autowired private UserSetting userSetting; @Autowired private RedisTemplate redisTemplate; @Autowired private IUserService userService; @Autowired private IInviteStreamService inviteStreamService; @Autowired private IDeviceChannelService deviceChannelService; @Autowired private SipInviteSessionManager sessionManager; @Autowired private Ijt1078Service ijt1078Service; @Autowired private Ijt1078PlayService jt1078PlayService; @Autowired private ISendRtpServerService sendRtpServerService; @Autowired private IRecordPlanService recordPlanService; @Override public boolean authenticatePlay(String app, String stream, String callId) { if (app == null || stream == null) { return false; } if (MediaApp.GB28181.equals(app)) { return true; } StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(app, stream); if (streamAuthorityInfo == null || streamAuthorityInfo.getCallId() == null) { return true; } return streamAuthorityInfo.getCallId().equals(callId); } @Override public ResultForOnPublish authenticatePublish(MediaServer mediaServer, String app, String stream, String params) { // 推流鉴权的处理 if (!MediaApp.GB28181.equals(app) && !MediaApp.JT1078.equals(app) ) { if (MediaApp.GB28181_TALK.equals(app) && stream.endsWith("_talk")) { ResultForOnPublish result = new ResultForOnPublish(); result.setEnable_mp4(false); result.setEnable_audio(true); return result; } if ("jt_talk".equals(app) && stream.endsWith("_talk")) { ResultForOnPublish result = new ResultForOnPublish(); result.setEnable_mp4(false); result.setEnable_audio(true); return result; } if ("mp4_record".equals(app) ) { ResultForOnPublish result = new ResultForOnPublish(); result.setEnable_mp4(false); result.setEnable_audio(true); return result; } StreamProxy streamProxyItem = streamProxyService.getStreamProxyByAppAndStream(app, stream); if (streamProxyItem != null) { ResultForOnPublish result = new ResultForOnPublish(); result.setEnable_audio(streamProxyItem.isEnableAudio()); result.setEnable_mp4(streamProxyItem.isEnableMp4()); return result; } if (userSetting.getPushAuthority()) { // 对于推流进行鉴权 Map paramMap = MediaServerUtils.urlParamToMap(params); // 推流鉴权 if (params == null) { log.info("推流鉴权失败: 缺少必要参数:sign=md5(user表的pushKey)"); throw new ControllerException(ErrorCode.ERROR401.getCode(), "Unauthorized"); } String sign = paramMap.get("sign"); if (sign == null) { log.info("推流鉴权失败: 缺少必要参数:sign=md5(user表的pushKey)"); throw new ControllerException(ErrorCode.ERROR401.getCode(), "Unauthorized"); } // 推流自定义播放鉴权码 String callId = paramMap.get("callId"); // 鉴权配置 boolean hasAuthority = userService.checkPushAuthority(callId, sign); if (!hasAuthority) { log.info("推流鉴权失败: sign 无权限: callId={}. sign={}", callId, sign); throw new ControllerException(ErrorCode.ERROR401.getCode(), "Unauthorized"); } StreamAuthorityInfo streamAuthorityInfo = StreamAuthorityInfo.getInstanceByHook(app, stream, mediaServer.getId()); streamAuthorityInfo.setCallId(callId); streamAuthorityInfo.setSign(sign); // 鉴权通过 redisCatchStorage.updateStreamAuthorityInfo(app, stream, streamAuthorityInfo); } } ResultForOnPublish result = new ResultForOnPublish(); result.setEnable_audio(true); // 国标流 if (MediaApp.GB28181.equals(app)) { InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(null, stream); if (inviteInfo != null) { result.setEnable_mp4(inviteInfo.getRecord()); }else { result.setEnable_mp4(userSetting.getRecordSip()); } // 单端口模式下修改流 ID if (!mediaServer.isRtpEnable() && inviteInfo == null) { String ssrc = String.format("%010d", Long.parseLong(stream, 16)); inviteInfo = inviteStreamService.getInviteInfoBySSRC(ssrc); if (inviteInfo != null) { result.setStream_replace(inviteInfo.getStream()); log.info("[HOOK]推流鉴权 stream: {} 替换为 {}", stream, inviteInfo.getStream()); stream = inviteInfo.getStream(); } } // 设置音频信息及录制信息 SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransactionByStream(app, stream); if (ssrcTransaction != null ) { // 为录制国标模拟一个鉴权信息, 方便后续写入录像文件时使用 StreamAuthorityInfo streamAuthorityInfo = StreamAuthorityInfo.getInstanceByHook(app, stream, mediaServer.getId()); streamAuthorityInfo.setApp(app); streamAuthorityInfo.setStream(ssrcTransaction.getStream()); streamAuthorityInfo.setCallId(ssrcTransaction.getSipTransactionInfo().getCallId()); redisCatchStorage.updateStreamAuthorityInfo(app, ssrcTransaction.getStream(), streamAuthorityInfo); String deviceId = ssrcTransaction.getDeviceId(); Integer channelId = ssrcTransaction.getChannelId(); DeviceChannel deviceChannel = deviceChannelService.getOneForSourceById(channelId); if (deviceChannel != null) { result.setEnable_audio(deviceChannel.isHasAudio()); } // 如果是录像下载就设置视频间隔十秒 if (ssrcTransaction.getType() == InviteSessionType.DOWNLOAD) { // 获取录像的总时长,然后设置为这个视频的时长 InviteInfo inviteInfoForDownload = inviteStreamService.getInviteInfo(InviteSessionType.DOWNLOAD, channelId, stream); if (inviteInfoForDownload != null) { String startTime = inviteInfoForDownload.getStartTime(); String endTime = inviteInfoForDownload.getEndTime(); long difference = DateUtil.getDifference(startTime, endTime) / 1000; result.setMp4_max_second((int) difference); result.setEnable_mp4(true); // 设置为2保证得到的mp4的时长是正常的 result.setModify_stamp(2); } } // 如果是talk对讲,则默认获取声音 if (ssrcTransaction.getType() == InviteSessionType.TALK) { result.setEnable_audio(true); } } } else if (app.equals(MediaApp.GB28181_BROADCAST)) { result.setEnable_audio(true); result.setEnable_mp4(userSetting.getRecordSip()); } else if (app.equals(MediaApp.GB28181_TALK)) { result.setEnable_audio(true); result.setEnable_mp4(userSetting.getRecordSip()); }else { result.setEnable_mp4(userSetting.getRecordPushLive()); } if (app.equalsIgnoreCase(MediaApp.GB28181)) { String receiveKey = VideoManagerConstants.WVP_OTHER_RECEIVE_RTP_INFO + userSetting.getServerId() + "_" + stream; OtherRtpSendInfo otherRtpSendInfo = (OtherRtpSendInfo) redisTemplate.opsForValue().get(receiveKey); String receiveKeyForPS = VideoManagerConstants.WVP_OTHER_RECEIVE_PS_INFO + userSetting.getServerId() + "_" + stream; OtherPsSendInfo otherPsSendInfo = (OtherPsSendInfo) redisTemplate.opsForValue().get(receiveKeyForPS); if (otherRtpSendInfo != null || otherPsSendInfo != null) { result.setEnable_mp4(true); } } return result; } @Override public boolean closeStreamOnNoneReader(String mediaServerId, String app, String stream, String schema) { boolean result = false; if (recordPlanService.recording(app, stream) != null) { return false; } // 国标类型的流 if (MediaApp.GB28181.equals(app)) { result = userSetting.getStreamOnDemand(); // 国标流, 点播/录像回放/录像下载 InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(null, stream); if (inviteInfo != null) { if (inviteInfo.getStatus() == InviteSessionStatus.ok){ // 录像下载 if (inviteInfo.getType() == InviteSessionType.DOWNLOAD) { return false; } DeviceChannel deviceChannel = deviceChannelService.getOneForSourceById(inviteInfo.getChannelId()); if (deviceChannel == null) { return false; } } return result; } }else if (MediaApp.JT1078.equals(app)) { // 判断是否是1078播放类型 JTMediaStreamType jtMediaStreamType = ijt1078Service.checkStreamFromJt(stream); if (jtMediaStreamType != null) { String[] streamParamArray = stream.split("_"); if (jtMediaStreamType.equals(JTMediaStreamType.PLAY)) { jt1078PlayService.stopPlay(streamParamArray[0], Integer.parseInt(streamParamArray[1])); }else if (jtMediaStreamType.equals(JTMediaStreamType.PLAYBACK)) { jt1078PlayService.stopPlayback(streamParamArray[0], Integer.parseInt(streamParamArray[1])); } }else { return false; } }else if (MediaApp.GB28181_TALK.equals(app) || MediaApp.GB28181_BROADCAST.equals(app)) { return false; } else if ("mp4_record".equals(app)) { return true; } else { // 非国标流 推流/拉流代理 // 拉流代理 StreamProxy streamProxy = streamProxyService.getStreamProxyByAppAndStream(app, stream); if (streamProxy != null) { if (streamProxy.isEnableDisableNoneReader()) { // 无人观看停用 // 修改数据 streamProxyService.stopByAppAndStream(app, stream); return true; } else { // 无人观看不做处理 return false; } }else { return false; } } return result; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/impl/MobilePositionServiceImpl.java ================================================ package com.genersoft.iot.vmp.service.impl; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; import com.genersoft.iot.vmp.gb28181.bean.MobilePosition; import com.genersoft.iot.vmp.gb28181.bean.Platform; import com.genersoft.iot.vmp.gb28181.dao.DeviceChannelMapper; import com.genersoft.iot.vmp.gb28181.dao.DeviceMapper; import com.genersoft.iot.vmp.gb28181.dao.DeviceMobilePositionMapper; import com.genersoft.iot.vmp.gb28181.dao.PlatformMapper; import com.genersoft.iot.vmp.gb28181.utils.Coordtransform; import com.genersoft.iot.vmp.service.IMobilePositionService; import com.genersoft.iot.vmp.utils.DateUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @Slf4j @Service public class MobilePositionServiceImpl implements IMobilePositionService { @Autowired private DeviceMapper deviceMapper; @Autowired private DeviceChannelMapper channelMapper; @Autowired private DeviceMobilePositionMapper mobilePositionMapper; @Autowired private UserSetting userSetting; @Autowired private PlatformMapper platformMapper; @Autowired private RedisTemplate redisTemplate; private final String REDIS_MOBILE_POSITION_LIST = "redis_mobile_position_list"; @Override public void add(MobilePosition mobilePosition) { List list = new ArrayList<>(); list.add(mobilePosition); add(list); } @Override public void add(List mobilePositionList) { redisTemplate.opsForList().leftPushAll(REDIS_MOBILE_POSITION_LIST, mobilePositionList); } private List get(int length) { Long size = redisTemplate.opsForList().size(REDIS_MOBILE_POSITION_LIST); if (size == null || size == 0) { return new ArrayList<>(); } return redisTemplate.opsForList().rightPop(REDIS_MOBILE_POSITION_LIST, Math.min(length, size)); } /** * 查询移动位置轨迹 */ @Override public synchronized List queryMobilePositions(String deviceId, String channelId, String startTime, String endTime) { return mobilePositionMapper.queryPositionByDeviceIdAndTime(deviceId, channelId, startTime, endTime); } @Override public List queryEnablePlatformListWithAsMessageChannel() { return platformMapper.queryEnablePlatformListWithAsMessageChannel(); } /** * 查询最新移动位置 */ @Override public MobilePosition queryLatestPosition(String deviceId) { return mobilePositionMapper.queryLatestPositionByDevice(deviceId); } @Scheduled(fixedDelay = 1000) @Transactional public void executeTaskQueue() { int countLimit = 3000; List mobilePositions = get(countLimit); if (mobilePositions == null || mobilePositions.isEmpty()) { return; } if (userSetting.getSavePositionHistory()) { mobilePositionMapper.batchadd(mobilePositions); } log.info("[移动位置订阅]更新通道位置: {}", mobilePositions.size()); Map> updateChannelMap = new HashMap<>(); for (MobilePosition mobilePosition : mobilePositions) { DeviceChannel deviceChannel = new DeviceChannel(); deviceChannel.setId(mobilePosition.getChannelId()); deviceChannel.setDeviceId(mobilePosition.getDeviceId()); deviceChannel.setLongitude(mobilePosition.getLongitude()); deviceChannel.setLatitude(mobilePosition.getLatitude()); deviceChannel.setGpsTime(mobilePosition.getTime()); deviceChannel.setUpdateTime(DateUtil.getNow()); if (mobilePosition.getLongitude() > 0 || mobilePosition.getLatitude() > 0) { Double[] wgs84Position = Coordtransform.GCJ02ToWGS84(mobilePosition.getLongitude(), mobilePosition.getLatitude()); deviceChannel.setGbLongitude(wgs84Position[0]); deviceChannel.setGbLatitude(wgs84Position[1]); } if (!updateChannelMap.containsKey(mobilePosition.getDeviceId())) { updateChannelMap.put(mobilePosition.getDeviceId(), new HashMap<>()); } updateChannelMap.get(mobilePosition.getDeviceId()).put(mobilePosition.getChannelId(), deviceChannel); } List deviceIds = new ArrayList<>(updateChannelMap.keySet()); if (deviceIds.isEmpty()) { log.info("[移动位置订阅]为查询到对应的设备,消息已经忽略"); return; } List deviceList = deviceMapper.queryByDeviceIds(deviceIds); for (Device device : deviceList) { Map channelMap = updateChannelMap.get(device.getDeviceId()); if (device.getGeoCoordSys().equalsIgnoreCase("GCJ02")) { channelMap.values().forEach(channel -> { Double[] wgs84Position = Coordtransform.GCJ02ToWGS84(channel.getLongitude(), channel.getLatitude()); channel.setGbLongitude(wgs84Position[0]); channel.setGbLatitude(wgs84Position[1]); }); } channelMapper.batchUpdatePosition(new ArrayList<>(channelMap.values())); } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/impl/RecordPlanServiceImpl.java ================================================ package com.genersoft.iot.vmp.service.impl; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import com.genersoft.iot.vmp.gb28181.dao.CommonGBChannelMapper; import com.genersoft.iot.vmp.gb28181.service.IGbChannelPlayService; import com.genersoft.iot.vmp.media.bean.MediaInfo; import com.genersoft.iot.vmp.media.event.media.MediaDepartureEvent; import com.genersoft.iot.vmp.media.service.IMediaServerService; import com.genersoft.iot.vmp.service.IRecordPlanService; import com.genersoft.iot.vmp.service.bean.InviteErrorCode; import com.genersoft.iot.vmp.service.bean.RecordPlan; import com.genersoft.iot.vmp.service.bean.RecordPlanItem; import com.genersoft.iot.vmp.storager.dao.RecordPlanMapper; import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import com.google.common.base.Joiner; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.event.EventListener; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.util.*; import java.util.concurrent.TimeUnit; @Service @Slf4j public class RecordPlanServiceImpl implements IRecordPlanService { @Autowired private RecordPlanMapper recordPlanMapper; @Autowired private CommonGBChannelMapper channelMapper; @Autowired private IGbChannelPlayService channelPlayService; @Autowired private IMediaServerService mediaServerService; /** * 流离开的处理 */ @Async("taskExecutor") @EventListener public void onApplicationEvent(MediaDepartureEvent event) { // 流断开,检查是否还处于录像状态, 如果是则继续录像 Integer channelId = recording(event.getApp(), event.getStream()); if(channelId == null) { return; } // 重新拉起 CommonGBChannel channel = channelMapper.queryById(channelId); if (channel == null) { log.warn("[录制计划] 流离开时拉起需要录像的流时, 发现通道不存在, id: {}", channelId); return; } // 开启点播, channelPlayService.play(channel, null, true, ((code, msg, streamInfo) -> { if (code == InviteErrorCode.SUCCESS.getCode() && streamInfo != null) { log.info("[录像] 流离开时拉起需要录像的流, 开启成功, 通道ID: {}", channel.getGbId()); recordStreamMap.put(channel.getGbId(), streamInfo); } else { recordStreamMap.remove(channelId); log.info("[录像] 流离开时拉起需要录像的流, 开启失败, 十分钟后重试, 通道ID: {}", channel.getGbId()); } })); } Map recordStreamMap = new HashMap<>(); @Scheduled(fixedRate = 1, timeUnit = TimeUnit.MINUTES) public void execution() { // 查询现在需要录像的通道Id List startChannelIdList = queryCurrentChannelRecord(); if (startChannelIdList.isEmpty()) { // 当前没有录像任务, 如果存在旧的正在录像的就移除 if(!recordStreamMap.isEmpty()) { Set recordStreamSet = new HashSet<>(recordStreamMap.keySet()); stopStreams(recordStreamSet, recordStreamMap); recordStreamMap.clear(); } }else { // 当前存在录像任务, 获取正在录像中存在但是当前录制列表不存在的内容,进行停止; 获取正在录像中没有但是当前需录制的列表中存在的进行开启. Set recordStreamSet = new HashSet<>(recordStreamMap.keySet()); startChannelIdList.forEach(recordStreamSet::remove); if (!recordStreamSet.isEmpty()) { // 正在录像中存在但是当前录制列表不存在的内容,进行停止; stopStreams(recordStreamSet, recordStreamMap); } // 移除startChannelIdList中已经在录像的部分, 剩下的都是需要新添加的(正在录像中没有但是当前需录制的列表中存在的进行开启) recordStreamMap.keySet().forEach(startChannelIdList::remove); if (!startChannelIdList.isEmpty()) { // 获取所有的关联的通道 List channelList = channelMapper.queryByIds(startChannelIdList); if (!channelList.isEmpty()) { // 查找是否已经开启录像, 如果没有则开启录像 for (CommonGBChannel channel : channelList) { // 开启点播, channelPlayService.play(channel, null, true, ((code, msg, streamInfo) -> { if (code == InviteErrorCode.SUCCESS.getCode() && streamInfo != null) { log.info("[录像] 开启成功, 通道ID: {}", channel.getGbId()); recordStreamMap.put(channel.getGbId(), streamInfo); } else { log.info("[录像] 开启失败, 十分钟后重试, 通道ID: {}", channel.getGbId()); } })); } } else { log.error("[录制计划] 数据异常, 这些关联的通道已经不存在了: {}", Joiner.on(",").join(startChannelIdList)); } } } } /** * 获取当前时间段应该录像的通道Id列表 */ private List queryCurrentChannelRecord(){ // 获取当前时间在一周内的序号, 数据库存储的从第几个30分钟开始, 0-47, 包括首尾 LocalDateTime now = LocalDateTime.now(); int week = now.getDayOfWeek().getValue(); int index = now.getHour() * 60 + now.getMinute(); // 查询现在需要录像的通道Id return recordPlanMapper.queryRecordIng(week, index); } private void stopStreams(Collection channelIds, Map recordStreamMap) { for (Integer channelId : channelIds) { try { StreamInfo streamInfo = recordStreamMap.get(channelId); if (streamInfo == null) { continue; } // 查看是否有人观看,存在则不做处理,等待后续自然处理,如果无人观看,则关闭该流 MediaInfo mediaInfo = mediaServerService.getMediaInfo(streamInfo.getMediaServer(), streamInfo.getApp(), streamInfo.getStream()); if (mediaInfo.getReaderCount() == null || mediaInfo.getReaderCount() == 0) { mediaServerService.closeStreams(streamInfo.getMediaServer(), streamInfo.getApp(), streamInfo.getStream()); log.info("[录制计划] 停止, 通道ID: {}", channelId); } }catch (Exception e) { log.error("[录制计划] 停止时异常", e); }finally { recordStreamMap.remove(channelId); } } } @Override public Integer recording(String app, String stream) { for (Integer channelId : recordStreamMap.keySet()) { StreamInfo streamInfo = recordStreamMap.get(channelId); if (streamInfo != null && streamInfo.getApp().equals(app) && streamInfo.getStream().equals(stream)) { return channelId; } } return null; } @Override @Transactional public void add(RecordPlan plan) { plan.setCreateTime(DateUtil.getNow()); plan.setUpdateTime(DateUtil.getNow()); recordPlanMapper.add(plan); if (plan.getId() > 0 && !plan.getPlanItemList().isEmpty()) { for (RecordPlanItem recordPlanItem : plan.getPlanItemList()) { recordPlanItem.setPlanId(plan.getId()); } recordPlanMapper.batchAddItem(plan.getId(), plan.getPlanItemList()); } // TODO 更新录像队列 } @Override public RecordPlan get(Integer planId) { RecordPlan recordPlan = recordPlanMapper.get(planId); if (recordPlan == null) { return null; } List recordPlanItemList = recordPlanMapper.getItemList(planId); if (!recordPlanItemList.isEmpty()) { recordPlan.setPlanItemList(recordPlanItemList); } return recordPlan; } @Override @Transactional public void update(RecordPlan plan) { plan.setUpdateTime(DateUtil.getNow()); recordPlanMapper.update(plan); recordPlanMapper.cleanItems(plan.getId()); if (plan.getPlanItemList() != null && !plan.getPlanItemList().isEmpty()){ List planItemList = new ArrayList<>(); for (RecordPlanItem recordPlanItem : plan.getPlanItemList()) { if (recordPlanItem.getStart() == null || recordPlanItem.getStop() == null || recordPlanItem.getWeekDay() == null){ continue; } if (recordPlanItem.getPlanId() == null) { recordPlanItem.setPlanId(plan.getId()); } planItemList.add(recordPlanItem); } if(!planItemList.isEmpty()) { recordPlanMapper.batchAddItem(plan.getId(), planItemList); } } // TODO 更新录像队列 } @Override @Transactional public void delete(Integer planId) { RecordPlan recordPlan = recordPlanMapper.get(planId); if (recordPlan == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "录制计划不存在"); } // 清理关联的通道 channelMapper.removeRecordPlanByPlanId(recordPlan.getId()); recordPlanMapper.cleanItems(planId); recordPlanMapper.delete(planId); // TODO 更新录像队列 } @Override public PageInfo query(Integer page, Integer count, String query) { PageHelper.startPage(page, count); if (query != null) { query = query.replaceAll("/", "//") .replaceAll("%", "/%") .replaceAll("_", "/_"); } List all = recordPlanMapper.query(query); return new PageInfo<>(all); } @Override public void link(List channelIds, Integer planId) { if (channelIds == null || channelIds.isEmpty()) { log.info("[录制计划] 关联/移除关联时, 通道编号必须存在"); throw new ControllerException(ErrorCode.ERROR100.getCode(), "通道编号必须存在"); } if (planId == null) { channelMapper.removeRecordPlan(channelIds); }else { channelMapper.addRecordPlan(channelIds, planId); } // 查看当前的待录制列表是否变化,如果变化,则调用录制计划马上开始录制 execution(); } @Override public PageInfo queryChannelList(int page, int count, String query, Integer dataType, Boolean online, Integer planId, Boolean hasLink) { PageHelper.startPage(page, count); if (query != null) { query = query.replaceAll("/", "//") .replaceAll("%", "/%") .replaceAll("_", "/_"); } List all = channelMapper.queryForRecordPlanForWebList(planId, query, dataType, online, hasLink); return new PageInfo<>(all); } @Override public void linkAll(Integer planId) { channelMapper.addRecordPlanForAll(planId); } @Override public void cleanAll(Integer planId) { channelMapper.removeRecordPlanByPlanId(planId); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/impl/RoleServerImpl.java ================================================ package com.genersoft.iot.vmp.service.impl; import com.genersoft.iot.vmp.service.IRoleService; import com.genersoft.iot.vmp.storager.dao.RoleMapper; import com.genersoft.iot.vmp.storager.dao.dto.Role; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service public class RoleServerImpl implements IRoleService { @Autowired private RoleMapper roleMapper; @Override public Role getRoleById(int id) { return roleMapper.selectById(id); } @Override public int add(Role role) { return roleMapper.add(role); } @Override public int delete(int id) { return roleMapper.delete(id); } @Override public List getAll() { return roleMapper.selectAll(); } @Override public int update(Role role) { return roleMapper.update(role); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/impl/RtpServerServiceImpl.java ================================================ package com.genersoft.iot.vmp.service.impl; import com.genersoft.iot.vmp.common.enums.MediaApp; import com.genersoft.iot.vmp.conf.DynamicTask; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.gb28181.bean.OpenRTPServerResult; import com.genersoft.iot.vmp.gb28181.session.SSRCFactory; import com.genersoft.iot.vmp.gb28181.session.SipInviteSessionManager; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.event.hook.Hook; import com.genersoft.iot.vmp.media.event.hook.HookData; import com.genersoft.iot.vmp.media.event.hook.HookSubscribe; import com.genersoft.iot.vmp.media.event.hook.HookType; import com.genersoft.iot.vmp.media.event.media.MediaArrivalEvent; import com.genersoft.iot.vmp.media.event.media.MediaDepartureEvent; import com.genersoft.iot.vmp.media.service.IMediaServerService; import com.genersoft.iot.vmp.service.IReceiveRtpServerService; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.service.bean.InviteErrorCode; import com.genersoft.iot.vmp.service.bean.RTPServerParam; import com.genersoft.iot.vmp.service.bean.SSRCInfo; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.event.EventListener; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import java.util.Objects; @Slf4j @Service public class RtpServerServiceImpl implements IReceiveRtpServerService { private final static String TIMEOUT_TASK_KEY_PREFIX = "RTP_SERVER_TIMEOUT_TASK"; @Autowired private IMediaServerService mediaServerService; @Autowired private DynamicTask dynamicTask; @Autowired private SSRCFactory ssrcFactory; @Autowired private UserSetting userSetting; @Autowired private HookSubscribe subscribe; @Autowired private SipInviteSessionManager sessionManager; /** * 流到来的处理 */ @Async("taskExecutor") @org.springframework.context.event.EventListener public void onApplicationEvent(MediaArrivalEvent event) { } /** * 流离开的处理 */ @Async("taskExecutor") @EventListener public void onApplicationEvent(MediaDepartureEvent event) { } @Override public SSRCInfo openGbRTPServer(MediaServer mediaServer, String streamId, String presetSSRC, int tcpMode, boolean playback, boolean ssrcCheck, boolean onlyAuto, boolean disableAuto, ErrorCallback callback) { if (callback == null) { log.warn("[开启国标RTP收流] 失败,回调为NULL"); return null; } if (mediaServer == null) { log.warn("[开启国标RTP收流] 失败,媒体节点为NULL"); return null; } // 获取 mediaServer 可用的 ssrc final String ssrc; if (presetSSRC != null) { ssrc = presetSSRC; }else { if (playback) { ssrc = ssrcFactory.getPlayBackSsrc(mediaServer.getId()); }else { ssrc = ssrcFactory.getPlaySsrc(mediaServer.getId()); } } if (streamId == null) { streamId = String.format("%08x", Long.parseLong(ssrc)).toUpperCase(); } if (ssrcCheck && tcpMode > 0) { // 目前zlm不支持 tcp模式更新ssrc,暂时关闭ssrc校验 log.warn("[openRTPServer] 平台对接时下级可能自定义ssrc,但是tcp模式zlm收流目前无法更新ssrc,可能收流超时,此时请使用udp收流或者关闭ssrc校验"); } SSRCInfo ssrcInfo = new SSRCInfo(0, ssrc, MediaApp.GB28181, streamId); RTPServerParam rtpServerParam = new RTPServerParam(mediaServer, MediaApp.GB28181, streamId, ssrcCheck ? Long.parseLong(ssrc): 0L, null, onlyAuto, disableAuto, false, tcpMode); int rtpServerPort = openRTPServer(rtpServerParam, ((code, msg, data) -> { if (code == InviteErrorCode.SUCCESS.getCode()) { OpenRTPServerResult openRTPServerResult = new OpenRTPServerResult(); openRTPServerResult.setHookData(data); openRTPServerResult.setSsrcInfo(ssrcInfo); callback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), openRTPServerResult); } else { // 释放ssrc if (presetSSRC == null) { ssrcFactory.releaseSsrc(mediaServer.getId(), ssrc); } OpenRTPServerResult openRTPServerResult = new OpenRTPServerResult(); openRTPServerResult.setSsrcInfo(ssrcInfo); callback.run(code, msg, openRTPServerResult); } })); ssrcInfo.setPort(rtpServerPort); return new SSRCInfo(rtpServerPort, ssrc, MediaApp.GB28181, streamId); } @Override public int openRTPServer(RTPServerParam rtpServerParam, ErrorCallback callback) { if (callback == null) { log.warn("[开启RTP收流] 失败,回调为NULL"); return -1; } if (rtpServerParam.getMediaServer() == null) { log.warn("[开启RTP收流] 失败,媒体节点为NULL"); return -1; } // 设置流超时的定时任务 String timeOutTaskKey = String.format("%s_%s_%s_%s", TIMEOUT_TASK_KEY_PREFIX, rtpServerParam.getMediaServer().getId(), rtpServerParam.getApp(), rtpServerParam.getStreamId()); Hook rtpHook = Hook.getInstance(HookType.on_media_arrival, rtpServerParam.getApp(), rtpServerParam.getStreamId(), rtpServerParam.getMediaServer().getId()); dynamicTask.startDelay(timeOutTaskKey, () -> { // 收流超时 // 关闭收流端口 mediaServerService.closeRTPServer(rtpServerParam.getMediaServer(), rtpServerParam.getApp(), rtpServerParam.getStreamId()); subscribe.removeSubscribe(rtpHook); callback.run(InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getCode(), InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getMsg(), null); }, userSetting.getPlayTimeout()); // 开启流到来的监听 subscribe.addSubscribe(rtpHook, (hookData) -> { dynamicTask.stop(timeOutTaskKey); // hook响应 callback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), hookData); subscribe.removeSubscribe(rtpHook); }); int rtpServerPort; if (rtpServerParam.getMediaServer().isRtpEnable()) { rtpServerPort = mediaServerService.createRTPServer(rtpServerParam.getMediaServer(), rtpServerParam.getApp(), rtpServerParam.getStreamId(), Objects.requireNonNullElse(rtpServerParam.getSsrc(), 0L), rtpServerParam.getPort(), rtpServerParam.isOnlyAuto(), rtpServerParam.isDisableAudio(), rtpServerParam.isReUsePort(), rtpServerParam.getTcpMode()); } else { rtpServerPort = rtpServerParam.getMediaServer().getRtpProxyPort(); } if (rtpServerPort == 0) { callback.run(InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(), "开启RTPServer失败", null); return -1; } return rtpServerPort; } @Override public void closeRTPServer(MediaServer mediaServer, String app, String stream) { if (mediaServer == null) { return; } String timeOutTaskKey = String.format("%s_%s_%s_%s", TIMEOUT_TASK_KEY_PREFIX, mediaServer.getId(), app, stream); if (dynamicTask.contains(timeOutTaskKey)) { dynamicTask.stop(timeOutTaskKey); } mediaServerService.closeRTPServer(mediaServer, app, stream); } @Override public void closeRTPServerByMediaServerId(String mediaServerId, String app, String stream) { MediaServer mediaServer = mediaServerService.getOne(mediaServerId); if (mediaServer == null) { return; } closeRTPServer(mediaServer, app, stream); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/impl/SendRtpServerServiceImpl.java ================================================ package com.genersoft.iot.vmp.service.impl; import com.genersoft.iot.vmp.common.VideoManagerConstants; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.gb28181.bean.PlayException; import com.genersoft.iot.vmp.gb28181.bean.SendRtpInfo; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.service.ISendRtpServerService; import com.genersoft.iot.vmp.utils.JsonUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.math.NumberUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.support.atomic.RedisAtomicInteger; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; @Service @Slf4j public class SendRtpServerServiceImpl implements ISendRtpServerService { @Autowired private UserSetting userSetting; @Autowired private RedisTemplate redisTemplate; @Override public SendRtpInfo createSendRtpInfo(MediaServer mediaServer, String ip, Integer port, String ssrc, String requesterId, String deviceId, Integer channelId, Boolean isTcp, Boolean rtcp) { int localPort = getNextPort(mediaServer); if (localPort <= 0) { return null; } return SendRtpInfo.getInstance(localPort, mediaServer, ip, port, ssrc, deviceId, null, channelId, isTcp, rtcp, userSetting.getServerId()); } @Override public SendRtpInfo createSendRtpInfo(MediaServer mediaServer, String ip, Integer port, String ssrc, String platformId, String app, String stream, Integer channelId, Boolean tcp, Boolean rtcp){ int localPort = getNextPort(mediaServer); if (localPort <= 0) { throw new PlayException(javax.sip.message.Response.SERVER_INTERNAL_ERROR, "server internal error"); } SendRtpInfo sendRtpInfo = SendRtpInfo.getInstance(localPort, mediaServer, ip, port, ssrc, null, platformId, channelId, tcp, rtcp, userSetting.getServerId()); if (sendRtpInfo == null) { return null; } sendRtpInfo.setApp(app); sendRtpInfo.setStream(stream); return sendRtpInfo; } @Override public void update(SendRtpInfo sendRtpItem) { redisTemplate.opsForHash().put(VideoManagerConstants.SEND_RTP_INFO_CALLID, sendRtpItem.getCallId(), sendRtpItem); redisTemplate.opsForHash().put(VideoManagerConstants.SEND_RTP_INFO_STREAM + sendRtpItem.getStream(), sendRtpItem.getTargetId(), sendRtpItem); redisTemplate.opsForHash().put(VideoManagerConstants.SEND_RTP_INFO_CHANNEL + sendRtpItem.getChannelId(), sendRtpItem.getTargetId(), sendRtpItem); } @Override public SendRtpInfo queryByChannelId(Integer channelId, String targetId) { String key = VideoManagerConstants.SEND_RTP_INFO_CHANNEL + channelId; return JsonUtil.redisHashJsonToObject(redisTemplate, key, targetId, SendRtpInfo.class); } @Override public SendRtpInfo queryByCallId(String callId) { String key = VideoManagerConstants.SEND_RTP_INFO_CALLID; return (SendRtpInfo)redisTemplate.opsForHash().get(key, callId); } @Override public SendRtpInfo queryByStream(String stream, String targetId) { String key = VideoManagerConstants.SEND_RTP_INFO_STREAM + stream; return JsonUtil.redisHashJsonToObject(redisTemplate, key, targetId, SendRtpInfo.class); } @Override public List queryByStream(String stream) { String key = VideoManagerConstants.SEND_RTP_INFO_STREAM + stream; List values = redisTemplate.opsForHash().values(key); List result= new ArrayList<>(); for (Object o : values) { result.add((SendRtpInfo) o); } return result; } /** * 删除RTP推送信息缓存 */ @Override public void delete(SendRtpInfo sendRtpInfo) { if (sendRtpInfo == null) { return; } redisTemplate.opsForHash().delete(VideoManagerConstants.SEND_RTP_INFO_CALLID, sendRtpInfo.getCallId()); redisTemplate.opsForHash().delete(VideoManagerConstants.SEND_RTP_INFO_STREAM + sendRtpInfo.getStream(), sendRtpInfo.getTargetId()); redisTemplate.opsForHash().delete(VideoManagerConstants.SEND_RTP_INFO_CHANNEL + sendRtpInfo.getChannelId(), sendRtpInfo.getTargetId()); } @Override public void deleteByCallId(String callId) { SendRtpInfo sendRtpInfo = queryByCallId(callId); if (sendRtpInfo == null) { return; } delete(sendRtpInfo); } @Override public void deleteByStream(String stream, String targetId) { SendRtpInfo sendRtpInfo = queryByStream(stream, targetId); if (sendRtpInfo == null) { return; } delete(sendRtpInfo); } @Override public void deleteByStream(String stream) { List sendRtpInfos = queryByStream(stream); for (SendRtpInfo sendRtpInfo : sendRtpInfos) { delete(sendRtpInfo); } } @Override public void deleteByChannel(Integer channelId, String targetId) { SendRtpInfo sendRtpInfo = queryByChannelId(channelId, targetId); if (sendRtpInfo == null) { return; } delete(sendRtpInfo); } @Override public List queryByChannelId(int channelId) { String key = VideoManagerConstants.SEND_RTP_INFO_CHANNEL + channelId; List values = redisTemplate.opsForHash().values(key); List result= new ArrayList<>(); for (Object o : values) { result.add((SendRtpInfo) o); } return result; } @Override public List queryAll() { String key = VideoManagerConstants.SEND_RTP_INFO_CALLID; List values = redisTemplate.opsForHash().values(key); List result= new ArrayList<>(); for (Object o : values) { result.add((SendRtpInfo) o); } return result; } /** * 查询某个通道是否存在上级点播(RTP推送) */ @Override public boolean isChannelSendingRTP(Integer channelId) { List sendRtpInfoList = queryByChannelId(channelId); return !sendRtpInfoList.isEmpty(); } @Override public List queryForPlatform(String platformId) { List sendRtpInfos = queryAll(); if (!sendRtpInfos.isEmpty()) { sendRtpInfos.removeIf(sendRtpInfo -> !sendRtpInfo.isSendToPlatform() || !sendRtpInfo.getTargetId().equals(platformId)); } return sendRtpInfos; } private Set getAllSendRtpPort() { String key = VideoManagerConstants.SEND_RTP_INFO_CALLID; List values = redisTemplate.opsForHash().values(key); Set result = new HashSet<>(); for (Object value : values) { SendRtpInfo sendRtpInfo = (SendRtpInfo) value; result.add(sendRtpInfo.getPort()); } return result; } @Override public synchronized int getNextPort(MediaServer mediaServer) { if (mediaServer == null) { log.warn("[发送端口管理] 参数错误,mediaServer为NULL"); return -1; } String sendIndexKey = VideoManagerConstants.SEND_RTP_PORT + userSetting.getServerId() + ":" + mediaServer.getId(); Set sendRtpSet = getAllSendRtpPort(); String sendRtpPortRange = mediaServer.getSendRtpPortRange(); int startPort; int endPort; if (sendRtpPortRange != null) { String[] portArray = sendRtpPortRange.split(","); if (portArray.length != 2 || !NumberUtils.isParsable(portArray[0]) || !NumberUtils.isParsable(portArray[1])) { log.warn("{}发送端口配置格式错误,自动使用50000-60000作为端口范围", mediaServer.getId()); startPort = 50000; endPort = 60000; }else { if ( Integer.parseInt(portArray[1]) - Integer.parseInt(portArray[0]) < 1) { log.warn("{}发送端口配置错误,结束端口至少比开始端口大一,自动使用50000-60000作为端口范围", mediaServer.getId()); startPort = 50000; endPort = 60000; }else { startPort = Integer.parseInt(portArray[0]); endPort = Integer.parseInt(portArray[1]); } } }else { log.warn("{}未设置发送端口默认值,自动使用50000-60000作为端口范围", mediaServer.getId()); startPort = 50000; endPort = 60000; } if (redisTemplate == null || redisTemplate.getConnectionFactory() == null) { log.warn("{}获取redis连接信息失败", mediaServer.getId()); return -1; } RedisAtomicInteger redisAtomicInteger = new RedisAtomicInteger(sendIndexKey , redisTemplate.getConnectionFactory()); if (redisAtomicInteger.get() < startPort) { redisAtomicInteger.set(startPort); return startPort; }else { for (int i = 0; i < endPort - startPort; i++) { int port = redisAtomicInteger.getAndIncrement(); if (port > endPort) { redisAtomicInteger.set(startPort); if (sendRtpSet.contains(startPort)) { continue; }else { return startPort; } } if (!sendRtpSet.contains(port)) { return port; } } } log.warn("{}获取发送端口失败, 无可用端口", mediaServer.getId()); return -1; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/impl/UserApiKeyServiceImpl.java ================================================ package com.genersoft.iot.vmp.service.impl; import com.genersoft.iot.vmp.service.IUserApiKeyService; import com.genersoft.iot.vmp.storager.dao.UserApiKeyMapper; import com.genersoft.iot.vmp.storager.dao.dto.UserApiKey; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import java.util.List; @Service public class UserApiKeyServiceImpl implements IUserApiKeyService { @Autowired UserApiKeyMapper userApiKeyMapper; @Autowired private RedisTemplate redisTemplate; @Override public int addApiKey(UserApiKey userApiKey) { return userApiKeyMapper.add(userApiKey); } @Override public boolean isApiKeyExists(String apiKey) { return userApiKeyMapper.isApiKeyExists(apiKey); } @Override public PageInfo getUserApiKeys(int page, int count) { PageHelper.startPage(page, count); List userApiKeys = userApiKeyMapper.getUserApiKeys(); return new PageInfo<>(userApiKeys); } @Cacheable(cacheNames = "userApiKey", key = "#id", sync = true) @Override public UserApiKey getUserApiKeyById(Integer id) { return userApiKeyMapper.selectById(id); } @CacheEvict(cacheNames = "userApiKey", key = "#id") @Override public int enable(Integer id) { return userApiKeyMapper.enable(id); } @CacheEvict(cacheNames = "userApiKey", key = "#id") @Override public int disable(Integer id) { return userApiKeyMapper.disable(id); } @CacheEvict(cacheNames = "userApiKey", key = "#id") @Override public int remark(Integer id, String remark) { return userApiKeyMapper.remark(id, remark); } @CacheEvict(cacheNames = "userApiKey", key = "#id") @Override public int delete(Integer id) { return userApiKeyMapper.delete(id); } @CacheEvict(cacheNames = "userApiKey", key = "#id") @Override public int reset(Integer id, String apiKey) { return userApiKeyMapper.apiKey(id, apiKey); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/impl/UserServiceImpl.java ================================================ package com.genersoft.iot.vmp.service.impl; import com.genersoft.iot.vmp.service.IUserService; import com.genersoft.iot.vmp.storager.dao.UserMapper; import com.genersoft.iot.vmp.storager.dao.dto.User; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.DigestUtils; import java.util.List; @Service public class UserServiceImpl implements IUserService { @Autowired private UserMapper userMapper; @Override public User getUser(String username, String password) { return userMapper.select(username, password); } @Override public boolean changePassword(int id, String password) { User user = userMapper.selectById(id); user.setPassword(password); return userMapper.update(user) > 0; } @Override public User getUserById(int id) { return userMapper.selectById(id); } @Override public User getUserByUsername(String username) { return userMapper.getUserByUsername(username); } @Override public int addUser(User user) { User userByUsername = userMapper.getUserByUsername(user.getUsername()); if (userByUsername != null) { return 0; } return userMapper.add(user); } @Override public int deleteUser(int id) { return userMapper.delete(id); } @Override public List getAllUsers() { return userMapper.selectAll(); } @Override public int updateUsers(User user) { return userMapper.update(user); } @Override public boolean checkPushAuthority(String callId, String sign) { List users = userMapper.getUsers(); if (users.size() == 0) { return false; } for (User user : users) { if (user.getPushKey() == null) { continue; } String checkStr = callId == null? user.getPushKey():(callId + "_" + user.getPushKey()) ; String checkSign = DigestUtils.md5DigestAsHex(checkStr.getBytes()); if (checkSign.equals(sign)) { return true; } } return false; } @Override public PageInfo getUsers(int page, int count) { PageHelper.startPage(page, count); List users = userMapper.getUsers(); return new PageInfo<>(users); } @Override public int changePushKey(int id, String pushKey) { return userMapper.changePushKey(id,pushKey); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/redisMsg/IRedisRpcPlayService.java ================================================ package com.genersoft.iot.vmp.service.redisMsg; import com.genersoft.iot.vmp.common.InviteSessionType; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.gb28181.bean.RecordInfo; import com.genersoft.iot.vmp.service.bean.DownloadFileInfo; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.vmanager.bean.AudioBroadcastResult; public interface IRedisRpcPlayService { void play(String serverId, Integer channelId, ErrorCallback callback); void stop(String serverId, InviteSessionType type, int channelId, String stream); void playback(String serverId, Integer channelId, String startTime, String endTime, ErrorCallback callback); void playbackPause(String serverId, String streamId); void playbackResume(String serverId, String streamId); void download(String serverId, Integer channelId, String startTime, String endTime, int downloadSpeed, ErrorCallback callback); void queryRecordInfo(String serverId, Integer channelId, String startTime, String endTime, ErrorCallback callback); String frontEndCommand(String serverId, Integer channelId, int cmdCode, int parameter1, int parameter2, int combindCode2); void playPush(String serverId, Integer id, ErrorCallback callback); void playProxy(String serverId, int id, ErrorCallback callback); void stopProxy(String serverId, int id); DownloadFileInfo getRecordPlayUrl(String serverId, Integer recordId); AudioBroadcastResult audioBroadcast(String serverId, String deviceId, String channelDeviceId, Boolean broadcastMode); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/redisMsg/IRedisRpcService.java ================================================ package com.genersoft.iot.vmp.service.redisMsg; import com.genersoft.iot.vmp.common.CommonCallback; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.controller.bean.ChannelListForRpcParam; import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import java.util.List; public interface IRedisRpcService { SendRtpInfo getSendRtpItem(String callId); WVPResult startSendRtp(String callId, SendRtpInfo sendRtpItem); WVPResult stopSendRtp(String callId); long waitePushStreamOnline(SendRtpInfo sendRtpItem, CommonCallback callback); void stopWaitePushStreamOnline(SendRtpInfo sendRtpItem); void rtpSendStopped(String callId); void removeCallback(long key); long onStreamOnlineEvent(String app, String stream, CommonCallback callback); void unPushStreamOnlineEvent(String app, String stream); void subscribeCatalog(int id, int cycle); void subscribeMobilePosition(int id, int cycle, int interval); boolean updatePlatform(String serverId, Platform platform); boolean deletePlatform(String serverId, Integer platformId); int addPlatformChannelList(String serverGBId, ChannelListForRpcParam channelListForRpcParam); int removeAllPlatformChannel(String serverId, Integer platformId); int removePlatformChannelList(String serverId, ChannelListForRpcParam channelListForRpcParam); boolean updateCustomPlatformChannel(String serverId, PlatformChannel channel); boolean pushPlatformChannel(String serverId, Integer platformId); void catalogEventPublish(String serverId, CatalogEvent catalogEvent); WVPResult devicesSync(String serverId, String deviceId); SyncStatus getChannelSyncStatus(String serverId, String deviceId); WVPResult deviceBasicConfig(String serverId, Device device, BasicParam basicParam); WVPResult deviceConfigQuery(String serverId, Device device, String channelId, String configType); void teleboot(String serverId, Device device); WVPResult recordControl(String serverId, Device device, String channelId, String recordCmdStr); WVPResult guard(String serverId, Device device, String guardCmdStr); WVPResult resetAlarm(String serverId, Device device, String channelId, String alarmMethod, String alarmType); void iFrame(String serverId, Device device, String channelId); WVPResult homePosition(String serverId, Device device, String channelId, Boolean enabled, Integer resetTime, Integer presetIndex); void dragZoomIn(String serverId, Device device, String channelId, int length, int width, int midpointx, int midpointy, int lengthx, int lengthy); void dragZoomOut(String serverId, Device device, String channelId, int length, int width, int midpointx, int midpointy, int lengthx, int lengthy); WVPResult deviceStatus(String serverId, Device device); WVPResult alarm(String serverId, Device device, String startPriority, String endPriority, String alarmMethod, String alarmType, String startTime, String endTime); WVPResult deviceInfo(String serverId, Device device); WVPResult> queryPreset(String serverId, Device device, String channelId); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisAlarmMsgListener.java ================================================ package com.genersoft.iot.vmp.service.redisMsg; import com.alibaba.fastjson2.JSON; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.gb28181.bean.AlarmChannelMessage; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm; import com.genersoft.iot.vmp.gb28181.bean.Platform; import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService; import com.genersoft.iot.vmp.gb28181.service.IDeviceService; import com.genersoft.iot.vmp.gb28181.service.IPlatformChannelService; import com.genersoft.iot.vmp.gb28181.service.IPlatformService; import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander; import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; import com.genersoft.iot.vmp.service.IMobilePositionService; import com.genersoft.iot.vmp.utils.DateUtil; import jakarta.validation.constraints.NotNull; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.connection.Message; import org.springframework.data.redis.connection.MessageListener; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; import javax.sip.InvalidArgumentException; import javax.sip.SipException; import java.text.ParseException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; /** * 监听 SUBSCRIBE alarm_receive * 发布 PUBLISH alarm_receive '{ "gbId": "", "alarmSn": 1, "alarmType": "111", "alarmDescription": "222", }' */ @Slf4j @Component public class RedisAlarmMsgListener implements MessageListener { @Autowired private ISIPCommander commander; @Autowired private ISIPCommanderForPlatform commanderForPlatform; @Autowired private IDeviceService deviceService; @Autowired private IDeviceChannelService channelService; @Autowired private IMobilePositionService mobilePositionService; @Autowired private IPlatformService platformService; @Autowired private IPlatformChannelService platformChannelService; private final ConcurrentLinkedQueue taskQueue = new ConcurrentLinkedQueue<>(); @Autowired private UserSetting userSetting; @Override public void onMessage(@NotNull Message message, byte[] bytes) { log.info("[REDIS: ALARM]: {}", new String(message.getBody())); taskQueue.offer(message); } @Scheduled(fixedDelay = 100) public void executeTaskQueue() { if (taskQueue.isEmpty()) { return; } List messageDataList = new ArrayList<>(); int size = taskQueue.size(); for (int i = 0; i < size; i++) { Message msg = taskQueue.poll(); if (msg != null) { messageDataList.add(msg); } } if (messageDataList.isEmpty()) { return; } for (Message msg : messageDataList) { try { AlarmChannelMessage alarmChannelMessage = JSON.parseObject(msg.getBody(), AlarmChannelMessage.class); if (alarmChannelMessage == null) { log.warn("[REDIS的ALARM通知]消息解析失败"); continue; } String chanelId = alarmChannelMessage.getGbId(); DeviceAlarm deviceAlarm = new DeviceAlarm(); deviceAlarm.setCreateTime(DateUtil.getNow()); deviceAlarm.setChannelId(chanelId); deviceAlarm.setAlarmDescription(alarmChannelMessage.getAlarmDescription()); deviceAlarm.setAlarmMethod("" + alarmChannelMessage.getAlarmSn()); deviceAlarm.setAlarmType("" + alarmChannelMessage.getAlarmType()); deviceAlarm.setAlarmPriority("1"); deviceAlarm.setAlarmTime(DateUtil.getNow()); deviceAlarm.setLongitude(0); deviceAlarm.setLatitude(0); if (ObjectUtils.isEmpty(chanelId)) { if (userSetting.getSendToPlatformsWhenIdLost()) { // 发送给所有的上级 List parentPlatforms = platformService.queryEnablePlatformList(userSetting.getServerId()); if (!parentPlatforms.isEmpty()) { for (Platform parentPlatform : parentPlatforms) { try { deviceAlarm.setChannelId(parentPlatform.getDeviceGBId()); commanderForPlatform.sendAlarmMessage(parentPlatform, deviceAlarm); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 国标级联 发送报警: {}", e.getMessage()); } } } } else { // 获取开启了消息推送的设备和平台 List parentPlatforms = mobilePositionService.queryEnablePlatformListWithAsMessageChannel(); if (!parentPlatforms.isEmpty()) { for (Platform parentPlatform : parentPlatforms) { try { deviceAlarm.setChannelId(parentPlatform.getDeviceGBId()); commanderForPlatform.sendAlarmMessage(parentPlatform, deviceAlarm); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 国标级联 发送报警: {}", e.getMessage()); } } } } // 获取开启了消息推送的设备和平台 List devices = channelService.queryDeviceWithAsMessageChannel(); if (!devices.isEmpty()) { for (Device device : devices) { try { deviceAlarm.setChannelId(device.getDeviceId()); commander.sendAlarmMessage(device, deviceAlarm); } catch (InvalidArgumentException | SipException | ParseException e) { log.error("[命令发送失败] 发送报警: {}", e.getMessage()); } } } } else { // 获取该通道ID是属于设备还是对应的上级平台 Device device = deviceService.getDeviceBySourceChannelDeviceId(chanelId); List platforms = platformChannelService.queryByPlatformBySharChannelId(chanelId); if (device != null && device.getServerId().equals(userSetting.getServerId()) && (platforms == null || platforms.isEmpty())) { try { commander.sendAlarmMessage(device, deviceAlarm); } catch (InvalidArgumentException | SipException | ParseException e) { log.error("[命令发送失败] 发送报警: {}", e.getMessage()); } } else if (device == null && (platforms != null && !platforms.isEmpty() )) { for (Platform platform : platforms) { if (platform.getServerId().equals(userSetting.getServerId())) { try { commanderForPlatform.sendAlarmMessage(platform, deviceAlarm); } catch (InvalidArgumentException | SipException | ParseException e) { log.error("[命令发送失败] 发送报警: {}", e.getMessage()); } } } } else { log.warn("[REDIS的ALARM通知] 未查询到" + chanelId + "所属的平台或设备"); } } } catch (Exception e) { log.error("未处理的异常 ", e); log.warn("[REDIS的ALARM通知] 发现未处理的异常, {}", e.getMessage()); } } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisCloseStreamMsgListener.java ================================================ package com.genersoft.iot.vmp.service.redisMsg; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.streamPush.service.IStreamPushService; import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.connection.Message; import org.springframework.data.redis.connection.MessageListener; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; /** * 接收来自redis的关闭流更新通知 * 消息举例: PUBLISH VM_MSG_STREAM_PUSH_CLOSE "{'app': 'live', 'stream': 'stream'}" * @author lin */ @Slf4j @Component public class RedisCloseStreamMsgListener implements MessageListener { @Autowired private IStreamPushService pushService; private final ConcurrentLinkedQueue taskQueue = new ConcurrentLinkedQueue<>(); @Override public void onMessage(@NotNull Message message, byte[] bytes) { log.info("[REDIS: 关闭流]: {}", new String(message.getBody())); taskQueue.offer(message); } @Scheduled(fixedDelay = 100) public void executeTaskQueue() { if (taskQueue.isEmpty()) { return; } List messageDataList = new ArrayList<>(); int size = taskQueue.size(); for (int i = 0; i < size; i++) { Message msg = taskQueue.poll(); if (msg != null) { messageDataList.add(msg); } } if (messageDataList.isEmpty()) { return; } for (Message msg : messageDataList) { try { JSONObject jsonObject = JSON.parseObject(msg.getBody()); String app = jsonObject.getString("app"); String stream = jsonObject.getString("stream"); pushService.stopByAppAndStream(app, stream); }catch (Exception e) { log.warn("[REDIS的关闭推流通知] 发现未处理的异常, \r\n{}", JSON.toJSONString(msg)); log.error("[REDIS的关闭推流通知] 异常内容: ", e); } } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisGpsMsgListener.java ================================================ package com.genersoft.iot.vmp.service.redisMsg; import com.alibaba.fastjson2.JSON; import com.genersoft.iot.vmp.gb28181.service.IGbChannelService; import com.genersoft.iot.vmp.service.bean.GPSMsgInfo; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.streamPush.service.IStreamPushService; import com.genersoft.iot.vmp.utils.DateUtil; import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.connection.Message; import org.springframework.data.redis.connection.MessageListener; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; /** * 接收来自redis的GPS更新通知 * * @author lin * 监听: SUBSCRIBE VM_MSG_GPS * 发布 PUBLISH VM_MSG_GPS '{"messageId":"1727228507555","id":"24212345671381000047","lng":116.30307666666667,"lat":40.03295833333333,"time":"2024-09-25T09:41:47","direction":"56.0","speed":0.0,"altitude":60.0,"unitNo":"100000000","memberNo":"10000047"}' */ @Slf4j @Component public class RedisGpsMsgListener implements MessageListener { @Autowired private IRedisCatchStorage redisCatchStorage; @Autowired private IStreamPushService streamPushService; @Autowired private IGbChannelService channelService; private final ConcurrentLinkedQueue taskQueue = new ConcurrentLinkedQueue<>(); @Override public void onMessage(@NotNull Message message, byte[] bytes) { log.debug("[REDIS: GPS]: {}", new String(message.getBody())); taskQueue.offer(message); } @Scheduled(fixedDelay = 200, timeUnit = TimeUnit.MILLISECONDS) //每400毫秒执行一次 public void executeTaskQueue() { if (taskQueue.isEmpty()) { return; } List messageDataList = new ArrayList<>(); int size = taskQueue.size(); for (int i = 0; i < size; i++) { Message msg = taskQueue.poll(); if (msg != null) { messageDataList.add(msg); } } if (messageDataList.isEmpty()) { return; } for (Message msg : messageDataList) { try { GPSMsgInfo gpsMsgInfo = JSON.parseObject(msg.getBody(), GPSMsgInfo.class); gpsMsgInfo.setStored(false); gpsMsgInfo.setTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(gpsMsgInfo.getTime())); log.debug("[REDIS的位置变化通知], {}", JSON.toJSONString(gpsMsgInfo)); // 只是放入redis缓存起来 redisCatchStorage.updateGpsMsgInfo(gpsMsgInfo); } catch (Exception e) { log.warn("[REDIS的位置变化通知] 发现未处理的异常, \r\n{}", JSON.toJSONString(msg)); log.error("[REDIS的位置变化通知] 异常内容: ", e); } } } /** * 定时将经纬度更新到数据库 */ @Scheduled(fixedDelay = 2, timeUnit = TimeUnit.SECONDS) //每2秒执行一次 public void execute() { // 需要查询到 List gpsMsgInfoList = redisCatchStorage.getAllGpsMsgInfo(); if (!gpsMsgInfoList.isEmpty()) { gpsMsgInfoList = gpsMsgInfoList.stream().filter(gpsMsgInfo -> !gpsMsgInfo.isStored()).collect(Collectors.toList());; if (!gpsMsgInfoList.isEmpty()) { channelService.updateGPSFromGPSMsgInfo(gpsMsgInfoList); for (GPSMsgInfo msgInfo : gpsMsgInfoList) { msgInfo.setStored(true); redisCatchStorage.updateGpsMsgInfo(msgInfo); } } } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisGroupChangeListener.java ================================================ package com.genersoft.iot.vmp.service.redisMsg; import com.alibaba.fastjson2.JSON; import com.genersoft.iot.vmp.common.VideoManagerConstants; import com.genersoft.iot.vmp.conf.SipConfig; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.gb28181.bean.Group; import com.genersoft.iot.vmp.gb28181.bean.RedisGroupMessage; import com.genersoft.iot.vmp.gb28181.service.IGroupService; import com.genersoft.iot.vmp.streamPush.service.IStreamPushService; import com.genersoft.iot.vmp.utils.DateUtil; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.RandomStringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.connection.Message; import org.springframework.data.redis.connection.MessageListener; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; /** * @Auther: JiangFeng * @Date: 2022/8/16 11:32 * @Description: 接收redis发送的推流设备列表更新通知 * 监听: SUBSCRIBE VM_MSG_GROUP_LIST_CHANGE * 发布 PUBLISH VM_MSG_GROUP_LIST_CHANGE '[{"groupName":"测试域修改新","topGroupGAlias":3,"messageType":"update","groupAlias":3}]' */ @Slf4j @Component public class RedisGroupChangeListener implements MessageListener { @Resource private IGroupService groupService; @Resource private IStreamPushService streamPushService; @Autowired private UserSetting userSetting; @Autowired private SipConfig sipConfig; private final ConcurrentLinkedQueue taskQueue = new ConcurrentLinkedQueue<>(); @Override public void onMessage(Message message, byte[] bytes) { log.info("[REDIS-分组信息改变] key: {}, : {}", VideoManagerConstants.VM_MSG_GROUP_LIST_CHANGE, new String(message.getBody())); taskQueue.offer(message); } @Scheduled(fixedDelay = 100) public void executeTaskQueue() { if (taskQueue.isEmpty()) { return; } List messageDataList = new ArrayList<>(); int size = taskQueue.size(); for (int i = 0; i < size; i++) { Message msg = taskQueue.poll(); if (msg != null) { messageDataList.add(msg); } } if (messageDataList.isEmpty()) { return; } for (Message msg : messageDataList) { try { List groupMessages = JSON.parseArray(new String(msg.getBody()), RedisGroupMessage.class); for (int i = 0; i < groupMessages.size(); i++) { RedisGroupMessage groupMessage = groupMessages.get(i); log.info("[REDIS消息-分组信息更新] {}", groupMessage.toString()); Group group = groupService.queryGroupByAlias(groupMessage.getGroupAlias()); switch (groupMessage.getMessageType()){ case "add": // 此处使用别名作为判断依据,别名此处常常是分组在第三方系统里的唯一ID if (groupMessage.getGroupAlias() == null || ObjectUtils.isEmpty(groupMessage.getGroupName()) || ObjectUtils.isEmpty(groupMessage.getTopGroupGAlias())) { log.info("[REDIS消息-分组信息新增] 消息关键字段缺失, {}", groupMessage.toString()); continue; } if (group != null) { log.info("[REDIS消息-分组信息新增] 失败 {},别名已经存在", groupMessage.getGroupAlias()); continue; } group = new Group(); boolean isTop = groupMessage.getTopGroupGAlias().equals(groupMessage.getGroupAlias()); String deviceId = buildGroupDeviceId(isTop); group.setDeviceId(deviceId); group.setAlias(groupMessage.getGroupAlias()); group.setName(groupMessage.getGroupName()); if (!isTop) { if (ObjectUtils.isEmpty(groupMessage.getTopGroupGAlias()) ) { log.info("[REDIS消息-分组信息新增] 消息缺失业务分组别名或者父节点别名, {}", groupMessage.toString()); continue; } Group topGroup = groupService.queryGroupByAlias(groupMessage.getTopGroupGAlias()); if (topGroup == null) { log.info("[REDIS消息-分组信息新增] 业务分组信息未入库, {}", groupMessage.toString()); continue; } group.setBusinessGroup(topGroup.getDeviceId()); group.setParentId(topGroup.getId()); } if (groupMessage.getParentGAlias() != null) { Group parentGroup = groupService.queryGroupByAlias(groupMessage.getParentGAlias()); if (parentGroup == null) { log.info("[REDIS消息-分组信息新增] 虚拟组织父节点信息未入库, {}", groupMessage.toString()); continue; } group.setParentId(parentGroup.getId()); group.setParentDeviceId(parentGroup.getDeviceId()); } group.setCreateTime(DateUtil.getNow()); group.setUpdateTime(DateUtil.getNow()); groupService.add(group); break; case "update": // 此处使用别名作为判断依据,别名此处常常是分组在第三方系统里的唯一ID if (groupMessage.getGroupAlias() == null) { log.info("[REDIS消息-分组信息更新] 消息关键字段缺失, {}", groupMessage.toString()); continue; } if (group == null ) { log.info("[REDIS消息-分组信息更新] 失败 {},别名不存在", groupMessage.getGroupAlias()); continue; } group.setName(groupMessage.getGroupName()); group.setUpdateTime(DateUtil.getNow()); if (groupMessage.getParentGAlias() != null) { Group parentGroup = groupService.queryGroupByAlias(groupMessage.getParentGAlias()); if (parentGroup == null) { log.info("[REDIS消息-分组信息更新] 虚拟组织父节点信息未入库, {}", groupMessage.toString()); continue; } group.setParentId(parentGroup.getId()); group.setParentDeviceId(parentGroup.getDeviceId()); }else { Group businessGroup = groupService.queryGroupByDeviceId(group.getBusinessGroup()); if (businessGroup == null ) { log.info("[REDIS消息-分组信息更新] 失败 {},业务分组不存在", groupMessage.getGroupAlias()); continue; } group.setParentId(businessGroup.getId()); group.setParentDeviceId(null); } groupService.update(group); break; case "delete": // 此处使用别名作为判断依据,别名此处常常是分组在第三方系统里的唯一ID if (groupMessage.getGroupAlias() == null) { log.info("[REDIS消息-分组信息删除] 消息关键字段缺失, {}", groupMessage.toString()); continue; } if (group == null) { log.info("[REDIS消息-分组信息删除] 失败 {},别名不存在", groupMessage.getGroupAlias()); continue; } groupService.delete(group.getId()); break; default: log.info("[REDIS消息-分组信息改变] 未识别的消息类型 {},目前支持的消息类型为 add、update、delete", groupMessage.getMessageType()); } } } catch (Exception e) { log.warn("[REDIS消息-业务分组同步回复] 发现未处理的异常, \r\n{}", new String(msg.getBody())); log.error("[REDIS消息-业务分组同步回复] 异常内容: ", e); } } } /** * 生成分组国标编号 */ private String buildGroupDeviceId(boolean isTop) { try { String deviceTemplate = userSetting.getGroupSyncDeviceTemplate(); if (ObjectUtils.isEmpty(deviceTemplate) || !deviceTemplate.contains("%s")) { String domain = sipConfig.getDomain(); if (domain.length() != 10) { domain = sipConfig.getId().substring(0, 10); } deviceTemplate = domain + "%s0%s"; } String codeType = "216"; if (isTop) { codeType = "215"; } return String.format(deviceTemplate, codeType, RandomStringUtils.secureStrong().next(6, false, true)); }catch (Exception e) { log.error("[REDIS消息-业务分组同步回复] 构建新的分组编号失败", e); return null; } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisGroupMsgListener.java ================================================ package com.genersoft.iot.vmp.service.redisMsg; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.common.VideoManagerConstants; import com.genersoft.iot.vmp.conf.SipConfig; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.gb28181.bean.Group; import com.genersoft.iot.vmp.gb28181.bean.RedisGroupMessage; import com.genersoft.iot.vmp.gb28181.service.IGroupService; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.utils.DateUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.RandomStringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.connection.Message; import org.springframework.data.redis.connection.MessageListener; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentLinkedQueue; /** * 接收redis发送的推流设备列表更新通知 * 监听: SUBSCRIBE VM_MSG_GROUP_LIST_RESPONSE * 发布 PUBLISH VM_MSG_GROUP_LIST_RESPONSE '[{"groupName":"研发AAS","topGroupGAlias":"6","groupAlias":"6"},{"groupName":"测试AAS","topGroupGAlias":"5","groupAlias":"5"},{"groupName":"研发2","topGroupGAlias":"4","groupAlias":"4"},{"groupName":"啊实打实的","topGroupGAlias":"4","groupAlias":"100000009"},{"groupName":"测试域","topGroupGAlias":"3","groupAlias":"3"},{"groupName":"结构1","topGroupGAlias":"3","groupAlias":"100000000"},{"groupName":"结构1-1","topGroupGAlias":"3","parentGAlias":"100000000","groupAlias":"100000001"},{"groupName":"结构2-2","topGroupGAlias":"3","groupAlias":"100000002"},{"groupName":"结构1-2","topGroupGAlias":"3","parentGAlias":"100000001","groupAlias":"100000003"},{"groupName":"结构1-3","topGroupGAlias":"3","parentGAlias":"100000003","groupAlias":"100000004"},{"groupName":"研发1","topGroupGAlias":"3","groupAlias":"100000005"},{"groupName":"研发3","topGroupGAlias":"3","parentGAlias":"100000005","groupAlias":"100000006"},{"groupName":"测试42","topGroupGAlias":"3","parentGAlias":"100000000","groupAlias":"100000010"},{"groupName":"测试2","topGroupGAlias":"3","parentGAlias":"100000000","groupAlias":"100000011"},{"groupName":"测试3","topGroupGAlias":"3","parentGAlias":"100000000","groupAlias":"100000007"},{"groupName":"测试4","topGroupGAlias":"3","parentGAlias":"100000007","groupAlias":"100000008"}]' */ @Slf4j @Component public class RedisGroupMsgListener implements MessageListener { @Autowired private IGroupService groupService; @Autowired private IRedisCatchStorage redisCatchStorage; @Autowired private UserSetting userSetting; @Autowired private SipConfig sipConfig; private final ConcurrentLinkedQueue taskQueue = new ConcurrentLinkedQueue<>(); @Override public void onMessage(Message message, byte[] bytes) { String serverId = redisCatchStorage.chooseOneServer(null); if (!userSetting.getServerId().equals(serverId)) { return; } log.info("[REDIS: 业务分组同步回复] key: {}, : {}", VideoManagerConstants.VM_MSG_GROUP_LIST_RESPONSE, new String(message.getBody())); taskQueue.offer(message); } @Scheduled(fixedDelay = 100) public void executeTaskQueue() { if (taskQueue.isEmpty()) { return; } List messageDataList = new ArrayList<>(); int size = taskQueue.size(); for (int i = 0; i < size; i++) { Message msg = taskQueue.poll(); if (msg != null) { messageDataList.add(msg); } } if (messageDataList.isEmpty()) { log.warn("[REDIS消息-业务分组同步回复] 处理队列时发现队列为空"); return; } // 按照别名获取所有业务分组 Map aliasGroupMap = groupService.queryGroupByAliasMap(); Map aliasGroupToSave = new LinkedHashMap<>(); for (Message msg : messageDataList) { try { log.info("[REDIS消息-业务分组同步回复] 处理数据: {}", new String(msg.getBody())); List groupMessages = JSON.parseArray(new String(msg.getBody()), RedisGroupMessage.class); log.info("[REDIS消息-业务分组同步回复] 待处理数量: {}", groupMessages.size()); for (RedisGroupMessage groupMessage : groupMessages) { // 此处使用别名作为判断依据,别名此处常常是分组在第三方系统里的唯一ID if (groupMessage.getGroupAlias() == null || ObjectUtils.isEmpty(groupMessage.getGroupName()) || ObjectUtils.isEmpty(groupMessage.getTopGroupGAlias())) { log.info("[REDIS消息-业务分组同步回复] 消息关键字段缺失, {}", groupMessage.toString()); continue; } boolean isTop = groupMessage.getTopGroupGAlias().equals(groupMessage.getGroupAlias()); Group group = aliasGroupMap.get(groupMessage.getGroupAlias()); if (group == null) { group = new Group(); String deviceId = buildGroupDeviceId(isTop); group.setDeviceId(deviceId); group.setAlias(groupMessage.getGroupAlias()); group.setName(groupMessage.getGroupName()); group.setCreateTime(DateUtil.getNow()); } if (!isTop) { if (ObjectUtils.isEmpty(groupMessage.getTopGroupGAlias())) { log.info("[REDIS消息-业务分组同步回复] 消息缺失业务分组别名, {}", groupMessage.toString()); continue; } Group topGroup = aliasGroupMap.get(groupMessage.getTopGroupGAlias()); if (topGroup == null) { topGroup = aliasGroupToSave.get(groupMessage.getTopGroupGAlias()); } if (topGroup == null) { log.info("[REDIS消息-业务分组同步回复] 业务分组信息未发送或者未首先发送, {}", groupMessage.toString()); continue; } group.setBusinessGroup(topGroup.getDeviceId()); if (groupMessage.getParentGAlias() != null) { Group parentGroup = aliasGroupMap.get(groupMessage.getParentGAlias()); if (parentGroup == null) { parentGroup = aliasGroupToSave.get(groupMessage.getParentGAlias()); } if (parentGroup == null) { log.info("[REDIS消息-业务分组同步回复] 虚拟组织父节点未发送或者未首先发送, {}", groupMessage.toString()); continue; } group.setParentId(null); group.setParentDeviceId(parentGroup.getDeviceId()); } else { group.setParentId(null); group.setParentDeviceId(topGroup.getDeviceId()); } } else { group.setParentId(null); group.setBusinessGroup(group.getDeviceId()); group.setParentDeviceId(null); } group.setUpdateTime(DateUtil.getNow()); aliasGroupToSave.put(group.getAlias(), group); } log.info("[业务分组同步回复-存储分组数据] {}", JSONObject.toJSONString(aliasGroupToSave.values())); // 存储分组数据 groupService.saveByAlias(aliasGroupToSave.values()); } catch (ControllerException e) { log.warn("[REDIS消息-业务分组同步回复] 失败, \r\n{}", e.getMsg()); } catch (Exception e) { log.warn("[REDIS消息-业务分组同步回复] 发现未处理的异常, \r\n{}", new String(msg.getBody())); log.error("[REDIS消息-业务分组同步回复] 异常内容: ", e); } } } /** * 生成分组国标编号 */ private String buildGroupDeviceId(boolean isTop) { try { String deviceTemplate = userSetting.getGroupSyncDeviceTemplate(); if (ObjectUtils.isEmpty(deviceTemplate) || !deviceTemplate.contains("%s")) { String domain = sipConfig.getDomain(); if (domain.length() != 10) { domain = sipConfig.getId().substring(0, 10); } deviceTemplate = domain + "%s0%s"; } String codeType = "216"; if (isTop) { codeType = "215"; } return String.format(deviceTemplate, codeType, RandomStringUtils.insecure().next(6, false, true)); } catch (Exception e) { log.error("[REDIS消息-业务分组同步回复] 构建新的分组编号失败", e); return null; } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisPushStreamListMsgListener.java ================================================ package com.genersoft.iot.vmp.service.redisMsg; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.media.service.IMediaServerService; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.streamPush.bean.RedisPushStreamMessage; import com.genersoft.iot.vmp.streamPush.bean.StreamPush; import com.genersoft.iot.vmp.streamPush.service.IStreamPushService; import com.genersoft.iot.vmp.utils.DateUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.connection.Message; import org.springframework.data.redis.connection.MessageListener; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import jakarta.annotation.Resource; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentLinkedQueue; /** * @Auther: JiangFeng * @Date: 2022/8/16 11:32 * @Description: 接收redis发送的推流设备列表更新通知 * 监听: SUBSCRIBE VM_MSG_PUSH_STREAM_LIST_CHANGE * 发布 PUBLISH VM_MSG_PUSH_STREAM_LIST_CHANGE '[{"app":1000,"stream":10000000,"gbId":"12345678901234567890","name":"A6","status":false},{"app":1000,"stream":10000021,"gbId":"24212345671381000021","name":"终端9273","status":false},{"app":1000,"stream":10000022,"gbId":"24212345671381000022","name":"终端9434","status":true},{"app":1000,"stream":10000025,"gbId":"24212345671381000025","name":"华为M10","status":false},{"app":1000,"stream":10000051,"gbId":"11111111111381111122","name":"终端9720","status":false}]' */ @Slf4j @Component public class RedisPushStreamListMsgListener implements MessageListener { @Resource private IMediaServerService mediaServerService; @Resource private IStreamPushService streamPushService; @Resource private IRedisCatchStorage redisCatchStorage; @Resource private UserSetting userSetting; private final ConcurrentLinkedQueue taskQueue = new ConcurrentLinkedQueue<>(); @Override public void onMessage(Message message, byte[] bytes) { log.info("[REDIS: 推流设备列表更新]: {}", new String(message.getBody())); taskQueue.offer(message); } @Scheduled(fixedDelay = 100) public void executeTaskQueue() { if (taskQueue.isEmpty()) { return; } List messageDataList = new ArrayList<>(); int size = taskQueue.size(); for (int i = 0; i < size; i++) { Message msg = taskQueue.poll(); if (msg != null) { messageDataList.add(msg); } } if (messageDataList.isEmpty()) { return; } for (Message msg : messageDataList) { try { List streamPushItems = JSON.parseArray(new String(msg.getBody()), RedisPushStreamMessage.class); //查询全部的app+stream 用于判断是添加还是修改 Map allAppAndStream = streamPushService.getAllAppAndStreamMap(); Map allGBId = streamPushService.getAllGBId(); // 用于存储更具APP+Stream过滤后的数据,可以直接存入stream_push表与gb_stream表 List streamPushItemForSave = new ArrayList<>(); List streamPushItemForUpdate = new ArrayList<>(); for (RedisPushStreamMessage pushStreamMessage : streamPushItems) { String app = pushStreamMessage.getApp(); String stream = pushStreamMessage.getStream(); boolean contains = allAppAndStream.containsKey(app + stream); //不存在就添加 if (!contains) { if (allGBId.containsKey(pushStreamMessage.getGbId())) { StreamPush streamPushInDb = allGBId.get(pushStreamMessage.getGbId()); log.warn("[REDIS消息-推流设备列表更新-INSERT] 国标编号重复: {}, 已分配给{}/{}", streamPushInDb.getGbDeviceId(), streamPushInDb.getApp(), streamPushInDb.getStream()); continue; } StreamPush streamPush = pushStreamMessage.buildstreamPush(); streamPush.setCreateTime(DateUtil.getNow()); streamPush.setUpdateTime(DateUtil.getNow()); streamPush.setMediaServerId(mediaServerService.getDefaultMediaServer().getId()); streamPushItemForSave.add(streamPush); allGBId.put(streamPush.getGbDeviceId(), streamPush); } else { StreamPush streamPushForGbDeviceId = allGBId.get(pushStreamMessage.getGbId()); if (streamPushForGbDeviceId != null && (!streamPushForGbDeviceId.getApp().equals(pushStreamMessage.getApp()) || !streamPushForGbDeviceId.getStream().equals(pushStreamMessage.getStream()))) { StreamPush streamPushInDb = allGBId.get(pushStreamMessage.getGbId()); log.warn("[REDIS消息-推流设备列表更新-UPDATE] 国标编号重复: {}, 已分配给{}/{}", pushStreamMessage.getGbId(), streamPushInDb.getApp(), streamPushInDb.getStream()); continue; } StreamPush streamPush = allAppAndStream.get(app + stream); streamPush.setUpdateTime(DateUtil.getNow()); streamPush.setGbDeviceId(pushStreamMessage.getGbId()); streamPush.setGbName(pushStreamMessage.getName()); if (pushStreamMessage.getStatus() != null) { streamPush.setGbStatus(pushStreamMessage.getStatus() ? "ON" : "OFF"); } //存在就只修改 name和gbId streamPushItemForUpdate.add(streamPush); } } if (!streamPushItemForSave.isEmpty()) { log.info("添加{}条", streamPushItemForSave.size()); log.info(JSONObject.toJSONString(streamPushItemForSave)); streamPushService.batchAdd(streamPushItemForSave); } if (!streamPushItemForUpdate.isEmpty()) { log.info("修改{}条", streamPushItemForUpdate.size()); log.info(JSONObject.toJSONString(streamPushItemForUpdate)); streamPushService.batchUpdateForRedisMsg(streamPushItemForUpdate); } } catch (Exception e) { log.warn("[REDIS消息-推流设备列表更新] 发现未处理的异常, \r\n{}", new String(msg.getBody())); log.error("[REDIS消息-推流设备列表更新] 异常内容: ", e); } } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisPushStreamResponseListener.java ================================================ package com.genersoft.iot.vmp.service.redisMsg; import com.alibaba.fastjson2.JSON; import com.genersoft.iot.vmp.service.bean.MessageForPushChannelResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.data.redis.connection.Message; import org.springframework.data.redis.connection.MessageListener; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; /** * 接收redis返回的推流结果 * * @author lin * PUBLISH VM_MSG_STREAM_PUSH_RESPONSE '{"code":0,"msg":"失败","app":"1000","stream":"10000022"}' */ @Slf4j @Component public class RedisPushStreamResponseListener implements MessageListener { private ConcurrentLinkedQueue taskQueue = new ConcurrentLinkedQueue<>(); @Qualifier("taskExecutor") @Autowired private ThreadPoolTaskExecutor taskExecutor; private final Map responseEvents = new ConcurrentHashMap<>(); public interface PushStreamResponseEvent { void run(MessageForPushChannelResponse response); } @Override public void onMessage(Message message, byte[] bytes) { log.info("[REDIS: 推流结果]: {}", new String(message.getBody())); taskQueue.offer(message); } @Scheduled(fixedDelay = 100) public void executeTaskQueue() { if (taskQueue.isEmpty()) { return; } List messageDataList = new ArrayList<>(); int size = taskQueue.size(); for (int i = 0; i < size; i++) { Message msg = taskQueue.poll(); if (msg != null) { messageDataList.add(msg); } } if (messageDataList.isEmpty()) { return; } for (Message msg : messageDataList) { try { MessageForPushChannelResponse response = JSON.parseObject(new String(msg.getBody()), MessageForPushChannelResponse.class); if (response == null || ObjectUtils.isEmpty(response.getApp()) || ObjectUtils.isEmpty(response.getStream())) { log.info("[REDIS消息-请求推流结果]:参数不全"); continue; } // 查看正在等待的invite消息 if (responseEvents.get(response.getApp() + response.getStream()) != null) { responseEvents.get(response.getApp() + response.getStream()).run(response); } } catch (Exception e) { log.warn("[REDIS消息-请求推流结果] 发现未处理的异常, \r\n{}", JSON.toJSONString(msg)); log.error("[REDIS消息-请求推流结果] 异常内容: ", e); } } } public void addEvent(String app, String stream, PushStreamResponseEvent callback) { responseEvents.put(app + stream, callback); } public void removeEvent(String app, String stream) { responseEvents.remove(app + stream); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisPushStreamStatusMsgListener.java ================================================ package com.genersoft.iot.vmp.service.redisMsg; import com.alibaba.fastjson2.JSON; import com.genersoft.iot.vmp.common.VideoManagerConstants; import com.genersoft.iot.vmp.conf.DynamicTask; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.service.bean.PushStreamStatusChangeFromRedisDto; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.streamPush.service.IStreamPushService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.data.redis.connection.Message; import org.springframework.data.redis.connection.MessageListener; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; /** * 接收redis发送的推流设备上线下线通知 * * @author lin * 发送 PUBLISH VM_MSG_PUSH_STREAM_STATUS_CHANGE '{"setAllOffline":false,"offlineStreams":[{"app":"1000","stream":"10000022","timeStamp":1726729716551}]}' * 订阅 SUBSCRIBE VM_MSG_PUSH_STREAM_STATUS_CHANGE */ @Slf4j @Component public class RedisPushStreamStatusMsgListener implements MessageListener, ApplicationRunner { @Autowired private IRedisCatchStorage redisCatchStorage; @Autowired private IStreamPushService streamPushService; @Autowired private DynamicTask dynamicTask; @Autowired private UserSetting userSetting; private final ConcurrentLinkedQueue taskQueue = new ConcurrentLinkedQueue<>(); @Override public void onMessage(Message message, byte[] bytes) { log.info("[REDIS: 推流设备状态变化]: {}", new String(message.getBody())); taskQueue.offer(message); } @Scheduled(fixedDelay = 100) public void executeTaskQueue() { if (taskQueue.isEmpty()) { return; } List messageDataList = new ArrayList<>(); int size = taskQueue.size(); for (int i = 0; i < size; i++) { Message msg = taskQueue.poll(); if (msg != null) { messageDataList.add(msg); } } if (messageDataList.isEmpty()) { return; } for (Message msg : messageDataList) { try { PushStreamStatusChangeFromRedisDto streamStatusMessage = JSON.parseObject(msg.getBody(), PushStreamStatusChangeFromRedisDto.class); if (streamStatusMessage == null) { log.warn("[REDIS消息]推流设备状态变化消息解析失败"); continue; } // 取消定时任务 dynamicTask.stop(VideoManagerConstants.VM_MSG_GET_ALL_ONLINE_REQUESTED); if (streamStatusMessage.isSetAllOffline()) { // 所有设备离线 streamPushService.allOfflineForRedisMsg(); } if (streamStatusMessage.getOfflineStreams() != null && !streamStatusMessage.getOfflineStreams().isEmpty()) { // 更新部分设备离线 log.info("[REDIS: 推流设备状态变化] 更新部分设备离线: {}个", streamStatusMessage.getOfflineStreams().size()); streamPushService.offlineforRedisMsg(streamStatusMessage.getOfflineStreams()); } if (streamStatusMessage.getOnlineStreams() != null && !streamStatusMessage.getOnlineStreams().isEmpty()) { // 更新部分设备上线 log.info("[REDIS: 推流设备状态变化] 更新部分设备上线: {}个", streamStatusMessage.getOnlineStreams().size()); streamPushService.onlineForRedisMsg(streamStatusMessage.getOnlineStreams()); } } catch (Exception e) { log.warn("[REDIS消息-推流设备状态变化] 发现未处理的异常, \r\n{}", JSON.parseObject(msg.getBody())); log.error("[REDIS消息-推流设备状态变化] 异常内容: ", e); } } } @Override public void run(ApplicationArguments args) throws Exception { if (userSetting.getUsePushingAsStatus()) { return; } // 查询是否存在推流设备,没有则不发送 List allAppAndStream = streamPushService.getAllAppAndStream(); if (allAppAndStream == null || allAppAndStream.isEmpty()) { return; } // 启动时设置所有推流通道离线,发起查询请求 redisCatchStorage.sendStreamPushRequestedMsgForStatus(); dynamicTask.startDelay(VideoManagerConstants.VM_MSG_GET_ALL_ONLINE_REQUESTED, () -> { log.info("[REDIS消息]未收到redis回复推流设备状态,执行推流设备离线"); // 五秒收不到请求就设置通道离线,然后通知上级离线 streamPushService.allOfflineForRedisMsg(); }, 5000); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/redisMsg/control/RedisRpcChannelPlayController.java ================================================ package com.genersoft.iot.vmp.service.redisMsg.control; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.common.InviteSessionType; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.conf.redis.RedisRpcConfig; import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcMessage; import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcRequest; import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcResponse; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import com.genersoft.iot.vmp.gb28181.bean.InviteMessageInfo; import com.genersoft.iot.vmp.gb28181.service.IGbChannelPlayService; import com.genersoft.iot.vmp.gb28181.service.IGbChannelService; import com.genersoft.iot.vmp.gb28181.service.IPTZService; import com.genersoft.iot.vmp.service.bean.InviteErrorCode; import com.genersoft.iot.vmp.service.redisMsg.dto.RedisRpcController; import com.genersoft.iot.vmp.service.redisMsg.dto.RedisRpcMapping; import com.genersoft.iot.vmp.service.redisMsg.dto.RpcController; import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import javax.sip.message.Response; @Component @Slf4j @RedisRpcController("channel") public class RedisRpcChannelPlayController extends RpcController { @Autowired private UserSetting userSetting; @Autowired private RedisTemplate redisTemplate; @Autowired private IGbChannelService channelService; @Autowired private IGbChannelPlayService channelPlayService; @Autowired private IPTZService iptzService; private void sendResponse(RedisRpcResponse response){ log.info("[redis-rpc] >> {}", response); response.setToId(userSetting.getServerId()); RedisRpcMessage message = new RedisRpcMessage(); message.setResponse(response); redisTemplate.convertAndSend(RedisRpcConfig.REDIS_REQUEST_CHANNEL_KEY, message); } /** * 点播国标设备 */ @RedisRpcMapping("play") public RedisRpcResponse playChannel(RedisRpcRequest request) { int channelId = Integer.parseInt(request.getParam().toString()); RedisRpcResponse response = request.getResponse(); if (channelId <= 0) { response.setStatusCode(ErrorCode.ERROR400.getCode()); response.setBody("param error"); return response; } // 获取对应的设备和通道信息 CommonGBChannel channel = channelService.getOne(channelId); if (channel == null) { response.setStatusCode(ErrorCode.ERROR400.getCode()); response.setBody("param error"); return response; } InviteMessageInfo inviteInfo = new InviteMessageInfo(); inviteInfo.setSessionName("Play"); channelPlayService.startInvite(channel, inviteInfo, null, (code, msg, data) ->{ if (code == InviteErrorCode.SUCCESS.getCode()) { response.setStatusCode(ErrorCode.SUCCESS.getCode()); response.setBody(data); }else { response.setStatusCode(code); } // 手动发送结果 sendResponse(response); }); return null; } /** * 点播国标设备 */ @RedisRpcMapping("queryRecordInfo") public RedisRpcResponse queryRecordInfo(RedisRpcRequest request) { JSONObject paramJson = JSONObject.parseObject(request.getParam().toString()); int channelId = paramJson.getIntValue("channelId"); String startTime = paramJson.getString("startTime"); String endTime = paramJson.getString("endTime"); RedisRpcResponse response = request.getResponse(); if (channelId <= 0) { response.setStatusCode(ErrorCode.ERROR400.getCode()); response.setBody("param error"); return response; } // 获取对应的设备和通道信息 CommonGBChannel channel = channelService.getOne(channelId); if (channel == null) { response.setStatusCode(ErrorCode.ERROR400.getCode()); response.setBody("param error"); return response; } try { channelPlayService.queryRecord(channel, startTime, endTime, (code, msg, data) ->{ if (code == InviteErrorCode.SUCCESS.getCode()) { response.setStatusCode(code); response.setBody(data); }else { response.setStatusCode(code); } // 手动发送结果 sendResponse(response); }); }catch (ControllerException e) { response.setStatusCode(ErrorCode.ERROR100.getCode()); response.setBody(e.getMessage()); } return null; } /** * 暂停录像回放 */ @RedisRpcMapping("playbackPause") public RedisRpcResponse playbackPause(RedisRpcRequest request) { String streamId = request.getParam().toString(); RedisRpcResponse response = request.getResponse(); if (streamId == null) { response.setStatusCode(ErrorCode.ERROR400.getCode()); response.setBody("param error"); return response; } try { // channelPlayService.playbackPause(streamId); response.setStatusCode(ErrorCode.SUCCESS.getCode()); }catch (ControllerException e) { response.setStatusCode(ErrorCode.ERROR100.getCode()); response.setBody(e.getMessage()); } return response; } /** * 恢复录像回放 */ @RedisRpcMapping("playbackResume") public RedisRpcResponse playbackResume(RedisRpcRequest request) { String streamId = request.getParam().toString(); RedisRpcResponse response = request.getResponse(); if (streamId == null) { response.setStatusCode(ErrorCode.ERROR400.getCode()); response.setBody("param error"); return response; } try { // channelPlayService.playbackResume(streamId); response.setStatusCode(ErrorCode.SUCCESS.getCode()); }catch (ControllerException e) { response.setStatusCode(ErrorCode.ERROR100.getCode()); response.setBody(e.getMessage()); } return response; } /** * 停止点播国标设备 */ @RedisRpcMapping("stop") public RedisRpcResponse stop(RedisRpcRequest request) { JSONObject jsonObject = JSONObject.parseObject(request.getParam().toString()); RedisRpcResponse response = request.getResponse(); Integer channelId = jsonObject.getIntValue("channelId"); if (channelId == null || channelId <= 0) { response.setStatusCode(ErrorCode.ERROR400.getCode()); response.setBody("param error"); return response; } String stream = jsonObject.getString("stream"); InviteSessionType type = jsonObject.getObject("inviteSessionType", InviteSessionType.class); // 获取对应的设备和通道信息 CommonGBChannel channel = channelService.getOne(channelId); if (channel == null) { response.setStatusCode(ErrorCode.ERROR400.getCode()); response.setBody("param error"); return response; } try { channelPlayService.stopInvite(type, channel, stream); response.setStatusCode(ErrorCode.SUCCESS.getCode()); }catch (Exception e){ response.setStatusCode(Response.SERVER_INTERNAL_ERROR); response.setBody(e.getMessage()); } return response; } /** * 录像回放国标设备 */ @RedisRpcMapping("playback") public RedisRpcResponse playbackChannel(RedisRpcRequest request) { JSONObject paramJson = JSONObject.parseObject(request.getParam().toString()); int channelId = paramJson.getIntValue("channelId"); String startTime = paramJson.getString("startTime"); String endTime = paramJson.getString("endTime"); RedisRpcResponse response = request.getResponse(); if (channelId <= 0) { response.setStatusCode(ErrorCode.ERROR400.getCode()); response.setBody("param error"); return response; } // 获取对应的设备和通道信息 CommonGBChannel channel = channelService.getOne(channelId); if (channel == null) { response.setStatusCode(ErrorCode.ERROR400.getCode()); response.setBody("param error"); return response; } InviteMessageInfo inviteInfo = new InviteMessageInfo(); inviteInfo.setSessionName("Playback"); inviteInfo.setStartTime(DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime)); inviteInfo.setStopTime(DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime)); channelPlayService.startInvite(channel, inviteInfo, null, (code, msg, data) ->{ if (code == InviteErrorCode.SUCCESS.getCode()) { response.setStatusCode(ErrorCode.SUCCESS.getCode()); response.setBody(data); }else { response.setStatusCode(code); } // 手动发送结果 sendResponse(response); }); return null; } /** * 录像回放国标设备 */ @RedisRpcMapping("download") public RedisRpcResponse downloadChannel(RedisRpcRequest request) { JSONObject paramJson = JSONObject.parseObject(request.getParam().toString()); int channelId = paramJson.getIntValue("channelId"); String startTime = paramJson.getString("startTime"); String endTime = paramJson.getString("endTime"); int downloadSpeed = paramJson.getIntValue("downloadSpeed"); RedisRpcResponse response = request.getResponse(); if (channelId <= 0) { response.setStatusCode(ErrorCode.ERROR400.getCode()); response.setBody("param error"); return response; } // 获取对应的设备和通道信息 CommonGBChannel channel = channelService.getOne(channelId); if (channel == null) { response.setStatusCode(ErrorCode.ERROR400.getCode()); response.setBody("param error"); return response; } InviteMessageInfo inviteInfo = new InviteMessageInfo(); inviteInfo.setSessionName("Download"); inviteInfo.setStartTime(DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime)); inviteInfo.setStopTime(DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime)); inviteInfo.setDownloadSpeed(downloadSpeed + ""); channelPlayService.startInvite(channel, inviteInfo, null, (code, msg, data) ->{ if (code == InviteErrorCode.SUCCESS.getCode()) { response.setStatusCode(ErrorCode.SUCCESS.getCode()); response.setBody(data); }else { response.setStatusCode(code); } // 手动发送结果 sendResponse(response); }); return null; } /** * 云台控制 */ @RedisRpcMapping("ptz/frontEndCommand") public RedisRpcResponse frontEndCommand(RedisRpcRequest request) { JSONObject paramJson = JSONObject.parseObject(request.getParam().toString()); int channelId = paramJson.getIntValue("channelId"); int cmdCode = paramJson.getIntValue("cmdCode"); int parameter1 = paramJson.getIntValue("parameter1"); int parameter2 = paramJson.getIntValue("parameter2"); int combindCode2 = paramJson.getIntValue("combindCode2"); RedisRpcResponse response = request.getResponse(); if (channelId <= 0 || cmdCode < 0 || parameter1 < 0 || parameter2 < 0 || combindCode2 < 0) { response.setStatusCode(ErrorCode.ERROR400.getCode()); response.setBody("param error"); return response; } // 获取对应的设备和通道信息 CommonGBChannel channel = channelService.getOne(channelId); if (channel == null) { response.setStatusCode(ErrorCode.ERROR400.getCode()); response.setBody("param error"); return response; } try { iptzService.frontEndCommand(channel, cmdCode, parameter1, parameter2, combindCode2); }catch (ControllerException e) { response.setStatusCode(ErrorCode.ERROR100.getCode()); response.setBody(e.getMessage()); return response; } response.setStatusCode(ErrorCode.SUCCESS.getCode()); return response; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/redisMsg/control/RedisRpcCloudRecordController.java ================================================ package com.genersoft.iot.vmp.service.redisMsg.control; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.redis.RedisRpcConfig; import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcMessage; import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcRequest; import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcResponse; import com.genersoft.iot.vmp.service.ICloudRecordService; import com.genersoft.iot.vmp.service.bean.DownloadFileInfo; import com.genersoft.iot.vmp.service.redisMsg.dto.RedisRpcController; import com.genersoft.iot.vmp.service.redisMsg.dto.RedisRpcMapping; import com.genersoft.iot.vmp.service.redisMsg.dto.RpcController; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; @Component @Slf4j @RedisRpcController("cloudRecord") public class RedisRpcCloudRecordController extends RpcController { @Autowired private UserSetting userSetting; @Autowired private RedisTemplate redisTemplate; @Autowired private ICloudRecordService cloudRecordService; private void sendResponse(RedisRpcResponse response){ log.info("[redis-rpc] >> {}", response); response.setToId(userSetting.getServerId()); RedisRpcMessage message = new RedisRpcMessage(); message.setResponse(response); redisTemplate.convertAndSend(RedisRpcConfig.REDIS_REQUEST_CHANNEL_KEY, message); } /** * 播放 */ @RedisRpcMapping("play") public RedisRpcResponse play(RedisRpcRequest request) { int id = Integer.parseInt(request.getParam().toString()); RedisRpcResponse response = request.getResponse(); if (id <= 0) { response.setStatusCode(ErrorCode.ERROR400.getCode()); response.setBody("param error"); return response; } DownloadFileInfo downloadFileInfo = cloudRecordService.getPlayUrlPath(id); if (downloadFileInfo == null) { response.setStatusCode(ErrorCode.ERROR100.getCode()); response.setBody("get play url error"); return response; } response.setStatusCode(ErrorCode.SUCCESS.getCode()); response.setBody(JSONObject.toJSONString(downloadFileInfo)); return response; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/redisMsg/control/RedisRpcDeviceController.java ================================================ package com.genersoft.iot.vmp.service.redisMsg.control; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.conf.redis.RedisRpcConfig; import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcMessage; import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcRequest; import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcResponse; import com.genersoft.iot.vmp.gb28181.bean.BasicParam; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.SyncStatus; import com.genersoft.iot.vmp.gb28181.service.IDeviceService; import com.genersoft.iot.vmp.service.redisMsg.dto.RedisRpcController; import com.genersoft.iot.vmp.service.redisMsg.dto.RedisRpcMapping; import com.genersoft.iot.vmp.service.redisMsg.dto.RpcController; import com.genersoft.iot.vmp.streamProxy.service.IStreamProxyService; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; @Component @Slf4j @RedisRpcController("device") public class RedisRpcDeviceController extends RpcController { @Autowired private UserSetting userSetting; @Autowired private RedisTemplate redisTemplate; @Autowired private IDeviceService deviceService; @Autowired private IStreamProxyService streamProxyService; private void sendResponse(RedisRpcResponse response){ log.info("[redis-rpc] >> {}", response); response.setToId(userSetting.getServerId()); RedisRpcMessage message = new RedisRpcMessage(); message.setResponse(response); redisTemplate.convertAndSend(RedisRpcConfig.REDIS_REQUEST_CHANNEL_KEY, message); } /** * 通道同步 */ @RedisRpcMapping("devicesSync") public RedisRpcResponse devicesSync(RedisRpcRequest request) { String deviceId = request.getParam().toString(); Device device = deviceService.getDeviceByDeviceId(deviceId); RedisRpcResponse response = request.getResponse(); if (device == null || !userSetting.getServerId().equals(device.getServerId())) { response.setStatusCode(ErrorCode.ERROR400.getCode()); response.setBody("param error"); return response; } if (device.getRegisterTime() == null) { response.setStatusCode(ErrorCode.ERROR400.getCode()); response.setBody("设备尚未注册过"); return response; } WVPResult result = deviceService.devicesSync(device); response.setStatusCode(ErrorCode.SUCCESS.getCode()); response.setBody(JSONObject.toJSONString(result)); return response; } /** * 获取通道同步状态 */ @RedisRpcMapping("getChannelSyncStatus") public RedisRpcResponse getChannelSyncStatus(RedisRpcRequest request) { String deviceId = request.getParam().toString(); Device device = deviceService.getDeviceByDeviceId(deviceId); RedisRpcResponse response = request.getResponse(); if (device == null || !userSetting.getServerId().equals(device.getServerId())) { response.setStatusCode(ErrorCode.ERROR400.getCode()); response.setBody("param error"); return response; } SyncStatus channelSyncStatus = deviceService.getChannelSyncStatus(deviceId); response.setStatusCode(ErrorCode.SUCCESS.getCode()); response.setBody(JSONObject.toJSONString(channelSyncStatus)); return response; } @RedisRpcMapping("deviceBasicConfig") public RedisRpcResponse deviceBasicConfig(RedisRpcRequest request) { BasicParam basicParam = JSONObject.parseObject(request.getParam().toString(), BasicParam.class); Device device = deviceService.getDeviceByDeviceId(basicParam.getDeviceId()); RedisRpcResponse response = request.getResponse(); if (device == null || !userSetting.getServerId().equals(device.getServerId())) { response.setStatusCode(ErrorCode.ERROR400.getCode()); response.setBody("param error"); return response; } deviceService.deviceBasicConfig(device, basicParam, (code, msg, data) -> { response.setStatusCode(code); response.setBody(new WVPResult<>(code, msg, data)); // 手动发送结果 sendResponse(response); }); return null; } @RedisRpcMapping("deviceConfigQuery") public RedisRpcResponse deviceConfigQuery(RedisRpcRequest request) { JSONObject paramJson = JSONObject.parseObject(request.getParam().toString()); String deviceId = paramJson.getString("deviceId"); String channelId = paramJson.getString("channelId"); String configType = paramJson.getString("configType"); Device device = deviceService.getDeviceByDeviceId(deviceId); RedisRpcResponse response = request.getResponse(); if (device == null || !userSetting.getServerId().equals(device.getServerId())) { response.setStatusCode(ErrorCode.ERROR400.getCode()); response.setBody("param error"); return response; } deviceService.deviceConfigQuery(device, channelId, configType, (code, msg, data) -> { response.setStatusCode(code); response.setBody(new WVPResult<>(code, msg, data)); // 手动发送结果 sendResponse(response); }); return null; } @RedisRpcMapping("teleboot") public RedisRpcResponse teleboot(RedisRpcRequest request) { String deviceId = request.getParam().toString(); Device device = deviceService.getDeviceByDeviceId(deviceId); RedisRpcResponse response = request.getResponse(); if (device == null || !userSetting.getServerId().equals(device.getServerId())) { response.setStatusCode(ErrorCode.ERROR400.getCode()); response.setBody("param error"); return response; } try { deviceService.teleboot(device); }catch (ControllerException e) { response.setStatusCode(e.getCode()); response.setBody(WVPResult.fail(ErrorCode.ERROR100.getCode(), e.getMsg())); return response; } response.setStatusCode(ErrorCode.SUCCESS.getCode()); response.setBody(WVPResult.success()); return response; } @RedisRpcMapping("record") public RedisRpcResponse record(RedisRpcRequest request) { JSONObject paramJson = JSONObject.parseObject(request.getParam().toString()); String deviceId = paramJson.getString("deviceId"); String channelId = paramJson.getString("channelId"); String recordCmdStr = paramJson.getString("recordCmdStr"); Device device = deviceService.getDeviceByDeviceId(deviceId); RedisRpcResponse response = request.getResponse(); if (device == null || !userSetting.getServerId().equals(device.getServerId())) { response.setStatusCode(ErrorCode.ERROR400.getCode()); response.setBody("param error"); return response; } try { deviceService.record(device, channelId, recordCmdStr, (code, msg, data) -> { response.setStatusCode(ErrorCode.SUCCESS.getCode()); response.setBody(new WVPResult<>(code, msg, data)); // 手动发送结果 sendResponse(response); }); }catch (ControllerException e) { response.setStatusCode(e.getCode()); response.setBody(WVPResult.fail(ErrorCode.ERROR100.getCode(), e.getMsg())); sendResponse(response); } return null; } @RedisRpcMapping("guard") public RedisRpcResponse guard(RedisRpcRequest request) { JSONObject paramJson = JSONObject.parseObject(request.getParam().toString()); String deviceId = paramJson.getString("deviceId"); String guardCmdStr = paramJson.getString("guardCmdStr"); Device device = deviceService.getDeviceByDeviceId(deviceId); RedisRpcResponse response = request.getResponse(); if (device == null || !userSetting.getServerId().equals(device.getServerId())) { response.setStatusCode(ErrorCode.ERROR400.getCode()); response.setBody("param error"); return response; } try { deviceService.guard(device, guardCmdStr, (code, msg, data) -> { response.setStatusCode(ErrorCode.SUCCESS.getCode()); response.setBody(new WVPResult<>(code, msg, data)); // 手动发送结果 sendResponse(response); }); }catch (ControllerException e) { response.setStatusCode(e.getCode()); response.setBody(WVPResult.fail(ErrorCode.ERROR100.getCode(), e.getMsg())); sendResponse(response); } return null; } @RedisRpcMapping("resetAlarm") public RedisRpcResponse resetAlarm(RedisRpcRequest request) { JSONObject paramJson = JSONObject.parseObject(request.getParam().toString()); String deviceId = paramJson.getString("deviceId"); String channelId = paramJson.getString("channelId"); String alarmMethod = paramJson.getString("alarmMethod"); String alarmType = paramJson.getString("alarmType"); Device device = deviceService.getDeviceByDeviceId(deviceId); RedisRpcResponse response = request.getResponse(); if (device == null || !userSetting.getServerId().equals(device.getServerId())) { response.setStatusCode(ErrorCode.ERROR400.getCode()); response.setBody("param error"); return response; } try { deviceService.resetAlarm(device, channelId, alarmMethod, alarmType, (code, msg, data) -> { response.setStatusCode(ErrorCode.SUCCESS.getCode()); response.setBody(new WVPResult<>(code, msg, data)); // 手动发送结果 sendResponse(response); }); }catch (ControllerException e) { response.setStatusCode(e.getCode()); response.setBody(WVPResult.fail(ErrorCode.ERROR100.getCode(), e.getMsg())); sendResponse(response); } return null; } @RedisRpcMapping("iFrame") public RedisRpcResponse iFrame(RedisRpcRequest request) { JSONObject paramJson = JSONObject.parseObject(request.getParam().toString()); String deviceId = paramJson.getString("deviceId"); String channelId = paramJson.getString("channelId"); Device device = deviceService.getDeviceByDeviceId(deviceId); RedisRpcResponse response = request.getResponse(); if (device == null || !userSetting.getServerId().equals(device.getServerId())) { response.setStatusCode(ErrorCode.ERROR400.getCode()); response.setBody("param error"); return response; } try { deviceService.iFrame(device, channelId); }catch (ControllerException e) { response.setStatusCode(e.getCode()); response.setBody(WVPResult.fail(ErrorCode.ERROR100.getCode(), e.getMsg())); sendResponse(response); } return null; } @RedisRpcMapping("homePosition") public RedisRpcResponse homePosition(RedisRpcRequest request) { JSONObject paramJson = JSONObject.parseObject(request.getParam().toString()); String deviceId = paramJson.getString("deviceId"); String channelId = paramJson.getString("channelId"); Boolean enabled = paramJson.getBoolean("enabled"); Integer resetTime = paramJson.getInteger("resetTime"); Integer presetIndex = paramJson.getInteger("presetIndex"); Device device = deviceService.getDeviceByDeviceId(deviceId); RedisRpcResponse response = request.getResponse(); if (device == null || !userSetting.getServerId().equals(device.getServerId())) { response.setStatusCode(ErrorCode.ERROR400.getCode()); response.setBody("param error"); return response; } try { deviceService.homePosition(device, channelId, enabled, resetTime, presetIndex, (code, msg, data) -> { response.setStatusCode(ErrorCode.SUCCESS.getCode()); response.setBody(new WVPResult<>(code, msg, data)); // 手动发送结果 sendResponse(response); }); }catch (ControllerException e) { response.setStatusCode(e.getCode()); response.setBody(WVPResult.fail(ErrorCode.ERROR100.getCode(), e.getMsg())); sendResponse(response); } return null; } @RedisRpcMapping("dragZoomIn") public RedisRpcResponse dragZoomIn(RedisRpcRequest request) { JSONObject paramJson = JSONObject.parseObject(request.getParam().toString()); String deviceId = paramJson.getString("deviceId"); String channelId = paramJson.getString("channelId"); Integer length = paramJson.getInteger("length"); Integer width = paramJson.getInteger("width"); Integer midpointx = paramJson.getInteger("midpointx"); Integer midpointy = paramJson.getInteger("midpointy"); Integer lengthx = paramJson.getInteger("lengthx"); Integer lengthy = paramJson.getInteger("lengthy"); Device device = deviceService.getDeviceByDeviceId(deviceId); RedisRpcResponse response = request.getResponse(); if (device == null || !userSetting.getServerId().equals(device.getServerId())) { response.setStatusCode(ErrorCode.ERROR400.getCode()); response.setBody("param error"); return response; } try { deviceService.dragZoomIn(device, channelId, length, width, midpointx, midpointy, lengthx, lengthy, (code, msg, data) -> { response.setStatusCode(ErrorCode.SUCCESS.getCode()); response.setBody(new WVPResult<>(code, msg, data)); // 手动发送结果 sendResponse(response); }); }catch (ControllerException e) { response.setStatusCode(e.getCode()); response.setBody(WVPResult.fail(ErrorCode.ERROR100.getCode(), e.getMsg())); sendResponse(response); } return null; } @RedisRpcMapping("dragZoomOut") public RedisRpcResponse dragZoomOut(RedisRpcRequest request) { JSONObject paramJson = JSONObject.parseObject(request.getParam().toString()); String deviceId = paramJson.getString("deviceId"); String channelId = paramJson.getString("channelId"); Integer length = paramJson.getInteger("length"); Integer width = paramJson.getInteger("width"); Integer midpointx = paramJson.getInteger("midpointx"); Integer midpointy = paramJson.getInteger("midpointy"); Integer lengthx = paramJson.getInteger("lengthx"); Integer lengthy = paramJson.getInteger("lengthy"); Device device = deviceService.getDeviceByDeviceId(deviceId); RedisRpcResponse response = request.getResponse(); if (device == null || !userSetting.getServerId().equals(device.getServerId())) { response.setStatusCode(ErrorCode.ERROR400.getCode()); response.setBody("param error"); return response; } try { deviceService.dragZoomOut(device, channelId, length, width, midpointx, midpointy, lengthx, lengthy, (code, msg, data) -> { response.setStatusCode(ErrorCode.SUCCESS.getCode()); response.setBody(new WVPResult<>(code, msg, data)); // 手动发送结果 sendResponse(response); }); }catch (ControllerException e) { response.setStatusCode(e.getCode()); response.setBody(WVPResult.fail(ErrorCode.ERROR100.getCode(), e.getMsg())); sendResponse(response); } return null; } @RedisRpcMapping("alarm") public RedisRpcResponse alarm(RedisRpcRequest request) { JSONObject paramJson = JSONObject.parseObject(request.getParam().toString()); String deviceId = paramJson.getString("deviceId"); String startPriority = paramJson.getString("startPriority"); String endPriority = paramJson.getString("endPriority"); String alarmMethod = paramJson.getString("alarmMethod"); String alarmType = paramJson.getString("alarmType"); String startTime = paramJson.getString("startTime"); String endTime = paramJson.getString("endTime"); Device device = deviceService.getDeviceByDeviceId(deviceId); RedisRpcResponse response = request.getResponse(); if (device == null || !userSetting.getServerId().equals(device.getServerId())) { response.setStatusCode(ErrorCode.ERROR400.getCode()); response.setBody("param error"); return response; } try { deviceService.alarm(device, startPriority, endPriority, alarmMethod, alarmType, startTime, endTime, (code, msg, data) -> { response.setStatusCode(ErrorCode.SUCCESS.getCode()); response.setBody(new WVPResult<>(code, msg, data)); // 手动发送结果 sendResponse(response); }); }catch (ControllerException e) { response.setStatusCode(e.getCode()); response.setBody(WVPResult.fail(ErrorCode.ERROR100.getCode(), e.getMsg())); sendResponse(response); } return null; } @RedisRpcMapping("deviceStatus") public RedisRpcResponse deviceStatus(RedisRpcRequest request) { String deviceId = request.getParam().toString(); Device device = deviceService.getDeviceByDeviceId(deviceId); RedisRpcResponse response = request.getResponse(); if (device == null || !userSetting.getServerId().equals(device.getServerId())) { response.setStatusCode(ErrorCode.ERROR400.getCode()); response.setBody("param error"); return response; } try { deviceService.deviceStatus(device, (code, msg, data) -> { response.setStatusCode(ErrorCode.SUCCESS.getCode()); response.setBody(new WVPResult<>(code, msg, data)); // 手动发送结果 sendResponse(response); }); }catch (ControllerException e) { response.setStatusCode(e.getCode()); response.setBody(WVPResult.fail(ErrorCode.ERROR100.getCode(), e.getMsg())); sendResponse(response); } return null; } @RedisRpcMapping("info") public RedisRpcResponse info(RedisRpcRequest request) { String deviceId = request.getParam().toString(); Device device = deviceService.getDeviceByDeviceId(deviceId); RedisRpcResponse response = request.getResponse(); if (device == null || !userSetting.getServerId().equals(device.getServerId())) { response.setStatusCode(ErrorCode.ERROR400.getCode()); response.setBody("param error"); return response; } try { deviceService.deviceInfo(device, (code, msg, data) -> { response.setStatusCode(ErrorCode.SUCCESS.getCode()); response.setBody(new WVPResult<>(code, msg, data)); // 手动发送结果 sendResponse(response); }); }catch (ControllerException e) { response.setStatusCode(e.getCode()); response.setBody(WVPResult.fail(ErrorCode.ERROR100.getCode(), e.getMsg())); sendResponse(response); } return null; } @RedisRpcMapping("info") public RedisRpcResponse queryPreset(RedisRpcRequest request) { JSONObject paramJson = JSONObject.parseObject(request.getParam().toString()); String deviceId = paramJson.getString("deviceId"); String channelId = paramJson.getString("channelId"); Device device = deviceService.getDeviceByDeviceId(deviceId); RedisRpcResponse response = request.getResponse(); if (device == null || !userSetting.getServerId().equals(device.getServerId())) { response.setStatusCode(ErrorCode.ERROR400.getCode()); response.setBody("param error"); return response; } try { deviceService.queryPreset(device, channelId, (code, msg, data) -> { response.setStatusCode(ErrorCode.SUCCESS.getCode()); response.setBody(new WVPResult<>(code, msg, data)); // 手动发送结果 sendResponse(response); }); }catch (ControllerException e) { response.setStatusCode(e.getCode()); response.setBody(WVPResult.fail(ErrorCode.ERROR100.getCode(), e.getMsg())); sendResponse(response); } return null; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/redisMsg/control/RedisRpcDevicePlayController.java ================================================ package com.genersoft.iot.vmp.service.redisMsg.control; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.redis.RedisRpcConfig; import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcMessage; import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcRequest; import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcResponse; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.service.IDeviceService; import com.genersoft.iot.vmp.gb28181.service.IPlayService; import com.genersoft.iot.vmp.service.redisMsg.dto.RedisRpcController; import com.genersoft.iot.vmp.service.redisMsg.dto.RedisRpcMapping; import com.genersoft.iot.vmp.service.redisMsg.dto.RpcController; import com.genersoft.iot.vmp.vmanager.bean.AudioBroadcastResult; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; @Component @Slf4j @RedisRpcController("devicePlay") public class RedisRpcDevicePlayController extends RpcController { @Autowired private UserSetting userSetting; @Autowired private RedisTemplate redisTemplate; @Autowired private IDeviceService deviceService; @Autowired private IPlayService playService; private void sendResponse(RedisRpcResponse response){ log.info("[redis-rpc] >> {}", response); response.setToId(userSetting.getServerId()); RedisRpcMessage message = new RedisRpcMessage(); message.setResponse(response); redisTemplate.convertAndSend(RedisRpcConfig.REDIS_REQUEST_CHANNEL_KEY, message); } /** * 获取通道同步状态 */ @RedisRpcMapping("audioBroadcast") public RedisRpcResponse audioBroadcast(RedisRpcRequest request) { JSONObject paramJson = JSON.parseObject(request.getParam().toString()); String deviceId = paramJson.getString("deviceId"); String channelDeviceId = paramJson.getString("channelDeviceId"); Boolean broadcastMode = paramJson.getBoolean("broadcastMode"); Device device = deviceService.getDeviceByDeviceId(deviceId); RedisRpcResponse response = request.getResponse(); if (device == null || !userSetting.getServerId().equals(device.getServerId())) { response.setStatusCode(ErrorCode.ERROR400.getCode()); response.setBody("param error"); return response; } AudioBroadcastResult audioBroadcastResult = playService.audioBroadcast(deviceId, channelDeviceId, broadcastMode); response.setStatusCode(ErrorCode.SUCCESS.getCode()); response.setBody(JSONObject.toJSONString(audioBroadcastResult)); return response; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/redisMsg/control/RedisRpcGbDeviceController.java ================================================ package com.genersoft.iot.vmp.service.redisMsg.control; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.common.InviteSessionType; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.conf.redis.RedisRpcConfig; import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcMessage; import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcRequest; import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcResponse; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.InviteMessageInfo; import com.genersoft.iot.vmp.gb28181.service.IDeviceService; import com.genersoft.iot.vmp.gb28181.service.IGbChannelPlayService; import com.genersoft.iot.vmp.gb28181.service.IGbChannelService; import com.genersoft.iot.vmp.gb28181.service.IPTZService; import com.genersoft.iot.vmp.service.bean.InviteErrorCode; import com.genersoft.iot.vmp.service.redisMsg.dto.RedisRpcController; import com.genersoft.iot.vmp.service.redisMsg.dto.RedisRpcMapping; import com.genersoft.iot.vmp.service.redisMsg.dto.RpcController; import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import javax.sip.message.Response; @Component @Slf4j @RedisRpcController("device") public class RedisRpcGbDeviceController extends RpcController { @Autowired private UserSetting userSetting; @Autowired private RedisTemplate redisTemplate; @Autowired private IDeviceService deviceService; private void sendResponse(RedisRpcResponse response){ log.info("[redis-rpc] >> {}", response); response.setToId(userSetting.getServerId()); RedisRpcMessage message = new RedisRpcMessage(); message.setResponse(response); redisTemplate.convertAndSend(RedisRpcConfig.REDIS_REQUEST_CHANNEL_KEY, message); } /** * 目录订阅 */ @RedisRpcMapping("subscribeCatalog") public RedisRpcResponse subscribeCatalog(RedisRpcRequest request) { JSONObject paramJson = JSONObject.parseObject(request.getParam().toString()); int id = paramJson.getIntValue("id"); int cycle = paramJson.getIntValue("cycle"); RedisRpcResponse response = request.getResponse(); if (id <= 0) { response.setStatusCode(ErrorCode.ERROR400.getCode()); response.setBody("param error"); return response; } deviceService.subscribeCatalog(id, cycle); response.setStatusCode(ErrorCode.SUCCESS.getCode()); return response; } /** * 移动位置订阅 */ @RedisRpcMapping("subscribeMobilePosition") public RedisRpcResponse subscribeMobilePosition(RedisRpcRequest request) { JSONObject paramJson = JSONObject.parseObject(request.getParam().toString()); int id = paramJson.getIntValue("id"); int cycle = paramJson.getIntValue("cycle"); int interval = paramJson.getIntValue("interval"); RedisRpcResponse response = request.getResponse(); if (id <= 0) { response.setStatusCode(ErrorCode.ERROR400.getCode()); response.setBody("param error"); return response; } deviceService.subscribeMobilePosition(id, cycle, interval); response.setStatusCode(ErrorCode.SUCCESS.getCode()); return response; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/redisMsg/control/RedisRpcPlatformController.java ================================================ package com.genersoft.iot.vmp.service.redisMsg.control; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.redis.RedisRpcConfig; import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcMessage; import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcRequest; import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcResponse; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import com.genersoft.iot.vmp.gb28181.bean.Platform; import com.genersoft.iot.vmp.gb28181.bean.PlatformChannel; import com.genersoft.iot.vmp.gb28181.controller.bean.ChannelListForRpcParam; import com.genersoft.iot.vmp.gb28181.event.EventPublisher; import com.genersoft.iot.vmp.gb28181.service.IPlatformChannelService; import com.genersoft.iot.vmp.gb28181.service.IPlatformService; import com.genersoft.iot.vmp.service.redisMsg.dto.RedisRpcController; import com.genersoft.iot.vmp.service.redisMsg.dto.RedisRpcMapping; import com.genersoft.iot.vmp.service.redisMsg.dto.RpcController; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import java.util.List; @Component @Slf4j @RedisRpcController("platform") public class RedisRpcPlatformController extends RpcController { @Autowired private UserSetting userSetting; @Autowired private RedisTemplate redisTemplate; @Autowired private IPlatformService platformService; @Autowired private IPlatformChannelService platformChannelService; @Autowired private EventPublisher eventPublisher; private void sendResponse(RedisRpcResponse response){ log.info("[redis-rpc] >> {}", response); response.setToId(userSetting.getServerId()); RedisRpcMessage message = new RedisRpcMessage(); message.setResponse(response); redisTemplate.convertAndSend(RedisRpcConfig.REDIS_REQUEST_CHANNEL_KEY, message); } /** * 更新 */ @RedisRpcMapping("update") public RedisRpcResponse update(RedisRpcRequest request) { Platform platform = JSONObject.parseObject(request.getParam().toString(), Platform.class); RedisRpcResponse response = request.getResponse(); boolean update = platformService.update(platform); response.setStatusCode(ErrorCode.SUCCESS.getCode()); response.setBody(Boolean.toString(update)); return response; } /** * 删除 */ @RedisRpcMapping("delete") public RedisRpcResponse delete(RedisRpcRequest request) { Integer platformId = Integer.parseInt(request.getParam().toString()); RedisRpcResponse response = request.getResponse(); try { boolean result = platformService.delete(platformId); response.setStatusCode(ErrorCode.SUCCESS.getCode()); response.setBody(Boolean.toString(result)); }catch (Exception e) { response.setStatusCode(ErrorCode.SUCCESS.getCode()); response.setBody("false"); } return response; } /** * 主动推送通道 */ @RedisRpcMapping("pushChannel") public RedisRpcResponse pushChannel(RedisRpcRequest request) { Integer platformId = Integer.parseInt(request.getParam().toString()); RedisRpcResponse response = request.getResponse(); try { platformChannelService.pushChannel(platformId); response.setStatusCode(ErrorCode.SUCCESS.getCode()); response.setBody("true"); }catch (Exception e) { response.setStatusCode(ErrorCode.SUCCESS.getCode()); response.setBody("false"); } return response; } /** * 共享通道 */ @RedisRpcMapping("addChannelList") public RedisRpcResponse addChannelList(RedisRpcRequest request) { ChannelListForRpcParam param = JSONObject.parseObject(request.getParam().toString(), ChannelListForRpcParam.class); RedisRpcResponse response = request.getResponse(); try { int result = platformChannelService.addChannels(param.getPlatformId(), param.getChannelIds()); response.setStatusCode(ErrorCode.SUCCESS.getCode()); response.setBody(result + ""); }catch (Exception e) { response.setStatusCode(ErrorCode.SUCCESS.getCode()); response.setBody("0"); } return response; } /** * 移除全部共享通道 */ @RedisRpcMapping("removeAllChannel") public RedisRpcResponse removeAllChannel(RedisRpcRequest request) { Integer platformId = Integer.parseInt(request.getParam().toString()); RedisRpcResponse response = request.getResponse(); try { int result = platformChannelService.removeAllChannel(platformId); response.setStatusCode(ErrorCode.SUCCESS.getCode()); response.setBody(result + ""); }catch (Exception e) { response.setStatusCode(ErrorCode.SUCCESS.getCode()); response.setBody("0"); } return response; } /** * 取消共享通道 */ @RedisRpcMapping("removeChannelList") public RedisRpcResponse removeChannelList(RedisRpcRequest request) { ChannelListForRpcParam param = JSONObject.parseObject(request.getParam().toString(), ChannelListForRpcParam.class); RedisRpcResponse response = request.getResponse(); try { int result = platformChannelService.removeChannels(param.getPlatformId(), param.getChannelIds()); response.setStatusCode(ErrorCode.SUCCESS.getCode()); response.setBody(result + ""); }catch (Exception e) { response.setStatusCode(ErrorCode.SUCCESS.getCode()); response.setBody("0"); } return response; } /** * 自定义通道 */ @RedisRpcMapping("updateCustomChannel") public RedisRpcResponse updateCustomChannel(RedisRpcRequest request) { PlatformChannel param = JSONObject.parseObject(request.getParam().toString(), PlatformChannel.class); RedisRpcResponse response = request.getResponse(); try { platformChannelService.updateCustomChannel(param); response.setStatusCode(ErrorCode.SUCCESS.getCode()); response.setBody("true"); }catch (Exception e) { response.setStatusCode(ErrorCode.SUCCESS.getCode()); response.setBody("false"); } return response; } /** * 目录更新推送 */ @RedisRpcMapping("catalogEventPublish") public RedisRpcResponse catalogEventPublish(RedisRpcRequest request) { JSONObject jsonObject = JSONObject.parseObject(request.getParam().toString()); Platform platform = jsonObject.getObject("platform", Platform.class); List channels = jsonObject.getJSONArray("channels").toJavaList(CommonGBChannel.class); String type = jsonObject.getString("type"); eventPublisher.catalogEventPublish(platform, channels, type); RedisRpcResponse response = request.getResponse(); response.setStatusCode(ErrorCode.SUCCESS.getCode()); return response; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/redisMsg/control/RedisRpcSendRtpController.java ================================================ package com.genersoft.iot.vmp.service.redisMsg.control; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcRequest; import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcResponse; import com.genersoft.iot.vmp.gb28181.bean.SendRtpInfo; import com.genersoft.iot.vmp.gb28181.session.SSRCFactory; import com.genersoft.iot.vmp.media.bean.MediaInfo; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.service.IMediaServerService; import com.genersoft.iot.vmp.service.ISendRtpServerService; import com.genersoft.iot.vmp.service.redisMsg.dto.RedisRpcController; import com.genersoft.iot.vmp.service.redisMsg.dto.RedisRpcMapping; import com.genersoft.iot.vmp.service.redisMsg.dto.RpcController; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component @Slf4j @RedisRpcController("sendRtp") public class RedisRpcSendRtpController extends RpcController { @Autowired private SSRCFactory ssrcFactory; @Autowired private IMediaServerService mediaServerService; @Autowired private ISendRtpServerService sendRtpServerService; @Autowired private UserSetting userSetting; /** * 获取发流的信息 */ @RedisRpcMapping("getSendRtpItem") public RedisRpcResponse getSendRtpItem(RedisRpcRequest request) { String callId = request.getParam().toString(); SendRtpInfo sendRtpItem = sendRtpServerService.queryByCallId(callId); if (sendRtpItem == null) { log.info("[redis-rpc] 获取发流的信息, 未找到redis中的发流信息, callId:{}", callId); RedisRpcResponse response = request.getResponse(); response.setStatusCode(ErrorCode.SUCCESS.getCode()); return response; } log.info("[redis-rpc] 获取发流的信息: {}/{}, 目标地址: {}:{}", sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getIp(), sendRtpItem.getPort()); // 查询本级是否有这个流 MediaServer mediaServerItem = mediaServerService.getMediaServerByAppAndStream(sendRtpItem.getApp(), sendRtpItem.getStream()); if (mediaServerItem == null) { RedisRpcResponse response = request.getResponse(); response.setStatusCode(ErrorCode.SUCCESS.getCode()); } // 自平台内容 int localPort = sendRtpServerService.getNextPort(mediaServerItem); if (localPort <= 0) { log.info("[redis-rpc] getSendRtpItem->服务器端口资源不足" ); RedisRpcResponse response = request.getResponse(); response.setStatusCode(ErrorCode.SUCCESS.getCode()); } // 写入redis, 超时时回复 sendRtpItem.setStatus(1); sendRtpItem.setServerId(userSetting.getServerId()); sendRtpItem.setLocalIp(mediaServerItem.getSdpIp()); if (sendRtpItem.getSsrc() == null) { // 上级平台点播时不使用上级平台指定的ssrc,使用自定义的ssrc,参考国标文档-点播外域设备媒体流SSRC处理方式 String ssrc = "Play".equalsIgnoreCase(sendRtpItem.getSessionName()) ? ssrcFactory.getPlaySsrc(mediaServerItem.getId()) : ssrcFactory.getPlayBackSsrc(mediaServerItem.getId()); sendRtpItem.setSsrc(ssrc); } sendRtpServerService.update(sendRtpItem); RedisRpcResponse response = request.getResponse(); response.setStatusCode(ErrorCode.SUCCESS.getCode()); response.setBody(callId); return response; } /** * 开始发流 */ @RedisRpcMapping("startSendRtp") public RedisRpcResponse startSendRtp(RedisRpcRequest request) { String callId = request.getParam().toString(); SendRtpInfo sendRtpItem = sendRtpServerService.queryByCallId(callId); RedisRpcResponse response = request.getResponse(); response.setStatusCode(ErrorCode.SUCCESS.getCode()); if (sendRtpItem == null) { log.info("[redis-rpc] 开始发流, 未找到redis中的发流信息, callId:{}", callId); WVPResult wvpResult = WVPResult.fail(ErrorCode.ERROR100.getCode(), "未找到redis中的发流信息"); response.setBody(wvpResult); return response; } log.info("[redis-rpc] 开始发流: {}/{}, 目标地址: {}:{}", sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getIp(), sendRtpItem.getPort()); MediaServer mediaServer = mediaServerService.getOne(sendRtpItem.getMediaServerId()); if (mediaServer == null) { log.info("[redis-rpc] startSendRtp->未找到MediaServer: {}", sendRtpItem.getMediaServerId() ); WVPResult wvpResult = WVPResult.fail(ErrorCode.ERROR100.getCode(), "未找到MediaServer"); response.setBody(wvpResult); return response; } MediaInfo mediaInfo = mediaServerService.getMediaInfo(mediaServer, sendRtpItem.getApp(), sendRtpItem.getStream()); if (mediaInfo == null) { log.info("[redis-rpc] startSendRtp->流不在线: {}/{}", sendRtpItem.getApp(), sendRtpItem.getStream() ); WVPResult wvpResult = WVPResult.fail(ErrorCode.ERROR100.getCode(), "流不在线"); response.setBody(wvpResult); return response; } try { mediaServerService.startSendRtp(mediaServer, sendRtpItem); }catch (ControllerException exception) { log.info("[redis-rpc] 发流失败: {}/{}, 目标地址: {}:{}, {}", sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getIp(), sendRtpItem.getPort(), exception.getMsg()); WVPResult wvpResult = WVPResult.fail(exception.getCode(), exception.getMsg()); response.setBody(wvpResult); return response; } log.info("[redis-rpc] 发流成功: {}/{}, 目标地址: {}:{}", sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getIp(), sendRtpItem.getPort()); WVPResult wvpResult = WVPResult.success(); response.setBody(wvpResult); return response; } /** * 停止发流 */ @RedisRpcMapping("stopSendRtp") public RedisRpcResponse stopSendRtp(RedisRpcRequest request) { String callId = request.getParam().toString(); SendRtpInfo sendRtpItem = sendRtpServerService.queryByCallId(callId); RedisRpcResponse response = request.getResponse(); response.setStatusCode(ErrorCode.SUCCESS.getCode()); if (sendRtpItem == null) { log.info("[redis-rpc] 停止推流, 未找到redis中的发流信息, key:{}", callId); WVPResult wvpResult = WVPResult.fail(ErrorCode.ERROR100.getCode(), "未找到redis中的发流信息"); response.setBody(wvpResult); return response; } log.info("[redis-rpc] 停止推流: {}/{}, 目标地址: {}:{}", sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getIp(), sendRtpItem.getPort() ); MediaServer mediaServer = mediaServerService.getOne(sendRtpItem.getMediaServerId()); if (mediaServer == null) { log.info("[redis-rpc] stopSendRtp->未找到MediaServer: {}", sendRtpItem.getMediaServerId() ); WVPResult wvpResult = WVPResult.fail(ErrorCode.ERROR100.getCode(), "未找到MediaServer"); response.setBody(wvpResult); return response; } try { mediaServerService.stopSendRtp(mediaServer, sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getSsrc()); }catch (ControllerException exception) { log.info("[redis-rpc] 停止推流失败: {}/{}, 目标地址: {}:{}, code: {}, msg: {}", sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getIp(), sendRtpItem.getPort(), exception.getCode(), exception.getMsg() ); response.setBody(WVPResult.fail(exception.getCode(), exception.getMsg())); return response; } log.info("[redis-rpc] 停止推流成功: {}/{}, 目标地址: {}:{}", sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getIp(), sendRtpItem.getPort() ); response.setBody(WVPResult.success()); return response; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/redisMsg/control/RedisRpcStreamProxyController.java ================================================ package com.genersoft.iot.vmp.service.redisMsg.control; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.redis.RedisRpcConfig; import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcMessage; import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcRequest; import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcResponse; import com.genersoft.iot.vmp.service.redisMsg.dto.RedisRpcController; import com.genersoft.iot.vmp.service.redisMsg.dto.RedisRpcMapping; import com.genersoft.iot.vmp.service.redisMsg.dto.RpcController; import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy; import com.genersoft.iot.vmp.streamProxy.service.IStreamProxyPlayService; import com.genersoft.iot.vmp.streamProxy.service.IStreamProxyService; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; @Component @Slf4j @RedisRpcController("streamProxy") public class RedisRpcStreamProxyController extends RpcController { @Autowired private UserSetting userSetting; @Autowired private RedisTemplate redisTemplate; @Autowired private IStreamProxyPlayService streamProxyPlayService; @Autowired private IStreamProxyService streamProxyService; private void sendResponse(RedisRpcResponse response){ log.info("[redis-rpc] >> {}", response); response.setToId(userSetting.getServerId()); RedisRpcMessage message = new RedisRpcMessage(); message.setResponse(response); redisTemplate.convertAndSend(RedisRpcConfig.REDIS_REQUEST_CHANNEL_KEY, message); } /** * 播放 */ @RedisRpcMapping("play") public RedisRpcResponse play(RedisRpcRequest request) { int id = Integer.parseInt(request.getParam().toString()); RedisRpcResponse response = request.getResponse(); if (id <= 0) { response.setStatusCode(ErrorCode.ERROR400.getCode()); response.setBody("param error"); return response; } StreamProxy streamProxy = streamProxyService.getStreamProxy(id); if (streamProxy == null) { response.setStatusCode(ErrorCode.ERROR400.getCode()); response.setBody("param error"); return response; } streamProxyPlayService.startProxy(streamProxy, (code, msg, streamInfo) -> { response.setStatusCode(code); response.setBody(JSONObject.toJSONString(streamInfo)); sendResponse(response); }); return null; } /** * 停止 */ @RedisRpcMapping("stop") public RedisRpcResponse stop(RedisRpcRequest request) { int id = Integer.parseInt(request.getParam().toString()); RedisRpcResponse response = request.getResponse(); if (id <= 0) { response.setStatusCode(ErrorCode.ERROR400.getCode()); response.setBody("param error"); return response; } StreamProxy streamProxy = streamProxyService.getStreamProxy(id); if (streamProxy == null) { response.setStatusCode(ErrorCode.ERROR400.getCode()); response.setBody("param error"); return response; } streamProxyPlayService.stopProxy(streamProxy); response.setStatusCode(ErrorCode.SUCCESS.getCode()); return response; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/redisMsg/control/RedisRpcStreamPushController.java ================================================ package com.genersoft.iot.vmp.service.redisMsg.control; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.redis.RedisRpcConfig; import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcMessage; import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcRequest; import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcResponse; import com.genersoft.iot.vmp.gb28181.bean.SendRtpInfo; import com.genersoft.iot.vmp.gb28181.session.SSRCFactory; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.event.hook.Hook; import com.genersoft.iot.vmp.media.event.hook.HookSubscribe; import com.genersoft.iot.vmp.media.event.hook.HookType; import com.genersoft.iot.vmp.media.service.IMediaServerService; import com.genersoft.iot.vmp.service.ISendRtpServerService; import com.genersoft.iot.vmp.service.redisMsg.dto.RedisRpcController; import com.genersoft.iot.vmp.service.redisMsg.dto.RedisRpcMapping; import com.genersoft.iot.vmp.service.redisMsg.dto.RpcController; import com.genersoft.iot.vmp.streamPush.service.IStreamPushPlayService; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; @Component @Slf4j @RedisRpcController("streamPush") public class RedisRpcStreamPushController extends RpcController { @Autowired private SSRCFactory ssrcFactory; @Autowired private IMediaServerService mediaServerService; @Autowired private ISendRtpServerService sendRtpServerService; @Autowired private UserSetting userSetting; @Autowired private HookSubscribe hookSubscribe; @Autowired private RedisTemplate redisTemplate; @Autowired private IStreamPushPlayService streamPushPlayService; private void sendResponse(RedisRpcResponse response){ log.info("[redis-rpc] >> {}", response); response.setToId(userSetting.getServerId()); RedisRpcMessage message = new RedisRpcMessage(); message.setResponse(response); redisTemplate.convertAndSend(RedisRpcConfig.REDIS_REQUEST_CHANNEL_KEY, message); } /** * 监听流上线 */ @RedisRpcMapping("waitePushStreamOnline") public RedisRpcResponse waitePushStreamOnline(RedisRpcRequest request) { SendRtpInfo sendRtpItem = JSONObject.parseObject(request.getParam().toString(), SendRtpInfo.class); log.info("[redis-rpc] 监听流上线: {}/{}, 目标地址: {}:{}", sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getIp(), sendRtpItem.getPort()); // 查询本级是否有这个流 MediaServer mediaServer = mediaServerService.getMediaServerByAppAndStream(sendRtpItem.getApp(), sendRtpItem.getStream()); if (mediaServer != null) { log.info("[redis-rpc] 监听流上线时发现流已存在直接返回: {}/{}, 目标地址: {}:{}", sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getIp(), sendRtpItem.getPort() ); // 读取redis中的上级点播信息,生成sendRtpItm发送出去 if (sendRtpItem.getSsrc() == null) { // 上级平台点播时不使用上级平台指定的ssrc,使用自定义的ssrc,参考国标文档-点播外域设备媒体流SSRC处理方式 String ssrc = "Play".equalsIgnoreCase(sendRtpItem.getSessionName()) ? ssrcFactory.getPlaySsrc(mediaServer.getId()) : ssrcFactory.getPlayBackSsrc(mediaServer.getId()); sendRtpItem.setSsrc(ssrc); } sendRtpItem.setMediaServerId(mediaServer.getId()); sendRtpItem.setLocalIp(mediaServer.getSdpIp()); sendRtpItem.setServerId(userSetting.getServerId()); sendRtpServerService.update(sendRtpItem); RedisRpcResponse response = request.getResponse(); response.setBody(sendRtpItem.getChannelId()); response.setStatusCode(ErrorCode.SUCCESS.getCode()); } // 监听流上线。 流上线直接发送sendRtpItem消息给实际的信令处理者 Hook hook = Hook.getInstance(HookType.on_media_arrival, sendRtpItem.getApp(), sendRtpItem.getStream(), null); hookSubscribe.addSubscribe(hook, (hookData) -> { log.info("[redis-rpc] 监听流上线,流已上线: {}/{}, 目标地址: {}:{}", sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getIp(), sendRtpItem.getPort()); // 读取redis中的上级点播信息,生成sendRtpItm发送出去 if (sendRtpItem.getSsrc() == null) { // 上级平台点播时不使用上级平台指定的ssrc,使用自定义的ssrc,参考国标文档-点播外域设备媒体流SSRC处理方式 String ssrc = "Play".equalsIgnoreCase(sendRtpItem.getSessionName()) ? ssrcFactory.getPlaySsrc(hookData.getMediaServer().getId()) : ssrcFactory.getPlayBackSsrc(hookData.getMediaServer().getId()); sendRtpItem.setSsrc(ssrc); } sendRtpItem.setMediaServerId(hookData.getMediaServer().getId()); sendRtpItem.setLocalIp(hookData.getMediaServer().getSdpIp()); sendRtpItem.setServerId(userSetting.getServerId()); redisTemplate.opsForValue().set(sendRtpItem.getChannelId(), sendRtpItem); RedisRpcResponse response = request.getResponse(); response.setBody(sendRtpItem.getChannelId()); response.setStatusCode(ErrorCode.SUCCESS.getCode()); // 手动发送结果 sendResponse(response); hookSubscribe.removeSubscribe(hook); }); return null; } /** * 监听流上线 */ @RedisRpcMapping("onStreamOnlineEvent") public RedisRpcResponse onStreamOnlineEvent(RedisRpcRequest request) { StreamInfo streamInfo = JSONObject.parseObject(request.getParam().toString(), StreamInfo.class); log.info("[redis-rpc] 监听流信息,等待流上线: {}/{}", streamInfo.getApp(), streamInfo.getStream()); // 查询本级是否有这个流 StreamInfo streamInfoInServer = mediaServerService.getMediaByAppAndStream(streamInfo.getApp(), streamInfo.getStream()); if (streamInfoInServer != null) { log.info("[redis-rpc] 监听流上线时发现流已存在直接返回: {}/{}", streamInfo.getApp(), streamInfo.getStream()); RedisRpcResponse response = request.getResponse(); response.setBody(JSONObject.toJSONString(streamInfoInServer)); response.setStatusCode(ErrorCode.SUCCESS.getCode()); return response; } // 监听流上线。 流上线直接发送sendRtpItem消息给实际的信令处理者 Hook hook = Hook.getInstance(HookType.on_media_arrival, streamInfo.getApp(), streamInfo.getStream()); hookSubscribe.addSubscribe(hook, (hookData) -> { log.info("[redis-rpc] 监听流上线,流已上线: {}/{}", streamInfo.getApp(), streamInfo.getStream()); // 读取redis中的上级点播信息,生成sendRtpItm发送出去 RedisRpcResponse response = request.getResponse(); StreamInfo streamInfoByAppAndStream = mediaServerService.getStreamInfoByAppAndStream(hookData.getMediaServer(), streamInfo.getApp(), streamInfo.getStream(), hookData.getMediaInfo(), hookData.getMediaInfo() != null ? hookData.getMediaInfo().getCallId() : null); response.setBody(JSONObject.toJSONString(streamInfoByAppAndStream)); response.setStatusCode(ErrorCode.SUCCESS.getCode()); // 手动发送结果 sendResponse(response); hookSubscribe.removeSubscribe(hook); }); return null; } /** * 停止监听流上线 */ @RedisRpcMapping("stopWaitePushStreamOnline") public RedisRpcResponse stopWaitePushStreamOnline(RedisRpcRequest request) { SendRtpInfo sendRtpItem = JSONObject.parseObject(request.getParam().toString(), SendRtpInfo.class); log.info("[redis-rpc] 停止监听流上线: {}/{}, 目标地址: {}:{}", sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getIp(), sendRtpItem.getPort() ); // 监听流上线。 流上线直接发送sendRtpItem消息给实际的信令处理者 Hook hook = Hook.getInstance(HookType.on_media_arrival, sendRtpItem.getApp(), sendRtpItem.getStream(), null); hookSubscribe.removeSubscribe(hook); RedisRpcResponse response = request.getResponse(); response.setStatusCode(ErrorCode.SUCCESS.getCode()); return response; } /** * 停止监听流上线 */ @RedisRpcMapping("unPushStreamOnlineEvent") public RedisRpcResponse unPushStreamOnlineEvent(RedisRpcRequest request) { StreamInfo streamInfo = JSONObject.parseObject(request.getParam().toString(), StreamInfo.class); log.info("[redis-rpc] 停止监听流上线: {}/{}", streamInfo.getApp(), streamInfo.getStream()); // 监听流上线。 流上线直接发送sendRtpItem消息给实际的信令处理者 Hook hook = Hook.getInstance(HookType.on_media_arrival, streamInfo.getApp(), streamInfo.getStream(), null); hookSubscribe.removeSubscribe(hook); RedisRpcResponse response = request.getResponse(); response.setStatusCode(ErrorCode.SUCCESS.getCode()); return response; } /** * 停止监听流上线 */ @RedisRpcMapping("play") public RedisRpcResponse play(RedisRpcRequest request) { JSONObject paramJson = JSONObject.parseObject(request.getParam().toString()); int id = paramJson.getInteger("id"); RedisRpcResponse response = request.getResponse(); if (id <= 0) { response.setStatusCode(ErrorCode.ERROR400.getCode()); response.setBody("param error"); return response; } try { streamPushPlayService.start(id, (code, msg, data) -> { if (code == ErrorCode.SUCCESS.getCode()) { response.setStatusCode(ErrorCode.SUCCESS.getCode()); response.setBody(JSONObject.toJSONString(data)); sendResponse(response); } }, null, null); }catch (IllegalArgumentException e) { response.setStatusCode(ErrorCode.ERROR100.getCode()); response.setBody(e.getMessage()); sendResponse(response); } return null; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/redisMsg/dto/RedisRpcController.java ================================================ package com.genersoft.iot.vmp.service.redisMsg.dto; import java.lang.annotation.*; @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RedisRpcController { /** * 请求路径 */ String value() default ""; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/redisMsg/dto/RedisRpcMapping.java ================================================ package com.genersoft.iot.vmp.service.redisMsg.dto; import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RedisRpcMapping { /** * 请求路径 */ String value() default ""; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/redisMsg/dto/RpcController.java ================================================ package com.genersoft.iot.vmp.service.redisMsg.dto; import com.genersoft.iot.vmp.conf.redis.RedisRpcConfig; import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcClassHandler; import org.springframework.beans.factory.annotation.Autowired; import jakarta.annotation.PostConstruct; import java.lang.reflect.Method; public class RpcController { @Autowired private RedisRpcConfig redisRpcConfig; @PostConstruct public void init() { String controllerPath = this.getClass().getAnnotation(RedisRpcController.class).value(); // 扫描其下的方法 Method[] methods = this.getClass().getDeclaredMethods(); for (Method method : methods) { RedisRpcMapping annotation = method.getAnnotation(RedisRpcMapping.class); if (annotation != null) { String methodPath = annotation.value(); if (methodPath != null) { redisRpcConfig.addHandler(controllerPath + "/" + methodPath, new RedisRpcClassHandler(this, method)); } } } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/redisMsg/service/RedisRpcPlayServiceImpl.java ================================================ package com.genersoft.iot.vmp.service.redisMsg.service; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.common.InviteSessionType; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.conf.redis.RedisRpcConfig; import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcRequest; import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcResponse; import com.genersoft.iot.vmp.gb28181.bean.RecordInfo; import com.genersoft.iot.vmp.service.bean.DownloadFileInfo; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.service.bean.InviteErrorCode; import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcPlayService; import com.genersoft.iot.vmp.vmanager.bean.AudioBroadcastResult; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.concurrent.TimeUnit; @Slf4j @Service public class RedisRpcPlayServiceImpl implements IRedisRpcPlayService { @Autowired private RedisRpcConfig redisRpcConfig; @Autowired private UserSetting userSetting; private RedisRpcRequest buildRequest(String uri, Object param) { RedisRpcRequest request = new RedisRpcRequest(); request.setFromId(userSetting.getServerId()); request.setParam(param); request.setUri(uri); return request; } @Override public void play(String serverId, Integer channelId, ErrorCallback callback) { RedisRpcRequest request = buildRequest("channel/play", channelId); request.setToId(serverId); RedisRpcResponse response = redisRpcConfig.request(request, userSetting.getPlayTimeout(), TimeUnit.MILLISECONDS); if (response == null) { callback.run(ErrorCode.ERROR100.getCode(), ErrorCode.ERROR100.getMsg(), null); }else { if (response.getStatusCode() == ErrorCode.SUCCESS.getCode()) { StreamInfo streamInfo = JSON.parseObject(response.getBody().toString(), StreamInfo.class); callback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), streamInfo); }else { callback.run(response.getStatusCode(), response.getBody().toString(), null); } } } @Override public void stop(String serverId, InviteSessionType type, int channelId, String stream) { JSONObject jsonObject = new JSONObject(); jsonObject.put("channelId", channelId); jsonObject.put("stream", stream); jsonObject.put("inviteSessionType", type); RedisRpcRequest request = buildRequest("channel/stop", jsonObject.toJSONString()); request.setToId(serverId); RedisRpcResponse response = redisRpcConfig.request(request, 50, TimeUnit.MICROSECONDS); if (response == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), ErrorCode.ERROR100.getMsg()); }else { if (response.getStatusCode() != ErrorCode.SUCCESS.getCode()) { throw new ControllerException(ErrorCode.ERROR100.getCode(), ErrorCode.ERROR100.getMsg()); } } } @Override public void queryRecordInfo(String serverId, Integer channelId, String startTime, String endTime, ErrorCallback callback) { JSONObject jsonObject = new JSONObject(); jsonObject.put("channelId", channelId); jsonObject.put("startTime", startTime); jsonObject.put("endTime", endTime); RedisRpcRequest request = buildRequest("channel/queryRecordInfo", jsonObject); request.setToId(serverId); RedisRpcResponse response = redisRpcConfig.request(request, userSetting.getRecordInfoTimeout(), TimeUnit.MILLISECONDS); if (response == null) { callback.run(ErrorCode.ERROR100.getCode(), ErrorCode.ERROR100.getMsg(), null); }else { if (response.getStatusCode() == ErrorCode.SUCCESS.getCode()) { RecordInfo recordInfo = JSON.parseObject(response.getBody().toString(), RecordInfo.class); callback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), recordInfo); }else { callback.run(response.getStatusCode(), response.getBody().toString(), null); } } } @Override public void playback(String serverId, Integer channelId, String startTime, String endTime, ErrorCallback callback) { JSONObject jsonObject = new JSONObject(); jsonObject.put("channelId", channelId); jsonObject.put("startTime", startTime); jsonObject.put("endTime", endTime); RedisRpcRequest request = buildRequest("channel/playback", jsonObject.toString()); request.setToId(serverId); RedisRpcResponse response = redisRpcConfig.request(request, userSetting.getPlayTimeout(), TimeUnit.MILLISECONDS); if (response == null) { callback.run(ErrorCode.ERROR100.getCode(), ErrorCode.ERROR100.getMsg(), null); }else { if (response.getStatusCode() == ErrorCode.SUCCESS.getCode()) { StreamInfo streamInfo = JSON.parseObject(response.getBody().toString(), StreamInfo.class); callback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), streamInfo); }else { callback.run(response.getStatusCode(), response.getBody().toString(), null); } } } @Override public void playbackPause(String serverId, String streamId) { RedisRpcRequest request = buildRequest("channel/playbackPause", streamId); request.setToId(serverId); RedisRpcResponse response = redisRpcConfig.request(request, 5, TimeUnit.SECONDS); if (response == null) { log.info("[RPC 暂停回放] 失败, streamId: {}", streamId); }else { if (response.getStatusCode() != ErrorCode.SUCCESS.getCode()) { log.info("[RPC 暂停回放] 失败, {}, streamId: {}", response.getBody(), streamId); } } } @Override public void playbackResume(String serverId, String streamId) { RedisRpcRequest request = buildRequest("channel/playbackResume", streamId); request.setToId(serverId); RedisRpcResponse response = redisRpcConfig.request(request, 5, TimeUnit.SECONDS); if (response == null) { log.info("[RPC 恢复回放] 失败, streamId: {}", streamId); }else { if (response.getStatusCode() != ErrorCode.SUCCESS.getCode()) { log.info("[RPC 恢复回放] 失败, {}, streamId: {}", response.getBody(), streamId); } } } @Override public void download(String serverId, Integer channelId, String startTime, String endTime, int downloadSpeed, ErrorCallback callback) { JSONObject jsonObject = new JSONObject(); jsonObject.put("channelId", channelId); jsonObject.put("startTime", startTime); jsonObject.put("endTime", endTime); jsonObject.put("downloadSpeed", downloadSpeed); RedisRpcRequest request = buildRequest("channel/download", jsonObject.toString()); request.setToId(serverId); RedisRpcResponse response = redisRpcConfig.request(request, userSetting.getPlayTimeout(), TimeUnit.MILLISECONDS); if (response == null) { callback.run(ErrorCode.ERROR100.getCode(), ErrorCode.ERROR100.getMsg(), null); }else { if (response.getStatusCode() == ErrorCode.SUCCESS.getCode()) { StreamInfo streamInfo = JSON.parseObject(response.getBody().toString(), StreamInfo.class); callback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), streamInfo); }else { callback.run(response.getStatusCode(), response.getBody().toString(), null); } } } @Override public String frontEndCommand(String serverId, Integer channelId, int cmdCode, int parameter1, int parameter2, int combindCode2) { JSONObject jsonObject = new JSONObject(); jsonObject.put("channelId", channelId); jsonObject.put("cmdCode", cmdCode); jsonObject.put("parameter1", parameter1); jsonObject.put("parameter2", parameter2); jsonObject.put("combindCode2", combindCode2); RedisRpcRequest request = buildRequest("channel/ptz/frontEndCommand", jsonObject.toString()); request.setToId(serverId); RedisRpcResponse response = redisRpcConfig.request(request, userSetting.getPlayTimeout(), TimeUnit.MILLISECONDS); if (response == null) { return ErrorCode.ERROR100.getMsg(); }else { if (response.getStatusCode() != ErrorCode.SUCCESS.getCode()) { return response.getBody().toString(); } } return null; } @Override public void playPush(String serverId, Integer id, ErrorCallback callback) { JSONObject jsonObject = new JSONObject(); jsonObject.put("id", id); RedisRpcRequest request = buildRequest("streamPush/play", jsonObject); request.setToId(serverId); RedisRpcResponse response = redisRpcConfig.request(request, userSetting.getPlayTimeout(), TimeUnit.SECONDS); if (response == null) { callback.run(ErrorCode.ERROR100.getCode(), ErrorCode.ERROR100.getMsg(), null); }else { if (response.getStatusCode() == ErrorCode.SUCCESS.getCode()) { StreamInfo streamInfo = JSON.parseObject(response.getBody().toString(), StreamInfo.class); callback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), streamInfo); }else { callback.run(response.getStatusCode(), response.getBody().toString(), null); } } } @Override public void playProxy(String serverId, int id, ErrorCallback callback) { RedisRpcRequest request = buildRequest("streamProxy/play", id); request.setToId(serverId); RedisRpcResponse response = redisRpcConfig.request(request, userSetting.getPlayTimeout(), TimeUnit.SECONDS); if (response == null) { callback.run(ErrorCode.ERROR100.getCode(), ErrorCode.ERROR100.getMsg(), null); }else { if (response.getStatusCode() == ErrorCode.SUCCESS.getCode()) { StreamInfo streamInfo = JSON.parseObject(response.getBody().toString(), StreamInfo.class); callback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), streamInfo); }else { callback.run(response.getStatusCode(), response.getBody().toString(), null); } } } @Override public void stopProxy(String serverId, int id) { RedisRpcRequest request = buildRequest("streamProxy/stop", id); RedisRpcResponse response = redisRpcConfig.request(request, userSetting.getPlayTimeout(), TimeUnit.SECONDS); if (response != null && response.getStatusCode() == ErrorCode.SUCCESS.getCode()) { log.info("[rpc 拉流代理] 停止成功: id: {}", id); }else { log.info("[rpc 拉流代理] 停止失败 id: {}", id); } } @Override public DownloadFileInfo getRecordPlayUrl(String serverId, Integer recordId) { RedisRpcRequest request = buildRequest("cloudRecord/play", recordId); RedisRpcResponse response = redisRpcConfig.request(request, userSetting.getPlayTimeout(), TimeUnit.SECONDS); if (response != null && response.getStatusCode() == ErrorCode.SUCCESS.getCode()) { return JSON.parseObject(response.getBody().toString(), DownloadFileInfo.class); } return null; } @Override public AudioBroadcastResult audioBroadcast(String serverId, String deviceId, String channelDeviceId, Boolean broadcastMode) { JSONObject jsonObject = new JSONObject(); jsonObject.put("deviceId", deviceId); jsonObject.put("channelDeviceId", channelDeviceId); jsonObject.put("broadcastMode", broadcastMode); RedisRpcRequest request = buildRequest("devicePlay/audioBroadcast", jsonObject.toString()); request.setToId(serverId); RedisRpcResponse response = redisRpcConfig.request(request, userSetting.getPlayTimeout(), TimeUnit.SECONDS); if (response != null && response.getStatusCode() == ErrorCode.SUCCESS.getCode()) { return JSON.parseObject(response.getBody().toString(), AudioBroadcastResult.class); } return null; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/service/redisMsg/service/RedisRpcServiceImpl.java ================================================ package com.genersoft.iot.vmp.service.redisMsg.service; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.common.CommonCallback; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.conf.redis.RedisRpcConfig; import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcRequest; import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcResponse; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.controller.bean.ChannelListForRpcParam; import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent; import com.genersoft.iot.vmp.gb28181.session.SSRCFactory; import com.genersoft.iot.vmp.media.event.hook.Hook; import com.genersoft.iot.vmp.media.event.hook.HookSubscribe; import com.genersoft.iot.vmp.media.event.hook.HookType; import com.genersoft.iot.vmp.media.service.IMediaServerService; import com.genersoft.iot.vmp.service.ISendRtpServerService; import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcService; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import java.util.List; import java.util.concurrent.TimeUnit; @Slf4j @Service public class RedisRpcServiceImpl implements IRedisRpcService { @Autowired private RedisRpcConfig redisRpcConfig; @Autowired private UserSetting userSetting; @Autowired private HookSubscribe hookSubscribe; @Autowired private SSRCFactory ssrcFactory; @Autowired private RedisTemplate redisTemplate; @Autowired private IMediaServerService mediaServerService; @Autowired private ISendRtpServerService sendRtpServerService; private RedisRpcRequest buildRequest(String uri, Object param) { RedisRpcRequest request = new RedisRpcRequest(); request.setFromId(userSetting.getServerId()); request.setParam(param); request.setUri(uri); return request; } @Override public SendRtpInfo getSendRtpItem(String callId) { RedisRpcRequest request = buildRequest("sendRtp/getSendRtpItem", callId); RedisRpcResponse response = redisRpcConfig.request(request, 10, TimeUnit.MILLISECONDS); if (response.getBody() == null) { return null; } return (SendRtpInfo)redisTemplate.opsForValue().get(response.getBody().toString()); } @Override public WVPResult startSendRtp(String callId, SendRtpInfo sendRtpItem) { log.info("[请求其他WVP] 开始推流,wvp:{}, {}/{}", sendRtpItem.getServerId(), sendRtpItem.getApp(), sendRtpItem.getStream()); RedisRpcRequest request = buildRequest("sendRtp/startSendRtp", callId); request.setToId(sendRtpItem.getServerId()); RedisRpcResponse response = redisRpcConfig.request(request, 10, TimeUnit.MILLISECONDS); return JSON.parseObject(response.getBody().toString(), WVPResult.class); } @Override public WVPResult stopSendRtp(String callId) { SendRtpInfo sendRtpItem = (SendRtpInfo)redisTemplate.opsForValue().get(callId); if (sendRtpItem == null) { log.info("[请求其他WVP] 停止推流, 未找到redis中的发流信息, key:{}", callId); return WVPResult.fail(ErrorCode.ERROR100.getCode(), "未找到发流信息"); } log.info("[请求其他WVP] 停止推流,wvp:{}, {}/{}", sendRtpItem.getServerId(), sendRtpItem.getApp(), sendRtpItem.getStream()); RedisRpcRequest request = buildRequest("sendRtp/stopSendRtp", callId); request.setToId(sendRtpItem.getServerId()); RedisRpcResponse response = redisRpcConfig.request(request, 10, TimeUnit.MILLISECONDS); return JSON.parseObject(response.getBody().toString(), WVPResult.class); } @Override public long waitePushStreamOnline(SendRtpInfo sendRtpItem, CommonCallback callback) { log.info("[请求所有WVP监听流上线] {}/{}", sendRtpItem.getApp(), sendRtpItem.getStream()); // 监听流上线。 流上线直接发送sendRtpItem消息给实际的信令处理者 Hook hook = Hook.getInstance(HookType.on_media_arrival, sendRtpItem.getApp(), sendRtpItem.getStream(), null); RedisRpcRequest request = buildRequest("streamPush/waitePushStreamOnline", sendRtpItem); request.setToId(sendRtpItem.getServerId()); hookSubscribe.addSubscribe(hook, (hookData) -> { // 读取redis中的上级点播信息,生成sendRtpItm发送出去 if (sendRtpItem.getSsrc() == null) { // 上级平台点播时不使用上级平台指定的ssrc,使用自定义的ssrc,参考国标文档-点播外域设备媒体流SSRC处理方式 String ssrc = "Play".equalsIgnoreCase(sendRtpItem.getSessionName()) ? ssrcFactory.getPlaySsrc(hookData.getMediaServer().getId()) : ssrcFactory.getPlayBackSsrc(hookData.getMediaServer().getId()); sendRtpItem.setSsrc(ssrc); } sendRtpItem.setMediaServerId(hookData.getMediaServer().getId()); sendRtpItem.setLocalIp(hookData.getMediaServer().getSdpIp()); sendRtpItem.setServerId(userSetting.getServerId()); sendRtpServerService.update(sendRtpItem); if (callback != null) { callback.run(sendRtpItem.getChannelId()); } hookSubscribe.removeSubscribe(hook); redisRpcConfig.removeCallback(request.getSn()); }); redisRpcConfig.request(request, response -> { if (response.getBody() == null) { log.info("[请求所有WVP监听流上线] 流上线,但是未找到发流信息:{}/{}", sendRtpItem.getApp(), sendRtpItem.getStream()); return; } log.info("[请求所有WVP监听流上线] 流上线 {}/{}->{}", sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.toString()); if (callback != null) { callback.run(Integer.parseInt(response.getBody().toString())); } hookSubscribe.removeSubscribe(hook); }); return request.getSn(); } @Override public void stopWaitePushStreamOnline(SendRtpInfo sendRtpItem) { log.info("[停止WVP监听流上线] {}/{}", sendRtpItem.getApp(), sendRtpItem.getStream()); Hook hook = Hook.getInstance(HookType.on_media_arrival, sendRtpItem.getApp(), sendRtpItem.getStream(), null); hookSubscribe.removeSubscribe(hook); RedisRpcRequest request = buildRequest("streamPush/stopWaitePushStreamOnline", sendRtpItem); request.setToId(sendRtpItem.getServerId()); redisRpcConfig.request(request, 10, TimeUnit.MILLISECONDS); } @Override public void rtpSendStopped(String callId) { SendRtpInfo sendRtpItem = (SendRtpInfo)redisTemplate.opsForValue().get(callId); if (sendRtpItem == null) { log.info("[停止WVP监听流上线] 未找到redis中的发流信息, key:{}", callId); return; } RedisRpcRequest request = buildRequest("streamPush/rtpSendStopped", callId); request.setToId(sendRtpItem.getServerId()); redisRpcConfig.request(request, 10, TimeUnit.MILLISECONDS); } @Override public void removeCallback(long key) { redisRpcConfig.removeCallback(key); } @Override public long onStreamOnlineEvent(String app, String stream, CommonCallback callback) { log.info("[请求所有WVP监听流上线] {}/{}", app, stream); // 监听流上线。 流上线直接发送sendRtpItem消息给实际的信令处理者 Hook hook = Hook.getInstance(HookType.on_media_arrival, app, stream); StreamInfo streamInfoParam = new StreamInfo(); streamInfoParam.setApp(app); streamInfoParam.setStream(stream); RedisRpcRequest request = buildRequest("streamPush/onStreamOnlineEvent", streamInfoParam); hookSubscribe.addSubscribe(hook, (hookData) -> { log.info("[请求所有WVP监听流上线] 监听流上线 {}/{}", app, stream); if (callback != null) { callback.run(mediaServerService.getStreamInfoByAppAndStream(hookData.getMediaServer(), app, stream, hookData.getMediaInfo(), hookData.getMediaInfo() != null ? hookData.getMediaInfo().getCallId() : null)); } hookSubscribe.removeSubscribe(hook); redisRpcConfig.removeCallback(request.getSn()); }); redisRpcConfig.request(request, response -> { if (response.getBody() == null) { log.info("[请求所有WVP监听流上线] 流上线,但是未找到发流信息:{}/{}", app, stream); return; } log.info("[请求所有WVP监听流上线] 流上线 {}/{}", app, stream); if (callback != null) { callback.run(JSON.parseObject(response.getBody().toString(), StreamInfo.class)); } hookSubscribe.removeSubscribe(hook); }); return request.getSn(); } @Override public void unPushStreamOnlineEvent(String app, String stream) { StreamInfo streamInfoParam = new StreamInfo(); streamInfoParam.setApp(app); streamInfoParam.setStream(stream); RedisRpcRequest request = buildRequest("streamPush/unPushStreamOnlineEvent", streamInfoParam); redisRpcConfig.request(request, 10, TimeUnit.MILLISECONDS); } @Override public void subscribeCatalog(int id, int cycle) { JSONObject jsonObject = new JSONObject(); jsonObject.put("id", id); jsonObject.put("cycle", cycle); RedisRpcRequest request = buildRequest("device/subscribeCatalog", jsonObject); redisRpcConfig.request(request, 10, TimeUnit.MILLISECONDS); } @Override public void subscribeMobilePosition(int id, int cycle, int interval) { JSONObject jsonObject = new JSONObject(); jsonObject.put("id", id); jsonObject.put("cycle", cycle); jsonObject.put("interval", cycle); RedisRpcRequest request = buildRequest("device/subscribeMobilePosition", jsonObject); redisRpcConfig.request(request, 10, TimeUnit.MILLISECONDS); } @Override public boolean updatePlatform(String serverId, Platform platform) { RedisRpcRequest request = buildRequest("platform/update", platform); request.setToId(serverId); RedisRpcResponse response = redisRpcConfig.request(request, 40, TimeUnit.MILLISECONDS); if(response == null) { return false; } return Boolean.parseBoolean(response.getBody().toString()); } @Override public boolean deletePlatform(String serverId, Integer platformId) { RedisRpcRequest request = buildRequest("platform/delete", platformId); request.setToId(serverId); RedisRpcResponse response = redisRpcConfig.request(request, 20, TimeUnit.SECONDS); if(response == null) { return false; } return Boolean.parseBoolean(response.getBody().toString()); } @Override public int addPlatformChannelList(String serverId, ChannelListForRpcParam channelListForRpcParam) { RedisRpcRequest request = buildRequest("platform/addChannelList", channelListForRpcParam); request.setToId(serverId); RedisRpcResponse response = redisRpcConfig.request(request, 20, TimeUnit.SECONDS); if(response == null) { return 0; } return Integer.parseInt(response.getBody().toString()); } @Override public int removeAllPlatformChannel(String serverId, Integer platformId) { RedisRpcRequest request = buildRequest("platform/removeAllChannel", platformId); request.setToId(serverId); RedisRpcResponse response = redisRpcConfig.request(request, 20, TimeUnit.SECONDS); if(response == null) { return 0; } return Integer.parseInt(response.getBody().toString()); } @Override public int removePlatformChannelList(String serverId, ChannelListForRpcParam channelListForRpcParam) { RedisRpcRequest request = buildRequest("platform/removeChannelList", channelListForRpcParam); request.setToId(serverId); RedisRpcResponse response = redisRpcConfig.request(request, 20, TimeUnit.SECONDS); if(response == null) { return 0; } return Integer.parseInt(response.getBody().toString()); } @Override public boolean updateCustomPlatformChannel(String serverId, PlatformChannel channel) { RedisRpcRequest request = buildRequest("platform/updateCustomChannel", channel); request.setToId(serverId); RedisRpcResponse response = redisRpcConfig.request(request, 20, TimeUnit.SECONDS); if(response == null) { return false; } return Boolean.parseBoolean(response.getBody().toString()); } @Override public boolean pushPlatformChannel(String serverId, Integer platformId) { RedisRpcRequest request = buildRequest("platform/pushChannel", platformId); request.setToId(serverId); RedisRpcResponse response = redisRpcConfig.request(request, 20, TimeUnit.SECONDS); if(response == null) { return false; } return Boolean.parseBoolean(response.getBody().toString()); } @Override public void catalogEventPublish(String serverId, CatalogEvent event) { JSONObject jsonObject = new JSONObject(); jsonObject.put("platform", event.getPlatform()); jsonObject.put("channels", event.getChannels()); jsonObject.put("type", event.getType()); RedisRpcRequest request = buildRequest("platform/catalogEventPublish", jsonObject); if (serverId != null) { request.setToId(serverId); } redisRpcConfig.request(request, 10, TimeUnit.MILLISECONDS); } @Override public WVPResult devicesSync(String serverId, String deviceId) { RedisRpcRequest request = buildRequest("device/devicesSync", deviceId); request.setToId(serverId); RedisRpcResponse response = redisRpcConfig.request(request, 100, TimeUnit.MILLISECONDS); return JSON.parseObject(response.getBody().toString(), WVPResult.class); } @Override public SyncStatus getChannelSyncStatus(String serverId, String deviceId) { RedisRpcRequest request = buildRequest("device/getChannelSyncStatus", deviceId); request.setToId(serverId); RedisRpcResponse response = redisRpcConfig.request(request, 100, TimeUnit.MILLISECONDS); return JSON.parseObject(response.getBody().toString(), SyncStatus.class); } @Override public WVPResult deviceBasicConfig(String serverId, Device device, BasicParam basicParam) { RedisRpcRequest request = buildRequest("device/deviceBasicConfig", JSONObject.toJSONString(basicParam)); request.setToId(serverId); RedisRpcResponse response = redisRpcConfig.request(request, 50, TimeUnit.MILLISECONDS); return JSON.parseObject(response.getBody().toString(), WVPResult.class); } @Override public WVPResult deviceConfigQuery(String serverId, Device device, String channelId, String configType) { JSONObject jsonObject = new JSONObject(); jsonObject.put("device", device.getDeviceId()); jsonObject.put("channelId", channelId); jsonObject.put("configType", configType); RedisRpcRequest request = buildRequest("device/deviceConfigQuery", jsonObject); request.setToId(serverId); RedisRpcResponse response = redisRpcConfig.request(request, 50, TimeUnit.MILLISECONDS); return JSON.parseObject(response.getBody().toString(), WVPResult.class); } @Override public void teleboot(String serverId, Device device) { RedisRpcRequest request = buildRequest("device/teleboot", device.getDeviceId()); request.setToId(serverId); RedisRpcResponse response = redisRpcConfig.request(request, 50, TimeUnit.MILLISECONDS); if (response.getStatusCode() != ErrorCode.SUCCESS.getCode()) { throw new ControllerException(response.getStatusCode(), response.getBody().toString()); } } @Override public WVPResult recordControl(String serverId, Device device, String channelId, String recordCmdStr) { JSONObject jsonObject = new JSONObject(); jsonObject.put("device", device.getDeviceId()); jsonObject.put("channelId", channelId); jsonObject.put("recordCmdStr", recordCmdStr); RedisRpcRequest request = buildRequest("device/record", jsonObject); request.setToId(serverId); RedisRpcResponse response = redisRpcConfig.request(request, 50, TimeUnit.MILLISECONDS); return JSON.parseObject(response.getBody().toString(), WVPResult.class); } @Override public WVPResult guard(String serverId, Device device, String guardCmdStr) { JSONObject jsonObject = new JSONObject(); jsonObject.put("device", device.getDeviceId()); jsonObject.put("guardCmdStr", guardCmdStr); RedisRpcRequest request = buildRequest("device/guard", jsonObject); request.setToId(serverId); RedisRpcResponse response = redisRpcConfig.request(request, 50, TimeUnit.MILLISECONDS); return JSON.parseObject(response.getBody().toString(), WVPResult.class); } @Override public WVPResult resetAlarm(String serverId, Device device, String channelId, String alarmMethod, String alarmType) { JSONObject jsonObject = new JSONObject(); jsonObject.put("device", device.getDeviceId()); jsonObject.put("channelId", channelId); jsonObject.put("alarmMethod", alarmMethod); jsonObject.put("alarmType", alarmType); RedisRpcRequest request = buildRequest("device/resetAlarm", jsonObject); request.setToId(serverId); RedisRpcResponse response = redisRpcConfig.request(request, 50, TimeUnit.MILLISECONDS); return JSON.parseObject(response.getBody().toString(), WVPResult.class); } @Override public void iFrame(String serverId, Device device, String channelId) { JSONObject jsonObject = new JSONObject(); jsonObject.put("device", device.getDeviceId()); jsonObject.put("channelId", channelId); RedisRpcRequest request = buildRequest("device/iFrame", jsonObject); request.setToId(serverId); redisRpcConfig.request(request, 50, TimeUnit.MILLISECONDS); } @Override public WVPResult homePosition(String serverId, Device device, String channelId, Boolean enabled, Integer resetTime, Integer presetIndex) { JSONObject jsonObject = new JSONObject(); jsonObject.put("device", device.getDeviceId()); jsonObject.put("channelId", channelId); jsonObject.put("enabled", enabled); jsonObject.put("resetTime", resetTime); jsonObject.put("presetIndex", presetIndex); RedisRpcRequest request = buildRequest("device/homePosition", jsonObject); request.setToId(serverId); RedisRpcResponse response = redisRpcConfig.request(request, 50, TimeUnit.MILLISECONDS); return JSON.parseObject(response.getBody().toString(), WVPResult.class); } @Override public void dragZoomIn(String serverId, Device device, String channelId, int length, int width, int midpointx, int midpointy, int lengthx, int lengthy) { JSONObject jsonObject = new JSONObject(); jsonObject.put("device", device.getDeviceId()); jsonObject.put("channelId", channelId); jsonObject.put("length", length); jsonObject.put("width", width); jsonObject.put("midpointx", midpointx); jsonObject.put("midpointy", midpointy); jsonObject.put("lengthx", lengthx); jsonObject.put("lengthy", lengthy); RedisRpcRequest request = buildRequest("device/dragZoomIn", jsonObject); request.setToId(serverId); redisRpcConfig.request(request, 50, TimeUnit.MILLISECONDS); } @Override public void dragZoomOut(String serverId, Device device, String channelId, int length, int width, int midpointx, int midpointy, int lengthx, int lengthy) { JSONObject jsonObject = new JSONObject(); jsonObject.put("device", device.getDeviceId()); jsonObject.put("channelId", channelId); jsonObject.put("length", length); jsonObject.put("width", width); jsonObject.put("midpointx", midpointx); jsonObject.put("midpointy", midpointy); jsonObject.put("lengthx", lengthx); jsonObject.put("lengthy", lengthy); RedisRpcRequest request = buildRequest("device/dragZoomOut", jsonObject); request.setToId(serverId); redisRpcConfig.request(request, 50, TimeUnit.MILLISECONDS); } @Override public WVPResult deviceStatus(String serverId, Device device) { RedisRpcRequest request = buildRequest("device/deviceStatus", device.getDeviceId()); request.setToId(serverId); RedisRpcResponse response = redisRpcConfig.request(request, 50, TimeUnit.MILLISECONDS); return JSON.parseObject(response.getBody().toString(), WVPResult.class); } @Override public WVPResult deviceInfo(String serverId, Device device) { RedisRpcRequest request = buildRequest("device/info", device.getDeviceId()); request.setToId(serverId); RedisRpcResponse response = redisRpcConfig.request(request, 50, TimeUnit.MILLISECONDS); return JSON.parseObject(response.getBody().toString(), WVPResult.class); } @Override public WVPResult> queryPreset(String serverId, Device device, String channelId) { JSONObject jsonObject = new JSONObject(); jsonObject.put("device", device.getDeviceId()); jsonObject.put("channelId", channelId); RedisRpcRequest request = buildRequest("device/queryPreset", jsonObject); request.setToId(serverId); RedisRpcResponse response = redisRpcConfig.request(request, 60000, TimeUnit.MILLISECONDS); return JSON.parseObject(response.getBody().toString(), WVPResult.class); } @Override public WVPResult alarm(String serverId, Device device, String startPriority, String endPriority, String alarmMethod, String alarmType, String startTime, String endTime) { JSONObject jsonObject = new JSONObject(); jsonObject.put("device", device.getDeviceId()); // jsonObject.put("channelId", channelId); jsonObject.put("startPriority", startPriority); jsonObject.put("endPriority", endPriority); jsonObject.put("alarmMethod", alarmMethod); jsonObject.put("alarmType", alarmType); jsonObject.put("startTime", startTime); jsonObject.put("endTime", endTime); RedisRpcRequest request = buildRequest("device/alarm", jsonObject); request.setToId(serverId); RedisRpcResponse response = redisRpcConfig.request(request, 50, TimeUnit.MILLISECONDS); return JSON.parseObject(response.getBody().toString(), WVPResult.class); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java ================================================ package com.genersoft.iot.vmp.storager; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.common.ServerInfo; import com.genersoft.iot.vmp.common.SystemAllInfo; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.media.bean.MediaInfo; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo; import com.genersoft.iot.vmp.service.bean.GPSMsgInfo; import com.genersoft.iot.vmp.service.bean.MessageForPushChannel; import java.util.List; import java.util.Map; public interface IRedisCatchStorage { /** * 计数器。为cseq进行计数 * * @return */ Long getCSEQ(); /** * 在redis添加wvp的信息 */ void updateWVPInfo(ServerInfo serverInfo, int time); void removeOfflineWVPInfo(String serverId); /** * 发送推流生成与推流消失消息 * @param jsonObject 消息内容 */ void sendStreamChangeMsg(String type, JSONObject jsonObject); /** * 发送报警消息 * @param msg 消息内容 */ void sendAlarmMsg(AlarmChannelMessage msg); /** * 添加流信息到redis * @param mediaServerItem * @param app * @param streamId */ void addStream(MediaServer mediaServerItem, String type, String app, String streamId, MediaInfo item); /** * 移除流信息从redis * @param mediaServerId * @param app * @param streamId */ void removeStream(String mediaServerId, String type, String app, String streamId); /** * 移除流信息从redis * @param mediaServerId */ void removeStream(String mediaServerId, String type); List getStreams(String mediaServerId, String pull); /** * 将device信息写入redis * @param device */ void updateDevice(Device device); void removeDevice(String deviceId); /** * 获取Device */ Device getDevice(String deviceId); void resetAllCSEQ(); void updateGpsMsgInfo(GPSMsgInfo gpsMsgInfo); GPSMsgInfo getGpsMsgInfo(String gbId); List getAllGpsMsgInfo(); MediaInfo getStreamInfo(String app, String streamId, String mediaServerId); MediaInfo getProxyStream(String app, String streamId); void addCpuInfo(double cpuInfo); void addMemInfo(double memInfo); void addNetInfo(Map networkInterfaces); void sendStreamPushRequestedMsg(MessageForPushChannel messageForPushChannel); /** * 判断设备状态 */ boolean deviceIsOnline(String deviceId); /** * 存储推流的鉴权信息 * @param app 应用名 * @param stream 流 * @param streamAuthorityInfo 鉴权信息 */ void updateStreamAuthorityInfo(String app, String stream, StreamAuthorityInfo streamAuthorityInfo); /** * 移除推流的鉴权信息 * @param app 应用名 * @param streamId 流 */ void removeStreamAuthorityInfo(String app, String streamId); /** * 获取推流的鉴权信息 * @param app 应用名 * @param stream 流 * @return */ StreamAuthorityInfo getStreamAuthorityInfo(String app, String stream); List getAllStreamAuthorityInfo(); /** * 发送redis消息 查询所有推流设备的状态 */ void sendStreamPushRequestedMsgForStatus(); SystemAllInfo getSystemInfo(); int getPushStreamCount(String id); int getProxyStreamCount(String id); int getGbSendCount(String id); void addDiskInfo(List> diskInfo); List queryAllSendRTPServer(); List getAllDevices(); void removeAllDevice(); void sendDeviceOrChannelStatus(String deviceId, String channelId, boolean online); void sendChannelAddOrDelete(String deviceId, String channelId, boolean add); void sendPlatformStartPlayMsg(SendRtpInfo sendRtpItem, DeviceChannel channel, Platform platform); void sendPlatformStopPlayMsg(SendRtpInfo sendRtpItem, Platform platform, CommonGBChannel channel); void addPushListItem(String app, String stream, MediaInfo param); MediaInfo getPushListItem(String app, String stream); void removePushListItem(String app, String stream, String mediaServerId); void sendPushStreamClose(MessageForPushChannel messageForPushChannel); void addWaiteSendRtpItem(SendRtpInfo sendRtpItem, int platformPlayTimeout); SendRtpInfo getWaiteSendRtpItem(String app, String stream); void sendStartSendRtp(SendRtpInfo sendRtpItem); void sendPushStreamOnline(SendRtpInfo sendRtpItem); ServerInfo queryServerInfo(String serverId); String chooseOneServer(String serverId); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/storager/dao/CloudRecordServiceMapper.java ================================================ package com.genersoft.iot.vmp.storager.dao; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.service.bean.CloudRecordItem; import org.apache.ibatis.annotations.*; import java.util.Collection; import java.util.List; @Mapper public interface CloudRecordServiceMapper { @Insert(" ") int add(CloudRecordItem cloudRecordItem); @Select(" ") List getList(@Param("query") String query, @Param("app") String app, @Param("stream") String stream, @Param("startTimeStamp")Long startTimeStamp, @Param("endTimeStamp")Long endTimeStamp, @Param("callId")String callId, List mediaServerItemList, List ids, @Param("ascOrder") Boolean ascOrder); @Select(" ") List queryRecordFilePathList(@Param("app") String app, @Param("stream") String stream, @Param("startTimeStamp")Long startTimeStamp, @Param("endTimeStamp")Long endTimeStamp, @Param("callId")String callId, List mediaServerItemList); @Update(" ") int updateCollectList(@Param("collect") boolean collect, List cloudRecordItemList); @Delete(" ") void deleteByFileList(List filePathList, @Param("mediaServerId") String mediaServerId); @Select(" ") List queryRecordListForDelete(@Param("endTimeStamp")Long endTimeStamp, String mediaServerId); @Update(" ") int changeCollectById(@Param("collect") boolean collect, @Param("recordId") Integer recordId); @Delete(" ") int deleteList(List cloudRecordItemIdList); @Select(" ") List getListByCallId(@Param("callId") String callId); @Select(" ") CloudRecordItem queryOne(@Param("id") Integer id); @Select(" ") CloudRecordItem getListByFileName(@Param("app") String app, @Param("stream") String stream, @Param("fileName") String fileName); @Update(" ") void updateTimeLen(@Param("id") int id, @Param("time") Long time, @Param("endTime") long endTime); @Select(" ") List queryMediaServerId(@Param("app") String app, @Param("stream") String stream, @Param("startTimeStamp")Long startTimeStamp, @Param("endTimeStamp")Long endTimeStamp); @Select(" ") List queryRecordByIds(Collection ids); @Select(" ") List queryRecordByAppStreamAndCallId(String app, String stream, String callId); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/storager/dao/MediaServerMapper.java ================================================ package com.genersoft.iot.vmp.storager.dao; import com.genersoft.iot.vmp.media.bean.MediaServer; import org.apache.ibatis.annotations.*; import org.apache.ibatis.annotations.Param; import org.springframework.stereotype.Repository; import java.util.List; @Mapper @Repository public interface MediaServerMapper { @Insert("INSERT INTO wvp_media_server (" + "id,"+ "ip,"+ "hook_ip,"+ "sdp_ip,"+ "stream_ip,"+ "http_port,"+ "http_ssl_port,"+ "rtmp_port,"+ "rtmp_ssl_port,"+ "rtp_proxy_port,"+ "jtt_proxy_port,"+ "rtsp_port,"+ "flv_port," + "mp4_port," + "flv_ssl_port," + "ws_flv_port," + "ws_flv_ssl_port," + "rtsp_ssl_port,"+ "auto_config,"+ "secret,"+ "rtp_enable,"+ "rtp_port_range,"+ "send_rtp_port_range,"+ "record_assist_port,"+ "record_day,"+ "record_path,"+ "default_server,"+ "type,"+ "create_time,"+ "update_time,"+ "transcode_suffix,"+ "server_id,"+ "hook_alive_interval"+ ") VALUES " + "(" + "#{id}, " + "#{ip}, " + "#{hookIp}, " + "#{sdpIp}, " + "#{streamIp}, " + "#{httpPort}, " + "#{httpSSlPort}, " + "#{rtmpPort}, " + "#{rtmpSSlPort}, " + "#{rtpProxyPort}, " + "#{jttProxyPort}, " + "#{rtspPort}, " + "#{flvPort}, " + "#{mp4Port}, " + "#{flvSSLPort}, " + "#{wsFlvPort}, " + "#{wsFlvSSLPort}, " + "#{rtspSSLPort}, " + "#{autoConfig}, " + "#{secret}, " + "#{rtpEnable}, " + "#{rtpPortRange}, " + "#{sendRtpPortRange}, " + "#{recordAssistPort}, " + "#{recordDay}, " + "#{recordPath}, " + "#{defaultServer}, " + "#{type}, " + "#{createTime}, " + "#{updateTime}, " + "#{transcodeSuffix}, " + "#{serverId}, " + "#{hookAliveInterval})") int add(MediaServer mediaServerItem); @Update(value = {" "}) int update(MediaServer mediaServerItem); @Update(value = {" "}) int updateByHostAndPort(MediaServer mediaServerItem); @Select("SELECT * FROM wvp_media_server WHERE id=#{id}") MediaServer queryOne(@Param("id") String id); @Select("SELECT * FROM wvp_media_server WHERE id=#{id} and server_id = #{serverId}") MediaServer queryOneWithServerId(@Param("id") String id, @Param("serverId") String serverId); @Select("SELECT * FROM wvp_media_server where server_id = #{serverId}") List queryAll(@Param("serverId") String serverId); @Select("SELECT * FROM wvp_media_server where default_server=false AND server_id = #{serverId}") List queryAllWithOutDefault(@Param("serverId") String serverId); @Delete("DELETE FROM wvp_media_server WHERE id=#{id} and server_id = #{serverId}") void delOne(String id, @Param("serverId") String serverId); @Select("SELECT * FROM wvp_media_server WHERE ip=#{host} and http_port=#{port} and server_id = #{serverId}") MediaServer queryOneByHostAndPort(@Param("host") String host, @Param("port") int port, @Param("serverId") String serverId); @Select("SELECT * FROM wvp_media_server WHERE default_server=true and server_id = #{serverId}") MediaServer queryDefault(@Param("serverId") String serverId); @Select("SELECT * FROM wvp_media_server WHERE record_assist_port > 0 and server_id = #{serverId}") List queryAllWithAssistPort(@Param("serverId") String serverId); @Delete("DELETE FROM wvp_media_server WHERE default_server=true and server_id = #{serverId}") void deleteDefault(@Param("serverId") String serverId); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/storager/dao/RecordPlanMapper.java ================================================ package com.genersoft.iot.vmp.storager.dao; import com.genersoft.iot.vmp.service.bean.RecordPlan; import com.genersoft.iot.vmp.service.bean.RecordPlanItem; import org.apache.ibatis.annotations.*; import java.util.List; @Mapper public interface RecordPlanMapper { @Insert(" ") @Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id") void add(RecordPlan plan); @Insert(" ") void batchAddItem(@Param("planId") int planId, List planItemList); @Select("select * from wvp_record_plan where id = #{planId}") RecordPlan get(@Param("planId") Integer planId); @Select(" ") List query(@Param("query") String query); @Update("UPDATE wvp_record_plan SET update_time=#{updateTime}, name=#{name}, snap=#{snap} WHERE id=#{id}") void update(RecordPlan plan); @Delete("DELETE FROM wvp_record_plan WHERE id=#{planId}") void delete(@Param("planId") Integer planId); @Select("select * from wvp_record_plan_item where plan_id = #{planId}") List getItemList(@Param("planId") Integer planId); @Delete("DELETE FROM wvp_record_plan_item WHERE plan_id = #{planId}") void cleanItems(@Param("planId") Integer planId); @Select(" ") List queryRecordIng(@Param("week") int week, @Param("index") int index); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/storager/dao/RoleMapper.java ================================================ package com.genersoft.iot.vmp.storager.dao; import com.genersoft.iot.vmp.storager.dao.dto.Role; import org.apache.ibatis.annotations.*; import org.springframework.stereotype.Repository; import java.util.List; @Mapper @Repository public interface RoleMapper { @Insert("INSERT INTO wvp_user_role (name, authority, create_time, update_time) VALUES" + "(#{name}, #{authority}, #{createTime}, #{updateTime})") int add(Role role); @Update(value = {" "}) int update(Role role); @Delete("DELETE from wvp_user_role WHERE id != 1 and id=#{id}") int delete(int id); @Select("select * from wvp_user_role WHERE id=#{id}") Role selectById(int id); @Select("select * from wvp_user_role") List selectAll(); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/storager/dao/UserApiKeyMapper.java ================================================ package com.genersoft.iot.vmp.storager.dao; import com.genersoft.iot.vmp.storager.dao.dto.UserApiKey; import org.apache.ibatis.annotations.*; import org.springframework.stereotype.Repository; import java.util.List; @Mapper @Repository public interface UserApiKeyMapper { @SelectKey(databaseId = "postgresql", statement = "SELECT currval('wvp_user_api_key_id_seq'::regclass) AS id", keyProperty = "id", before = false, resultType = Integer.class) @SelectKey(databaseId = "mysql", statement = "SELECT LAST_INSERT_ID() AS id", keyProperty = "id", before = false, resultType = Integer.class) @Insert("INSERT INTO wvp_user_api_key (user_id, app, api_key, expired_at, remark, enable, create_time, update_time) VALUES" + "(#{userId}, #{app}, #{apiKey}, #{expiredAt}, #{remark}, #{enable}, #{createTime}, #{updateTime})") int add(UserApiKey userApiKey); @Update(value = {""}) int update(UserApiKey userApiKey); @Update("UPDATE wvp_user_api_key SET enable = true WHERE id = #{id}") int enable(@Param("id") int id); @Update("UPDATE wvp_user_api_key SET enable = false WHERE id = #{id}") int disable(@Param("id") int id); @Update("UPDATE wvp_user_api_key SET api_key = #{apiKey} WHERE id = #{id}") int apiKey(@Param("id") int id, @Param("apiKey") String apiKey); @Update("UPDATE wvp_user_api_key SET remark = #{remark} WHERE id = #{id}") int remark(@Param("id") int id, @Param("remark") String remark); @Delete("DELETE FROM wvp_user_api_key WHERE id = #{id}") int delete(@Param("id") int id); @Select("SELECT uak.id, uak.user_id, uak.app, uak.api_key, uak.expired_at, uak.remark, uak.enable, uak.create_time, uak.update_time, u.username AS username FROM wvp_user_api_key uak LEFT JOIN wvp_user u on u.id = uak.user_id WHERE uak.id = #{id}") UserApiKey selectById(@Param("id") int id); @Select("SELECT uak.id, uak.user_id, uak.app, uak.api_key, uak.expired_at, uak.remark, uak.enable, uak.create_time, uak.update_time, u.username AS username FROM wvp_user_api_key uak LEFT JOIN wvp_user u on u.id = uak.user_id WHERE uak.api_key = #{apiKey}") UserApiKey selectByApiKey(@Param("apiKey") String apiKey); @Select("SELECT uak.id, uak.user_id, uak.app, uak.api_key, uak.expired_at, uak.remark, uak.enable, uak.create_time, uak.update_time, u.username AS username FROM wvp_user_api_key uak LEFT JOIN wvp_user u on u.id = uak.user_id") List selectAll(); @Select("SELECT uak.id, uak.user_id, uak.app, uak.api_key, uak.expired_at, uak.remark, uak.enable, uak.create_time, uak.update_time, u.username AS username FROM wvp_user_api_key uak LEFT JOIN wvp_user u on u.id = uak.user_id") List getUserApiKeys(); @Select("SELECT COUNT(0) FROM wvp_user_api_key WHERE api_key = #{apiKey}") boolean isApiKeyExists(@Param("apiKey") String apiKey); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/storager/dao/UserMapper.java ================================================ package com.genersoft.iot.vmp.storager.dao; import com.genersoft.iot.vmp.storager.dao.dto.User; import org.apache.ibatis.annotations.*; import org.apache.ibatis.annotations.Param; import org.springframework.stereotype.Repository; import java.util.List; @Mapper @Repository public interface UserMapper { @Insert("INSERT INTO wvp_user (username, password, role_id, push_key, create_time, update_time) VALUES" + "(#{username}, #{password}, #{role.id}, #{pushKey}, #{createTime}, #{updateTime})") int add(User user); @Update(value = {" "}) int update(User user); @Delete("DELETE from wvp_user WHERE id != 1 and id=#{id}") int delete(int id); @Select("select u.*, r.name as roleName, r.authority as roleAuthority , r.create_time as role_create_time , r.update_time as role_update_time from wvp_user u, wvp_user_role r WHERE u.role_id=r.id and u.username=#{username} AND u.password=#{password}") @Results(id = "roleMap", value = { @Result(column = "role_id", property = "role.id"), @Result(column = "role_name", property = "role.name"), @Result(column = "role_authority", property = "role.authority"), @Result(column = "role_create_time", property = "role.createTime"), @Result(column = "role_update_time", property = "role.updateTime") }) User select(@Param("username") String username, @Param("password") String password); @Select("select u.*, r.name as role_name, r.authority as role_authority , r.create_time as role_create_time , r.update_time as role_update_time from wvp_user u, wvp_user_role r WHERE u.role_id=r.id and u.id=#{id}") @ResultMap(value="roleMap") User selectById(int id); @Select("select u.*, r.name as role_name, r.authority as role_authority , r.create_time as role_create_time , r.update_time as role_update_time from wvp_user u, wvp_user_role r WHERE u.role_id=r.id and u.username=#{username}") @ResultMap(value="roleMap") User getUserByUsername(String username); @Select("select u.*, r.name as role_name, r.authority as role_authority , r.create_time as role_create_time , r.update_time as role_update_time from wvp_user u, wvp_user_role r WHERE u.role_id=r.id") @ResultMap(value="roleMap") List selectAll(); @Select("select u.id, u.username,u.push_key,u.role_id, r.name as role_name, r.authority as role_authority , r.create_time as role_create_time , r.update_time as role_update_time from wvp_user u join wvp_user_role r on u.role_id=r.id") @ResultMap(value="roleMap") List getUsers(); @Update("UPDATE wvp_user set push_key=#{pushKey} where id=#{id}") int changePushKey(@Param("id") int id, @Param("pushKey") String pushKey); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/storager/dao/dto/ChannelSourceInfo.java ================================================ package com.genersoft.iot.vmp.storager.dao.dto; public class ChannelSourceInfo { private String name; private int count; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getCount() { return count; } public void setCount(int count) { this.count = count; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/storager/dao/dto/LogDto.java ================================================ package com.genersoft.iot.vmp.storager.dao.dto; public class LogDto { private int id; private String name; private String type; private String uri; private String address; private String result; private long timing; private String username; private String createTime; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getType() { return type; } public void setType(String type) { this.type = type; } public String getUri() { return uri; } public void setUri(String uri) { this.uri = uri; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public String getResult() { return result; } public void setResult(String result) { this.result = result; } public long getTiming() { return timing; } public void setTiming(long timing) { this.timing = timing; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getCreateTime() { return createTime; } public void setCreateTime(String createTime) { this.createTime = createTime; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/storager/dao/dto/PlatformRegisterInfo.java ================================================ package com.genersoft.iot.vmp.storager.dao.dto; /** * 平台发送注册/注销消息时缓存此消息 * @author lin */ public class PlatformRegisterInfo { /** * 平台Id */ private String platformId; /** * 是否时注册,false为注销 */ private boolean register; public static PlatformRegisterInfo getInstance(String platformId, boolean register) { PlatformRegisterInfo platformRegisterInfo = new PlatformRegisterInfo(); platformRegisterInfo.setPlatformId(platformId); platformRegisterInfo.setRegister(register); return platformRegisterInfo; } public String getPlatformId() { return platformId; } public void setPlatformId(String platformId) { this.platformId = platformId; } public boolean isRegister() { return register; } public void setRegister(boolean register) { this.register = register; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/storager/dao/dto/RecordInfo.java ================================================ package com.genersoft.iot.vmp.storager.dao.dto; /** * 录像记录 */ public class RecordInfo { /** * ID */ private int id; /** * 应用名 */ private String app; /** * 流ID */ private String stream; /** * 对应的zlm流媒体的ID */ private String mediaServerId; /** * 创建时间 */ private String createTime; /** * 类型 对应zlm的 originType * unknown = 0, * rtmp_push=1, * rtsp_push=2, * rtp_push=3, * pull=4, * ffmpeg_pull=5, * mp4_vod=6, * device_chn=7, * rtc_push=8 */ private int type; /** * 国标录像时的设备ID */ private String deviceId; /** * 国标录像时的通道ID */ private String channelId; /** * 拉流代理录像时的名称 */ private String name; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getApp() { return app; } public void setApp(String app) { this.app = app; } public String getStream() { return stream; } public void setStream(String stream) { this.stream = stream; } public String getMediaServerId() { return mediaServerId; } public void setMediaServerId(String mediaServerId) { this.mediaServerId = mediaServerId; } public String getCreateTime() { return createTime; } public void setCreateTime(String createTime) { this.createTime = createTime; } public int getType() { return type; } public void setType(int type) { this.type = type; } public String getDeviceId() { return deviceId; } public void setDeviceId(String deviceId) { this.deviceId = deviceId; } public String getChannelId() { return channelId; } public void setChannelId(String channelId) { this.channelId = channelId; } public String getName() { return name; } public void setName(String name) { this.name = name; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/storager/dao/dto/Role.java ================================================ package com.genersoft.iot.vmp.storager.dao.dto; public class Role { private int id; private String name; private String authority; private String createTime; private String updateTime; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAuthority() { return authority; } public void setAuthority(String authority) { this.authority = authority; } public String getCreateTime() { return createTime; } public void setCreateTime(String createTime) { this.createTime = createTime; } public String getUpdateTime() { return updateTime; } public void setUpdateTime(String updateTime) { this.updateTime = updateTime; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/storager/dao/dto/User.java ================================================ package com.genersoft.iot.vmp.storager.dao.dto; public class User { private int id; private String username; private String password; private String createTime; private String updateTime; private String pushKey; private Role role; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getCreateTime() { return createTime; } public void setCreateTime(String createTime) { this.createTime = createTime; } public String getUpdateTime() { return updateTime; } public void setUpdateTime(String updateTime) { this.updateTime = updateTime; } public Role getRole() { return role; } public void setRole(Role role) { this.role = role; } public String getPushKey() { return pushKey; } public void setPushKey(String pushKey) { this.pushKey = pushKey; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/storager/dao/dto/UserApiKey.java ================================================ package com.genersoft.iot.vmp.storager.dao.dto; import io.swagger.v3.oas.annotations.media.Schema; import java.io.Serializable; /** * 用户信息 */ @Schema(description = "用户ApiKey信息") public class UserApiKey implements Serializable { /** * Id */ @Schema(description = "Id") private int id; /** * 用户Id */ @Schema(description = "用户Id") private int userId; /** * 应用名 */ @Schema(description = "应用名") private String app; /** * ApiKey */ @Schema(description = "ApiKey") private String apiKey; /** * 过期时间(null=永不过期) */ @Schema(description = "过期时间(null=永不过期)") private long expiredAt; /** * 备注信息 */ @Schema(description = "备注信息") private String remark; /** * 是否启用 */ @Schema(description = "是否启用") private boolean enable; /** * 创建时间 */ @Schema(description = "创建时间") private String createTime; /** * 更新时间 */ @Schema(description = "更新时间") private String updateTime; /** * 用户名 */ private String username; public int getId() { return id; } public void setId(int id) { this.id = id; } public int getUserId() { return userId; } public void setUserId(int userId) { this.userId = userId; } public String getApp() { return app; } public void setApp(String app) { this.app = app; } public String getApiKey() { return apiKey; } public void setApiKey(String apiKey) { this.apiKey = apiKey; } public long getExpiredAt() { return expiredAt; } public void setExpiredAt(long expiredAt) { this.expiredAt = expiredAt; } public String getRemark() { return remark; } public void setRemark(String remark) { this.remark = remark; } public boolean isEnable() { return enable; } public void setEnable(boolean enable) { this.enable = enable; } public String getCreateTime() { return createTime; } public void setCreateTime(String createTime) { this.createTime = createTime; } public String getUpdateTime() { return updateTime; } public void setUpdateTime(String updateTime) { this.updateTime = updateTime; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java ================================================ package com.genersoft.iot.vmp.storager.impl; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.common.ServerInfo; import com.genersoft.iot.vmp.common.SystemAllInfo; import com.genersoft.iot.vmp.common.VideoManagerConstants; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.dao.DeviceChannelMapper; import com.genersoft.iot.vmp.gb28181.dao.DeviceMapper; import com.genersoft.iot.vmp.media.bean.MediaInfo; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo; import com.genersoft.iot.vmp.service.bean.GPSMsgInfo; import com.genersoft.iot.vmp.service.bean.MessageForPushChannel; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.utils.JsonUtil; import com.genersoft.iot.vmp.utils.SystemInfoUtils; import com.genersoft.iot.vmp.utils.redis.RedisUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; import java.time.Duration; import java.util.*; @SuppressWarnings("rawtypes") @Slf4j @Component public class RedisCatchStorageImpl implements IRedisCatchStorage { @Autowired private DeviceChannelMapper deviceChannelMapper; @Autowired private DeviceMapper deviceMapper; @Autowired private UserSetting userSetting; @Autowired private RedisTemplate redisTemplate; @Autowired private StringRedisTemplate stringRedisTemplate; @Override public List queryAllSendRTPServer() { return Collections.emptyList(); } @Override public Long getCSEQ() { String key = VideoManagerConstants.SIP_CSEQ_PREFIX + userSetting.getServerId(); Long result = redisTemplate.opsForValue().increment(key, 1L); if (result != null && result > Integer.MAX_VALUE) { redisTemplate.opsForValue().set(key, 1); result = 1L; } return result; } @Override public void resetAllCSEQ() { String key = VideoManagerConstants.SIP_CSEQ_PREFIX + userSetting.getServerId(); redisTemplate.opsForValue().set(key, 1); } @Override public void updateWVPInfo(ServerInfo serverInfo, int time) { String key = VideoManagerConstants.WVP_SERVER_PREFIX + userSetting.getServerId(); Duration duration = Duration.ofSeconds(time); redisTemplate.opsForValue().set(key, serverInfo, duration); // 设置平台的分数值 String setKey = VideoManagerConstants.WVP_SERVER_LIST; // 首次设置就设置为0, 后续值越小说明越是最近启动的 redisTemplate.opsForZSet().add(setKey, userSetting.getServerId(), System.currentTimeMillis()); } @Override public void removeOfflineWVPInfo(String serverId) { String setKey = VideoManagerConstants.WVP_SERVER_LIST; // 首次设置就设置为0, 后续值越小说明越是最近启动的 redisTemplate.opsForZSet().remove(setKey, serverId); } @Override public void sendStreamChangeMsg(String type, JSONObject jsonObject) { String key = VideoManagerConstants.WVP_MSG_STREAM_CHANGE_PREFIX + type; log.info("[redis 流变化事件] 发送 {}: {}", key, jsonObject.toString()); redisTemplate.convertAndSend(key, jsonObject); } @Override public void addStream(MediaServer mediaServerItem, String type, String app, String streamId, MediaInfo mediaInfo) { // 查找是否使用了callID StreamAuthorityInfo streamAuthorityInfo = getStreamAuthorityInfo(app, streamId); String key = VideoManagerConstants.WVP_SERVER_STREAM_PREFIX + userSetting.getServerId() + "_" + type.toUpperCase() + "_" + app + "_" + streamId + "_" + mediaServerItem.getId(); if (streamAuthorityInfo != null) { mediaInfo.setCallId(streamAuthorityInfo.getCallId()); } redisTemplate.opsForValue().set(key, JSON.toJSONString(mediaInfo)); } @Override public void removeStream(String mediaServerId, String type, String app, String streamId) { String key = VideoManagerConstants.WVP_SERVER_STREAM_PREFIX + userSetting.getServerId() + "_" + type.toUpperCase() + "_" + app + "_" + streamId + "_" + mediaServerId; redisTemplate.delete(key); } @Override public void removeStream(String mediaServerId, String type) { String key = VideoManagerConstants.WVP_SERVER_STREAM_PREFIX + userSetting.getServerId() + "_" + type.toUpperCase() + "_*_*_" + mediaServerId; List streams = RedisUtil.scan(redisTemplate, key); for (Object stream : streams) { redisTemplate.delete(stream); } } @Override public List getStreams(String mediaServerId, String type) { List result = new ArrayList<>(); String key = VideoManagerConstants.WVP_SERVER_STREAM_PREFIX + userSetting.getServerId() + "_" + type.toUpperCase() + "_*_*_" + mediaServerId; List streams = RedisUtil.scan(redisTemplate, key); for (Object stream : streams) { String mediaInfoJson = (String)redisTemplate.opsForValue().get(stream); MediaInfo mediaInfo = JSON.parseObject(mediaInfoJson, MediaInfo.class); result.add(mediaInfo); } return result; } @Override public void updateDevice(Device device) { String key = VideoManagerConstants.DEVICE_PREFIX; redisTemplate.opsForHash().put(key, device.getDeviceId(), device); } @Override public void removeDevice(String deviceId) { String key = VideoManagerConstants.DEVICE_PREFIX; redisTemplate.opsForHash().delete(key, deviceId); } @Override public void removeAllDevice() { String key = VideoManagerConstants.DEVICE_PREFIX; redisTemplate.delete(key); } @Override public List getAllDevices() { String key = VideoManagerConstants.DEVICE_PREFIX; List result = new ArrayList<>(); List values = redisTemplate.opsForHash().values(key); for (Object value : values) { if (Objects.nonNull(value)) { result.add((Device)value); } } return result; } @Override public Device getDevice(String deviceId) { String key = VideoManagerConstants.DEVICE_PREFIX; Device device; Object object = redisTemplate.opsForHash().get(key, deviceId); if (object == null){ device = deviceMapper.getDeviceByDeviceId(deviceId); if (device != null) { updateDevice(device); } }else { device = (Device)object; } return device; } @Override public void updateGpsMsgInfo(GPSMsgInfo gpsMsgInfo) { String key = VideoManagerConstants.WVP_STREAM_GPS_MSG_PREFIX + userSetting.getServerId(); Duration duration = Duration.ofSeconds(60L); gpsMsgInfo.setStored(false); redisTemplate.opsForHash().put(key, gpsMsgInfo.getId(),gpsMsgInfo); redisTemplate.expire(key, duration); // 默认GPS消息保存1分钟 } @Override public GPSMsgInfo getGpsMsgInfo(String channelId) { String key = VideoManagerConstants.WVP_STREAM_GPS_MSG_PREFIX + userSetting.getServerId(); return (GPSMsgInfo) redisTemplate.opsForHash().get(key, channelId); } @Override public List getAllGpsMsgInfo() { String key = VideoManagerConstants.WVP_STREAM_GPS_MSG_PREFIX + userSetting.getServerId(); List result = new ArrayList<>(); List values = redisTemplate.opsForHash().values(key); for (Object value : values) { result.add((GPSMsgInfo)value); } return result; } @Override public void updateStreamAuthorityInfo(String app, String stream, StreamAuthorityInfo streamAuthorityInfo) { String key = VideoManagerConstants.MEDIA_STREAM_AUTHORITY; String objectKey = app+ "_" + stream; redisTemplate.opsForHash().put(key, objectKey, streamAuthorityInfo); } @Override public void removeStreamAuthorityInfo(String app, String stream) { String key = VideoManagerConstants.MEDIA_STREAM_AUTHORITY; String objectKey = app+ "_" + stream; redisTemplate.opsForHash().delete(key, objectKey); } @Override public StreamAuthorityInfo getStreamAuthorityInfo(String app, String stream) { String key = VideoManagerConstants.MEDIA_STREAM_AUTHORITY; String objectKey = app+ "_" + stream; return (StreamAuthorityInfo)redisTemplate.opsForHash().get(key, objectKey); } @Override public List getAllStreamAuthorityInfo() { String key = VideoManagerConstants.MEDIA_STREAM_AUTHORITY; List result = new ArrayList<>(); List values = redisTemplate.opsForHash().values(key); for (Object value : values) { result.add((StreamAuthorityInfo)value); } return result; } @Override public MediaInfo getStreamInfo(String app, String streamId, String mediaServerId) { String scanKey = VideoManagerConstants.WVP_SERVER_STREAM_PREFIX + userSetting.getServerId() + "_*_" + app + "_" + streamId + "_" + mediaServerId; MediaInfo result = null; List keys = RedisUtil.scan(redisTemplate, scanKey); if (keys.size() > 0) { String key = (String) keys.get(0); String mediaInfoJson = (String)redisTemplate.opsForValue().get(key); result = JSON.parseObject(mediaInfoJson, MediaInfo.class); } return result; } @Override public MediaInfo getProxyStream(String app, String streamId) { String scanKey = VideoManagerConstants.WVP_SERVER_STREAM_PREFIX + userSetting.getServerId() + "_PULL_" + app + "_" + streamId + "_*"; MediaInfo result = null; List keys = RedisUtil.scan(redisTemplate, scanKey); if (keys.size() > 0) { String key = (String) keys.get(0); String mediaInfoJson = (String)redisTemplate.opsForValue().get(key); result = JSON.parseObject(mediaInfoJson, MediaInfo.class); } return result; } @Override public void addCpuInfo(double cpuInfo) { String key = VideoManagerConstants.SYSTEM_INFO_CPU_PREFIX + userSetting.getServerId(); Map infoMap = new HashMap<>(); infoMap.put("time", DateUtil.getNow()); infoMap.put("data", String.valueOf(cpuInfo)); redisTemplate.opsForList().rightPush(key, infoMap); // 每秒一个,最多只存30个 Long size = redisTemplate.opsForList().size(key); if (size != null && size >= 30) { for (int i = 0; i < size - 30; i++) { redisTemplate.opsForList().leftPop(key); } } } @Override public void addMemInfo(double memInfo) { String key = VideoManagerConstants.SYSTEM_INFO_MEM_PREFIX + userSetting.getServerId(); Map infoMap = new HashMap<>(); infoMap.put("time", DateUtil.getNow()); infoMap.put("data", String.valueOf(memInfo)); redisTemplate.opsForList().rightPush(key, infoMap); // 每秒一个,最多只存30个 Long size = redisTemplate.opsForList().size(key); if (size != null && size >= 30) { for (int i = 0; i < size - 30; i++) { redisTemplate.opsForList().leftPop(key); } } } @Override public void addNetInfo(Map networkInterfaces) { String key = VideoManagerConstants.SYSTEM_INFO_NET_PREFIX + userSetting.getServerId(); Map infoMap = new HashMap<>(); infoMap.put("time", DateUtil.getNow()); for (String netKey : networkInterfaces.keySet()) { infoMap.put(netKey, networkInterfaces.get(netKey)); } redisTemplate.opsForList().rightPush(key, infoMap); // 每秒一个,最多只存30个 Long size = redisTemplate.opsForList().size(key); if (size != null && size >= 30) { for (int i = 0; i < size - 30; i++) { redisTemplate.opsForList().leftPop(key); } } } @Override public void addDiskInfo(List> diskInfo) { String key = VideoManagerConstants.SYSTEM_INFO_DISK_PREFIX + userSetting.getServerId(); redisTemplate.opsForValue().set(key, diskInfo); } @Override public SystemAllInfo getSystemInfo() { String cpuKey = VideoManagerConstants.SYSTEM_INFO_CPU_PREFIX + userSetting.getServerId(); String memKey = VideoManagerConstants.SYSTEM_INFO_MEM_PREFIX + userSetting.getServerId(); String netKey = VideoManagerConstants.SYSTEM_INFO_NET_PREFIX + userSetting.getServerId(); String diskKey = VideoManagerConstants.SYSTEM_INFO_DISK_PREFIX + userSetting.getServerId(); SystemAllInfo systemAllInfo = new SystemAllInfo(); systemAllInfo.setCpu(redisTemplate.opsForList().range(cpuKey, 0, -1)); systemAllInfo.setMem(redisTemplate.opsForList().range(memKey, 0, -1)); systemAllInfo.setNet(redisTemplate.opsForList().range(netKey, 0, -1)); systemAllInfo.setDisk(redisTemplate.opsForValue().get(diskKey)); systemAllInfo.setNetTotal(SystemInfoUtils.getNetworkTotal()); return systemAllInfo; } @Override public void sendStreamPushRequestedMsg(MessageForPushChannel msg) { String key = VideoManagerConstants.VM_MSG_STREAM_PUSH_REQUESTED; log.info("[redis发送通知] 发送 推流被请求 {}: {}/{}", key, msg.getApp(), msg.getStream()); redisTemplate.convertAndSend(key, JSON.toJSON(msg)); } @Override public void sendAlarmMsg(AlarmChannelMessage msg) { // 此消息用于对接第三方服务下级来的消息内容 String key = VideoManagerConstants.VM_MSG_SUBSCRIBE_ALARM; log.info("[redis发送通知] 发送 报警{}: {}", key, JSON.toJSON(msg)); redisTemplate.convertAndSend(key, JSON.toJSON(msg)); } @Override public boolean deviceIsOnline(String deviceId) { return getDevice(deviceId).isOnLine(); } @Override public void sendStreamPushRequestedMsgForStatus() { String key = VideoManagerConstants.VM_MSG_GET_ALL_ONLINE_REQUESTED; log.info("[redis通知] 发送 获取所有推流设备的状态"); JSONObject jsonObject = new JSONObject(); jsonObject.put(key, key); redisTemplate.convertAndSend(key, jsonObject); } @Override public int getPushStreamCount(String id) { String key = VideoManagerConstants.WVP_SERVER_STREAM_PREFIX + userSetting.getServerId() + "_PUSH_*_*_" + id; return RedisUtil.scan(redisTemplate, key).size(); } @Override public int getProxyStreamCount(String id) { String key = VideoManagerConstants.WVP_SERVER_STREAM_PREFIX + userSetting.getServerId() + "_PULL_*_*_" + id; return RedisUtil.scan(redisTemplate, key).size(); } @Override public int getGbSendCount(String id) { String key = VideoManagerConstants.SEND_RTP_INFO_CALLID; return redisTemplate.opsForHash().size(key).intValue(); } @Override public void sendDeviceOrChannelStatus(String deviceId, String channelId, boolean online) { String key = VideoManagerConstants.VM_MSG_SUBSCRIBE_DEVICE_STATUS; StringBuilder msg = new StringBuilder(); msg.append(deviceId); if (channelId != null) { msg.append(":").append(channelId); } msg.append(" ").append(online? "ON":"OFF"); log.info("[redis通知] 推送设备/通道状态-> {} ", msg); // 使用 RedisTemplate 发送字符串消息会导致发送的消息多带了双引号 stringRedisTemplate.convertAndSend(key, msg.toString()); } @Override public void sendChannelAddOrDelete(String deviceId, String channelId, boolean add) { String key = VideoManagerConstants.VM_MSG_SUBSCRIBE_DEVICE_STATUS; StringBuilder msg = new StringBuilder(); msg.append(deviceId); if (channelId != null) { msg.append(":").append(channelId); } msg.append(" ").append(add? "ADD":"DELETE"); log.info("[redis通知] 推送通道-> {}", msg); // 使用 RedisTemplate 发送字符串消息会导致发送的消息多带了双引号 stringRedisTemplate.convertAndSend(key, msg.toString()); } @Override public void sendPlatformStartPlayMsg(SendRtpInfo sendRtpItem, DeviceChannel channel, Platform platform) { if (platform == null) { log.info("[redis发送通知] 失败, 平台信息为NULL"); return; } if (sendRtpItem.getPlayType() != InviteStreamType.PUSH) { log.info("[redis发送通知] 取消, 流来源通道不是推流设备"); return; } MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0, sendRtpItem.getApp(), sendRtpItem.getStream(), channel.getDeviceId(), platform.getServerGBId(), platform.getName(), userSetting.getServerId(), sendRtpItem.getMediaServerId()); messageForPushChannel.setPlatFormIndex(platform.getId()); String key = VideoManagerConstants.VM_MSG_STREAM_START_PLAY_NOTIFY; log.info("[redis发送通知] 发送 推流被上级平台观看 {}: {}/{}->{}", key, sendRtpItem.getApp(), sendRtpItem.getStream(), platform.getServerGBId()); redisTemplate.convertAndSend(key, JSON.toJSON(messageForPushChannel)); } @Override public void sendPlatformStopPlayMsg(SendRtpInfo sendRtpItem, Platform platform, CommonGBChannel channel) { MessageForPushChannel msg = MessageForPushChannel.getInstance(0, sendRtpItem.getApp(), sendRtpItem.getStream(), channel.getGbDeviceId(), sendRtpItem.getTargetId(), platform.getName(), userSetting.getServerId(), sendRtpItem.getMediaServerId()); msg.setPlatFormIndex(platform.getId()); String key = VideoManagerConstants.VM_MSG_STREAM_STOP_PLAY_NOTIFY; log.info("[redis发送通知] 发送 上级平台停止观看 {}: {}/{}->{}", key, sendRtpItem.getApp(), sendRtpItem.getStream(), platform.getServerGBId()); redisTemplate.convertAndSend(key, JSON.toJSON(msg)); } @Override public void addPushListItem(String app, String stream, MediaInfo mediaInfo) { String key = VideoManagerConstants.PUSH_STREAM_LIST + app + "_" + stream; redisTemplate.opsForValue().set(key, mediaInfo); } @Override public MediaInfo getPushListItem(String app, String stream) { String key = VideoManagerConstants.PUSH_STREAM_LIST + app + "_" + stream; return (MediaInfo)redisTemplate.opsForValue().get(key); } @Override public void removePushListItem(String app, String stream, String mediaServerId) { String key = VideoManagerConstants.PUSH_STREAM_LIST + app + "_" + stream; MediaInfo param = (MediaInfo)redisTemplate.opsForValue().get(key); if (param != null && userSetting.getServerId().equals(param.getServerId())) { redisTemplate.delete(key); } } @Override public void sendPushStreamClose(MessageForPushChannel msg) { String key = VideoManagerConstants.VM_MSG_STREAM_PUSH_CLOSE_REQUESTED; log.info("[redis发送通知] 发送 停止向上级推流 {}: {}/{}->{}", key, msg.getApp(), msg.getStream(), msg.getPlatFormId()); redisTemplate.convertAndSend(key, JSON.toJSON(msg)); } @Override public void addWaiteSendRtpItem(SendRtpInfo sendRtpItem, int platformPlayTimeout) { String key = VideoManagerConstants.WAITE_SEND_PUSH_STREAM + sendRtpItem.getApp() + "_" + sendRtpItem.getStream(); redisTemplate.opsForValue().set(key, sendRtpItem); } @Override public SendRtpInfo getWaiteSendRtpItem(String app, String stream) { String key = VideoManagerConstants.WAITE_SEND_PUSH_STREAM + app + "_" + stream; return JsonUtil.redisJsonToObject(redisTemplate, key, SendRtpInfo.class); } @Override public void sendStartSendRtp(SendRtpInfo sendRtpItem) { String key = VideoManagerConstants.START_SEND_PUSH_STREAM + sendRtpItem.getApp() + "_" + sendRtpItem.getStream(); log.info("[redis发送通知] 通知其他WVP推流 {}: {}/{}->{}", key, sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getTargetId()); redisTemplate.convertAndSend(key, JSON.toJSON(sendRtpItem)); } @Override public void sendPushStreamOnline(SendRtpInfo sendRtpItem) { String key = VideoManagerConstants.VM_MSG_STREAM_PUSH_CLOSE_REQUESTED; log.info("[redis发送通知] 流上线 {}: {}/{}->{}", key, sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getTargetId()); redisTemplate.convertAndSend(key, JSON.toJSON(sendRtpItem)); } @Override public ServerInfo queryServerInfo(String serverId) { String key = VideoManagerConstants.WVP_SERVER_PREFIX + serverId; return (ServerInfo)redisTemplate.opsForValue().get(key); } @Override public String chooseOneServer(String serverId) { String key = VideoManagerConstants.WVP_SERVER_LIST; if (serverId != null) { redisTemplate.opsForZSet().remove(key, serverId); } // 获取得分最高的,也是最后更新时间到redis的wvp,这样可以避免读取到离线的wvp,同时时间最新也一定程度代表最健康的 Set range = redisTemplate.opsForZSet().reverseRange(key, 0, 0); if (range == null || range.isEmpty()) { return null; } return (String) range.iterator().next(); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/streamProxy/bean/StreamProxy.java ================================================ package com.genersoft.iot.vmp.streamProxy.bean; import com.genersoft.iot.vmp.common.enums.ChannelDataType; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import org.springframework.util.ObjectUtils; /** * @author lin */ @Data @Schema(description = "拉流代理的信息") @EqualsAndHashCode(callSuper = true) public class StreamProxy extends CommonGBChannel { /** * 数据库自增ID */ @Schema(description = "数据库自增ID") private int id; @Schema(description = "类型,取值,default: 流媒体直接拉流(默认),ffmpeg: ffmpeg实现拉流") private String type; @Schema(description = "应用名") private String app; @Schema(description = "流ID") private String stream; @Schema(description = "当前拉流使用的流媒体服务ID") private String mediaServerId; @Schema(description = "固定选择的流媒体服务ID") private String relatesMediaServerId; @Schema(description = "服务ID") private String serverId; @Schema(description = "拉流地址") private String srcUrl; @Schema(description = "超时时间:秒") private int timeout; @Schema(description = "ffmpeg模板KEY") private String ffmpegCmdKey; @Schema(description = "rtsp拉流时,拉流方式,0:tcp,1:udp,2:组播") private String rtspType; @Schema(description = "是否启用") private boolean enable; @Schema(description = "是否启用音频") private boolean enableAudio; @Schema(description = "是否启用MP4") private boolean enableMp4; @Schema(description = "是否 无人观看时自动停用") private boolean enableDisableNoneReader; @Schema(description = "拉流代理时zlm返回的key,用于停止拉流代理") private String streamKey; @Schema(description = "拉流状态") private Boolean pulling; public CommonGBChannel buildCommonGBChannel() { if (ObjectUtils.isEmpty(this.getGbDeviceId())) { return null; } if (ObjectUtils.isEmpty(this.getGbName())) { this.setGbName( app+ "-" +stream); } this.setDataType(ChannelDataType.STREAM_PROXY); this.setDataDeviceId(this.getId()); return this; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/streamProxy/bean/StreamProxyParam.java ================================================ package com.genersoft.iot.vmp.streamProxy.bean; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; /** * @author lin */ @Data @Schema(description = "拉流代理的信息") public class StreamProxyParam { @Schema(description = "类型,取值,default: 流媒体直接拉流(默认),ffmpeg: ffmpeg实现拉流") private String type; @Schema(description = "应用名") private String app; @Schema(description = "名称") private String name; @Schema(description = "流ID") private String stream; @Schema(description = "流媒体服务ID") private String mediaServerId; @Schema(description = "拉流地址") private String url; @Schema(description = "超时时间:秒") private int timeoutMs; @Schema(description = "ffmpeg模板KEY") private String ffmpegCmdKey; @Schema(description = "rtsp拉流时,拉流方式,0:tcp,1:udp,2:组播") private String rtpType; @Schema(description = "是否启用") private boolean enable; @Schema(description = "是否启用音频") private boolean enableAudio; @Schema(description = "是否启用MP4") private boolean enableMp4; @Schema(description = "是否 无人观看时自动停用") private boolean enableDisableNoneReader; public StreamProxy buildStreamProxy(String serverId) { StreamProxy streamProxy = new StreamProxy(); streamProxy.setApp(app); streamProxy.setStream(stream); streamProxy.setRelatesMediaServerId(mediaServerId); streamProxy.setServerId(serverId); streamProxy.setSrcUrl(url); streamProxy.setTimeout(timeoutMs/1000); streamProxy.setRtspType(rtpType); streamProxy.setEnable(enable); streamProxy.setEnableAudio(enableAudio); streamProxy.setEnableMp4(enableMp4); streamProxy.setEnableDisableNoneReader(enableDisableNoneReader); streamProxy.setFfmpegCmdKey(ffmpegCmdKey); streamProxy.setGbName(name); return streamProxy; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/streamProxy/controller/StreamProxyController.java ================================================ package com.genersoft.iot.vmp.streamProxy.controller; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.conf.security.JwtUtils; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.service.IMediaServerService; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.service.bean.InviteErrorCode; import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy; import com.genersoft.iot.vmp.streamProxy.service.IStreamProxyPlayService; import com.genersoft.iot.vmp.streamProxy.service.IStreamProxyService; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.StreamContent; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import com.github.pagehelper.PageInfo; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.web.bind.annotation.*; import org.springframework.web.context.request.async.DeferredResult; import java.net.MalformedURLException; import java.net.URL; import java.util.Map; @SuppressWarnings("rawtypes") /** * 拉流代理接口 */ @Tag(name = "拉流代理", description = "") @RestController @Slf4j @RequestMapping(value = "/api/proxy") public class StreamProxyController { @Autowired private IMediaServerService mediaServerService; @Autowired private IStreamProxyService streamProxyService; @Autowired private IStreamProxyPlayService streamProxyPlayService; @Autowired private UserSetting userSetting; @Operation(summary = "分页查询流代理", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "page", description = "当前页") @Parameter(name = "count", description = "每页查询数量") @Parameter(name = "query", description = "查询内容") @Parameter(name = "pulling", description = "是否正在拉流") @Parameter(name = "mediaServerId", description = "流媒体ID") @GetMapping(value = "/list") @ResponseBody public PageInfo list(@RequestParam(required = false)Integer page, @RequestParam(required = false)Integer count, @RequestParam(required = false)String query, @RequestParam(required = false)Boolean pulling, @RequestParam(required = false)String mediaServerId){ if (ObjectUtils.isEmpty(mediaServerId)) { mediaServerId = null; } if (ObjectUtils.isEmpty(query)) { query = null; } return streamProxyService.getAll(page, count, query, pulling, mediaServerId); } @Operation(summary = "查询流代理", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "app", description = "应用名") @Parameter(name = "stream", description = "流Id") @GetMapping(value = "/one") @ResponseBody public StreamProxy one(String app, String stream){ return streamProxyService.getStreamProxyByAppAndStream(app, stream); } @Operation(summary = "新增代理", security = @SecurityRequirement(name = JwtUtils.HEADER), parameters = { @Parameter(name = "param", description = "代理参数", required = true), }) @PostMapping(value = "/add") @ResponseBody public StreamProxy add(@RequestBody StreamProxy param){ log.info("添加代理: " + JSONObject.toJSONString(param)); if (ObjectUtils.isEmpty(param.getRelatesMediaServerId())) { param.setRelatesMediaServerId(null); } if (ObjectUtils.isEmpty(param.getType())) { param.setType("default"); } if (ObjectUtils.isEmpty(param.getGbId())) { param.setGbDeviceId(null); } param.setServerId(userSetting.getServerId()); streamProxyService.add(param); return param; } @Operation(summary = "更新代理", security = @SecurityRequirement(name = JwtUtils.HEADER), parameters = { @Parameter(name = "param", description = "代理参数", required = true), }) @PostMapping(value = "/update") @ResponseBody public StreamProxy update(@RequestBody StreamProxy param){ log.info("更新代理: " + JSONObject.toJSONString(param)); if (param.getId() == 0) { throw new ControllerException(ErrorCode.ERROR400.getCode(), "缺少代理信息的ID"); } if (ObjectUtils.isEmpty(param.getRelatesMediaServerId())) { param.setRelatesMediaServerId(null); } if (ObjectUtils.isEmpty(param.getGbId())) { param.setGbDeviceId(null); } streamProxyService.update(param); return param; } @GetMapping(value = "/ffmpeg_cmd/list") @ResponseBody @Operation(summary = "获取ffmpeg.cmd模板", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "mediaServerId", description = "流媒体ID", required = true) public Map getFFmpegCMDs(@RequestParam String mediaServerId){ log.debug("获取节点[ {} ]ffmpeg.cmd模板", mediaServerId ); MediaServer mediaServerItem = mediaServerService.getOne(mediaServerId); if (mediaServerItem == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "流媒体: " + mediaServerId + "未找到"); } return streamProxyService.getFFmpegCMDs(mediaServerItem); } @DeleteMapping(value = "/del") @ResponseBody @Operation(summary = "移除代理", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "app", description = "应用名", required = true) @Parameter(name = "stream", description = "流id", required = true) public void del(@RequestParam String app, @RequestParam String stream){ log.info("移除代理: " + app + "/" + stream); if (app == null || stream == null) { throw new ControllerException(ErrorCode.ERROR400.getCode(), app == null ?"app不能为null":"stream不能为null"); }else { streamProxyService.delteByAppAndStream(app, stream); } } @DeleteMapping(value = "/delete") @ResponseBody @Operation(summary = "移除代理", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "id", description = "代理ID", required = true) public void delte(int id){ log.info("移除代理: {}", id); streamProxyService.delete(id); } @GetMapping(value = "/start") @ResponseBody @Operation(summary = "播放代理", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "id", description = "代理Id", required = true) public DeferredResult> start(HttpServletRequest request, int id){ log.info("播放代理: {}", id); StreamProxy streamProxy = streamProxyService.getStreamProxy(id); Assert.notNull(streamProxy, "代理信息不存在"); DeferredResult> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue()); ErrorCallback callback = (code, msg, streamInfo) -> { if (code == InviteErrorCode.SUCCESS.getCode()) { WVPResult wvpResult = WVPResult.success(); if (streamInfo != null) { if (userSetting.getUseSourceIpAsStreamIp()) { streamInfo=streamInfo.clone();//深拷贝 String host; try { URL url=new URL(request.getRequestURL().toString()); host=url.getHost(); } catch (MalformedURLException e) { host=request.getLocalAddr(); } streamInfo.changeStreamIp(host); } if (!ObjectUtils.isEmpty(streamInfo.getMediaServer().getTranscodeSuffix()) && !"null".equalsIgnoreCase(streamInfo.getMediaServer().getTranscodeSuffix())) { streamInfo.setStream(streamInfo.getStream() + "_" + streamInfo.getMediaServer().getTranscodeSuffix()); } wvpResult.setData(new StreamContent(streamInfo)); }else { wvpResult.setCode(code); wvpResult.setMsg(msg); } result.setResult(wvpResult); }else { result.setResult(WVPResult.fail(code, msg)); } }; streamProxyPlayService.start(id, null, callback); return result; } @GetMapping(value = "/stop") @ResponseBody @Operation(summary = "停止播放", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "id", description = "代理Id", required = true) public void stop(int id){ log.info("停止播放: {}", id); streamProxyPlayService.stop(id); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/streamProxy/dao/StreamProxyMapper.java ================================================ package com.genersoft.iot.vmp.streamProxy.dao; import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy; import com.genersoft.iot.vmp.streamProxy.dao.provider.StreamProxyProvider; import org.apache.ibatis.annotations.*; import org.springframework.stereotype.Repository; import java.util.List; @Mapper @Repository public interface StreamProxyMapper { @Insert("INSERT INTO wvp_stream_proxy (type, app, stream,relates_media_server_id, src_url, " + "timeout, ffmpeg_cmd_key, rtsp_type, enable_audio, enable_mp4, enable, pulling, " + "enable_disable_none_reader, server_id, create_time) VALUES" + "(#{type}, #{app}, #{stream}, #{relatesMediaServerId}, #{srcUrl}, " + "#{timeout}, #{ffmpegCmdKey}, #{rtspType}, #{enableAudio}, #{enableMp4}, #{enable}, #{pulling}, " + "#{enableDisableNoneReader}, #{serverId}, #{createTime} )") @Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id") int add(StreamProxy streamProxyDto); @Update("UPDATE wvp_stream_proxy " + "SET type=#{type}, " + "app=#{app}," + "stream=#{stream}," + "relates_media_server_id=#{relatesMediaServerId}, " + "src_url=#{srcUrl}," + "timeout=#{timeout}, " + "ffmpeg_cmd_key=#{ffmpegCmdKey}, " + "rtsp_type=#{rtspType}, " + "enable_audio=#{enableAudio}, " + "enable=#{enable}, " + "pulling=#{pulling}, " + "enable_disable_none_reader=#{enableDisableNoneReader}, " + "enable_mp4=#{enableMp4} " + "WHERE id=#{id}") int update(StreamProxy streamProxyDto); @Delete("DELETE FROM wvp_stream_proxy WHERE app=#{app} AND stream=#{stream}") int delByAppAndStream(String app, String stream); @SelectProvider(type = StreamProxyProvider.class, method = "selectAll") List selectAll(@Param("query") String query, @Param("pulling") Boolean pulling, @Param("mediaServerId") String mediaServerId); @SelectProvider(type = StreamProxyProvider.class, method = "selectOneByAppAndStream") StreamProxy selectOneByAppAndStream(@Param("app") String app, @Param("stream") String stream); @SelectProvider(type = StreamProxyProvider.class, method = "selectForPushingInMediaServer") List selectForPushingInMediaServer(@Param("mediaServerId") String mediaServerId, @Param("enable") boolean enable); @Select("select count(1) from wvp_stream_proxy") int getAllCount(); @Select("select count(1) from wvp_stream_proxy where pulling = true") int getOnline(); @Delete("DELETE FROM wvp_stream_proxy WHERE id=#{id}") int delete(@Param("id") int id); @Delete(value = "") void deleteByList(List streamProxiesForRemove); @Update("UPDATE wvp_stream_proxy " + "SET pulling=true " + "WHERE id=#{id}") int online(@Param("id") int id); @Update("UPDATE wvp_stream_proxy " + "SET pulling=false " + "WHERE id=#{id}") int offline(@Param("id") int id); @SelectProvider(type = StreamProxyProvider.class, method = "select") StreamProxy select(@Param("id") int id); @Update("UPDATE wvp_stream_proxy " + " SET pulling=false, media_server_id = null," + " stream_key = null " + " WHERE id=#{id}") void removeStream(@Param("id")int id); @Update("UPDATE wvp_stream_proxy " + " SET pulling=#{pulling}, media_server_id = #{mediaServerId}, " + " stream_key = #{streamKey} " + " WHERE id=#{id}") void updateStream(StreamProxy streamProxy); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/streamProxy/dao/provider/StreamProxyProvider.java ================================================ package com.genersoft.iot.vmp.streamProxy.dao.provider; import com.genersoft.iot.vmp.common.enums.ChannelDataType; import java.util.Map; public class StreamProxyProvider { public String getBaseSelectSql(){ return "SELECT " + " st.*, " + ChannelDataType.STREAM_PROXY + " as data_type, " + " st.id as data_device_id, " + " wdc.*, " + " wdc.id as gb_id" + " FROM wvp_stream_proxy st " + " LEFT join wvp_device_channel wdc " + " on wdc.data_type = 3 and st.id = wdc.data_device_id "; } public String select(Map params ){ return getBaseSelectSql() + " WHERE st.id = " + params.get("id"); } public String selectForPushingInMediaServer(Map params ){ return getBaseSelectSql() + " WHERE st.pulling=true and st.media_server_id=#{mediaServerId} order by st.create_time desc"; } public String selectOneByAppAndStream(Map params ){ return getBaseSelectSql() + String.format(" WHERE st.app='%s' AND st.stream='%s' order by st.create_time desc", params.get("app"), params.get("stream")); } public String selectAll(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(getBaseSelectSql()); sqlBuild.append(" WHERE 1=1 "); if (params.get("query") != null) { sqlBuild.append(" AND ") .append(" (") .append(" st.app LIKE ").append("'%").append(params.get("query")).append("%' escape '/'") .append(" OR") .append(" st.stream LIKE ").append("'%").append(params.get("query")).append("%' escape '/'") .append(" OR") .append(" wdc.gb_device_id LIKE ").append("'%").append(params.get("query")).append("%' escape '/'") .append(" OR") .append(" wdc.gb_name LIKE ").append("'%").append(params.get("query")).append("%' escape '/'") .append(" )") ; } Object pulling = params.get("pulling"); if (pulling != null) { if ((Boolean) pulling) { sqlBuild.append(" AND st.pulling=1 "); }else { sqlBuild.append(" AND st.pulling=0 "); } } if (params.get("mediaServerId") != null) { sqlBuild.append(" AND st.media_server_id='").append(params.get("mediaServerId")).append("'"); } sqlBuild.append(" order by st.create_time desc"); return sqlBuild.toString(); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/streamProxy/service/IStreamProxyPlayService.java ================================================ package com.genersoft.iot.vmp.streamProxy.service; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy; import jakarta.validation.constraints.NotNull; public interface IStreamProxyPlayService { void start(int id, Boolean record, ErrorCallback callback); void startProxy(@NotNull StreamProxy streamProxy, ErrorCallback callback); void stop(int id); void stopProxy(StreamProxy streamProxy); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/streamProxy/service/IStreamProxyService.java ================================================ package com.genersoft.iot.vmp.streamProxy.service; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy; import com.genersoft.iot.vmp.vmanager.bean.ResourceBaseInfo; import com.github.pagehelper.PageInfo; import java.util.Map; public interface IStreamProxyService { /** * 分页查询 * @param page * @param count * @return */ PageInfo getAll(Integer page, Integer count, String query, Boolean pulling,String mediaServerId); /** * 删除视频代理 * @param app * @param stream */ void delteByAppAndStream(String app, String stream); /** * 启用视频代理 * @param app * @param stream * @return */ void startByAppAndStream(String app, String stream, ErrorCallback callback); /** * 停用用视频代理 * @param app * @param stream * @return */ void stopByAppAndStream(String app, String stream); /** * 获取ffmpeg.cmd模板 * * @return */ Map getFFmpegCMDs(MediaServer mediaServerItem); /** * 根据app与stream获取streamProxy * @return */ StreamProxy getStreamProxyByAppAndStream(String app, String streamId); /** * 新的节点加入 * @param mediaServer * @return */ void zlmServerOnline(MediaServer mediaServer); /** * 节点离线 * @param mediaServer * @return */ void zlmServerOffline(MediaServer mediaServer); /** * 更新代理流 */ boolean update(StreamProxy streamProxyItem); /** * 获取统计信息 * @return */ ResourceBaseInfo getOverview(); void add(StreamProxy streamProxy); StreamProxy getStreamProxy(int id); void delete(int id); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/streamProxy/service/impl/SourcePlayServiceForStreamProxyImpl.java ================================================ package com.genersoft.iot.vmp.streamProxy.service.impl; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.common.enums.ChannelDataType; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import com.genersoft.iot.vmp.gb28181.bean.Platform; import com.genersoft.iot.vmp.gb28181.service.ISourcePlayService; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.streamProxy.service.IStreamProxyPlayService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import javax.sip.message.Response; @Slf4j @Service(ChannelDataType.PLAY_SERVICE + ChannelDataType.STREAM_PROXY) public class SourcePlayServiceForStreamProxyImpl implements ISourcePlayService { @Autowired private IStreamProxyPlayService playService; @Override public void play(CommonGBChannel channel, Platform platform, Boolean record, ErrorCallback callback) { // 拉流代理通道 try { playService.start(channel.getDataDeviceId(), record, callback); }catch (Exception e) { callback.run(Response.BUSY_HERE, "busy here", null); } } @Override public void stopPlay(CommonGBChannel channel) { // 拉流代理通道 try { playService.stop(channel.getDataDeviceId()); }catch (Exception e) { log.error("[停止点播失败] {}({})", channel.getGbName(), channel.getGbDeviceId(), e); } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/streamProxy/service/impl/StreamProxyPlayServiceImpl.java ================================================ package com.genersoft.iot.vmp.streamProxy.service.impl; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.conf.DynamicTask; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.event.hook.Hook; import com.genersoft.iot.vmp.media.event.hook.HookSubscribe; import com.genersoft.iot.vmp.media.event.hook.HookType; import com.genersoft.iot.vmp.media.service.IMediaServerService; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.service.bean.InviteErrorCode; import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcPlayService; import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy; import com.genersoft.iot.vmp.streamProxy.dao.StreamProxyMapper; import com.genersoft.iot.vmp.streamProxy.service.IStreamProxyPlayService; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import jakarta.validation.constraints.NotNull; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import java.util.UUID; /** * 视频代理业务 */ @Slf4j @Service public class StreamProxyPlayServiceImpl implements IStreamProxyPlayService { @Autowired private StreamProxyMapper streamProxyMapper; @Autowired private IMediaServerService mediaServerService; @Autowired private HookSubscribe subscribe; @Autowired private DynamicTask dynamicTask; @Autowired private UserSetting userSetting; @Autowired private IRedisRpcPlayService redisRpcPlayService; @Override public void start(int id, Boolean record, ErrorCallback callback) { log.info("[拉流代理], 开始拉流,ID:{}", id); StreamProxy streamProxy = streamProxyMapper.select(id); if (streamProxy == null) { throw new ControllerException(ErrorCode.ERROR404.getCode(), "代理信息未找到"); } log.info("[拉流代理] 类型: {}, app:{}, stream: {}, 流地址: {}", streamProxy.getType(), streamProxy.getApp(), streamProxy.getStream(), streamProxy.getSrcUrl()); if (record != null) { streamProxy.setEnableMp4(record); } startProxy(streamProxy, callback); } @Override public void startProxy(@NotNull StreamProxy streamProxy, ErrorCallback callback){ if (!streamProxy.isEnable()) { callback.run(ErrorCode.ERROR100.getCode(), "代理未启用", null); return; } if (streamProxy.getServerId() == null) { streamProxy.setServerId(userSetting.getServerId()); } if (!userSetting.getServerId().equals(streamProxy.getServerId())) { log.info("[拉流代理] 由其他服务{}管理", streamProxy.getServerId()); redisRpcPlayService.playProxy(streamProxy.getServerId(), streamProxy.getId(), callback); return; } if (streamProxy.getMediaServerId() != null) { StreamInfo streamInfo = mediaServerService.getStreamInfoByAppAndStreamWithCheck(streamProxy.getApp(), streamProxy.getStream(), streamProxy.getMediaServerId(), null, false); if (streamInfo != null) { callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo); return; } } MediaServer mediaServer; String mediaServerId = streamProxy.getRelatesMediaServerId(); if (mediaServerId == null) { mediaServer = mediaServerService.getMediaServerForMinimumLoad(null); }else { mediaServer = mediaServerService.getOne(mediaServerId); } if (mediaServer == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), mediaServerId == null?"未找到可用的媒体节点":"未找到节点" + mediaServerId); } // 设置流超时的定时任务 String timeOutTaskKey = UUID.randomUUID().toString(); Hook rtpHook = Hook.getInstance(HookType.on_media_arrival, streamProxy.getApp(), streamProxy.getStream(), mediaServer.getId()); dynamicTask.startDelay(timeOutTaskKey, () -> { log.info("[拉流代理] 收流超时,app:{},stream: {}", streamProxy.getApp(), streamProxy.getStream()); // 收流超时 subscribe.removeSubscribe(rtpHook); callback.run(InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getCode(), InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getMsg(), null); }, userSetting.getPlayTimeout()); // 开启流到来的监听 subscribe.addSubscribe(rtpHook, (hookData) -> { log.info("[拉流代理] 收流成功,app:{},stream: {}", hookData.getApp(), hookData.getStream()); dynamicTask.stop(timeOutTaskKey); StreamInfo streamInfo = mediaServerService.getStreamInfoByAppAndStream(mediaServer, hookData.getApp(), hookData.getStream(), hookData.getMediaInfo(), null); // hook响应 callback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), streamInfo); subscribe.removeSubscribe(rtpHook); streamProxy.setPulling(true); streamProxyMapper.updateStream(streamProxy); }); String key = mediaServerService.startProxy(mediaServer, streamProxy); streamProxy.setStreamKey(key); streamProxy.setMediaServerId(mediaServer.getId()); streamProxyMapper.updateStream(streamProxy); } @Override public void stop(int id) { StreamProxy streamProxy = streamProxyMapper.select(id); if (streamProxy == null) { throw new ControllerException(ErrorCode.ERROR404.getCode(), "代理信息未找到"); } if (!userSetting.getServerId().equals(streamProxy.getServerId())) { redisRpcPlayService.stopProxy(streamProxy.getServerId(), streamProxy.getId()); return; } stopProxy(streamProxy); } @Override public void stopProxy(StreamProxy streamProxy){ String mediaServerId = streamProxy.getMediaServerId(); Assert.notNull(mediaServerId, "代理节点不存在"); MediaServer mediaServer = mediaServerService.getOne(mediaServerId); if (mediaServer == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "媒体节点不存在"); } if (ObjectUtils.isEmpty(streamProxy.getStreamKey())) { mediaServerService.closeStreams(mediaServer, streamProxy.getApp(), streamProxy.getStream()); }else { mediaServerService.stopProxy(mediaServer, streamProxy.getStreamKey(), streamProxy.getType()); } streamProxyMapper.removeStream(streamProxy.getId()); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/streamProxy/service/impl/StreamProxyServiceImpl.java ================================================ package com.genersoft.iot.vmp.streamProxy.service.impl; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.common.enums.ChannelDataType; import com.genersoft.iot.vmp.common.enums.MediaApp; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import com.genersoft.iot.vmp.gb28181.service.IGbChannelService; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.event.media.MediaArrivalEvent; import com.genersoft.iot.vmp.media.event.media.MediaDepartureEvent; import com.genersoft.iot.vmp.media.event.media.MediaNotFoundEvent; import com.genersoft.iot.vmp.media.event.mediaServer.MediaServerOfflineEvent; import com.genersoft.iot.vmp.media.event.mediaServer.MediaServerOnlineEvent; import com.genersoft.iot.vmp.media.service.IMediaServerService; import com.genersoft.iot.vmp.media.zlm.dto.hook.OriginType; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy; import com.genersoft.iot.vmp.streamProxy.dao.StreamProxyMapper; import com.genersoft.iot.vmp.streamProxy.service.IStreamProxyPlayService; import com.genersoft.iot.vmp.streamProxy.service.IStreamProxyService; import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.ResourceBaseInfo; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.event.EventListener; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 视频代理业务 */ @Slf4j @Service public class StreamProxyServiceImpl implements IStreamProxyService { @Autowired private StreamProxyMapper streamProxyMapper; @Autowired private IRedisCatchStorage redisCatchStorage; @Autowired private UserSetting userSetting; @Autowired private IStreamProxyPlayService playService; @Autowired private IMediaServerService mediaServerService; @Autowired private IGbChannelService gbChannelService; @Autowired DataSourceTransactionManager dataSourceTransactionManager; @Autowired TransactionDefinition transactionDefinition; /** * 流到来的处理 */ @Async("taskExecutor") @Transactional @org.springframework.context.event.EventListener public void onApplicationEvent(MediaArrivalEvent event) { if ("rtsp".equals(event.getSchema())) { streamChangeHandler(event.getApp(), event.getStream(), event.getMediaServer().getId(), true); } } /** * 流离开的处理 */ @Async("taskExecutor") @EventListener @Transactional public void onApplicationEvent(MediaDepartureEvent event) { if ("rtsp".equals(event.getSchema())) { streamChangeHandler(event.getApp(), event.getStream(), event.getMediaServer().getId(), false); } } /** * 流未找到的处理 */ @Async("taskExecutor") @EventListener public void onApplicationEvent(MediaNotFoundEvent event) { if (MediaApp.isKeywords(event.getApp())) { return; } // 拉流代理 StreamProxy streamProxyByAppAndStream = getStreamProxyByAppAndStream(event.getApp(), event.getStream()); if (streamProxyByAppAndStream != null && streamProxyByAppAndStream.isEnableDisableNoneReader()) { startByAppAndStream(event.getApp(), event.getStream(), ((code, msg, data) -> { log.info("[拉流代理] 自动点播成功, app: {}, stream: {}", event.getApp(), event.getStream()); })); } } /** * 流媒体节点上线 */ @Async("taskExecutor") @EventListener @Transactional public void onApplicationEvent(MediaServerOnlineEvent event) { zlmServerOnline(event.getMediaServer()); } /** * 流媒体节点离线 */ @Async("taskExecutor") @EventListener @Transactional public void onApplicationEvent(MediaServerOfflineEvent event) { zlmServerOffline(event.getMediaServer()); } @Override @Transactional public void add(StreamProxy streamProxy) { StreamProxy streamProxyInDb = streamProxyMapper.selectOneByAppAndStream(streamProxy.getApp(), streamProxy.getStream()); if (streamProxyInDb != null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "APP+STREAM已经存在"); } if (streamProxy.getGbDeviceId() != null) { gbChannelService.add(streamProxy.buildCommonGBChannel()); } streamProxy.setCreateTime(DateUtil.getNow()); streamProxy.setUpdateTime(DateUtil.getNow()); streamProxyMapper.add(streamProxy); streamProxy.setDataType(ChannelDataType.STREAM_PROXY); streamProxy.setDataDeviceId(streamProxy.getId()); } @Override public void delete(int id) { StreamProxy streamProxy = getStreamProxy(id); if (streamProxy == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "代理不存在"); } delete(streamProxy); } private void delete(StreamProxy streamProxy) { Assert.notNull(streamProxy, "代理不可为NULL"); if (streamProxy.getPulling() != null && streamProxy.getPulling()) { playService.stopProxy(streamProxy); } if (streamProxy.getGbId() > 0) { gbChannelService.delete(streamProxy.getGbId()); } streamProxyMapper.delete(streamProxy.getId()); } @Override @Transactional public void delteByAppAndStream(String app, String stream) { StreamProxy streamProxy = streamProxyMapper.selectOneByAppAndStream(app, stream); if (streamProxy == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "代理不存在"); } delete(streamProxy); } /** * 更新代理流 */ @Override public boolean update(StreamProxy streamProxy) { streamProxy.setUpdateTime(DateUtil.getNow()); StreamProxy streamProxyInDb = streamProxyMapper.select(streamProxy.getId()); if (streamProxyInDb == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "代理不存在"); } int updateResult = streamProxyMapper.update(streamProxy); if (updateResult > 0 && !ObjectUtils.isEmpty(streamProxy.getGbDeviceId())) { if (streamProxy.getGbId() > 0) { gbChannelService.update(streamProxy.buildCommonGBChannel()); } else { gbChannelService.add(streamProxy.buildCommonGBChannel()); } } return true; } @Override public PageInfo getAll(Integer page, Integer count, String query, Boolean pulling, String mediaServerId) { PageHelper.startPage(page, count); if (query != null) { query = query.replaceAll("/", "//") .replaceAll("%", "/%") .replaceAll("_", "/_"); } List all = streamProxyMapper.selectAll(query, pulling, mediaServerId); return new PageInfo<>(all); } @Override public void startByAppAndStream(String app, String stream, ErrorCallback callback) { StreamProxy streamProxy = streamProxyMapper.selectOneByAppAndStream(app, stream); if (streamProxy == null) { throw new ControllerException(ErrorCode.ERROR404.getCode(), "代理信息未找到"); } playService.startProxy(streamProxy, callback); } @Override public void stopByAppAndStream(String app, String stream) { StreamProxy streamProxy = streamProxyMapper.selectOneByAppAndStream(app, stream); if (streamProxy == null) { throw new ControllerException(ErrorCode.ERROR404.getCode(), "代理信息未找到"); } playService.stopProxy(streamProxy); } @Override public Map getFFmpegCMDs(MediaServer mediaServer) { return mediaServerService.getFFmpegCMDs(mediaServer); } @Override public StreamProxy getStreamProxyByAppAndStream(String app, String stream) { return streamProxyMapper.selectOneByAppAndStream(app, stream); } @Override @Transactional public void zlmServerOnline(MediaServer mediaServer) { if (mediaServer == null) { return; } // 这里主要是控制数据库/redis缓存/以及zlm中存在的代理流 三者状态一致。以数据库中数据为根本 redisCatchStorage.removeStream(mediaServer.getId(), "PULL"); List streamProxies = streamProxyMapper.selectForPushingInMediaServer(mediaServer.getId(), true); if (streamProxies.isEmpty()) { return; } Map streamProxyMapForDb = new HashMap<>(); for (StreamProxy streamProxy : streamProxies) { streamProxyMapForDb.put(streamProxy.getApp() + "_" + streamProxy.getStream(), streamProxy); } List streamInfoList = mediaServerService.getMediaList(mediaServer, null, null, null); List channelListForOnline = new ArrayList<>(); for (StreamInfo streamInfo : streamInfoList) { String key = streamInfo.getApp() + streamInfo.getStream(); StreamProxy streamProxy = streamProxyMapForDb.get(key); if (streamProxy == null) { // 流媒体存在,数据库中不存在 continue; } if (streamInfo.getOriginType() == OriginType.PULL.ordinal() || streamInfo.getOriginType() == OriginType.FFMPEG_PULL.ordinal()) { if (streamProxyMapForDb.get(key) != null) { redisCatchStorage.addStream(mediaServer, "pull", streamInfo.getApp(), streamInfo.getStream(), streamInfo.getMediaInfo()); if ("OFF".equalsIgnoreCase(streamProxy.getGbStatus()) && streamProxy.getGbId() > 0) { streamProxy.setGbStatus("ON"); channelListForOnline.add(streamProxy.buildCommonGBChannel()); } streamProxyMapForDb.remove(key); } } } if (!channelListForOnline.isEmpty()) { gbChannelService.online(channelListForOnline, true); } List channelListForOffline = new ArrayList<>(); List streamProxiesForRemove = new ArrayList<>(); if (!streamProxyMapForDb.isEmpty()) { for (StreamProxy streamProxy : streamProxyMapForDb.values()) { if ("ON".equalsIgnoreCase(streamProxy.getGbStatus()) && streamProxy.getGbId() > 0) { streamProxy.setGbStatus("OFF"); channelListForOffline.add(streamProxy.buildCommonGBChannel()); } } } if (!channelListForOffline.isEmpty()) { gbChannelService.offline(channelListForOffline, true); } if (!streamProxiesForRemove.isEmpty()) { streamProxyMapper.deleteByList(streamProxiesForRemove); } if (!streamProxyMapForDb.isEmpty()) { for (StreamProxy streamProxy : streamProxyMapForDb.values()) { streamProxyMapper.offline(streamProxy.getId()); } } } @Override public void zlmServerOffline(MediaServer mediaServer) { List streamProxies = streamProxyMapper.selectForPushingInMediaServer(mediaServer.getId(), true); // 清理redis相关的缓存 redisCatchStorage.removeStream(mediaServer.getId(), "PULL"); if (streamProxies.isEmpty()) { return; } List streamProxiesForSendMessage = new ArrayList<>(); List channelListForOffline = new ArrayList<>(); for (StreamProxy streamProxy : streamProxies) { if (streamProxy.getGbId() > 0 && "ON".equalsIgnoreCase(streamProxy.getGbStatus())) { channelListForOffline.add(streamProxy.buildCommonGBChannel()); } if ("ON".equalsIgnoreCase(streamProxy.getGbStatus())) { streamProxiesForSendMessage.add(streamProxy); } } if (!channelListForOffline.isEmpty()) { // 修改国标关联的国标通道的状态 gbChannelService.offline(channelListForOffline, true); } if (!streamProxiesForSendMessage.isEmpty()) { for (StreamProxy streamProxy : streamProxiesForSendMessage) { JSONObject jsonObject = new JSONObject(); jsonObject.put("serverId", userSetting.getServerId()); jsonObject.put("app", streamProxy.getApp()); jsonObject.put("stream", streamProxy.getStream()); jsonObject.put("register", false); jsonObject.put("mediaServerId", mediaServer); redisCatchStorage.sendStreamChangeMsg("pull", jsonObject); } } } @Transactional public void streamChangeHandler(String app, String stream, String mediaServerId, boolean status) { // 状态变化时推送到国标上级 StreamProxy streamProxy = streamProxyMapper.selectOneByAppAndStream(app, stream); if (streamProxy == null) { return; } streamProxy.setPulling(status); streamProxy.setMediaServerId(mediaServerId); streamProxy.setUpdateTime(DateUtil.getNow()); streamProxyMapper.updateStream(streamProxy); } @Override public ResourceBaseInfo getOverview() { int total = streamProxyMapper.getAllCount(); int online = streamProxyMapper.getOnline(); return new ResourceBaseInfo(total, online); } @Override public StreamProxy getStreamProxy(int id) { return streamProxyMapper.select(id); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/streamPush/bean/BatchRemoveParam.java ================================================ package com.genersoft.iot.vmp.streamPush.bean; import lombok.Data; import java.util.Set; @Data public class BatchRemoveParam { private Set ids; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/streamPush/bean/RedisPushStreamMessage.java ================================================ package com.genersoft.iot.vmp.streamPush.bean; import lombok.Data; @Data public class RedisPushStreamMessage { private String gbId; private String app; private String stream; private String name; private Boolean status; // 终端所属的虚拟组织 private String groupGbId; // 终端所属的虚拟组织别名 可选,可作为地方同步组织结构到wvp时的关联关系 private String groupAlias; // 生产商 private String manufacturer; // 设备型号 private String model; // 摄像机类型 private Integer ptzType; public StreamPush buildstreamPush() { StreamPush push = new StreamPush(); push.setApp(app); push.setStream(stream); push.setGbName(name); push.setGbDeviceId(gbId); push.setStartOfflinePush(true); push.setGbManufacturer(manufacturer); push.setGbModel(model); push.setGbPtzType(ptzType); if (status != null) { push.setGbStatus(status?"ON":"OFF"); } push.setEnableBroadcast(0); return push; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/streamPush/bean/StreamPush.java ================================================ package com.genersoft.iot.vmp.streamPush.bean; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.common.enums.ChannelDataType; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import com.genersoft.iot.vmp.media.event.media.MediaArrivalEvent; import com.genersoft.iot.vmp.utils.DateUtil; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import org.jetbrains.annotations.NotNull; import org.springframework.util.ObjectUtils; @Data @Schema(description = "推流信息") @EqualsAndHashCode(callSuper = true) @NoArgsConstructor public class StreamPush extends CommonGBChannel implements Comparable{ /** * id */ @Schema(description = "id") private Integer id; /** * 应用名 */ @Schema(description = "应用名") private String app; /** * 流id */ @Schema(description = "流id") private String stream; /** * 使用的流媒体ID */ @Schema(description = "使用的流媒体ID") private String mediaServerId; /** * 使用的服务ID */ @Schema(description = "使用的服务ID") private String serverId; /** * 推流时间 */ @Schema(description = "推流时间") private String pushTime; /** * 更新时间 */ @Schema(description = "更新时间") private String updateTime; /** * 创建时间 */ @Schema(description = "创建时间") private String createTime; /** * 是否正在推流 */ @Schema(description = "是否正在推流") private boolean pushing; /** * 拉起离线推流 */ @Schema(description = "拉起离线推流") private boolean startOfflinePush; /** * 速度,单位:km/h (可选) */ @Schema(description = "GPS的速度") private Double gpsSpeed; /** * 方向,取值为当前摄像头方向与正北方的顺时针夹角,取值范围0°~360°,单位:(°)(可选) */ @Schema(description = "GPS的方向") private Double gpsDirection; /** * 海拔高度,单位:m(可选) */ @Schema(description = "GPS的海拔高度") private Double gpsAltitude; /** * GPS的更新时间 */ @Schema(description = "GPS的更新时间") private String gpsTime; private String uniqueKey; private Integer dataType = ChannelDataType.STREAM_PUSH; @Override public int compareTo(@NotNull StreamPush streamPushItem) { return Long.valueOf(DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(this.createTime) - DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(streamPushItem.getCreateTime())).intValue(); } public static StreamPush getInstance(StreamInfo streamInfo) { StreamPush streamPush = new StreamPush(); streamPush.setApp(streamInfo.getApp()); if (streamInfo.getMediaServer() != null) { streamPush.setMediaServerId(streamInfo.getMediaServer().getId()); } streamPush.setStream(streamInfo.getStream()); streamPush.setCreateTime(DateUtil.getNow()); streamPush.setServerId(streamInfo.getServerId()); return streamPush; } public static StreamPush getInstance(MediaArrivalEvent event, String serverId){ StreamPush streamPushItem = new StreamPush(); streamPushItem.setApp(event.getApp()); streamPushItem.setMediaServerId(event.getMediaServer().getId()); streamPushItem.setStream(event.getStream()); streamPushItem.setCreateTime(DateUtil.getNow()); streamPushItem.setServerId(serverId); return streamPushItem; } public CommonGBChannel buildCommonGBChannel() { if (ObjectUtils.isEmpty(this.getGbDeviceId())) { return null; } if (ObjectUtils.isEmpty(this.getGbName())) { this.setGbName( app+ "-" +stream); } this.setDataType(ChannelDataType.STREAM_PUSH); this.setDataDeviceId(this.getId()); return this; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/streamPush/bean/StreamPushExcelDto.java ================================================ package com.genersoft.iot.vmp.streamPush.bean; import com.alibaba.excel.annotation.ExcelProperty; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @Data public class StreamPushExcelDto { @ExcelProperty("名称") private String name; @ExcelProperty("应用名") private String app; @ExcelProperty("流ID") private String stream; @ExcelProperty("国标ID") private String gbDeviceId; @ExcelProperty("在线状态") private boolean status; @Schema(description = "经度 WGS-84坐标系") private Double longitude; @Schema(description = "纬度 WGS-84坐标系") private Double latitude; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/streamPush/controller/StreamPushController.java ================================================ package com.genersoft.iot.vmp.streamPush.controller; import com.alibaba.excel.EasyExcel; import com.alibaba.excel.ExcelReader; import com.alibaba.excel.exception.ExcelDataConvertException; import com.alibaba.excel.read.metadata.ReadSheet; import com.genersoft.iot.vmp.common.enums.ChannelDataType; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.conf.security.JwtUtils; import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage; import com.genersoft.iot.vmp.media.service.IMediaServerService; import com.genersoft.iot.vmp.service.IMediaService; import com.genersoft.iot.vmp.streamPush.bean.BatchRemoveParam; import com.genersoft.iot.vmp.streamPush.bean.StreamPush; import com.genersoft.iot.vmp.streamPush.bean.StreamPushExcelDto; import com.genersoft.iot.vmp.streamPush.enent.StreamPushUploadFileHandler; import com.genersoft.iot.vmp.streamPush.service.IStreamPushPlayService; import com.genersoft.iot.vmp.streamPush.service.IStreamPushService; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.StreamContent; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import com.github.pagehelper.PageInfo; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.web.bind.annotation.*; import org.springframework.web.context.request.async.DeferredResult; import org.springframework.web.multipart.MultipartFile; import jakarta.servlet.http.HttpServletRequest; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; @Tag(name = "推流信息管理") @RestController @Slf4j @RequestMapping(value = "/api/push") public class StreamPushController { @Autowired private IStreamPushService streamPushService; @Autowired private IStreamPushPlayService streamPushPlayService; @Autowired private IMediaServerService mediaServerService; @Autowired private DeferredResultHolder resultHolder; @Autowired private IMediaService mediaService; @Autowired private UserSetting userSetting; @GetMapping(value = "/list") @ResponseBody @Operation(summary = "推流列表查询", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "page", description = "当前页") @Parameter(name = "count", description = "每页查询数量") @Parameter(name = "query", description = "查询内容") @Parameter(name = "pushing", description = "是否正在推流") @Parameter(name = "mediaServerId", description = "流媒体ID") public PageInfo list(@RequestParam(required = false)Integer page, @RequestParam(required = false)Integer count, @RequestParam(required = false)String query, @RequestParam(required = false)Boolean pushing, @RequestParam(required = false)String mediaServerId ){ if (ObjectUtils.isEmpty(query)) { query = null; } if (ObjectUtils.isEmpty(mediaServerId)) { mediaServerId = null; } PageInfo pushList = streamPushService.getPushList(page, count, query, pushing, mediaServerId); return pushList; } @PostMapping(value = "/remove") @ResponseBody @Operation(summary = "删除", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "id", description = "应用名", required = true) public void delete(int id){ if (streamPushService.delete(id) <= 0){ throw new ControllerException(ErrorCode.ERROR100); } } @PostMapping(value = "upload") @ResponseBody public DeferredResult>> uploadChannelFile(@RequestParam(value = "file") MultipartFile file){ // 最多处理文件一个小时 DeferredResult>> result = new DeferredResult<>(60*60*1000L); // 录像查询以channelId作为deviceId查询 String key = DeferredResultHolder.UPLOAD_FILE_CHANNEL; String uuid = UUID.randomUUID().toString(); log.info("通道导入文件类型: {}",file.getContentType() ); if (file.isEmpty()) { log.warn("通道导入文件为空"); WVPResult wvpResult = new WVPResult<>(); wvpResult.setCode(-1); wvpResult.setMsg("文件为空"); result.setResult(ResponseEntity.status(HttpStatus.BAD_REQUEST).body(wvpResult)); return result; } if (file.getContentType() == null) { WVPResult wvpResult = new WVPResult<>(); wvpResult.setCode(-1); wvpResult.setMsg("无法识别文件类型"); result.setResult(ResponseEntity.status(HttpStatus.BAD_REQUEST).body(wvpResult)); return result; } // 同时只处理一个文件 if (resultHolder.exist(key, null)) { log.warn("已有导入任务正在执行"); WVPResult wvpResult = new WVPResult<>(); wvpResult.setCode(-1); wvpResult.setMsg("已有导入任务正在执行"); result.setResult(ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).body(wvpResult)); return result; } resultHolder.put(key, uuid, result); result.onTimeout(()->{ log.warn("通道导入超时,可能文件过大"); RequestMessage msg = new RequestMessage(); msg.setKey(key); WVPResult wvpResult = new WVPResult<>(); wvpResult.setCode(-1); wvpResult.setMsg("导入超时,可能文件过大"); msg.setData(wvpResult); resultHolder.invokeAllResult(msg); }); //获取文件流 InputStream inputStream = null; try { String name = file.getName(); inputStream = file.getInputStream(); } catch (IOException e) { log.error("未处理的异常 ", e); } try { //传入参数 ExcelReader excelReader = EasyExcel.read(inputStream, StreamPushExcelDto.class, new StreamPushUploadFileHandler(streamPushService, mediaServerService.getDefaultMediaServer().getId(), (errorStreams, errorGBs)->{ log.info("通道导入成功,存在重复App+Stream为{}个,存在国标ID为{}个", errorStreams.size(), errorGBs.size()); RequestMessage msg = new RequestMessage(); msg.setKey(key); WVPResult>> wvpResult = new WVPResult<>(); if (errorStreams.isEmpty() && errorGBs.isEmpty()) { wvpResult.setCode(0); wvpResult.setMsg("成功"); }else { wvpResult.setCode(1); wvpResult.setMsg("导入成功。但是存在重复数据"); Map> errorData = new HashMap<>(); errorData.put("gbId", errorGBs); errorData.put("stream", errorStreams); wvpResult.setData(errorData); } msg.setData(wvpResult); resultHolder.invokeAllResult(msg); })).build(); ReadSheet readSheet = EasyExcel.readSheet(0).build(); excelReader.read(readSheet); excelReader.finish(); }catch (ExcelDataConvertException e) { log.error("通道导入失败:行: {}, 列: {}, 内容: {}", e.getRowIndex(), e.getColumnIndex(), e.getCellData().getStringValue()); RequestMessage msg = new RequestMessage(); msg.setKey(key); WVPResult wvpResult = new WVPResult<>(); wvpResult.setCode(ErrorCode.ERROR100.getCode()); wvpResult.setMsg("数据异常: " + e.getRowIndex() +"行" + e.getColumnIndex() + "列, 内容:" + e.getCellData().getStringValue() ); msg.setData(wvpResult); resultHolder.invokeAllResult(msg); }catch (Exception e) { log.warn("通道导入失败:", e); RequestMessage msg = new RequestMessage(); msg.setKey(key); WVPResult wvpResult = new WVPResult<>(); wvpResult.setCode(ErrorCode.ERROR100.getCode()); wvpResult.setMsg("通道导入失败: " + e.getMessage() ); msg.setData(wvpResult); resultHolder.invokeAllResult(msg); } return result; } /** * 添加推流信息 * @param stream 推流信息 * @return */ @PostMapping(value = "/add") @ResponseBody @Operation(summary = "添加推流信息", security = @SecurityRequirement(name = JwtUtils.HEADER)) public StreamPush add(@RequestBody StreamPush stream){ if (ObjectUtils.isEmpty(stream.getGbId())) { throw new ControllerException(ErrorCode.ERROR400.getCode(), "国标ID不可为空"); } if (ObjectUtils.isEmpty(stream.getApp()) && ObjectUtils.isEmpty(stream.getStream())) { throw new ControllerException(ErrorCode.ERROR400.getCode(), "app或stream不可为空"); } stream.setGbStatus("OFF"); stream.setPushing(false); if (!streamPushService.add(stream)) { throw new ControllerException(ErrorCode.ERROR100); } stream.setDataType(ChannelDataType.STREAM_PUSH); stream.setDataDeviceId(stream.getId()); return stream; } @PostMapping(value = "/update") @ResponseBody @Operation(summary = "更新推流信息", security = @SecurityRequirement(name = JwtUtils.HEADER)) public void update(@RequestBody StreamPush stream){ if (ObjectUtils.isEmpty(stream.getId())) { throw new ControllerException(ErrorCode.ERROR400.getCode(), "ID不可为空"); } if (!streamPushService.update(stream)) { throw new ControllerException(ErrorCode.ERROR100); } } @DeleteMapping(value = "/batchRemove") @ResponseBody @Operation(summary = "删除多个推流", security = @SecurityRequirement(name = JwtUtils.HEADER)) public void batchStop(@RequestBody BatchRemoveParam ids){ if(ids.getIds().isEmpty()) { return; } streamPushService.batchRemove(ids.getIds()); } @GetMapping(value = "/start") @ResponseBody @Operation(summary = "开始播放", security = @SecurityRequirement(name = JwtUtils.HEADER)) public DeferredResult> start(HttpServletRequest request, Integer id){ Assert.notNull(id, "推流ID不可为NULL"); DeferredResult> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue()); result.onTimeout(()->{ WVPResult fail = WVPResult.fail(ErrorCode.ERROR100.getCode(), "等待推流超时"); result.setResult(fail); }); streamPushPlayService.start(id, (code, msg, streamInfo) -> { if (code == 0 && streamInfo != null) { if (userSetting.getUseSourceIpAsStreamIp()) { streamInfo=streamInfo.clone();//深拷贝 String host; try { URL url=new URL(request.getRequestURL().toString()); host=url.getHost(); } catch (MalformedURLException e) { host=request.getLocalAddr(); } streamInfo.changeStreamIp(host); } WVPResult success = WVPResult.success(new StreamContent(streamInfo)); result.setResult(success); } }, null, null); return result; } @GetMapping(value = "/forceClose") @ResponseBody @Operation(summary = "强制停止推流", security = @SecurityRequirement(name = JwtUtils.HEADER)) public void stop(String app, String stream){ streamPushPlayService.stop(app, stream); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/streamPush/dao/StreamPushMapper.java ================================================ package com.genersoft.iot.vmp.streamPush.dao; import com.genersoft.iot.vmp.common.enums.ChannelDataType; import com.genersoft.iot.vmp.streamPush.bean.StreamPush; import com.genersoft.iot.vmp.service.bean.StreamPushItemFromRedis; import org.apache.ibatis.annotations.*; import org.springframework.stereotype.Repository; import java.util.List; import java.util.Map; import java.util.Set; @Mapper @Repository public interface StreamPushMapper { Integer dataType = ChannelDataType.GB28181; @Insert("INSERT INTO wvp_stream_push (app, stream, media_server_id, server_id, push_time, update_time, create_time, pushing, start_offline_push) VALUES" + "(#{app}, #{stream}, #{mediaServerId} , #{serverId} , #{pushTime} ,#{updateTime}, #{createTime}, #{pushing}, #{startOfflinePush})") @Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id") int add(StreamPush streamPushItem); @Update(value = {" "}) int update(StreamPush streamPushItem); @Delete("DELETE FROM wvp_stream_push WHERE id=#{id}") int del(@Param("id") int id); @Select(value = {" "}) List selectAll(@Param("query") String query, @Param("pushing") Boolean pushing, @Param("mediaServerId") String mediaServerId); @Select("SELECT st.*, st.id as data_device_id, wdc.*, wdc.id as gb_id FROM wvp_stream_push st LEFT join wvp_device_channel wdc on wdc.data_type = 2 and st.id = wdc.data_device_id WHERE st.app=#{app} AND st.stream=#{stream}") StreamPush selectByAppAndStream(@Param("app") String app, @Param("stream") String stream); @Insert("") @Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id") int addAll(List streamPushItems); @Select("SELECT st.*, st.id as data_device_id, wdc.*, wdc.id as gb_id FROM wvp_stream_push st LEFT join wvp_device_channel wdc on wdc.data_type = 2 and st.id = wdc.data_device_id WHERE st.media_server_id=#{mediaServerId}") List selectAllByMediaServerId(String mediaServerId); @Select("SELECT st.*, st.id as data_device_id, wdc.*, wdc.id as gb_id FROM wvp_stream_push st LEFT join wvp_device_channel wdc on wdc.data_type = 2 and st.id = wdc.data_device_id WHERE st.media_server_id=#{mediaServerId} and wdc.gb_device_id is null") List selectAllByMediaServerIdWithOutGbID(String mediaServerId); @Update("UPDATE wvp_stream_push " + "SET pushing=#{pushing}, server_id=#{serverId}, media_server_id=#{mediaServerId} " + "WHERE id=#{id}") int updatePushStatus(StreamPush streamPush); @Select("") List getListInList(List offlineStreams); @Select("SELECT CONCAT(app,stream) from wvp_stream_push") List getAllAppAndStream(); @Select("select count(1) from wvp_stream_push ") int getAllCount(); @Select(value = {" "}) int getAllPushing(Boolean usePushingAsStatus); @MapKey("uniqueKey") @Select("SELECT CONCAT(wsp.app, wsp.stream) as unique_key, wsp.*, wdc.* , " + " wdc.id as gb_id " + " from wvp_stream_push wsp " + " LEFT join wvp_device_channel wdc on wdc.data_type = 2 and wsp.id = wdc.data_device_id") Map getAllAppAndStreamMap(); @MapKey("gbDeviceId") @Select("SELECT wdc.gb_device_id, wsp.id as data_device_id, wsp.*, wsp.* , wdc.id as gb_id " + " from wvp_stream_push wsp " + " LEFT join wvp_device_channel wdc on wdc.data_type = 2 and wsp.id = wdc.data_device_id") Map getAllGBId(); @Select("SELECT st.*, st.id as data_device_id, wdc.*, wdc.id as gb_id FROM wvp_stream_push st LEFT join wvp_device_channel wdc on wdc.data_type = 2 and st.id = wdc.data_device_id WHERE st.id=#{id}") StreamPush queryOne(@Param("id") int id); @Select("") List selectInSet(Set ids); @Delete("") void batchDel(List streamPushList); @Update({""}) int batchUpdate(List streamPushItemForUpdate); @Delete(" DELETE FROM wvp_stream_push" + " WHERE server_id = #{serverId}" + " AND NOT EXISTS (" + " SELECT 1 " + " FROM wvp_device_channel wdc " + " WHERE wdc.data_type = 2 " + " AND wvp_stream_push.id = wdc.data_device_id" + " );") void deleteWithoutGBId(@Param("serverId") String serverId); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/streamPush/enent/StreamPushUploadFileHandler.java ================================================ package com.genersoft.iot.vmp.streamPush.enent; import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.event.AnalysisEventListener; import com.genersoft.iot.vmp.streamPush.bean.StreamPush; import com.genersoft.iot.vmp.streamPush.bean.StreamPushExcelDto; import com.genersoft.iot.vmp.streamPush.service.IStreamPushService; import com.genersoft.iot.vmp.utils.DateUtil; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; import org.springframework.util.ObjectUtils; import java.util.*; public class StreamPushUploadFileHandler extends AnalysisEventListener { /** * 错误数据的回调,用于将错误数据发送给页面 */ private final ErrorDataHandler errorDataHandler; /** * 推流的业务类用于存储数据 */ private final IStreamPushService pushService; /** * 默认流媒体节点ID */ private final String defaultMediaServerId; /** * 用于存储更具APP+Stream过滤后的数据,可以直接存入stream_push表与gb_stream表 */ private final Map streamPushItemForSave = new HashMap<>(); /** * 用于存储APP+Stream->国标ID 的数据结构, 数据一一对应,全局判断APP+Stream->国标ID是否存在不对应 */ private final BiMap gBMap = HashBiMap.create(); /** * 用于存储APP+Stream-> 在数据库中的数据 */ private final BiMap pushMapInDb = HashBiMap.create(); /** * 记录错误的APP+Stream */ private final List errorStreamList = new ArrayList<>(); /** * 记录错误的国标ID */ private final List errorInfoList = new ArrayList<>(); /** * 读取数量计数器 */ private int loadedSize = 0; public StreamPushUploadFileHandler(IStreamPushService pushService, String defaultMediaServerId, ErrorDataHandler errorDataHandler) { this.pushService = pushService; this.defaultMediaServerId = defaultMediaServerId; this.errorDataHandler = errorDataHandler; // 获取数据库已有的数据,已经存在的则忽略 List allAppAndStreams = pushService.getAllAppAndStream(); if (!allAppAndStreams.isEmpty()) { for (String allAppAndStream : allAppAndStreams) { pushMapInDb.put(allAppAndStream, allAppAndStream); } } } public interface ErrorDataHandler{ void handle(List streams, List gbId); } @Override public void invoke(StreamPushExcelDto streamPushExcelDto, AnalysisContext analysisContext) { if (ObjectUtils.isEmpty(streamPushExcelDto.getApp()) || ObjectUtils.isEmpty(streamPushExcelDto.getStream()) || ObjectUtils.isEmpty(streamPushExcelDto.getGbDeviceId())) { return; } Integer rowIndex = analysisContext.readRowHolder().getRowIndex(); if (gBMap.get(streamPushExcelDto.getApp() + streamPushExcelDto.getStream()) == null) { try { gBMap.put(streamPushExcelDto.getApp() + streamPushExcelDto.getStream(), streamPushExcelDto.getGbDeviceId()); }catch (IllegalArgumentException e) { errorInfoList.add("行:" + rowIndex + ", " + streamPushExcelDto.getGbDeviceId() + " 国标ID重复使用"); return; } }else { if (!gBMap.get(streamPushExcelDto.getApp() + streamPushExcelDto.getStream()).equals(streamPushExcelDto.getGbDeviceId())) { errorInfoList.add("行:" + rowIndex + ", " + streamPushExcelDto.getGbDeviceId() + " 同样的应用名和流ID使用了不同的国标ID"); return; } } StreamPush streamPush = new StreamPush(); streamPush.setApp(streamPushExcelDto.getApp()); streamPush.setStream(streamPushExcelDto.getStream()); streamPush.setGbDeviceId(streamPushExcelDto.getGbDeviceId()); streamPush.setGbStatus(streamPushExcelDto.isStatus()?"ON":"OFF"); streamPush.setCreateTime(DateUtil.getNow()); streamPush.setMediaServerId(defaultMediaServerId); streamPush.setGbName(streamPushExcelDto.getName()); streamPush.setGbLongitude(streamPushExcelDto.getLongitude()); streamPush.setGbLatitude(streamPushExcelDto.getLatitude()); streamPush.setUpdateTime(DateUtil.getNow()); streamPushItemForSave.put(streamPush.getApp() + streamPush.getStream(), streamPush); loadedSize ++; if (loadedSize > 1000) { saveData(); streamPushItemForSave.clear(); loadedSize = 0; } } @Override public void doAfterAllAnalysed(AnalysisContext analysisContext) { // 这里也要保存数据,确保最后遗留的数据也存储到数据库 saveData(); streamPushItemForSave.clear(); gBMap.clear(); errorDataHandler.handle(errorStreamList, errorInfoList); } private void saveData(){ if (!streamPushItemForSave.isEmpty()) { // 向数据库查询是否存在重复的app pushService.batchAdd(new ArrayList<>(streamPushItemForSave.values())); } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/streamPush/service/IStreamPushPlayService.java ================================================ package com.genersoft.iot.vmp.streamPush.service; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.service.bean.ErrorCallback; public interface IStreamPushPlayService { void start(Integer id, ErrorCallback callback, String platformDeviceId, String platformName ); void stop(String app, String stream); void stop(Integer id); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/streamPush/service/IStreamPushService.java ================================================ package com.genersoft.iot.vmp.streamPush.service; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.service.bean.StreamPushItemFromRedis; import com.genersoft.iot.vmp.streamPush.bean.StreamPush; import com.genersoft.iot.vmp.vmanager.bean.ResourceBaseInfo; import com.github.pagehelper.PageInfo; import java.util.List; import java.util.Map; import java.util.Set; /** * @author lin */ public interface IStreamPushService { /** * 获取 */ PageInfo getPushList(Integer page, Integer count, String query, Boolean pushing, String mediaServerId); List getPushList(String mediaSererId); StreamPush getPush(String app, String streamId); boolean stop(StreamPush streamPush); /** * 停止一路推流 * @param app 应用名 * @param stream 流ID */ boolean stopByAppAndStream(String app, String stream); /** * 新的节点加入 */ void zlmServerOnline(MediaServer mediaServer); /** * 节点离线 */ void zlmServerOffline(MediaServer mediaServer); /** * 批量添加 */ void batchAdd(List streamPushExcelDtoList); /** * 全部离线 */ void allOfflineForRedisMsg(); /** * 推流离线 */ void offlineforRedisMsg(List offlineStreams); /** * 推流上线 */ void onlineForRedisMsg(List onlineStreams); /** * 增加推流 */ boolean add(StreamPush stream); boolean update(StreamPush stream); /** * 获取全部的app+Streanm 用于判断推流列表是新增还是修改 * @return */ List getAllAppAndStream(); /** * 获取统计信息 * @return */ ResourceBaseInfo getOverview(); Map getAllAppAndStreamMap(); Map getAllGBId(); void deleteByAppAndStream(String app, String stream); void updatePushStatus(StreamPush streamPush); void batchUpdateForRedisMsg(List streamPushItemForUpdate); int delete(int id); void batchRemove(Set ids); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/streamPush/service/impl/SourcePlayServiceForStreamPushImpl.java ================================================ package com.genersoft.iot.vmp.streamPush.service.impl; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.common.enums.ChannelDataType; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import com.genersoft.iot.vmp.gb28181.bean.Platform; import com.genersoft.iot.vmp.gb28181.bean.PlayException; import com.genersoft.iot.vmp.gb28181.service.ISourcePlayService; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.streamPush.service.IStreamPushPlayService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import javax.sip.message.Response; @Slf4j @Service(ChannelDataType.PLAY_SERVICE + ChannelDataType.STREAM_PUSH) public class SourcePlayServiceForStreamPushImpl implements ISourcePlayService { @Autowired private IStreamPushPlayService playService; @Override public void play(CommonGBChannel channel, Platform platform, Boolean record, ErrorCallback callback) { String serverGBId = null; String platformName = null; if (platform != null) { // 推流 serverGBId = platform.getServerGBId(); platformName = platform.getName(); } // 推流 try { playService.start(channel.getDataDeviceId(), callback, serverGBId, platformName); }catch (PlayException e) { callback.run(e.getCode(), e.getMsg(), null); }catch (Exception e) { log.error("[点播推流通道失败] 通道: {}({})", channel.getGbName(), channel.getGbDeviceId(), e); callback.run(Response.BUSY_HERE, "busy here", null); } } @Override public void stopPlay(CommonGBChannel channel) { // 推流 try { playService.stop(channel.getDataDeviceId()); }catch (Exception e) { log.error("[停止点播失败] {}({})", channel.getGbName(), channel.getGbDeviceId(), e); } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/streamPush/service/impl/StreamPushPlayServiceImpl.java ================================================ package com.genersoft.iot.vmp.streamPush.service.impl; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.conf.DynamicTask; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.media.bean.MediaInfo; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.service.IMediaServerService; import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.service.bean.MessageForPushChannel; import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcPlayService; import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcService; import com.genersoft.iot.vmp.service.redisMsg.RedisPushStreamResponseListener; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.streamPush.bean.StreamPush; import com.genersoft.iot.vmp.streamPush.dao.StreamPushMapper; import com.genersoft.iot.vmp.streamPush.service.IStreamPushPlayService; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.Assert; import java.util.UUID; @Service @Slf4j public class StreamPushPlayServiceImpl implements IStreamPushPlayService { @Autowired private StreamPushMapper streamPushMapper; @Autowired private IMediaServerService mediaServerService; @Autowired private IRedisCatchStorage redisCatchStorage; @Autowired private UserSetting userSetting; @Autowired private DynamicTask dynamicTask; @Autowired private IRedisRpcService redisRpcService; @Autowired private IRedisRpcPlayService redisRpcPlayService; @Autowired private RedisPushStreamResponseListener redisPushStreamResponseListener; @Override public void start(Integer id, ErrorCallback callback, String platformDeviceId, String platformName ) { StreamPush streamPush = streamPushMapper.queryOne(id); Assert.notNull(streamPush, "推流信息未找到"); if (streamPush.isPushing() && !userSetting.getServerId().equals(streamPush.getServerId())) { redisRpcPlayService.playPush(streamPush.getServerId(), id, callback); return; } MediaServer mediaServer = mediaServerService.getOne(streamPush.getMediaServerId()); if (mediaServer != null) { MediaInfo mediaInfo = mediaServerService.getMediaInfo(mediaServer, streamPush.getApp(), streamPush.getStream()); if (mediaInfo != null) { String callId = null; StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(streamPush.getApp(), streamPush.getStream()); if (streamAuthorityInfo != null) { callId = streamAuthorityInfo.getCallId(); } callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), mediaServerService.getStreamInfoByAppAndStream(mediaServer, streamPush.getApp(), streamPush.getStream(), mediaInfo, callId)); if (!streamPush.isPushing()) { streamPush.setPushing(true); streamPushMapper.update(streamPush); } return; } } Assert.isTrue(streamPush.isStartOfflinePush(), "通道未推流"); // 发送redis消息以使设备上线,流上线后被 log.info("[ app={}, stream={} ]通道未推流,发送redis信息控制设备开始推流", streamPush.getApp(), streamPush.getStream()); MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(1, streamPush.getApp(), streamPush.getStream(), streamPush.getGbDeviceId(), platformDeviceId, platformName, userSetting.getServerId(), null); redisCatchStorage.sendStreamPushRequestedMsg(messageForPushChannel); // 设置超时 String timeOutTaskKey = UUID.randomUUID().toString(); dynamicTask.startDelay(timeOutTaskKey, () -> { redisRpcService.unPushStreamOnlineEvent(streamPush.getApp(), streamPush.getStream()); log.info("[ app={}, stream={} ] 等待设备开始推流超时", streamPush.getApp(), streamPush.getStream()); callback.run(ErrorCode.ERROR100.getCode(), "timeout", null); }, userSetting.getPlatformPlayTimeout()); // long key = redisRpcService.onStreamOnlineEvent(streamPush.getApp(), streamPush.getStream(), (streamInfo) -> { dynamicTask.stop(timeOutTaskKey); if (streamInfo == null) { log.warn("等待推流得到结果未空: {}/{}", streamPush.getApp(), streamPush.getStream()); callback.run(ErrorCode.ERROR100.getCode(), "fail", null); }else { callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo); } }); // 添加回复的拒绝或者错误的通知 // redis消息例如: PUBLISH VM_MSG_STREAM_PUSH_RESPONSE '{"code":1,"msg":"失败","app":"1","stream":"2"}' redisPushStreamResponseListener.addEvent(streamPush.getApp(), streamPush.getStream(), response -> { if (response.getCode() != 0) { dynamicTask.stop(timeOutTaskKey); redisRpcService.unPushStreamOnlineEvent(streamPush.getApp(), streamPush.getStream()); redisRpcService.removeCallback(key); callback.run(response.getCode(), response.getMsg(), null); } }); } @Override public void stop(String app, String stream) { StreamPush streamPush = streamPushMapper.selectByAppAndStream(app, stream); if (streamPush == null || !streamPush.isPushing()) { return; } String mediaServerId = streamPush.getMediaServerId(); MediaServer mediaServer = mediaServerService.getOne(mediaServerId); Assert.notNull(mediaServer, "未找到使用的节点"); mediaServerService.closeStreams(mediaServer, app, stream); } @Override public void stop(Integer id) { StreamPush streamPush = streamPushMapper.queryOne(id); if (streamPush == null || !streamPush.isPushing()) { return; } String mediaServerId = streamPush.getMediaServerId(); MediaServer mediaServer = mediaServerService.getOne(mediaServerId); Assert.notNull(mediaServer, "未找到使用的节点"); mediaServerService.closeStreams(mediaServer, streamPush.getApp(), streamPush.getStream()); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/streamPush/service/impl/StreamPushServiceImpl.java ================================================ package com.genersoft.iot.vmp.streamPush.service.impl; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.common.enums.MediaApp; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import com.genersoft.iot.vmp.gb28181.service.IGbChannelService; import com.genersoft.iot.vmp.media.bean.MediaInfo; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.event.media.MediaArrivalEvent; import com.genersoft.iot.vmp.media.event.media.MediaDepartureEvent; import com.genersoft.iot.vmp.media.event.mediaServer.MediaServerOfflineEvent; import com.genersoft.iot.vmp.media.event.mediaServer.MediaServerOnlineEvent; import com.genersoft.iot.vmp.media.service.IMediaServerService; import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo; import com.genersoft.iot.vmp.media.zlm.dto.hook.OriginType; import com.genersoft.iot.vmp.service.ISendRtpServerService; import com.genersoft.iot.vmp.service.bean.StreamPushItemFromRedis; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.streamPush.bean.StreamPush; import com.genersoft.iot.vmp.streamPush.dao.StreamPushMapper; import com.genersoft.iot.vmp.streamPush.service.IStreamPushService; import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.ResourceBaseInfo; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.event.EventListener; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import java.util.*; @Service @Slf4j public class StreamPushServiceImpl implements IStreamPushService { @Autowired private StreamPushMapper streamPushMapper; @Autowired private IRedisCatchStorage redisCatchStorage; @Autowired private UserSetting userSetting; @Autowired private IMediaServerService mediaServerService; @Autowired private ISendRtpServerService sendRtpServerService; @Autowired private IGbChannelService gbChannelService; /** * 流到来的处理 */ @Async("taskExecutor") @EventListener @Transactional public void onApplicationEvent(MediaArrivalEvent event) { MediaInfo mediaInfo = event.getMediaInfo(); if (mediaInfo == null) { return; } if (mediaInfo.getOriginType() != OriginType.RTMP_PUSH.ordinal() && mediaInfo.getOriginType() != OriginType.RTSP_PUSH.ordinal() && mediaInfo.getOriginType() != OriginType.RTC_PUSH.ordinal()) { return; } StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(event.getApp(), event.getStream()); if (streamAuthorityInfo == null) { streamAuthorityInfo = StreamAuthorityInfo.getInstanceByHook(event); } else { streamAuthorityInfo.setOriginType(mediaInfo.getOriginType()); } redisCatchStorage.updateStreamAuthorityInfo(event.getApp(), event.getStream(), streamAuthorityInfo); StreamPush streamPushInDb = getPush(event.getApp(), event.getStream()); if (streamPushInDb == null) { StreamPush streamPush = StreamPush.getInstance(event, userSetting.getServerId()); streamPush.setPushing(true); streamPush.setServerId(userSetting.getServerId()); streamPush.setUpdateTime(DateUtil.getNow()); streamPush.setPushTime(DateUtil.getNow()); add(streamPush); }else { streamPushInDb.setPushTime(DateUtil.getNow()); streamPushInDb.setPushing(true); streamPushInDb.setServerId(userSetting.getServerId()); streamPushInDb.setMediaServerId(mediaInfo.getMediaServer().getId()); updatePushStatus(streamPushInDb); } // 冗余数据,自己系统中自用 if (!MediaApp.GB28181_BROADCAST.equals(event.getApp()) && !MediaApp.GB28181_TALK.equals(event.getApp())) { redisCatchStorage.addPushListItem(event.getApp(), event.getStream(), event.getMediaInfo()); } // 发送流变化redis消息 JSONObject jsonObject = new JSONObject(); jsonObject.put("serverId", userSetting.getServerId()); jsonObject.put("app", event.getApp()); jsonObject.put("stream", event.getStream()); jsonObject.put("register", true); jsonObject.put("mediaServerId", event.getMediaServer().getId()); redisCatchStorage.sendStreamChangeMsg(OriginType.values()[event.getMediaInfo().getOriginType()].getType(), jsonObject); } /** * 流离开的处理 */ @Async("taskExecutor") @EventListener @Transactional public void onApplicationEvent(MediaDepartureEvent event) { // 兼容流注销时类型从redis记录获取 MediaInfo mediaInfo = redisCatchStorage.getPushListItem(event.getApp(), event.getStream()); if (mediaInfo != null) { log.info("[推流信息] 查询到redis存在推流缓存, 开始清理,{}/{}", event.getApp(), event.getStream()); String type = OriginType.values()[mediaInfo.getOriginType()].getType(); // 冗余数据,自己系统中自用 redisCatchStorage.removePushListItem(event.getApp(), event.getStream(), event.getMediaServer().getId()); // 发送流变化redis消息 JSONObject jsonObject = new JSONObject(); jsonObject.put("serverId", userSetting.getServerId()); jsonObject.put("app", event.getApp()); jsonObject.put("stream", event.getStream()); jsonObject.put("register", false); jsonObject.put("mediaServerId", event.getMediaServer().getId()); redisCatchStorage.sendStreamChangeMsg(type, jsonObject); } StreamPush streamPush = getPush(event.getApp(), event.getStream()); if (streamPush == null) { return; } if (streamPush.getGbDeviceId() != null) { streamPush.setPushing(false); updatePushStatus(streamPush); }else { deleteByAppAndStream(event.getApp(), event.getStream()); } } /** * 流媒体节点上线 */ @Async("taskExecutor") @EventListener @Transactional public void onApplicationEvent(MediaServerOnlineEvent event) { zlmServerOnline(event.getMediaServer()); } /** * 流媒体节点离线 */ @Async("taskExecutor") @EventListener @Transactional public void onApplicationEvent(MediaServerOfflineEvent event) { zlmServerOffline(event.getMediaServer()); } @Override public PageInfo getPushList(Integer page, Integer count, String query, Boolean pushing, String mediaServerId) { PageHelper.startPage(page, count); if (query != null) { query = query.replaceAll("/", "//") .replaceAll("%", "/%") .replaceAll("_", "/_"); } List all = streamPushMapper.selectAll(query, pushing, mediaServerId); return new PageInfo<>(all); } @Override public List getPushList(String mediaServerId) { return streamPushMapper.selectAllByMediaServerIdWithOutGbID(mediaServerId); } @Override public StreamPush getPush(String app, String stream) { return streamPushMapper.selectByAppAndStream(app, stream); } @Override @Transactional public boolean add(StreamPush stream) { log.info("[添加推流] app: {}, stream: {}, 国标编号: {}", stream.getApp(), stream.getStream(), stream.getGbDeviceId()); StreamPush streamPushInDb = streamPushMapper.selectByAppAndStream(stream.getApp(), stream.getStream()); if (streamPushInDb != null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "应用名+流ID已存在"); } stream.setUpdateTime(DateUtil.getNow()); stream.setCreateTime(DateUtil.getNow()); int addResult = streamPushMapper.add(stream); if (addResult <= 0) { return false; } if (ObjectUtils.isEmpty(stream.getGbDeviceId())) { return true; } CommonGBChannel channel = gbChannelService.queryByDeviceId(stream.getGbDeviceId()); if (channel != null) { log.info("[添加推流]失败,国标编号已存在: {} app: {}, stream: {}, ", stream.getGbDeviceId(), stream.getApp(), stream.getStream()); } int addChannelResult = gbChannelService.add(stream.buildCommonGBChannel()); return addChannelResult > 0; } @Override @Transactional public void deleteByAppAndStream(String app, String stream) { log.info("[删除推流] app: {}, stream: {}, ", app, stream); StreamPush streamPush = streamPushMapper.selectByAppAndStream(app, stream); if (streamPush == null) { log.info("[删除推流]失败, 不存在 app: {}, stream: {}, ", app, stream); return; } if (streamPush.isPushing()) { stop(streamPush); } if (streamPush.getGbId() > 0) { gbChannelService.delete(streamPush.getGbId()); } streamPushMapper.del(streamPush.getId()); } @Override @Transactional public boolean update(StreamPush streamPush) { Assert.notNull(streamPush, "推流信息不可为NULL"); Assert.isTrue(streamPush.getId() > 0, "推流信息ID必须存在"); log.info("[更新推流]:id: {}, app: {}, stream: {}, ", streamPush.getId(), streamPush.getApp(), streamPush.getStream()); StreamPush streamPushInDb = streamPushMapper.queryOne(streamPush.getId()); if (!streamPushInDb.getApp().equals(streamPush.getApp()) || !streamPushInDb.getStream().equals(streamPush.getStream())) { // app或者stream变化 StreamPush streamPushInDbForAppAndStream = streamPushMapper.selectByAppAndStream(streamPush.getApp(), streamPush.getStream()); if (streamPushInDbForAppAndStream != null && !streamPushInDbForAppAndStream.getId().equals(streamPush.getId())) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "应用名+流ID已存在"); } } streamPush.setUpdateTime(DateUtil.getNow()); streamPushMapper.update(streamPush); if (streamPush.getGbId() > 0) { gbChannelService.update(streamPush.buildCommonGBChannel()); } return true; } @Override @Transactional public boolean stop(StreamPush streamPush) { log.info("[主动停止推流] id: {}, app: {}, stream: {}, ", streamPush.getId(), streamPush.getApp(), streamPush.getStream()); MediaServer mediaServer = null; if (streamPush.getMediaServerId() == null) { log.info("[主动停止推流]未找到使用MediaServer,开始自动检索 id: {}, app: {}, stream: {}, ", streamPush.getId(), streamPush.getApp(), streamPush.getStream()); mediaServer = mediaServerService.getMediaServerByAppAndStream(streamPush.getApp(), streamPush.getStream()); if (mediaServer != null) { log.info("[主动停止推流] 检索到MediaServer为{}, id: {}, app: {}, stream: {}, ", mediaServer.getId(), streamPush.getId(), streamPush.getApp(), streamPush.getStream()); }else { log.info("[主动停止推流]未找到使用MediaServer id: {}, app: {}, stream: {}, ", streamPush.getId(), streamPush.getApp(), streamPush.getStream()); } }else { mediaServer = mediaServerService.getOne(streamPush.getMediaServerId()); if (mediaServer == null) { log.info("[主动停止推流]未找到使用的MediaServer: {},开始自动检索 id: {}, app: {}, stream: {}, ",streamPush.getMediaServerId(), streamPush.getId(), streamPush.getApp(), streamPush.getStream()); mediaServer = mediaServerService.getMediaServerByAppAndStream(streamPush.getApp(), streamPush.getStream()); if (mediaServer != null) { log.info("[主动停止推流] 检索到MediaServer为{}, id: {}, app: {}, stream: {}, ", mediaServer.getId(), streamPush.getId(), streamPush.getApp(), streamPush.getStream()); }else { log.info("[主动停止推流]未找到使用MediaServer id: {}, app: {}, stream: {}, ", streamPush.getId(), streamPush.getApp(), streamPush.getStream()); } } } if (mediaServer != null) { mediaServerService.closeStreams(mediaServer, streamPush.getApp(), streamPush.getStream()); mediaServerService.stopSendRtp(mediaServer, streamPush.getApp(), streamPush.getStream(), null); } streamPush.setPushing(false); if (userSetting.getUsePushingAsStatus()) { CommonGBChannel commonGBChannel = streamPush.buildCommonGBChannel(); if (commonGBChannel != null) { gbChannelService.offline(commonGBChannel); } } sendRtpServerService.deleteByStream(streamPush.getStream()); streamPush.setUpdateTime(DateUtil.getNow()); streamPushMapper.update(streamPush); return true; } @Override @Transactional public boolean stopByAppAndStream(String app, String stream) { log.info("[主动停止推流] : app: {}, stream: {}, ", app, stream); StreamPush streamPushItem = streamPushMapper.selectByAppAndStream(app, stream); if (streamPushItem != null) { stop(streamPushItem); } return true; } @Override @Transactional public void zlmServerOnline(MediaServer mediaServer) { // 同步zlm推流信息 if (mediaServer == null) { return; } // 数据库记录 List pushList = getPushList(mediaServer.getId()); Map pushItemMap = new HashMap<>(); // redis记录 List mediaInfoList = redisCatchStorage.getStreams(mediaServer.getId(), "PUSH"); Map streamInfoPushItemMap = new HashMap<>(); if (!pushList.isEmpty()) { for (StreamPush streamPushItem : pushList) { if (ObjectUtils.isEmpty(streamPushItem.getGbId())) { pushItemMap.put(streamPushItem.getApp() + streamPushItem.getStream(), streamPushItem); } } } if (!mediaInfoList.isEmpty()) { for (MediaInfo mediaInfo : mediaInfoList) { if (mediaInfo == null) { continue; } streamInfoPushItemMap.put(mediaInfo.getApp() + mediaInfo.getStream(), mediaInfo); } } // 获取所有推流鉴权信息,清理过期的 List allStreamAuthorityInfo = redisCatchStorage.getAllStreamAuthorityInfo(); Map streamAuthorityInfoInfoMap = new HashMap<>(); for (StreamAuthorityInfo streamAuthorityInfo : allStreamAuthorityInfo) { streamAuthorityInfoInfoMap.put(streamAuthorityInfo.getApp() + streamAuthorityInfo.getStream(), streamAuthorityInfo); } List mediaList = mediaServerService.getMediaList(mediaServer, null, null, null); if (mediaList == null) { return; } List streamPushItems = handleJSON(mediaList); if (streamPushItems != null) { for (StreamPush streamPushItem : streamPushItems) { pushItemMap.remove(streamPushItem.getApp() + streamPushItem.getStream()); streamInfoPushItemMap.remove(streamPushItem.getApp() + streamPushItem.getStream()); streamAuthorityInfoInfoMap.remove(streamPushItem.getApp() + streamPushItem.getStream()); } } List changedStreamPushList = new ArrayList<>(pushItemMap.values()); if (!changedStreamPushList.isEmpty()) { for (StreamPush streamPush : changedStreamPushList) { stop(streamPush); } } Collection mediaInfos = streamInfoPushItemMap.values(); if (!mediaInfos.isEmpty()) { String type = "PUSH"; for (MediaInfo mediaInfo : mediaInfos) { JSONObject jsonObject = new JSONObject(); jsonObject.put("serverId", userSetting.getServerId()); jsonObject.put("app", mediaInfo.getApp()); jsonObject.put("stream", mediaInfo.getStream()); jsonObject.put("register", false); jsonObject.put("mediaServerId", mediaServer.getId()); redisCatchStorage.sendStreamChangeMsg(type, jsonObject); // 移除redis内流的信息 redisCatchStorage.removeStream(mediaServer.getId(), "PUSH", mediaInfo.getApp(), mediaInfo.getStream()); // 冗余数据,自己系统中自用 redisCatchStorage.removePushListItem(mediaInfo.getApp(), mediaInfo.getStream(), mediaServer.getId()); } } if (!pushItemMap.isEmpty()) { for (StreamPush streamPush : pushItemMap.values()) { // 如果没有国标编号,从数据库中删除 delete(streamPush.getId()); } } Collection streamAuthorityInfos = streamAuthorityInfoInfoMap.values(); if (!streamAuthorityInfos.isEmpty()) { for (StreamAuthorityInfo streamAuthorityInfo : streamAuthorityInfos) { // 移除redis内流的信息 redisCatchStorage.removeStreamAuthorityInfo(streamAuthorityInfo.getApp(), streamAuthorityInfo.getStream()); } } } @Override @Transactional public void zlmServerOffline(MediaServer mediaServer) { List streamPushItems = streamPushMapper.selectAllByMediaServerId(mediaServer.getId()); if (!streamPushItems.isEmpty()) { for (StreamPush streamPushItem : streamPushItems) { stop(streamPushItem); } } // 移除没有GBId的推流 streamPushMapper.deleteWithoutGBId(mediaServer.getId()); // 发送流停止消息 String type = "PUSH"; // 发送redis消息 List mediaInfoList = redisCatchStorage.getStreams(mediaServer.getId(), type); if (!mediaInfoList.isEmpty()) { for (MediaInfo mediaInfo : mediaInfoList) { // 移除redis内流的信息 redisCatchStorage.removeStream(mediaServer.getId(), type, mediaInfo.getApp(), mediaInfo.getStream()); JSONObject jsonObject = new JSONObject(); jsonObject.put("serverId", userSetting.getServerId()); jsonObject.put("app", mediaInfo.getApp()); jsonObject.put("stream", mediaInfo.getStream()); jsonObject.put("register", false); jsonObject.put("mediaServerId", mediaServer.getId()); redisCatchStorage.sendStreamChangeMsg(type, jsonObject); // 冗余数据,自己系统中自用 redisCatchStorage.removePushListItem(mediaInfo.getApp(), mediaInfo.getStream(), mediaServer.getId()); } } } @Override @Transactional public void batchAdd(List streamPushItems) { streamPushMapper.addAll(streamPushItems); List commonGBChannels = new ArrayList<>(); for (StreamPush streamPush : streamPushItems) { if (!ObjectUtils.isEmpty(streamPush.getGbDeviceId())) { commonGBChannels.add(streamPush.buildCommonGBChannel()); } } gbChannelService.batchAdd(commonGBChannels); } @Override public void allOfflineForRedisMsg() { String serverId = redisCatchStorage.chooseOneServer(null); boolean permission = userSetting.getServerId().equals(serverId); List streamPushList = streamPushMapper.selectAll(null, null, null); if (streamPushList.isEmpty()) { return; } List commonGBChannelList = new ArrayList<>(); for (StreamPush streamPush : streamPushList) { CommonGBChannel commonGBChannel = streamPush.buildCommonGBChannel(); if (commonGBChannel != null) { commonGBChannelList.add(streamPush.buildCommonGBChannel()); } } gbChannelService.offline(commonGBChannelList, permission); } @Override public void offlineforRedisMsg(List offlineStreams) { String serverId = redisCatchStorage.chooseOneServer(null); boolean permission = userSetting.getServerId().equals(serverId); // 更新部分设备离线 List streamPushList = streamPushMapper.getListInList(offlineStreams); if (streamPushList.isEmpty()) { log.info("[推流设备] 设备离线操作未发现可操作数据。"); return; } List commonGBChannelList = gbChannelService.queryListByStreamPushList(streamPushList); gbChannelService.offline(commonGBChannelList, permission); } @Override public void onlineForRedisMsg(List onlineStreams) { if (onlineStreams.isEmpty()) { log.info("[设备上线] 推流设备列表为空"); return; } String serverId = redisCatchStorage.chooseOneServer(null); boolean permission = userSetting.getServerId().equals(serverId); // 更新部分设备上线streamPushService List streamPushList = streamPushMapper.getListInList(onlineStreams); if (streamPushList.isEmpty()) { for (StreamPushItemFromRedis onlineStream : onlineStreams) { log.info("[设备上线] 未查询到这些通道: {}/{}", onlineStream.getApp(), onlineStream.getStream()); } return; } List commonGBChannelList = gbChannelService.queryListByStreamPushList(streamPushList); gbChannelService.online(commonGBChannelList, permission); } @Override public List getAllAppAndStream() { return streamPushMapper.getAllAppAndStream(); } @Override public ResourceBaseInfo getOverview() { int total = streamPushMapper.getAllCount(); int online = streamPushMapper.getAllPushing(userSetting.getUsePushingAsStatus()); return new ResourceBaseInfo(total, online); } @Override public Map getAllAppAndStreamMap() { return streamPushMapper.getAllAppAndStreamMap(); } @Override public Map getAllGBId() { return streamPushMapper.getAllGBId(); } @Override @Transactional public void updatePushStatus(StreamPush streamPush) { if (userSetting.getUsePushingAsStatus()) { streamPush.setGbStatus(streamPush.isPushing()?"ON":"OFF"); } streamPushMapper.updatePushStatus(streamPush); if (ObjectUtils.isEmpty(streamPush.getGbDeviceId())) { return; } if (userSetting.getUsePushingAsStatus()) { if ("ON".equalsIgnoreCase(streamPush.getGbStatus()) ) { gbChannelService.online(streamPush.buildCommonGBChannel()); }else { gbChannelService.offline(streamPush.buildCommonGBChannel()); } } } private List handleJSON(List streamInfoList) { if (streamInfoList == null || streamInfoList.isEmpty()) { return null; } Map result = new HashMap<>(); for (StreamInfo streamInfo : streamInfoList) { // 不保存国标推理以及拉流代理的流 if (streamInfo.getOriginType() == OriginType.RTSP_PUSH.ordinal() || streamInfo.getOriginType() == OriginType.RTMP_PUSH.ordinal() || streamInfo.getOriginType() == OriginType.RTC_PUSH.ordinal() ) { String key = streamInfo.getApp() + "_" + streamInfo.getStream(); StreamPush streamPushItem = result.get(key); if (streamPushItem == null) { streamPushItem = StreamPush.getInstance(streamInfo); result.put(key, streamPushItem); } } } return new ArrayList<>(result.values()); } @Override @Transactional public void batchUpdateForRedisMsg(List streamPushItemForUpdate) { String serverId = redisCatchStorage.chooseOneServer(null); boolean permission = userSetting.getServerId().equals(serverId); if (permission) { streamPushMapper.batchUpdate(streamPushItemForUpdate); } List commonGBChannels = new ArrayList<>(); for (StreamPush streamPush : streamPushItemForUpdate) { if (!ObjectUtils.isEmpty(streamPush.getGbDeviceId())) { commonGBChannels.add(streamPush.buildCommonGBChannel()); } } gbChannelService.batchUpdateForStreamPushRedisMsg(commonGBChannels, permission); } @Override @Transactional public int delete(int id) { StreamPush streamPush = streamPushMapper.queryOne(id); if (streamPush == null) { return 0; } if(streamPush.isPushing()) { MediaServer mediaServer = mediaServerService.getOne(streamPush.getMediaServerId()); mediaServerService.closeStreams(mediaServer, streamPush.getApp(), streamPush.getStream()); } if (streamPush.getGbDeviceId() != null) { gbChannelService.delete(streamPush.getGbId()); } return streamPushMapper.del(id); } @Override @Transactional public void batchRemove(Set ids) { List streamPushList = streamPushMapper.selectInSet(ids); if (streamPushList.isEmpty()) { return; } Set channelIds = new HashSet<>(); streamPushList.stream().forEach(streamPush -> { if (streamPush.getGbDeviceId() != null) { channelIds.add(streamPush.getGbId()); } }); streamPushMapper.batchDel(streamPushList); gbChannelService.delete(channelIds); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/utils/CivilCodeUtil.java ================================================ package com.genersoft.iot.vmp.utils; import com.genersoft.iot.vmp.common.CivilCodePo; import com.genersoft.iot.vmp.gb28181.bean.Region; import lombok.extern.slf4j.Slf4j; import org.springframework.util.ObjectUtils; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @Slf4j public enum CivilCodeUtil { INSTANCE; // 用与消息的缓存 private final Map civilCodeMap = new ConcurrentHashMap<>(); CivilCodeUtil() { } public void add(List civilCodePoList) { if (!civilCodePoList.isEmpty()) { for (CivilCodePo civilCodePo : civilCodePoList) { civilCodeMap.put(civilCodePo.getCode(), civilCodePo); } } } public void add(CivilCodePo civilCodePo) { civilCodeMap.put(civilCodePo.getCode(), civilCodePo); } public CivilCodePo get(String code) { return civilCodeMap.get(code); } public CivilCodePo getParentCode(String code) { if (code.length() > 8) { return null; } if (code.length() == 8) { String parentCode = code.substring(0, 6); return civilCodeMap.get(parentCode); }else { CivilCodePo civilCodePo = civilCodeMap.get(code); if (civilCodePo == null){ return null; } String parentCode = civilCodePo.getParentCode(); if (parentCode == null) { return null; } return civilCodeMap.get(parentCode); } } public CivilCodePo getCivilCodePo(String code) { if (code.length() > 8) { return null; }else { return civilCodeMap.get(code); } } public List getAllParentCode(String civilCode) { List civilCodePoList = new ArrayList<>(); CivilCodePo parentCode = getParentCode(civilCode); if (parentCode != null) { civilCodePoList.add(parentCode); List allParentCode = getAllParentCode(parentCode.getCode()); if (!allParentCode.isEmpty()) { civilCodePoList.addAll(allParentCode); }else { return civilCodePoList; } } return civilCodePoList; } public boolean isEmpty() { return civilCodeMap.isEmpty(); } public int size() { return civilCodeMap.size(); } public List getAllChild(String parent) { List result = new ArrayList<>(); for (String key : civilCodeMap.keySet()) { if (parent == null) { if (ObjectUtils.isEmpty(civilCodeMap.get(key).getParentCode().trim())) { result.add(Region.getInstance(key, civilCodeMap.get(key).getName(), civilCodeMap.get(key).getParentCode())); } }else if (civilCodeMap.get(key).getParentCode().equals(parent)) { result.add(Region.getInstance(key, civilCodeMap.get(key).getName(), civilCodeMap.get(key).getParentCode())); } } return result; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/utils/Coordtransform.java ================================================ package com.genersoft.iot.vmp.utils; /** * 坐标转换 * 一个提供了百度坐标(BD09)、国测局坐标(火星坐标,GCJ02)、和WGS84坐标系之间的转换的工具类 * 参考https://github.com/wandergis/coordtransform 写的Java版本 * @author Xinconan * @date 2016-03-18 * @url https://github.com/xinconan/coordtransform */ public class Coordtransform { private static double x_PI = 3.14159265358979324 * 3000.0 / 180.0; private static double PI = 3.1415926535897932384626; private static double a = 6378245.0; private static double ee = 0.00669342162296594323; /** * 百度坐标系 (BD-09) 与 火星坐标系 (GCJ-02)的转换 * 即 百度 转 谷歌、高德 * @param bd_lon * @param bd_lat * @return Double[lon,lat] */ public static Double[] BD09ToGCJ02(Double bd_lon,Double bd_lat){ double x = bd_lon - 0.0065; double y = bd_lat - 0.006; double z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * x_PI); double theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * x_PI); Double[] arr = new Double[2]; arr[0] = z * Math.cos(theta); arr[1] = z * Math.sin(theta); return arr; } /** * 火星坐标系 (GCJ-02) 与百度坐标系 (BD-09) 的转换 * 即谷歌、高德 转 百度 * @param gcj_lon * @param gcj_lat * @return Double[lon,lat] */ public static Double[] GCJ02ToBD09(Double gcj_lon,Double gcj_lat){ double z = Math.sqrt(gcj_lon * gcj_lon + gcj_lat * gcj_lat) + 0.00002 * Math.sin(gcj_lat * x_PI); double theta = Math.atan2(gcj_lat, gcj_lon) + 0.000003 * Math.cos(gcj_lon * x_PI); Double[] arr = new Double[2]; arr[0] = z * Math.cos(theta) + 0.0065; arr[1] = z * Math.sin(theta) + 0.006; return arr; } /** * WGS84转GCJ02 * @param wgs_lon * @param wgs_lat * @return Double[lon,lat] */ public static Double[] WGS84ToGCJ02(Double wgs_lon,Double wgs_lat){ if(outOfChina(wgs_lon, wgs_lat)){ return new Double[]{wgs_lon,wgs_lat}; } double dlat = transformlat(wgs_lon - 105.0, wgs_lat - 35.0); double dlng = transformlng(wgs_lon - 105.0, wgs_lat - 35.0); double radlat = wgs_lat / 180.0 * PI; double magic = Math.sin(radlat); magic = 1 - ee * magic * magic; double sqrtmagic = Math.sqrt(magic); dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI); dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * PI); Double[] arr = new Double[2]; arr[0] = wgs_lon + dlng; arr[1] = wgs_lat + dlat; return arr; } /** * GCJ02转WGS84 * @param gcj_lon * @param gcj_lat * @return Double[lon,lat] */ public static Double[] GCJ02ToWGS84(Double gcj_lon,Double gcj_lat){ if(outOfChina(gcj_lon, gcj_lat)){ return new Double[]{gcj_lon,gcj_lat}; } double dlat = transformlat(gcj_lon - 105.0, gcj_lat - 35.0); double dlng = transformlng(gcj_lon - 105.0, gcj_lat - 35.0); double radlat = gcj_lat / 180.0 * PI; double magic = Math.sin(radlat); magic = 1 - ee * magic * magic; double sqrtmagic = Math.sqrt(magic); dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI); dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * PI); double mglat = gcj_lat + dlat; double mglng = gcj_lon + dlng; return new Double[]{gcj_lon * 2 - mglng, gcj_lat * 2 - mglat}; } private static Double transformlat(double lng, double lat) { double ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng)); ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0; ret += (20.0 * Math.sin(lat * PI) + 40.0 * Math.sin(lat / 3.0 * PI)) * 2.0 / 3.0; ret += (160.0 * Math.sin(lat / 12.0 * PI) + 320 * Math.sin(lat * PI / 30.0)) * 2.0 / 3.0; return ret; } private static Double transformlng(double lng,double lat) { double ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng)); ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0; ret += (20.0 * Math.sin(lng * PI) + 40.0 * Math.sin(lng / 3.0 * PI)) * 2.0 / 3.0; ret += (150.0 * Math.sin(lng / 12.0 * PI) + 300.0 * Math.sin(lng / 30.0 * PI)) * 2.0 / 3.0; return ret; } /** * outOfChina * @描述: 判断是否在国内,不在国内则不做偏移 * @param lng * @param lat * @return {boolean} */ private static boolean outOfChina(Double lng,Double lat) { return (lng < 72.004 || lng > 137.8347) || ((lat < 0.8293 || lat > 55.8271) || false); }; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/utils/DateUtil.java ================================================ package com.genersoft.iot.vmp.utils; import jakarta.validation.constraints.NotNull; import org.apache.commons.lang3.ObjectUtils; 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.ChronoUnit; import java.time.temporal.TemporalAccessor; import java.util.Locale; /** * 全局时间工具类 * @author lin */ public class DateUtil { /** * 兼容不规范的iso8601时间格式 */ private static final String ISO8601_COMPATIBLE_PATTERN = "yyyy-M-d'T'H:m:s"; /** * 用以输出标准的iso8601时间格式 */ private static final String ISO8601_PATTERN = "yyyy-MM-dd'T'HH:mm:ss"; /** * iso8601时间格式带时区,例如:2024-02-21T11:10:36+08:00 */ private static final String ISO8601_ZONE_PATTERN = "yyyy-MM-dd'T'HH:mm:ssXXX"; /** * 兼容的时间格式 iso8601时间格式带毫秒 */ private static final String ISO8601_MILLISECOND_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS"; /** * wvp内部统一时间格式 */ public static final String PATTERN = "yyyy-MM-dd HH:mm:ss"; /** * wvp内部统一时间格式 */ public static final String URL_PATTERN = "yyyyMMddHHmmss"; public static final String PATTERN1078 = "yyMMddHHmmss"; public static final String PATTERN1078Date = "yyyyMMdd"; /** * 日期格式 */ public static final String date_PATTERN = "yyyy-MM-dd"; public static final String zoneStr = "Asia/Shanghai"; public static final DateTimeFormatter formatterCompatibleISO8601 = DateTimeFormatter.ofPattern(ISO8601_COMPATIBLE_PATTERN, Locale.getDefault()).withZone(ZoneId.of(zoneStr)); public static final DateTimeFormatter formatterISO8601 = DateTimeFormatter.ofPattern(ISO8601_PATTERN, Locale.getDefault()).withZone(ZoneId.of(zoneStr)); public static final DateTimeFormatter formatterZoneISO8601 = DateTimeFormatter.ofPattern(ISO8601_ZONE_PATTERN, Locale.getDefault()).withZone(ZoneId.of(zoneStr)); public static final DateTimeFormatter formatterMillisecondISO8601 = DateTimeFormatter.ofPattern(ISO8601_MILLISECOND_PATTERN, Locale.getDefault()).withZone(ZoneId.of(zoneStr)); public static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(PATTERN, Locale.getDefault()).withZone(ZoneId.of(zoneStr)); public static final DateTimeFormatter DateFormatter = DateTimeFormatter.ofPattern(date_PATTERN, Locale.getDefault()).withZone(ZoneId.of(zoneStr)); public static final DateTimeFormatter urlFormatter = DateTimeFormatter.ofPattern(URL_PATTERN, Locale.getDefault()).withZone(ZoneId.of(zoneStr)); public static final DateTimeFormatter formatter1078 = DateTimeFormatter.ofPattern(PATTERN1078, Locale.getDefault()).withZone(ZoneId.of(zoneStr)); public static final DateTimeFormatter formatter1078date = DateTimeFormatter.ofPattern(PATTERN1078Date, Locale.getDefault()).withZone(ZoneId.of(zoneStr)); public static String yyyy_MM_dd_HH_mm_ssToISO8601(@NotNull String formatTime) { return formatterISO8601.format(formatter.parse(formatTime)); } public static String yyyy_MM_dd_HH_mm_ssToUrl(@NotNull String formatTime) { return urlFormatter.format(formatter.parse(formatTime)); } public static String ISO8601Toyyyy_MM_dd_HH_mm_ss(String formatTime) { // 三种日期格式都尝试,为了兼容不同厂家的日期格式 if (verification(formatTime, formatterCompatibleISO8601)) { return formatter.format(formatterCompatibleISO8601.parse(formatTime)); } else if (verification(formatTime, formatterZoneISO8601)) { return formatter.format(formatterZoneISO8601.parse(formatTime)); } else if (verification(formatTime, formatterMillisecondISO8601)) { return formatter.format(formatterMillisecondISO8601.parse(formatTime)); } return formatter.format(formatterISO8601.parse(formatTime)); } public static String urlToyyyy_MM_dd_HH_mm_ss(String formatTime) { return formatter.format(urlFormatter.parse(formatTime)); } public static String yyyy_MM_dd_HH_mm_ssTo1078(String formatTime) { return formatter1078.format(formatter.parse(formatTime)); } public static String jt1078Toyyyy_MM_dd_HH_mm_ss(String formatTime) { return formatter.format(formatter1078.parse(formatTime)); } public static String jt1078dateToyyyy_MM_dd(String formatTime) { return DateFormatter.format(formatter1078date.parse(formatTime)); } /** * yyyy_MM_dd_HH_mm_ss 转时间戳 * @param formatTime * @return */ public static long yyyy_MM_dd_HH_mm_ssToTimestamp(String formatTime) { TemporalAccessor temporalAccessor = formatter.parse(formatTime); Instant instant = Instant.from(temporalAccessor); return instant.getEpochSecond(); } /** * 时间戳 转 yyyy_MM_dd_HH_mm_ss */ public static String timestampTo_yyyy_MM_dd_HH_mm_ss(long timestamp) { Instant instant = Instant.ofEpochSecond(timestamp); return formatter.format(LocalDateTime.ofInstant(instant, ZoneId.of(zoneStr))); } /** * 时间戳 转 yyyy_MM_dd_HH_mm_ss */ public static String timestampMsToUrlToyyyy_MM_dd_HH_mm_ss(long timestamp) { Instant instant = Instant.ofEpochMilli(timestamp); return urlFormatter.format(LocalDateTime.ofInstant(instant, ZoneId.of(zoneStr))); } /** * yyyy_MM_dd_HH_mm_ss 转时间戳(毫秒) * * @param formatTime * @return */ public static long yyyy_MM_dd_HH_mm_ssToTimestampMs(String formatTime) { TemporalAccessor temporalAccessor = formatter.parse(formatTime); Instant instant = Instant.from(temporalAccessor); return instant.toEpochMilli(); } /** * 时间戳(毫秒) 转 yyyy_MM_dd_HH_mm_ss */ public static String timestampMsTo_yyyy_MM_dd_HH_mm_ss(long timestamp) { Instant instant = Instant.ofEpochMilli(timestamp); return formatter.format(LocalDateTime.ofInstant(instant, ZoneId.of(zoneStr))); } /** * yyyy_MM_dd_HH_mm_ss 转时间戳(毫秒) * * @param formatTime * @return */ public static long urlToTimestampMs(String formatTime) { TemporalAccessor temporalAccessor = urlFormatter.parse(formatTime); Instant instant = Instant.from(temporalAccessor); return instant.toEpochMilli(); } /** * 时间戳 转 yyyy_MM_dd */ public static String timestampTo_yyyy_MM_dd(long timestamp) { Instant instant = Instant.ofEpochMilli(timestamp); return DateFormatter.format(LocalDateTime.ofInstant(instant, ZoneId.of(zoneStr))); } /** * 获取当前时间 * @return */ public static String getNow() { LocalDateTime nowDateTime = LocalDateTime.now(); return formatter.format(nowDateTime); } /** * 获取当前时间 * @return */ public static String getNowForUrl() { LocalDateTime nowDateTime = LocalDateTime.now(); return urlFormatter.format(nowDateTime); } /** * 格式校验 * @param timeStr 时间字符串 * @param dateTimeFormatter 待校验的格式 * @return */ public static boolean verification(String timeStr, DateTimeFormatter dateTimeFormatter) { try { LocalDate.parse(timeStr, dateTimeFormatter); return true; }catch (DateTimeParseException exception) { return false; } } public static String getNowForISO8601() { LocalDateTime nowDateTime = LocalDateTime.now(); return formatterISO8601.format(nowDateTime); } public static long getDifferenceForNow(String keepaliveTime) { if (ObjectUtils.isEmpty(keepaliveTime)) { return 0; } Instant beforeInstant = Instant.from(formatter.parse(keepaliveTime)); return ChronoUnit.MILLIS.between(beforeInstant, Instant.now()); } public static long getDifference(String startTime, String endTime) { if (ObjectUtils.isEmpty(startTime) || ObjectUtils.isEmpty(endTime)) { return 0; } Instant startInstant = Instant.from(formatter.parse(startTime)); Instant endInstant = Instant.from(formatter.parse(endTime)); return ChronoUnit.MILLIS.between(startInstant, endInstant); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/utils/GitUtil.java ================================================ package com.genersoft.iot.vmp.utils; import lombok.Getter; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.PropertySource; import org.springframework.stereotype.Component; /** * 一个优秀的颓废程序猿(CSDN) */ @Getter @Component @PropertySource(value = {"classpath:git.properties" }, ignoreResourceNotFound = true) public class GitUtil { @Value("${git.branch:}") private String branch; @Value("${git.commit.id:}") private String gitCommitId; @Value("${git.remote.origin.url:}") private String gitUrl; @Value("${git.build.time:}") private String buildDate; @Value("${git.build.version:}") private String buildVersion; @Value("${git.commit.id.abbrev:}") private String commitIdShort; @Value("${git.commit.time:}") private String commitTime; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/utils/GpsUtil.java ================================================ package com.genersoft.iot.vmp.utils; import com.genersoft.iot.vmp.gb28181.bean.BaiduPoint; import lombok.extern.slf4j.Slf4j; import java.util.Base64; @Slf4j public class GpsUtil { public static BaiduPoint Wgs84ToBd09(String xx, String yy) { double lng = Double.parseDouble(xx); double lat = Double.parseDouble(yy); Double[] gcj02 = Coordtransform.WGS84ToGCJ02(lng, lat); Double[] doubles = Coordtransform.GCJ02ToBD09(gcj02[0], gcj02[1]); BaiduPoint bdPoint= new BaiduPoint(); bdPoint.setBdLng(doubles[0] + ""); bdPoint.setBdLat(doubles[1] + ""); return bdPoint; } /** * BASE64解码 * @param str * @return string */ public static byte[] decode(String str) { byte[] bt = null; final Base64.Decoder decoder = Base64.getDecoder(); bt = decoder.decode(str); // .decodeBuffer(str); return bt; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/utils/HttpUtils.java ================================================ package com.genersoft.iot.vmp.utils; import lombok.extern.slf4j.Slf4j; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import java.io.IOException; import java.io.InputStream; import java.util.zip.ZipOutputStream; @Slf4j public class HttpUtils { public static boolean downLoadFile(String url, ZipOutputStream zos) { OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder() .url(url) .build(); try (Response response = client.newCall(request).execute()) { if (!response.isSuccessful()) { log.error("下载失败,HTTP 状态码: {}, URL: {}", response.code(), url); return false; } // 获取响应体的输入流 InputStream inputStream = null; if (response.body() != null) { inputStream = response.body().byteStream(); } if (inputStream == null) { log.error("响应体为空,无法下载文件: {}", url); return false; } // 将输入流写入zip文件 byte[] buffer = new byte[8192]; // 8KB 缓冲区,提高性能 int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { zos.write(buffer, 0, bytesRead); } log.debug("成功下载文件: {}, 大小: {} bytes", url, response.body().contentLength()); return true; } catch (IOException e) { log.error("下载过程中出错: {}, URL: {}", e.getMessage(), url); return false; } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/utils/IpPortUtil.java ================================================ package com.genersoft.iot.vmp.utils; import java.net.Inet6Address; import java.net.InetAddress; import java.net.UnknownHostException; public class IpPortUtil { /** * 拼接IP和端口 * @param ip IP地址字符串 * @param port 端口号字符串 * @return 拼接后的字符串 * @throws IllegalArgumentException 如果IP地址无效或端口无效 */ public static String concatenateIpAndPort(String ip, String port) { if (port == null || port.isEmpty()) { throw new IllegalArgumentException("端口号不能为空"); } // 验证端口是否为有效数字 try { int portNum = Integer.parseInt(port); if (portNum < 0 || portNum > 65535) { throw new IllegalArgumentException("端口号必须在0-65535范围内"); } } catch (NumberFormatException e) { throw new IllegalArgumentException("端口号必须是有效数字", e); } try { InetAddress inetAddress = InetAddress.getByName(ip); if (inetAddress instanceof Inet6Address) { // IPv6地址需要加上方括号 return "[" + ip + "]:" + port; } else { // IPv4地址直接拼接 return ip + ":" + port; } } catch (UnknownHostException e) { throw new IllegalArgumentException("无效的IP地址: " + ip, e); } } // 测试用例 public static void main(String[] args) { // IPv4测试 String ipv4 = "192.168.1.1"; String port1 = "8080"; System.out.println(concatenateIpAndPort(ipv4, port1)); // 输出: 192.168.1.1:8080 // IPv6测试 String ipv6 = "2001:0db8:85a3:0000:0000:8a2e:0370:7334"; String port2 = "80"; System.out.println(concatenateIpAndPort(ipv6, port2)); // 输出: [2001:0db8:85a3:0000:0000:8a2e:0370:7334]:80 // 压缩格式IPv6测试 String ipv6Compressed = "2001:db8::1"; System.out.println(concatenateIpAndPort(ipv6Compressed, port2)); // 输出: [2001:db8::1]:80 // 无效IP测试 try { System.out.println(concatenateIpAndPort("invalid.ip", "1234")); } catch (IllegalArgumentException e) { System.out.println("捕获到预期异常: " + e.getMessage()); } // 无效端口测试 - 非数字 try { System.out.println(concatenateIpAndPort(ipv4, "abc")); } catch (IllegalArgumentException e) { System.out.println("捕获到预期异常: " + e.getMessage()); } // 无效端口测试 - 超出范围 try { System.out.println(concatenateIpAndPort(ipv4, "70000")); } catch (IllegalArgumentException e) { System.out.println("捕获到预期异常: " + e.getMessage()); } // 空端口测试 try { System.out.println(concatenateIpAndPort(ipv4, "")); } catch (IllegalArgumentException e) { System.out.println("捕获到预期异常: " + e.getMessage()); } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/utils/JsonUtil.java ================================================ package com.genersoft.iot.vmp.utils; import org.springframework.data.redis.core.RedisTemplate; import java.util.Objects; /** * JsonUtil * * @author KunLong-Luo * @version 1.0.0 * @since 2023/2/2 15:24 */ public final class JsonUtil { private JsonUtil() { } /** * safe json type conversion * * @param key redis key * @param clazz cast type * @param * @return result type */ public static T redisJsonToObject(RedisTemplate redisTemplate, String key, Class clazz) { Object jsonObject = redisTemplate.opsForValue().get(key); if (Objects.isNull(jsonObject)) { return null; } return clazz.cast(jsonObject); } public static T redisHashJsonToObject(RedisTemplate redisTemplate, String key, String objKey, Class clazz) { // if (key == null || objKey == null) { // return null; // } Object jsonObject = redisTemplate.opsForHash().get(key, objKey); if (Objects.isNull(jsonObject)) { return null; } return clazz.cast(jsonObject); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/utils/MediaServerUtils.java ================================================ package com.genersoft.iot.vmp.utils; import org.springframework.util.ObjectUtils; import java.util.HashMap; import java.util.Map; public class MediaServerUtils { public static Map urlParamToMap(String params) { HashMap map = new HashMap<>(); if (ObjectUtils.isEmpty(params)) { return map; } String[] paramsArray = params.split("&"); if (paramsArray.length == 0) { return map; } for (String param : paramsArray) { String[] paramArray = param.split("="); if (paramArray.length == 2) { map.put(paramArray[0], paramArray[1]); } } return map; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/utils/SSLSocketClientUtil.java ================================================ package com.genersoft.iot.vmp.utils; import javax.net.ssl.*; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; public class SSLSocketClientUtil { public static SSLSocketFactory getSocketFactory(TrustManager manager) { SSLSocketFactory socketFactory = null; try { SSLContext sslContext = SSLContext.getInstance("SSL"); sslContext.init(null, new TrustManager[]{manager}, new SecureRandom()); socketFactory = sslContext.getSocketFactory(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (KeyManagementException e) { e.printStackTrace(); } return socketFactory; } public static X509TrustManager getX509TrustManager() { return new X509TrustManager() { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { } @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } }; } public static HostnameVerifier getHostnameVerifier() { HostnameVerifier hostnameVerifier = new HostnameVerifier() { @Override public boolean verify(String s, SSLSession sslSession) { return true; } }; return hostnameVerifier; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/utils/SpringBeanFactory.java ================================================ package com.genersoft.iot.vmp.utils; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; /** * @description:spring bean获取工厂,获取spring中的已初始化的bean * @author: swwheihei * @date: 2019年6月25日 下午4:51:52 * */ @Component public class SpringBeanFactory implements ApplicationContextAware { // Spring应用上下文环境 private static ApplicationContext applicationContext; /** * 实现ApplicationContextAware接口的回调方法,设置上下文环境 */ @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { SpringBeanFactory.applicationContext = applicationContext; } public static ApplicationContext getApplicationContext() { return applicationContext; } /** * 获取对象 这里重写了bean方法,起主要作用 */ public static T getBean(String beanId) throws BeansException { if (applicationContext == null) { return null; } return (T) applicationContext.getBean(beanId); } /** * 获取当前环境 */ public static String getActiveProfile() { return applicationContext.getEnvironment().getActiveProfiles()[0]; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/utils/SystemInfoUtils.java ================================================ package com.genersoft.iot.vmp.utils; import lombok.extern.slf4j.Slf4j; import org.springframework.util.DigestUtils; import oshi.SystemInfo; import oshi.hardware.CentralProcessor; import oshi.hardware.GlobalMemory; import oshi.hardware.HardwareAbstractionLayer; import oshi.hardware.NetworkIF; import oshi.software.os.OperatingSystem; import java.io.File; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; /** * 实现参考自xiaozhangnomoney原创文章, * 版权声明:本文为xiaozhangnomoney原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明 * 原文出处链接:https://blog.csdn.net/xiaozhangnomoney/article/details/107769147 */ @Slf4j public class SystemInfoUtils { /** * 获取cpu信息 * @return * @throws InterruptedException */ public static double getCpuInfo() throws InterruptedException { SystemInfo systemInfo = new SystemInfo(); CentralProcessor processor = systemInfo.getHardware().getProcessor(); long[] prevTicks = processor.getSystemCpuLoadTicks(); // 睡眠1s TimeUnit.SECONDS.sleep(1); long[] ticks = processor.getSystemCpuLoadTicks(); long nice = ticks[CentralProcessor.TickType.NICE.getIndex()] - prevTicks[CentralProcessor.TickType.NICE.getIndex()]; long irq = ticks[CentralProcessor.TickType.IRQ.getIndex()] - prevTicks[CentralProcessor.TickType.IRQ.getIndex()]; long softirq = ticks[CentralProcessor.TickType.SOFTIRQ.getIndex()] - prevTicks[CentralProcessor.TickType.SOFTIRQ.getIndex()]; long steal = ticks[CentralProcessor.TickType.STEAL.getIndex()] - prevTicks[CentralProcessor.TickType.STEAL.getIndex()]; long cSys = ticks[CentralProcessor.TickType.SYSTEM.getIndex()] - prevTicks[CentralProcessor.TickType.SYSTEM.getIndex()]; long user = ticks[CentralProcessor.TickType.USER.getIndex()] - prevTicks[CentralProcessor.TickType.USER.getIndex()]; long iowait = ticks[CentralProcessor.TickType.IOWAIT.getIndex()] - prevTicks[CentralProcessor.TickType.IOWAIT.getIndex()]; long idle = ticks[CentralProcessor.TickType.IDLE.getIndex()] - prevTicks[CentralProcessor.TickType.IDLE.getIndex()]; long totalCpu = user + nice + cSys + idle + iowait + irq + softirq + steal; return 1.0-(idle * 1.0 / totalCpu); } /** * 获取内存使用率 * @return */ public static double getMemInfo(){ SystemInfo systemInfo = new SystemInfo(); GlobalMemory memory = systemInfo.getHardware().getMemory(); //总内存 long totalByte = memory.getTotal(); //剩余 long acaliableByte = memory.getAvailable(); return (totalByte-acaliableByte)*1.0/totalByte; } /** * 获取网络上传和下载 * @return */ public static Map getNetworkInterfaces() { SystemInfo si = new SystemInfo(); HardwareAbstractionLayer hal = si.getHardware(); List beforeRecvNetworkIFs = hal.getNetworkIFs(); NetworkIF beforeBet= beforeRecvNetworkIFs.get(beforeRecvNetworkIFs.size() - 1); long beforeRecv = beforeBet.getBytesRecv(); long beforeSend = beforeBet.getBytesSent(); try { Thread.sleep(1000); } catch (InterruptedException e) { log.error("[线程休眠失败] : {}", e.getMessage()); } List afterNetworkIFs = hal.getNetworkIFs(); NetworkIF afterNet = afterNetworkIFs.get(afterNetworkIFs.size() - 1); HashMap map = new HashMap<>(); // 速度单位: Mbps map.put("in",formatUnits(afterNet.getBytesRecv()-beforeRecv, 1048576L)); map.put("out",formatUnits(afterNet.getBytesSent()-beforeSend, 1048576L)); return map; } /** * 获取带宽总值 * @return */ public static long getNetworkTotal() { SystemInfo si = new SystemInfo(); HardwareAbstractionLayer hal = si.getHardware(); List recvNetworkIFs = hal.getNetworkIFs(); NetworkIF networkIF= recvNetworkIFs.get(recvNetworkIFs.size() - 1); return networkIF.getSpeed()/1048576L/8L; } public static double formatUnits(long value, long prefix) { return (double)value / (double)prefix; } /** * 获取进程数 * @return */ public static int getProcessesCount(){ SystemInfo si = new SystemInfo(); OperatingSystem os = si.getOperatingSystem(); int processCount = os.getProcessCount(); return processCount; } public static List> getDiskInfo() { List> result = new ArrayList<>(); String osName = System.getProperty("os.name"); List pathArray = new ArrayList<>(); if (osName.startsWith("Mac OS")) { // 苹果 pathArray.add("/"); } else if (osName.startsWith("Windows")) { // windows pathArray.add("C:"); } else { pathArray.add("/"); pathArray.add("/home"); } for (String path : pathArray) { Map infoMap = new HashMap<>(); infoMap.put("path", path); File partitionFile = new File(path); // 单位: GB infoMap.put("use", (partitionFile.getTotalSpace() - partitionFile.getFreeSpace())/1024/1024/1024D); infoMap.put("free", partitionFile.getFreeSpace()/1024/1024/1024D); result.add(infoMap); } return result; } public static String getHardwareId(){ SystemInfo systemInfo = new SystemInfo(); HardwareAbstractionLayer hardware = systemInfo.getHardware(); // CPU ID String cpuId = hardware.getProcessor().getProcessorIdentifier().getProcessorID(); // 主板序号 String serialNumber = hardware.getComputerSystem().getSerialNumber(); return DigestUtils.md5DigestAsHex( ( DigestUtils.md5DigestAsHex(cpuId.getBytes(StandardCharsets.UTF_8)) + DigestUtils.md5DigestAsHex(serialNumber.getBytes(StandardCharsets.UTF_8)) ).getBytes(StandardCharsets.UTF_8)); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/utils/TileUtils.java ================================================ package com.genersoft.iot.vmp.utils; import com.genersoft.iot.vmp.gb28181.controller.bean.Extent; import java.util.ArrayList; import java.util.List; public class TileUtils { private static final double MAX_LATITUDE = 85.05112878; /** * 根据坐标获取指定层级的x y 值 * * @param lon 经度 * @param lat 纬度 * @param z 层级 * @return double[2] = {xTileFloat, yTileFloat} */ public static double[] lonLatToTileXY(double lon, double lat, int z) { double n = Math.pow(2.0, z); double x = (lon + 180.0) / 360.0 * n; // clamp latitude to WebMercator bounds double latClamped = Math.max(Math.min(lat, MAX_LATITUDE), -MAX_LATITUDE); double latRad = Math.toRadians(latClamped); double y = (1.0 - (Math.log(Math.tan(latRad) + 1.0 / Math.cos(latRad)) / Math.PI)) / 2.0 * n; return new double[]{x, y}; } /** * 根据坐标范围获取指定层级的x y 范围 * * @param bbox array length 4: {minLon, minLat, maxLon, maxLat} * @param z zoom level * @return TileRange object with xMin,xMax,yMin,yMax,z */ public static TileRange bboxToTileRange(double[] bbox, int z) { double minLon = bbox[0], minLat = bbox[1], maxLon = bbox[2], maxLat = bbox[3]; // If bbox crosses antimeridian (minLon > maxLon), caller should split into two bboxes. if (minLon > maxLon) { throw new IllegalArgumentException("bbox crosses antimeridian; split it before calling bboxToTileRange."); } double[] tMin = lonLatToTileXY(minLon, maxLat, z); // top-left (use maxLat) double[] tMax = lonLatToTileXY(maxLon, minLat, z); // bottom-right (use minLat) int xMin = (int) Math.floor(Math.min(tMin[0], tMax[0])); int xMax = (int) Math.floor(Math.max(tMin[0], tMax[0])); int yMin = (int) Math.floor(Math.min(tMin[1], tMax[1])); int yMax = (int) Math.floor(Math.max(tMin[1], tMax[1])); int maxIndex = ((int) Math.pow(2, z)) - 1; xMin = clamp(xMin, 0, maxIndex); xMax = clamp(xMax, 0, maxIndex); yMin = clamp(yMin, 0, maxIndex); yMax = clamp(yMax, 0, maxIndex); return new TileRange(xMin, xMax, yMin, yMax, z); } /** * If bbox crosses antimeridian (minLon > maxLon), split into two bboxes: * [minLon, minLat, 180, maxLat] and [-180, minLat, maxLon, maxLat] * * @param bbox input bbox array length 4 * @return list of 1 or 2 bboxes (each double[4]) */ public static List splitAntimeridian(double[] bbox) { double minLon = bbox[0], minLat = bbox[1], maxLon = bbox[2], maxLat = bbox[3]; List parts = new ArrayList<>(); if (minLon <= maxLon) { parts.add(new double[]{minLon, minLat, maxLon, maxLat}); } else { parts.add(new double[]{minLon, minLat, 180.0, maxLat}); parts.add(new double[]{-180.0, minLat, maxLon, maxLat}); } return parts; } private static int clamp(int v, int a, int b) { return Math.max(a, Math.min(b, v)); } /** * Return list of tile coordinates (x,y,z) covering bbox at zoom z. * Be careful: the number of tiles can be large for high zooms & big bbox. * */ public static List tilesForBoxAtZoom(Extent extent, int z) { List tiles = new ArrayList<>(); List parts = splitAntimeridian(new double[]{extent.getMinLng(), extent.getMinLat(), extent.getMaxLng(), extent.getMaxLat()}); for (double[] part : parts) { TileRange range = bboxToTileRange(part, z); for (int x = range.xMin; x <= range.xMax; x++) { for (int y = range.yMin; y <= range.yMax; y++) { tiles.add(new TileCoord(x, y, z)); } } } return tiles; } // Simple helper classes public static class TileRange { public final int xMin, xMax, yMin, yMax, z; public TileRange(int xMin, int xMax, int yMin, int yMax, int z) { this.xMin = xMin; this.xMax = xMax; this.yMin = yMin; this.yMax = yMax; this.z = z; } } public static class TileCoord { public final int x, y, z; public TileCoord(int x, int y, int z) { this.x = x; this.y = y; this.z = z; } @Override public String toString() { return "{" + "z=" + z + ", x=" + x + ", y=" + y + '}'; } } /** * tile X/Z -> longitude (deg) */ public static double tile2lon(int x, int z) { double n = Math.pow(2.0, z); return x / n * 360.0 - 180.0; } /** * tile Y/Z -> latitude (deg) */ public static double tile2lat(int y, int z) { double n = Math.pow(2.0, z); double latRad = Math.atan(Math.sinh(Math.PI * (1 - 2.0 * y / n))); return Math.toDegrees(latRad); } /** * lon/lat -> pixel in tile (0..256) */ public static double[] lonLatToTilePixel(double lon, double lat, int z, int tileX, int tileY) { double n = Math.pow(2.0, z); double xtile = (lon + 180.0) / 360.0 * n; double latRad = Math.toRadians(lat); double ytile = (1.0 - Math.log(Math.tan(latRad) + 1.0 / Math.cos(latRad)) / Math.PI) / 2.0 * n; double pixelX = (xtile - tileX) * 256.0; double pixelY = (ytile - tileY) * 256.0; return new double[] { pixelX, pixelY }; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/utils/UJson.java ================================================ package com.genersoft.iot.vmp.utils; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import java.util.Iterator; import java.util.Map; import java.util.Objects; /** * @author gaofuwang * @version 1.0 * @date 2022/3/11 10:17 */ @Slf4j public class UJson { public static final ObjectMapper JSON_MAPPER = new ObjectMapper(); static { JSON_MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,false); } private ObjectNode node; public UJson(){ this.node = JSON_MAPPER.createObjectNode(); } public UJson(String json){ if(StringUtils.isBlank(json)){ this.node = JSON_MAPPER.createObjectNode(); }else{ try { this.node = JSON_MAPPER.readValue(json, ObjectNode.class); }catch (Exception e){ log.error(e.getMessage(), e); this.node = JSON_MAPPER.createObjectNode(); } } } public UJson(ObjectNode node){ this.node = node; } public String asText(String key){ JsonNode jsonNode = node.get(key); if(Objects.isNull(jsonNode)){ return ""; } return jsonNode.asText(); } public String asText(String key, String defaultVal){ JsonNode jsonNode = node.get(key); if(Objects.isNull(jsonNode)){ return ""; } return jsonNode.asText(defaultVal); } public UJson put(String key, String value){ this.node.put(key, value); return this; } public UJson put(String key, Integer value){ this.node.put(key, value); return this; } public static UJson json(){ return new UJson(); } public static UJson json(String json){ return new UJson(json); } public static T readJson(String json, Class clazz){ if(StringUtils.isBlank(json)){ return null; } try { return JSON_MAPPER.readValue(json, clazz); }catch (Exception e){ log.error(e.getMessage(), e); return null; } } public static String writeJson(Object object) { try{ return JSON_MAPPER.writeValueAsString(object); }catch (Exception e){ log.error(e.getMessage(), e); return ""; } } @Override public String toString() { return node.toString(); } public int asInt(String key, int defValue) { JsonNode jsonNode = this.node.get(key); if(Objects.isNull(jsonNode)){ return defValue; } return jsonNode.asInt(defValue); } public UJson getSon(String key) { JsonNode sonNode = this.node.get(key); if(Objects.isNull(sonNode)){ return new UJson(); } return new UJson((ObjectNode) sonNode); } public UJson set(String key, ObjectNode sonNode) { this.node.set(key, sonNode); return this; } public UJson set(String key, UJson sonNode) { this.node.set(key, sonNode.node); return this; } public Iterator> fields() { return this.node.fields(); } public ObjectNode getNode() { return this.node; } public UJson setAll(UJson json) { this.node.setAll(json.node); return this; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/utils/redis/FastJsonRedisSerializer.java ================================================ package com.genersoft.iot.vmp.utils.redis; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONReader; import com.alibaba.fastjson2.JSONWriter; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.SerializationException; import java.nio.charset.Charset; /** * @description:使用fastjson实现redis的序列化 * @author: swwheihei * @date: 2020年5月6日 下午8:40:11 */ public class FastJsonRedisSerializer implements RedisSerializer { public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); private Class clazz; public FastJsonRedisSerializer(Class clazz) { super(); this.clazz = clazz; } @Override public byte[] serialize(T t) throws SerializationException { if (t == null) { return new byte[0]; } return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName, JSONWriter.Feature.WritePairAsJavaBean).getBytes(DEFAULT_CHARSET); } @Override public T deserialize(byte[] bytes) throws SerializationException { if (bytes == null || bytes.length <= 0) { return null; } String str = new String(bytes, DEFAULT_CHARSET); return JSON.parseObject(str, clazz, JSONReader.Feature.SupportAutoType); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/utils/redis/RedisUtil.java ================================================ package com.genersoft.iot.vmp.utils.redis; import com.google.common.collect.Lists; import org.springframework.data.redis.core.Cursor; import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ScanOptions; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; /** * Redis工具类 * * @author swwheihei * @date 2020年5月6日 下午8:27:29 */ @SuppressWarnings(value = {"rawtypes", "unchecked"}) public class RedisUtil { /** * 模糊查询 * * @param query 查询参数 * @return */ public static List scan(RedisTemplate redisTemplate, String query) { Set resultKeys = (Set) redisTemplate.execute((RedisCallback>) connection -> { ScanOptions scanOptions = ScanOptions.scanOptions().match("*" + query + "*").count(1000).build(); Cursor scan = connection.scan(scanOptions); Set keys = new HashSet<>(); while (scan.hasNext()) { byte[] next = scan.next(); keys.add(new String(next)); } return keys; }); return Lists.newArrayList(resultKeys); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/utils/redis/RedisUtil2.java ================================================ //package com.genersoft.iot.vmp.utils.redis; // //import com.alibaba.fastjson2.JSONObject; //import com.genersoft.iot.vmp.utils.SpringBeanFactory; //import org.springframework.data.redis.core.*; //import org.springframework.util.CollectionUtils; // //import java.util.*; //import java.util.concurrent.TimeUnit; // ///** // * Redis工具类 // * @author swwheihei // * @date 2020年5月6日 下午8:27:29 // */ //@SuppressWarnings(value = {"rawtypes", "unchecked"}) //public class RedisUtil2 { // // private static RedisTemplate redisTemplate; // // static { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // // /** // * 指定缓存失效时间 // * @param key 键 // * @param time 时间(秒) // * @return true / false // */ // public static boolean expire(String key, long time) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // try { // if (time > 0) { // redisTemplate.expire(key, time, TimeUnit.SECONDS); // } // return true; // } catch (Exception e) { // e.printStackTrace(); // return false; // } // } // // /** // * 根据 key 获取过期时间 // * @param key 键 // */ // public static long getExpire(String key) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // return redisTemplate.getExpire(key, TimeUnit.SECONDS); // } // // /** // * 判断 key 是否存在 // * @param key 键 // * @return true / false // */ // public static boolean hasKey(String key) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // try { // return redisTemplate.hasKey(key); // } catch (Exception e) { // e.printStackTrace(); // return false; // } // } // // /** // * 删除缓存 // * @SuppressWarnings("unchecked") 忽略类型转换警告 // * @param key 键(一个或者多个) // */ // public static boolean del(String... key) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // try { // if (key != null && key.length > 0) { // if (key.length == 1) { // redisTemplate.delete(key[0]); // } else { //// 传入一个 Collection 集合 // redisTemplate.delete(CollectionUtils.arrayToList(key)); // } // } // return true; // } catch (Exception e) { // e.printStackTrace(); // return false; // } // } // //// ============================== String ============================== // // /** // * 普通缓存获取 // * @param key 键 // * @return 值 // */ // public static Object get(String key) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // return key == null ? null : redisTemplate.opsForValue().get(key); // } // // /** // * 普通缓存放入 // * @param key 键 // * @param value 值 // * @return true / false // */ // public static boolean set(String key, Object value) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // try { // redisTemplate.opsForValue().set(key, value); // return true; // } catch (Exception e) { // e.printStackTrace(); // return false; // } // } // // /** // * 普通缓存放入并设置时间 // * @param key 键 // * @param value 值 // * @param time 时间(秒),如果 time < 0 则设置无限时间 // * @return true / false // */ // public static boolean set(String key, Object value, long time) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // try { // if (time > 0) { // redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS); // } else { // set(key, value); // } // return true; // } catch (Exception e) { // e.printStackTrace(); // return false; // } // } // // /** // * 递增 // * @param key 键 // * @param delta 递增大小 // * @return // */ // public static long incr(String key, long delta) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // if (delta < 0) { // throw new RuntimeException("递增因子必须大于 0"); // } // return redisTemplate.opsForValue().increment(key, delta); // } // // /** // * 递减 // * @param key 键 // * @param delta 递减大小 // * @return // */ // public static long decr(String key, long delta) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // if (delta < 0) { // throw new RuntimeException("递减因子必须大于 0"); // } // return redisTemplate.opsForValue().increment(key, delta); // } // //// ============================== Map ============================== // // /** // * HashGet // * @param key 键(no null) // * @param item 项(no null) // * @return 值 // */ // public static Object hget(String key, String item) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // return redisTemplate.opsForHash().get(key, item); // } // // /** // * 获取 key 对应的 map // * @param key 键(no null) // * @return 对应的多个键值 // */ // public static Map hmget(String key) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // return redisTemplate.opsForHash().entries(key); // } // // /** // * HashSet // * @param key 键 // * @param map 值 // * @return true / false // */ // public static boolean hmset(String key, Map map) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // try { // redisTemplate.opsForHash().putAll(key, map); // return true; // } catch (Exception e) { // e.printStackTrace(); // return false; // } // } // // /** // * HashSet 并设置时间 // * @param key 键 // * @param map 值 // * @param time 时间 // * @return true / false // */ // public static boolean hmset(String key, Map map, long time) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // try { // redisTemplate.opsForHash().putAll(key, map); // if (time > 0) { // expire(key, time); // } // return true; // } catch (Exception e) { // e.printStackTrace(); // return false; // } // } // // /** // * 向一张 Hash表 中放入数据,如不存在则创建 // * @param key 键 // * @param item 项 // * @param value 值 // * @return true / false // */ // public static boolean hset(String key, String item, Object value) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // try { // redisTemplate.opsForHash().put(key, item, value); // return true; // } catch (Exception e) { // e.printStackTrace(); // return false; // } // } // // /** // * 向一张 Hash表 中放入数据,并设置时间,如不存在则创建 // * @param key 键 // * @param item 项 // * @param value 值 // * @param time 时间(如果原来的 Hash表 设置了时间,这里会覆盖) // * @return true / false // */ // public static boolean hset(String key, String item, Object value, long time) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // try { // redisTemplate.opsForHash().put(key, item, value); // if (time > 0) { // expire(key, time); // } // return true; // } catch (Exception e) { // e.printStackTrace(); // return false; // } // } // // /** // * 删除 Hash表 中的值 // * @param key 键 // * @param item 项(可以多个,no null) // */ // public static void hdel(String key, Object... item) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // redisTemplate.opsForHash().delete(key, item); // } // // /** // * 判断 Hash表 中是否有该键的值 // * @param key 键(no null) // * @param item 值(no null) // * @return true / false // */ // public static boolean hHasKey(String key, String item) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // return redisTemplate.opsForHash().hasKey(key, item); // } // // /** // * Hash递增,如果不存在则创建一个,并把新增的值返回 // * @param key 键 // * @param item 项 // * @param by 递增大小 > 0 // * @return // */ // public static Double hincr(String key, String item, Double by) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // return redisTemplate.opsForHash().increment(key, item, by); // } // // /** // * Hash递减 // * @param key 键 // * @param item 项 // * @param by 递减大小 // * @return // */ // public static Double hdecr(String key, String item, Double by) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // return redisTemplate.opsForHash().increment(key, item, -by); // } // //// ============================== Set ============================== // // /** // * 根据 key 获取 set 中的所有值 // * @param key 键 // * @return 值 // */ // public static Set sGet(String key) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // try { // return redisTemplate.opsForSet().members(key); // } catch (Exception e) { // e.printStackTrace(); // return null; // } // } // // /** // * 从键为 key 的 set 中,根据 value 查询是否存在 // * @param key 键 // * @param value 值 // * @return true / false // */ // public static boolean sHasKey(String key, Object value) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // try { // return redisTemplate.opsForSet().isMember(key, value); // } catch (Exception e) { // e.printStackTrace(); // return false; // } // } // // /** // * 将数据放入 set缓存 // * @param key 键值 // * @param values 值(可以多个) // * @return 成功个数 // */ // public static long sSet(String key, Object... values) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // try { // return redisTemplate.opsForSet().add(key, values); // } catch (Exception e) { // e.printStackTrace(); // return 0; // } // } // // /** // * 将数据放入 set缓存,并设置时间 // * @param key 键 // * @param time 时间 // * @param values 值(可以多个) // * @return 成功放入个数 // */ // public static long sSet(String key, long time, Object... values) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // try { // long count = redisTemplate.opsForSet().add(key, values); // if (time > 0) { // expire(key, time); // } // return count; // } catch (Exception e) { // e.printStackTrace(); // return 0; // } // } // // /** // * 获取 set缓存的长度 // * @param key 键 // * @return 长度 // */ // public static long sGetSetSize(String key) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // try { // return redisTemplate.opsForSet().size(key); // } catch (Exception e) { // e.printStackTrace(); // return 0; // } // } // // /** // * 移除 set缓存中,值为 value 的 // * @param key 键 // * @param values 值 // * @return 成功移除个数 // */ // public static long setRemove(String key, Object... values) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // try { // return redisTemplate.opsForSet().remove(key, values); // } catch (Exception e) { // e.printStackTrace(); // return 0; // } // } //// ============================== ZSet ============================== // // /** // * 添加一个元素, zset与set最大的区别就是每个元素都有一个score,因此有个排序的辅助功能; zadd // * // * @param key // * @param value // * @param score // */ // public static void zAdd(Object key, Object value, double score) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // redisTemplate.opsForZSet().add(key, value, score); // } // // /** // * 删除元素 zrem // * // * @param key // * @param value // */ // public static void zRemove(Object key, Object value) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // redisTemplate.opsForZSet().remove(key, value); // } // // /** // * score的增加or减少 zincrby // * // * @param key // * @param value // * @param delta -1 表示减 1 表示加1 // */ // public static Double zIncrScore(Object key, Object value, double delta) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // return redisTemplate.opsForZSet().incrementScore(key, value, delta); // } // // /** // * 查询value对应的score zscore // * // * @param key // * @param value // * @return // */ // public static Double zScore(Object key, Object value) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // return redisTemplate.opsForZSet().score(key, value); // } // // /** // * 判断value在zset中的排名 zrank // * // * @param key // * @param value // * @return // */ // public static Long zRank(Object key, Object value) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // return redisTemplate.opsForZSet().rank(key, value); // } // // /** // * 返回集合的长度 // * // * @param key // * @return // */ // public static Long zSize(Object key) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // return redisTemplate.opsForZSet().zCard(key); // } // // /** // * 查询集合中指定顺序的值, 0 -1 表示获取全部的集合内容 zrange // * // * 返回有序的集合,score小的在前面 // * // * @param key // * @param start // * @param end // * @return // */ // public static Set zRange(Object key, int start, int end) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // return redisTemplate.opsForZSet().range(key, start, end); // } // /** // * 查询集合中指定顺序的值和score,0, -1 表示获取全部的集合内容 // * // * @param key // * @param start // * @param end // * @return // */ // public static Set> zRangeWithScore(Object key, int start, int end) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // return redisTemplate.opsForZSet().rangeWithScores(key, start, end); // } // /** // * 查询集合中指定顺序的值 zrevrange // * // * 返回有序的集合中,score大的在前面 // * // * @param key // * @param start // * @param end // * @return // */ // public static Set zRevRange(Object key, int start, int end) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // return redisTemplate.opsForZSet().reverseRange(key, start, end); // } // /** // * 根据score的值,来获取满足条件的集合 zrangebyscore // * // * @param key // * @param min // * @param max // * @return // */ // public static Set zSortRange(Object key, int min, int max) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // return redisTemplate.opsForZSet().rangeByScore(key, min, max); // } // // //// ============================== List ============================== // // /** // * 获取 list缓存的内容 // * @param key 键 // * @param start 开始 // * @param end 结束(0 到 -1 代表所有值) // * @return // */ // public static List lGet(String key, long start, long end) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // try { // return redisTemplate.opsForList().range(key, start, end); // } catch (Exception e) { // e.printStackTrace(); // return null; // } // } // // /** // * 获取 list缓存的长度 // * @param key 键 // * @return 长度 // */ // public static long lGetListSize(String key) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // try { // return redisTemplate.opsForList().size(key); // } catch (Exception e) { // e.printStackTrace(); // return 0; // } // } // // /** // * 根据索引 index 获取键为 key 的 list 中的元素 // * @param key 键 // * @param index 索引 // * 当 index >= 0 时 {0:表头, 1:第二个元素} // * 当 index < 0 时 {-1:表尾, -2:倒数第二个元素} // * @return 值 // */ // public static Object lGetIndex(String key, long index) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // try { // return redisTemplate.opsForList().index(key, index); // } catch (Exception e) { // e.printStackTrace(); // return null; // } // } // // /** // * 将值 value 插入键为 key 的 list 中,如果 list 不存在则创建空 list // * @param key 键 // * @param value 值 // * @return true / false // */ // public static boolean lSet(String key, Object value) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // try { // redisTemplate.opsForList().rightPush(key, value); // return true; // } catch (Exception e) { // e.printStackTrace(); // return false; // } // } // // /** // * 将值 value 插入键为 key 的 list 中,并设置时间 // * @param key 键 // * @param value 值 // * @param time 时间 // * @return true / false // */ // public static boolean lSet(String key, Object value, long time) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // try { // redisTemplate.opsForList().rightPush(key, value); // if (time > 0) { // expire(key, time); // } // return true; // } catch (Exception e) { // e.printStackTrace(); // return false; // } // } // // /** // * 将 values 插入键为 key 的 list 中 // * @param key 键 // * @param values 值 // * @return true / false // */ // public static boolean lSetList(String key, List values) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // try { // redisTemplate.opsForList().rightPushAll(key, values); // return true; // } catch (Exception e) { // e.printStackTrace(); // return false; // } // } // // /** // * 将 values 插入键为 key 的 list 中,并设置时间 // * @param key 键 // * @param values 值 // * @param time 时间 // * @return true / false // */ // public static boolean lSetList(String key, List values, long time) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // try { // redisTemplate.opsForList().rightPushAll(key, values); // if (time > 0) { // expire(key, time); // } // return true; // } catch (Exception e) { // e.printStackTrace(); // return false; // } // } // // /** // * 根据索引 index 修改键为 key 的值 // * @param key 键 // * @param index 索引 // * @param value 值 // * @return true / false // */ // public static boolean lUpdateIndex(String key, long index, Object value) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // try { // redisTemplate.opsForList().set(key, index, value); // return true; // } catch (Exception e) { // e.printStackTrace(); // return false; // } // } // // /** // * 在键为 key 的 list 中删除值为 value 的元素 // * @param key 键 // * @param count 如果 count == 0 则删除 list 中所有值为 value 的元素 // * 如果 count > 0 则删除 list 中最左边那个值为 value 的元素 // * 如果 count < 0 则删除 list 中最右边那个值为 value 的元素 // * @param value // * @return // */ // public static long lRemove(String key, long count, Object value) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // try { // return redisTemplate.opsForList().remove(key, count, value); // } catch (Exception e) { // e.printStackTrace(); // return 0; // } // } // // /** // * 在键为 key 的 list中移除第一个元素 // * @param key 键 // * @return // */ // public static Object lLeftPop(String key) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // return redisTemplate.opsForList().leftPop(key); // } // // /** // * 在键为 key 的 list中移除、最后一个元素 // * @param key 键 // * @return // */ // public static Object lrightPop(String key) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // return redisTemplate.opsForList().rightPop(key); // } // // /** // * 模糊查询 // * @param key 键 // * @return true / false // */ // public static List keys(String key) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // try { // Set set = redisTemplate.keys(key); // return new ArrayList<>(set); // } catch (Exception e) { // e.printStackTrace(); // return null; // } // } // // // /** // * 模糊查询 // * @param query 查询参数 // * @return // */ //// public static List scan(String query) { //// List result = new ArrayList<>(); //// try { //// Cursor> cursor = redisTemplate.opsForHash().scan("field", //// ScanOptions.scanOptions().match(query).count(1000).build()); //// while (cursor.hasNext()) { //// Map.Entry entry = cursor.next(); //// result.add(entry.getKey()); //// Object key = entry.getKey(); //// Object valueSet = entry.getValue(); //// } //// //关闭cursor //// cursor.close(); //// } catch (Exception e) { //// e.printStackTrace(); //// } //// return result; //// } // // /** // * 模糊查询 // * @param query 查询参数 // * @return // */ // public static List scan(String query) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // Set resultKeys = (Set) redisTemplate.execute((RedisCallback>) connection -> { // ScanOptions scanOptions = ScanOptions.scanOptions().match("*" + query + "*").count(1000).build(); // Cursor scan = connection.scan(scanOptions); // Set keys = new HashSet<>(); // while (scan.hasNext()) { // byte[] next = scan.next(); // keys.add(new String(next)); // } // return keys; // }); // // return new ArrayList<>(resultKeys); // } // public static List scan2(String query) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // Set keys = redisTemplate.keys(query); // return new ArrayList<>(keys); // } // // ============================== 消息发送与订阅 ============================== // public static void convertAndSend(String channel, JSONObject msg) { // if (redisTemplate == null) { // redisTemplate = SpringBeanFactory.getBean("redisTemplate"); // } // redisTemplate.convertAndSend(channel, msg); // } // //} ================================================ FILE: src/main/java/com/genersoft/iot/vmp/vmanager/TestController.java ================================================ package com.genersoft.iot.vmp.vmanager; import com.genersoft.iot.vmp.common.InviteInfo; import com.genersoft.iot.vmp.common.InviteSessionType; import com.genersoft.iot.vmp.common.VideoManagerConstants; import com.genersoft.iot.vmp.media.event.hook.Hook; import com.genersoft.iot.vmp.media.event.hook.HookSubscribe; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.Cursor; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ScanOptions; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.List; import java.util.Map; @RestController @RequestMapping("/api/test") public class TestController { @Autowired private HookSubscribe subscribe; @Autowired private RedisTemplate redisTemplate; @GetMapping("/hook/list") public List all(){ return subscribe.getAll(); } @GetMapping("/redis") public List redis(){ InviteSessionType type = InviteSessionType.PLAY; String channelId = null; String stream = null; String key = VideoManagerConstants.INVITE_PREFIX; String keyPattern = (type != null ? type : "*") + ":" + (channelId != null ? channelId : "*") + ":" + (stream != null ? stream : "*") + ":*"; ScanOptions options = ScanOptions.scanOptions().match(keyPattern).count(20).build(); Cursor> cursor = redisTemplate.opsForHash().scan(key, options); List result = new ArrayList<>(); try { while (cursor.hasNext()) { System.out.println(cursor.next().getKey()); result.add((InviteInfo) cursor.next().getValue()); } }catch (Exception e) { }finally { cursor.close(); } return result; } // @Bean // public ServletRegistrationBean druidStatViewServlet() { // ServletRegistrationBean registrationBean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*"); // registrationBean.addInitParameter("allow", "127.0.0.1");// IP白名单 (没有配置或者为空,则允许所有访问) // registrationBean.addInitParameter("deny", "");// IP黑名单 (存在共同时,deny优先于allow) // registrationBean.addInitParameter("loginUsername", "admin"); // registrationBean.addInitParameter("loginPassword", "admin"); // registrationBean.addInitParameter("resetEnable", "false"); // return registrationBean; // } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/vmanager/bean/AudioBroadcastResult.java ================================================ package com.genersoft.iot.vmp.vmanager.bean; /** * @author lin */ public class AudioBroadcastResult { /** * 推流的各个方式流地址 */ private StreamContent streamInfo; /** * 编码格式 */ private String codec; /** * 向zlm推流的应用名 */ private String app; /** * 向zlm推流的流ID */ private String stream; public StreamContent getStreamInfo() { return streamInfo; } public void setStreamInfo(StreamContent streamInfo) { this.streamInfo = streamInfo; } public String getCodec() { return codec; } public void setCodec(String codec) { this.codec = codec; } public String getApp() { return app; } public void setApp(String app) { this.app = app; } public String getStream() { return stream; } public void setStream(String stream) { this.stream = stream; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/vmanager/bean/BatchGBStreamParam.java ================================================ package com.genersoft.iot.vmp.vmanager.bean; import com.genersoft.iot.vmp.gb28181.bean.GbStream; import io.swagger.v3.oas.annotations.media.Schema; import java.util.List; /** * @author lin */ @Schema(description = "多个推流信息") public class BatchGBStreamParam { @Schema(description = "推流信息列表") private List gbStreams; public List getGbStreams() { return gbStreams; } public void setGbStreams(List gbStreams) { this.gbStreams = gbStreams; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/vmanager/bean/DeferredResultEx.java ================================================ package com.genersoft.iot.vmp.vmanager.bean; import org.springframework.web.context.request.async.DeferredResult; public class DeferredResultEx { private DeferredResult deferredResult; private DeferredResultFilter filter; public DeferredResultEx(DeferredResult result) { this.deferredResult = result; } public DeferredResult getDeferredResult() { return deferredResult; } public void setDeferredResult(DeferredResult deferredResult) { this.deferredResult = deferredResult; } public DeferredResultFilter getFilter() { return filter; } public void setFilter(DeferredResultFilter filter) { this.filter = filter; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/vmanager/bean/DeferredResultFilter.java ================================================ package com.genersoft.iot.vmp.vmanager.bean; public interface DeferredResultFilter { Object handler(Object o); } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/vmanager/bean/ErrorCode.java ================================================ package com.genersoft.iot.vmp.vmanager.bean; /** * 全局错误码 */ public enum ErrorCode { SUCCESS(0, "成功"), ERROR100(100, "失败"), ERROR400(400, "参数或方法错误"), ERROR404(404, "资源未找到"), ERROR403(403, "无权限操作"), ERROR486(486, "超时或无响应"), ERROR401(401, "请登录后重新请求"), ERROR408(408, "请求超时"), ERROR500(500, "系统异常"); private final int code; private final String msg; ErrorCode(int code, String msg) { this.code = code; this.msg = msg; } public int getCode() { return code; } public String getMsg() { return msg; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/vmanager/bean/MapConfig.java ================================================ package com.genersoft.iot.vmp.vmanager.bean; import lombok.Data; @Data public class MapConfig { private String name; private String coordinateSystem; private String tilesUrl; private Integer tileSize; private Integer zoom; private Double[] center; private Integer maxZoom; private Integer minZoom; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/vmanager/bean/MapModelIcon.java ================================================ package com.genersoft.iot.vmp.vmanager.bean; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @Data public class MapModelIcon { @Schema(description = "名称") private String name; @Schema(description = "别名") private String alias; @Schema(description = "路径") private String path; public static MapModelIcon getInstance(String name, String alias, String path) { MapModelIcon mapModelIcon = new MapModelIcon(); mapModelIcon.setAlias(alias); mapModelIcon.setName(name); mapModelIcon.setPath(path); return mapModelIcon; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/vmanager/bean/OtherPsSendInfo.java ================================================ package com.genersoft.iot.vmp.vmanager.bean; public class OtherPsSendInfo { /** * 发流IP */ private String sendLocalIp; /** * 发流端口 */ private int sendLocalPort; /** * 收流IP */ private String receiveIp; /** * 收流端口 */ private int receivePort; /** * 会话ID */ private String callId; /** * 流ID */ private String stream; /** * 推流应用名 */ private String pushApp; /** * 推流流ID */ private String pushStream; /** * 推流SSRC */ private String pushSSRC; public String getSendLocalIp() { return sendLocalIp; } public void setSendLocalIp(String sendLocalIp) { this.sendLocalIp = sendLocalIp; } public int getSendLocalPort() { return sendLocalPort; } public void setSendLocalPort(int sendLocalPort) { this.sendLocalPort = sendLocalPort; } public String getReceiveIp() { return receiveIp; } public void setReceiveIp(String receiveIp) { this.receiveIp = receiveIp; } public int getReceivePort() { return receivePort; } public void setReceivePort(int receivePort) { this.receivePort = receivePort; } public String getCallId() { return callId; } public void setCallId(String callId) { this.callId = callId; } public String getStream() { return stream; } public void setStream(String stream) { this.stream = stream; } public String getPushApp() { return pushApp; } public void setPushApp(String pushApp) { this.pushApp = pushApp; } public String getPushStream() { return pushStream; } public void setPushStream(String pushStream) { this.pushStream = pushStream; } public String getPushSSRC() { return pushSSRC; } public void setPushSSRC(String pushSSRC) { this.pushSSRC = pushSSRC; } @Override public String toString() { return "OtherPsSendInfo{" + "sendLocalIp='" + sendLocalIp + '\'' + ", sendLocalPort=" + sendLocalPort + ", receiveIp='" + receiveIp + '\'' + ", receivePort=" + receivePort + ", callId='" + callId + '\'' + ", stream='" + stream + '\'' + ", pushApp='" + pushApp + '\'' + ", pushStream='" + pushStream + '\'' + ", pushSSRC='" + pushSSRC + '\'' + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/vmanager/bean/OtherRtpSendInfo.java ================================================ package com.genersoft.iot.vmp.vmanager.bean; public class OtherRtpSendInfo { /** * 发流IP */ private String sendLocalIp; /** * 音频发流端口 */ private int sendLocalPortForAudio; /** * 视频发流端口 */ private int sendLocalPortForVideo; /** * 收流IP */ private String receiveIp; /** * 音频收流端口 */ private int receivePortForAudio; /** * 视频收流端口 */ private int receivePortForVideo; /** * 会话ID */ private String callId; /** * 流ID */ private String stream; /** * 推流应用名 */ private String pushApp; /** * 推流流ID */ private String pushStream; /** * 推流SSRC */ private String pushSSRC; public String getReceiveIp() { return receiveIp; } public void setReceiveIp(String receiveIp) { this.receiveIp = receiveIp; } public int getReceivePortForAudio() { return receivePortForAudio; } public void setReceivePortForAudio(int receivePortForAudio) { this.receivePortForAudio = receivePortForAudio; } public int getReceivePortForVideo() { return receivePortForVideo; } public void setReceivePortForVideo(int receivePortForVideo) { this.receivePortForVideo = receivePortForVideo; } public String getCallId() { return callId; } public void setCallId(String callId) { this.callId = callId; } public String getStream() { return stream; } public void setStream(String stream) { this.stream = stream; } public String getPushApp() { return pushApp; } public void setPushApp(String pushApp) { this.pushApp = pushApp; } public String getPushStream() { return pushStream; } public void setPushStream(String pushStream) { this.pushStream = pushStream; } public String getPushSSRC() { return pushSSRC; } public void setPushSSRC(String pushSSRC) { this.pushSSRC = pushSSRC; } public String getSendLocalIp() { return sendLocalIp; } public void setSendLocalIp(String sendLocalIp) { this.sendLocalIp = sendLocalIp; } public int getSendLocalPortForAudio() { return sendLocalPortForAudio; } public void setSendLocalPortForAudio(int sendLocalPortForAudio) { this.sendLocalPortForAudio = sendLocalPortForAudio; } public int getSendLocalPortForVideo() { return sendLocalPortForVideo; } public void setSendLocalPortForVideo(int sendLocalPortForVideo) { this.sendLocalPortForVideo = sendLocalPortForVideo; } @Override public String toString() { return "OtherRtpSendInfo{" + "sendLocalIp='" + sendLocalIp + '\'' + ", sendLocalPortForAudio=" + sendLocalPortForAudio + ", sendLocalPortForVideo=" + sendLocalPortForVideo + ", receiveIp='" + receiveIp + '\'' + ", receivePortForAudio=" + receivePortForAudio + ", receivePortForVideo=" + receivePortForVideo + ", callId='" + callId + '\'' + ", stream='" + stream + '\'' + ", pushApp='" + pushApp + '\'' + ", pushStream='" + pushStream + '\'' + ", pushSSRC='" + pushSSRC + '\'' + '}'; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/vmanager/bean/PlayTypeEnum.java ================================================ package com.genersoft.iot.vmp.vmanager.bean; public enum PlayTypeEnum { PLAY("0", "直播"), PLAY_BACK("1", "回放"); private String value; private String name; PlayTypeEnum(String value, String name) { this.value = value; this.name = name; } public String getValue() { return value; } public String getName() { return name; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/vmanager/bean/RecordFile.java ================================================ package com.genersoft.iot.vmp.vmanager.bean; public class RecordFile { private String app; private String stream; private String fileName; private String mediaServerId; private String date; public String getApp() { return app; } public void setApp(String app) { this.app = app; } public String getStream() { return stream; } public void setStream(String stream) { this.stream = stream; } public String getFileName() { return fileName; } public void setFileName(String fileName) { this.fileName = fileName; } public String getMediaServerId() { return mediaServerId; } public void setMediaServerId(String mediaServerId) { this.mediaServerId = mediaServerId; } public String getDate() { return date; } public void setDate(String date) { this.date = date; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/vmanager/bean/ResourceBaseInfo.java ================================================ package com.genersoft.iot.vmp.vmanager.bean; public class ResourceBaseInfo { private int total; private int online; public ResourceBaseInfo() { } public ResourceBaseInfo(int total, int online) { this.total = total; this.online = online; } public int getTotal() { return total; } public void setTotal(int total) { this.total = total; } public int getOnline() { return online; } public void setOnline(int online) { this.online = online; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/vmanager/bean/ResourceInfo.java ================================================ package com.genersoft.iot.vmp.vmanager.bean; public class ResourceInfo { private ResourceBaseInfo device; private ResourceBaseInfo channel; private ResourceBaseInfo push; private ResourceBaseInfo proxy; public ResourceBaseInfo getDevice() { return device; } public void setDevice(ResourceBaseInfo device) { this.device = device; } public ResourceBaseInfo getChannel() { return channel; } public void setChannel(ResourceBaseInfo channel) { this.channel = channel; } public ResourceBaseInfo getPush() { return push; } public void setPush(ResourceBaseInfo push) { this.push = push; } public ResourceBaseInfo getProxy() { return proxy; } public void setProxy(ResourceBaseInfo proxy) { this.proxy = proxy; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/vmanager/bean/SnapPath.java ================================================ package com.genersoft.iot.vmp.vmanager.bean; import io.swagger.v3.oas.annotations.media.Schema; @Schema(description = "截图地址信息") public class SnapPath { @Schema(description = "相对地址") private String path; @Schema(description = "绝对地址") private String absoluteFilePath; @Schema(description = "请求地址") private String url; public static SnapPath getInstance(String path, String absoluteFilePath, String url) { SnapPath snapPath = new SnapPath(); snapPath.setPath(path); snapPath.setAbsoluteFilePath(absoluteFilePath); snapPath.setUrl(url); return snapPath; } public String getPath() { return path; } public void setPath(String path) { this.path = path; } public String getAbsoluteFilePath() { return absoluteFilePath; } public void setAbsoluteFilePath(String absoluteFilePath) { this.absoluteFilePath = absoluteFilePath; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/vmanager/bean/StreamContent.java ================================================ package com.genersoft.iot.vmp.vmanager.bean; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.media.bean.MediaInfo; import com.genersoft.iot.vmp.service.bean.DownloadFileInfo; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @Data @Schema(description = "流信息") public class StreamContent { @Schema(description = "应用名") private String app; @Schema(description = "流ID") private String stream; @Schema(description = "IP") private String ip; @Schema(description = "HTTP-FLV流地址") private String flv; @Schema(description = "HTTPS-FLV流地址") private String https_flv; @Schema(description = "Websocket-FLV流地址") private String ws_flv; @Schema(description = "Websockets-FLV流地址") private String wss_flv; @Schema(description = "HTTP-FMP4流地址") private String fmp4; @Schema(description = "HTTPS-FMP4流地址") private String https_fmp4; @Schema(description = "Websocket-FMP4流地址") private String ws_fmp4; @Schema(description = "Websockets-FMP4流地址") private String wss_fmp4; @Schema(description = "HLS流地址") private String hls; @Schema(description = "HTTPS-HLS流地址") private String https_hls; @Schema(description = "Websocket-HLS流地址") private String ws_hls; @Schema(description = "Websockets-HLS流地址") private String wss_hls; @Schema(description = "HTTP-TS流地址") private String ts; @Schema(description = "HTTPS-TS流地址") private String https_ts; @Schema(description = "Websocket-TS流地址") private String ws_ts; @Schema(description = "Websockets-TS流地址") private String wss_ts; @Schema(description = "RTMP流地址") private String rtmp; @Schema(description = "RTMPS流地址") private String rtmps; @Schema(description = "RTSP流地址") private String rtsp; @Schema(description = "RTSPS流地址") private String rtsps; @Schema(description = "RTC流地址") private String rtc; @Schema(description = "RTCS流地址") private String rtcs; @Schema(description = "流媒体ID") private String mediaServerId; @Schema(description = "流编码信息") private MediaInfo mediaInfo; @Schema(description = "开始时间") private String startTime; @Schema(description = "结束时间") private String endTime; @Schema(description = "时长(回放时使用)") private Double duration; @Schema(description = "文件下载地址(录像下载使用)") private DownloadFileInfo downLoadFilePath; @Schema(description = "转码后的视频流") private StreamContent transcodeStream; private double progress; @Schema(description = "拉流代理返回的KEY") private String key; @Schema(description = "使用的WVP ID") private String serverId; public StreamContent(StreamInfo streamInfo) { if (streamInfo == null) { return; } this.app = streamInfo.getApp(); this.stream = streamInfo.getStream(); if (streamInfo.getFlv() != null) { this.flv = streamInfo.getFlv().getUrl(); } if (streamInfo.getHttps_flv() != null) { this.https_flv = streamInfo.getHttps_flv().getUrl(); } if (streamInfo.getWs_flv() != null) { this.ws_flv = streamInfo.getWs_flv().getUrl(); } if (streamInfo.getWss_flv() != null) { this.wss_flv = streamInfo.getWss_flv().getUrl(); } if (streamInfo.getFmp4() != null) { this.fmp4 = streamInfo.getFmp4().getUrl(); } if (streamInfo.getHttps_fmp4() != null) { this.https_fmp4 = streamInfo.getHttps_fmp4().getUrl(); } if (streamInfo.getWs_fmp4() != null) { this.ws_fmp4 = streamInfo.getWs_fmp4().getUrl(); } if (streamInfo.getWss_fmp4() != null) { this.wss_fmp4 = streamInfo.getWss_fmp4().getUrl(); } if (streamInfo.getHls() != null) { this.hls = streamInfo.getHls().getUrl(); } if (streamInfo.getHttps_hls() != null) { this.https_hls = streamInfo.getHttps_hls().getUrl(); } if (streamInfo.getWs_hls() != null) { this.ws_hls = streamInfo.getWs_hls().getUrl(); } if (streamInfo.getWss_hls() != null) { this.wss_hls = streamInfo.getWss_hls().getUrl(); } if (streamInfo.getTs() != null) { this.ts = streamInfo.getTs().getUrl(); } if (streamInfo.getHttps_ts() != null) { this.https_ts = streamInfo.getHttps_ts().getUrl(); } if (streamInfo.getWs_ts() != null) { this.ws_ts = streamInfo.getWs_ts().getUrl(); } if (streamInfo.getRtmp() != null) { this.rtmp = streamInfo.getRtmp().getUrl(); } if (streamInfo.getRtmps() != null) { this.rtmps = streamInfo.getRtmps().getUrl(); } if (streamInfo.getRtsp() != null) { this.rtsp = streamInfo.getRtsp().getUrl(); } if (streamInfo.getRtsps() != null) { this.rtsps = streamInfo.getRtsps().getUrl(); } if (streamInfo.getRtc() != null) { this.rtc = streamInfo.getRtc().getUrl(); } if (streamInfo.getRtcs() != null) { this.rtcs = streamInfo.getRtcs().getUrl(); } if (streamInfo.getMediaServer() != null) { this.mediaServerId = streamInfo.getMediaServer().getId(); } this.mediaInfo = streamInfo.getMediaInfo(); this.startTime = streamInfo.getStartTime(); this.endTime = streamInfo.getEndTime(); this.progress = streamInfo.getProgress(); this.duration = streamInfo.getDuration(); this.key = streamInfo.getKey(); this.serverId = streamInfo.getServerId(); if (streamInfo.getDownLoadFilePath() != null) { this.downLoadFilePath = streamInfo.getDownLoadFilePath(); } if (streamInfo.getTranscodeStream() != null) { this.transcodeStream = new StreamContent(streamInfo.getTranscodeStream()); } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/vmanager/bean/SystemConfigInfo.java ================================================ package com.genersoft.iot.vmp.vmanager.bean; import com.genersoft.iot.vmp.common.VersionPo; import com.genersoft.iot.vmp.conf.SipConfig; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.jt1078.config.JT1078Config; import lombok.Data; @Data public class SystemConfigInfo { private int serverPort; private SipConfig sip; private UserSetting addOn; private VersionPo version; private JT1078Config jt1078Config; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/vmanager/bean/TablePageInfo.java ================================================ package com.genersoft.iot.vmp.vmanager.bean; import java.util.ArrayList; import java.util.List; public class TablePageInfo { //当前页 private int pageNum; //每页的数量 private int pageSize; //当前页的数量 private int size; //总页数 private int pages; //总数 private int total; private List resultData; private List list; public TablePageInfo(List resultData) { this.resultData = resultData; } public TablePageInfo() { } public void startPage(int page, int count) { if (count <= 0) count = 10; if (page <= 0) page = 1; this.pageNum = page; this.pageSize = count; this.total = resultData.size(); this.pages = total % count == 0 ? total / count : total / count + 1; int fromIndx = (page - 1) * count; if (fromIndx > this.total - 1) { this.list = new ArrayList<>(); this.size = 0; return; } int toIndx = page * count; if (toIndx > this.total) { toIndx = this.total; } this.list = this.resultData.subList(fromIndx, toIndx); this.size = this.list.size(); } public int getPageNum() { return pageNum; } public void setPageNum(int pageNum) { this.pageNum = pageNum; } public int getPageSize() { return pageSize; } public void setPageSize(int pageSize) { this.pageSize = pageSize; } public int getSize() { return size; } public void setSize(int size) { this.size = size; } public int getPages() { return pages; } public void setPages(int pages) { this.pages = pages; } public int getTotal() { return total; } public void setTotal(int total) { this.total = total; } public List getList() { return list; } public void setList(List list) { this.list = list; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/vmanager/bean/WVPResult.java ================================================ package com.genersoft.iot.vmp.vmanager.bean; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.Setter; @Setter @Getter @Schema(description = "统一返回结果") public class WVPResult implements Cloneable{ public WVPResult() { } public WVPResult(int code, String msg, T data) { this.code = code; this.msg = msg; this.data = data; } @Schema(description = "错误码,0为成功") private int code; @Schema(description = "描述,错误时描述错误原因") private String msg; @Schema(description = "数据") private T data; public static WVPResult success(T t, String msg) { return new WVPResult<>(ErrorCode.SUCCESS.getCode(), msg, t); } public static WVPResult success() { return new WVPResult<>(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), null); } public static WVPResult success(T t) { return success(t, ErrorCode.SUCCESS.getMsg()); } public static WVPResult fail(int code, String msg) { return new WVPResult<>(code, msg, null); } public static WVPResult fail(ErrorCode errorCode) { return fail(errorCode.getCode(), errorCode.getMsg()); } @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/vmanager/cloudRecord/CloudRecordController.java ================================================ package com.genersoft.iot.vmp.vmanager.cloudRecord; import com.alibaba.fastjson2.JSONArray; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.conf.security.JwtUtils; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.service.IMediaServerService; import com.genersoft.iot.vmp.service.ICloudRecordService; import com.genersoft.iot.vmp.service.bean.CloudRecordItem; import com.genersoft.iot.vmp.service.bean.DownloadFileInfo; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.service.bean.InviteErrorCode; import com.genersoft.iot.vmp.streamPush.bean.BatchRemoveParam; import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.utils.HttpUtils; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.StreamContent; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import com.genersoft.iot.vmp.vmanager.cloudRecord.bean.CloudRecordUrl; import com.github.pagehelper.PageInfo; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.ObjectUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import org.springframework.web.context.request.async.DeferredResult; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Calendar; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @SuppressWarnings("rawtypes") @Tag(name = "云端录像接口") @Slf4j @RestController @RequestMapping("/api/cloud/record") public class CloudRecordController { @Autowired private ICloudRecordService cloudRecordService; @Autowired private IMediaServerService mediaServerService; @Autowired private UserSetting userSetting; @ResponseBody @GetMapping("/date/list") @Operation(summary = "查询存在云端录像的日期", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "app", description = "应用名", required = true) @Parameter(name = "stream", description = "流ID", required = true) @Parameter(name = "year", description = "年,置空则查询当年", required = false) @Parameter(name = "month", description = "月,置空则查询当月", required = false) @Parameter(name = "mediaServerId", description = "流媒体ID,置空则查询全部", required = false) public List openRtpServer( @RequestParam(required = true) String app, @RequestParam(required = true) String stream, @RequestParam(required = false) Integer year, @RequestParam(required = false) Integer month, @RequestParam(required = false) String mediaServerId ) { log.info("[云端录像] 查询存在云端录像的日期 app->{}, stream->{}, mediaServerId->{}, year->{}, month->{}", app, stream, mediaServerId, year, month); Calendar calendar = Calendar.getInstance(); if (ObjectUtils.isEmpty(year)) { year = calendar.get(Calendar.YEAR); } if (ObjectUtils.isEmpty(month)) { month = calendar.get(Calendar.MONTH) + 1; } List mediaServers; if (!ObjectUtils.isEmpty(mediaServerId)) { mediaServers = new ArrayList<>(); MediaServer mediaServer = mediaServerService.getOne(mediaServerId); if (mediaServer == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到流媒体: " + mediaServerId); } mediaServers.add(mediaServer); } else { mediaServers = mediaServerService.getAllOnlineList(); } if (mediaServers.isEmpty()) { return new ArrayList<>(); } return cloudRecordService.getDateList(app, stream, year, month, mediaServers); } @ResponseBody @GetMapping("/list") @Operation(summary = "分页查询云端录像", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "query", description = "检索内容", required = false) @Parameter(name = "app", description = "应用名", required = false) @Parameter(name = "stream", description = "流ID", required = false) @Parameter(name = "page", description = "当前页", required = true) @Parameter(name = "count", description = "每页查询数量", required = true) @Parameter(name = "startTime", description = "开始时间(yyyy-MM-dd HH:mm:ss)", required = false) @Parameter(name = "endTime", description = "结束时间(yyyy-MM-dd HH:mm:ss)", required = false) @Parameter(name = "mediaServerId", description = "流媒体ID,置空则查询全部流媒体", required = false) @Parameter(name = "callId", description = "每次录像的唯一标识,置空则查询全部流媒体", required = false) @Parameter(name = "ascOrder", description = "是否升序排序, 升序: true, 降序: false", required = false) public PageInfo openRtpServer(@RequestParam(required = false) String query, @RequestParam(required = false) String app, @RequestParam(required = false) String stream, @RequestParam int page, @RequestParam int count, @RequestParam(required = false) String startTime, @RequestParam(required = false) String endTime, @RequestParam(required = false) String mediaServerId, @RequestParam(required = false) String callId, @RequestParam(required = false) Boolean ascOrder ) { log.info("[云端录像] 查询 app->{}, stream->{}, mediaServerId->{}, page->{}, count->{}, startTime->{}, endTime->{}, callId->{}", app, stream, mediaServerId, page, count, startTime, endTime, callId); List mediaServers; if (!ObjectUtils.isEmpty(mediaServerId)) { mediaServers = new ArrayList<>(); MediaServer mediaServer = mediaServerService.getOne(mediaServerId); if (mediaServer == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到流媒体: " + mediaServerId); } mediaServers.add(mediaServer); } else { mediaServers = null; } if (query != null && ObjectUtils.isEmpty(query.trim())) { query = null; } if (app != null && ObjectUtils.isEmpty(app.trim())) { app = null; } if (stream != null && ObjectUtils.isEmpty(stream.trim())) { stream = null; } if (startTime != null && ObjectUtils.isEmpty(startTime.trim())) { startTime = null; } if (endTime != null && ObjectUtils.isEmpty(endTime.trim())) { endTime = null; } if (callId != null && ObjectUtils.isEmpty(callId.trim())) { callId = null; } return cloudRecordService.getList(page, count, query, app, stream, startTime, endTime, mediaServers, callId, ascOrder); } @ResponseBody @GetMapping("/task/add") @Operation(summary = "添加合并任务") @Parameter(name = "app", description = "应用名", required = false) @Parameter(name = "stream", description = "流ID", required = false) @Parameter(name = "mediaServerId", description = "流媒体ID", required = false) @Parameter(name = "startTime", description = "鉴权ID", required = false) @Parameter(name = "endTime", description = "鉴权ID", required = false) @Parameter(name = "callId", description = "鉴权ID", required = false) @Parameter(name = "remoteHost", description = "返回地址时的远程地址", required = false) public String addTask(HttpServletRequest request, @RequestParam(required = false) String app, @RequestParam(required = false) String stream, @RequestParam(required = false) String mediaServerId, @RequestParam(required = false) String startTime, @RequestParam(required = false) String endTime, @RequestParam(required = false) String callId, @RequestParam(required = false) String remoteHost) { MediaServer mediaServer; if (mediaServerId == null) { mediaServer = mediaServerService.getDefaultMediaServer(); } else { mediaServer = mediaServerService.getOne(mediaServerId); } if (mediaServer == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到可用的流媒体"); } else { if (remoteHost == null) { remoteHost = request.getScheme() + "://" + mediaServer.getIp() + ":" + mediaServer.getRecordAssistPort(); } } return cloudRecordService.addTask(app, stream, mediaServer, startTime, endTime, callId, remoteHost, mediaServerId != null); } @ResponseBody @GetMapping("/task/list") @Operation(summary = "查询合并任务") @Parameter(name = "taskId", description = "任务Id", required = false) @Parameter(name = "mediaServerId", description = "流媒体ID", required = false) @Parameter(name = "isEnd", description = "是否结束", required = false) public JSONArray queryTaskList(HttpServletRequest request, @RequestParam(required = false) String app, @RequestParam(required = false) String stream, @RequestParam(required = false) String callId, @RequestParam(required = false) String taskId, @RequestParam(required = false) String mediaServerId, @RequestParam(required = false) Boolean isEnd) { if (ObjectUtils.isEmpty(mediaServerId)) { mediaServerId = null; } return cloudRecordService.queryTask(app, stream, callId, taskId, mediaServerId, isEnd, request.getScheme()); } @ResponseBody @GetMapping("/collect/add") @Operation(summary = "添加收藏") @Parameter(name = "app", description = "应用名", required = false) @Parameter(name = "stream", description = "流ID", required = false) @Parameter(name = "mediaServerId", description = "流媒体ID", required = false) @Parameter(name = "startTime", description = "鉴权ID", required = false) @Parameter(name = "endTime", description = "鉴权ID", required = false) @Parameter(name = "callId", description = "鉴权ID", required = false) @Parameter(name = "recordId", description = "录像记录的ID,用于精准收藏一个视频文件", required = false) public int addCollect(@RequestParam(required = false) String app, @RequestParam(required = false) String stream, @RequestParam(required = false) String mediaServerId, @RequestParam(required = false) String startTime, @RequestParam(required = false) String endTime, @RequestParam(required = false) String callId, @RequestParam(required = false) Integer recordId) { log.info("[云端录像] 添加收藏,app={},stream={},mediaServerId={},startTime={},endTime={},callId={},recordId={}", app, stream, mediaServerId, startTime, endTime, callId, recordId); if (recordId != null) { return cloudRecordService.changeCollectById(recordId, true); } else { return cloudRecordService.changeCollect(true, app, stream, mediaServerId, startTime, endTime, callId); } } @ResponseBody @GetMapping("/collect/delete") @Operation(summary = "移除收藏") @Parameter(name = "app", description = "应用名", required = false) @Parameter(name = "stream", description = "流ID", required = false) @Parameter(name = "mediaServerId", description = "流媒体ID", required = false) @Parameter(name = "startTime", description = "鉴权ID", required = false) @Parameter(name = "endTime", description = "鉴权ID", required = false) @Parameter(name = "callId", description = "鉴权ID", required = false) @Parameter(name = "recordId", description = "录像记录的ID,用于精准精准移除一个视频文件的收藏", required = false) public int deleteCollect(@RequestParam(required = false) String app, @RequestParam(required = false) String stream, @RequestParam(required = false) String mediaServerId, @RequestParam(required = false) String startTime, @RequestParam(required = false) String endTime, @RequestParam(required = false) String callId, @RequestParam(required = false) Integer recordId) { log.info("[云端录像] 移除收藏,app={},stream={},mediaServerId={},startTime={},endTime={},callId={},recordId={}", app, stream, mediaServerId, startTime, endTime, callId, recordId); if (recordId != null) { return cloudRecordService.changeCollectById(recordId, false); } else { return cloudRecordService.changeCollect(false, app, stream, mediaServerId, startTime, endTime, callId); } } @ResponseBody @GetMapping("/play/path") @Operation(summary = "获取播放地址") @Parameter(name = "recordId", description = "录像记录的ID", required = true) public DownloadFileInfo getPlayUrlPath(@RequestParam(required = true) Integer recordId) { return cloudRecordService.getPlayUrlPath(recordId); } @ResponseBody @GetMapping("/loadRecord") @Operation(summary = "加载录像文件形成播放地址") @Parameter(name = "app", description = "应用名", required = true) @Parameter(name = "stream", description = "流ID", required = true) @Parameter(name = "cloudRecordId", description = "云端录像ID", required = true) public DeferredResult> loadRecord( HttpServletRequest request, @RequestParam(required = true) String app, @RequestParam(required = true) String stream, @RequestParam(required = true) int cloudRecordId ) { DeferredResult> result = new DeferredResult<>(); result.onTimeout(()->{ log.info("[加载录像文件超时] app={}, stream={}, cloudRecordId={}", app, stream, cloudRecordId); WVPResult wvpResult = new WVPResult<>(); wvpResult.setCode(ErrorCode.ERROR100.getCode()); wvpResult.setMsg("加载录像文件超时"); result.setResult(wvpResult); }); ErrorCallback callback = (code, msg, streamInfo) -> { WVPResult wvpResult = new WVPResult<>(); if (code == InviteErrorCode.SUCCESS.getCode()) { wvpResult.setCode(ErrorCode.SUCCESS.getCode()); wvpResult.setMsg(ErrorCode.SUCCESS.getMsg()); if (streamInfo != null) { if (userSetting.getUseSourceIpAsStreamIp()) { streamInfo=streamInfo.clone();//深拷贝 String host; try { URL url=new URL(request.getRequestURL().toString()); host=url.getHost(); } catch (MalformedURLException e) { host=request.getLocalAddr(); } streamInfo.changeStreamIp(host); } if (!org.springframework.util.ObjectUtils.isEmpty(streamInfo.getMediaServer().getTranscodeSuffix()) && !"null".equalsIgnoreCase(streamInfo.getMediaServer().getTranscodeSuffix())) { streamInfo.setStream(streamInfo.getStream() + "_" + streamInfo.getMediaServer().getTranscodeSuffix()); } wvpResult.setData(new StreamContent(streamInfo)); }else { wvpResult.setCode(code); wvpResult.setMsg(msg); } }else { wvpResult.setCode(code); wvpResult.setMsg(msg); } result.setResult(wvpResult); }; cloudRecordService.loadMP4File(app, stream, cloudRecordId, callback); return result; } @ResponseBody @GetMapping("/seek") @Operation(summary = "定位录像播放到制定位置") @Parameter(name = "mediaServerId", description = "使用的节点Id", required = true) @Parameter(name = "app", description = "应用名", required = true) @Parameter(name = "stream", description = "流ID", required = true) @Parameter(name = "seek", description = "要定位的时间位置,从录像开始的时间算起", required = true) public void seekRecord( @RequestParam(required = true) String mediaServerId, @RequestParam(required = true) String app, @RequestParam(required = true) String stream, @RequestParam(required = true) Double seek, @RequestParam(required = false) String schema ) { if (schema == null) { schema = "ts"; } cloudRecordService.seekRecord(mediaServerId, app, stream, seek, schema); } @ResponseBody @GetMapping("/speed") @Operation(summary = "设置录像播放速度") @Parameter(name = "mediaServerId", description = "使用的节点Id", required = true) @Parameter(name = "app", description = "应用名", required = true) @Parameter(name = "stream", description = "流ID", required = true) @Parameter(name = "speed", description = "要设置的录像倍速", required = true) public void setRecordSpeed( @RequestParam(required = true) String mediaServerId, @RequestParam(required = true) String app, @RequestParam(required = true) String stream, @RequestParam(required = true) Integer speed, @RequestParam(required = false) String schema ) { if (schema == null) { schema = "ts"; } cloudRecordService.setRecordSpeed(mediaServerId, app, stream, speed, schema); } @ResponseBody @DeleteMapping("/delete") @Operation(summary = "删除录像文件") @Parameter(name = "ids", description = "文件ID集合", required = true) public void deleteFileByIds(@RequestBody BatchRemoveParam ids) { cloudRecordService.deleteFileByIds(ids.getIds()); } @ResponseBody @GetMapping("/download/zip") public void downloadZipFileFromUrl(HttpServletResponse response, Integer[] ids) { log.info("[下载指定录像文件的压缩包] 查询 ids->{}", ids); List arrayList = new ArrayList<>(List.of(ids)); List cloudRecordItemList = cloudRecordService.getUrlListByIds(arrayList); if (ObjectUtils.isEmpty(cloudRecordItemList)) { log.warn("[下载指定录像文件的压缩包] 未找到录像文件,ids->{}", ids); return; } // 设置响应头 response.setContentType("application/zip"); response.setCharacterEncoding("UTF-8"); response.setHeader("Content-Disposition", "attachment; filename=record_" + System.currentTimeMillis() + ".zip"); try (ZipOutputStream zos = new ZipOutputStream(response.getOutputStream())) { for (CloudRecordUrl recordUrl : cloudRecordItemList) { try { zos.putNextEntry(new ZipEntry(recordUrl.getFileName())); boolean downloadSuccess = HttpUtils.downLoadFile(recordUrl.getDownloadUrl(), zos); if (!downloadSuccess) { log.warn("[下载指定录像文件的压缩包] 下载文件失败: {}", recordUrl.getDownloadUrl()); zos.closeEntry(); continue; } // try (FileInputStream fis = new FileInputStream(recordUrl.getFilePath())) { // byte[] buf = new byte[8192]; // 8KB 缓冲区,提高性能 // int len; // while ((len = fis.read(buf)) != -1) { // zos.write(buf, 0, len); // } // } zos.closeEntry(); } catch (Exception e) { log.error("[下载指定录像文件的压缩包] 处理文件失败: {}, 错误: {}", recordUrl.getFileName(), e.getMessage()); // 继续处理下一个文件 } } } catch (IOException e) { log.error("[下载指定录像文件的压缩包] 创建压缩包失败,查询 ids->{}", ids, e); } } /************************* 以下这些接口只适合wvp和zlm部署在同一台服务器的情况,且wvp只有一个zlm节点的情况 ***************************************/ /** * 下载指定录像文件的压缩包 * @param query 检索内容 * @param app 应用名 * @param stream 流ID * @param startTime 开始时间(yyyy-MM-dd HH:mm:ss) * @param endTime 结束时间(yyyy-MM-dd HH:mm:ss) * @param mediaServerId 流媒体ID,置空则查询全部流媒体 * @param callId 每次录像的唯一标识,置空则查询全部流媒体 * @param ids 指定的Id */ @ResponseBody @GetMapping("/zip") public void downloadZipFile(HttpServletResponse response, @RequestParam(required = false) String query, @RequestParam(required = false) String app, @RequestParam(required = false) String stream, @RequestParam(required = false) String startTime, @RequestParam(required = false) String endTime, @RequestParam(required = false) String mediaServerId, @RequestParam(required = false) String callId, @RequestParam(required = false) List ids ) { log.info("[下载指定录像文件的压缩包] 查询 app->{}, stream->{}, mediaServerId->{}, startTime->{}, endTime->{}, callId->{}", app, stream, mediaServerId, startTime, endTime, callId); List mediaServers; if (!ObjectUtils.isEmpty(mediaServerId)) { mediaServers = new ArrayList<>(); MediaServer mediaServer = mediaServerService.getOne(mediaServerId); if (mediaServer == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到流媒体: " + mediaServerId); } mediaServers.add(mediaServer); } else { mediaServers = mediaServerService.getAll(); } if (mediaServers.isEmpty()) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "当前无流媒体"); } if (query != null && ObjectUtils.isEmpty(query.trim())) { query = null; } if (app != null && ObjectUtils.isEmpty(app.trim())) { app = null; } if (stream != null && ObjectUtils.isEmpty(stream.trim())) { stream = null; } if (startTime != null && ObjectUtils.isEmpty(startTime.trim())) { startTime = null; } if (endTime != null && ObjectUtils.isEmpty(endTime.trim())) { endTime = null; } if (callId != null && ObjectUtils.isEmpty(callId.trim())) { callId = null; } // 设置响应头 response.setContentType("application/zip"); response.setCharacterEncoding("UTF-8"); if (stream != null && callId != null) { response.setHeader("Content-Disposition", "attachment;filename=" + stream + "_" + callId + ".zip"); } else { response.setHeader("Content-Disposition", "attachment;filename=cloud_record_" + System.currentTimeMillis() + ".zip"); } List cloudRecordItemList = cloudRecordService.getAllList(query, app, stream, startTime, endTime, mediaServers, callId, ids); if (ObjectUtils.isEmpty(cloudRecordItemList)) { return; } try (ZipOutputStream zos = new ZipOutputStream(response.getOutputStream())) { for (CloudRecordItem cloudRecordItem : cloudRecordItemList) { try { String fileName = DateUtil.timestampMsToUrlToyyyy_MM_dd_HH_mm_ss((long)cloudRecordItem.getStartTime()) + ".mp4"; zos.putNextEntry(new ZipEntry(fileName)); File file = new File(cloudRecordItem.getFilePath()); if (!file.exists() || file.isDirectory()) { log.warn("[下载指定录像文件的压缩包] 文件不存在或为目录: {}", cloudRecordItem.getFilePath()); zos.closeEntry(); continue; } try (FileInputStream fis = new FileInputStream(cloudRecordItem.getFilePath())) { byte[] buf = new byte[8192]; // 8KB 缓冲区,提高性能 int len; while ((len = fis.read(buf)) != -1) { zos.write(buf, 0, len); } } zos.closeEntry(); log.debug("[下载指定录像文件的压缩包] 成功添加文件: {}", fileName); } catch (Exception e) { log.error("[下载指定录像文件的压缩包] 处理文件失败: {}, 错误: {}", cloudRecordItem.getFilePath(), e.getMessage()); // 继续处理下一个文件 } } } catch (IOException e) { log.error("[下载指定录像文件的压缩包] 失败: 查询 app->{}, stream->{}, mediaServerId->{}, startTime->{}, endTime->{}, callId->{}", app, stream, mediaServerId, startTime, endTime, callId, e); } } /** * * @param query 检索内容 * @param app 应用名 * @param stream 流ID * @param startTime 开始时间(yyyy-MM-dd HH:mm:ss) * @param endTime 结束时间(yyyy-MM-dd HH:mm:ss) * @param mediaServerId 流媒体ID,置空则查询全部流媒体 * @param callId 每次录像的唯一标识,置空则查询全部流媒体 * @param remoteHost 拼接播放地址时使用的远程地址 */ @ResponseBody @GetMapping("/list-url") @Operation(summary = "分页查询云端录像", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "query", description = "检索内容", required = false) @Parameter(name = "app", description = "应用名", required = false) @Parameter(name = "stream", description = "流ID", required = false) @Parameter(name = "page", description = "当前页", required = true) @Parameter(name = "count", description = "每页查询数量", required = true) @Parameter(name = "startTime", description = "开始时间(yyyy-MM-dd HH:mm:ss)", required = false) @Parameter(name = "endTime", description = "结束时间(yyyy-MM-dd HH:mm:ss)", required = false) @Parameter(name = "mediaServerId", description = "流媒体ID,置空则查询全部流媒体", required = false) @Parameter(name = "callId", description = "每次录像的唯一标识,置空则查询全部流媒体", required = false) public PageInfo getListWithUrl(HttpServletRequest request, @RequestParam(required = false) String query, @RequestParam(required = false) String app, @RequestParam(required = false) String stream, @RequestParam int page, @RequestParam int count, @RequestParam(required = false) String startTime, @RequestParam(required = false) String endTime, @RequestParam(required = false) String mediaServerId, @RequestParam(required = false) String callId, @RequestParam(required = false) String remoteHost ) { log.info("[云端录像] 查询URL app->{}, stream->{}, mediaServerId->{}, page->{}, count->{}, startTime->{}, endTime->{}, callId->{}", app, stream, mediaServerId, page, count, startTime, endTime, callId); List mediaServers; if (!ObjectUtils.isEmpty(mediaServerId)) { mediaServers = new ArrayList<>(); MediaServer mediaServer = mediaServerService.getOne(mediaServerId); if (mediaServer == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到流媒体: " + mediaServerId); } mediaServers.add(mediaServer); } else { mediaServers = null; } if (query != null && ObjectUtils.isEmpty(query.trim())) { query = null; } if (app != null && ObjectUtils.isEmpty(app.trim())) { app = null; } if (stream != null && ObjectUtils.isEmpty(stream.trim())) { stream = null; } if (startTime != null && ObjectUtils.isEmpty(startTime.trim())) { startTime = null; } if (endTime != null && ObjectUtils.isEmpty(endTime.trim())) { endTime = null; } if (callId != null && ObjectUtils.isEmpty(callId.trim())) { callId = null; } MediaServer mediaServer = mediaServerService.getDefaultMediaServer(); if (mediaServer == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到流媒体节点"); } if (remoteHost == null) { remoteHost = request.getScheme() + "://" + request.getLocalAddr() + ":" + (request.getScheme().equals("https") ? mediaServer.getHttpSSlPort() : mediaServer.getHttpPort()); } PageInfo cloudRecordItemPageInfo = cloudRecordService.getList(page, count, query, app, stream, startTime, endTime, mediaServers, callId, null); PageInfo cloudRecordUrlPageInfo = new PageInfo<>(); if (!ObjectUtils.isEmpty(cloudRecordItemPageInfo)) { cloudRecordUrlPageInfo.setPageNum(cloudRecordItemPageInfo.getPageNum()); cloudRecordUrlPageInfo.setPageSize(cloudRecordItemPageInfo.getPageSize()); cloudRecordUrlPageInfo.setSize(cloudRecordItemPageInfo.getSize()); cloudRecordUrlPageInfo.setEndRow(cloudRecordItemPageInfo.getEndRow()); cloudRecordUrlPageInfo.setStartRow(cloudRecordItemPageInfo.getStartRow()); cloudRecordUrlPageInfo.setPages(cloudRecordItemPageInfo.getPages()); cloudRecordUrlPageInfo.setPrePage(cloudRecordItemPageInfo.getPrePage()); cloudRecordUrlPageInfo.setNextPage(cloudRecordItemPageInfo.getNextPage()); cloudRecordUrlPageInfo.setIsFirstPage(cloudRecordItemPageInfo.isIsFirstPage()); cloudRecordUrlPageInfo.setIsLastPage(cloudRecordItemPageInfo.isIsLastPage()); cloudRecordUrlPageInfo.setHasPreviousPage(cloudRecordItemPageInfo.isHasPreviousPage()); cloudRecordUrlPageInfo.setHasNextPage(cloudRecordItemPageInfo.isHasNextPage()); cloudRecordUrlPageInfo.setNavigatePages(cloudRecordItemPageInfo.getNavigatePages()); cloudRecordUrlPageInfo.setNavigateFirstPage(cloudRecordItemPageInfo.getNavigateFirstPage()); cloudRecordUrlPageInfo.setNavigateLastPage(cloudRecordItemPageInfo.getNavigateLastPage()); cloudRecordUrlPageInfo.setNavigatepageNums(cloudRecordItemPageInfo.getNavigatepageNums()); cloudRecordUrlPageInfo.setTotal(cloudRecordItemPageInfo.getTotal()); List cloudRecordUrlList = new ArrayList<>(cloudRecordItemPageInfo.getList().size()); List cloudRecordItemList = cloudRecordItemPageInfo.getList(); for (CloudRecordItem cloudRecordItem : cloudRecordItemList) { CloudRecordUrl cloudRecordUrl = new CloudRecordUrl(); cloudRecordUrl.setId(cloudRecordItem.getId()); cloudRecordUrl.setDownloadUrl(remoteHost + "/index/api/downloadFile?file_path=" + cloudRecordItem.getFilePath() + "&save_name=" + cloudRecordItem.getStream() + "_" + cloudRecordItem.getCallId() + "_" + DateUtil.timestampMsToUrlToyyyy_MM_dd_HH_mm_ss((long)cloudRecordItem.getStartTime())); cloudRecordUrl.setPlayUrl(remoteHost + "/index/api/downloadFile?file_path=" + cloudRecordItem.getFilePath()); cloudRecordUrlList.add(cloudRecordUrl); } cloudRecordUrlPageInfo.setList(cloudRecordUrlList); } return cloudRecordUrlPageInfo; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/vmanager/cloudRecord/bean/CloudRecordUrl.java ================================================ package com.genersoft.iot.vmp.vmanager.cloudRecord.bean; import lombok.Getter; import lombok.Setter; @Setter @Getter public class CloudRecordUrl { private String filePath; private String playUrl; private String downloadUrl; private String fileName; private int id; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/vmanager/log/LogController.java ================================================ package com.genersoft.iot.vmp.vmanager.log; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.conf.security.JwtUtils; import com.genersoft.iot.vmp.service.ILogService; import com.genersoft.iot.vmp.service.bean.LogFileInfo; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; import org.apache.commons.compress.utils.IOUtils; import org.apache.commons.lang3.ObjectUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; import jakarta.servlet.ServletOutputStream; import jakarta.servlet.http.HttpServletResponse; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.util.List; @SuppressWarnings("rawtypes") @Tag(name = "日志文件查询接口") @Slf4j @RestController @RequestMapping("/api/log") public class LogController { @Autowired private ILogService logService; @ResponseBody @GetMapping("/list") @Operation(summary = "分页查询日志文件", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "query", description = "检索内容", required = false) @Parameter(name = "startTime", description = "开始时间(yyyy-MM-dd HH:mm:ss)", required = false) @Parameter(name = "endTime", description = "结束时间(yyyy-MM-dd HH:mm:ss)", required = false) public List queryList(@RequestParam(required = false) String query, @RequestParam(required = false) String startTime, @RequestParam(required = false) String endTime ) { if (ObjectUtils.isEmpty(query)) { query = null; } if (ObjectUtils.isEmpty(startTime)) { startTime = null; } if (ObjectUtils.isEmpty(endTime)) { endTime = null; } return logService.queryList(query, startTime, endTime); } /** * 下载指定日志文件 */ @ResponseBody @GetMapping("/file/{fileName}") public void downloadFile(HttpServletResponse response, @PathVariable String fileName) { try { File file = logService.getFileByName(fileName); if (file == null || !file.exists() || !file.isFile()) { throw new ControllerException(ErrorCode.ERROR400); } final InputStream in = Files.newInputStream(file.toPath()); response.setContentType(MediaType.TEXT_PLAIN_VALUE); ServletOutputStream outputStream = response.getOutputStream(); IOUtils.copy(in, response.getOutputStream()); in.close(); outputStream.close(); } catch (IOException e) { response.setStatus(HttpServletResponse.SC_NO_CONTENT); } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/vmanager/ps/PsController.java ================================================ package com.genersoft.iot.vmp.vmanager.ps; import com.genersoft.iot.vmp.common.VideoManagerConstants; import com.genersoft.iot.vmp.common.enums.MediaApp; import com.genersoft.iot.vmp.conf.DynamicTask; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.conf.security.JwtUtils; import com.genersoft.iot.vmp.gb28181.bean.SendRtpInfo; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.event.hook.Hook; import com.genersoft.iot.vmp.media.event.hook.HookSubscribe; import com.genersoft.iot.vmp.media.event.hook.HookType; import com.genersoft.iot.vmp.media.service.IMediaServerService; import com.genersoft.iot.vmp.service.IReceiveRtpServerService; import com.genersoft.iot.vmp.service.ISendRtpServerService; import com.genersoft.iot.vmp.service.bean.InviteErrorCode; import com.genersoft.iot.vmp.service.bean.RTPServerParam; import com.genersoft.iot.vmp.service.bean.SSRCInfo; import com.genersoft.iot.vmp.utils.redis.RedisUtil; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.OtherPsSendInfo; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; import okhttp3.OkHttpClient; import okhttp3.Request; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.web.bind.annotation.*; import java.io.IOException; import java.util.List; import java.util.UUID; import java.util.concurrent.TimeUnit; @SuppressWarnings("rawtypes") @Tag(name = "第三方PS服务对接") @Slf4j @RestController @RequestMapping("/api/ps") public class PsController { @Autowired private HookSubscribe hookSubscribe; @Autowired private IMediaServerService mediaServerService; @Autowired private ISendRtpServerService sendRtpServerService; @Autowired private IReceiveRtpServerService receiveRtpServerService; @Autowired private UserSetting userSetting; @Autowired private DynamicTask dynamicTask; @Autowired private RedisTemplate redisTemplate; @GetMapping(value = "/receive/open") @ResponseBody @Operation(summary = "开启收流和获取发流信息", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "isSend", description = "是否发送,false时只开启收流, true同时返回推流信息", required = true) @Parameter(name = "callId", description = "整个过程的唯一标识,为了与后续接口关联", required = true) @Parameter(name = "ssrc", description = "来源流的SSRC,不传则不校验来源ssrc", required = false) @Parameter(name = "stream", description = "形成的流的ID", required = true) @Parameter(name = "tcpMode", description = "收流模式, 0为UDP, 1为TCP被动", required = true) @Parameter(name = "callBack", description = "回调地址,如果收流超时会通道回调通知,回调为get请求,参数为callId", required = true) public OtherPsSendInfo openRtpServer(Boolean isSend, @RequestParam(required = false)String ssrc, String callId, String stream, Integer tcpMode, String callBack) { log.info("[第三方PS服务对接->开启收流和获取发流信息] isSend->{}, ssrc->{}, callId->{}, stream->{}, tcpMode->{}, callBack->{}", isSend, ssrc, callId, stream, tcpMode==0?"UDP":"TCP被动", callBack); MediaServer mediaServer = mediaServerService.getDefaultMediaServer(); if (mediaServer == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(),"没有可用的MediaServer"); } if (stream == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(),"stream参数不可为空"); } if (isSend != null && isSend && callId == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(),"isSend为true时,CallID不能为空"); } long ssrcInt = 0; if (ssrc != null) { try { ssrcInt = Long.parseLong(ssrc); }catch (NumberFormatException e) { throw new ControllerException(ErrorCode.ERROR100.getCode(),"ssrc格式错误"); } } String receiveKey = VideoManagerConstants.WVP_OTHER_RECEIVE_PS_INFO + userSetting.getServerId() + "_" + callId + "_" + stream; RTPServerParam rtpServerParam = new RTPServerParam(); rtpServerParam.setMediaServer(mediaServer); rtpServerParam.setApp(MediaApp.GB28181); rtpServerParam.setStreamId(stream); rtpServerParam.setSsrc(ssrcInt); rtpServerParam.setTcpMode(tcpMode); int rtpServerPort = receiveRtpServerService.openRTPServer(rtpServerParam, ((code, msg, data) -> { if (callBack == null) { return; } if (code == InviteErrorCode.SUCCESS.getCode()) { log.info("[第三方PS服务对接->开启收流和获取发流信息] 成功回调,callId->{}, data->{}", callId, data); // 将信息写入redis中,以备后用 redisTemplate.delete(receiveKey); OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder(); OkHttpClient client = httpClientBuilder.build(); String url = callBack + "?callId=" + callId; Request request = new Request.Builder().get().url(url).build(); try { client.newCall(request).execute(); } catch (IOException e) { log.error("[第三方PS服务对接->开启收流和获取发流信息] 成功回调 callId->{}, 发送回调失败", callId, e); } } else { log.info("[第三方PS服务对接->开启收流和获取发流信息] 失败回调,callId->{}, code->{}, msg->{}", callId, code, msg); // 将信息写入redis中,以备后用 redisTemplate.delete(receiveKey); } })); if (rtpServerPort == 0) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "获取端口失败"); } OtherPsSendInfo otherPsSendInfo = new OtherPsSendInfo(); otherPsSendInfo.setReceiveIp(mediaServer.getSdpIp()); otherPsSendInfo.setReceivePort(rtpServerPort); otherPsSendInfo.setCallId(callId); otherPsSendInfo.setStream(stream); // 将信息写入redis中,以备后用 redisTemplate.opsForValue().set(receiveKey, otherPsSendInfo); if (isSend != null && isSend) { String key = VideoManagerConstants.WVP_OTHER_SEND_PS_INFO + userSetting.getServerId() + "_" + callId; // 预创建发流信息 int port = sendRtpServerService.getNextPort(mediaServer); otherPsSendInfo.setSendLocalIp(mediaServer.getSdpIp()); otherPsSendInfo.setSendLocalPort(port); // 将信息写入redis中,以备后用 redisTemplate.opsForValue().set(key, otherPsSendInfo, 300, TimeUnit.SECONDS); log.info("[第三方PS服务对接->开启收流和获取发流信息] 结果,callId->{}, {}", callId, otherPsSendInfo); } return otherPsSendInfo; } @GetMapping(value = "/receive/close") @ResponseBody @Operation(summary = "关闭收流", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "stream", description = "流的ID", required = true) public void closeRtpServer(String stream) { log.info("[第三方PS服务对接->关闭收流] stream->{}", stream); MediaServer mediaServerItem = mediaServerService.getDefaultMediaServer(); receiveRtpServerService.closeRTPServer(mediaServerItem, MediaApp.GB28181, stream); String receiveKey = VideoManagerConstants.WVP_OTHER_RECEIVE_PS_INFO + userSetting.getServerId() + "_*_" + stream; List scan = RedisUtil.scan(redisTemplate, receiveKey); if (!scan.isEmpty()) { for (Object key : scan) { // 将信息写入redis中,以备后用 redisTemplate.delete(key); } } } @GetMapping(value = "/send/start") @ResponseBody @Operation(summary = "发送流", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "ssrc", description = "发送流的SSRC", required = true) @Parameter(name = "dstIp", description = "目标收流IP", required = true) @Parameter(name = "dstPort", description = "目标收流端口", required = true) @Parameter(name = "app", description = "待发送应用名", required = true) @Parameter(name = "stream", description = "待发送流Id", required = true) @Parameter(name = "callId", description = "整个过程的唯一标识,不传则使用随机端口发流", required = true) @Parameter(name = "isUdp", description = "是否为UDP", required = true) public void sendRTP(String ssrc, String dstIp, Integer dstPort, String app, String stream, String callId, Boolean isUdp ) { log.info("[第三方PS服务对接->发送流] " + "ssrc->{}, \r\n" + "dstIp->{}, \n" + "dstPort->{}, \n" + "app->{}, \n" + "stream->{}, \n" + "callId->{} \n", ssrc, dstIp, dstPort, app, stream, callId); MediaServer mediaServer = mediaServerService.getDefaultMediaServer(); String key = VideoManagerConstants.WVP_OTHER_SEND_PS_INFO + userSetting.getServerId() + "_" + callId; OtherPsSendInfo sendInfo = (OtherPsSendInfo)redisTemplate.opsForValue().get(key); if (sendInfo == null) { sendInfo = new OtherPsSendInfo(); } sendInfo.setPushApp(app); sendInfo.setPushStream(stream); sendInfo.setPushSSRC(ssrc); SendRtpInfo sendRtpItem = SendRtpInfo.getInstance(app, stream, ssrc, dstIp, dstPort, !isUdp, sendInfo.getSendLocalPort(), null); Boolean streamReady = mediaServerService.isStreamReady(mediaServer, app, stream); if (streamReady) { mediaServerService.startSendRtp(mediaServer, sendRtpItem); log.info("[第三方PS服务对接->发送流] 视频流发流成功,callId->{},param->{}", callId, sendRtpItem); redisTemplate.opsForValue().set(key, sendInfo); }else { log.info("[第三方PS服务对接->发送流] 流不存在,等待流上线,callId->{}", callId); String uuid = UUID.randomUUID().toString(); Hook hook = Hook.getInstance(HookType.on_media_arrival, app, stream, mediaServer.getId()); dynamicTask.startDelay(uuid, ()->{ log.info("[第三方PS服务对接->发送流] 等待流上线超时 callId->{}", callId); redisTemplate.delete(key); hookSubscribe.removeSubscribe(hook); }, 10000); // 订阅 zlm启动事件, 新的zlm也会从这里进入系统 OtherPsSendInfo finalSendInfo = sendInfo; hookSubscribe.removeSubscribe(hook); hookSubscribe.addSubscribe(hook, (hookData)->{ dynamicTask.stop(uuid); log.info("[第三方PS服务对接->发送流] 流上线,开始发流 callId->{}", callId); try { Thread.sleep(400); } catch (InterruptedException e) { throw new RuntimeException(e); } mediaServerService.startSendRtp(mediaServer, sendRtpItem); log.info("[第三方PS服务对接->发送流] 视频流发流成功,callId->{},param->{}", callId, sendRtpItem); redisTemplate.opsForValue().set(key, finalSendInfo); hookSubscribe.removeSubscribe(hook); }); } } @GetMapping(value = "/send/stop") @ResponseBody @Operation(summary = "关闭发送流") @Parameter(name = "callId", description = "整个过程的唯一标识,不传则使用随机端口发流", required = true) public void closeSendRTP(String callId) { log.info("[第三方PS服务对接->关闭发送流] callId->{}", callId); String key = VideoManagerConstants.WVP_OTHER_SEND_PS_INFO + userSetting.getServerId() + "_" + callId; OtherPsSendInfo sendInfo = (OtherPsSendInfo)redisTemplate.opsForValue().get(key); if (sendInfo == null){ throw new ControllerException(ErrorCode.ERROR100.getCode(), "未开启发流"); } MediaServer mediaServerItem = mediaServerService.getDefaultMediaServer(); boolean result = mediaServerService.stopSendRtp(mediaServerItem, sendInfo.getPushApp(), sendInfo.getStream(), sendInfo.getPushSSRC()); if (!result) { log.info("[第三方PS服务对接->关闭发送流] 失败 callId->{}", callId); throw new ControllerException(ErrorCode.ERROR100.getCode(), "停止发流失败"); }else { log.info("[第三方PS服务对接->关闭发送流] 成功 callId->{}", callId); } redisTemplate.delete(key); } @GetMapping(value = "/getTestPort") @ResponseBody public int getTestPort() { MediaServer defaultMediaServer = mediaServerService.getDefaultMediaServer(); // for (int i = 0; i <300; i++) { // new Thread(() -> { // int nextPort = sendRtpPortManager.getNextPort(defaultMediaServer); // try { // Thread.sleep((int)Math.random()*10); // } catch (InterruptedException e) { // throw new RuntimeException(e); // } // System.out.println(nextPort); // }).start(); // } return sendRtpServerService.getNextPort(defaultMediaServer); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/vmanager/recordPlan/RecordPlanController.java ================================================ package com.genersoft.iot.vmp.vmanager.recordPlan; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.conf.security.JwtUtils; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService; import com.genersoft.iot.vmp.service.IRecordPlanService; import com.genersoft.iot.vmp.service.bean.RecordPlan; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.recordPlan.bean.RecordPlanParam; import com.github.pagehelper.PageInfo; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.ObjectUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.Assert; import org.springframework.web.bind.annotation.*; import java.util.ArrayList; import java.util.List; @Tag(name = "录制计划") @Slf4j @RestController @RequestMapping("/api/record/plan") public class RecordPlanController { @Autowired private IRecordPlanService recordPlanService; @Autowired private IDeviceChannelService deviceChannelService; @ResponseBody @PostMapping("/add") @Operation(summary = "添加录制计划", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "plan", description = "计划", required = true) public void add(@RequestBody RecordPlan plan) { if (plan.getPlanItemList() == null || plan.getPlanItemList().isEmpty()) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "添加录制计划时,录制计划不可为空"); } recordPlanService.add(plan); } @ResponseBody @PostMapping("/link") @Operation(summary = "通道关联录制计划", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "param", description = "通道关联录制计划", required = true) public void link(@RequestBody RecordPlanParam param) { if (param.getAllLink() != null) { if (param.getAllLink()) { recordPlanService.linkAll(param.getPlanId()); }else { recordPlanService.cleanAll(param.getPlanId()); } return; } if (param.getChannelIds() == null && param.getDeviceDbIds() == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "通道ID和国标设备ID不可都为NULL"); } List channelIds = new ArrayList<>(); if (param.getChannelIds() != null) { channelIds.addAll(param.getChannelIds()); }else { List chanelIdList = deviceChannelService.queryChaneIdListByDeviceDbIds(param.getDeviceDbIds()); if (chanelIdList != null && !chanelIdList.isEmpty()) { channelIds = chanelIdList; } } recordPlanService.link(channelIds, param.getPlanId()); } @ResponseBody @GetMapping("/get") @Operation(summary = "查询录制计划", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "planId", description = "计划ID", required = true) public RecordPlan get(Integer planId) { if (planId == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "计划ID不可为NULL"); } return recordPlanService.get(planId); } @ResponseBody @GetMapping("/query") @Operation(summary = "查询录制计划列表", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "query", description = "检索内容", required = false) @Parameter(name = "page", description = "当前页", required = true) @Parameter(name = "count", description = "每页查询数量", required = true) public PageInfo query(@RequestParam(required = false) String query, @RequestParam Integer page, @RequestParam Integer count) { if (query != null && ObjectUtils.isEmpty(query.trim())) { query = null; } return recordPlanService.query(page, count, query); } @Operation(summary = "分页查询录制计划关联的所有通道", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "page", description = "当前页", required = true) @Parameter(name = "count", description = "每页条数", required = true) @Parameter(name = "planId", description = "录制计划ID") @Parameter(name = "channelType", description = "通道类型, 0:国标设备,1:推流设备,2:拉流代理") @Parameter(name = "query", description = "查询内容") @Parameter(name = "online", description = "是否在线") @Parameter(name = "hasLink", description = "是否已经关联") @GetMapping("/channel/list") @ResponseBody public PageInfo queryChannelList(int page, int count, @RequestParam(required = false) Integer planId, @RequestParam(required = false) String query, @RequestParam(required = false) Integer channelType, @RequestParam(required = false) Boolean online, @RequestParam(required = false) Boolean hasLink) { Assert.notNull(planId, "录制计划ID不可为NULL"); if (org.springframework.util.ObjectUtils.isEmpty(query)) { query = null; } return recordPlanService.queryChannelList(page, count, query, channelType, online, planId, hasLink); } @ResponseBody @PostMapping("/update") @Operation(summary = "更新录制计划", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "plan", description = "计划", required = true) public void update(@RequestBody RecordPlan plan) { if (plan == null || plan.getId() == 0) { throw new ControllerException(ErrorCode.ERROR400); } recordPlanService.update(plan); } @ResponseBody @DeleteMapping("/delete") @Operation(summary = "删除录制计划", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "planId", description = "计划ID", required = true) public void delete(Integer planId) { if (planId == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "计划IDID不可为NULL"); } recordPlanService.delete(planId); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/vmanager/recordPlan/bean/RecordPlanParam.java ================================================ package com.genersoft.iot.vmp.vmanager.recordPlan.bean; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.util.List; @Data @Schema(description = "录制计划-添加/编辑参数") public class RecordPlanParam { @Schema(description = "关联的通道ID") private List channelIds; @Schema(description = "关联的设备ID,会为设备下的所有通道关联此录制计划,channelId存在是此项不生效,") private List deviceDbIds; @Schema(description = "全部关联/全部取消关联") private Boolean allLink; @Schema(description = "录制计划ID, ID为空是删除关联的计划") private Integer planId; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/vmanager/rtp/RtpController.java ================================================ package com.genersoft.iot.vmp.vmanager.rtp; import com.genersoft.iot.vmp.common.VideoManagerConstants; import com.genersoft.iot.vmp.common.enums.MediaApp; import com.genersoft.iot.vmp.conf.DynamicTask; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.conf.security.JwtUtils; import com.genersoft.iot.vmp.gb28181.bean.SendRtpInfo; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.event.hook.Hook; import com.genersoft.iot.vmp.media.event.hook.HookSubscribe; import com.genersoft.iot.vmp.media.event.hook.HookType; import com.genersoft.iot.vmp.media.service.IMediaServerService; import com.genersoft.iot.vmp.service.IReceiveRtpServerService; import com.genersoft.iot.vmp.service.ISendRtpServerService; import com.genersoft.iot.vmp.service.bean.InviteErrorCode; import com.genersoft.iot.vmp.service.bean.RTPServerParam; import com.genersoft.iot.vmp.service.bean.SSRCInfo; import com.genersoft.iot.vmp.utils.redis.RedisUtil; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.OtherRtpSendInfo; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; import okhttp3.OkHttpClient; import okhttp3.Request; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.util.ObjectUtils; import org.springframework.web.bind.annotation.*; import java.io.IOException; import java.util.List; import java.util.UUID; import java.util.concurrent.TimeUnit; @SuppressWarnings("rawtypes") @Tag(name = "第三方服务对接") @Slf4j @RestController @RequestMapping("/api/rtp") public class RtpController { @Autowired private ISendRtpServerService sendRtpServerService; @Autowired private IReceiveRtpServerService receiveRtpServerService; @Autowired private HookSubscribe hookSubscribe; @Autowired private IMediaServerService mediaServerService; @Autowired private UserSetting userSetting; @Autowired private DynamicTask dynamicTask; @Autowired private RedisTemplate redisTemplate; @GetMapping(value = "/receive/open") @ResponseBody @Operation(summary = "开启收流和获取发流信息", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "isSend", description = "是否发送,false时只开启收流, true同时返回推流信息", required = true) @Parameter(name = "callId", description = "整个过程的唯一标识,为了与后续接口关联", required = true) @Parameter(name = "ssrc", description = "来源流的SSRC,不传则不校验来源ssrc", required = false) @Parameter(name = "stream", description = "形成的流的ID", required = true) @Parameter(name = "tcpMode", description = "收流模式, 0为UDP, 1为TCP被动", required = true) @Parameter(name = "callBack", description = "回调地址,如果收流超时会通道回调通知,回调为get请求,参数为callId", required = true) public OtherRtpSendInfo openRtpServer(Boolean isSend, @RequestParam(required = false)String ssrc, String callId, String stream, Integer tcpMode, String callBack) { log.info("[第三方服务对接->开启收流和获取发流信息] isSend->{}, ssrc->{}, callId->{}, stream->{}, tcpMode->{}, callBack->{}", isSend, ssrc, callId, stream, tcpMode==0?"UDP":"TCP被动", callBack); MediaServer mediaServer = mediaServerService.getDefaultMediaServer(); if (mediaServer == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(),"没有可用的MediaServer"); } if (stream == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(),"stream参数不可为空"); } if (isSend != null && isSend && callId == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(),"isSend为true时,CallID不能为空"); } long ssrcInt = 0; if (ssrc != null) { try { ssrcInt = Long.parseLong(ssrc); }catch (NumberFormatException e) { throw new ControllerException(ErrorCode.ERROR100.getCode(),"ssrc格式错误"); } } String receiveKey = VideoManagerConstants.WVP_OTHER_RECEIVE_RTP_INFO + userSetting.getServerId() + "_" + callId + "_" + stream; RTPServerParam rtpServerParam = new RTPServerParam(); rtpServerParam.setMediaServer(mediaServer); rtpServerParam.setApp(MediaApp.GB28181); rtpServerParam.setStreamId(stream); rtpServerParam.setSsrc(ssrcInt); rtpServerParam.setTcpMode(tcpMode); int rtpServerPortForVideo = receiveRtpServerService.openRTPServer(rtpServerParam, ((code, msg, data) -> { if (callBack == null) { return; } if (code == InviteErrorCode.SUCCESS.getCode()) { log.info("[开启收流和获取发流信息] 视频流收流成功,callId->{},stream->{}", callId, stream); OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder(); OkHttpClient client = httpClientBuilder.build(); String url = callBack + "?callId=" + callId; Request request = new Request.Builder().get().url(url).build(); try { client.newCall(request).execute(); } catch (IOException e) { log.error("[第三方服务对接->开启收流和获取发流信息] 等待收流超时 callId->{}, 发送回调失败", callId, e); } }else { log.info("[开启收流和获取发流信息] 视频流收流失败,callId->{},stream->{}", callId, stream); } })); rtpServerParam.setStreamId(stream + "_a"); int rtpServerPortForAudio = receiveRtpServerService.openRTPServer(rtpServerParam, ((code, msg, data) -> { if (code == InviteErrorCode.SUCCESS.getCode()) { log.info("[开启收流和获取发流信息] 音频流收流成功,callId->{},stream->{}", callId, stream); }else { log.info("[开启收流和获取发流信息] 音频流收流失败,callId->{},stream->{}", callId, stream); } })); if (rtpServerPortForVideo == 0 || rtpServerPortForAudio == 0) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "获取端口失败"); } String key = VideoManagerConstants.WVP_OTHER_SEND_RTP_INFO + userSetting.getServerId() + "_" + callId; OtherRtpSendInfo otherRtpSendInfo = new OtherRtpSendInfo(); otherRtpSendInfo.setReceiveIp(mediaServer.getSdpIp()); otherRtpSendInfo.setReceivePortForVideo(rtpServerPortForVideo); otherRtpSendInfo.setReceivePortForAudio(rtpServerPortForAudio); otherRtpSendInfo.setCallId(callId); otherRtpSendInfo.setStream(stream); // 将信息写入redis中,以备后用 redisTemplate.opsForValue().set(receiveKey, otherRtpSendInfo); if (isSend != null && isSend) { // 预创建发流信息 int portForVideo = sendRtpServerService.getNextPort(mediaServer); int portForAudio = sendRtpServerService.getNextPort(mediaServer); otherRtpSendInfo.setSendLocalIp(mediaServer.getSdpIp()); otherRtpSendInfo.setSendLocalPortForVideo(portForVideo); otherRtpSendInfo.setSendLocalPortForAudio(portForAudio); // 将信息写入redis中,以备后用 redisTemplate.opsForValue().set(key, otherRtpSendInfo, 300, TimeUnit.SECONDS); log.info("[第三方服务对接->开启收流和获取发流信息] 结果,callId->{}, {}", callId, otherRtpSendInfo); } // 将信息写入redis中,以备后用 redisTemplate.opsForValue().set(key, otherRtpSendInfo, 300, TimeUnit.SECONDS); return otherRtpSendInfo; } @GetMapping(value = "/receive/close") @ResponseBody @Operation(summary = "关闭收流", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "stream", description = "流的ID", required = true) public void closeRtpServer(String stream) { log.info("[第三方服务对接->关闭收流] stream->{}", stream); MediaServer mediaServerItem = mediaServerService.getDefaultMediaServer(); receiveRtpServerService.closeRTPServer(mediaServerItem, MediaApp.GB28181, stream); receiveRtpServerService.closeRTPServer(mediaServerItem, MediaApp.GB28181, stream+ "_a"); String receiveKey = VideoManagerConstants.WVP_OTHER_RECEIVE_RTP_INFO + userSetting.getServerId() + "_*_" + stream; List scan = RedisUtil.scan(redisTemplate, receiveKey); if (scan.size() > 0) { for (Object key : scan) { // 将信息写入redis中,以备后用 redisTemplate.delete(key); } } } @GetMapping(value = "/send/start") @ResponseBody @Operation(summary = "发送流", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "ssrc", description = "发送流的SSRC", required = true) @Parameter(name = "dstIpForAudio", description = "目标音频收流IP", required = false) @Parameter(name = "dstIpForVideo", description = "目标视频收流IP", required = false) @Parameter(name = "dstPortForAudio", description = "目标音频收流端口", required = false) @Parameter(name = "dstPortForVideo", description = "目标视频收流端口", required = false) @Parameter(name = "app", description = "待发送应用名", required = true) @Parameter(name = "stream", description = "待发送流Id", required = true) @Parameter(name = "callId", description = "整个过程的唯一标识,不传则使用随机端口发流", required = true) @Parameter(name = "isUdp", description = "是否为UDP", required = true) @Parameter(name = "ptForAudio", description = "rtp的音频pt", required = false) @Parameter(name = "ptForVideo", description = "rtp的视频pt", required = false) public void sendRTP(String ssrc, @RequestParam(required = false)String dstIpForAudio, @RequestParam(required = false)String dstIpForVideo, @RequestParam(required = false)Integer dstPortForAudio, @RequestParam(required = false)Integer dstPortForVideo, String app, String stream, String callId, Boolean isUdp, @RequestParam(required = false)Integer ptForAudio, @RequestParam(required = false)Integer ptForVideo ) { log.info("[第三方服务对接->发送流] " + "ssrc->{}, \r\n" + "dstIpForAudio->{}, \n" + "dstIpForAudio->{}, \n" + "dstPortForAudio->{}, \n" + "dstPortForVideo->{}, \n" + "app->{}, \n" + "stream->{}, \n" + "callId->{}, \n" + "ptForAudio->{}, \n" + "ptForVideo->{}", ssrc, dstIpForAudio, dstIpForVideo, dstPortForAudio, dstPortForVideo, app, stream, callId, ptForAudio, ptForVideo); if (!((dstPortForAudio > 0 && !ObjectUtils.isEmpty(dstPortForAudio) || (dstPortForVideo > 0 && !ObjectUtils.isEmpty(dstIpForVideo))))) { throw new ControllerException(ErrorCode.ERROR400.getCode(), "至少应该存在一组音频或视频发送参数"); } MediaServer mediaServer = mediaServerService.getDefaultMediaServer(); String key = VideoManagerConstants.WVP_OTHER_SEND_RTP_INFO + userSetting.getServerId() + "_" + callId; OtherRtpSendInfo sendInfo = (OtherRtpSendInfo)redisTemplate.opsForValue().get(key); if (sendInfo == null) { sendInfo = new OtherRtpSendInfo(); } sendInfo.setPushApp(app); sendInfo.setPushStream(stream); sendInfo.setPushSSRC(ssrc); SendRtpInfo sendRtpItemForVideo; SendRtpInfo sendRtpItemForAudio; if (!ObjectUtils.isEmpty(dstIpForAudio) && dstPortForAudio > 0) { sendRtpItemForAudio = SendRtpInfo.getInstance(app, stream, ssrc, dstIpForAudio, dstPortForAudio, !isUdp, sendInfo.getSendLocalPortForAudio(), ptForAudio); } else { sendRtpItemForAudio = null; } if (!ObjectUtils.isEmpty(dstIpForVideo) && dstPortForVideo > 0) { sendRtpItemForVideo = SendRtpInfo.getInstance(app, stream, ssrc, dstIpForAudio, dstPortForAudio, !isUdp, sendInfo.getSendLocalPortForVideo(), ptForVideo); } else { sendRtpItemForVideo = null; } Boolean streamReady = mediaServerService.isStreamReady(mediaServer, app, stream); if (streamReady) { if (sendRtpItemForVideo != null) { mediaServerService.startSendRtp(mediaServer, sendRtpItemForVideo); log.info("[第三方服务对接->发送流] 视频流发流成功,callId->{},param->{}", callId, sendRtpItemForVideo); redisTemplate.opsForValue().set(key, sendInfo); } if(sendRtpItemForAudio != null) { mediaServerService.startSendRtp(mediaServer, sendRtpItemForAudio); log.info("[第三方服务对接->发送流] 音频流发流成功,callId->{},param->{}", callId, sendRtpItemForAudio); redisTemplate.opsForValue().set(key, sendInfo); } }else { log.info("[第三方服务对接->发送流] 流不存在,等待流上线,callId->{}", callId); String uuid = UUID.randomUUID().toString(); Hook hook = Hook.getInstance(HookType.on_media_arrival, app, stream, mediaServer.getId()); dynamicTask.startDelay(uuid, ()->{ log.info("[第三方服务对接->发送流] 等待流上线超时 callId->{}", callId); redisTemplate.delete(key); hookSubscribe.removeSubscribe(hook); }, 10000); // 订阅 zlm启动事件, 新的zlm也会从这里进入系统 hookSubscribe.removeSubscribe(hook); OtherRtpSendInfo finalSendInfo = sendInfo; hookSubscribe.addSubscribe(hook, (hookData)->{ dynamicTask.stop(uuid); log.info("[第三方服务对接->发送流] 流上线,开始发流 callId->{}", callId); try { Thread.sleep(400); } catch (InterruptedException e) { throw new RuntimeException(e); } if (sendRtpItemForVideo != null) { mediaServerService.startSendRtp(mediaServer, sendRtpItemForVideo); log.info("[第三方服务对接->发送流] 视频流发流成功,callId->{},param->{}", callId, sendRtpItemForVideo); redisTemplate.opsForValue().set(key, finalSendInfo); } if(sendRtpItemForAudio != null) { mediaServerService.startSendRtp(mediaServer, sendRtpItemForAudio); log.info("[第三方服务对接->发送流] 音频流发流成功,callId->{},param->{}", callId, sendRtpItemForAudio); redisTemplate.opsForValue().set(key, finalSendInfo); } hookSubscribe.removeSubscribe(hook); }); } } @GetMapping(value = "/send/stop") @ResponseBody @Operation(summary = "关闭发送流", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "callId", description = "整个过程的唯一标识,不传则使用随机端口发流", required = true) public void closeSendRTP(String callId) { log.info("[第三方服务对接->关闭发送流] callId->{}", callId); String key = VideoManagerConstants.WVP_OTHER_SEND_RTP_INFO + userSetting.getServerId() + "_" + callId; OtherRtpSendInfo sendInfo = (OtherRtpSendInfo)redisTemplate.opsForValue().get(key); if (sendInfo == null){ throw new ControllerException(ErrorCode.ERROR100.getCode(), "未开启发流"); } MediaServer mediaServerItem = mediaServerService.getDefaultMediaServer(); mediaServerService.stopSendRtp(mediaServerItem, sendInfo.getPushApp(), sendInfo.getPushStream(), sendInfo.getPushSSRC()); log.info("[第三方服务对接->关闭发送流] 成功 callId->{}", callId); redisTemplate.delete(key); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/vmanager/server/ServerController.java ================================================ package com.genersoft.iot.vmp.vmanager.server; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.common.SystemAllInfo; import com.genersoft.iot.vmp.common.VersionPo; import com.genersoft.iot.vmp.conf.SipConfig; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.VersionInfo; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.conf.security.JwtUtils; import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService; import com.genersoft.iot.vmp.gb28181.service.IDeviceService; import com.genersoft.iot.vmp.jt1078.config.JT1078Config; import com.genersoft.iot.vmp.media.bean.MediaInfo; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.event.mediaServer.MediaServerChangeEvent; import com.genersoft.iot.vmp.media.service.IMediaServerService; import com.genersoft.iot.vmp.service.IMapService; import com.genersoft.iot.vmp.service.bean.MediaServerLoad; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.streamProxy.service.IStreamProxyService; import com.genersoft.iot.vmp.streamPush.service.IStreamPushService; import com.genersoft.iot.vmp.vmanager.bean.*; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationEventPublisher; import org.springframework.util.ObjectUtils; import org.springframework.web.bind.annotation.*; import oshi.SystemInfo; import oshi.hardware.CentralProcessor; import oshi.hardware.GlobalMemory; import oshi.hardware.HardwareAbstractionLayer; import oshi.hardware.NetworkIF; import oshi.software.os.OperatingSystem; import java.io.File; import java.text.DecimalFormat; import java.util.*; @SuppressWarnings("rawtypes") @Tag(name = "服务控制") @Slf4j @RestController @RequestMapping("/api/server") public class ServerController { @Autowired private IMediaServerService mediaServerService; @Autowired private VersionInfo versionInfo; @Autowired private SipConfig sipConfig; @Autowired private UserSetting userSetting; @Autowired private JT1078Config jt1078Config; @Autowired private IDeviceService deviceService; @Autowired private IDeviceChannelService channelService; @Autowired private IStreamPushService pushService; @Autowired private IStreamProxyService proxyService; @Autowired(required = false) private IMapService mapService; @Value("${server.port}") private int serverPort; @Autowired private IRedisCatchStorage redisCatchStorage; @Autowired private ApplicationEventPublisher applicationEventPublisher; @GetMapping(value = "/media_server/list") @ResponseBody @Operation(summary = "流媒体服务列表", security = @SecurityRequirement(name = JwtUtils.HEADER)) public List getMediaServerList() { return mediaServerService.getAll(); } @GetMapping(value = "/media_server/online/list") @ResponseBody @Operation(summary = "在线流媒体服务列表", security = @SecurityRequirement(name = JwtUtils.HEADER)) public List getOnlineMediaServerList() { return mediaServerService.getAllOnline(); } @GetMapping(value = "/media_server/one/{id}") @ResponseBody @Operation(summary = "停止视频回放", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "id", description = "流媒体服务ID", required = true) public MediaServer getMediaServer(@PathVariable String id) { return mediaServerService.getOne(id); } @Operation(summary = "测试流媒体服务", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "ip", description = "流媒体服务IP", required = true) @Parameter(name = "port", description = "流媒体服务HTT端口", required = true) @Parameter(name = "secret", description = "流媒体服务secret", required = true) @GetMapping(value = "/media_server/check") @ResponseBody public MediaServer checkMediaServer(@RequestParam String ip, @RequestParam int port, @RequestParam String secret, @RequestParam String type) { return mediaServerService.checkMediaServer(ip, port, secret, type); } @Operation(summary = "测试流媒体录像管理服务", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "ip", description = "流媒体服务IP", required = true) @Parameter(name = "port", description = "流媒体服务HTT端口", required = true) @GetMapping(value = "/media_server/record/check") @ResponseBody public void checkMediaRecordServer(@RequestParam String ip, @RequestParam int port) { boolean checkResult = mediaServerService.checkMediaRecordServer(ip, port); if (!checkResult) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "连接失败"); } } @Operation(summary = "保存流媒体服务", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "mediaServerItem", description = "流媒体信息", required = true) @PostMapping(value = "/media_server/save") @ResponseBody public void saveMediaServer(@RequestBody MediaServer mediaServer) { MediaServer mediaServerItemInDatabase = mediaServerService.getOneFromDatabase(mediaServer.getId()); if (mediaServerItemInDatabase != null) { mediaServerService.update(mediaServer); } else { mediaServerService.add(mediaServer); // 发送事件 MediaServerChangeEvent event = new MediaServerChangeEvent(this); event.setMediaServerItemList(mediaServer); applicationEventPublisher.publishEvent(event); } } @Operation(summary = "移除流媒体服务", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "id", description = "流媒体ID", required = true) @DeleteMapping(value = "/media_server/delete") @ResponseBody public void deleteMediaServer(@RequestParam String id) { MediaServer mediaServer = mediaServerService.getOne(id); if (mediaServer == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "流媒体不存在"); } mediaServerService.delete(mediaServer); } @Operation(summary = "获取流信息", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "app", description = "应用名", required = true) @Parameter(name = "stream", description = "流ID", required = true) @Parameter(name = "mediaServerId", description = "流媒体ID", required = true) @GetMapping(value = "/media_server/media_info") @ResponseBody public MediaInfo getMediaInfo(String app, String stream, String mediaServerId) { MediaServer mediaServer = mediaServerService.getOneFromCluster(mediaServerId); if (mediaServer == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "流媒体不存在"); } return mediaServerService.getMediaInfo(mediaServer, app, stream); } @Operation(summary = "关闭服务", security = @SecurityRequirement(name = JwtUtils.HEADER)) @GetMapping(value = "/shutdown") @ResponseBody public void shutdown() { log.info("正在关闭服务。。。"); System.exit(1); } @Operation(summary = "获取系统配置信息", security = @SecurityRequirement(name = JwtUtils.HEADER)) @GetMapping(value = "/system/configInfo") @ResponseBody public SystemConfigInfo getConfigInfo() { SystemConfigInfo systemConfigInfo = new SystemConfigInfo(); systemConfigInfo.setVersion(versionInfo.getVersion()); systemConfigInfo.setSip(sipConfig); systemConfigInfo.setAddOn(userSetting); systemConfigInfo.setServerPort(serverPort); systemConfigInfo.setJt1078Config(jt1078Config); return systemConfigInfo; } @Operation(summary = "获取版本信息", security = @SecurityRequirement(name = JwtUtils.HEADER)) @GetMapping(value = "/version") @ResponseBody public VersionPo VersionPogetVersion() { return versionInfo.getVersion(); } @GetMapping(value = "/config") @Operation(summary = "获取配置信息", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "type", description = "配置类型(sip, base)", required = true) @ResponseBody public JSONObject getVersion(String type) { JSONObject jsonObject = new JSONObject(); jsonObject.put("server.port", serverPort); if (ObjectUtils.isEmpty(type)) { jsonObject.put("sip", JSON.toJSON(sipConfig)); jsonObject.put("base", JSON.toJSON(userSetting)); } else { switch (type) { case "sip": jsonObject.put("sip", sipConfig); break; case "base": jsonObject.put("base", userSetting); break; default: break; } } return jsonObject; } @GetMapping(value = "/system/info") @ResponseBody @Operation(summary = "获取系统信息", security = @SecurityRequirement(name = JwtUtils.HEADER)) public SystemAllInfo getSystemInfo() { SystemAllInfo systemAllInfo = redisCatchStorage.getSystemInfo(); return systemAllInfo; } @GetMapping(value = "/media_server/load") @ResponseBody @Operation(summary = "获取负载信息", security = @SecurityRequirement(name = JwtUtils.HEADER)) public List getMediaLoad() { List result = new ArrayList<>(); List allOnline = mediaServerService.getAllOnline(); if (allOnline.isEmpty()) { return result; } else { for (MediaServer mediaServerItem : allOnline) { result.add(mediaServerService.getLoad(mediaServerItem)); } } return result; } @GetMapping(value = "/resource/info") @ResponseBody @Operation(summary = "获取负载信息", security = @SecurityRequirement(name = JwtUtils.HEADER)) public ResourceInfo getResourceInfo() { ResourceInfo result = new ResourceInfo(); ResourceBaseInfo deviceInfo = deviceService.getOverview(); result.setDevice(deviceInfo); ResourceBaseInfo channelInfo = channelService.getOverview(); result.setChannel(channelInfo); ResourceBaseInfo pushInfo = pushService.getOverview(); result.setPush(pushInfo); ResourceBaseInfo proxyInfo = proxyService.getOverview(); result.setProxy(proxyInfo); return result; } @GetMapping(value = "/info") @ResponseBody @Operation(summary = "获取系统信息", security = @SecurityRequirement(name = JwtUtils.HEADER)) public Map> getInfo(HttpServletRequest request) { Map> result = new LinkedHashMap<>(); Map hardwareMap = new LinkedHashMap<>(); result.put("硬件信息", hardwareMap); SystemInfo systemInfo = new SystemInfo(); HardwareAbstractionLayer hardware = systemInfo.getHardware(); // 获取CPU信息 CentralProcessor.ProcessorIdentifier processorIdentifier = hardware.getProcessor().getProcessorIdentifier(); hardwareMap.put("CPU", processorIdentifier.getName()); // 获取内存 GlobalMemory memory = hardware.getMemory(); hardwareMap.put("内存", formatByte(memory.getTotal() - memory.getAvailable()) + "/" + formatByte(memory.getTotal())); hardwareMap.put("制造商", systemInfo.getHardware().getComputerSystem().getManufacturer()); hardwareMap.put("产品名称", systemInfo.getHardware().getComputerSystem().getModel()); // 网卡 List networkIFs = hardware.getNetworkIFs(); StringBuilder ips = new StringBuilder(); for (int i = 0; i < networkIFs.size(); i++) { NetworkIF networkIF = networkIFs.get(i); String ipsStr = StringUtils.join(networkIF.getIPv4addr()); if (ObjectUtils.isEmpty(ipsStr)) { continue; } ips.append(ipsStr); if (i < networkIFs.size() - 1) { ips.append(","); } } hardwareMap.put("网卡", ips.toString()); Map operatingSystemMap = new LinkedHashMap<>(); result.put("操作系统", operatingSystemMap); OperatingSystem operatingSystem = systemInfo.getOperatingSystem(); operatingSystemMap.put("名称", operatingSystem.getFamily() + " " + operatingSystem.getVersionInfo().getVersion()); operatingSystemMap.put("类型", operatingSystem.getManufacturer()); Map platformMap = new LinkedHashMap<>(); result.put("平台信息", platformMap); VersionPo version = versionInfo.getVersion(); platformMap.put("版本", version.getVersion()); platformMap.put("构建日期", version.getBUILD_DATE()); platformMap.put("GIT分支", version.getGIT_BRANCH()); platformMap.put("GIT地址", version.getGIT_URL()); platformMap.put("GIT日期", version.getGIT_DATE()); platformMap.put("GIT版本", version.getGIT_Revision_SHORT()); platformMap.put("DOCKER环境", new File("/.dockerenv").exists()?"是":"否"); Map docmap = new LinkedHashMap<>(); result.put("文档地址", docmap); docmap.put("部署文档", "https://doc.wvp-pro.cn"); docmap.put("接口文档", String.format("%s://%s:%s/doc.html", request.getScheme(), request.getServerName(), request.getServerPort())); return result; } /** * 单位转换 */ private static String formatByte(long byteNumber) { //换算单位 double FORMAT = 1024.0; double kbNumber = byteNumber / FORMAT; if (kbNumber < FORMAT) { return new DecimalFormat("#.##KB").format(kbNumber); } double mbNumber = kbNumber / FORMAT; if (mbNumber < FORMAT) { return new DecimalFormat("#.##MB").format(mbNumber); } double gbNumber = mbNumber / FORMAT; if (gbNumber < FORMAT) { return new DecimalFormat("#.##GB").format(gbNumber); } double tbNumber = gbNumber / FORMAT; return new DecimalFormat("#.##TB").format(tbNumber); } @GetMapping(value = "/map/config") @ResponseBody @Operation(summary = "获取地图配置", security = @SecurityRequirement(name = JwtUtils.HEADER)) public List getMapConfig() { if (mapService == null) { return Collections.emptyList(); } return mapService.getConfig(); } @GetMapping(value = "/map/model-icon/list") @ResponseBody @Operation(summary = "获取地图配置图标", security = @SecurityRequirement(name = JwtUtils.HEADER)) public List getMapModelIconList() { if (mapService == null) { return Collections.emptyList(); } return mapService.getModelList(); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/vmanager/user/RoleController.java ================================================ package com.genersoft.iot.vmp.vmanager.user; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.conf.security.JwtUtils; import com.genersoft.iot.vmp.conf.security.SecurityUtils; import com.genersoft.iot.vmp.service.IRoleService; import com.genersoft.iot.vmp.storager.dao.dto.Role; import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; @Tag(name = "角色管理") @RestController @RequestMapping("/api/role") public class RoleController { @Autowired private IRoleService roleService; @PostMapping("/add") @Operation(summary = "添加角色", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "name", description = "角色名", required = true) @Parameter(name = "authority", description = "权限(自行定义内容,目前未使用)", required = true) public void add(@RequestParam String name, @RequestParam(required = false) String authority){ // 获取当前登录用户id int currenRoleId = SecurityUtils.getUserInfo().getRole().getId(); if (currenRoleId != 1) { // 只用角色id为0才可以删除和添加用户 throw new ControllerException(ErrorCode.ERROR403); } Role role = new Role(); role.setName(name); role.setAuthority(authority); role.setCreateTime(DateUtil.getNow()); role.setUpdateTime(DateUtil.getNow()); int addResult = roleService.add(role); if (addResult <= 0) { throw new ControllerException(ErrorCode.ERROR100); } } @DeleteMapping("/delete") @Operation(summary = "删除角色", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "id", description = "用户Id", required = true) public void delete(@RequestParam Integer id){ // 获取当前登录用户id int currenRoleId = SecurityUtils.getUserInfo().getRole().getId(); if (currenRoleId != 1) { // 只用角色id为0才可以删除和添加用户 throw new ControllerException(ErrorCode.ERROR403); } int deleteResult = roleService.delete(id); if (deleteResult <= 0) { throw new ControllerException(ErrorCode.ERROR100); } } @GetMapping("/all") @Operation(summary = "查询角色", security = @SecurityRequirement(name = JwtUtils.HEADER)) public List all(){ // 获取当前登录用户id return roleService.getAll(); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/vmanager/user/UserApiKeyController.java ================================================ package com.genersoft.iot.vmp.vmanager.user; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.conf.security.JwtUtils; import com.genersoft.iot.vmp.conf.security.SecurityUtils; import com.genersoft.iot.vmp.service.IUserApiKeyService; import com.genersoft.iot.vmp.service.IUserService; import com.genersoft.iot.vmp.storager.dao.dto.User; import com.genersoft.iot.vmp.storager.dao.dto.UserApiKey; import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.github.pagehelper.PageInfo; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; import java.util.HashMap; import java.util.Map; @Tag(name = "用户ApiKey管理") @RestController @RequestMapping("/api/userApiKey") public class UserApiKeyController { public static final int EXPIRATION_TIME = Integer.MAX_VALUE; @Autowired private IUserService userService; @Autowired private IUserApiKeyService userApiKeyService; /** * 添加用户ApiKey * * @param userId * @param app * @param remark * @param expiresAt * @param enable */ @PostMapping("/add") @Operation(summary = "添加用户ApiKey", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "userId", description = "用户Id", required = true) @Parameter(name = "app", description = "应用名称", required = false) @Parameter(name = "remark", description = "备注信息", required = false) @Parameter(name = "expiredAt", description = "过期时间(不传代表永不过期)", required = false) @Transactional public synchronized void add( @RequestParam(required = true) int userId, @RequestParam(required = false) String app, @RequestParam(required = false) String remark, @RequestParam(required = false) String expiresAt, @RequestParam(required = false) Boolean enable ) { User user = userService.getUserById(userId); if (user == null) { throw new ControllerException(ErrorCode.ERROR400.getCode(), "用户不存在"); } Long expirationTime = null; if (expiresAt != null) { expirationTime = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestampMs(expiresAt); long difference = (expirationTime - System.currentTimeMillis()) / (60 * 1000); if (difference < 0) { throw new ControllerException(ErrorCode.ERROR400.getCode(), "过期时间不能早于当前时间"); } } UserApiKey userApiKey = new UserApiKey(); userApiKey.setUserId(userId); userApiKey.setApp(app); userApiKey.setApiKey(null); userApiKey.setRemark(remark); userApiKey.setExpiredAt(expirationTime != null ? expirationTime : 0); userApiKey.setEnable(enable != null ? enable : false); userApiKey.setCreateTime(DateUtil.getNow()); userApiKey.setUpdateTime(DateUtil.getNow()); int addResult = userApiKeyService.addApiKey(userApiKey); if (addResult <= 0) { throw new ControllerException(ErrorCode.ERROR100); } String apiKey; do { Map extra = new HashMap<>(1); extra.put("apiKeyId", userApiKey.getId()); apiKey = JwtUtils.createToken(user.getUsername(), expirationTime, extra); } while (userApiKeyService.isApiKeyExists(apiKey)); int resetResult = userApiKeyService.reset(userApiKey.getId(), apiKey); if (resetResult <= 0) { throw new ControllerException(ErrorCode.ERROR100); } } /** * 分页查询ApiKey * * @param page 当前页 * @param count 每页查询数量 * @return 分页ApiKey列表 */ @GetMapping("/userApiKeys") @Operation(summary = "分页查询用户", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "page", description = "当前页", required = true) @Parameter(name = "count", description = "每页查询数量", required = true) @Transactional public PageInfo userApiKeys(@RequestParam(required = true) int page, @RequestParam(required = true) int count) { return userApiKeyService.getUserApiKeys(page, count); } @PostMapping("/enable") @Operation(summary = "启用用户ApiKey", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "id", description = "用户ApiKeyId", required = true) @Transactional public void enable(@RequestParam(required = true) Integer id) { // 获取当前登录用户id int currenRoleId = SecurityUtils.getUserInfo().getRole().getId(); if (currenRoleId != 1) { // 只用角色id为1才可以管理UserApiKey throw new ControllerException(ErrorCode.ERROR403); } UserApiKey userApiKey = userApiKeyService.getUserApiKeyById(id); if (userApiKey == null) { throw new ControllerException(ErrorCode.ERROR400.getCode(), "ApiKey不存在"); } int enableResult = userApiKeyService.enable(id); if (enableResult <= 0) { throw new ControllerException(ErrorCode.ERROR100); } } @PostMapping("/disable") @Operation(summary = "停用用户ApiKey", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "id", description = "用户ApiKeyId", required = true) @Transactional public void disable(@RequestParam(required = true) Integer id) { // 获取当前登录用户id int currenRoleId = SecurityUtils.getUserInfo().getRole().getId(); if (currenRoleId != 1) { // 只用角色id为1才可以管理UserApiKey throw new ControllerException(ErrorCode.ERROR403); } UserApiKey userApiKey = userApiKeyService.getUserApiKeyById(id); if (userApiKey == null) { throw new ControllerException(ErrorCode.ERROR400.getCode(), "ApiKey不存在"); } int disableResult = userApiKeyService.disable(id); if (disableResult <= 0) { throw new ControllerException(ErrorCode.ERROR100); } } @PostMapping("/reset") @Operation(summary = "重置用户ApiKey", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "id", description = "用户ApiKeyId", required = true) @Transactional public void reset(@RequestParam(required = true) Integer id) { // 获取当前登录用户id int currenRoleId = SecurityUtils.getUserInfo().getRole().getId(); if (currenRoleId != 1) { // 只用角色id为1才可以管理UserApiKey throw new ControllerException(ErrorCode.ERROR403); } UserApiKey userApiKey = userApiKeyService.getUserApiKeyById(id); if (userApiKey == null) { throw new ControllerException(ErrorCode.ERROR400.getCode(), "ApiKey不存在"); } User user = userService.getUserById(userApiKey.getUserId()); if (user == null) { throw new ControllerException(ErrorCode.ERROR400.getCode(), "用户不存在"); } Long expirationTime = null; if (userApiKey.getExpiredAt() > 0) { long timestamp = userApiKey.getExpiredAt(); expirationTime = (timestamp - System.currentTimeMillis()) / (60 * 1000); if (expirationTime < 0) { throw new ControllerException(ErrorCode.ERROR400.getCode(), "ApiKey已失效"); } } String apiKey; do { Map extra = new HashMap<>(1); extra.put("apiKeyId", userApiKey.getId()); apiKey = JwtUtils.createToken(user.getUsername(), expirationTime, extra); } while (userApiKeyService.isApiKeyExists(apiKey)); int resetResult = userApiKeyService.reset(id, apiKey); if (resetResult <= 0) { throw new ControllerException(ErrorCode.ERROR100); } } @PostMapping("/remark") @Operation(summary = "备注用户ApiKey", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "id", description = "用户ApiKeyId", required = true) @Parameter(name = "remark", description = "用户ApiKey备注", required = false) @Transactional public void remark(@RequestParam(required = true) Integer id, @RequestParam(required = false) String remark) { // 获取当前登录用户id int currenRoleId = SecurityUtils.getUserInfo().getRole().getId(); if (currenRoleId != 1) { // 只用角色id为1才可以管理UserApiKey throw new ControllerException(ErrorCode.ERROR403); } UserApiKey userApiKey = userApiKeyService.getUserApiKeyById(id); if (userApiKey == null) { throw new ControllerException(ErrorCode.ERROR400.getCode(), "ApiKey不存在"); } int remarkResult = userApiKeyService.remark(id, remark); if (remarkResult <= 0) { throw new ControllerException(ErrorCode.ERROR100); } } @DeleteMapping("/delete") @Operation(summary = "删除用户ApiKey", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "id", description = "用户ApiKeyId", required = true) @Transactional public void delete(@RequestParam(required = true) Integer id) { // 获取当前登录用户id int currenRoleId = SecurityUtils.getUserInfo().getRole().getId(); if (currenRoleId != 1) { // 只用角色id为1才可以管理UserApiKey throw new ControllerException(ErrorCode.ERROR403); } UserApiKey userApiKey = userApiKeyService.getUserApiKeyById(id); if (userApiKey == null) { throw new ControllerException(ErrorCode.ERROR400.getCode(), "ApiKey不存在"); } int deleteResult = userApiKeyService.delete(id); if (deleteResult <= 0) { throw new ControllerException(ErrorCode.ERROR100); } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/vmanager/user/UserController.java ================================================ package com.genersoft.iot.vmp.vmanager.user; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.conf.security.JwtUtils; import com.genersoft.iot.vmp.conf.security.SecurityUtils; import com.genersoft.iot.vmp.conf.security.dto.LoginUser; import com.genersoft.iot.vmp.service.IRoleService; import com.genersoft.iot.vmp.service.IUserService; import com.genersoft.iot.vmp.storager.dao.dto.Role; import com.genersoft.iot.vmp.storager.dao.dto.User; import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import com.github.pagehelper.PageInfo; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.util.DigestUtils; import org.springframework.util.ObjectUtils; import org.springframework.web.bind.annotation.*; import javax.security.sasl.AuthenticationException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.time.LocalDateTime; import java.util.List; @Tag(name = "用户管理") @RestController @RequestMapping("/api/user") public class UserController { @Autowired private AuthenticationManager authenticationManager; @Autowired private IUserService userService; @Autowired private IRoleService roleService; @Autowired private UserSetting userSetting; @GetMapping("/login") @PostMapping("/login") @Operation(summary = "登录", description = "登录成功后返回AccessToken, 可以从返回值获取到也可以从响应头中获取到," + "后续的请求需要添加请求头 'access-token'或者放在参数里") @Parameter(name = "username", description = "用户名", required = true) @Parameter(name = "password", description = "密码(32位md5加密)", required = true) public LoginUser login(HttpServletRequest request, HttpServletResponse response, @RequestParam String username, @RequestParam String password){ LoginUser user; try { user = SecurityUtils.login(username, password, authenticationManager); } catch (AuthenticationException e) { throw new ControllerException(ErrorCode.ERROR100.getCode(), e.getMessage()); } if (user == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "用户名或密码错误"); }else { String jwt = JwtUtils.createToken(username); response.setHeader(JwtUtils.getHeader(), jwt); user.setAccessToken(jwt); user.setServerId(userSetting.getServerId()); } return user; } @PostMapping("/changePassword") @Operation(summary = "修改密码", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "username", description = "用户名", required = true) @Parameter(name = "oldpassword", description = "旧密码(已md5加密的密码)", required = true) @Parameter(name = "password", description = "新密码(未md5加密的密码)", required = true) public void changePassword(@RequestParam String oldPassword, @RequestParam String password){ // 获取当前登录用户id LoginUser userInfo = SecurityUtils.getUserInfo(); if (userInfo== null) { throw new ControllerException(ErrorCode.ERROR100); } String username = userInfo.getUsername(); LoginUser user = null; try { user = SecurityUtils.login(username, oldPassword, authenticationManager); if (user == null) { throw new ControllerException(ErrorCode.ERROR100); } //int userId = SecurityUtils.getUserId(); boolean result = userService.changePassword(user.getId(), DigestUtils.md5DigestAsHex(password.getBytes())); if (!result) { throw new ControllerException(ErrorCode.ERROR100); } } catch (AuthenticationException e) { throw new ControllerException(ErrorCode.ERROR100.getCode(), e.getMessage()); } } @PostMapping("/add") @Operation(summary = "添加用户", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "username", description = "用户名", required = true) @Parameter(name = "password", description = "密码(未md5加密的密码)", required = true) @Parameter(name = "roleId", description = "角色ID", required = true) public void add(@RequestParam String username, @RequestParam String password, @RequestParam Integer roleId){ if (ObjectUtils.isEmpty(username) || ObjectUtils.isEmpty(password) || roleId == null) { throw new ControllerException(ErrorCode.ERROR400.getCode(), "参数不可为空"); } // 获取当前登录用户id int currenRoleId = SecurityUtils.getUserInfo().getRole().getId(); if (currenRoleId != 1) { // 只用角色id为1才可以删除和添加用户 throw new ControllerException(ErrorCode.ERROR400.getCode(), "用户无权限"); } User user = new User(); user.setUsername(username); user.setPassword(DigestUtils.md5DigestAsHex(password.getBytes())); //新增用户的pushKey的生成规则为md5(时间戳+用户名) user.setPushKey(DigestUtils.md5DigestAsHex((System.currentTimeMillis()+password).getBytes())); Role role = roleService.getRoleById(roleId); if (role == null) { throw new ControllerException(ErrorCode.ERROR400.getCode(), "角色不存在"); } user.setRole(role); user.setCreateTime(DateUtil.getNow()); user.setUpdateTime(DateUtil.getNow()); int addResult = userService.addUser(user); if (addResult <= 0) { throw new ControllerException(ErrorCode.ERROR100); } } @DeleteMapping("/delete") @Operation(summary = "删除用户", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "id", description = "用户Id", required = true) public void delete(@RequestParam Integer id){ // 获取当前登录用户id int currenRoleId = SecurityUtils.getUserInfo().getRole().getId(); if (currenRoleId != 1) { // 只用角色id为0才可以删除和添加用户 throw new ControllerException(ErrorCode.ERROR400.getCode(), "用户无权限"); } int deleteResult = userService.deleteUser(id); if (deleteResult <= 0) { throw new ControllerException(ErrorCode.ERROR100); } } @GetMapping("/all") @Operation(summary = "查询用户", security = @SecurityRequirement(name = JwtUtils.HEADER)) public List all(){ // 获取当前登录用户id return userService.getAllUsers(); } /** * 分页查询用户 * * @param page 当前页 * @param count 每页查询数量 * @return 分页用户列表 */ @GetMapping("/users") @Operation(summary = "分页查询用户", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "page", description = "当前页", required = true) @Parameter(name = "count", description = "每页查询数量", required = true) public PageInfo users(int page, int count) { return userService.getUsers(page, count); } @RequestMapping("/changePushKey") @Operation(summary = "修改pushkey", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "userId", description = "用户Id", required = true) @Parameter(name = "pushKey", description = "新的pushKey", required = true) public void changePushKey(@RequestParam Integer userId,@RequestParam String pushKey) { // 获取当前登录用户id int currenRoleId = SecurityUtils.getUserInfo().getRole().getId(); WVPResult result = new WVPResult<>(); if (currenRoleId != 1) { // 只用角色id为0才可以删除和添加用户 throw new ControllerException(ErrorCode.ERROR400.getCode(), "用户无权限"); } int resetPushKeyResult = userService.changePushKey(userId,pushKey); if (resetPushKeyResult <= 0) { throw new ControllerException(ErrorCode.ERROR100); } } @PostMapping("/changePasswordForAdmin") @Operation(summary = "管理员修改普通用户密码", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "adminId", description = "管理员id", required = true) @Parameter(name = "userId", description = "用户id", required = true) @Parameter(name = "password", description = "新密码(未md5加密的密码)", required = true) public void changePasswordForAdmin(@RequestParam int userId, @RequestParam String password) { // 获取当前登录用户id LoginUser userInfo = SecurityUtils.getUserInfo(); if (userInfo == null) { throw new ControllerException(ErrorCode.ERROR100); } Role role = userInfo.getRole(); if (role != null && role.getId() == 1) { boolean result = userService.changePassword(userId, DigestUtils.md5DigestAsHex(password.getBytes())); if (!result) { throw new ControllerException(ErrorCode.ERROR100); } } } @PostMapping("/userInfo") @Operation(summary = "管理员修改普通用户密码") public LoginUser getUserInfo() { // 获取当前登录用户id LoginUser userInfo = SecurityUtils.getUserInfo(); if (userInfo == null) { throw new ControllerException(ErrorCode.ERROR100); } User user = userService.getUser(userInfo.getUsername(), userInfo.getPassword()); return new LoginUser(user, LocalDateTime.now()); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/web/custom/CameraChannelController.java ================================================ package com.genersoft.iot.vmp.web.custom; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.conf.DynamicTask; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.conf.security.JwtUtils; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.service.IMediaServerService; import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo; import com.genersoft.iot.vmp.service.ICloudRecordService; import com.genersoft.iot.vmp.service.bean.CloudRecordItem; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.service.bean.InviteErrorCode; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.streamPush.bean.StreamPush; import com.genersoft.iot.vmp.streamPush.service.IStreamPushPlayService; import com.genersoft.iot.vmp.streamPush.service.IStreamPushService; import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.utils.HttpUtils; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.StreamContent; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import com.genersoft.iot.vmp.vmanager.cloudRecord.bean.CloudRecordUrl; import com.genersoft.iot.vmp.web.custom.bean.*; import com.genersoft.iot.vmp.web.custom.service.CameraChannelService; import com.github.pagehelper.PageInfo; import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.web.bind.annotation.*; import org.springframework.web.context.request.async.DeferredResult; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.UUID; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @Tag(name = "第三方接口") @Slf4j @RestController @RequestMapping(value = "/api/sy") @ConditionalOnProperty(value = "sy.enable", havingValue = "true") @Hidden public class CameraChannelController { @Autowired private CameraChannelService channelService; @Autowired private UserSetting userSetting; @Autowired private IRedisCatchStorage redisCatchStorage; @Autowired private IMediaServerService mediaServerService; @Autowired private ICloudRecordService cloudRecordService; @Autowired private IStreamPushPlayService streamPushPlayService; @Autowired private DynamicTask dynamicTask; @Autowired private IStreamPushService streamPushService; @Value("${sy.ptz-control-time-interval}") private int ptzControlTimeInterval = 300; @GetMapping(value = "/camera/list") @ResponseBody @Operation(summary = "查询摄像机列表, 只查询当前虚拟组织下的", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "page", description = "当前页") @Parameter(name = "count", description = "每页查询数量") @Parameter(name = "groupAlias", description = "分组别名") @Parameter(name = "geoCoordSys", description = "坐标系类型:WGS84,GCJ02、BD09") @Parameter(name = "status", description = "摄像头状态") public PageInfo queryList(@RequestParam(required = false, value = "page", defaultValue = "1" )Integer page, @RequestParam(required = false, value = "count", defaultValue = "100")Integer count, String groupAlias, @RequestParam(required = false) String geoCoordSys, @RequestParam(required = false) Boolean status){ return channelService.queryList(page, count, groupAlias, status, geoCoordSys); } @GetMapping(value = "/camera/list-with-child") @ResponseBody @Operation(summary = "查询摄像机列表, 查询当前虚拟组织下以及全部子节点", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "page", description = "当前页") @Parameter(name = "count", description = "每页查询数量") @Parameter(name = "query", description = "查询内容") @Parameter(name = "sortName", description = "排序字段名") @Parameter(name = "order", description = "排序方式(true: 升序 或 false: 降序 )") @Parameter(name = "groupAlias", description = "分组别名") @Parameter(name = "geoCoordSys", description = "坐标系类型:WGS84,GCJ02、BD09") @Parameter(name = "status", description = "摄像头状态") public PageInfo queryListWithChild(@RequestParam(required = false, value = "page", defaultValue = "1" )Integer page, @RequestParam(required = false, value = "count", defaultValue = "100")Integer count, @RequestParam(required = false) String query, @RequestParam(required = false) String sortName, @RequestParam(required = false) Boolean order, @RequestParam(required = false) String groupAlias, @RequestParam(required = false) String geoCoordSys, @RequestParam(required = false) Boolean status){ if (ObjectUtils.isEmpty(query)) { query = null; } if (ObjectUtils.isEmpty(sortName)) { sortName = null; } if (ObjectUtils.isEmpty(order)) { order = null; } if (ObjectUtils.isEmpty(groupAlias)) { groupAlias = null; } return channelService.queryListWithChild(page, count, query, sortName, order, groupAlias, status, geoCoordSys); } @GetMapping(value = "/camera/cont-with-child") @ResponseBody @Operation(summary = "查询摄像机列表的总数和在线数", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "groupAlias", description = "分组别名") public List queryCountWithChild(String groupAlias){ return channelService.queryCountWithChild(groupAlias); } @GetMapping(value = "/camera/one") @ResponseBody @Operation(summary = "查询单个摄像头信息", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "通道编号") @Parameter(name = "deviceCode", description = "摄像头设备国标编号, 对于非国标摄像头可以不设置此参数") @Parameter(name = "geoCoordSys", description = "坐标系类型:WGS84,GCJ02、BD09") public CameraChannel getOne(String deviceId, @RequestParam(required = false) String deviceCode, @RequestParam(required = false) String geoCoordSys) { return channelService.queryOne(deviceId, deviceCode, geoCoordSys); } @GetMapping(value = "/camera/update") @ResponseBody @Operation(summary = "更新摄像头信息", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "通道编号") @Parameter(name = "deviceCode", description = "摄像头设备国标编号, 对于非国标摄像头可以不设置此参数") @Parameter(name = "name", description = "通道名称") @Parameter(name = "longitude", description = "经度") @Parameter(name = "latitude", description = "纬度") @Parameter(name = "geoCoordSys", description = "坐标系类型:WGS84,GCJ02、BD09") public void updateCamera(String deviceId, @RequestParam(required = false) String deviceCode, @RequestParam(required = false) String name, @RequestParam(required = false) Double longitude, @RequestParam(required = false) Double latitude, @RequestParam(required = false) String geoCoordSys) { channelService.updateCamera(deviceId, deviceCode, name, longitude, latitude, geoCoordSys); } @PostMapping(value = "/camera/list/ids") @ResponseBody @Operation(summary = "根据编号查询多个摄像头信息", security = @SecurityRequirement(name = JwtUtils.HEADER)) public List queryListByDeviceIds(@RequestBody IdsQueryParam param) { return channelService.queryListByDeviceIds(param.getDeviceIds(), param.getGeoCoordSys()); } @GetMapping(value = "/camera/list/box") @ResponseBody @Operation(summary = "根据矩形查询摄像头", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "minLongitude", description = "最小经度") @Parameter(name = "maxLongitude", description = "最大经度") @Parameter(name = "minLatitude", description = "最小纬度") @Parameter(name = "maxLatitude", description = "最大纬度") @Parameter(name = "level", description = "地图级别") @Parameter(name = "groupAlias", description = "分组别名") @Parameter(name = "geoCoordSys", description = "坐标系类型:WGS84,GCJ02、BD09") public List queryListInBox(Double minLongitude, Double maxLongitude, Double minLatitude, Double maxLatitude, @RequestParam(required = false) Integer level, String groupAlias, @RequestParam(required = false) String geoCoordSys) { return channelService.queryListInBox(minLongitude, maxLongitude, minLatitude, maxLatitude, level, groupAlias, geoCoordSys); } @PostMapping(value = "/camera/list/polygon") @ResponseBody @Operation(summary = "根据多边形查询摄像头", security = @SecurityRequirement(name = JwtUtils.HEADER)) public List queryListInPolygon(@RequestBody PolygonQueryParam param) { return channelService.queryListInPolygon(param.getPosition(), param.getGroupAlias(), param.getLevel(), param.getGeoCoordSys()); } @GetMapping(value = "/camera/list/circle") @ResponseBody @Operation(summary = "根据圆范围查询摄像头", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "centerLongitude", description = "圆心经度") @Parameter(name = "centerLatitude", description = "圆心纬度") @Parameter(name = "radius", description = "查询范围的半径,单位米") @Parameter(name = "level", description = "地图级别") @Parameter(name = "groupAlias", description = "分组别名") @Parameter(name = "geoCoordSys", description = "坐标系类型:WGS84,GCJ02、BD09") public List queryListInCircle(Double centerLongitude, Double centerLatitude, Double radius, String groupAlias, @RequestParam(required = false) String geoCoordSys, @RequestParam(required = false) Integer level) { return channelService.queryListInCircle(centerLongitude, centerLatitude, radius, level, groupAlias, geoCoordSys); } @GetMapping(value = "/camera/list/address") @ResponseBody @Operation(summary = "根据安装地址和监视方位获取摄像头", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "address", description = "安装地址") @Parameter(name = "directionType", description = "监视方位", required = false) @Parameter(name = "geoCoordSys", description = "坐标系类型:WGS84,GCJ02、BD09") public List queryListByAddressAndDirectionType(String address, @RequestParam(required = false) Integer directionType, @RequestParam(required = false) String geoCoordSys) { return channelService.queryListByAddressAndDirectionType(address, directionType, geoCoordSys); } @GetMapping(value = "/camera/control/play") @ResponseBody @Operation(summary = "播放摄像头", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "通道编号") @Parameter(name = "deviceCode", description = "摄像头设备国标编号, 对于非国标摄像头可以不设置此参数") public DeferredResult> play(HttpServletRequest request, String deviceId, @RequestParam(required = false) String deviceCode) { log.info("[SY-播放摄像头] API调用,deviceId:{} ,deviceCode:{} ",deviceId, deviceCode); DeferredResult> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue()); ErrorCallback callback = (code, msg, cameraStreamInfo) -> { if (code == InviteErrorCode.SUCCESS.getCode()) { StreamInfo streamInfo = cameraStreamInfo.getStreamInfo(); CommonGBChannel channel = cameraStreamInfo.getChannel(); WVPResult wvpResult = WVPResult.success(); if (cameraStreamInfo.getStreamInfo() != null) { if (userSetting.getUseSourceIpAsStreamIp()) { streamInfo=streamInfo.clone();//深拷贝 String host; try { URL url=new URL(request.getRequestURL().toString()); host=url.getHost(); } catch (MalformedURLException e) { host=request.getLocalAddr(); } streamInfo.changeStreamIp(host); } if (!ObjectUtils.isEmpty(streamInfo.getMediaServer().getTranscodeSuffix()) && !"null".equalsIgnoreCase(streamInfo.getMediaServer().getTranscodeSuffix())) { streamInfo.setStream(streamInfo.getStream() + "_" + streamInfo.getMediaServer().getTranscodeSuffix()); } CameraStreamContent cameraStreamContent = new CameraStreamContent(streamInfo); cameraStreamContent.setName(channel.getGbName()); if (channel.getGbPtzType() != null) { cameraStreamContent.setControlType( (channel.getGbPtzType() == 1 || channel.getGbPtzType() == 4 || channel.getGbPtzType() == 5) ? 1 : 0); }else { cameraStreamContent.setControlType(0); } wvpResult.setData(cameraStreamContent); }else { wvpResult.setCode(code); wvpResult.setMsg(msg); } result.setResult(wvpResult); }else { result.setResult(WVPResult.fail(code, msg)); } }; channelService.play(deviceId, deviceCode, callback); return result; } @GetMapping(value = "/camera/control/stop") @ResponseBody @Operation(summary = "停止播放摄像头", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "通道编号") @Parameter(name = "deviceCode", description = "摄像头设备国标编号, 对于非国标摄像头可以不设置此参数") public void stopPlay(String deviceId, @RequestParam(required = false) String deviceCode) { log.info("[SY-停止播放摄像头] API调用,deviceId:{} ,deviceCode:{} ",deviceId, deviceCode); channelService.stopPlay(deviceId, deviceCode); } @Operation(summary = "云台控制", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "通道编号") @Parameter(name = "deviceCode", description = "摄像头设备国标编号, 对于非国标摄像头可以不设置此参数") @Parameter(name = "command", description = "控制指令,允许值: left, right, up, down, upleft, upright, downleft, downright, zoomin, zoomout, stop", required = true) @Parameter(name = "speed", description = "速度(0-100)", required = true) @GetMapping("/camera/control/ptz") public DeferredResult> ptz(String deviceId, @RequestParam(required = false) String deviceCode, String command, Integer speed){ log.info("[SY-云台控制] API调用,deviceId:{} ,deviceCode:{} ,command:{} ,speed:{} ",deviceId, deviceCode, command, speed); DeferredResult> result = new DeferredResult<>(); result.onTimeout(()->{ WVPResult wvpResult = WVPResult.fail(ErrorCode.ERROR100.getCode(), "请求超时"); result.setResult(wvpResult); }); channelService.ptz(deviceId, deviceCode, command, speed, (code, msg, data) -> { WVPResult wvpResult = new WVPResult<>(); wvpResult.setCode(code); wvpResult.setMsg(msg); wvpResult.setData(data); result.setResult(wvpResult); }); // 设置时间间隔后自动发送停止 if (!command.equalsIgnoreCase("stop")) { dynamicTask.startDelay(UUID.randomUUID().toString(), () -> { channelService.ptz(deviceId, deviceCode, "stop", speed, (code, msg, data) -> {}); }, ptzControlTimeInterval); } return result; } @GetMapping(value = "/camera/list-for-mobile") @ResponseBody @Operation(summary = "查询移动设备摄像机列表", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "page", description = "当前页") @Parameter(name = "count", description = "每页查询数量") @Parameter(name = "topGroupAlias", description = "分组别名") public PageInfo queryListForMobile(@RequestParam(required = false, value = "page", defaultValue = "1" )Integer page, @RequestParam(required = false, value = "count", defaultValue = "100")Integer count, @RequestParam(required = false) String topGroupAlias){ return channelService.queryListForMobile(page, count, topGroupAlias); } @Operation(summary = "获取推流播放地址", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "app", description = "应用名", required = true) @Parameter(name = "stream", description = "流id", required = true) @Parameter(name = "callId", description = "推流时携带的自定义鉴权ID", required = true) @GetMapping(value = "/push/play") @ResponseBody public DeferredResult> getStreamInfoByAppAndStream(HttpServletRequest request, String app, String stream, String callId){ StreamPush streamPush = streamPushService.getPush(app, stream); Assert.notNull(streamPush, "地址不存在"); // 权限校验 StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(app, stream); if (streamAuthorityInfo == null || streamAuthorityInfo.getCallId() == null || !streamAuthorityInfo.getCallId().equals(callId)) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "播放地址鉴权失败"); } DeferredResult> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue()); result.onTimeout(()->{ WVPResult fail = WVPResult.fail(ErrorCode.ERROR100.getCode(), "等待推流超时"); result.setResult(fail); }); streamPushPlayService.start(streamPush.getId(), (code, msg, streamInfo) -> { if (code == 0 && streamInfo != null) { streamInfo=streamInfo.clone();//深拷贝 String host; try { URL url=new URL(request.getRequestURL().toString()); host=url.getHost(); } catch (MalformedURLException e) { host=request.getLocalAddr(); } streamInfo.changeStreamIp(host); WVPResult success = WVPResult.success(new StreamContent(streamInfo)); result.setResult(success); } }, null, null); return result; } @Operation(summary = "获取推流播放地址(不做检查)", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "app", description = "应用名", required = true) @Parameter(name = "stream", description = "流id", required = true) @Parameter(name = "callId", description = "推流时携带的自定义鉴权ID", required = true) @GetMapping(value = "/push/play-without-check") @ResponseBody public StreamContent getStreamInfoByAppAndStreamWithoutCheck(HttpServletRequest request, String app, String stream, String callId){ MediaServer mediaServer = mediaServerService.getDefaultMediaServer(); Assert.notNull(mediaServer, "流媒体服务器不存在"); StreamInfo streamInfo = mediaServerService.getStreamInfoByAppAndStream(mediaServer, app, stream, null, callId); streamInfo=streamInfo.clone();//深拷贝 String host; try { URL url=new URL(request.getRequestURL().toString()); host=url.getHost(); } catch (MalformedURLException e) { host=request.getLocalAddr(); } streamInfo.changeStreamIp(host); return new StreamContent(streamInfo); } @ResponseBody @GetMapping("/record/collect/add") @Operation(summary = "添加收藏") @Parameter(name = "app", description = "应用名", required = false) @Parameter(name = "stream", description = "流ID", required = false) @Parameter(name = "mediaServerId", description = "流媒体ID", required = false) @Parameter(name = "startTime", description = "鉴权ID", required = false) @Parameter(name = "endTime", description = "鉴权ID", required = false) @Parameter(name = "callId", description = "鉴权ID", required = false) @Parameter(name = "recordId", description = "录像记录的ID,用于精准收藏一个视频文件", required = false) public int addCollect(@RequestParam(required = false) String app, @RequestParam(required = false) String stream, @RequestParam(required = false) String mediaServerId, @RequestParam(required = false) String startTime, @RequestParam(required = false) String endTime, @RequestParam(required = false) String callId, @RequestParam(required = false) Integer recordId) { log.info("[云端录像] 添加收藏,app={},stream={},mediaServerId={},startTime={},endTime={},callId={},recordId={}", app, stream, mediaServerId, startTime, endTime, callId, recordId); if (recordId != null) { return cloudRecordService.changeCollectById(recordId, true); } else { return cloudRecordService.changeCollect(true, app, stream, mediaServerId, startTime, endTime, callId); } } @ResponseBody @GetMapping("/record/collect/delete") @Operation(summary = "移除收藏") @Parameter(name = "app", description = "应用名", required = false) @Parameter(name = "stream", description = "流ID", required = false) @Parameter(name = "mediaServerId", description = "流媒体ID", required = false) @Parameter(name = "startTime", description = "鉴权ID", required = false) @Parameter(name = "endTime", description = "鉴权ID", required = false) @Parameter(name = "callId", description = "鉴权ID", required = false) @Parameter(name = "recordId", description = "录像记录的ID,用于精准精准移除一个视频文件的收藏", required = false) public int deleteCollect(@RequestParam(required = false) String app, @RequestParam(required = false) String stream, @RequestParam(required = false) String mediaServerId, @RequestParam(required = false) String startTime, @RequestParam(required = false) String endTime, @RequestParam(required = false) String callId, @RequestParam(required = false) Integer recordId) { log.info("[云端录像] 移除收藏,app={},stream={},mediaServerId={},startTime={},endTime={},callId={},recordId={}", app, stream, mediaServerId, startTime, endTime, callId, recordId); if (recordId != null) { return cloudRecordService.changeCollectById(recordId, false); } else { return cloudRecordService.changeCollect(false, app, stream, mediaServerId, startTime, endTime, callId); } } /************************* 以下这些接口只适合wvp和zlm部署在同一台服务器的情况,且wvp只有一个zlm节点的情况 ***************************************/ /** * 下载指定录像文件的压缩包 * @param app 应用名 * @param stream 流ID * @param callId 每次录像的唯一标识,置空则查询全部流媒体 */ @ResponseBody @GetMapping("/record/zip") public void downloadZipFile(HttpServletResponse response, @RequestParam(required = false) String app, @RequestParam(required = false) String stream, @RequestParam(required = false) String callId ) { log.info("[下载指定录像文件的压缩包] 查询 app->{}, stream->{}, callId->{}", app, stream, callId); if (app != null && ObjectUtils.isEmpty(app.trim())) { app = null; } if (stream != null && ObjectUtils.isEmpty(stream.trim())) { stream = null; } if (callId != null && ObjectUtils.isEmpty(callId.trim())) { callId = null; } // 设置响应头 response.setContentType("application/zip"); response.setCharacterEncoding("UTF-8"); if (stream != null && callId != null) { response.addHeader("Content-Disposition", "attachment;filename=" + stream + "_" + callId + ".zip"); } List cloudRecordItemList = cloudRecordService.getUrlList(app, stream, callId); if (ObjectUtils.isEmpty(cloudRecordItemList)) { log.warn("[下载指定录像文件的压缩包] 未找到录像文件,app->{}, stream->{}, callId->{}", app, stream, callId); return; } try (ZipOutputStream zos = new ZipOutputStream(response.getOutputStream())) { for (CloudRecordUrl recordUrl : cloudRecordItemList) { try { zos.putNextEntry(new ZipEntry(recordUrl.getFileName())); boolean downloadSuccess = HttpUtils.downLoadFile(recordUrl.getDownloadUrl(), zos); if (!downloadSuccess) { log.warn("[下载指定录像文件的压缩包] 下载文件失败: {}", recordUrl.getDownloadUrl()); zos.closeEntry(); continue; } zos.closeEntry(); } catch (Exception e) { log.error("[下载指定录像文件的压缩包] 处理文件失败: {}, 错误: {}", recordUrl.getFileName(), e.getMessage()); // 继续处理下一个文件 } } } catch (IOException e) { log.error("[下载指定录像文件的压缩包] 创建压缩包失败,查询 app->{}, stream->{}, callId->{}", app, stream, callId, e); } } /** * * @param query 检索内容 * @param app 应用名 * @param stream 流ID * @param startTime 开始时间(yyyy-MM-dd HH:mm:ss) * @param endTime 结束时间(yyyy-MM-dd HH:mm:ss) * @param mediaServerId 流媒体ID,置空则查询全部流媒体 * @param callId 每次录像的唯一标识,置空则查询全部流媒体 * @param remoteHost 拼接播放地址时使用的远程地址 */ @ResponseBody @GetMapping("/record/list-url") @Operation(summary = "分页查询云端录像", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "query", description = "检索内容", required = false) @Parameter(name = "app", description = "应用名", required = false) @Parameter(name = "stream", description = "流ID", required = false) @Parameter(name = "page", description = "当前页", required = true) @Parameter(name = "count", description = "每页查询数量", required = true) @Parameter(name = "startTime", description = "开始时间(yyyy-MM-dd HH:mm:ss)", required = false) @Parameter(name = "endTime", description = "结束时间(yyyy-MM-dd HH:mm:ss)", required = false) @Parameter(name = "mediaServerId", description = "流媒体ID,置空则查询全部流媒体", required = false) @Parameter(name = "callId", description = "每次录像的唯一标识,置空则查询全部流媒体", required = false) public PageInfo getListWithUrl(HttpServletRequest request, @RequestParam(required = false) String query, @RequestParam(required = false) String app, @RequestParam(required = false) String stream, @RequestParam int page, @RequestParam int count, @RequestParam(required = false) String startTime, @RequestParam(required = false) String endTime, @RequestParam(required = false) String mediaServerId, @RequestParam(required = false) String callId, @RequestParam(required = false) String remoteHost ) { log.info("[云端录像] 查询URL app->{}, stream->{}, mediaServerId->{}, page->{}, count->{}, startTime->{}, endTime->{}, callId->{}", app, stream, mediaServerId, page, count, startTime, endTime, callId); List mediaServers; if (!ObjectUtils.isEmpty(mediaServerId)) { mediaServers = new ArrayList<>(); MediaServer mediaServer = mediaServerService.getOne(mediaServerId); if (mediaServer == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到流媒体: " + mediaServerId); } mediaServers.add(mediaServer); } else { mediaServers = null; } if (query != null && ObjectUtils.isEmpty(query.trim())) { query = null; } if (app != null && ObjectUtils.isEmpty(app.trim())) { app = null; } if (stream != null && ObjectUtils.isEmpty(stream.trim())) { stream = null; } if (startTime != null && ObjectUtils.isEmpty(startTime.trim())) { startTime = null; } if (endTime != null && ObjectUtils.isEmpty(endTime.trim())) { endTime = null; } if (callId != null && ObjectUtils.isEmpty(callId.trim())) { callId = null; } MediaServer mediaServer = mediaServerService.getDefaultMediaServer(); if (mediaServer == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到流媒体节点"); } if (remoteHost == null) { remoteHost = request.getScheme() + "://" + request.getLocalAddr() + ":" + (request.getScheme().equals("https") ? mediaServer.getHttpSSlPort() : mediaServer.getHttpPort()); } PageInfo cloudRecordItemPageInfo = cloudRecordService.getList(page, count, query, app, stream, startTime, endTime, mediaServers, callId, null); PageInfo cloudRecordUrlPageInfo = new PageInfo<>(); if (!ObjectUtils.isEmpty(cloudRecordItemPageInfo)) { cloudRecordUrlPageInfo.setPageNum(cloudRecordItemPageInfo.getPageNum()); cloudRecordUrlPageInfo.setPageSize(cloudRecordItemPageInfo.getPageSize()); cloudRecordUrlPageInfo.setSize(cloudRecordItemPageInfo.getSize()); cloudRecordUrlPageInfo.setEndRow(cloudRecordItemPageInfo.getEndRow()); cloudRecordUrlPageInfo.setStartRow(cloudRecordItemPageInfo.getStartRow()); cloudRecordUrlPageInfo.setPages(cloudRecordItemPageInfo.getPages()); cloudRecordUrlPageInfo.setPrePage(cloudRecordItemPageInfo.getPrePage()); cloudRecordUrlPageInfo.setNextPage(cloudRecordItemPageInfo.getNextPage()); cloudRecordUrlPageInfo.setIsFirstPage(cloudRecordItemPageInfo.isIsFirstPage()); cloudRecordUrlPageInfo.setIsLastPage(cloudRecordItemPageInfo.isIsLastPage()); cloudRecordUrlPageInfo.setHasPreviousPage(cloudRecordItemPageInfo.isHasPreviousPage()); cloudRecordUrlPageInfo.setHasNextPage(cloudRecordItemPageInfo.isHasNextPage()); cloudRecordUrlPageInfo.setNavigatePages(cloudRecordItemPageInfo.getNavigatePages()); cloudRecordUrlPageInfo.setNavigateFirstPage(cloudRecordItemPageInfo.getNavigateFirstPage()); cloudRecordUrlPageInfo.setNavigateLastPage(cloudRecordItemPageInfo.getNavigateLastPage()); cloudRecordUrlPageInfo.setNavigatepageNums(cloudRecordItemPageInfo.getNavigatepageNums()); cloudRecordUrlPageInfo.setTotal(cloudRecordItemPageInfo.getTotal()); List cloudRecordUrlList = new ArrayList<>(cloudRecordItemPageInfo.getList().size()); List cloudRecordItemList = cloudRecordItemPageInfo.getList(); for (CloudRecordItem cloudRecordItem : cloudRecordItemList) { CloudRecordUrl cloudRecordUrl = new CloudRecordUrl(); cloudRecordUrl.setId(cloudRecordItem.getId()); cloudRecordUrl.setDownloadUrl(remoteHost + "/index/api/downloadFile?file_path=" + cloudRecordItem.getFilePath() + "&save_name=" + cloudRecordItem.getStream() + "_" + cloudRecordItem.getCallId() + "_" + DateUtil.timestampMsToUrlToyyyy_MM_dd_HH_mm_ss((long)cloudRecordItem.getStartTime())); cloudRecordUrl.setPlayUrl(remoteHost + "/index/api/downloadFile?file_path=" + cloudRecordItem.getFilePath()); cloudRecordUrlList.add(cloudRecordUrl); } cloudRecordUrlPageInfo.setList(cloudRecordUrlList); } return cloudRecordUrlPageInfo; } @GetMapping(value = "/forceClose") @ResponseBody @Operation(summary = "强制停止推流", security = @SecurityRequirement(name = JwtUtils.HEADER)) public void stop(String app, String stream){ streamPushPlayService.stop(app, stream); } @GetMapping(value = "/camera/meeting/list") @ResponseBody @Operation(summary = "查询会议设备", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "topGroupAlias", description = "分组别名") public List queryMeetingChannelList(String topGroupAlias){ return channelService.queryMeetingChannelList(topGroupAlias); } @GetMapping(value = "/test") @ResponseBody public SYMember test(String device){ return channelService.getMember(device); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/web/custom/bean/CameraChannel.java ================================================ package com.genersoft.iot.vmp.web.custom.bean; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.Setter; @Getter @Setter @Schema(description = "摄像头信息") public class CameraChannel extends CommonGBChannel { @Schema(description = "摄像头设备国标编号") private String deviceCode; @Schema(description = "图标路径") private String icon; /** * 分组别名 */ @Schema(description = "所属组织结构别名") private String groupAlias; /** * 分组所属业务分组别名 */ @Schema(description = "所属业务分组别名") private String topGroupGAlias; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/web/custom/bean/CameraCount.java ================================================ package com.genersoft.iot.vmp.web.custom.bean; import lombok.Data; @Data public class CameraCount { private String groupAlias; private String deviceId; private Long allCount; private Long onlineCount; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/web/custom/bean/CameraGroup.java ================================================ package com.genersoft.iot.vmp.web.custom.bean; import com.genersoft.iot.vmp.gb28181.bean.Group; import lombok.Getter; import java.util.ArrayList; import java.util.List; public class CameraGroup extends Group { @Getter private CameraGroup parent; @Getter private final List child = new ArrayList<>(); public void setParent(CameraGroup parent) { if (parent == null) { return; } this.parent = parent; parent.addChild(this); } public void addChild(CameraGroup child) { if (child == null) { return; } this.child.add(child); if (this.parent != null) { this.parent.addChild(child); } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/web/custom/bean/CameraStreamContent.java ================================================ package com.genersoft.iot.vmp.web.custom.bean; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.vmanager.bean.StreamContent; import lombok.Getter; import lombok.Setter; @Getter @Setter public class CameraStreamContent extends StreamContent { public CameraStreamContent(StreamInfo streamInfo) { super(streamInfo); } private String name; // 0不可动,1可动 private Integer controlType; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/web/custom/bean/CameraStreamInfo.java ================================================ package com.genersoft.iot.vmp.web.custom.bean; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import lombok.Getter; import lombok.Setter; @Getter @Setter public class CameraStreamInfo { private CommonGBChannel channel; private StreamInfo streamInfo; public CameraStreamInfo(CommonGBChannel channel, StreamInfo streamInfo) { this.channel = channel; this.streamInfo = streamInfo; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/web/custom/bean/ChannelParam.java ================================================ package com.genersoft.iot.vmp.web.custom.bean; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @Data @Schema(description = "通道信息") public class ChannelParam { @Schema(description = "摄像头设备国标编号, 对于非国标摄像头可以不设置此参数") private String deviceCode; @Schema(description = "通道编号") private String deviceId; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/web/custom/bean/IdsQueryParam.java ================================================ package com.genersoft.iot.vmp.web.custom.bean; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.util.List; @Data @Schema(description = "根据多个ID获取摄像头列表") public class IdsQueryParam { @Schema(description = "通道编号列表") private List deviceIds; @Schema(description = "坐标系类型:WGS84,GCJ02、BD09") private String geoCoordSys; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/web/custom/bean/Point.java ================================================ package com.genersoft.iot.vmp.web.custom.bean; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @Data @Schema(description = "坐标") public class Point { private double lng; private double lat; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/web/custom/bean/PolygonQueryParam.java ================================================ package com.genersoft.iot.vmp.web.custom.bean; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.util.List; @Data @Schema(description = "多边形检索摄像头参数") public class PolygonQueryParam { @Schema(description = "多边形位置,格式: [{'lng':116.32, 'lat': 39: 39.2}, {'lng':115.32, 'lat': 39: 38.2}, {'lng':125.32, 'lat': 39: 38.2}]") private List position; @Schema(description = "地图级别") private Integer level; @Schema(description = "分组别名") private String groupAlias; @Schema(description = "坐标系类型:WGS84,GCJ02、BD09") private String geoCoordSys; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/web/custom/bean/SYMember.java ================================================ package com.genersoft.iot.vmp.web.custom.bean; import lombok.Getter; import lombok.Setter; @Setter @Getter public class SYMember { private String no; private Long unicodeNo; private Long blockId; private String unitNo; private String terminalMemberStatus; private String channelDeviceId; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/web/custom/conf/CachedBodyHttpServletRequest.java ================================================ package com.genersoft.iot.vmp.web.custom.conf; import jakarta.servlet.ReadListener; import jakarta.servlet.ServletInputStream; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequestWrapper; import lombok.extern.slf4j.Slf4j; import java.io.*; import java.nio.charset.StandardCharsets; /** * 自定义请求包装器,用于缓存请求体内容 * 解决流只能读取一次的问题 */ @Slf4j public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper { private byte[] cachedBody; private String cachedBodyString; public CachedBodyHttpServletRequest(HttpServletRequest request) { super(request); } @Override public ServletInputStream getInputStream() throws IOException { if (cachedBody == null) { cacheInputStream(); } return new CachedBodyServletInputStream(cachedBody); } @Override public BufferedReader getReader() throws IOException { if (cachedBodyString == null) { if (cachedBody == null) { cacheInputStream(); } cachedBodyString = new String(cachedBody, StandardCharsets.UTF_8); } return new BufferedReader(new StringReader(cachedBodyString)); } /** * 获取缓存的请求体内容 */ public String getCachedBody() { if (cachedBodyString == null) { if (cachedBody == null) { try { cacheInputStream(); } catch (IOException e) { log.warn("缓存请求体失败: {}", e.getMessage()); return ""; } } if (cachedBody != null) { cachedBodyString = new String(cachedBody, StandardCharsets.UTF_8); } else { cachedBodyString = ""; } } return cachedBodyString; } /** * 获取缓存的请求体字节数组 */ public byte[] getCachedBodyBytes() { if (cachedBody == null) { try { cacheInputStream(); } catch (IOException e) { log.warn("缓存请求体失败: {}", e.getMessage()); return new byte[0]; } } return cachedBody != null ? cachedBody : new byte[0]; } private void cacheInputStream() throws IOException { try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); InputStream inputStream = super.getInputStream()) { byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { baos.write(buffer, 0, bytesRead); } cachedBody = baos.toByteArray(); log.debug("成功缓存请求体,长度: {}", cachedBody.length); } catch (Exception e) { log.error("缓存请求体时发生异常: ", e); cachedBody = new byte[0]; } } /** * 自定义 ServletInputStream 实现 */ private static class CachedBodyServletInputStream extends ServletInputStream { private final ByteArrayInputStream inputStream; public CachedBodyServletInputStream(byte[] body) { // 处理null值情况 this.inputStream = new ByteArrayInputStream(body != null ? body : new byte[0]); } @Override public boolean isFinished() { return inputStream.available() == 0; } @Override public boolean isReady() { return true; } @Override public void setReadListener(ReadListener readListener) { // 不需要实现 } @Override public int read() throws IOException { return inputStream.read(); } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/web/custom/conf/SignAuthenticationFilter.java ================================================ package com.genersoft.iot.vmp.web.custom.conf; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.HexUtil; import cn.hutool.crypto.SmUtil; import cn.hutool.crypto.symmetric.SM4; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.http.MediaType; import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; import org.springframework.web.filter.OncePerRequestFilter; import javax.sip.message.Response; import java.io.IOException; import java.io.PrintWriter; import java.util.Map; import java.util.Set; import java.util.TreeSet; /** * sign token 过滤器 */ @Slf4j @Component @ConditionalOnProperty(value = "sy.enable", havingValue = "true") public class SignAuthenticationFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest servletRequest, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { // 忽略登录请求的token验证 String requestURI = servletRequest.getRequestURI(); // 包装原始请求,缓存请求体 CachedBodyHttpServletRequest request = new CachedBodyHttpServletRequest(servletRequest); if (!requestURI.startsWith("/api/sy")) { chain.doFilter(request, response); return; } // if (request.getParameter("ccerty") != null) { // chain.doFilter(request, response); // return; // } // 设置响应内容类型 response.setContentType("application/json;charset=UTF-8"); try { String sign = request.getParameter("sign"); String appKey = request.getParameter("appKey"); String accessToken = request.getParameter("accessToken"); String timestampStr = request.getParameter("timestamp"); if (sign == null || appKey == null || accessToken == null || timestampStr == null) { log.info("[SY-接口验签] 缺少关键参数:sign/appKey/accessToken/timestamp, 请求地址: {} ", requestURI); response.setStatus(Response.OK); PrintWriter out = response.getWriter(); out.println(getErrorResult(1, "参数非法")); out.close(); return; } // 添加空值检查 if (SyTokenManager.INSTANCE.appMap == null || SyTokenManager.INSTANCE.appMap.get(appKey) == null) { log.info("[SY-接口验签] appKey {} 对应的 secret 不存在, 请求地址: {} ", appKey, requestURI); response.setStatus(Response.OK); PrintWriter out = response.getWriter(); out.println(getErrorResult(1, "参数非法")); out.close(); return; } Map parameterMap = request.getParameterMap(); // 参数排序 Set paramKeys = new TreeSet<>(parameterMap.keySet()); // 拼接签名信息 // 参数拼接 StringBuilder beforeSign = new StringBuilder(); for (String paramKey : paramKeys) { if (paramKey.equals("sign")) { continue; } // 添加数组长度检查 String[] values = parameterMap.get(paramKey); if (values != null && values.length > 0) { beforeSign.append(paramKey).append(values[0]); } } // 如果是post请求的json消息,拼接body字符串 if (request.getContentLength() > 0 && request.getMethod().equalsIgnoreCase("POST") && request.getContentType() != null && request.getContentType().equalsIgnoreCase(MediaType.APPLICATION_JSON_VALUE)) { // 读取body内容 - 使用自定义缓存机制 String requestBody = request.getCachedBody(); if (!ObjectUtils.isEmpty(requestBody)) { beforeSign.append(requestBody); log.debug("[SY-接口验签] 读取到请求体内容,长度: {}", requestBody.length()); } else { log.warn("[SY-接口验签] 请求体内容为空"); } } // 添加空值检查 String secret = SyTokenManager.INSTANCE.appMap.get(appKey); if (secret == null) { log.info("[SY-接口验签] 无法获取appKey {} 对应的 secret, 请求地址: {} ", appKey, requestURI); response.setStatus(Response.OK); PrintWriter out = response.getWriter(); out.println(getErrorResult(1, "参数非法")); out.close(); return; } beforeSign.append(secret); // 生成签名 String buildSign = SmUtil.sm3(beforeSign.toString()); if (!buildSign.equals(sign)) { log.info("[SY-接口验签] 失败,加密前内容: {}, 请求地址: {} ", beforeSign, requestURI); response.setStatus(Response.OK); PrintWriter out = response.getWriter(); out.println(getErrorResult(2, "签名错误")); out.close(); return; } // 验证请求时间戳 long timestamp = Long.parseLong(timestampStr); long currentTimeMillis = System.currentTimeMillis(); // 添加空值检查 if (SyTokenManager.INSTANCE.expires == null) { log.info("[SY-接口验签] expires配置为空, 请求地址: {} ", requestURI); response.setStatus(Response.OK); PrintWriter out = response.getWriter(); out.println(getErrorResult(2, "签名错误")); out.close(); return; } if (currentTimeMillis > SyTokenManager.INSTANCE.expires * 60 * 1000 + timestamp ) { log.info("[SY-接口验签] 时间戳已经过期, 请求时间戳:{}, 当前时间: {}, 过期时间: {}, 请求地址: {} ", timestamp, currentTimeMillis, timestamp + SyTokenManager.INSTANCE.expires * 60 * 1000, requestURI); response.setStatus(Response.OK); PrintWriter out = response.getWriter(); out.println(getErrorResult(3, "接口己过期")); out.close(); return; } // accessToken校验 // 添加空值检查 if (SyTokenManager.INSTANCE.adminToken == null) { log.info("[SY-接口验签] adminToken配置为空, 请求地址: {} ", requestURI); response.setStatus(Response.OK); PrintWriter out = response.getWriter(); out.println(getErrorResult(2, "签名错误")); out.close(); return; } if (accessToken.equals(SyTokenManager.INSTANCE.adminToken)) { log.info("[SY-接口验签] adminToken已经默认放行, 请求地址: {} ", requestURI); chain.doFilter(request, response); return; }else { // 添加空值检查 if (SyTokenManager.INSTANCE.sm4Key == null) { log.info("[SY-接口验签] sm4Key配置为空, 请求地址: {} ", requestURI); response.setStatus(Response.OK); PrintWriter out = response.getWriter(); out.println(getErrorResult(2, "签名错误")); out.close(); return; } // 对token进行解密 SM4 sm4 = SmUtil.sm4(HexUtil.decodeHex(SyTokenManager.INSTANCE.sm4Key)); String decryptStr = sm4.decryptStr(accessToken, CharsetUtil.CHARSET_UTF_8); if (decryptStr == null) { log.info("[SY-接口验签] accessToken解密失败, 请求地址: {} ", requestURI); response.setStatus(Response.OK); PrintWriter out = response.getWriter(); out.println(getErrorResult(2, "签名错误")); out.close(); return; } JSONObject jsonObject = JSON.parseObject(decryptStr); Long expirationTime = jsonObject.getLong("expirationTime"); if (expirationTime == null || expirationTime < System.currentTimeMillis()) { log.info("[SY-接口验签] accessToken 已经过期, 请求地址: {} ", requestURI); response.setStatus(Response.OK); PrintWriter out = response.getWriter(); out.println(getErrorResult(4, "token已过期或错误")); out.close(); return; } } }catch (NumberFormatException e) { log.info("[SY-接口验签] 时间戳格式错误, 请求地址: {} ", requestURI); response.setStatus(Response.OK); if (!response.isCommitted()) { PrintWriter out = response.getWriter(); out.println(getErrorResult(2, "签名错误")); out.close(); } return; }catch (Exception e) { log.info("[SY-接口验签] 读取body失败, 请求地址: {} ", requestURI, e); response.setStatus(Response.OK); if (!response.isCommitted()) { PrintWriter out = response.getWriter(); out.println(getErrorResult(2, "签名错误")); out.close(); } return; } chain.doFilter(request, response); } private String getErrorResult(Integer code, String message) { WVPResult wvpResult = new WVPResult<>(); wvpResult.setCode(code); wvpResult.setMsg(message); return JSON.toJSONString(wvpResult); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/web/custom/conf/SyTokenManager.java ================================================ package com.genersoft.iot.vmp.web.custom.conf; import java.util.HashMap; import java.util.Map; public enum SyTokenManager { INSTANCE; /** * 普通用户 app Key 和 secret */ public final Map appMap = new HashMap<>(); /** * 管理员专属token */ public String adminToken; /** * sm4密钥 */ public String sm4Key; /** * 接口有效时长,单位分钟 */ public Long expires; } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/web/custom/service/CameraChannelService.java ================================================ package com.genersoft.iot.vmp.web.custom.service; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.conf.DynamicTask; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import com.genersoft.iot.vmp.gb28181.bean.FrontEndControlCodeForPTZ; import com.genersoft.iot.vmp.gb28181.bean.Group; import com.genersoft.iot.vmp.gb28181.bean.MobilePosition; import com.genersoft.iot.vmp.gb28181.dao.CommonGBChannelMapper; import com.genersoft.iot.vmp.gb28181.dao.GroupMapper; import com.genersoft.iot.vmp.gb28181.event.channel.ChannelEvent; import com.genersoft.iot.vmp.gb28181.event.subscribe.mobilePosition.MobilePositionEvent; import com.genersoft.iot.vmp.gb28181.service.IGbChannelControlService; import com.genersoft.iot.vmp.gb28181.service.IGbChannelPlayService; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.utils.Coordtransform; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.web.custom.bean.*; import com.genersoft.iot.vmp.web.custom.conf.SyTokenManager; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import com.github.xiaoymin.knife4j.core.util.Assert; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.event.EventListener; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import java.util.*; @Slf4j @Service @ConditionalOnProperty(value = "sy.enable", havingValue = "true") public class CameraChannelService implements CommandLineRunner { private final String REDIS_GPS_MESSAGE = "VM_MSG_MOBILE_GPS"; private final String REDIS_CHANNEL_MESSAGE = "VM_MSG_MOBILE_CHANNEL"; private final String REDIS_MEMBER_STATUS_MESSAGE = "VM_MSG_MEMBER_STATUS_CHANNEL"; private final String MOBILE_CHANNEL_PREFIX = "nationalStandardMobileTerminal_"; private final String DELAY_TASK_KEY = "DELAY_TASK_KEY_"; @Autowired private CommonGBChannelMapper channelMapper; @Autowired private GroupMapper groupMapper; @Autowired private RedisTemplate redisTemplate; @Autowired private RedisTemplate redisTemplateForString; @Autowired private IGbChannelPlayService channelPlayService; @Autowired private IGbChannelControlService channelControlService; @Autowired private UserSetting userSetting; @Autowired private DynamicTask dynamicTask; @Override public void run(String... args) { // 启动时获取全局token String taskKey = UUID.randomUUID().toString(); if (!refreshToken()) { log.info("[SY-读取Token]失败,30秒后重试"); dynamicTask.startDelay(taskKey, ()->{ this.run(args); }, 30000); }else { log.info("[SY-读取Token] 成功"); } } private boolean refreshToken() { String adminToken = redisTemplateForString.opsForValue().get("SYSTEM_ACCESS_TOKEN"); if (adminToken == null) { log.warn("[SY读取TOKEN] SYSTEM_ACCESS_TOKEN 读取失败"); return false; } SyTokenManager.INSTANCE.adminToken = adminToken; String sm4Key = redisTemplateForString.opsForValue().get("SYSTEM_SM4_KEY"); if (sm4Key == null) { log.warn("[SY读取TOKEN] SYSTEM_SM4_KEY 读取失败"); return false; } SyTokenManager.INSTANCE.sm4Key = sm4Key; JSONObject appJson = (JSONObject)redisTemplate.opsForValue().get("SYSTEM_APPKEY"); if (appJson == null) { log.warn("[SY读取TOKEN] SYSTEM_APPKEY 读取失败"); return false; } SyTokenManager.INSTANCE.appMap.put(appJson.getString("appKey"), appJson.getString("appSecret")); JSONObject timeJson = (JSONObject)redisTemplate.opsForValue().get("sys_INTERFACE_VALID_TIME"); if (timeJson == null) { log.warn("[SY读取TOKEN] sys_INTERFACE_VALID_TIME 读取失败"); return false; } SyTokenManager.INSTANCE.expires = timeJson.getLong("systemValue"); return true; } // 监听通道变化,如果是移动设备则发送redis消息 @EventListener public void onApplicationEvent(ChannelEvent event) { List channels = event.getChannels(); if (channels.isEmpty()) { return; } List resultListForAdd = new ArrayList<>(); List resultListForDelete = new ArrayList<>(); List resultListForUpdate = new ArrayList<>(); List resultListForOnline = new ArrayList<>(); List resultListForOffline = new ArrayList<>(); Map delayChannelMap = new HashMap<>(); List memberList = new ArrayList<>(); switch (event.getMessageType()) { case UPDATE: List oldChannelList = event.getOldChannels(); List channelList = event.getChannels(); // 更新操作 if (oldChannelList == null || oldChannelList.isEmpty()) { // 无旧设备则不需要判断, 目前只有分组或行政区划转换为通道信息时没有旧的通道信息,这两个类型也是不需要发送通知的,直接忽略即可 break; } // 需要比对旧数据,看看是否是新增的移动设备或者取消的移动设备 // 将 channelList 转为以 gbDeviceId 为 key 的 Map Map oldChannelMap = new HashMap<>(); for (CommonGBChannel channel : oldChannelList) { if (channel != null && channel.getGbDeviceId() != null) { oldChannelMap.put(channel.getGbDeviceId(), channel); } } for (CommonGBChannel channel : channelList) { if (channel.getGbPtzType() != null && channel.getGbPtzType() == 99) { CommonGBChannel oldChannel = oldChannelMap.get(channel.getGbDeviceId()); if (channel.getGbStatus() == null) { channel.setGbStatus(oldChannel.getGbStatus()); } if (oldChannel != null) { if (oldChannel.getGbPtzType() != null && oldChannel.getGbPtzType() == 99) { resultListForUpdate.add(channel); // 如果状态变化发送消息 if (!Objects.equals(oldChannel.getGbStatus(), channel.getGbStatus())) { SYMember member = getMember(channel.getGbDeviceId()); if (member != null) { if ("ON".equals(channel.getGbStatus())) { member.setTerminalMemberStatus("ONLINE"); }else { member.setTerminalMemberStatus("OFFLINE"); } memberList.add(member); } } }else { resultListForAdd.add(channel); if ("ON".equals(channel.getGbStatus())) { delayChannelMap.put(channel.getGbDeviceId(), channel); } } }else { resultListForAdd.add(channel); if ("ON".equals(channel.getGbStatus())) { delayChannelMap.put(channel.getGbDeviceId(), channel); } } }else { CommonGBChannel oldChannel = oldChannelMap.get(channel.getGbDeviceId()); if (oldChannel != null && oldChannel.getGbPtzType() != null && oldChannel.getGbPtzType() == 99) { CameraChannel cameraChannel = new CameraChannel(); cameraChannel.setGbDeviceId(channel.getGbDeviceId()); resultListForDelete.add(cameraChannel); SYMember member = getMember(cameraChannel.getGbDeviceId()); if (member != null) { member.setTerminalMemberStatus("OFFLINE"); memberList.add(member); } } } } break; case DEL: for (CommonGBChannel channel : channels) { if (channel.getGbPtzType() != null && channel.getGbPtzType() == 99) { CameraChannel cameraChannel = new CameraChannel(); cameraChannel.setGbDeviceId(channel.getGbDeviceId()); resultListForDelete.add(cameraChannel); SYMember member = getMember(cameraChannel.getGbDeviceId()); if (member != null) { member.setTerminalMemberStatus("OFFLINE"); memberList.add(member); } } } break; case ON: case OFF: case DEFECT: case VLOST: for (CommonGBChannel channel : channels) { if (channel.getGbPtzType() != null && channel.getGbPtzType() == 99) { CameraChannel cameraChannel = channelMapper.queryCameraChannelById(channel.getGbId()); SYMember member = getMember(cameraChannel.getGbDeviceId()); if (event.getMessageType() == ChannelEvent.ChannelEventMessageType.ON) { cameraChannel.setGbStatus("ON"); resultListForOnline.add(cameraChannel); if (member != null) { member.setTerminalMemberStatus("ONLINE"); memberList.add(member); } }else { cameraChannel.setGbStatus("OFF"); resultListForOffline.add(cameraChannel); if (member != null) { member.setTerminalMemberStatus("OFFLINE"); memberList.add(member); } } } } break; case ADD: for (CommonGBChannel channel : channels) { if (channel.getGbPtzType() != null && channel.getGbPtzType() == 99) { resultListForAdd.add(channel); if ("ON".equals(channel.getGbStatus())) { delayChannelMap.put(channel.getGbDeviceId(), channel); } } } break; } if (!resultListForDelete.isEmpty()) { JSONObject jsonObject = new JSONObject(); jsonObject.put("type", ChannelEvent.ChannelEventMessageType.DEL); jsonObject.put("list", resultListForDelete); log.info("[SY-redis发送通知-DEL] 发送 通道信息变化 {}: {}", REDIS_CHANNEL_MESSAGE, jsonObject.toString()); redisTemplateForString.convertAndSend(REDIS_CHANNEL_MESSAGE, jsonObject.toString()); } if (!resultListForAdd.isEmpty()) { sendChannelMessage(resultListForAdd, ChannelEvent.ChannelEventMessageType.ADD); } if (!resultListForUpdate.isEmpty()) { sendChannelMessage(resultListForUpdate, ChannelEvent.ChannelEventMessageType.UPDATE); } if (!resultListForOnline.isEmpty()) { sendChannelMessage(resultListForOnline, ChannelEvent.ChannelEventMessageType.ON); } if (!resultListForOffline.isEmpty()) { sendChannelMessage(resultListForOffline, ChannelEvent.ChannelEventMessageType.OFF); } if (!memberList.isEmpty()) { sendMemberStatusMessage(memberList); } if (!delayChannelMap.isEmpty()) { // 对于在线的终端进行延迟检查和发送 for (CommonGBChannel commonGBChannel : delayChannelMap.values()) { String key = DELAY_TASK_KEY + commonGBChannel.getGbDeviceId(); dynamicTask.startDelay(key, () -> { dynamicTask.stop(key); SYMember member = getMember(commonGBChannel.getGbDeviceId()); if (member == null) { return; } member.setTerminalMemberStatus("ONLINE"); sendMemberStatusMessage(List.of(member)); }, 3000); } } } private void sendMemberStatusMessage(List memberList) { // 取消延时发送 for (SYMember syMember : memberList) { String key = DELAY_TASK_KEY + syMember.getChannelDeviceId(); if (dynamicTask.contains(key)) { log.info("[SY-redis发送通知] 取消延时新增任务: {}", key); dynamicTask.stop(key); } } String jsonString = JSONObject.toJSONString(memberList); log.info("[SY-redis发送通知] 发送 状态变化 {}: {}", REDIS_MEMBER_STATUS_MESSAGE, jsonString); redisTemplateForString.convertAndSend(REDIS_MEMBER_STATUS_MESSAGE, jsonString); } private void sendChannelMessage(List channelList, ChannelEvent.ChannelEventMessageType type) { if (channelList.isEmpty()) { log.warn("[SY-redis发送通知-{}] 发送失败,数据为空, 通道信息变化 {}", type, REDIS_CHANNEL_MESSAGE); return; } List cameraChannelList = channelMapper.queryCameraChannelByIds(channelList); JSONObject jsonObject = new JSONObject(); jsonObject.put("type", type); jsonObject.put("list", cameraChannelList); log.info("[SY-redis发送通知-{}] 发送 通道信息变化 {}: {}", type, REDIS_CHANNEL_MESSAGE, jsonObject.toString()); redisTemplateForString.convertAndSend(REDIS_CHANNEL_MESSAGE, jsonObject.toString()); } // 监听GPS消息,如果是移动设备则发送redis消息 @EventListener public void onApplicationEvent(MobilePositionEvent event) { MobilePosition mobilePosition = event.getMobilePosition(); // 从redis补充信息 SYMember member = getMember(mobilePosition.getChannelDeviceId()); if (member == null) { log.info("[SY-redis发送通知-移动设备位置信息] 缓存未获取 {}", mobilePosition.toString()); return; } // 发送redis消息 JSONObject jsonObject = new JSONObject(); jsonObject.put("gpsDate", mobilePosition.getTime()); jsonObject.put("unicodeNo", member.getUnicodeNo()); jsonObject.put("memberNo", member.getNo()); jsonObject.put("unitNo", member.getUnitNo()); jsonObject.put("longitude", mobilePosition.getLongitude()); jsonObject.put("latitude", mobilePosition.getLatitude()); jsonObject.put("altitude", mobilePosition.getAltitude()); jsonObject.put("direction", mobilePosition.getDirection()); jsonObject.put("speed", mobilePosition.getSpeed()); jsonObject.put("blockId", member.getBlockId()); jsonObject.put("gbDeviceId", mobilePosition.getChannelDeviceId()); log.info("[SY-redis发送通知-移动设备位置信息] 发送 {}: {}", REDIS_GPS_MESSAGE, jsonObject.toString()); redisTemplateForString.convertAndSend(REDIS_GPS_MESSAGE, jsonObject.toString()); } public SYMember getMember(String deviceId) { // 从redis补充信息 String key = MOBILE_CHANNEL_PREFIX + deviceId; JSONObject jsonObject = (JSONObject)redisTemplate.opsForValue().get(key); if (jsonObject == null) { return null; } SYMember syMember = JSONObject.parseObject(jsonObject.toString(), SYMember.class); syMember.setChannelDeviceId(deviceId); return syMember; } public PageInfo queryList(Integer page, Integer count, String groupAlias, Boolean status, String geoCoordSys) { // 构建组织结构信息 Group group = groupMapper.queryGroupByAlias(groupAlias); if (group == null) { log.warn("[SY-查询摄像机列表, 只查询当前虚拟组织下的] 组织结构不存在: {}", groupAlias); return new PageInfo<>(Collections.emptyList()); } String groupDeviceId = group.getDeviceId(); // 构建分页 PageHelper.startPage(page, count); List all = channelMapper.queryListForSy(groupDeviceId, status); PageInfo groupPageInfo = new PageInfo<>(all); List list = addIconPathAndPositionForCameraChannelList(groupPageInfo.getList(), geoCoordSys); groupPageInfo.setList(list); return groupPageInfo; } public PageInfo queryListWithChild(Integer page, Integer count, String query, String sortName, Boolean order, String groupAlias, Boolean status, String geoCoordSys) { List groupList = null; // 构建组织结构信息 if (groupAlias != null) { CameraGroup group = groupMapper.queryGroupByAlias(groupAlias); if (group == null) { log.warn("[SY-查询摄像机列表, 查询当前虚拟组织下以及全部子节点] 组织结构不存在: {}", groupAlias); return new PageInfo<>(Collections.emptyList()); } // 获取所有子节点 groupList = queryAllGroupChildren(group.getId(), group.getBusinessGroup()); groupList.add(group); } // 构建分页 PageHelper.startPage(page, count); if (query != null) { query = query.replaceAll("/", "//") .replaceAll("%", "/%") .replaceAll("_", "/_"); } if (order == null) { order = true; } List all = channelMapper.queryListWithChildForSy(query, sortName, order, groupList, status); PageInfo groupPageInfo = new PageInfo<>(all); List list = addIconPathAndPositionForCameraChannelList(groupPageInfo.getList(), geoCoordSys); groupPageInfo.setList(list); return groupPageInfo; } // 获取所有子节点 private List queryAllGroupChildren(int groupId, String businessGroup) { Map groupMap = groupMapper.queryByBusinessGroupForMap(businessGroup); for (CameraGroup cameraGroup : groupMap.values()) { cameraGroup.setParent(groupMap.get(cameraGroup.getParentId())); } CameraGroup cameraGroup = groupMap.get(groupId); if (cameraGroup == null) { return Collections.emptyList(); }else { return cameraGroup.getChild(); } } public List queryCountWithChild(String groupAlias) { // 构建组织结构信息 CameraGroup group = groupMapper.queryGroupByAlias(groupAlias); if (group == null) { log.warn("[SY-按组织结构统计摄像头数量] 组织结构不存在: {}", groupAlias); return Collections.emptyList(); } // 获取所有子节点 List groupList = queryAllGroupChildren(group.getId(), group.getBusinessGroup()); groupList.add(group); // TODO 此处整理可优化,尽量让sql直接返回对应的结构 无需二次整理 List cameraCounts = groupMapper.queryCountWithChild(groupList); if (cameraCounts.isEmpty()) { return Collections.emptyList(); }else { Map cameraGroupMap = new HashMap<>(); for (CameraGroup cameraGroup : groupList) { cameraGroupMap.put(cameraGroup.getDeviceId(), cameraGroup.getAlias()); } List result = new ArrayList<>(); for (CameraCount cameraCount : cameraCounts) { String alias = cameraGroupMap.get(cameraCount.getDeviceId()); if (alias == null) { continue; } cameraCount.setGroupAlias(alias); result.add(cameraCount); } return result; } } /** * 为通道增加图片信息和转换坐标系 */ private List addIconPathAndPositionForCameraChannelList(List channels, String geoCoordSys) { // 读取redis 图标信息 /* { "brand": "WVP", "createdTime": 1715845840000, "displayInSelect": true, "id": 12, "imagesPath": "images/lt132", "machineName": "图传对讲单兵", "machineType": "LT132" }, */ JSONArray jsonArray = (JSONArray) redisTemplate.opsForValue().get("machineInfo"); Map pathMap = new HashMap<>(); if (jsonArray != null && !jsonArray.isEmpty()) { for (int i = 0; i < jsonArray.size(); i++) { JSONObject jsonObject = jsonArray.getJSONObject(i); String machineType = jsonObject.getString("machineType"); String imagesPath = jsonObject.getString("imagesPath"); if (machineType != null && imagesPath != null) { pathMap.put(machineType, imagesPath); } } }else { log.warn("[读取通道图标信息失败]"); } for (CameraChannel channel : channels) { if (channel.getGbModel() != null && pathMap.get(channel.getGbModel()) != null) { channel.setIcon(pathMap.get(channel.getGbModel())); } // 坐标系转换 if (geoCoordSys != null && channel.getGbLongitude() != null && channel.getGbLatitude() != null && channel.getGbLongitude() > 0 && channel.getGbLatitude() > 0) { if (geoCoordSys.equalsIgnoreCase("GCJ02")) { Double[] position = Coordtransform.WGS84ToGCJ02(channel.getGbLongitude(), channel.getGbLatitude()); channel.setGbLongitude(position[0]); channel.setGbLatitude(position[1]); }else if (geoCoordSys.equalsIgnoreCase("BD09")) { Double[] gcj02Position = Coordtransform.WGS84ToGCJ02(channel.getGbLongitude(), channel.getGbLatitude()); Double[] position = Coordtransform.GCJ02ToBD09(gcj02Position[0], gcj02Position[1]); channel.setGbLongitude(position[0]); channel.setGbLatitude(position[1]); } } } return channels; } public CameraChannel queryOne(String deviceId, String deviceCode, String geoCoordSys) { List cameraChannels = channelMapper.queryGbChannelByChannelDeviceIdAndGbDeviceId(deviceId, deviceCode); Assert.isTrue(cameraChannels.isEmpty(), "通道不存在"); List channels = addIconPathAndPositionForCameraChannelList(cameraChannels, geoCoordSys); CameraChannel channel = channels.get(0); if (deviceCode != null) { channel.setDeviceCode(deviceCode); } return channel; } /** * 播放通道 * @param deviceId 通道编号 * @param deviceCode 通道对应的国标设备的编号 * @param callback 点播结果的回放 */ public void play(String deviceId, String deviceCode, ErrorCallback callback) { List cameraChannels = channelMapper.queryGbChannelByChannelDeviceIdAndGbDeviceId(deviceId, deviceCode); Assert.isTrue(cameraChannels.isEmpty(), "通道不存在"); CameraChannel channel = cameraChannels.get(0); channelPlayService.play(channel, null, userSetting.getRecordSip(), (code, msg, data) -> { callback.run(code, msg, new CameraStreamInfo(channel, data)); }); } /** * 停止播放通道 * @param deviceId 通道编号 * @param deviceCode 通道对应的国标设备的编号 */ public void stopPlay(String deviceId, String deviceCode) { List cameraChannels = channelMapper.queryGbChannelByChannelDeviceIdAndGbDeviceId(deviceId, deviceCode); Assert.isTrue(cameraChannels.isEmpty(), "通道不存在"); CameraChannel channel = cameraChannels.get(0); channelPlayService.stopPlay(channel); } public void ptz(String deviceId, String deviceCode, String command, Integer speed, ErrorCallback callback) { List cameraChannels = channelMapper.queryGbChannelByChannelDeviceIdAndGbDeviceId(deviceId, deviceCode); Assert.isTrue(cameraChannels.isEmpty(), "通道不存在"); CameraChannel channel = cameraChannels.get(0); if (speed == null) { speed = 50; }else if (speed < 0 || speed > 100) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "panSpeed 为 0-100的数字"); } FrontEndControlCodeForPTZ controlCode = new FrontEndControlCodeForPTZ(); controlCode.setPanSpeed(speed); controlCode.setTiltSpeed(speed); controlCode.setZoomSpeed(speed); switch (command){ case "left": controlCode.setPan(0); break; case "right": controlCode.setPan(1); break; case "up": controlCode.setTilt(0); break; case "down": controlCode.setTilt(1); break; case "upleft": controlCode.setPan(0); controlCode.setTilt(0); break; case "upright": controlCode.setTilt(0); controlCode.setPan(1); break; case "downleft": controlCode.setPan(0); controlCode.setTilt(1); break; case "downright": controlCode.setTilt(1); controlCode.setPan(1); break; case "zoomin": controlCode.setZoom(1); break; case "zoomout": controlCode.setZoom(0); break; default: break; } channelControlService.ptz(channel, controlCode, callback); } public void updateCamera(String deviceId, String deviceCode, String name, Double longitude, Double latitude, String geoCoordSys) { List cameraChannels = channelMapper.queryGbChannelByChannelDeviceIdAndGbDeviceId(deviceId, deviceCode); Assert.isTrue(cameraChannels.isEmpty(), "通道不存在"); CameraChannel commonGBChannel = cameraChannels.get(0); commonGBChannel.setGbName(name); if (geoCoordSys != null && longitude != null && latitude != null && longitude > 0 && latitude > 0) { if (geoCoordSys.equalsIgnoreCase("GCJ02")) { Double[] position = Coordtransform.GCJ02ToWGS84(longitude, latitude); commonGBChannel.setGbLongitude(position[0]); commonGBChannel.setGbLatitude(position[1]); }else if (geoCoordSys.equalsIgnoreCase("BD09")) { Double[] gcj02Position = Coordtransform.BD09ToGCJ02(longitude, latitude); Double[] position = Coordtransform.GCJ02ToWGS84(gcj02Position[0], gcj02Position[1]); commonGBChannel.setGbLongitude(position[0]); commonGBChannel.setGbLatitude(position[1]); }else { commonGBChannel.setGbLongitude(longitude); commonGBChannel.setGbLatitude(latitude); } }else { commonGBChannel.setGbLongitude(longitude); commonGBChannel.setGbLatitude(latitude); } channelMapper.update(commonGBChannel); } public List queryListByDeviceIds(List deviceIds, String geoCoordSys) { List cameraChannels = channelMapper.queryListByDeviceIds(deviceIds); return addIconPathAndPositionForCameraChannelList(cameraChannels, geoCoordSys); } public List queryListByAddressAndDirectionType(String address, Integer directionType, String geoCoordSys) { List cameraChannels = channelMapper.queryListByAddressAndDirectionType(address, directionType); return addIconPathAndPositionForCameraChannelList(cameraChannels, geoCoordSys); } public List queryListInBox(Double minLongitude, Double maxLongitude, Double minLatitude, Double maxLatitude, Integer level, String groupAlias, String geoCoordSys) { // 构建组织结构信息 CameraGroup group = groupMapper.queryGroupByAlias(groupAlias); if (group == null) { log.warn("[SY-框选] 组织结构不存在: {}", groupAlias); return Collections.emptyList(); } // 获取所有子节点 List groupList = queryAllGroupChildren(group.getId(), group.getBusinessGroup()); groupList.add(group); // 参数坐标系列转换 if (geoCoordSys != null) { if (geoCoordSys.equalsIgnoreCase("GCJ02")) { Double[] minPosition = Coordtransform.GCJ02ToWGS84(minLongitude, minLatitude); minLongitude = minPosition[0]; minLatitude = minPosition[1]; Double[] maxPosition = Coordtransform.GCJ02ToWGS84(maxLongitude, maxLatitude); maxLongitude = maxPosition[0]; maxLatitude = maxPosition[1]; }else if (geoCoordSys.equalsIgnoreCase("BD09")) { Double[] gcj02MinPosition = Coordtransform.BD09ToGCJ02(minLongitude, minLatitude); Double[] minPosition = Coordtransform.GCJ02ToWGS84(gcj02MinPosition[0], gcj02MinPosition[1]); minLongitude = minPosition[0]; minLatitude = minPosition[1]; Double[] gcj02MaxPosition = Coordtransform.BD09ToGCJ02(maxLongitude, maxLatitude); Double[] maxPosition = Coordtransform.GCJ02ToWGS84(gcj02MaxPosition[0], gcj02MaxPosition[1]); maxLongitude = maxPosition[0]; maxLatitude = maxPosition[1]; } } List all = channelMapper.queryListInBox(minLongitude, maxLongitude, minLatitude, maxLatitude, level, groupList); return addIconPathAndPositionForCameraChannelList(all, geoCoordSys); } public List queryListInCircle(Double centerLongitude, Double centerLatitude, Double radius, Integer level, String groupAlias, String geoCoordSys) { // 构建组织结构信息 CameraGroup group = groupMapper.queryGroupByAlias(groupAlias); if (group == null) { log.warn("[SY-圈选] 组织结构不存在: {}", groupAlias); return Collections.emptyList(); } // 获取所有子节点 List groupList = queryAllGroupChildren(group.getId(), group.getBusinessGroup()); groupList.add(group); // 参数坐标系列转换 if (geoCoordSys != null) { if (geoCoordSys.equalsIgnoreCase("GCJ02")) { Double[] position = Coordtransform.GCJ02ToWGS84(centerLongitude, centerLatitude); centerLongitude = position[0]; centerLatitude = position[1]; }else if (geoCoordSys.equalsIgnoreCase("BD09")) { Double[] gcj02Position = Coordtransform.BD09ToGCJ02(centerLongitude, centerLatitude); Double[] position = Coordtransform.GCJ02ToWGS84(gcj02Position[0], gcj02Position[1]); centerLongitude = position[0]; centerLatitude = position[1]; } } List all = channelMapper.queryListInCircle(centerLongitude, centerLatitude, radius, level, groupList); return addIconPathAndPositionForCameraChannelList(all, geoCoordSys); } public List queryListInPolygon(List pointList, String groupAlias, Integer level, String geoCoordSys) { // 构建组织结构信息 CameraGroup group = groupMapper.queryGroupByAlias(groupAlias); if (group == null) { log.warn("[SY-多边形] 组织结构不存在: {}", groupAlias); return Collections.emptyList(); } // 获取所有子节点 List groupList = queryAllGroupChildren(group.getId(), group.getBusinessGroup()); groupList.add(group); // 参数坐标系列转换 if (geoCoordSys != null) { for (Point point : pointList) { if (geoCoordSys.equalsIgnoreCase("GCJ02")) { Double[] position = Coordtransform.GCJ02ToWGS84(point.getLng(), point.getLat()); point.setLng(position[0]); point.setLat(position[1]); }else if (geoCoordSys.equalsIgnoreCase("BD09")) { Double[] gcj02Position = Coordtransform.BD09ToGCJ02(point.getLng(), point.getLat()); Double[] position = Coordtransform.GCJ02ToWGS84(gcj02Position[0], gcj02Position[1]); point.setLng(position[0]); point.setLat(position[1]); } } } List all = channelMapper.queryListInPolygon(pointList, level, groupList); return addIconPathAndPositionForCameraChannelList(all, geoCoordSys); } public PageInfo queryListForMobile(Integer page, Integer count, String topGroupAlias) { CameraGroup cameraGroup = groupMapper.queryGroupByAlias(topGroupAlias); String business = null; if (cameraGroup != null) { business = cameraGroup.getDeviceId(); } // 构建分页 PageHelper.startPage(page, count); List all = channelMapper.queryListForSyMobile(business); PageInfo groupPageInfo = new PageInfo<>(all); List list = addIconPathAndPositionForCameraChannelList(groupPageInfo.getList(), null); groupPageInfo.setList(list); return groupPageInfo; } public List queryMeetingChannelList(String topGroupAlias) { CameraGroup cameraGroup = groupMapper.queryGroupByAlias(topGroupAlias); Assert.notNull(cameraGroup, "域不存在"); String business = cameraGroup.getDeviceId(); Assert.notNull(business, "域不存在"); return channelMapper.queryMeetingChannelList(business); } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/web/custom/service/SyServiceImpl.java ================================================ package com.genersoft.iot.vmp.web.custom.service; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.service.IMapService; import com.genersoft.iot.vmp.vmanager.bean.MapConfig; import com.genersoft.iot.vmp.vmanager.bean.MapModelIcon; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; /** * 第三方平台适配 */ @Slf4j @Service @ConditionalOnProperty(value = "sy.enable", havingValue = "true") public class SyServiceImpl implements IMapService { @Autowired private RedisTemplate redisTemplate; @Override public List getConfig() { List configList = new ArrayList<>(); JSONObject configObject = (JSONObject)redisTemplate.opsForValue().get("interfaceConfig1"); if (configObject == null) { return configList; } // 浅色地图 MapConfig mapConfigForDefault = readConfig("FRAGMENTIMG_SERVER", configObject); if (mapConfigForDefault != null) { mapConfigForDefault.setName("浅色地图"); configList.add(mapConfigForDefault); } // 深色地图 MapConfig mapConfigForDark = readConfig("POLARNIGHTBLUE_FRAGMENTIMG_SERVER", configObject); if (mapConfigForDark != null) { mapConfigForDark.setName("深色地图"); configList.add(mapConfigForDark); } // 卫星地图 MapConfig mapConfigForSatellited = readConfig("SATELLITE_FRAGMENTIMG_SERVER", configObject); if (mapConfigForSatellited != null) { mapConfigForSatellited.setName("卫星地图"); configList.add(mapConfigForSatellited); } return configList; } private MapConfig readConfig(String key, JSONObject jsonObject) { JSONArray fragmentimgServerArray = jsonObject.getJSONArray(key); if (fragmentimgServerArray == null || fragmentimgServerArray.isEmpty()) { return null; } JSONObject fragmentimgServer = fragmentimgServerArray.getJSONObject(0); // 坐标系 String geoCoordSys = fragmentimgServer.getString("csysType").toUpperCase(); // 获取地址 String path = fragmentimgServer.getString("path"); String ip = fragmentimgServer.getString("ip"); JSONObject portJson = fragmentimgServer.getJSONObject("port"); JSONObject httpPortJson = portJson.getJSONObject("httpPort"); String protocol = httpPortJson.getString("portType"); Integer port = httpPortJson.getInteger("port"); String tileUrl = String.format("%s://%s:%s%s", protocol, ip, port, path); MapConfig mapConfig = new MapConfig(); mapConfig.setCoordinateSystem(geoCoordSys); mapConfig.setTilesUrl(tileUrl); return mapConfig; } @Override public List getModelList() { // 读取redis 图标信息 /* { "brand": "WVP", "createdTime": 1715845840000, "displayInSelect": true, "id": 12, "imagesPath": "images/lt132", "machineName": "图传对讲单兵", "machineType": "LT132" }, */ List mapModelIconList = new ArrayList<>(); JSONArray jsonArray = (JSONArray) redisTemplate.opsForValue().get("machineInfo"); if (jsonArray != null && !jsonArray.isEmpty()) { for (int i = 0; i < jsonArray.size(); i++) { JSONObject jsonObject = jsonArray.getJSONObject(i); String machineType = jsonObject.getString("machineType"); String machineName = jsonObject.getString("machineName"); String imagesPath = jsonObject.getString("imagesPath"); mapModelIconList.add(MapModelIcon.getInstance(machineType, machineName, imagesPath)); } } return mapModelIconList; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiControlController.java ================================================ package com.genersoft.iot.vmp.web.gb28181; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; import com.genersoft.iot.vmp.gb28181.service.IDeviceService; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import io.swagger.v3.oas.annotations.Hidden; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import javax.sip.InvalidArgumentException; import javax.sip.SipException; import java.text.ParseException; /** * API兼容:设备控制 */ @Slf4j @RestController @RequestMapping(value = "/api/v1/control") @Hidden public class ApiControlController { @Autowired private SIPCommander cmder; @Autowired private IDeviceService deviceService; /** * 设备控制 - 云台控制 * @param serial 设备编号 * @param command 控制指令 允许值: left, right, up, down, upleft, upright, downleft, downright, zoomin, zoomout, stop * @param channel 通道序号 * @param code 通道编号 * @param speed 速度(0~255) 默认值: 129 */ @GetMapping(value = "/ptz") private void ptz(String serial,String command, @RequestParam(required = false)Integer channel, @RequestParam(required = false)String code, @RequestParam(required = false)Integer speed){ if (log.isDebugEnabled()) { log.debug("模拟接口> 设备云台控制 API调用,deviceId:{} ,channelId:{} ,command:{} ,speed:{} ", serial, code, command, speed); } if (channel == null) {channel = 0;} if (speed == null) {speed = 0;} Device device = deviceService.getDeviceByDeviceId(serial); if (device == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "device[ " + serial + " ]未找到"); } int cmdCode = -1; switch (command){ case "left": cmdCode = 2; break; case "right": cmdCode = 1; break; case "up": cmdCode = 8; break; case "down": cmdCode = 4; break; case "upleft": cmdCode = 10; break; case "upright": cmdCode = 9; break; case "downleft": cmdCode = 6; break; case "downright": cmdCode = 5; break; case "zoomin": cmdCode = 16; break; case "zoomout": cmdCode = 32; break; case "stop": cmdCode = 0; break; default: break; } if (cmdCode == -1) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "未识别的指令:" + command); } // 默认值 50 try { cmder.frontEndCmd(device, code, cmdCode, speed, speed, speed); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 云台控制: {}", e.getMessage()); throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage()); } } /** * 设备控制 - 预置位控制 * @param serial 设备编号 * @param code 通道编号,通过 /api/v1/device/channellist 获取的 ChannelList.ID, 该参数和 channel 二选一传递即可 * @param channel 通道序号, 默认值: 1 * @param command 控制指令 允许值: set, goto, remove * @param preset 预置位编号(1~255) * @param name 预置位名称, command=set 时有效 */ @GetMapping(value = "/preset") private void list(String serial,String command, @RequestParam(required = false)Integer channel, @RequestParam(required = false)String code, @RequestParam(required = false)String name, @RequestParam(required = false)Integer preset){ if (log.isDebugEnabled()) { log.debug("模拟接口> 预置位控制 API调用,deviceId:{} ,channelId:{} ,command:{} ,name:{} ,preset:{} ", serial, code, command, name, preset); } if (channel == null) {channel = 0;} Device device = deviceService.getDeviceByDeviceId(serial); if (device == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "device[ " + serial + " ]未找到"); } int cmdCode = 0; switch (command){ case "set": cmdCode = 129; break; case "goto": cmdCode = 130; break; case "remove": cmdCode = 131; break; default: break; } try { cmder.frontEndCmd(device, code, cmdCode, 0, preset, 0); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 预置位控制: {}", e.getMessage()); throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage()); } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiController.java ================================================ package com.genersoft.iot.vmp.web.gb28181; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.conf.SipConfig; import io.swagger.v3.oas.annotations.Hidden; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; /** * API兼容:系统接口 */ @Controller @Slf4j @RequestMapping(value = "/api/v1") @Hidden public class ApiController { @Autowired private SipConfig sipConfig; @GetMapping("/getserverinfo") private JSONObject getserverinfo(){ JSONObject result = new JSONObject(); result.put("Authorization","ceshi"); result.put("Hardware",""); result.put("InterfaceVersion","2.5.5"); result.put("IsDemo",""); result.put("Hardware","false"); result.put("APIAuth","false"); result.put("RemainDays","永久"); result.put("RunningTime",""); result.put("ServerTime","2020-09-02 17:11"); result.put("StartUpTime","2020-09-02 17:11"); result.put("Server",""); result.put("SIPSerial", sipConfig.getId()); result.put("SIPRealm", sipConfig.getDomain()); result.put("SIPHost", sipConfig.getShowIp()); result.put("SIPPort", sipConfig.getPort()); result.put("ChannelCount","1000"); result.put("VersionType",""); result.put("LogoMiniText",""); result.put("LogoText",""); result.put("CopyrightText",""); return result; } @GetMapping(value = "/userinfo") private JSONObject userinfo(){ // JSONObject result = new JSONObject(); // result.put("ID","ceshi"); // result.put("Hardware",""); // result.put("InterfaceVersion","2.5.5"); // result.put("IsDemo",""); // result.put("Hardware","false"); // result.put("APIAuth","false"); // result.put("RemainDays","永久"); // result.put("RunningTime",""); // result.put("ServerTime","2020-09-02 17:11"); // result.put("StartUpTime","2020-09-02 17:11"); // result.put("Server",""); // result.put("SIPSerial", sipConfig.getId()); // result.put("SIPRealm", sipConfig.getDomain()); // result.put("SIPHost", sipConfig.getIp()); // result.put("SIPPort", sipConfig.getPort()); // result.put("ChannelCount","1000"); // result.put("VersionType",""); // result.put("LogoMiniText",""); // result.put("LogoText",""); // result.put("CopyrightText",""); return null; } /** * 系统接口 - 登录 * @param username 用户名 * @param password 密码(经过md5加密,32位长度,不带中划线,不区分大小写) * @return */ @GetMapping(value = "/login") @ResponseBody private JSONObject login(String username,String password ){ if (log.isDebugEnabled()) { log.debug(String.format("模拟接口> 登录 API调用,username:%s ,password:%s ", username, password)); } JSONObject result = new JSONObject(); result.put("CookieToken","ynBDDiKMg"); result.put("URLToken","MOBkORkqnrnoVGcKIAHXppgfkNWRdV7utZSkDrI448Q.oxNjAxNTM4NDk3LCJwIjoiZGJjODg5NzliNzVj" + "Nzc2YmU5MzBjM2JjNjg1ZWFiNGI5ZjhhN2Y0N2RlZjg3NWUyOTJkY2VkYjkwYmEwMTA0NyIsInQiOjE2MDA5MzM2OTcsInUiOiI" + "4ODlkZDYyM2ViIn0eyJlIj.GciOiJIUzI1NiIsInR5cCI6IkpXVCJ9eyJhb"); result.put("TokenTimeout",604800); result.put("AuthToken","MOBkORkqnrnoVGcKIAHXppgfkNWRdV7utZSkDrI448Q.oxNjAxNTM4NDk3LCJwIjoiZGJjODg5NzliNzVj" + "Nzc2YmU5MzBjM2JjNjg1ZWFiNGI5ZjhhN2Y0N2RlZjg3NWUyOTJkY2VkYjkwYmEwMTA0NyIsInQiOjE2MDA5MzM2OTcsInUiOiI" + "4ODlkZDYyM2ViIn0eyJlIj.GciOiJIUzI1NiIsInR5cCI6IkpXVCJ9eyJhb"); result.put("Token","ynBDDiKMg"); return result; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiDeviceController.java ================================================ package com.genersoft.iot.vmp.web.gb28181; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.Preset; import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService; import com.genersoft.iot.vmp.gb28181.service.IDeviceService; import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import com.genersoft.iot.vmp.web.gb28181.dto.DeviceChannelExtend; import com.github.pagehelper.PageInfo; import io.swagger.v3.oas.annotations.Hidden; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.async.DeferredResult; import java.util.*; /** * API兼容:设备信息 */ @SuppressWarnings("unchecked") @Slf4j @RestController @RequestMapping(value = "/api/v1/device") @Hidden public class ApiDeviceController { @Autowired private SIPCommander cmder; @Autowired private IDeviceChannelService channelService; @Autowired private DeferredResultHolder resultHolder; @Autowired private IDeviceService deviceService; /** * 分页获取设备列表 现在直接返回,尚未实现分页 * @param start * @param limit * @param q * @param online * @return */ @GetMapping(value = "/list") public JSONObject list( @RequestParam(required = false)Integer start, @RequestParam(required = false)Integer limit, @RequestParam(required = false)String q, @RequestParam(required = false)Boolean online ){ // if (logger.isDebugEnabled()) { // logger.debug("查询所有视频设备API调用"); // } JSONObject result = new JSONObject(); List devices; if (start == null || limit ==null) { devices = deviceService.getAllByStatus(online); result.put("DeviceCount", devices.size()); }else { PageInfo deviceList = deviceService.getAll(start/limit, limit,null, online); result.put("DeviceCount", deviceList.getTotal()); devices = deviceList.getList(); } JSONArray deviceJSONList = new JSONArray(); devices.stream().forEach(device -> { JSONObject deviceJsonObject = new JSONObject(); deviceJsonObject.put("ID", device.getDeviceId()); deviceJsonObject.put("Name", device.getName()); deviceJsonObject.put("Type", "GB"); deviceJsonObject.put("ChannelCount", device.getChannelCount()); deviceJsonObject.put("RecvStreamIP", ""); deviceJsonObject.put("CatalogInterval", 3600); // 通道目录抓取周期 deviceJsonObject.put("SubscribeInterval", device.getSubscribeCycleForCatalog()); // 订阅周期(秒), 0 表示后台不周期订阅 deviceJsonObject.put("Online", device.isOnLine()); deviceJsonObject.put("Password", ""); deviceJsonObject.put("MediaTransport", device.getTransport()); deviceJsonObject.put("RemoteIP", device.getIp()); deviceJsonObject.put("RemotePort", device.getPort()); deviceJsonObject.put("LastRegisterAt", ""); deviceJsonObject.put("LastKeepaliveAt", ""); deviceJsonObject.put("UpdatedAt", ""); deviceJsonObject.put("CreatedAt", ""); deviceJSONList.add(deviceJsonObject); }); result.put("DeviceList",deviceJSONList); return result; } @GetMapping(value = "/channellist") public JSONObject channellist( String serial, @RequestParam(required = false)String channel_type, @RequestParam(required = false)String code , @RequestParam(required = false)String dir_serial , @RequestParam(required = false)Integer start, @RequestParam(required = false)Integer limit, @RequestParam(required = false)String q, @RequestParam(required = false)Boolean online ){ JSONObject result = new JSONObject(); List deviceChannels; List channelIds = null; if (!ObjectUtils.isEmpty(code)) { String[] split = code.trim().split(","); channelIds = Arrays.asList(split); } List allDeviceChannelList = channelService.queryChannelExtendsByDeviceId(serial,channelIds,online); if (start == null || limit ==null) { deviceChannels = allDeviceChannelList; result.put("ChannelCount", deviceChannels.size()); }else { if (start > allDeviceChannelList.size()) { deviceChannels = new ArrayList<>(); }else { if (start + limit < allDeviceChannelList.size()) { deviceChannels = allDeviceChannelList.subList(start, start + limit); }else { deviceChannels = allDeviceChannelList.subList(start, allDeviceChannelList.size()); } } result.put("ChannelCount", allDeviceChannelList.size()); } JSONArray channleJSONList = new JSONArray(); deviceChannels.stream().forEach(deviceChannelExtend -> { JSONObject deviceJOSNChannel = new JSONObject(); deviceJOSNChannel.put("ID", deviceChannelExtend.getChannelId()); deviceJOSNChannel.put("DeviceID", deviceChannelExtend.getDeviceId()); deviceJOSNChannel.put("DeviceName", deviceChannelExtend.getDeviceName()); deviceJOSNChannel.put("DeviceOnline", deviceChannelExtend.isDeviceOnline()); deviceJOSNChannel.put("Channel", 0); // TODO 自定义序号 deviceJOSNChannel.put("Name", deviceChannelExtend.getName()); deviceJOSNChannel.put("Custom", false); deviceJOSNChannel.put("CustomName", ""); deviceJOSNChannel.put("SubCount", deviceChannelExtend.getSubCount()); // TODO ? 子节点数, SubCount > 0 表示该通道为子目录 deviceJOSNChannel.put("SnapURL", ""); deviceJOSNChannel.put("Manufacturer ", deviceChannelExtend.getManufacture()); deviceJOSNChannel.put("Model", deviceChannelExtend.getModel()); deviceJOSNChannel.put("Owner", deviceChannelExtend.getOwner()); deviceJOSNChannel.put("CivilCode", deviceChannelExtend.getCivilCode()); deviceJOSNChannel.put("Address", deviceChannelExtend.getAddress()); deviceJOSNChannel.put("Parental", deviceChannelExtend.getParental()); // 当为通道设备时, 是否有通道子设备, 1-有,0-没有 deviceJOSNChannel.put("ParentID", deviceChannelExtend.getParentId()); // 直接上级编号 deviceJOSNChannel.put("Secrecy", deviceChannelExtend.getSecrecy()); deviceJOSNChannel.put("RegisterWay", 1); // 注册方式, 缺省为1, 允许值: 1, 2, 3 // 1-IETF RFC3261, // 2-基于口令的双向认证, // 3-基于数字证书的双向认证 deviceJOSNChannel.put("Status", deviceChannelExtend.getStatus()); deviceJOSNChannel.put("Longitude", deviceChannelExtend.getLongitude()); deviceJOSNChannel.put("Latitude", deviceChannelExtend.getLatitude()); deviceJOSNChannel.put("PTZType ", deviceChannelExtend.getPTZType()); // 云台类型, 0 - 未知, 1 - 球机, 2 - 半球, // 3 - 固定枪机, 4 - 遥控枪机 deviceJOSNChannel.put("CustomPTZType", ""); deviceJOSNChannel.put("StreamID", deviceChannelExtend.getStreamId()); // StreamID 直播流ID, 有值表示正在直播 deviceJOSNChannel.put("NumOutputs ", -1); // 直播在线人数 channleJSONList.add(deviceJOSNChannel); }); result.put("ChannelList", channleJSONList); return result; } /** * 设备信息 - 获取下级通道预置位 * @param serial 设备编号 * @param code 通道编号,通过 /api/v1/device/channellist 获取的 ChannelList.ID, 该参数和 channel 二选一传递即可 * @param channel 通道序号, 默认值: 1 * @param fill 是否填充空置预置位,当下级返回预置位,但不够255个时,自动填充空置预置位到255个, 默认值: true, 允许值: true, false * @param timeout 超时时间(秒) 默认值: 15 * @return */ @GetMapping(value = "/fetchpreset") private DeferredResult> list(String serial, @RequestParam(required = false)Integer channel, @RequestParam(required = false)String code, @RequestParam(required = false)Boolean fill, @RequestParam(required = false)Integer timeout){ if (log.isDebugEnabled()) { log.debug("<模拟接口> 获取下级通道预置位 API调用,deviceId:{} ,channel:{} ,code:{} ,fill:{} ,timeout:{} ", serial, channel, code, fill, timeout); } Device device = deviceService.getDeviceByDeviceId(serial); Assert.notNull(device, "设备不存在"); DeferredResult> deferredResult = new DeferredResult<> (timeout * 1000L); deviceService.queryPreset(device, code, (resultCode, msg, data) -> { if (resultCode == ErrorCode.SUCCESS.getCode()) { List presetQuerySipReqList = (List)data; HashMap resultMap = new HashMap<>(); resultMap.put("DeviceID", code); resultMap.put("Result", "OK"); resultMap.put("SumNum", presetQuerySipReqList.size()); ArrayList> presetItemList = new ArrayList<>(presetQuerySipReqList.size()); for (Preset presetQuerySipReq : presetQuerySipReqList) { Map item = new HashMap<>(); item.put("PresetID", presetQuerySipReq.getPresetId()); item.put("PresetName", presetQuerySipReq.getPresetName()); item.put("PresetEnable", true); presetItemList.add(item); } resultMap.put("PresetItemList",presetItemList ); deferredResult.setResult(new WVPResult<>(resultCode, msg, resultMap)); }else { deferredResult.setResult(new WVPResult<>(resultCode, msg, null)); } }); deferredResult.onTimeout(()->{ log.warn("[获取设备预置位] 超时, {}", device.getDeviceId()); deferredResult.setResult(WVPResult.fail(ErrorCode.ERROR100.getCode(), "wait for presetquery timeout["+timeout+"s]")); }); return deferredResult; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiStreamController.java ================================================ package com.genersoft.iot.vmp.web.gb28181; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.common.InviteInfo; import com.genersoft.iot.vmp.common.InviteSessionType; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.common.enums.MediaApp; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService; import com.genersoft.iot.vmp.gb28181.service.IDeviceService; import com.genersoft.iot.vmp.gb28181.service.IInviteStreamService; import com.genersoft.iot.vmp.gb28181.service.IPlayService; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.service.bean.InviteErrorCode; import io.swagger.v3.oas.annotations.Hidden; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import org.springframework.web.context.request.async.DeferredResult; import javax.sip.InvalidArgumentException; import javax.sip.SipException; import java.text.ParseException; /** * API兼容:实时直播 */ @SuppressWarnings(value = {"rawtypes", "unchecked"}) @Slf4j @RestController @RequestMapping(value = "/api/v1/stream") @Hidden public class ApiStreamController { @Autowired private SIPCommander cmder; @Autowired private UserSetting userSetting; @Autowired private IDeviceService deviceService; @Autowired private IDeviceChannelService deviceChannelService; @Autowired private IPlayService playService; @Autowired private IInviteStreamService inviteStreamService; /** * 实时直播 - 开始直播 * @param serial 设备编号 * @param channel 通道序号 默认值: 1 * @param code 通道编号,通过 /api/v1/device/channellist 获取的 ChannelList.ID, 该参数和 channel 二选一传递即可 * @param cdn 转推 CDN 地址, 形如: [rtmp|rtsp]://xxx, encodeURIComponent * @param audio 是否开启音频, 默认 开启 * @param transport 流传输模式, 默认 UDP * @param checkchannelstatus 是否检查通道状态, 默认 false, 表示 拉流前不检查通道状态是否在线 * @param transportmode 当 transport=TCP 时有效, 指示流传输主被动模式, 默认被动 * @param timeout 拉流超时(秒), * @return */ @GetMapping("/start") private DeferredResult start(String serial , @RequestParam(required = false)Integer channel , @RequestParam(required = false)String code, @RequestParam(required = false)String cdn, @RequestParam(required = false)String audio, @RequestParam(required = false)String transport, @RequestParam(required = false)String checkchannelstatus , @RequestParam(required = false)String transportmode, @RequestParam(required = false)String timeout ){ DeferredResult result = new DeferredResult<>(userSetting.getPlayTimeout().longValue() + 10); Device device = deviceService.getDeviceByDeviceId(serial); if (device == null ) { JSONObject resultJSON = new JSONObject(); resultJSON.put("error","device[ " + serial + " ]未找到"); result.setResult(resultJSON); return result; }else if (!device.isOnLine()) { JSONObject resultJSON = new JSONObject(); resultJSON.put("error","device[ " + code + " ]offline"); result.setResult(resultJSON); return result; } DeviceChannel deviceChannel = deviceChannelService.getOne(serial, code); if (deviceChannel == null) { JSONObject resultJSON = new JSONObject(); resultJSON.put("error","channel[ " + code + " ]未找到"); result.setResult(resultJSON); return result; }else if (!deviceChannel.getStatus().equalsIgnoreCase("ON")) { JSONObject resultJSON = new JSONObject(); resultJSON.put("error","channel[ " + code + " ]offline"); result.setResult(resultJSON); return result; } result.onTimeout(()->{ log.info("播放等待超时"); JSONObject resultJSON = new JSONObject(); resultJSON.put("error","timeout"); result.setResult(resultJSON); inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceChannel.getId()); deviceChannelService.stopPlay(deviceChannel.getId()); // 清理RTP server }); MediaServer newMediaServerItem = playService.getNewMediaServerItem(device); playService.play(newMediaServerItem, serial, code, null, (errorCode, msg, data) -> { if (errorCode == InviteErrorCode.SUCCESS.getCode()) { if (data != null) { StreamInfo streamInfo = (StreamInfo)data; JSONObject resultJjson = new JSONObject(); resultJjson.put("StreamID", streamInfo.getStream()); resultJjson.put("DeviceID", serial); resultJjson.put("ChannelID", code); resultJjson.put("ChannelName", deviceChannel.getName()); resultJjson.put("ChannelCustomName", ""); if (streamInfo.getTranscodeStream() != null) { resultJjson.put("FLV", streamInfo.getTranscodeStream().getFlv().getUrl()); }else { resultJjson.put("FLV", streamInfo.getFlv().getUrl()); } if(streamInfo.getHttps_flv() != null) { if (streamInfo.getTranscodeStream() != null) { resultJjson.put("HTTPS_FLV", streamInfo.getTranscodeStream().getHttps_flv().getUrl()); }else { resultJjson.put("HTTPS_FLV", streamInfo.getHttps_flv().getUrl()); } } if (streamInfo.getTranscodeStream() != null) { resultJjson.put("WS_FLV", streamInfo.getTranscodeStream().getWs_flv().getUrl()); }else { resultJjson.put("WS_FLV", streamInfo.getWs_flv().getUrl()); } if(streamInfo.getWss_flv() != null) { if (streamInfo.getTranscodeStream() != null) { resultJjson.put("WSS_FLV", streamInfo.getTranscodeStream().getWss_flv().getUrl()); }else { resultJjson.put("WSS_FLV", streamInfo.getWss_flv().getUrl()); } } resultJjson.put("RTMP", streamInfo.getRtmp().getUrl()); if (streamInfo.getRtmps() != null) { resultJjson.put("RTMPS", streamInfo.getRtmps().getUrl()); } resultJjson.put("HLS", streamInfo.getHls().getUrl()); if (streamInfo.getHttps_hls() != null) { resultJjson.put("HTTPS_HLS", streamInfo.getHttps_hls().getUrl()); } resultJjson.put("RTSP", streamInfo.getRtsp().getUrl()); if (streamInfo.getRtsps() != null) { resultJjson.put("RTSPS", streamInfo.getRtsps().getUrl()); } resultJjson.put("WEBRTC", streamInfo.getRtc().getUrl()); if (streamInfo.getRtcs() != null) { resultJjson.put("HTTPS_WEBRTC", streamInfo.getRtcs().getUrl()); } resultJjson.put("CDN", ""); resultJjson.put("SnapURL", ""); resultJjson.put("Transport", device.getTransport()); resultJjson.put("StartAt", ""); resultJjson.put("Duration", ""); resultJjson.put("SourceVideoCodecName", ""); resultJjson.put("SourceVideoWidth", ""); resultJjson.put("SourceVideoHeight", ""); resultJjson.put("SourceVideoFrameRate", ""); resultJjson.put("SourceAudioCodecName", ""); resultJjson.put("SourceAudioSampleRate", ""); resultJjson.put("AudioEnable", ""); resultJjson.put("Ondemand", ""); resultJjson.put("InBytes", ""); resultJjson.put("InBitRate", ""); resultJjson.put("OutBytes", ""); resultJjson.put("NumOutputs", ""); resultJjson.put("CascadeSize", ""); resultJjson.put("RelaySize", ""); resultJjson.put("ChannelPTZType", "0"); result.setResult(resultJjson); }else { JSONObject resultJjson = new JSONObject(); resultJjson.put("error", "channel[ " + code + " ] " + msg); result.setResult(resultJjson); } }else { JSONObject resultJjson = new JSONObject(); resultJjson.put("error", "channel[ " + code + " ] " + msg); result.setResult(resultJjson); } }); return result; } /** * 实时直播 - 直播流停止 * @param serial 设备编号 * @param channel 通道序号 * @param code 通道国标编号 * @param check_outputs * @return */ @GetMapping("/stop") @ResponseBody private JSONObject stop(String serial , @RequestParam(required = false)Integer channel , @RequestParam(required = false)String code, @RequestParam(required = false)String check_outputs ){ Device device = deviceService.getDeviceByDeviceId(serial); if (device == null) { JSONObject result = new JSONObject(); result.put("error","未找到设备"); return result; } DeviceChannel deviceChannel = deviceChannelService.getOne(serial, code); if (deviceChannel == null) { JSONObject result = new JSONObject(); result.put("error","未找到通道"); return result; } InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceChannel.getId()); if (inviteInfo == null) { JSONObject result = new JSONObject(); result.put("error","未找到流信息"); return result; } try { cmder.streamByeCmd(device, code, MediaApp.GB28181, inviteInfo.getStream(), null, null); } catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) { JSONObject result = new JSONObject(); result.put("error","发送BYE失败:" + e.getMessage()); return result; } inviteStreamService.removeInviteInfo(inviteInfo); deviceChannelService.stopPlay(inviteInfo.getChannelId()); return null; } /** * 实时直播 - 直播流保活 * @param serial 设备编号 * @param channel 通道序号 * @param code 通道国标编号 * @return */ @GetMapping("/touch") @ResponseBody private JSONObject touch(String serial ,String t, @RequestParam(required = false)Integer channel , @RequestParam(required = false)String code, @RequestParam(required = false)String autorestart, @RequestParam(required = false)String audio, @RequestParam(required = false)String cdn ){ return null; } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/web/gb28181/AuthController.java ================================================ package com.genersoft.iot.vmp.web.gb28181; import com.genersoft.iot.vmp.service.IUserService; import com.genersoft.iot.vmp.storager.dao.dto.User; import io.swagger.v3.oas.annotations.Hidden; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping(value = "/auth") @Hidden public class AuthController { @Autowired private IUserService userService; @GetMapping("/login") public String devices(String name, String passwd){ User user = userService.getUser(name, passwd); if (user != null) { return "success"; }else { return "fail"; } } } ================================================ FILE: src/main/java/com/genersoft/iot/vmp/web/gb28181/dto/DeviceChannelExtend.java ================================================ package com.genersoft.iot.vmp.web.gb28181.dto; import lombok.Data; @Data public class DeviceChannelExtend { /** * 数据库自增ID */ private int id; /** * 通道id */ private String channelId; /** * 设备id */ private String deviceId; /** * 通道名 */ private String name; private String deviceName; private boolean deviceOnline; /** * 生产厂商 */ private String manufacture; /** * 型号 */ private String model; /** * 设备归属 */ private String owner; /** * 行政区域 */ private String civilCode; /** * 警区 */ private String block; /** * 安装地址 */ private String address; /** * 是否有子设备 1有, 0没有 */ private int parental; /** * 父级id */ private String parentId; /** * 信令安全模式 缺省为0; 0:不采用; 2: S/MIME签名方式; 3: S/ MIME加密签名同时采用方式; 4:数字摘要方式 */ private int safetyWay; /** * 注册方式 缺省为1;1:符合IETFRFC3261标准的认证注册模 式; 2:基于口令的双向认证注册模式; 3:基于数字证书的双向认证注册模式 */ private int registerWay; /** * 证书序列号 */ private String certNum; /** * 证书有效标识 缺省为0;证书有效标识:0:无效1: 有效 */ private int certifiable; /** * 证书无效原因码 */ private int errCode; /** * 证书终止有效期 */ private String endTime; /** * 保密属性 缺省为0; 0:不涉密, 1:涉密 */ private String secrecy; /** * IP地址 */ private String ipAddress; /** * 端口号 */ private int port; /** * 密码 */ private String password; /** * 云台类型 */ private int PTZType; /** * 云台类型描述字符串 */ private String PTZTypeText; /** * 创建时间 */ private String createTime; /** * 更新时间 */ private String updateTime; /** * 在线/离线 * 1在线,0离线 * 默认在线 * 信令: * ON * OFF * 遇到过NVR下的IPC下发信令可以推流, 但是 Status 响应 OFF */ private String status; /** * 经度 */ private double longitude; /** * 纬度 */ private double latitude; /** * 经度 GCJ02 */ private double longitudeGcj02; /** * 纬度 GCJ02 */ private double latitudeGcj02; /** * 经度 WGS84 */ private double longitudeWgs84; /** * 纬度 WGS84 */ private double latitudeWgs84; /** * 子设备数 */ private int subCount; /** * 流唯一编号,存在表示正在直播 */ private String streamId; /** * 是否含有音频 */ private boolean hasAudio; /** * 标记通道的类型,0->国标通道 1->直播流通道 2->业务分组/虚拟组织/行政区划 */ private int channelType; /** * 业务分组 */ private String businessGroupId; /** * GPS的更新时间 */ private String gpsTime; public void setPTZType(int PTZType) { this.PTZType = PTZType; switch (PTZType) { case 0: this.PTZTypeText = "未知"; break; case 1: this.PTZTypeText = "球机"; break; case 2: this.PTZTypeText = "半球"; break; case 3: this.PTZTypeText = "固定枪机"; break; case 4: this.PTZTypeText = "遥控枪机"; break; } } } ================================================ FILE: src/main/resources/application.yml ================================================ spring: application: name: wvp profiles: active: dev ================================================ FILE: src/main/resources/banner.txt ================================================ ___ __ ___ ___ ________ ________ ________ ________ |\ \ |\ \|\ \ / /|\ __ \ |\ __ \|\ __ \|\ __ \ \ \ \ \ \ \ \ \ / / | \ \|\ \ ____________\ \ \|\ \ \ \|\ \ \ \|\ \ \ \ \ __\ \ \ \ \/ / / \ \ ____\\____________\ \ ____\ \ _ _\ \ \\\ \ \ \ \|\__\_\ \ \ / / \ \ \___\|____________|\ \ \___|\ \ \\ \\ \ \\\ \ \ \____________\ \__/ / \ \__\ \ \__\ \ \__\\ _\\ \_______\ \|____________|\|__|/ \|__| \|__| \|__|\|__|\|_______| ================================================ FILE: src/main/resources/civilCode.csv ================================================ 编号,名称,上级 11,北京市, 1101,市辖区,11 110101,东城区,1101 110102,西城区,1101 110105,朝阳区,1101 110106,丰台区,1101 110107,石景山区,1101 110108,海淀区,1101 110109,门头沟区,1101 110111,房山区,1101 110112,通州区,1101 110113,顺义区,1101 110114,昌平区,1101 110115,大兴区,1101 110116,怀柔区,1101 110117,平谷区,1101 110118,密云区,1101 110119,延庆区,1101 12,天津市, 1201,市辖区,12 120101,和平区,1201 120102,河东区,1201 120103,河西区,1201 120104,南开区,1201 120105,河北区,1201 120106,红桥区,1201 120110,东丽区,1201 120111,西青区,1201 120112,津南区,1201 120113,北辰区,1201 120114,武清区,1201 120115,宝坻区,1201 120116,滨海新区,1201 120117,宁河区,1201 120118,静海区,1201 120119,蓟州区,1201 13,河北省, 1301,石家庄市,13 130102,长安区,1301 130104,桥西区,1301 130105,新华区,1301 130107,井陉矿区,1301 130108,裕华区,1301 130109,藁城区,1301 130110,鹿泉区,1301 130111,栾城区,1301 130121,井陉县,1301 130123,正定县,1301 130125,行唐县,1301 130126,灵寿县,1301 130127,高邑县,1301 130128,深泽县,1301 130129,赞皇县,1301 130130,无极县,1301 130131,平山县,1301 130132,元氏县,1301 130133,赵县,1301 130181,辛集市,1301 130183,晋州市,1301 130184,新乐市,1301 1302,唐山市,13 130202,路南区,1302 130203,路北区,1302 130204,古冶区,1302 130205,开平区,1302 130207,丰南区,1302 130208,丰润区,1302 130209,曹妃甸区,1302 130224,滦南县,1302 130225,乐亭县,1302 130227,迁西县,1302 130229,玉田县,1302 130281,遵化市,1302 130283,迁安市,1302 130284,滦州市,1302 1303,秦皇岛市,13 130302,海港区,1303 130303,山海关区,1303 130304,北戴河区,1303 130306,抚宁区,1303 130321,青龙满族自治县,1303 130322,昌黎县,1303 130324,卢龙县,1303 1304,邯郸市,13 130402,邯山区,1304 130403,丛台区,1304 130404,复兴区,1304 130406,峰峰矿区,1304 130407,肥乡区,1304 130408,永年区,1304 130423,临漳县,1304 130424,成安县,1304 130425,大名县,1304 130426,涉县,1304 130427,磁县,1304 130430,邱县,1304 130431,鸡泽县,1304 130432,广平县,1304 130433,馆陶县,1304 130434,魏县,1304 130435,曲周县,1304 130481,武安市,1304 1305,邢台市,13 130502,桥东区,1305 130503,桥西区,1305 130521,邢台县,1305 130522,临城县,1305 130523,内丘县,1305 130524,柏乡县,1305 130525,隆尧县,1305 130526,任县,1305 130527,南和县,1305 130528,宁晋县,1305 130529,巨鹿县,1305 130530,新河县,1305 130531,广宗县,1305 130532,平乡县,1305 130533,威县,1305 130534,清河县,1305 130535,临西县,1305 130581,南宫市,1305 130582,沙河市,1305 1306,保定市,13 130602,竞秀区,1306 130606,莲池区,1306 130607,满城区,1306 130608,清苑区,1306 130609,徐水区,1306 130623,涞水县,1306 130624,阜平县,1306 130626,定兴县,1306 130627,唐县,1306 130628,高阳县,1306 130629,容城县,1306 130630,涞源县,1306 130631,望都县,1306 130632,安新县,1306 130633,易县,1306 130634,曲阳县,1306 130635,蠡县,1306 130636,顺平县,1306 130637,博野县,1306 130638,雄县,1306 130681,涿州市,1306 130682,定州市,1306 130683,安国市,1306 130684,高碑店市,1306 1307,张家口市,13 130702,桥东区,1307 130703,桥西区,1307 130705,宣化区,1307 130706,下花园区,1307 130708,万全区,1307 130709,崇礼区,1307 130722,张北县,1307 130723,康保县,1307 130724,沽源县,1307 130725,尚义县,1307 130726,蔚县,1307 130727,阳原县,1307 130728,怀安县,1307 130730,怀来县,1307 130731,涿鹿县,1307 130732,赤城县,1307 1308,承德市,13 130802,双桥区,1308 130803,双滦区,1308 130804,鹰手营子矿区,1308 130821,承德县,1308 130822,兴隆县,1308 130824,滦平县,1308 130825,隆化县,1308 130826,丰宁满族自治县,1308 130827,宽城满族自治县,1308 130828,围场满族蒙古族自治县,1308 130881,平泉市,1308 1309,沧州市,13 130902,新华区,1309 130903,运河区,1309 130921,沧县,1309 130922,青县,1309 130923,东光县,1309 130924,海兴县,1309 130925,盐山县,1309 130926,肃宁县,1309 130927,南皮县,1309 130928,吴桥县,1309 130929,献县,1309 130930,孟村回族自治县,1309 130981,泊头市,1309 130982,任丘市,1309 130983,黄骅市,1309 130984,河间市,1309 1310,廊坊市,13 131002,安次区,1310 131003,广阳区,1310 131022,固安县,1310 131023,永清县,1310 131024,香河县,1310 131025,大城县,1310 131026,文安县,1310 131028,大厂回族自治县,1310 131081,霸州市,1310 131082,三河市,1310 1311,衡水市,13 131102,桃城区,1311 131103,冀州区,1311 131121,枣强县,1311 131122,武邑县,1311 131123,武强县,1311 131124,饶阳县,1311 131125,安平县,1311 131126,故城县,1311 131127,景县,1311 131128,阜城县,1311 131182,深州市,1311 14,山西省, 1401,太原市,14 140105,小店区,1401 140106,迎泽区,1401 140107,杏花岭区,1401 140108,尖草坪区,1401 140109,万柏林区,1401 140110,晋源区,1401 140121,清徐县,1401 140122,阳曲县,1401 140123,娄烦县,1401 140181,古交市,1401 1402,大同市,14 140212,新荣区,1402 140213,平城区,1402 140214,云冈区,1402 140215,云州区,1402 140221,阳高县,1402 140222,天镇县,1402 140223,广灵县,1402 140224,灵丘县,1402 140225,浑源县,1402 140226,左云县,1402 1403,阳泉市,14 140302,城区,1403 140303,矿区,1403 140311,郊区,1403 140321,平定县,1403 140322,盂县,1403 1404,长治市,14 140403,潞州区,1404 140404,上党区,1404 140405,屯留区,1404 140406,潞城区,1404 140423,襄垣县,1404 140425,平顺县,1404 140426,黎城县,1404 140427,壶关县,1404 140428,长子县,1404 140429,武乡县,1404 140430,沁县,1404 140431,沁源县,1404 1405,晋城市,14 140502,城区,1405 140521,沁水县,1405 140522,阳城县,1405 140524,陵川县,1405 140525,泽州县,1405 140581,高平市,1405 1406,朔州市,14 140602,朔城区,1406 140603,平鲁区,1406 140621,山阴县,1406 140622,应县,1406 140623,右玉县,1406 140681,怀仁市,1406 1407,晋中市,14 140702,榆次区,1407 140721,榆社县,1407 140722,左权县,1407 140723,和顺县,1407 140724,昔阳县,1407 140725,寿阳县,1407 140726,太谷县,1407 140727,祁县,1407 140728,平遥县,1407 140729,灵石县,1407 140781,介休市,1407 1408,运城市,14 140802,盐湖区,1408 140821,临猗县,1408 140822,万荣县,1408 140823,闻喜县,1408 140824,稷山县,1408 140825,新绛县,1408 140826,绛县,1408 140827,垣曲县,1408 140828,夏县,1408 140829,平陆县,1408 140830,芮城县,1408 140881,永济市,1408 140882,河津市,1408 1409,忻州市,14 140902,忻府区,1409 140921,定襄县,1409 140922,五台县,1409 140923,代县,1409 140924,繁峙县,1409 140925,宁武县,1409 140926,静乐县,1409 140927,神池县,1409 140928,五寨县,1409 140929,岢岚县,1409 140930,河曲县,1409 140931,保德县,1409 140932,偏关县,1409 140981,原平市,1409 1410,临汾市,14 141002,尧都区,1410 141021,曲沃县,1410 141022,翼城县,1410 141023,襄汾县,1410 141024,洪洞县,1410 141025,古县,1410 141026,安泽县,1410 141027,浮山县,1410 141028,吉县,1410 141029,乡宁县,1410 141030,大宁县,1410 141031,隰县,1410 141032,永和县,1410 141033,蒲县,1410 141034,汾西县,1410 141081,侯马市,1410 141082,霍州市,1410 1411,吕梁市,14 141102,离石区,1411 141121,文水县,1411 141122,交城县,1411 141123,兴县,1411 141124,临县,1411 141125,柳林县,1411 141126,石楼县,1411 141127,岚县,1411 141128,方山县,1411 141129,中阳县,1411 141130,交口县,1411 141181,孝义市,1411 141182,汾阳市,1411 15,内蒙古自治区, 1501,呼和浩特市,15 150102,新城区,1501 150103,回民区,1501 150104,玉泉区,1501 150105,赛罕区,1501 150121,土默特左旗,1501 150122,托克托县,1501 150123,和林格尔县,1501 150124,清水河县,1501 150125,武川县,1501 1502,包头市,15 150202,东河区,1502 150203,昆都仑区,1502 150204,青山区,1502 150205,石拐区,1502 150206,白云鄂博矿区,1502 150207,九原区,1502 150221,土默特右旗,1502 150222,固阳县,1502 150223,达尔罕茂明安联合旗,1502 1503,乌海市,15 150302,海勃湾区,1503 150303,海南区,1503 150304,乌达区,1503 1504,赤峰市,15 150402,红山区,1504 150403,元宝山区,1504 150404,松山区,1504 150421,阿鲁科尔沁旗,1504 150422,巴林左旗,1504 150423,巴林右旗,1504 150424,林西县,1504 150425,克什克腾旗,1504 150426,翁牛特旗,1504 150428,喀喇沁旗,1504 150429,宁城县,1504 150430,敖汉旗,1504 1505,通辽市,15 150502,科尔沁区,1505 150521,科尔沁左翼中旗,1505 150522,科尔沁左翼后旗,1505 150523,开鲁县,1505 150524,库伦旗,1505 150525,奈曼旗,1505 150526,扎鲁特旗,1505 150581,霍林郭勒市,1505 1506,鄂尔多斯市,15 150602,东胜区,1506 150603,康巴什区,1506 150621,达拉特旗,1506 150622,准格尔旗,1506 150623,鄂托克前旗,1506 150624,鄂托克旗,1506 150625,杭锦旗,1506 150626,乌审旗,1506 150627,伊金霍洛旗,1506 1507,呼伦贝尔市,15 150702,海拉尔区,1507 150703,扎赉诺尔区,1507 150721,阿荣旗,1507 150722,莫力达瓦达斡尔族自治旗,1507 150723,鄂伦春自治旗,1507 150724,鄂温克族自治旗,1507 150725,陈巴尔虎旗,1507 150726,新巴尔虎左旗,1507 150727,新巴尔虎右旗,1507 150781,满洲里市,1507 150782,牙克石市,1507 150783,扎兰屯市,1507 150784,额尔古纳市,1507 150785,根河市,1507 1508,巴彦淖尔市,15 150802,临河区,1508 150821,五原县,1508 150822,磴口县,1508 150823,乌拉特前旗,1508 150824,乌拉特中旗,1508 150825,乌拉特后旗,1508 150826,杭锦后旗,1508 1509,乌兰察布市,15 150902,集宁区,1509 150921,卓资县,1509 150922,化德县,1509 150923,商都县,1509 150924,兴和县,1509 150925,凉城县,1509 150926,察哈尔右翼前旗,1509 150927,察哈尔右翼中旗,1509 150928,察哈尔右翼后旗,1509 150929,四子王旗,1509 150981,丰镇市,1509 1522,兴安盟,15 152201,乌兰浩特市,1522 152202,阿尔山市,1522 152221,科尔沁右翼前旗,1522 152222,科尔沁右翼中旗,1522 152223,扎赉特旗,1522 152224,突泉县,1522 1525,锡林郭勒盟,15 152501,二连浩特市,1525 152502,锡林浩特市,1525 152522,阿巴嘎旗,1525 152523,苏尼特左旗,1525 152524,苏尼特右旗,1525 152525,东乌珠穆沁旗,1525 152526,西乌珠穆沁旗,1525 152527,太仆寺旗,1525 152528,镶黄旗,1525 152529,正镶白旗,1525 152530,正蓝旗,1525 152531,多伦县,1525 1529,阿拉善盟,15 152921,阿拉善左旗,1529 152922,阿拉善右旗,1529 152923,额济纳旗,1529 21,辽宁省, 2101,沈阳市,21 210102,和平区,2101 210103,沈河区,2101 210104,大东区,2101 210105,皇姑区,2101 210106,铁西区,2101 210111,苏家屯区,2101 210112,浑南区,2101 210113,沈北新区,2101 210114,于洪区,2101 210115,辽中区,2101 210123,康平县,2101 210124,法库县,2101 210181,新民市,2101 2102,大连市,21 210202,中山区,2102 210203,西岗区,2102 210204,沙河口区,2102 210211,甘井子区,2102 210212,旅顺口区,2102 210213,金州区,2102 210214,普兰店区,2102 210224,长海县,2102 210281,瓦房店市,2102 210283,庄河市,2102 2103,鞍山市,21 210302,铁东区,2103 210303,铁西区,2103 210304,立山区,2103 210311,千山区,2103 210321,台安县,2103 210323,岫岩满族自治县,2103 210381,海城市,2103 2104,抚顺市,21 210402,新抚区,2104 210403,东洲区,2104 210404,望花区,2104 210411,顺城区,2104 210421,抚顺县,2104 210422,新宾满族自治县,2104 210423,清原满族自治县,2104 2105,本溪市,21 210502,平山区,2105 210503,溪湖区,2105 210504,明山区,2105 210505,南芬区,2105 210521,本溪满族自治县,2105 210522,桓仁满族自治县,2105 2106,丹东市,21 210602,元宝区,2106 210603,振兴区,2106 210604,振安区,2106 210624,宽甸满族自治县,2106 210681,东港市,2106 210682,凤城市,2106 2107,锦州市,21 210702,古塔区,2107 210703,凌河区,2107 210711,太和区,2107 210726,黑山县,2107 210727,义县,2107 210781,凌海市,2107 210782,北镇市,2107 2108,营口市,21 210802,站前区,2108 210803,西市区,2108 210804,鲅鱼圈区,2108 210811,老边区,2108 210881,盖州市,2108 210882,大石桥市,2108 2109,阜新市,21 210902,海州区,2109 210903,新邱区,2109 210904,太平区,2109 210905,清河门区,2109 210911,细河区,2109 210921,阜新蒙古族自治县,2109 210922,彰武县,2109 2110,辽阳市,21 211002,白塔区,2110 211003,文圣区,2110 211004,宏伟区,2110 211005,弓长岭区,2110 211011,太子河区,2110 211021,辽阳县,2110 211081,灯塔市,2110 2111,盘锦市,21 211102,双台子区,2111 211103,兴隆台区,2111 211104,大洼区,2111 211122,盘山县,2111 2112,铁岭市,21 211202,银州区,2112 211204,清河区,2112 211221,铁岭县,2112 211223,西丰县,2112 211224,昌图县,2112 211281,调兵山市,2112 211282,开原市,2112 2113,朝阳市,21 211302,双塔区,2113 211303,龙城区,2113 211321,朝阳县,2113 211322,建平县,2113 211324,喀喇沁左翼蒙古族自治县,2113 211381,北票市,2113 211382,凌源市,2113 2114,葫芦岛市,21 211402,连山区,2114 211403,龙港区,2114 211404,南票区,2114 211421,绥中县,2114 211422,建昌县,2114 211481,兴城市,2114 22,吉林省, 2201,长春市,22 220102,南关区,2201 220103,宽城区,2201 220104,朝阳区,2201 220105,二道区,2201 220106,绿园区,2201 220112,双阳区,2201 220113,九台区,2201 220122,农安县,2201 220182,榆树市,2201 220183,德惠市,2201 2202,吉林市,22 220202,昌邑区,2202 220203,龙潭区,2202 220204,船营区,2202 220211,丰满区,2202 220221,永吉县,2202 220281,蛟河市,2202 220282,桦甸市,2202 220283,舒兰市,2202 220284,磐石市,2202 2203,四平市,22 220302,铁西区,2203 220303,铁东区,2203 220322,梨树县,2203 220323,伊通满族自治县,2203 220381,公主岭市,2203 220382,双辽市,2203 2204,辽源市,22 220402,龙山区,2204 220403,西安区,2204 220421,东丰县,2204 220422,东辽县,2204 2205,通化市,22 220502,东昌区,2205 220503,二道江区,2205 220521,通化县,2205 220523,辉南县,2205 220524,柳河县,2205 220581,梅河口市,2205 220582,集安市,2205 2206,白山市,22 220602,浑江区,2206 220605,江源区,2206 220621,抚松县,2206 220622,靖宇县,2206 220623,长白朝鲜族自治县,2206 220681,临江市,2206 2207,松原市,22 220702,宁江区,2207 220721,前郭尔罗斯蒙古族自治县,2207 220722,长岭县,2207 220723,乾安县,2207 220781,扶余市,2207 2208,白城市,22 220802,洮北区,2208 220821,镇赉县,2208 220822,通榆县,2208 220881,洮南市,2208 220882,大安市,2208 2224,延边朝鲜族自治州,22 222401,延吉市,2224 222402,图们市,2224 222403,敦化市,2224 222404,珲春市,2224 222405,龙井市,2224 222406,和龙市,2224 222424,汪清县,2224 222426,安图县,2224 23,黑龙江省, 2301,哈尔滨市,23 230102,道里区,2301 230103,南岗区,2301 230104,道外区,2301 230108,平房区,2301 230109,松北区,2301 230110,香坊区,2301 230111,呼兰区,2301 230112,阿城区,2301 230113,双城区,2301 230123,依兰县,2301 230124,方正县,2301 230125,宾县,2301 230126,巴彦县,2301 230127,木兰县,2301 230128,通河县,2301 230129,延寿县,2301 230183,尚志市,2301 230184,五常市,2301 2302,齐齐哈尔市,23 230202,龙沙区,2302 230203,建华区,2302 230204,铁锋区,2302 230205,昂昂溪区,2302 230206,富拉尔基区,2302 230207,碾子山区,2302 230208,梅里斯达斡尔族区,2302 230221,龙江县,2302 230223,依安县,2302 230224,泰来县,2302 230225,甘南县,2302 230227,富裕县,2302 230229,克山县,2302 230230,克东县,2302 230231,拜泉县,2302 230281,讷河市,2302 2303,鸡西市,23 230302,鸡冠区,2303 230303,恒山区,2303 230304,滴道区,2303 230305,梨树区,2303 230306,城子河区,2303 230307,麻山区,2303 230321,鸡东县,2303 230381,虎林市,2303 230382,密山市,2303 2304,鹤岗市,23 230402,向阳区,2304 230403,工农区,2304 230404,南山区,2304 230405,兴安区,2304 230406,东山区,2304 230407,兴山区,2304 230421,萝北县,2304 230422,绥滨县,2304 2305,双鸭山市,23 230502,尖山区,2305 230503,岭东区,2305 230505,四方台区,2305 230506,宝山区,2305 230521,集贤县,2305 230522,友谊县,2305 230523,宝清县,2305 230524,饶河县,2305 2306,大庆市,23 230602,萨尔图区,2306 230603,龙凤区,2306 230604,让胡路区,2306 230605,红岗区,2306 230606,大同区,2306 230621,肇州县,2306 230622,肇源县,2306 230623,林甸县,2306 230624,杜尔伯特蒙古族自治县,2306 2307,伊春市,23 230702,伊春区,2307 230703,南岔区,2307 230704,友好区,2307 230705,西林区,2307 230706,翠峦区,2307 230707,新青区,2307 230708,美溪区,2307 230709,金山屯区,2307 230710,五营区,2307 230711,乌马河区,2307 230712,汤旺河区,2307 230713,带岭区,2307 230714,乌伊岭区,2307 230715,红星区,2307 230716,上甘岭区,2307 230722,嘉荫县,2307 230781,铁力市,2307 2308,佳木斯市,23 230803,向阳区,2308 230804,前进区,2308 230805,东风区,2308 230811,郊区,2308 230822,桦南县,2308 230826,桦川县,2308 230828,汤原县,2308 230881,同江市,2308 230882,富锦市,2308 230883,抚远市,2308 2309,七台河市,23 230902,新兴区,2309 230903,桃山区,2309 230904,茄子河区,2309 230921,勃利县,2309 2310,牡丹江市,23 231002,东安区,2310 231003,阳明区,2310 231004,爱民区,2310 231005,西安区,2310 231025,林口县,2310 231081,绥芬河市,2310 231083,海林市,2310 231084,宁安市,2310 231085,穆棱市,2310 231086,东宁市,2310 2311,黑河市,23 231102,爱辉区,2311 231121,嫩江县,2311 231123,逊克县,2311 231124,孙吴县,2311 231181,北安市,2311 231182,五大连池市,2311 2312,绥化市,23 231202,北林区,2312 231221,望奎县,2312 231222,兰西县,2312 231223,青冈县,2312 231224,庆安县,2312 231225,明水县,2312 231226,绥棱县,2312 231281,安达市,2312 231282,肇东市,2312 231283,海伦市,2312 2327,大兴安岭地区,23 232701,漠河市,2327 232721,呼玛县,2327 232722,塔河县,2327 31,上海市, 3101,市辖区,31 310101,黄浦区,3101 310104,徐汇区,3101 310105,长宁区,3101 310106,静安区,3101 310107,普陀区,3101 310109,虹口区,3101 310110,杨浦区,3101 310112,闵行区,3101 310113,宝山区,3101 310114,嘉定区,3101 310115,浦东新区,3101 310116,金山区,3101 310117,松江区,3101 310118,青浦区,3101 310120,奉贤区,3101 310151,崇明区,3101 32,江苏省, 3201,南京市,32 320102,玄武区,3201 320104,秦淮区,3201 320105,建邺区,3201 320106,鼓楼区,3201 320111,浦口区,3201 320113,栖霞区,3201 320114,雨花台区,3201 320115,江宁区,3201 320116,六合区,3201 320117,溧水区,3201 320118,高淳区,3201 3202,无锡市,32 320205,锡山区,3202 320206,惠山区,3202 320211,滨湖区,3202 320213,梁溪区,3202 320214,新吴区,3202 320281,江阴市,3202 320282,宜兴市,3202 3203,徐州市,32 320302,鼓楼区,3203 320303,云龙区,3203 320305,贾汪区,3203 320311,泉山区,3203 320312,铜山区,3203 320321,丰县,3203 320322,沛县,3203 320324,睢宁县,3203 320381,新沂市,3203 320382,邳州市,3203 3204,常州市,32 320402,天宁区,3204 320404,钟楼区,3204 320411,新北区,3204 320412,武进区,3204 320413,金坛区,3204 320481,溧阳市,3204 3205,苏州市,32 320505,虎丘区,3205 320506,吴中区,3205 320507,相城区,3205 320508,姑苏区,3205 320509,吴江区,3205 320581,常熟市,3205 320582,张家港市,3205 320583,昆山市,3205 320585,太仓市,3205 3206,南通市,32 320602,崇川区,3206 320611,港闸区,3206 320612,通州区,3206 320623,如东县,3206 320681,启东市,3206 320682,如皋市,3206 320684,海门市,3206 320685,海安市,3206 3207,连云港市,32 320703,连云区,3207 320706,海州区,3207 320707,赣榆区,3207 320722,东海县,3207 320723,灌云县,3207 320724,灌南县,3207 3208,淮安市,32 320803,淮安区,3208 320804,淮阴区,3208 320812,清江浦区,3208 320813,洪泽区,3208 320826,涟水县,3208 320830,盱眙县,3208 320831,金湖县,3208 3209,盐城市,32 320902,亭湖区,3209 320903,盐都区,3209 320904,大丰区,3209 320921,响水县,3209 320922,滨海县,3209 320923,阜宁县,3209 320924,射阳县,3209 320925,建湖县,3209 320981,东台市,3209 3210,扬州市,32 321002,广陵区,3210 321003,邗江区,3210 321012,江都区,3210 321023,宝应县,3210 321081,仪征市,3210 321084,高邮市,3210 3211,镇江市,32 321102,京口区,3211 321111,润州区,3211 321112,丹徒区,3211 321181,丹阳市,3211 321182,扬中市,3211 321183,句容市,3211 3212,泰州市,32 321202,海陵区,3212 321203,高港区,3212 321204,姜堰区,3212 321281,兴化市,3212 321282,靖江市,3212 321283,泰兴市,3212 3213,宿迁市,32 321302,宿城区,3213 321311,宿豫区,3213 321322,沭阳县,3213 321323,泗阳县,3213 321324,泗洪县,3213 33,浙江省, 3301,杭州市,33 330102,上城区,3301 330103,下城区,3301 330104,江干区,3301 330105,拱墅区,3301 330106,西湖区,3301 330108,滨江区,3301 330109,萧山区,3301 330110,余杭区,3301 330111,富阳区,3301 330112,临安区,3301 330122,桐庐县,3301 330127,淳安县,3301 330182,建德市,3301 3302,宁波市,33 330203,海曙区,3302 330205,江北区,3302 330206,北仑区,3302 330211,镇海区,3302 330212,鄞州区,3302 330213,奉化区,3302 330225,象山县,3302 330226,宁海县,3302 330281,余姚市,3302 330282,慈溪市,3302 3303,温州市,33 330302,鹿城区,3303 330303,龙湾区,3303 330304,瓯海区,3303 330305,洞头区,3303 330324,永嘉县,3303 330326,平阳县,3303 330327,苍南县,3303 330328,文成县,3303 330329,泰顺县,3303 330381,瑞安市,3303 330382,乐清市,3303 3304,嘉兴市,33 330402,南湖区,3304 330411,秀洲区,3304 330421,嘉善县,3304 330424,海盐县,3304 330481,海宁市,3304 330482,平湖市,3304 330483,桐乡市,3304 3305,湖州市,33 330502,吴兴区,3305 330503,南浔区,3305 330521,德清县,3305 330522,长兴县,3305 330523,安吉县,3305 3306,绍兴市,33 330602,越城区,3306 330603,柯桥区,3306 330604,上虞区,3306 330624,新昌县,3306 330681,诸暨市,3306 330683,嵊州市,3306 3307,金华市,33 330702,婺城区,3307 330703,金东区,3307 330723,武义县,3307 330726,浦江县,3307 330727,磐安县,3307 330781,兰溪市,3307 330782,义乌市,3307 330783,东阳市,3307 330784,永康市,3307 3308,衢州市,33 330802,柯城区,3308 330803,衢江区,3308 330822,常山县,3308 330824,开化县,3308 330825,龙游县,3308 330881,江山市,3308 3309,舟山市,33 330902,定海区,3309 330903,普陀区,3309 330921,岱山县,3309 330922,嵊泗县,3309 3310,台州市,33 331002,椒江区,3310 331003,黄岩区,3310 331004,路桥区,3310 331022,三门县,3310 331023,天台县,3310 331024,仙居县,3310 331081,温岭市,3310 331082,临海市,3310 331083,玉环市,3310 3311,丽水市,33 331102,莲都区,3311 331121,青田县,3311 331122,缙云县,3311 331123,遂昌县,3311 331124,松阳县,3311 331125,云和县,3311 331126,庆元县,3311 331127,景宁畲族自治县,3311 331181,龙泉市,3311 34,安徽省, 3401,合肥市,34 340102,瑶海区,3401 340103,庐阳区,3401 340104,蜀山区,3401 340111,包河区,3401 340121,长丰县,3401 340122,肥东县,3401 340123,肥西县,3401 340124,庐江县,3401 340181,巢湖市,3401 3402,芜湖市,34 340202,镜湖区,3402 340203,弋江区,3402 340207,鸠江区,3402 340208,三山区,3402 340221,芜湖县,3402 340222,繁昌县,3402 340223,南陵县,3402 340225,无为县,3402 3403,蚌埠市,34 340302,龙子湖区,3403 340303,蚌山区,3403 340304,禹会区,3403 340311,淮上区,3403 340321,怀远县,3403 340322,五河县,3403 340323,固镇县,3403 3404,淮南市,34 340402,大通区,3404 340403,田家庵区,3404 340404,谢家集区,3404 340405,八公山区,3404 340406,潘集区,3404 340421,凤台县,3404 340422,寿县,3404 3405,马鞍山市,34 340503,花山区,3405 340504,雨山区,3405 340506,博望区,3405 340521,当涂县,3405 340522,含山县,3405 340523,和县,3405 3406,淮北市,34 340602,杜集区,3406 340603,相山区,3406 340604,烈山区,3406 340621,濉溪县,3406 3407,铜陵市,34 340705,铜官区,3407 340706,义安区,3407 340711,郊区,3407 340722,枞阳县,3407 3408,安庆市,34 340802,迎江区,3408 340803,大观区,3408 340811,宜秀区,3408 340822,怀宁县,3408 340825,太湖县,3408 340826,宿松县,3408 340827,望江县,3408 340828,岳西县,3408 340881,桐城市,3408 340882,潜山市,3408 3410,黄山市,34 341002,屯溪区,3410 341003,黄山区,3410 341004,徽州区,3410 341021,歙县,3410 341022,休宁县,3410 341023,黟县,3410 341024,祁门县,3410 3411,滁州市,34 341102,琅琊区,3411 341103,南谯区,3411 341122,来安县,3411 341124,全椒县,3411 341125,定远县,3411 341126,凤阳县,3411 341181,天长市,3411 341182,明光市,3411 3412,阜阳市,34 341202,颍州区,3412 341203,颍东区,3412 341204,颍泉区,3412 341221,临泉县,3412 341222,太和县,3412 341225,阜南县,3412 341226,颍上县,3412 341282,界首市,3412 3413,宿州市,34 341302,埇桥区,3413 341321,砀山县,3413 341322,萧县,3413 341323,灵璧县,3413 341324,泗县,3413 3415,六安市,34 341502,金安区,3415 341503,裕安区,3415 341504,叶集区,3415 341522,霍邱县,3415 341523,舒城县,3415 341524,金寨县,3415 341525,霍山县,3415 3416,亳州市,34 341602,谯城区,3416 341621,涡阳县,3416 341622,蒙城县,3416 341623,利辛县,3416 3417,池州市,34 341702,贵池区,3417 341721,东至县,3417 341722,石台县,3417 341723,青阳县,3417 3418,宣城市,34 341802,宣州区,3418 341821,郎溪县,3418 341822,广德县,3418 341823,泾县,3418 341824,绩溪县,3418 341825,旌德县,3418 341881,宁国市,3418 35,福建省, 3501,福州市,35 350102,鼓楼区,3501 350103,台江区,3501 350104,仓山区,3501 350105,马尾区,3501 350111,晋安区,3501 350112,长乐区,3501 350121,闽侯县,3501 350122,连江县,3501 350123,罗源县,3501 350124,闽清县,3501 350125,永泰县,3501 350128,平潭县,3501 350181,福清市,3501 3502,厦门市,35 350203,思明区,3502 350205,海沧区,3502 350206,湖里区,3502 350211,集美区,3502 350212,同安区,3502 350213,翔安区,3502 3503,莆田市,35 350302,城厢区,3503 350303,涵江区,3503 350304,荔城区,3503 350305,秀屿区,3503 350322,仙游县,3503 3504,三明市,35 350402,梅列区,3504 350403,三元区,3504 350421,明溪县,3504 350423,清流县,3504 350424,宁化县,3504 350425,大田县,3504 350426,尤溪县,3504 350427,沙县,3504 350428,将乐县,3504 350429,泰宁县,3504 350430,建宁县,3504 350481,永安市,3504 3505,泉州市,35 350502,鲤城区,3505 350503,丰泽区,3505 350504,洛江区,3505 350505,泉港区,3505 350521,惠安县,3505 350524,安溪县,3505 350525,永春县,3505 350526,德化县,3505 350527,金门县,3505 350581,石狮市,3505 350582,晋江市,3505 350583,南安市,3505 3506,漳州市,35 350602,芗城区,3506 350603,龙文区,3506 350622,云霄县,3506 350623,漳浦县,3506 350624,诏安县,3506 350625,长泰县,3506 350626,东山县,3506 350627,南靖县,3506 350628,平和县,3506 350629,华安县,3506 350681,龙海市,3506 3507,南平市,35 350702,延平区,3507 350703,建阳区,3507 350721,顺昌县,3507 350722,浦城县,3507 350723,光泽县,3507 350724,松溪县,3507 350725,政和县,3507 350781,邵武市,3507 350782,武夷山市,3507 350783,建瓯市,3507 3508,龙岩市,35 350802,新罗区,3508 350803,永定区,3508 350821,长汀县,3508 350823,上杭县,3508 350824,武平县,3508 350825,连城县,3508 350881,漳平市,3508 3509,宁德市,35 350902,蕉城区,3509 350921,霞浦县,3509 350922,古田县,3509 350923,屏南县,3509 350924,寿宁县,3509 350925,周宁县,3509 350926,柘荣县,3509 350981,福安市,3509 350982,福鼎市,3509 36,江西省, 3601,南昌市,36 360102,东湖区,3601 360103,西湖区,3601 360104,青云谱区,3601 360105,湾里区,3601 360111,青山湖区,3601 360112,新建区,3601 360121,南昌县,3601 360123,安义县,3601 360124,进贤县,3601 3602,景德镇市,36 360202,昌江区,3602 360203,珠山区,3602 360222,浮梁县,3602 360281,乐平市,3602 3603,萍乡市,36 360302,安源区,3603 360313,湘东区,3603 360321,莲花县,3603 360322,上栗县,3603 360323,芦溪县,3603 3604,九江市,36 360402,濂溪区,3604 360403,浔阳区,3604 360404,柴桑区,3604 360423,武宁县,3604 360424,修水县,3604 360425,永修县,3604 360426,德安县,3604 360428,都昌县,3604 360429,湖口县,3604 360430,彭泽县,3604 360481,瑞昌市,3604 360482,共青城市,3604 360483,庐山市,3604 3605,新余市,36 360502,渝水区,3605 360521,分宜县,3605 3606,鹰潭市,36 360602,月湖区,3606 360603,余江区,3606 360681,贵溪市,3606 3607,赣州市,36 360702,章贡区,3607 360703,南康区,3607 360704,赣县区,3607 360722,信丰县,3607 360723,大余县,3607 360724,上犹县,3607 360725,崇义县,3607 360726,安远县,3607 360727,龙南县,3607 360728,定南县,3607 360729,全南县,3607 360730,宁都县,3607 360731,于都县,3607 360732,兴国县,3607 360733,会昌县,3607 360734,寻乌县,3607 360735,石城县,3607 360781,瑞金市,3607 3608,吉安市,36 360802,吉州区,3608 360803,青原区,3608 360821,吉安县,3608 360822,吉水县,3608 360823,峡江县,3608 360824,新干县,3608 360825,永丰县,3608 360826,泰和县,3608 360827,遂川县,3608 360828,万安县,3608 360829,安福县,3608 360830,永新县,3608 360881,井冈山市,3608 3609,宜春市,36 360902,袁州区,3609 360921,奉新县,3609 360922,万载县,3609 360923,上高县,3609 360924,宜丰县,3609 360925,靖安县,3609 360926,铜鼓县,3609 360981,丰城市,3609 360982,樟树市,3609 360983,高安市,3609 3610,抚州市,36 361002,临川区,3610 361003,东乡区,3610 361021,南城县,3610 361022,黎川县,3610 361023,南丰县,3610 361024,崇仁县,3610 361025,乐安县,3610 361026,宜黄县,3610 361027,金溪县,3610 361028,资溪县,3610 361030,广昌县,3610 3611,上饶市,36 361102,信州区,3611 361103,广丰区,3611 361121,上饶县,3611 361123,玉山县,3611 361124,铅山县,3611 361125,横峰县,3611 361126,弋阳县,3611 361127,余干县,3611 361128,鄱阳县,3611 361129,万年县,3611 361130,婺源县,3611 361181,德兴市,3611 37,山东省, 3701,济南市,37 370102,历下区,3701 370103,市中区,3701 370104,槐荫区,3701 370105,天桥区,3701 370112,历城区,3701 370113,长清区,3701 370114,章丘区,3701 370115,济阳区,3701 370124,平阴县,3701 370126,商河县,3701 3702,青岛市,37 370202,市南区,3702 370203,市北区,3702 370211,黄岛区,3702 370212,崂山区,3702 370213,李沧区,3702 370214,城阳区,3702 370215,即墨区,3702 370281,胶州市,3702 370283,平度市,3702 370285,莱西市,3702 3703,淄博市,37 370302,淄川区,3703 370303,张店区,3703 370304,博山区,3703 370305,临淄区,3703 370306,周村区,3703 370321,桓台县,3703 370322,高青县,3703 370323,沂源县,3703 3704,枣庄市,37 370402,市中区,3704 370403,薛城区,3704 370404,峄城区,3704 370405,台儿庄区,3704 370406,山亭区,3704 370481,滕州市,3704 3705,东营市,37 370502,东营区,3705 370503,河口区,3705 370505,垦利区,3705 370522,利津县,3705 370523,广饶县,3705 3706,烟台市,37 370602,芝罘区,3706 370611,福山区,3706 370612,牟平区,3706 370613,莱山区,3706 370634,长岛县,3706 370681,龙口市,3706 370682,莱阳市,3706 370683,莱州市,3706 370684,蓬莱市,3706 370685,招远市,3706 370686,栖霞市,3706 370687,海阳市,3706 3707,潍坊市,37 370702,潍城区,3707 370703,寒亭区,3707 370704,坊子区,3707 370705,奎文区,3707 370724,临朐县,3707 370725,昌乐县,3707 370781,青州市,3707 370782,诸城市,3707 370783,寿光市,3707 370784,安丘市,3707 370785,高密市,3707 370786,昌邑市,3707 3708,济宁市,37 370811,任城区,3708 370812,兖州区,3708 370826,微山县,3708 370827,鱼台县,3708 370828,金乡县,3708 370829,嘉祥县,3708 370830,汶上县,3708 370831,泗水县,3708 370832,梁山县,3708 370881,曲阜市,3708 370883,邹城市,3708 3709,泰安市,37 370902,泰山区,3709 370911,岱岳区,3709 370921,宁阳县,3709 370923,东平县,3709 370982,新泰市,3709 370983,肥城市,3709 3710,威海市,37 371002,环翠区,3710 371003,文登区,3710 371082,荣成市,3710 371083,乳山市,3710 3711,日照市,37 371102,东港区,3711 371103,岚山区,3711 371121,五莲县,3711 371122,莒县,3711 3712,莱芜市,37 371202,莱城区,3712 371203,钢城区,3712 3713,临沂市,37 371302,兰山区,3713 371311,罗庄区,3713 371312,河东区,3713 371321,沂南县,3713 371322,郯城县,3713 371323,沂水县,3713 371324,兰陵县,3713 371325,费县,3713 371326,平邑县,3713 371327,莒南县,3713 371328,蒙阴县,3713 371329,临沭县,3713 3714,德州市,37 371402,德城区,3714 371403,陵城区,3714 371422,宁津县,3714 371423,庆云县,3714 371424,临邑县,3714 371425,齐河县,3714 371426,平原县,3714 371427,夏津县,3714 371428,武城县,3714 371481,乐陵市,3714 371482,禹城市,3714 3715,聊城市,37 371502,东昌府区,3715 371521,阳谷县,3715 371522,莘县,3715 371523,茌平县,3715 371524,东阿县,3715 371525,冠县,3715 371526,高唐县,3715 371581,临清市,3715 3716,滨州市,37 371602,滨城区,3716 371603,沾化区,3716 371621,惠民县,3716 371622,阳信县,3716 371623,无棣县,3716 371625,博兴县,3716 371681,邹平市,3716 3717,菏泽市,37 371702,牡丹区,3717 371703,定陶区,3717 371721,曹县,3717 371722,单县,3717 371723,成武县,3717 371724,巨野县,3717 371725,郓城县,3717 371726,鄄城县,3717 371728,东明县,3717 41,河南省, 4101,郑州市,41 410102,中原区,4101 410103,二七区,4101 410104,管城回族区,4101 410105,金水区,4101 410106,上街区,4101 410108,惠济区,4101 410122,中牟县,4101 410181,巩义市,4101 410182,荥阳市,4101 410183,新密市,4101 410184,新郑市,4101 410185,登封市,4101 4102,开封市,41 410202,龙亭区,4102 410203,顺河回族区,4102 410204,鼓楼区,4102 410205,禹王台区,4102 410212,祥符区,4102 410221,杞县,4102 410222,通许县,4102 410223,尉氏县,4102 410225,兰考县,4102 4103,洛阳市,41 410302,老城区,4103 410303,西工区,4103 410304,瀍河回族区,4103 410305,涧西区,4103 410306,吉利区,4103 410311,洛龙区,4103 410322,孟津县,4103 410323,新安县,4103 410324,栾川县,4103 410325,嵩县,4103 410326,汝阳县,4103 410327,宜阳县,4103 410328,洛宁县,4103 410329,伊川县,4103 410381,偃师市,4103 4104,平顶山市,41 410402,新华区,4104 410403,卫东区,4104 410404,石龙区,4104 410411,湛河区,4104 410421,宝丰县,4104 410422,叶县,4104 410423,鲁山县,4104 410425,郏县,4104 410481,舞钢市,4104 410482,汝州市,4104 4105,安阳市,41 410502,文峰区,4105 410503,北关区,4105 410505,殷都区,4105 410506,龙安区,4105 410522,安阳县,4105 410523,汤阴县,4105 410526,滑县,4105 410527,内黄县,4105 410581,林州市,4105 4106,鹤壁市,41 410602,鹤山区,4106 410603,山城区,4106 410611,淇滨区,4106 410621,浚县,4106 410622,淇县,4106 4107,新乡市,41 410702,红旗区,4107 410703,卫滨区,4107 410704,凤泉区,4107 410711,牧野区,4107 410721,新乡县,4107 410724,获嘉县,4107 410725,原阳县,4107 410726,延津县,4107 410727,封丘县,4107 410728,长垣县,4107 410781,卫辉市,4107 410782,辉县市,4107 4108,焦作市,41 410802,解放区,4108 410803,中站区,4108 410804,马村区,4108 410811,山阳区,4108 410821,修武县,4108 410822,博爱县,4108 410823,武陟县,4108 410825,温县,4108 410882,沁阳市,4108 410883,孟州市,4108 4109,濮阳市,41 410902,华龙区,4109 410922,清丰县,4109 410923,南乐县,4109 410926,范县,4109 410927,台前县,4109 410928,濮阳县,4109 4110,许昌市,41 411002,魏都区,4110 411003,建安区,4110 411024,鄢陵县,4110 411025,襄城县,4110 411081,禹州市,4110 411082,长葛市,4110 4111,漯河市,41 411102,源汇区,4111 411103,郾城区,4111 411104,召陵区,4111 411121,舞阳县,4111 411122,临颍县,4111 4112,三门峡市,41 411202,湖滨区,4112 411203,陕州区,4112 411221,渑池县,4112 411224,卢氏县,4112 411281,义马市,4112 411282,灵宝市,4112 4113,南阳市,41 411302,宛城区,4113 411303,卧龙区,4113 411321,南召县,4113 411322,方城县,4113 411323,西峡县,4113 411324,镇平县,4113 411325,内乡县,4113 411326,淅川县,4113 411327,社旗县,4113 411328,唐河县,4113 411329,新野县,4113 411330,桐柏县,4113 411381,邓州市,4113 4114,商丘市,41 411402,梁园区,4114 411403,睢阳区,4114 411421,民权县,4114 411422,睢县,4114 411423,宁陵县,4114 411424,柘城县,4114 411425,虞城县,4114 411426,夏邑县,4114 411481,永城市,4114 4115,信阳市,41 411502,浉河区,4115 411503,平桥区,4115 411521,罗山县,4115 411522,光山县,4115 411523,新县,4115 411524,商城县,4115 411525,固始县,4115 411526,潢川县,4115 411527,淮滨县,4115 411528,息县,4115 4116,周口市,41 411602,川汇区,4116 411621,扶沟县,4116 411622,西华县,4116 411623,商水县,4116 411624,沈丘县,4116 411625,郸城县,4116 411626,淮阳县,4116 411627,太康县,4116 411628,鹿邑县,4116 411681,项城市,4116 4117,驻马店市,41 411702,驿城区,4117 411721,西平县,4117 411722,上蔡县,4117 411723,平舆县,4117 411724,正阳县,4117 411725,确山县,4117 411726,泌阳县,4117 411727,汝南县,4117 411728,遂平县,4117 411729,新蔡县,4117 419001,济源市,41 42,湖北省, 4201,武汉市,42 420102,江岸区,4201 420103,江汉区,4201 420104,硚口区,4201 420105,汉阳区,4201 420106,武昌区,4201 420107,青山区,4201 420111,洪山区,4201 420112,东西湖区,4201 420113,汉南区,4201 420114,蔡甸区,4201 420115,江夏区,4201 420116,黄陂区,4201 420117,新洲区,4201 4202,黄石市,42 420202,黄石港区,4202 420203,西塞山区,4202 420204,下陆区,4202 420205,铁山区,4202 420222,阳新县,4202 420281,大冶市,4202 4203,十堰市,42 420302,茅箭区,4203 420303,张湾区,4203 420304,郧阳区,4203 420322,郧西县,4203 420323,竹山县,4203 420324,竹溪县,4203 420325,房县,4203 420381,丹江口市,4203 4205,宜昌市,42 420502,西陵区,4205 420503,伍家岗区,4205 420504,点军区,4205 420505,猇亭区,4205 420506,夷陵区,4205 420525,远安县,4205 420526,兴山县,4205 420527,秭归县,4205 420528,长阳土家族自治县,4205 420529,五峰土家族自治县,4205 420581,宜都市,4205 420582,当阳市,4205 420583,枝江市,4205 4206,襄阳市,42 420602,襄城区,4206 420606,樊城区,4206 420607,襄州区,4206 420624,南漳县,4206 420625,谷城县,4206 420626,保康县,4206 420682,老河口市,4206 420683,枣阳市,4206 420684,宜城市,4206 4207,鄂州市,42 420702,梁子湖区,4207 420703,华容区,4207 420704,鄂城区,4207 4208,荆门市,42 420802,东宝区,4208 420804,掇刀区,4208 420822,沙洋县,4208 420881,钟祥市,4208 420882,京山市,4208 4209,孝感市,42 420902,孝南区,4209 420921,孝昌县,4209 420922,大悟县,4209 420923,云梦县,4209 420981,应城市,4209 420982,安陆市,4209 420984,汉川市,4209 4210,荆州市,42 421002,沙市区,4210 421003,荆州区,4210 421022,公安县,4210 421023,监利县,4210 421024,江陵县,4210 421081,石首市,4210 421083,洪湖市,4210 421087,松滋市,4210 4211,黄冈市,42 421102,黄州区,4211 421121,团风县,4211 421122,红安县,4211 421123,罗田县,4211 421124,英山县,4211 421125,浠水县,4211 421126,蕲春县,4211 421127,黄梅县,4211 421181,麻城市,4211 421182,武穴市,4211 4212,咸宁市,42 421202,咸安区,4212 421221,嘉鱼县,4212 421222,通城县,4212 421223,崇阳县,4212 421224,通山县,4212 421281,赤壁市,4212 4213,随州市,42 421303,曾都区,4213 421321,随县,4213 421381,广水市,4213 4228,恩施土家族苗族自治州,42 422801,恩施市,4228 422802,利川市,4228 422822,建始县,4228 422823,巴东县,4228 422825,宣恩县,4228 422826,咸丰县,4228 422827,来凤县,4228 422828,鹤峰县,4228 429004,仙桃市,42 429005,潜江市,42 429006,天门市,42 429021,神农架林区,42 43,湖南省, 4301,长沙市,43 430102,芙蓉区,4301 430103,天心区,4301 430104,岳麓区,4301 430105,开福区,4301 430111,雨花区,4301 430112,望城区,4301 430121,长沙县,4301 430181,浏阳市,4301 430182,宁乡市,4301 4302,株洲市,43 430202,荷塘区,4302 430203,芦淞区,4302 430204,石峰区,4302 430211,天元区,4302 430212,渌口区,4302 430223,攸县,4302 430224,茶陵县,4302 430225,炎陵县,4302 430281,醴陵市,4302 4303,湘潭市,43 430302,雨湖区,4303 430304,岳塘区,4303 430321,湘潭县,4303 430381,湘乡市,4303 430382,韶山市,4303 4304,衡阳市,43 430405,珠晖区,4304 430406,雁峰区,4304 430407,石鼓区,4304 430408,蒸湘区,4304 430412,南岳区,4304 430421,衡阳县,4304 430422,衡南县,4304 430423,衡山县,4304 430424,衡东县,4304 430426,祁东县,4304 430481,耒阳市,4304 430482,常宁市,4304 4305,邵阳市,43 430502,双清区,4305 430503,大祥区,4305 430511,北塔区,4305 430521,邵东县,4305 430522,新邵县,4305 430523,邵阳县,4305 430524,隆回县,4305 430525,洞口县,4305 430527,绥宁县,4305 430528,新宁县,4305 430529,城步苗族自治县,4305 430581,武冈市,4305 4306,岳阳市,43 430602,岳阳楼区,4306 430603,云溪区,4306 430611,君山区,4306 430621,岳阳县,4306 430623,华容县,4306 430624,湘阴县,4306 430626,平江县,4306 430681,汨罗市,4306 430682,临湘市,4306 4307,常德市,43 430702,武陵区,4307 430703,鼎城区,4307 430721,安乡县,4307 430722,汉寿县,4307 430723,澧县,4307 430724,临澧县,4307 430725,桃源县,4307 430726,石门县,4307 430781,津市市,4307 4308,张家界市,43 430802,永定区,4308 430811,武陵源区,4308 430821,慈利县,4308 430822,桑植县,4308 4309,益阳市,43 430902,资阳区,4309 430903,赫山区,4309 430921,南县,4309 430922,桃江县,4309 430923,安化县,4309 430981,沅江市,4309 4310,郴州市,43 431002,北湖区,4310 431003,苏仙区,4310 431021,桂阳县,4310 431022,宜章县,4310 431023,永兴县,4310 431024,嘉禾县,4310 431025,临武县,4310 431026,汝城县,4310 431027,桂东县,4310 431028,安仁县,4310 431081,资兴市,4310 4311,永州市,43 431102,零陵区,4311 431103,冷水滩区,4311 431121,祁阳县,4311 431122,东安县,4311 431123,双牌县,4311 431124,道县,4311 431125,江永县,4311 431126,宁远县,4311 431127,蓝山县,4311 431128,新田县,4311 431129,江华瑶族自治县,4311 4312,怀化市,43 431202,鹤城区,4312 431221,中方县,4312 431222,沅陵县,4312 431223,辰溪县,4312 431224,溆浦县,4312 431225,会同县,4312 431226,麻阳苗族自治县,4312 431227,新晃侗族自治县,4312 431228,芷江侗族自治县,4312 431229,靖州苗族侗族自治县,4312 431230,通道侗族自治县,4312 431281,洪江市,4312 4313,娄底市,43 431302,娄星区,4313 431321,双峰县,4313 431322,新化县,4313 431381,冷水江市,4313 431382,涟源市,4313 4331,湘西土家族苗族自治州,43 433101,吉首市,4331 433122,泸溪县,4331 433123,凤凰县,4331 433124,花垣县,4331 433125,保靖县,4331 433126,古丈县,4331 433127,永顺县,4331 433130,龙山县,4331 44,广东省, 4401,广州市,44 440103,荔湾区,4401 440104,越秀区,4401 440105,海珠区,4401 440106,天河区,4401 440111,白云区,4401 440112,黄埔区,4401 440113,番禺区,4401 440114,花都区,4401 440115,南沙区,4401 440117,从化区,4401 440118,增城区,4401 4402,韶关市,44 440203,武江区,4402 440204,浈江区,4402 440205,曲江区,4402 440222,始兴县,4402 440224,仁化县,4402 440229,翁源县,4402 440232,乳源瑶族自治县,4402 440233,新丰县,4402 440281,乐昌市,4402 440282,南雄市,4402 4403,深圳市,44 440303,罗湖区,4403 440304,福田区,4403 440305,南山区,4403 440306,宝安区,4403 440307,龙岗区,4403 440308,盐田区,4403 440309,龙华区,4403 440310,坪山区,4403 440311,光明区,4403 4404,珠海市,44 440402,香洲区,4404 440403,斗门区,4404 440404,金湾区,4404 4405,汕头市,44 440507,龙湖区,4405 440511,金平区,4405 440512,濠江区,4405 440513,潮阳区,4405 440514,潮南区,4405 440515,澄海区,4405 440523,南澳县,4405 4406,佛山市,44 440604,禅城区,4406 440605,南海区,4406 440606,顺德区,4406 440607,三水区,4406 440608,高明区,4406 4407,江门市,44 440703,蓬江区,4407 440704,江海区,4407 440705,新会区,4407 440781,台山市,4407 440783,开平市,4407 440784,鹤山市,4407 440785,恩平市,4407 4408,湛江市,44 440802,赤坎区,4408 440803,霞山区,4408 440804,坡头区,4408 440811,麻章区,4408 440823,遂溪县,4408 440825,徐闻县,4408 440881,廉江市,4408 440882,雷州市,4408 440883,吴川市,4408 4409,茂名市,44 440902,茂南区,4409 440904,电白区,4409 440981,高州市,4409 440982,化州市,4409 440983,信宜市,4409 4412,肇庆市,44 441202,端州区,4412 441203,鼎湖区,4412 441204,高要区,4412 441223,广宁县,4412 441224,怀集县,4412 441225,封开县,4412 441226,德庆县,4412 441284,四会市,4412 4413,惠州市,44 441302,惠城区,4413 441303,惠阳区,4413 441322,博罗县,4413 441323,惠东县,4413 441324,龙门县,4413 4414,梅州市,44 441402,梅江区,4414 441403,梅县区,4414 441422,大埔县,4414 441423,丰顺县,4414 441424,五华县,4414 441426,平远县,4414 441427,蕉岭县,4414 441481,兴宁市,4414 4415,汕尾市,44 441502,城区,4415 441521,海丰县,4415 441523,陆河县,4415 441581,陆丰市,4415 4416,河源市,44 441602,源城区,4416 441621,紫金县,4416 441622,龙川县,4416 441623,连平县,4416 441624,和平县,4416 441625,东源县,4416 4417,阳江市,44 441702,江城区,4417 441704,阳东区,4417 441721,阳西县,4417 441781,阳春市,4417 4418,清远市,44 441802,清城区,4418 441803,清新区,4418 441821,佛冈县,4418 441823,阳山县,4418 441825,连山壮族瑶族自治县,4418 441826,连南瑶族自治县,4418 441881,英德市,4418 441882,连州市,4418 4419,东莞市,44 4420,中山市,44 4451,潮州市,44 445102,湘桥区,4451 445103,潮安区,4451 445122,饶平县,4451 4452,揭阳市,44 445202,榕城区,4452 445203,揭东区,4452 445222,揭西县,4452 445224,惠来县,4452 445281,普宁市,4452 4453,云浮市,44 445302,云城区,4453 445303,云安区,4453 445321,新兴县,4453 445322,郁南县,4453 445381,罗定市,4453 45,广西壮族自治区, 4501,南宁市,45 450102,兴宁区,4501 450103,青秀区,4501 450105,江南区,4501 450107,西乡塘区,4501 450108,良庆区,4501 450109,邕宁区,4501 450110,武鸣区,4501 450123,隆安县,4501 450124,马山县,4501 450125,上林县,4501 450126,宾阳县,4501 450127,横县,4501 4502,柳州市,45 450202,城中区,4502 450203,鱼峰区,4502 450204,柳南区,4502 450205,柳北区,4502 450206,柳江区,4502 450222,柳城县,4502 450223,鹿寨县,4502 450224,融安县,4502 450225,融水苗族自治县,4502 450226,三江侗族自治县,4502 4503,桂林市,45 450302,秀峰区,4503 450303,叠彩区,4503 450304,象山区,4503 450305,七星区,4503 450311,雁山区,4503 450312,临桂区,4503 450321,阳朔县,4503 450323,灵川县,4503 450324,全州县,4503 450325,兴安县,4503 450326,永福县,4503 450327,灌阳县,4503 450328,龙胜各族自治县,4503 450329,资源县,4503 450330,平乐县,4503 450332,恭城瑶族自治县,4503 450381,荔浦市,4503 4504,梧州市,45 450403,万秀区,4504 450405,长洲区,4504 450406,龙圩区,4504 450421,苍梧县,4504 450422,藤县,4504 450423,蒙山县,4504 450481,岑溪市,4504 4505,北海市,45 450502,海城区,4505 450503,银海区,4505 450512,铁山港区,4505 450521,合浦县,4505 4506,防城港市,45 450602,港口区,4506 450603,防城区,4506 450621,上思县,4506 450681,东兴市,4506 4507,钦州市,45 450702,钦南区,4507 450703,钦北区,4507 450721,灵山县,4507 450722,浦北县,4507 4508,贵港市,45 450802,港北区,4508 450803,港南区,4508 450804,覃塘区,4508 450821,平南县,4508 450881,桂平市,4508 4509,玉林市,45 450902,玉州区,4509 450903,福绵区,4509 450921,容县,4509 450922,陆川县,4509 450923,博白县,4509 450924,兴业县,4509 450981,北流市,4509 4510,百色市,45 451002,右江区,4510 451021,田阳县,4510 451022,田东县,4510 451023,平果县,4510 451024,德保县,4510 451026,那坡县,4510 451027,凌云县,4510 451028,乐业县,4510 451029,田林县,4510 451030,西林县,4510 451031,隆林各族自治县,4510 451081,靖西市,4510 4511,贺州市,45 451102,八步区,4511 451103,平桂区,4511 451121,昭平县,4511 451122,钟山县,4511 451123,富川瑶族自治县,4511 4512,河池市,45 451202,金城江区,4512 451203,宜州区,4512 451221,南丹县,4512 451222,天峨县,4512 451223,凤山县,4512 451224,东兰县,4512 451225,罗城仫佬族自治县,4512 451226,环江毛南族自治县,4512 451227,巴马瑶族自治县,4512 451228,都安瑶族自治县,4512 451229,大化瑶族自治县,4512 4513,来宾市,45 451302,兴宾区,4513 451321,忻城县,4513 451322,象州县,4513 451323,武宣县,4513 451324,金秀瑶族自治县,4513 451381,合山市,4513 4514,崇左市,45 451402,江州区,4514 451421,扶绥县,4514 451422,宁明县,4514 451423,龙州县,4514 451424,大新县,4514 451425,天等县,4514 451481,凭祥市,4514 46,海南省, 4601,海口市,46 460105,秀英区,4601 460106,龙华区,4601 460107,琼山区,4601 460108,美兰区,4601 4602,三亚市,46 460202,海棠区,4602 460203,吉阳区,4602 460204,天涯区,4602 460205,崖州区,4602 4603,三沙市,46 4604,儋州市,46 469001,五指山市,46 469002,琼海市,46 469005,文昌市,46 469006,万宁市,46 469007,东方市,46 469021,定安县,46 469022,屯昌县,46 469023,澄迈县,46 469024,临高县,46 469025,白沙黎族自治县,46 469026,昌江黎族自治县,46 469027,乐东黎族自治县,46 469028,陵水黎族自治县,46 469029,保亭黎族苗族自治县,46 469030,琼中黎族苗族自治县,46 50,重庆市, 5001,市辖区,50 500101,万州区,5001 500102,涪陵区,5001 500103,渝中区,5001 500104,大渡口区,5001 500105,江北区,5001 500106,沙坪坝区,5001 500107,九龙坡区,5001 500108,南岸区,5001 500109,北碚区,5001 500110,綦江区,5001 500111,大足区,5001 500112,渝北区,5001 500113,巴南区,5001 500114,黔江区,5001 500115,长寿区,5001 500116,江津区,5001 500117,合川区,5001 500118,永川区,5001 500119,南川区,5001 500120,璧山区,5001 500151,铜梁区,5001 500152,潼南区,5001 500153,荣昌区,5001 500154,开州区,5001 500155,梁平区,5001 500156,武隆区,5001 500229,城口县,5001 500230,丰都县,5001 500231,垫江县,5001 500233,忠县,5001 500235,云阳县,5001 500236,奉节县,5001 500237,巫山县,5001 500238,巫溪县,5001 500240,石柱土家族自治县,5001 500241,秀山土家族苗族自治县,5001 500242,酉阳土家族苗族自治县,5001 500243,彭水苗族土家族自治县,5001 51,四川省, 5101,成都市,51 510104,锦江区,5101 510105,青羊区,5101 510106,金牛区,5101 510107,武侯区,5101 510108,成华区,5101 510112,龙泉驿区,5101 510113,青白江区,5101 510114,新都区,5101 510115,温江区,5101 510116,双流区,5101 510117,郫都区,5101 510121,金堂县,5101 510129,大邑县,5101 510131,蒲江县,5101 510132,新津县,5101 510181,都江堰市,5101 510182,彭州市,5101 510183,邛崃市,5101 510184,崇州市,5101 510185,简阳市,5101 5103,自贡市,51 510302,自流井区,5103 510303,贡井区,5103 510304,大安区,5103 510311,沿滩区,5103 510321,荣县,5103 510322,富顺县,5103 5104,攀枝花市,51 510402,东区,5104 510403,西区,5104 510411,仁和区,5104 510421,米易县,5104 510422,盐边县,5104 5105,泸州市,51 510502,江阳区,5105 510503,纳溪区,5105 510504,龙马潭区,5105 510521,泸县,5105 510522,合江县,5105 510524,叙永县,5105 510525,古蔺县,5105 5106,德阳市,51 510603,旌阳区,5106 510604,罗江区,5106 510623,中江县,5106 510681,广汉市,5106 510682,什邡市,5106 510683,绵竹市,5106 5107,绵阳市,51 510703,涪城区,5107 510704,游仙区,5107 510705,安州区,5107 510722,三台县,5107 510723,盐亭县,5107 510725,梓潼县,5107 510726,北川羌族自治县,5107 510727,平武县,5107 510781,江油市,5107 5108,广元市,51 510802,利州区,5108 510811,昭化区,5108 510812,朝天区,5108 510821,旺苍县,5108 510822,青川县,5108 510823,剑阁县,5108 510824,苍溪县,5108 5109,遂宁市,51 510903,船山区,5109 510904,安居区,5109 510921,蓬溪县,5109 510922,射洪县,5109 510923,大英县,5109 5110,内江市,51 511002,市中区,5110 511011,东兴区,5110 511024,威远县,5110 511025,资中县,5110 511083,隆昌市,5110 5111,乐山市,51 511102,市中区,5111 511111,沙湾区,5111 511112,五通桥区,5111 511113,金口河区,5111 511123,犍为县,5111 511124,井研县,5111 511126,夹江县,5111 511129,沐川县,5111 511132,峨边彝族自治县,5111 511133,马边彝族自治县,5111 511181,峨眉山市,5111 5113,南充市,51 511302,顺庆区,5113 511303,高坪区,5113 511304,嘉陵区,5113 511321,南部县,5113 511322,营山县,5113 511323,蓬安县,5113 511324,仪陇县,5113 511325,西充县,5113 511381,阆中市,5113 5114,眉山市,51 511402,东坡区,5114 511403,彭山区,5114 511421,仁寿县,5114 511423,洪雅县,5114 511424,丹棱县,5114 511425,青神县,5114 5115,宜宾市,51 511502,翠屏区,5115 511503,南溪区,5115 511504,叙州区,5115 511523,江安县,5115 511524,长宁县,5115 511525,高县,5115 511526,珙县,5115 511527,筠连县,5115 511528,兴文县,5115 511529,屏山县,5115 5116,广安市,51 511602,广安区,5116 511603,前锋区,5116 511621,岳池县,5116 511622,武胜县,5116 511623,邻水县,5116 511681,华蓥市,5116 5117,达州市,51 511702,通川区,5117 511703,达川区,5117 511722,宣汉县,5117 511723,开江县,5117 511724,大竹县,5117 511725,渠县,5117 511781,万源市,5117 5118,雅安市,51 511802,雨城区,5118 511803,名山区,5118 511822,荥经县,5118 511823,汉源县,5118 511824,石棉县,5118 511825,天全县,5118 511826,芦山县,5118 511827,宝兴县,5118 5119,巴中市,51 511902,巴州区,5119 511903,恩阳区,5119 511921,通江县,5119 511922,南江县,5119 511923,平昌县,5119 5120,资阳市,51 512002,雁江区,5120 512021,安岳县,5120 512022,乐至县,5120 5132,阿坝藏族羌族自治州,51 513201,马尔康市,5132 513221,汶川县,5132 513222,理县,5132 513223,茂县,5132 513224,松潘县,5132 513225,九寨沟县,5132 513226,金川县,5132 513227,小金县,5132 513228,黑水县,5132 513230,壤塘县,5132 513231,阿坝县,5132 513232,若尔盖县,5132 513233,红原县,5132 5133,甘孜藏族自治州,51 513301,康定市,5133 513322,泸定县,5133 513323,丹巴县,5133 513324,九龙县,5133 513325,雅江县,5133 513326,道孚县,5133 513327,炉霍县,5133 513328,甘孜县,5133 513329,新龙县,5133 513330,德格县,5133 513331,白玉县,5133 513332,石渠县,5133 513333,色达县,5133 513334,理塘县,5133 513335,巴塘县,5133 513336,乡城县,5133 513337,稻城县,5133 513338,得荣县,5133 5134,凉山彝族自治州,51 513401,西昌市,5134 513422,木里藏族自治县,5134 513423,盐源县,5134 513424,德昌县,5134 513425,会理县,5134 513426,会东县,5134 513427,宁南县,5134 513428,普格县,5134 513429,布拖县,5134 513430,金阳县,5134 513431,昭觉县,5134 513432,喜德县,5134 513433,冕宁县,5134 513434,越西县,5134 513435,甘洛县,5134 513436,美姑县,5134 513437,雷波县,5134 52,贵州省, 5201,贵阳市,52 520102,南明区,5201 520103,云岩区,5201 520111,花溪区,5201 520112,乌当区,5201 520113,白云区,5201 520115,观山湖区,5201 520121,开阳县,5201 520122,息烽县,5201 520123,修文县,5201 520181,清镇市,5201 5202,六盘水市,52 520201,钟山区,5202 520203,六枝特区,5202 520221,水城县,5202 520281,盘州市,5202 5203,遵义市,52 520302,红花岗区,5203 520303,汇川区,5203 520304,播州区,5203 520322,桐梓县,5203 520323,绥阳县,5203 520324,正安县,5203 520325,道真仡佬族苗族自治县,5203 520326,务川仡佬族苗族自治县,5203 520327,凤冈县,5203 520328,湄潭县,5203 520329,余庆县,5203 520330,习水县,5203 520381,赤水市,5203 520382,仁怀市,5203 5204,安顺市,52 520402,西秀区,5204 520403,平坝区,5204 520422,普定县,5204 520423,镇宁布依族苗族自治县,5204 520424,关岭布依族苗族自治县,5204 520425,紫云苗族布依族自治县,5204 5205,毕节市,52 520502,七星关区,5205 520521,大方县,5205 520522,黔西县,5205 520523,金沙县,5205 520524,织金县,5205 520525,纳雍县,5205 520526,威宁彝族回族苗族自治县,5205 520527,赫章县,5205 5206,铜仁市,52 520602,碧江区,5206 520603,万山区,5206 520621,江口县,5206 520622,玉屏侗族自治县,5206 520623,石阡县,5206 520624,思南县,5206 520625,印江土家族苗族自治县,5206 520626,德江县,5206 520627,沿河土家族自治县,5206 520628,松桃苗族自治县,5206 5223,黔西南布依族苗族自治州,52 522301,兴义市,5223 522302,兴仁市,5223 522323,普安县,5223 522324,晴隆县,5223 522325,贞丰县,5223 522326,望谟县,5223 522327,册亨县,5223 522328,安龙县,5223 5226,黔东南苗族侗族自治州,52 522601,凯里市,5226 522622,黄平县,5226 522623,施秉县,5226 522624,三穗县,5226 522625,镇远县,5226 522626,岑巩县,5226 522627,天柱县,5226 522628,锦屏县,5226 522629,剑河县,5226 522630,台江县,5226 522631,黎平县,5226 522632,榕江县,5226 522633,从江县,5226 522634,雷山县,5226 522635,麻江县,5226 522636,丹寨县,5226 5227,黔南布依族苗族自治州,52 522701,都匀市,5227 522702,福泉市,5227 522722,荔波县,5227 522723,贵定县,5227 522725,瓮安县,5227 522726,独山县,5227 522727,平塘县,5227 522728,罗甸县,5227 522729,长顺县,5227 522730,龙里县,5227 522731,惠水县,5227 522732,三都水族自治县,5227 53,云南省, 5301,昆明市,53 530102,五华区,5301 530103,盘龙区,5301 530111,官渡区,5301 530112,西山区,5301 530113,东川区,5301 530114,呈贡区,5301 530115,晋宁区,5301 530124,富民县,5301 530125,宜良县,5301 530126,石林彝族自治县,5301 530127,嵩明县,5301 530128,禄劝彝族苗族自治县,5301 530129,寻甸回族彝族自治县,5301 530181,安宁市,5301 5303,曲靖市,53 530302,麒麟区,5303 530303,沾益区,5303 530304,马龙区,5303 530322,陆良县,5303 530323,师宗县,5303 530324,罗平县,5303 530325,富源县,5303 530326,会泽县,5303 530381,宣威市,5303 5304,玉溪市,53 530402,红塔区,5304 530403,江川区,5304 530422,澄江县,5304 530423,通海县,5304 530424,华宁县,5304 530425,易门县,5304 530426,峨山彝族自治县,5304 530427,新平彝族傣族自治县,5304 530428,元江哈尼族彝族傣族自治县,5304 5305,保山市,53 530502,隆阳区,5305 530521,施甸县,5305 530523,龙陵县,5305 530524,昌宁县,5305 530581,腾冲市,5305 5306,昭通市,53 530602,昭阳区,5306 530621,鲁甸县,5306 530622,巧家县,5306 530623,盐津县,5306 530624,大关县,5306 530625,永善县,5306 530626,绥江县,5306 530627,镇雄县,5306 530628,彝良县,5306 530629,威信县,5306 530681,水富市,5306 5307,丽江市,53 530702,古城区,5307 530721,玉龙纳西族自治县,5307 530722,永胜县,5307 530723,华坪县,5307 530724,宁蒗彝族自治县,5307 5308,普洱市,53 530802,思茅区,5308 530821,宁洱哈尼族彝族自治县,5308 530822,墨江哈尼族自治县,5308 530823,景东彝族自治县,5308 530824,景谷傣族彝族自治县,5308 530825,镇沅彝族哈尼族拉祜族自治县,5308 530826,江城哈尼族彝族自治县,5308 530827,孟连傣族拉祜族佤族自治县,5308 530828,澜沧拉祜族自治县,5308 530829,西盟佤族自治县,5308 5309,临沧市,53 530902,临翔区,5309 530921,凤庆县,5309 530922,云县,5309 530923,永德县,5309 530924,镇康县,5309 530925,双江拉祜族佤族布朗族傣族自治县,5309 530926,耿马傣族佤族自治县,5309 530927,沧源佤族自治县,5309 5323,楚雄彝族自治州,53 532301,楚雄市,5323 532322,双柏县,5323 532323,牟定县,5323 532324,南华县,5323 532325,姚安县,5323 532326,大姚县,5323 532327,永仁县,5323 532328,元谋县,5323 532329,武定县,5323 532331,禄丰县,5323 5325,红河哈尼族彝族自治州,53 532501,个旧市,5325 532502,开远市,5325 532503,蒙自市,5325 532504,弥勒市,5325 532523,屏边苗族自治县,5325 532524,建水县,5325 532525,石屏县,5325 532527,泸西县,5325 532528,元阳县,5325 532529,红河县,5325 532530,金平苗族瑶族傣族自治县,5325 532531,绿春县,5325 532532,河口瑶族自治县,5325 5326,文山壮族苗族自治州,53 532601,文山市,5326 532622,砚山县,5326 532623,西畴县,5326 532624,麻栗坡县,5326 532625,马关县,5326 532626,丘北县,5326 532627,广南县,5326 532628,富宁县,5326 5328,西双版纳傣族自治州,53 532801,景洪市,5328 532822,勐海县,5328 532823,勐腊县,5328 5329,大理白族自治州,53 532901,大理市,5329 532922,漾濞彝族自治县,5329 532923,祥云县,5329 532924,宾川县,5329 532925,弥渡县,5329 532926,南涧彝族自治县,5329 532927,巍山彝族回族自治县,5329 532928,永平县,5329 532929,云龙县,5329 532930,洱源县,5329 532931,剑川县,5329 532932,鹤庆县,5329 5331,德宏傣族景颇族自治州,53 533102,瑞丽市,5331 533103,芒市,5331 533122,梁河县,5331 533123,盈江县,5331 533124,陇川县,5331 5333,怒江傈僳族自治州,53 533301,泸水市,5333 533323,福贡县,5333 533324,贡山独龙族怒族自治县,5333 533325,兰坪白族普米族自治县,5333 5334,迪庆藏族自治州,53 533401,香格里拉市,5334 533422,德钦县,5334 533423,维西傈僳族自治县,5334 54,西藏自治区, 5401,拉萨市,54 540102,城关区,5401 540103,堆龙德庆区,5401 540104,达孜区,5401 540121,林周县,5401 540122,当雄县,5401 540123,尼木县,5401 540124,曲水县,5401 540127,墨竹工卡县,5401 5402,日喀则市,54 540202,桑珠孜区,5402 540221,南木林县,5402 540222,江孜县,5402 540223,定日县,5402 540224,萨迦县,5402 540225,拉孜县,5402 540226,昂仁县,5402 540227,谢通门县,5402 540228,白朗县,5402 540229,仁布县,5402 540230,康马县,5402 540231,定结县,5402 540232,仲巴县,5402 540233,亚东县,5402 540234,吉隆县,5402 540235,聂拉木县,5402 540236,萨嘎县,5402 540237,岗巴县,5402 5403,昌都市,54 540302,卡若区,5403 540321,江达县,5403 540322,贡觉县,5403 540323,类乌齐县,5403 540324,丁青县,5403 540325,察雅县,5403 540326,八宿县,5403 540327,左贡县,5403 540328,芒康县,5403 540329,洛隆县,5403 540330,边坝县,5403 5404,林芝市,54 540402,巴宜区,5404 540421,工布江达县,5404 540422,米林县,5404 540423,墨脱县,5404 540424,波密县,5404 540425,察隅县,5404 540426,朗县,5404 5405,山南市,54 540502,乃东区,5405 540521,扎囊县,5405 540522,贡嘎县,5405 540523,桑日县,5405 540524,琼结县,5405 540525,曲松县,5405 540526,措美县,5405 540527,洛扎县,5405 540528,加查县,5405 540529,隆子县,5405 540530,错那县,5405 540531,浪卡子县,5405 5406,那曲市,54 540602,色尼区,5406 540621,嘉黎县,5406 540622,比如县,5406 540623,聂荣县,5406 540624,安多县,5406 540625,申扎县,5406 540626,索县,5406 540627,班戈县,5406 540628,巴青县,5406 540629,尼玛县,5406 540630,双湖县,5406 5425,阿里地区,54 542521,普兰县,5425 542522,札达县,5425 542523,噶尔县,5425 542524,日土县,5425 542525,革吉县,5425 542526,改则县,5425 542527,措勤县,5425 61,陕西省, 6101,西安市,61 610102,新城区,6101 610103,碑林区,6101 610104,莲湖区,6101 610111,灞桥区,6101 610112,未央区,6101 610113,雁塔区,6101 610114,阎良区,6101 610115,临潼区,6101 610116,长安区,6101 610117,高陵区,6101 610118,鄠邑区,6101 610122,蓝田县,6101 610124,周至县,6101 6102,铜川市,61 610202,王益区,6102 610203,印台区,6102 610204,耀州区,6102 610222,宜君县,6102 6103,宝鸡市,61 610302,渭滨区,6103 610303,金台区,6103 610304,陈仓区,6103 610322,凤翔县,6103 610323,岐山县,6103 610324,扶风县,6103 610326,眉县,6103 610327,陇县,6103 610328,千阳县,6103 610329,麟游县,6103 610330,凤县,6103 610331,太白县,6103 6104,咸阳市,61 610402,秦都区,6104 610403,杨陵区,6104 610404,渭城区,6104 610422,三原县,6104 610423,泾阳县,6104 610424,乾县,6104 610425,礼泉县,6104 610426,永寿县,6104 610428,长武县,6104 610429,旬邑县,6104 610430,淳化县,6104 610431,武功县,6104 610481,兴平市,6104 610482,彬州市,6104 6105,渭南市,61 610502,临渭区,6105 610503,华州区,6105 610522,潼关县,6105 610523,大荔县,6105 610524,合阳县,6105 610525,澄城县,6105 610526,蒲城县,6105 610527,白水县,6105 610528,富平县,6105 610581,韩城市,6105 610582,华阴市,6105 6106,延安市,61 610602,宝塔区,6106 610603,安塞区,6106 610621,延长县,6106 610622,延川县,6106 610623,子长县,6106 610625,志丹县,6106 610626,吴起县,6106 610627,甘泉县,6106 610628,富县,6106 610629,洛川县,6106 610630,宜川县,6106 610631,黄龙县,6106 610632,黄陵县,6106 6107,汉中市,61 610702,汉台区,6107 610703,南郑区,6107 610722,城固县,6107 610723,洋县,6107 610724,西乡县,6107 610725,勉县,6107 610726,宁强县,6107 610727,略阳县,6107 610728,镇巴县,6107 610729,留坝县,6107 610730,佛坪县,6107 6108,榆林市,61 610802,榆阳区,6108 610803,横山区,6108 610822,府谷县,6108 610824,靖边县,6108 610825,定边县,6108 610826,绥德县,6108 610827,米脂县,6108 610828,佳县,6108 610829,吴堡县,6108 610830,清涧县,6108 610831,子洲县,6108 610881,神木市,6108 6109,安康市,61 610902,汉滨区,6109 610921,汉阴县,6109 610922,石泉县,6109 610923,宁陕县,6109 610924,紫阳县,6109 610925,岚皋县,6109 610926,平利县,6109 610927,镇坪县,6109 610928,旬阳县,6109 610929,白河县,6109 6110,商洛市,61 611002,商州区,6110 611021,洛南县,6110 611022,丹凤县,6110 611023,商南县,6110 611024,山阳县,6110 611025,镇安县,6110 611026,柞水县,6110 62,甘肃省, 6201,兰州市,62 620102,城关区,6201 620103,七里河区,6201 620104,西固区,6201 620105,安宁区,6201 620111,红古区,6201 620121,永登县,6201 620122,皋兰县,6201 620123,榆中县,6201 6202,嘉峪关市,62 6203,金昌市,62 620302,金川区,6203 620321,永昌县,6203 6204,白银市,62 620402,白银区,6204 620403,平川区,6204 620421,靖远县,6204 620422,会宁县,6204 620423,景泰县,6204 6205,天水市,62 620502,秦州区,6205 620503,麦积区,6205 620521,清水县,6205 620522,秦安县,6205 620523,甘谷县,6205 620524,武山县,6205 620525,张家川回族自治县,6205 6206,武威市,62 620602,凉州区,6206 620621,民勤县,6206 620622,古浪县,6206 620623,天祝藏族自治县,6206 6207,张掖市,62 620702,甘州区,6207 620721,肃南裕固族自治县,6207 620722,民乐县,6207 620723,临泽县,6207 620724,高台县,6207 620725,山丹县,6207 6208,平凉市,62 620802,崆峒区,6208 620821,泾川县,6208 620822,灵台县,6208 620823,崇信县,6208 620825,庄浪县,6208 620826,静宁县,6208 620881,华亭市,6208 6209,酒泉市,62 620902,肃州区,6209 620921,金塔县,6209 620922,瓜州县,6209 620923,肃北蒙古族自治县,6209 620924,阿克塞哈萨克族自治县,6209 620981,玉门市,6209 620982,敦煌市,6209 6210,庆阳市,62 621002,西峰区,6210 621021,庆城县,6210 621022,环县,6210 621023,华池县,6210 621024,合水县,6210 621025,正宁县,6210 621026,宁县,6210 621027,镇原县,6210 6211,定西市,62 621102,安定区,6211 621121,通渭县,6211 621122,陇西县,6211 621123,渭源县,6211 621124,临洮县,6211 621125,漳县,6211 621126,岷县,6211 6212,陇南市,62 621202,武都区,6212 621221,成县,6212 621222,文县,6212 621223,宕昌县,6212 621224,康县,6212 621225,西和县,6212 621226,礼县,6212 621227,徽县,6212 621228,两当县,6212 6229,临夏回族自治州,62 622901,临夏市,6229 622921,临夏县,6229 622922,康乐县,6229 622923,永靖县,6229 622924,广河县,6229 622925,和政县,6229 622926,东乡族自治县,6229 622927,积石山保安族东乡族撒拉族自治县,6229 6230,甘南藏族自治州,62 623001,合作市,6230 623021,临潭县,6230 623022,卓尼县,6230 623023,舟曲县,6230 623024,迭部县,6230 623025,玛曲县,6230 623026,碌曲县,6230 623027,夏河县,6230 63,青海省, 6301,西宁市,63 630102,城东区,6301 630103,城中区,6301 630104,城西区,6301 630105,城北区,6301 630121,大通回族土族自治县,6301 630122,湟中县,6301 630123,湟源县,6301 6302,海东市,63 630202,乐都区,6302 630203,平安区,6302 630222,民和回族土族自治县,6302 630223,互助土族自治县,6302 630224,化隆回族自治县,6302 630225,循化撒拉族自治县,6302 6322,海北藏族自治州,63 632221,门源回族自治县,6322 632222,祁连县,6322 632223,海晏县,6322 632224,刚察县,6322 6323,黄南藏族自治州,63 632321,同仁县,6323 632322,尖扎县,6323 632323,泽库县,6323 632324,河南蒙古族自治县,6323 6325,海南藏族自治州,63 632521,共和县,6325 632522,同德县,6325 632523,贵德县,6325 632524,兴海县,6325 632525,贵南县,6325 6326,果洛藏族自治州,63 632621,玛沁县,6326 632622,班玛县,6326 632623,甘德县,6326 632624,达日县,6326 632625,久治县,6326 632626,玛多县,6326 6327,玉树藏族自治州,63 632701,玉树市,6327 632722,杂多县,6327 632723,称多县,6327 632724,治多县,6327 632725,囊谦县,6327 632726,曲麻莱县,6327 6328,海西蒙古族藏族自治州,63 632801,格尔木市,6328 632802,德令哈市,6328 632803,茫崖市,6328 632821,乌兰县,6328 632822,都兰县,6328 632823,天峻县,6328 64,宁夏回族自治区, 6401,银川市,64 640104,兴庆区,6401 640105,西夏区,6401 640106,金凤区,6401 640121,永宁县,6401 640122,贺兰县,6401 640181,灵武市,6401 6402,石嘴山市,64 640202,大武口区,6402 640205,惠农区,6402 640221,平罗县,6402 6403,吴忠市,64 640302,利通区,6403 640303,红寺堡区,6403 640323,盐池县,6403 640324,同心县,6403 640381,青铜峡市,6403 6404,固原市,64 640402,原州区,6404 640422,西吉县,6404 640423,隆德县,6404 640424,泾源县,6404 640425,彭阳县,6404 6405,中卫市,64 640502,沙坡头区,6405 640521,中宁县,6405 640522,海原县,6405 65,新疆维吾尔自治区, 6501,乌鲁木齐市,65 650102,天山区,6501 650103,沙依巴克区,6501 650104,新市区,6501 650105,水磨沟区,6501 650106,头屯河区,6501 650107,达坂城区,6501 650109,米东区,6501 650121,乌鲁木齐县,6501 6502,克拉玛依市,65 650202,独山子区,6502 650203,克拉玛依区,6502 650204,白碱滩区,6502 650205,乌尔禾区,6502 6504,吐鲁番市,65 650402,高昌区,6504 650421,鄯善县,6504 650422,托克逊县,6504 6505,哈密市,65 650502,伊州区,6505 650521,巴里坤哈萨克自治县,6505 650522,伊吾县,6505 6523,昌吉回族自治州,65 652301,昌吉市,6523 652302,阜康市,6523 652323,呼图壁县,6523 652324,玛纳斯县,6523 652325,奇台县,6523 652327,吉木萨尔县,6523 652328,木垒哈萨克自治县,6523 6527,博尔塔拉蒙古自治州,65 652701,博乐市,6527 652702,阿拉山口市,6527 652722,精河县,6527 652723,温泉县,6527 6528,巴音郭楞蒙古自治州,65 652801,库尔勒市,6528 652822,轮台县,6528 652823,尉犁县,6528 652824,若羌县,6528 652825,且末县,6528 652826,焉耆回族自治县,6528 652827,和静县,6528 652828,和硕县,6528 652829,博湖县,6528 6529,阿克苏地区,65 652901,阿克苏市,6529 652922,温宿县,6529 652923,库车县,6529 652924,沙雅县,6529 652925,新和县,6529 652926,拜城县,6529 652927,乌什县,6529 652928,阿瓦提县,6529 652929,柯坪县,6529 6530,克孜勒苏柯尔克孜自治州,65 653001,阿图什市,6530 653022,阿克陶县,6530 653023,阿合奇县,6530 653024,乌恰县,6530 6531,喀什地区,65 653101,喀什市,6531 653121,疏附县,6531 653122,疏勒县,6531 653123,英吉沙县,6531 653124,泽普县,6531 653125,莎车县,6531 653126,叶城县,6531 653127,麦盖提县,6531 653128,岳普湖县,6531 653129,伽师县,6531 653130,巴楚县,6531 653131,塔什库尔干塔吉克自治县,6531 6532,和田地区,65 653201,和田市,6532 653221,和田县,6532 653222,墨玉县,6532 653223,皮山县,6532 653224,洛浦县,6532 653225,策勒县,6532 653226,于田县,6532 653227,民丰县,6532 6540,伊犁哈萨克自治州,65 654002,伊宁市,6540 654003,奎屯市,6540 654004,霍尔果斯市,6540 654021,伊宁县,6540 654022,察布查尔锡伯自治县,6540 654023,霍城县,6540 654024,巩留县,6540 654025,新源县,6540 654026,昭苏县,6540 654027,特克斯县,6540 654028,尼勒克县,6540 6542,塔城地区,65 654201,塔城市,6542 654202,乌苏市,6542 654221,额敏县,6542 654223,沙湾县,6542 654224,托里县,6542 654225,裕民县,6542 654226,和布克赛尔蒙古自治县,6542 6543,阿勒泰地区,65 654301,阿勒泰市,6543 654321,布尔津县,6543 654322,富蕴县,6543 654323,福海县,6543 654324,哈巴河县,6543 654325,青河县,6543 654326,吉木乃县,6543 659001,石河子市,65 659002,阿拉尔市,65 659003,图木舒克市,65 659004,五家渠市,65 659005,北屯市,65 659006,铁门关市,65 659007,双河市,65 659008,可克达拉市,65 659009,昆玉市,65 71,台湾省, 81,香港特别行政区, 82,澳门特别行政区, ================================================ FILE: src/main/resources/index.html ================================================ Title 111 ================================================ FILE: src/main/resources/install.sh ================================================ #! /bin/sh WORD_DIR=$(cd $(dirname $0); pwd) SERVICE_NAME="wvp" # 检查是否为 root 用户 if [ "$(id -u)" -ne 0 ]; then echo "提示: 建议使用 root 用户执行此脚本,否则可能权限不足!" read -p "继续?(y/n) " -n 1 -r if [[ ! $REPLY =~ ^[Yy]$ ]]; then exit 1 fi echo fi # 当前目录直接搜索(不含子目录) jar_files=(*.jar) if [ ${#jar_files[@]} -eq 0 ]; then echo "当前目录无 JAR 文件!" exit 1 fi # 遍历结果 for jar in "${jar_files[@]}"; do echo "找到 JAR 文件: $jar" done # 写文件 # 生成 Systemd 服务文件内容 SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service" cat << EOF | sudo tee "$SERVICE_FILE" > /dev/null [Unit] Description=${SERVICE_NAME} After=syslog.target [Service] User=$USER WorkingDirectory=${WORD_DIR} ExecStart=java -jar ${jar_files} SuccessExitStatus=143 Restart=on-failure RestartSec=10s Environment=SPRING_PROFILES_ACTIVE=prod [Install] WantedBy=multi-user.target EOF # 重载 Systemd 并启动服务 sudo systemctl daemon-reload sudo systemctl enable "$SERVICE_NAME" sudo systemctl start "$SERVICE_NAME" # 验证服务状态 echo "服务已安装!执行以下命令查看状态:" echo "sudo systemctl status $SERVICE_NAME" ================================================ FILE: src/main/resources/jwk.json ================================================ {"keys":[{"kty":"RSA","kid":"3e79646c4dbc408383a9eed09f2b85ae","n":"rThRAlbMRceko3NkymeSoN2ICVaDlNBLWv3cyLUeixjWcmuhnPv2JpXmgoxezKZfhH_0sChBof--BaaqSUukl9wWMW1bWCyFyU5qNczhQk3ANlhaLiSgXsqD-NKI3ObJjB-26fnOZb9QskCqrPW1lEtwgb9-skMAfGlh5kaDOKjYKI64DPSMMXpSiJEDM-7DK-TFfm0QfPcoH-k-1C02NHlGWehVUn9FUJ0TAiDxpKj28qOmYh7s1M7OU_h-Sso7LM-5zbftpcO6SINe81Gw9JPd7rKPCRxkw8ROSCCq-JH_zshM80kTK2nWcseGvhQ_4vKQIBp9PrAgCrGJHM160w","e":"AQAB","d":"AwS2NKo6iQS_k7GREg3X-kGh-zest00h4wYFcOHnFFlsczX47PlfArEeASxdAofrpi1soB0zd5UzRHnxAbH1vkexg076hoDQG__nzeQyEKu2K7xCZgdxW_V_cziH9gF3hZ-P2mfl9tPsng6OatElRt5BqaEingyY15ImiJK1-qi_LTx4gfwRfquKLbUgqJR4Tf6eKlwOzEo41Ilo26gnojNzWryB_XHG7lj6SngPDBJp7ty32je4Fv3A3hXt7JHDwloww6-xiRtUflDpSec4A-o-PHgbfoYLyM7mM4BDt4PM54EHm4u8WzypG0wNKDTiq4KSapei5xDbiG3RpngvAQ","p":"5kUHkGxnZvZT762Ex-0De2nYodAbbZNVR-eIPx2ng2VZmEbAU3cp_DxigpXWyQ0FwJ2Me8GvxnlbxJ7k7d-4AV2X8q6Q-UqXajHdudRU_QX05kPEgZ3xtPk5ekI0-u1BEQT7pY_gxlZC2mzXAcVLd-LwbVPuQEba5S4JMsjcHUE","q":"wJNa06-qZ2tWncGl7cfJdO-SJ_H3taowMhh-RsJmeVefjjN3pfVjjE0wG_rIP-BjjCB9OhvSnI8LDjoNu8uIg090DYnA6IUfZpWo3zjgedeyqQyXFVjjVQkn98zgp5NFLpuitZsl9-EHhh7JaZDCwaJ527MN3VCoQxeI75ggjxM","dp":"HQTH_kBbC5OxYjwIxrUswinFnia-viFaFvSrq-CN0rY8Az-vTxVuWhY2B-TgK3gTqIFyScpP34A9u1qW2Q9fffSQiInNRU1MJZrhKWED0NsmULprkjYYVsktoCWlzZWGpKFvIR8voW8Pf71FnziA2TvlNrHkDX-gaE9T422Cp8E","dq":"owJYqMWS1dYLTKBlx0ANbHl6W2u7xb_Y6h7HjTfzLBWazvEL_6QW7uVLqvN-XGuheDTsK6rvfWyr7BACHgvsc1JnJyqK64f8C4b1mnZ3tUt7RROONBi43ftRJLX9GHxV3F0LvvQkkI2gI8ydq0lJQkU5J1qKiuNCewBJ_p3kOZc","qi":"hNAZV6aWEEWfB1HkrfdtO6sjq9ceEod55ez82I1ZNgoKle8gpRkh3vw2EIJ_5lcw57s5rw8G-sCQPG1AQSZ6u9aURwHkIXjpIhLAlv6gvKkCh0smPPvnSiltJKOJsuHkrD6rGkV1f-MlCS51lKlk9xShQzkRidkNd4BUh0a7ktA"}]} ================================================ FILE: src/main/resources/logback-spring.xml ================================================ ${log.pattern} UTF-8 DEBUG ${log.pattern} UTF-8 ${LOG_HOME}/wvp-%d{yyyy-MM-dd}.%i.log 30 20MB %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}:%L - %msg%n UTF-8 DEBUG ${LOG_HOME}/sip-%d{yyyy-MM-dd}.%i.log 30 50MB %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}:%L - %msg%n UTF-8 ================================================ FILE: src/main/resources/配置详情.yml ================================================ # 此配置文件只是用作展示所有配置项, 不可直接使用 spring: cache: type: redis thymeleaf: cache: false # 设置接口超时时间 mvc: async: request-timeout: 20000 # [可选]上传文件大小限制 servlet: multipart: max-file-size: 10MB max-request-size: 100MB # REDIS数据库配置 data: redis: # [必须修改] Redis服务器IP, REDIS安装在本机的,使用127.0.0.1 host: 127.0.0.1 # [必须修改] 端口号 port: 6379 # [可选] 数据库 DB database: 6 # [可选] 访问密码,若你的redis服务器没有设置密码,就不需要用密码去连接 password: # [可选] 超时时间 timeout: 10000 # [可选] 一个pool最多可分配多少个jedis实例 poolMaxTotal: 1000 # [可选] 一个pool最多有多少个状态为idle(空闲)的jedis实例 poolMaxIdle: 500 # [可选] 最大的等待时间(秒) poolMaxWait: 5 # [必选] jdbc数据库配置 datasource: # kingbase配置 # type: com.zaxxer.hikari.HikariDataSource # driver-class-name: com.kingbase8.Driver # url: jdbc:kingbase8://192.168.1.55:54321/wvp?useUnicode=true&characterEncoding=utf8 # username: system # password: system # postgresql配置 # type: com.zaxxer.hikari.HikariDataSource # driver-class-name: org.postgresql.Driver # url: jdbc:postgresql://192.168.1.242:3306/242wvp # username: root # password: SYceshizu1234 # mysql配置 type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/wvp2?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true&serverTimezone=PRC&useSSL=false&allowMultiQueries=true username: root password: root123 hikari: connection-timeout: 20000 # 是客户端等待连接池连接的最大毫秒数 initialSize: 50 # 连接池初始化连接数 maximum-pool-size: 200 # 连接池最大连接数 minimum-idle: 10 # 连接池最小空闲连接数 idle-timeout: 300000 # 允许连接在连接池中空闲的最长时间(以毫秒为单位) max-lifetime: 1200000 # 是池中连接关闭后的最长生命周期(以毫秒为单位) # 修改分页插件为 postgresql, 数据库类型为mysql不需要 #pagehelper: # helper-dialect: postgresql # [可选] WVP监听的HTTP端口, 网页和接口调用都是这个端口 server: port: 18080 # [可选] HTTPS配置, 默认不开启 ssl: # [可选] 是否开启HTTPS访问 enabled: false # [可选] 证书文件路径,放置在resource/目录下即可,修改xxx为文件名 key-store: classpath:xxx.jks # [可选] 证书密码 key-store-password: password # [可选] 证书类型, 默认为jks,根据实际修改 key-store-type: JKS # 配置证书可以使用如下两项,如上面二选一即可 # PEM 编码证书 certificate: xx.pem # 私钥文件 certificate-private-key: xx.key # protocols: TLSv1.2 # ciphers: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 # 作为28181服务器的配置 sip: # [可选] 监听网卡IP,监听什么ip就是使用什么网卡,不配置则自动获取本机所有网卡,推荐不配置,系统自动获取 # 如果要监听多张网卡,可以使用逗号分隔多个IP, 例如: 192.168.1.4,10.0.0.4 # 如果不明白,就使用0.0.0.0,大部分情况都是可以的 # 请不要使用127.0.0.1,任何包括localhost在内的域名都是不可以的。 ip: 0.0.0.0 # [可选] 没有任何业务需求,仅仅是在前端展示的时候用 show-ip: 192.168.0.100 # [可选] 28181服务监听的端口 port: 5060 # 根据国标6.1.2中规定,domain宜采用ID统一编码的前十位编码。国标附录D中定义前8位为中心编码(由省级、市级、区级、基层编号组成,参照GB/T 2260-2007) # 后两位为行业编码,定义参照附录D.3 # 3701020049标识山东济南历下区 信息行业接入 # [可选] domain: 4401020049 # [可选] id: 44010200492000000001 # [可选] 公共认证密码 移除密码将必须提前添加设备才能通过认证 password: admin123 # [可选] 国标级联注册失败,再次发起注册的时间间隔。 默认60秒 register-time-interval: 60 # [可选] 云台控制速度 ptz-speed: 50 # TODO [可选] 收到心跳后自动上线, 重启服务后会将所有设备置为离线,默认false,等待注册后上线。设置为true则收到心跳设置为上线。 # keepalliveToOnline: false # 是否存储alarm信息 alarm: false # 命令发送等待回复的超时时间, 单位:毫秒 timeout: 1000 # 做为JT1078服务器的配置 ftp: #[必须修改] 是否开启1078的服务 enable: true port: 2121 passive-ports: 10000-10500 # 做为JT1078服务器的配置 jt1078: #[必须修改] 是否开启1078的服务 enable: true #[必修修改] 1708设备接入的端口 port: 21078 #[可选] 设备鉴权的密码 password: admin123 sy: enable: false # 云台控制开始到结束的时间间隔(毫秒) ptz-control-time-interval: 300 #zlm 默认服务器配置 media: # [必须修改] zlm服务器唯一id,用于触发hook时区别是哪台服务器,general.mediaServerId id: # [必须修改] zlm服务器的内网IP ip: 192.168.0.100 # [可选] 有公网IP就配置公网IP, 不可用域名 wan_ip: # [可选] 返回流地址时的ip,置空使用 media.ip stream-ip: # [可选] wvp在国标信令中使用的ip,此ip为摄像机可以访问到的ip, 置空使用 media.ip sdp-ip: # [可选] zlm服务器访问WVP所使用的IP, 默认使用127.0.0.1,zlm和wvp没有部署在同一台服务器时必须配置 hook-ip: 172.19.128.50 # [必须修改] zlm服务器的http.port http-port: 80 # [可选] 是否自动配置ZLM, 如果希望手动配置ZLM, 可以设为false, 不建议新接触的用户修改 auto-config: true # [可选] zlm服务器的hook.admin_params=secret secret: 035c73f7-bb6b-4889-a715-d9eb2d1925cc # 录像路径 record-path: ./www/record # 录像保存时长 record-day: 7 # 启用多端口模式, 多端口模式使用端口区分每路流,兼容性更好。 单端口使用流的ssrc区分, 点播超时建议使用多端口测试 rtp: # [可选] 是否启用多端口模式, 开启后会在portRange范围内选择端口用于媒体流传输 enable: true # [可选] 在此范围内选择端口用于媒体流传输, 必须提前在zlm上配置该属性,不然自动配置此属性可能不成功 port-range: 30000,30500 # 端口范围 # [可选] 国标级联在此范围内选择端口发送媒体流,请不要与收流端口范围重合 send-port-range: 50502,50506 # 端口范围 # 录像辅助服务, 部署此服务可以实现zlm录像的管理与下载, 0 表示不使用 record-assist-port: 0 # [可选] 日志配置, 如果不需要在jar外修改日志内容那么可以不配置此项 logging: config: classpath:logback-spring.xml # [根据业务需求配置] user-settings: # 服务ID,不写则为000000 server-id: # [可选] 自动点播, 使用固定流地址进行播放时,如果未点播则自动进行点播, 需要rtp.enable=true auto-apply-play: false # [可选] 部分设备需要扩展SDP,需要打开此设置 senior-sdp: false # 保存移动位置历史轨迹:true:保留历史数据,false:仅保留最后的位置(默认) save-position-history: false # 点播/录像回放 等待超时时间,单位:毫秒 play-timeout: 18000 # 获取设备录像数据超时时间,单位:毫秒 record-info-timeout: 10000 # 上级点播等待超时时间,单位:毫秒 platform-play-timeout: 60000 # 是否开启接口鉴权 interface-authentication: true # 接口鉴权例外的接口, 即不进行接口鉴权的接口,尽量详细书写,尽量不用/**,至少两级目录 interface-authentication-excludes: - /api/v1/** # 推流直播是否录制 record-push-live: true # 国标是否录制 record-sip: true # 使用推流状态作为推流通道状态 use-pushing-as-status: true # 使用来源请求ip作为streamIp,当且仅当你只有zlm节点它与wvp在一起的情况下开启 use-source-ip-as-stream-ip: false # 国标点播 按需拉流, true:有人观看拉流,无人观看释放, false:拉起后不自动释放 stream-on-demand: true # 推流鉴权, 默认开启 push-authority: true # 设备上线时是否自动同步通道 sync-channel-on-device-online: false # 国标级联语音喊话发流模式 * UDP:udp传输 TCP-ACTIVE:tcp主动模式 TCP-PASSIVE:tcp被动模式 broadcast-for-platform: UDP # 是否使用设备来源Ip作为回复IP, 不设置则为 false sip-use-source-ip-as-remote-address: false # 是否开启sip日志 sip-log: true # 是否开启sql日志 sql-log: true # 消息通道功能-缺少国标ID是否给所有上级发送消息 send-to-platforms-when-id-lost: true # 保持通道状态,不接受notify通道状态变化, 兼容海康平台发送错误消息 refuse-channel-status-channel-form-notify: false # 设置notify缓存队列最大长度,超过此长度的数据将返回486 BUSY_HERE,消息丢弃, 默认10000 max-notify-count-queue: 10000 # 设备/通道状态变化时发送消息 device-status-notify: false # 上级平台点播时不使用上级平台指定的ssrc,使用自定义的ssrc,参考国标文档-点播外域设备媒体流SSRC处理方式 use-custom-ssrc-for-parent-invite: true # 国标级联离线后多久重试一次注册 register-again-after-time: 60 # 国标续订方式,true为续订,每次注册在同一个会话里,false为重新注册,每次使用新的会话 register-keep-int-dialog: false # 开启接口文档页面。 默认开启,生产环境建议关闭,遇到swagger相关的漏洞时也可以关闭 doc-enable: true # 跨域配置,不配置此项则允许所有跨域请求,配置后则只允许配置的页面的地址请求, 可以配置多个 allowed-origins: - http://localhost:8008 - http://192.168.1.3:8008 # 国标设备离线后的上线策略, # 0: 国标标准实现,设备离线后不回复心跳,直到设备重新注册上线, # 1(默认): 对于离线设备,收到心跳就把设备设置为上线,并更新注册时间为上次这次心跳的时间。防止过期时间判断异常 gb-device-online: 0 # 登录超时时间(分钟), login-timeout: 30 # jwk文件路径,若不指定则使用resources目录下的jwk.json jwk-file: classpath:jwk.json # wvp集群模式下如果注册向上级的wvp奔溃,则自动选择一个其他wvp继续注册到上级 auto-register-platform: true # 按需发送位置, 默认发送移动位置订阅时如果位置不变则不发送, 设置为false按照国标间隔持续发送 send-position-on-demand: true # 部分设备会在短时间内发送大量注册, 导致协议栈内存溢出, 开启此项可以防止这部分设备注册, 避免服务崩溃,但是会降低系统性能, 描述如下 # 默认值为 true。 # 将此设置为 false 会使 Stack 在 Server Transaction 进入 TERMINATED 状态后关闭服务器套接字。 # 这允许服务器防止客户端发起的基于 TCP 的拒绝服务攻击(即发起数百个客户端事务)。 # 如果为 true(默认作),则堆栈将保持套接字打开,以便以牺牲线程和内存资源为代价来最大化性能 - 使自身容易受到 DOS 攻击。 sip-cache-server-connections: true # 关闭在线文档(生产环境建议关闭) springdoc: api-docs: enabled: false swagger-ui: enabled: false ================================================ FILE: src/test/java/com/genersoft/iot/vmp/jt1078/JT1078ServerTest.java ================================================ package com.genersoft.iot.vmp.jt1078; import com.genersoft.iot.vmp.jt1078.cmd.JT1078Template; import com.genersoft.iot.vmp.jt1078.codec.netty.TcpServer; import com.genersoft.iot.vmp.jt1078.proc.response.J9102; import com.genersoft.iot.vmp.jt1078.proc.response.J9201; import com.genersoft.iot.vmp.jt1078.proc.response.J9202; import com.genersoft.iot.vmp.jt1078.proc.response.J9205; import java.util.Scanner; /** * @author QingtaiJiang * @date 2023/4/28 14:22 * @email qingtaij@163.com */ public class JT1078ServerTest { private static final JT1078Template jt1078Template = new JT1078Template(); public static void main(String[] args) { System.out.println("Starting jt1078 server..."); TcpServer tcpServer = new TcpServer(21078, null, null); tcpServer.start(); System.out.println("Start jt1078 server success!"); Scanner s = new Scanner(System.in); while (true) { String code = s.nextLine(); switch (code) { case "1": test9102(); break; case "2": test9201(); break; case "3": test9202(); break; case "4": test9205(); break; default: break; } } } private static void test9102() { J9102 j9102 = new J9102(); j9102.setChannel(1); j9102.setCommand(0); j9102.setCloseType(0); j9102.setStreamType(0); Object s = jt1078Template.stopLive("18864197066", j9102, 6); System.out.println(s); } private static void test9201() { J9201 j9201 = new J9201(); j9201.setIp("192.168.1.1"); j9201.setChannel(1); j9201.setTcpPort(7618); j9201.setUdpPort(7618); j9201.setType(0); j9201.setRate(0); j9201.setStorageType(0); j9201.setPlaybackType(0); j9201.setPlaybackSpeed(0); j9201.setStartTime("230428134100"); j9201.setEndTime("230428134200"); Object s = jt1078Template.startBackLive("18864197066", j9201, 6); System.out.println(s); } private static void test9202() { J9202 j9202 = new J9202(); j9202.setChannel(1); j9202.setPlaybackType(2); j9202.setPlaybackSpeed(0); j9202.setPlaybackTime("230428134100"); Object s = jt1078Template.controlBackLive("18864197066", j9202, 6); System.out.println(s); } private static void test9205() { J9205 j9205 = new J9205(); j9205.setChannelId(1); j9205.setStartTime("230428134100"); j9205.setEndTime("230428134100"); j9205.setMediaType(0); j9205.setStreamType(0); j9205.setStorageType(0); Object s = jt1078Template.queryBackTime("18864197066", j9205, 6); System.out.println(s); } } ================================================ FILE: web/.editorconfig ================================================ # http://editorconfig.org root = true [*] charset = utf-8 indent_style = space indent_size = 2 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true [*.md] insert_final_newline = false trim_trailing_whitespace = false ================================================ FILE: web/.eslintignore ================================================ build/*.js src/assets public dist ================================================ FILE: web/.eslintrc.js ================================================ module.exports = { root: true, env: { node: true, browser: true, }, extends: ["plugin:vue/essential", "eslint:recommended"], parserOptions: { parser: "babel-eslint", }, rules: { // Disable or downgrade problematic rules "vue/require-prop-types": "off", "vue/require-default-prop": "off", "vue/no-unused-vars": "warn", "no-unused-vars": "warn", "no-undef": "warn", eqeqeq: "warn", "no-return-assign": "warn", "new-cap": "warn", "vue/html-self-closing": "off", "vue/html-closing-bracket-spacing": "off", "vue/this-in-template": "off", "vue/require-v-for-key": "warn", "vue/valid-v-model": "warn", "vue/attributes-order": "off", "no-multiple-empty-lines": "warn", // Style rules - make them warnings instead of errors quotes: ["warn", "single"], "comma-dangle": ["warn", "never"], "space-in-parens": "warn", "comma-spacing": "warn", "object-curly-spacing": ["warn", "always"], "arrow-spacing": "warn", semi: ["warn", "never"], "no-multi-spaces": "warn", // Turn off console warnings for development "no-console": process.env.NODE_ENV === "production" ? "warn" : "off", "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off", }, globals: { // Define global variables to prevent 'undefined' errors ZLMRTCClient: "readonly", jessibuca: "readonly", }, } ================================================ FILE: web/.gitignore ================================================ .DS_Store node_modules/ dist/ npm-debug.log* yarn-debug.log* yarn-error.log* package-lock.json tests/**/coverage/ # Editor directories and files .idea .vscode *.suo *.ntvs* *.njsproj *.sln ================================================ FILE: web/.travis.yml ================================================ language: node_js node_js: 10 script: npm run test notifications: email: false ================================================ FILE: web/LICENSE ================================================ MIT License Copyright (c) 2017-present PanJiaChen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: web/README-zh.md ================================================ # vue-admin-template > 这是一个极简的 vue admin 管理后台。它只包含了 Element UI & axios & iconfont & permission control & lint,这些搭建后台必要的东西。 [线上地址](http://panjiachen.github.io/vue-admin-template) [国内访问](https://panjiachen.gitee.io/vue-admin-template) 目前版本为 `v4.0+` 基于 `vue-cli` 进行构建,若你想使用旧版本,可以切换分支到[tag/3.11.0](https://github.com/PanJiaChen/vue-admin-template/tree/tag/3.11.0),它不依赖 `vue-cli`。

    SPONSORED BY

    ## Extra 如果你想要根据用户角色来动态生成侧边栏和 router,你可以使用该分支[permission-control](https://github.com/PanJiaChen/vue-admin-template/tree/permission-control) ## 相关项目 - [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin) - [electron-vue-admin](https://github.com/PanJiaChen/electron-vue-admin) - [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template) - [awesome-project](https://github.com/PanJiaChen/vue-element-admin/issues/2312) 写了一个系列的教程配套文章,如何从零构建后一个完整的后台项目: - [手摸手,带你用 vue 撸后台 系列一(基础篇)](https://juejin.im/post/59097cd7a22b9d0065fb61d2) - [手摸手,带你用 vue 撸后台 系列二(登录权限篇)](https://juejin.im/post/591aa14f570c35006961acac) - [手摸手,带你用 vue 撸后台 系列三 (实战篇)](https://juejin.im/post/593121aa0ce4630057f70d35) - [手摸手,带你用 vue 撸后台 系列四(vueAdmin 一个极简的后台基础模板,专门针对本项目的文章,算作是一篇文档)](https://juejin.im/post/595b4d776fb9a06bbe7dba56) - [手摸手,带你封装一个 vue component](https://segmentfault.com/a/1190000009090836) ## Build Setup ```bash # 克隆项目 git clone https://github.com/PanJiaChen/vue-admin-template.git # 进入项目目录 cd vue-admin-template # 安装依赖 npm install # 建议不要直接使用 cnpm 安装以来,会有各种诡异的 bug。可以通过如下操作解决 npm 下载速度慢的问题 npm install --registry=https://registry.npm.taobao.org # 启动服务 npm run dev ``` 浏览器访问 [http://localhost:9528](http://localhost:9528) ## 发布 ```bash # 构建测试环境 npm run build:stage # 构建生产环境 npm run build:prod ``` ## 其它 ```bash # 预览发布环境效果 npm run preview # 预览发布环境效果 + 静态资源分析 npm run preview -- --report # 代码格式检查 npm run lint # 代码格式检查并自动修复 npm run lint -- --fix ``` 更多信息请参考 [使用文档](https://panjiachen.github.io/vue-element-admin-site/zh/) ## 购买贴纸 你也可以通过 购买[官方授权的贴纸](https://smallsticker.com/product/vue-element-admin) 的方式来支持 vue-element-admin - 每售出一张贴纸,我们将获得 2 元的捐赠。 ## Demo ![demo](https://github.com/PanJiaChen/PanJiaChen.github.io/blob/master/images/demo.gif) ## Browsers support Modern browsers and Internet Explorer 10+. | [IE / Edge](http://godban.github.io/browsers-support-badges/)
    IE / Edge | [Firefox](http://godban.github.io/browsers-support-badges/)
    Firefox | [Chrome](http://godban.github.io/browsers-support-badges/)
    Chrome | [Safari](http://godban.github.io/browsers-support-badges/)
    Safari | | --------- | --------- | --------- | --------- | | IE10, IE11, Edge| last 2 versions| last 2 versions| last 2 versions ## License [MIT](https://github.com/PanJiaChen/vue-admin-template/blob/master/LICENSE) license. Copyright (c) 2017-present PanJiaChen ================================================ FILE: web/README.md ================================================ # vue-admin-template English | [简体中文](./README-zh.md) > A minimal vue admin template with Element UI & axios & iconfont & permission control & lint **Live demo:** http://panjiachen.github.io/vue-admin-template **The current version is `v4.0+` build on `vue-cli`. If you want to use the old version , you can switch branch to [tag/3.11.0](https://github.com/PanJiaChen/vue-admin-template/tree/tag/3.11.0), it does not rely on `vue-cli`**

    SPONSORED BY

    ## Build Setup ```bash # clone the project git clone https://github.com/PanJiaChen/vue-admin-template.git # enter the project directory cd vue-admin-template # install dependency npm install # develop npm run dev ``` This will automatically open http://localhost:9528 ## Build ```bash # build for test environment npm run build:stage # build for production environment npm run build:prod ``` ## Advanced ```bash # preview the release environment effect npm run preview # preview the release environment effect + static resource analysis npm run preview -- --report # code format check npm run lint # code format check and auto fix npm run lint -- --fix ``` Refer to [Documentation](https://panjiachen.github.io/vue-element-admin-site/guide/essentials/deploy.html) for more information ## Demo ![demo](https://github.com/PanJiaChen/PanJiaChen.github.io/blob/master/images/demo.gif) ## Extra If you want router permission && generate menu by user roles , you can use this branch [permission-control](https://github.com/PanJiaChen/vue-admin-template/tree/permission-control) For `typescript` version, you can use [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template) (Credits: [@Armour](https://github.com/Armour)) ## Related Project - [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin) - [electron-vue-admin](https://github.com/PanJiaChen/electron-vue-admin) - [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template) - [awesome-project](https://github.com/PanJiaChen/vue-element-admin/issues/2312) ## Browsers support Modern browsers and Internet Explorer 10+. | [IE / Edge](http://godban.github.io/browsers-support-badges/)
    IE / Edge | [Firefox](http://godban.github.io/browsers-support-badges/)
    Firefox | [Chrome](http://godban.github.io/browsers-support-badges/)
    Chrome | [Safari](http://godban.github.io/browsers-support-badges/)
    Safari | | --------- | --------- | --------- | --------- | | IE10, IE11, Edge| last 2 versions| last 2 versions| last 2 versions ## License [MIT](https://github.com/PanJiaChen/vue-admin-template/blob/master/LICENSE) license. Copyright (c) 2017-present PanJiaChen ================================================ FILE: web/babel.config.js ================================================ module.exports = { presets: [ // https://github.com/vuejs/vue-cli/tree/master/packages/@vue/babel-preset-app '@vue/cli-plugin-babel/preset' ], 'env': { 'development': { // babel-plugin-dynamic-import-node plugin only does one thing by converting all import() to require(). // This plugin can significantly increase the speed of hot updates, when you have a large number of pages. // https://panjiachen.github.io/vue-element-admin-site/guide/advanced/lazy-loading.html 'plugins': ['dynamic-import-node'] } } } ================================================ FILE: web/build/index.js ================================================ const { run } = require('runjs') const chalk = require('chalk') const config = require('../vue.config.js') const rawArgv = process.argv.slice(2) const args = rawArgv.join(' ') if (process.env.npm_config_preview || rawArgv.includes('--preview')) { const report = rawArgv.includes('--report') run(`vue-cli-service build ${args}`) const port = 9526 const publicPath = config.publicPath var connect = require('connect') var serveStatic = require('serve-static') const app = connect() app.use( publicPath, serveStatic('./src/main/resources/static', { index: ['index.html', '/'] }) ) app.listen(port, function () { console.log(chalk.green(`> Preview at http://localhost:${port}${publicPath}`)) if (report) { console.log(chalk.green(`> Report at http://localhost:${port}${publicPath}report.html`)) } }) } else { run(`vue-cli-service build ${args}`) } ================================================ FILE: web/jest.config.js ================================================ module.exports = { moduleFileExtensions: ['js', 'jsx', 'json', 'vue'], transform: { '^.+\\.vue$': 'vue-jest', '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub', '^.+\\.jsx?$': 'babel-jest' }, moduleNameMapper: { '^@/(.*)$': '/src/$1' }, snapshotSerializers: ['jest-serializer-vue'], testMatch: [ '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)' ], collectCoverageFrom: ['src/utils/**/*.{js,vue}', '!src/utils/auth.js', '!src/utils/request.js', 'src/components/**/*.{js,vue}'], coverageDirectory: '/tests/unit/coverage', // 'collectCoverage': true, 'coverageReporters': [ 'lcov', 'text-summary' ], testURL: 'http://localhost/' } ================================================ FILE: web/jsconfig.json ================================================ { "compilerOptions": { "baseUrl": "./", "paths": { "@/*": ["src/*"] } }, "exclude": ["node_modules", "dist"] } ================================================ FILE: web/mock/index.js ================================================ const Mock = require('mockjs') const { param2Obj } = require('./utils') const user = require('./user') const table = require('./table') const mocks = [ ...user, ...table ] // for front mock // please use it cautiously, it will redefine XMLHttpRequest, // which will cause many of your third-party libraries to be invalidated(like progress event). function mockXHR() { // mock patch // https://github.com/nuysoft/Mock/issues/300 Mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send Mock.XHR.prototype.send = function() { if (this.custom.xhr) { this.custom.xhr.withCredentials = this.withCredentials || false if (this.responseType) { this.custom.xhr.responseType = this.responseType } } this.proxy_send(...arguments) } function XHR2ExpressReqWrap(respond) { return function(options) { let result = null if (respond instanceof Function) { const { body, type, url } = options // https://expressjs.com/en/4x/api.html#req result = respond({ method: type, body: JSON.parse(body), query: param2Obj(url) }) } else { result = respond } return Mock.mock(result) } } for (const i of mocks) { Mock.mock(new RegExp(i.url), i.type || 'get', XHR2ExpressReqWrap(i.response)) } } module.exports = { mocks, mockXHR } ================================================ FILE: web/mock/mock-server.js ================================================ const chokidar = require('chokidar') const bodyParser = require('body-parser') const chalk = require('chalk') const path = require('path') const Mock = require('mockjs') const mockDir = path.join(process.cwd(), 'mock') function registerRoutes(app) { let mockLastIndex const { mocks } = require('./index.js') const mocksForServer = mocks.map(route => { return responseFake(route.url, route.type, route.response) }) for (const mock of mocksForServer) { app[mock.type](mock.url, mock.response) mockLastIndex = app._router.stack.length } const mockRoutesLength = Object.keys(mocksForServer).length return { mockRoutesLength: mockRoutesLength, mockStartIndex: mockLastIndex - mockRoutesLength } } function unregisterRoutes() { Object.keys(require.cache).forEach(i => { if (i.includes(mockDir)) { delete require.cache[require.resolve(i)] } }) } // for mock server const responseFake = (url, type, respond) => { return { url: new RegExp(`${process.env.VUE_APP_BASE_API}${url}`), type: type || 'get', response(req, res) { console.log('request invoke:' + req.path) res.json(Mock.mock(respond instanceof Function ? respond(req, res) : respond)) } } } module.exports = app => { // parse app.body // https://expressjs.com/en/4x/api.html#req.body app.use(bodyParser.json()) app.use(bodyParser.urlencoded({ extended: true })) const mockRoutes = registerRoutes(app) var mockRoutesLength = mockRoutes.mockRoutesLength var mockStartIndex = mockRoutes.mockStartIndex // watch files, hot reload mock server chokidar.watch(mockDir, { ignored: /mock-server/, ignoreInitial: true }).on('all', (event, path) => { if (event === 'change' || event === 'add') { try { // remove mock routes stack app._router.stack.splice(mockStartIndex, mockRoutesLength) // clear routes cache unregisterRoutes() const mockRoutes = registerRoutes(app) mockRoutesLength = mockRoutes.mockRoutesLength mockStartIndex = mockRoutes.mockStartIndex console.log(chalk.magentaBright(`\n > Mock Server hot reload success! changed ${path}`)) } catch (error) { console.log(chalk.redBright(error)) } } }) } ================================================ FILE: web/mock/table.js ================================================ const Mock = require('mockjs') const data = Mock.mock({ 'items|30': [{ id: '@id', title: '@sentence(10, 20)', 'status|1': ['published', 'draft', 'deleted'], author: 'name', display_time: '@datetime', pageviews: '@integer(300, 5000)' }] }) module.exports = [ { url: '/vue-admin-template/table/list', type: 'get', response: config => { const items = data.items return { code: 20000, data: { total: items.length, items: items } } } } ] ================================================ FILE: web/mock/user.js ================================================ const tokens = { admin: { token: 'admin-token' }, editor: { token: 'editor-token' } } const users = { 'admin-token': { roles: ['admin'], introduction: 'I am a super administrator', avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif', name: 'Super Admin' }, 'editor-token': { roles: ['editor'], introduction: 'I am an editor', avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif', name: 'Normal Editor' } } module.exports = [ // user login { url: '/vue-admin-template/user/login', type: 'post', response: config => { const { username } = config.body const token = tokens[username] // mock error if (!token) { return { code: 60204, message: 'Account and password are incorrect.' } } return { code: 20000, data: token } } }, // get user info { url: '/vue-admin-template/user/info\.*', type: 'get', response: config => { const { token } = config.query const info = users[token] // mock error if (!info) { return { code: 50008, message: 'Login failed, unable to get user details.' } } return { code: 20000, data: info } } }, // user logout { url: '/vue-admin-template/user/logout', type: 'post', response: _ => { return { code: 20000, data: 'success' } } } ] ================================================ FILE: web/mock/utils.js ================================================ /** * @param {string} url * @returns {Object} */ function param2Obj(url) { const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ') if (!search) { return {} } const obj = {} const searchArr = search.split('&') searchArr.forEach(v => { const index = v.indexOf('=') if (index !== -1) { const name = v.substring(0, index) const val = v.substring(index + 1, v.length) obj[name] = val } }) return obj } module.exports = { param2Obj } ================================================ FILE: web/package.json ================================================ { "name": "vue-admin-template", "version": "4.4.0", "description": "A vue admin template with Element UI & axios & iconfont & permission control & lint", "author": "Pan ", "scripts": { "dev": "vue-cli-service serve --host=0.0.0.0", "build:prod": "vue-cli-service build", "build:stage": "vue-cli-service build --mode staging", "preview": "node build/index.js --preview", "svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml", "lint": "eslint --ext .js,.vue src", "test:unit": "jest --clearCache && vue-cli-service test:unit", "test:ci": "npm run lint && npm run test:unit" }, "dependencies": { "@femessage/log-viewer": "^1.5.0", "@wchbrad/vue-easy-tree": "^1.0.13", "axios": "^0.24.0", "core-js": "3.6.5", "dayjs": "^1.11.13", "echarts": "^4.9.0", "element-ui": "^2.15.14", "gcoord": "^1.0.7", "js-cookie": "2.2.0", "moment": "^2.29.1", "moment-duration-format": "^2.3.2", "normalize.css": "7.0.0", "nprogress": "0.2.0", "ol": "^10.6.1", "path-to-regexp": "2.4.0", "screenfull": "5.1.0", "strip-ansi": "^7.1.0", "v-charts": "^1.19.0", "vue": "2.6.10", "vue-clipboard2": "^0.3.3", "vue-clipboards": "^1.3.0", "vue-contextmenujs": "^1.4.11", "vue-router": "3.0.6", "vue-ztree-2.0": "^1.0.4", "vuex": "3.1.0", "xlsx": "^0.18.5" }, "devDependencies": { "@vue/cli-plugin-babel": "4.4.4", "@vue/cli-plugin-eslint": "4.4.4", "@vue/cli-plugin-unit-jest": "4.4.4", "@vue/cli-service": "4.4.4", "@vue/test-utils": "1.0.0-beta.29", "autoprefixer": "9.5.1", "babel-eslint": "10.1.0", "babel-jest": "23.6.0", "babel-plugin-dynamic-import-node": "2.3.3", "chalk": "2.4.2", "connect": "3.6.6", "eslint": "6.7.2", "eslint-plugin-vue": "6.2.2", "html-webpack-plugin": "3.2.0", "mockjs": "1.0.1-beta3", "runjs": "4.3.2", "sass": "1.26.8", "sass-loader": "8.0.2", "script-ext-html-webpack-plugin": "2.1.3", "serve-static": "1.13.2", "svg-sprite-loader": "4.1.3", "svgo": "1.2.2", "vue-template-compiler": "2.6.10" }, "browserslist": [ "> 1%", "last 2 versions" ], "engines": { "node": ">=8.9", "npm": ">= 3.0.0" }, "license": "MIT" } ================================================ FILE: web/postcss.config.js ================================================ // https://github.com/michael-ciniawsky/postcss-load-config module.exports = { 'plugins': { // to edit target browsers: use "browserslist" field in package.json 'autoprefixer': {} } } ================================================ FILE: web/public/index.html ================================================ <%= webpackConfig.name %>
    ================================================ FILE: web/public/static/js/ZLMRTCClient.js ================================================ var ZLMRTCClient = (function (exports) { 'use strict'; const Events$1 = { WEBRTC_NOT_SUPPORT: 'WEBRTC_NOT_SUPPORT', WEBRTC_ICE_CANDIDATE_ERROR: 'WEBRTC_ICE_CANDIDATE_ERROR', WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED: 'WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED', WEBRTC_ON_REMOTE_STREAMS: 'WEBRTC_ON_REMOTE_STREAMS', WEBRTC_ON_LOCAL_STREAM: 'WEBRTC_ON_LOCAL_STREAM', WEBRTC_ON_CONNECTION_STATE_CHANGE: 'WEBRTC_ON_CONNECTION_STATE_CHANGE', WEBRTC_ON_DATA_CHANNEL_OPEN: 'WEBRTC_ON_DATA_CHANNEL_OPEN', WEBRTC_ON_DATA_CHANNEL_CLOSE: 'WEBRTC_ON_DATA_CHANNEL_CLOSE', WEBRTC_ON_DATA_CHANNEL_ERR: 'WEBRTC_ON_DATA_CHANNEL_ERR', WEBRTC_ON_DATA_CHANNEL_MSG: 'WEBRTC_ON_DATA_CHANNEL_MSG', CAPTURE_STREAM_FAILED: 'CAPTURE_STREAM_FAILED' }; const VERSION$1 = '1.0.1'; const BUILD_DATE = 'Mon Mar 27 2023 19:11:59 GMT+0800 (China Standard Time)'; // Copyright (C) <2018> Intel Corporation // // SPDX-License-Identifier: Apache-2.0 // eslint-disable-next-line require-jsdoc function isFirefox() { return window.navigator.userAgent.match('Firefox') !== null; } // eslint-disable-next-line require-jsdoc function isChrome() { return window.navigator.userAgent.match('Chrome') !== null; } // eslint-disable-next-line require-jsdoc function isEdge() { return window.navigator.userAgent.match(/Edge\/(\d+).(\d+)$/) !== null; } // Copyright (C) <2018> Intel Corporation /** * @class AudioSourceInfo * @classDesc Source info about an audio track. Values: 'mic', 'screen-cast', 'file', 'mixed'. * @memberOf Owt.Base * @readonly * @enum {string} */ const AudioSourceInfo = { MIC: 'mic', SCREENCAST: 'screen-cast', FILE: 'file', MIXED: 'mixed' }; /** * @class VideoSourceInfo * @classDesc Source info about a video track. Values: 'camera', 'screen-cast', 'file', 'mixed'. * @memberOf Owt.Base * @readonly * @enum {string} */ const VideoSourceInfo = { CAMERA: 'camera', SCREENCAST: 'screen-cast', FILE: 'file', MIXED: 'mixed' }; /** * @class TrackKind * @classDesc Kind of a track. Values: 'audio' for audio track, 'video' for video track, 'av' for both audio and video tracks. * @memberOf Owt.Base * @readonly * @enum {string} */ const TrackKind = { /** * Audio tracks. * @type string */ AUDIO: 'audio', /** * Video tracks. * @type string */ VIDEO: 'video', /** * Both audio and video tracks. * @type string */ AUDIO_AND_VIDEO: 'av' }; /** * @class Resolution * @memberOf Owt.Base * @classDesc The Resolution defines the size of a rectangle. * @constructor * @param {number} width * @param {number} height */ class Resolution { // eslint-disable-next-line require-jsdoc constructor(width, height) { /** * @member {number} width * @instance * @memberof Owt.Base.Resolution */ this.width = width; /** * @member {number} height * @instance * @memberof Owt.Base.Resolution */ this.height = height; } } /* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. */ let logDisabled_ = true; let deprecationWarnings_ = true; /** * Extract browser version out of the provided user agent string. * * @param {!string} uastring userAgent string. * @param {!string} expr Regular expression used as match criteria. * @param {!number} pos position in the version string to be returned. * @return {!number} browser version. */ function extractVersion(uastring, expr, pos) { const match = uastring.match(expr); return match && match.length >= pos && parseInt(match[pos], 10); } // Wraps the peerconnection event eventNameToWrap in a function // which returns the modified event object (or false to prevent // the event). function wrapPeerConnectionEvent(window, eventNameToWrap, wrapper) { if (!window.RTCPeerConnection) { return; } const proto = window.RTCPeerConnection.prototype; const nativeAddEventListener = proto.addEventListener; proto.addEventListener = function(nativeEventName, cb) { if (nativeEventName !== eventNameToWrap) { return nativeAddEventListener.apply(this, arguments); } const wrappedCallback = (e) => { const modifiedEvent = wrapper(e); if (modifiedEvent) { if (cb.handleEvent) { cb.handleEvent(modifiedEvent); } else { cb(modifiedEvent); } } }; this._eventMap = this._eventMap || {}; if (!this._eventMap[eventNameToWrap]) { this._eventMap[eventNameToWrap] = new Map(); } this._eventMap[eventNameToWrap].set(cb, wrappedCallback); return nativeAddEventListener.apply(this, [nativeEventName, wrappedCallback]); }; const nativeRemoveEventListener = proto.removeEventListener; proto.removeEventListener = function(nativeEventName, cb) { if (nativeEventName !== eventNameToWrap || !this._eventMap || !this._eventMap[eventNameToWrap]) { return nativeRemoveEventListener.apply(this, arguments); } if (!this._eventMap[eventNameToWrap].has(cb)) { return nativeRemoveEventListener.apply(this, arguments); } const unwrappedCb = this._eventMap[eventNameToWrap].get(cb); this._eventMap[eventNameToWrap].delete(cb); if (this._eventMap[eventNameToWrap].size === 0) { delete this._eventMap[eventNameToWrap]; } if (Object.keys(this._eventMap).length === 0) { delete this._eventMap; } return nativeRemoveEventListener.apply(this, [nativeEventName, unwrappedCb]); }; Object.defineProperty(proto, 'on' + eventNameToWrap, { get() { return this['_on' + eventNameToWrap]; }, set(cb) { if (this['_on' + eventNameToWrap]) { this.removeEventListener(eventNameToWrap, this['_on' + eventNameToWrap]); delete this['_on' + eventNameToWrap]; } if (cb) { this.addEventListener(eventNameToWrap, this['_on' + eventNameToWrap] = cb); } }, enumerable: true, configurable: true }); } function disableLog(bool) { if (typeof bool !== 'boolean') { return new Error('Argument type: ' + typeof bool + '. Please use a boolean.'); } logDisabled_ = bool; return (bool) ? 'adapter.js logging disabled' : 'adapter.js logging enabled'; } /** * Disable or enable deprecation warnings * @param {!boolean} bool set to true to disable warnings. */ function disableWarnings(bool) { if (typeof bool !== 'boolean') { return new Error('Argument type: ' + typeof bool + '. Please use a boolean.'); } deprecationWarnings_ = !bool; return 'adapter.js deprecation warnings ' + (bool ? 'disabled' : 'enabled'); } function log$1() { if (typeof window === 'object') { if (logDisabled_) { return; } if (typeof console !== 'undefined' && typeof console.log === 'function') { console.log.apply(console, arguments); } } } /** * Shows a deprecation warning suggesting the modern and spec-compatible API. */ function deprecated(oldMethod, newMethod) { if (!deprecationWarnings_) { return; } console.warn(oldMethod + ' is deprecated, please use ' + newMethod + ' instead.'); } /** * Browser detector. * * @return {object} result containing browser and version * properties. */ function detectBrowser(window) { // Returned result object. const result = {browser: null, version: null}; // Fail early if it's not a browser if (typeof window === 'undefined' || !window.navigator) { result.browser = 'Not a browser.'; return result; } const {navigator} = window; if (navigator.mozGetUserMedia) { // Firefox. result.browser = 'firefox'; result.version = extractVersion(navigator.userAgent, /Firefox\/(\d+)\./, 1); } else if (navigator.webkitGetUserMedia || (window.isSecureContext === false && window.webkitRTCPeerConnection && !window.RTCIceGatherer)) { // Chrome, Chromium, Webview, Opera. // Version matches Chrome/WebRTC version. // Chrome 74 removed webkitGetUserMedia on http as well so we need the // more complicated fallback to webkitRTCPeerConnection. result.browser = 'chrome'; result.version = extractVersion(navigator.userAgent, /Chrom(e|ium)\/(\d+)\./, 2); } else if (navigator.mediaDevices && navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) { // Edge. result.browser = 'edge'; result.version = extractVersion(navigator.userAgent, /Edge\/(\d+).(\d+)$/, 2); } else if (window.RTCPeerConnection && navigator.userAgent.match(/AppleWebKit\/(\d+)\./)) { // Safari. result.browser = 'safari'; result.version = extractVersion(navigator.userAgent, /AppleWebKit\/(\d+)\./, 1); result.supportsUnifiedPlan = window.RTCRtpTransceiver && 'currentDirection' in window.RTCRtpTransceiver.prototype; } else { // Default fallthrough: not supported. result.browser = 'Not a supported browser.'; return result; } return result; } /** * Checks if something is an object. * * @param {*} val The something you want to check. * @return true if val is an object, false otherwise. */ function isObject$1(val) { return Object.prototype.toString.call(val) === '[object Object]'; } /** * Remove all empty objects and undefined values * from a nested object -- an enhanced and vanilla version * of Lodash's `compact`. */ function compactObject(data) { if (!isObject$1(data)) { return data; } return Object.keys(data).reduce(function(accumulator, key) { const isObj = isObject$1(data[key]); const value = isObj ? compactObject(data[key]) : data[key]; const isEmptyObject = isObj && !Object.keys(value).length; if (value === undefined || isEmptyObject) { return accumulator; } return Object.assign(accumulator, {[key]: value}); }, {}); } /* iterates the stats graph recursively. */ function walkStats(stats, base, resultSet) { if (!base || resultSet.has(base.id)) { return; } resultSet.set(base.id, base); Object.keys(base).forEach(name => { if (name.endsWith('Id')) { walkStats(stats, stats.get(base[name]), resultSet); } else if (name.endsWith('Ids')) { base[name].forEach(id => { walkStats(stats, stats.get(id), resultSet); }); } }); } /* filter getStats for a sender/receiver track. */ function filterStats(result, track, outbound) { const streamStatsType = outbound ? 'outbound-rtp' : 'inbound-rtp'; const filteredResult = new Map(); if (track === null) { return filteredResult; } const trackStats = []; result.forEach(value => { if (value.type === 'track' && value.trackIdentifier === track.id) { trackStats.push(value); } }); trackStats.forEach(trackStat => { result.forEach(stats => { if (stats.type === streamStatsType && stats.trackId === trackStat.id) { walkStats(result, stats, filteredResult); } }); }); return filteredResult; } /* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. */ const logging = log$1; function shimGetUserMedia$3(window, browserDetails) { const navigator = window && window.navigator; if (!navigator.mediaDevices) { return; } const constraintsToChrome_ = function(c) { if (typeof c !== 'object' || c.mandatory || c.optional) { return c; } const cc = {}; Object.keys(c).forEach(key => { if (key === 'require' || key === 'advanced' || key === 'mediaSource') { return; } const r = (typeof c[key] === 'object') ? c[key] : {ideal: c[key]}; if (r.exact !== undefined && typeof r.exact === 'number') { r.min = r.max = r.exact; } const oldname_ = function(prefix, name) { if (prefix) { return prefix + name.charAt(0).toUpperCase() + name.slice(1); } return (name === 'deviceId') ? 'sourceId' : name; }; if (r.ideal !== undefined) { cc.optional = cc.optional || []; let oc = {}; if (typeof r.ideal === 'number') { oc[oldname_('min', key)] = r.ideal; cc.optional.push(oc); oc = {}; oc[oldname_('max', key)] = r.ideal; cc.optional.push(oc); } else { oc[oldname_('', key)] = r.ideal; cc.optional.push(oc); } } if (r.exact !== undefined && typeof r.exact !== 'number') { cc.mandatory = cc.mandatory || {}; cc.mandatory[oldname_('', key)] = r.exact; } else { ['min', 'max'].forEach(mix => { if (r[mix] !== undefined) { cc.mandatory = cc.mandatory || {}; cc.mandatory[oldname_(mix, key)] = r[mix]; } }); } }); if (c.advanced) { cc.optional = (cc.optional || []).concat(c.advanced); } return cc; }; const shimConstraints_ = function(constraints, func) { if (browserDetails.version >= 61) { return func(constraints); } constraints = JSON.parse(JSON.stringify(constraints)); if (constraints && typeof constraints.audio === 'object') { const remap = function(obj, a, b) { if (a in obj && !(b in obj)) { obj[b] = obj[a]; delete obj[a]; } }; constraints = JSON.parse(JSON.stringify(constraints)); remap(constraints.audio, 'autoGainControl', 'googAutoGainControl'); remap(constraints.audio, 'noiseSuppression', 'googNoiseSuppression'); constraints.audio = constraintsToChrome_(constraints.audio); } if (constraints && typeof constraints.video === 'object') { // Shim facingMode for mobile & surface pro. let face = constraints.video.facingMode; face = face && ((typeof face === 'object') ? face : {ideal: face}); const getSupportedFacingModeLies = browserDetails.version < 66; if ((face && (face.exact === 'user' || face.exact === 'environment' || face.ideal === 'user' || face.ideal === 'environment')) && !(navigator.mediaDevices.getSupportedConstraints && navigator.mediaDevices.getSupportedConstraints().facingMode && !getSupportedFacingModeLies)) { delete constraints.video.facingMode; let matches; if (face.exact === 'environment' || face.ideal === 'environment') { matches = ['back', 'rear']; } else if (face.exact === 'user' || face.ideal === 'user') { matches = ['front']; } if (matches) { // Look for matches in label, or use last cam for back (typical). return navigator.mediaDevices.enumerateDevices() .then(devices => { devices = devices.filter(d => d.kind === 'videoinput'); let dev = devices.find(d => matches.some(match => d.label.toLowerCase().includes(match))); if (!dev && devices.length && matches.includes('back')) { dev = devices[devices.length - 1]; // more likely the back cam } if (dev) { constraints.video.deviceId = face.exact ? {exact: dev.deviceId} : {ideal: dev.deviceId}; } constraints.video = constraintsToChrome_(constraints.video); logging('chrome: ' + JSON.stringify(constraints)); return func(constraints); }); } } constraints.video = constraintsToChrome_(constraints.video); } logging('chrome: ' + JSON.stringify(constraints)); return func(constraints); }; const shimError_ = function(e) { if (browserDetails.version >= 64) { return e; } return { name: { PermissionDeniedError: 'NotAllowedError', PermissionDismissedError: 'NotAllowedError', InvalidStateError: 'NotAllowedError', DevicesNotFoundError: 'NotFoundError', ConstraintNotSatisfiedError: 'OverconstrainedError', TrackStartError: 'NotReadableError', MediaDeviceFailedDueToShutdown: 'NotAllowedError', MediaDeviceKillSwitchOn: 'NotAllowedError', TabCaptureError: 'AbortError', ScreenCaptureError: 'AbortError', DeviceCaptureError: 'AbortError' }[e.name] || e.name, message: e.message, constraint: e.constraint || e.constraintName, toString() { return this.name + (this.message && ': ') + this.message; } }; }; const getUserMedia_ = function(constraints, onSuccess, onError) { shimConstraints_(constraints, c => { navigator.webkitGetUserMedia(c, onSuccess, e => { if (onError) { onError(shimError_(e)); } }); }); }; navigator.getUserMedia = getUserMedia_.bind(navigator); // Even though Chrome 45 has navigator.mediaDevices and a getUserMedia // function which returns a Promise, it does not accept spec-style // constraints. if (navigator.mediaDevices.getUserMedia) { const origGetUserMedia = navigator.mediaDevices.getUserMedia. bind(navigator.mediaDevices); navigator.mediaDevices.getUserMedia = function(cs) { return shimConstraints_(cs, c => origGetUserMedia(c).then(stream => { if (c.audio && !stream.getAudioTracks().length || c.video && !stream.getVideoTracks().length) { stream.getTracks().forEach(track => { track.stop(); }); throw new DOMException('', 'NotFoundError'); } return stream; }, e => Promise.reject(shimError_(e)))); }; } } /* * Copyright (c) 2018 The adapter.js project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. */ function shimGetDisplayMedia$2(window, getSourceId) { if (window.navigator.mediaDevices && 'getDisplayMedia' in window.navigator.mediaDevices) { return; } if (!(window.navigator.mediaDevices)) { return; } // getSourceId is a function that returns a promise resolving with // the sourceId of the screen/window/tab to be shared. if (typeof getSourceId !== 'function') { console.error('shimGetDisplayMedia: getSourceId argument is not ' + 'a function'); return; } window.navigator.mediaDevices.getDisplayMedia = function getDisplayMedia(constraints) { return getSourceId(constraints) .then(sourceId => { const widthSpecified = constraints.video && constraints.video.width; const heightSpecified = constraints.video && constraints.video.height; const frameRateSpecified = constraints.video && constraints.video.frameRate; constraints.video = { mandatory: { chromeMediaSource: 'desktop', chromeMediaSourceId: sourceId, maxFrameRate: frameRateSpecified || 3 } }; if (widthSpecified) { constraints.video.mandatory.maxWidth = widthSpecified; } if (heightSpecified) { constraints.video.mandatory.maxHeight = heightSpecified; } return window.navigator.mediaDevices.getUserMedia(constraints); }); }; } /* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. */ function shimMediaStream(window) { window.MediaStream = window.MediaStream || window.webkitMediaStream; } function shimOnTrack$1(window) { if (typeof window === 'object' && window.RTCPeerConnection && !('ontrack' in window.RTCPeerConnection.prototype)) { Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', { get() { return this._ontrack; }, set(f) { if (this._ontrack) { this.removeEventListener('track', this._ontrack); } this.addEventListener('track', this._ontrack = f); }, enumerable: true, configurable: true }); const origSetRemoteDescription = window.RTCPeerConnection.prototype.setRemoteDescription; window.RTCPeerConnection.prototype.setRemoteDescription = function setRemoteDescription() { if (!this._ontrackpoly) { this._ontrackpoly = (e) => { // onaddstream does not fire when a track is added to an existing // stream. But stream.onaddtrack is implemented so we use that. e.stream.addEventListener('addtrack', te => { let receiver; if (window.RTCPeerConnection.prototype.getReceivers) { receiver = this.getReceivers() .find(r => r.track && r.track.id === te.track.id); } else { receiver = {track: te.track}; } const event = new Event('track'); event.track = te.track; event.receiver = receiver; event.transceiver = {receiver}; event.streams = [e.stream]; this.dispatchEvent(event); }); e.stream.getTracks().forEach(track => { let receiver; if (window.RTCPeerConnection.prototype.getReceivers) { receiver = this.getReceivers() .find(r => r.track && r.track.id === track.id); } else { receiver = {track}; } const event = new Event('track'); event.track = track; event.receiver = receiver; event.transceiver = {receiver}; event.streams = [e.stream]; this.dispatchEvent(event); }); }; this.addEventListener('addstream', this._ontrackpoly); } return origSetRemoteDescription.apply(this, arguments); }; } else { // even if RTCRtpTransceiver is in window, it is only used and // emitted in unified-plan. Unfortunately this means we need // to unconditionally wrap the event. wrapPeerConnectionEvent(window, 'track', e => { if (!e.transceiver) { Object.defineProperty(e, 'transceiver', {value: {receiver: e.receiver}}); } return e; }); } } function shimGetSendersWithDtmf(window) { // Overrides addTrack/removeTrack, depends on shimAddTrackRemoveTrack. if (typeof window === 'object' && window.RTCPeerConnection && !('getSenders' in window.RTCPeerConnection.prototype) && 'createDTMFSender' in window.RTCPeerConnection.prototype) { const shimSenderWithDtmf = function(pc, track) { return { track, get dtmf() { if (this._dtmf === undefined) { if (track.kind === 'audio') { this._dtmf = pc.createDTMFSender(track); } else { this._dtmf = null; } } return this._dtmf; }, _pc: pc }; }; // augment addTrack when getSenders is not available. if (!window.RTCPeerConnection.prototype.getSenders) { window.RTCPeerConnection.prototype.getSenders = function getSenders() { this._senders = this._senders || []; return this._senders.slice(); // return a copy of the internal state. }; const origAddTrack = window.RTCPeerConnection.prototype.addTrack; window.RTCPeerConnection.prototype.addTrack = function addTrack(track, stream) { let sender = origAddTrack.apply(this, arguments); if (!sender) { sender = shimSenderWithDtmf(this, track); this._senders.push(sender); } return sender; }; const origRemoveTrack = window.RTCPeerConnection.prototype.removeTrack; window.RTCPeerConnection.prototype.removeTrack = function removeTrack(sender) { origRemoveTrack.apply(this, arguments); const idx = this._senders.indexOf(sender); if (idx !== -1) { this._senders.splice(idx, 1); } }; } const origAddStream = window.RTCPeerConnection.prototype.addStream; window.RTCPeerConnection.prototype.addStream = function addStream(stream) { this._senders = this._senders || []; origAddStream.apply(this, [stream]); stream.getTracks().forEach(track => { this._senders.push(shimSenderWithDtmf(this, track)); }); }; const origRemoveStream = window.RTCPeerConnection.prototype.removeStream; window.RTCPeerConnection.prototype.removeStream = function removeStream(stream) { this._senders = this._senders || []; origRemoveStream.apply(this, [stream]); stream.getTracks().forEach(track => { const sender = this._senders.find(s => s.track === track); if (sender) { // remove sender this._senders.splice(this._senders.indexOf(sender), 1); } }); }; } else if (typeof window === 'object' && window.RTCPeerConnection && 'getSenders' in window.RTCPeerConnection.prototype && 'createDTMFSender' in window.RTCPeerConnection.prototype && window.RTCRtpSender && !('dtmf' in window.RTCRtpSender.prototype)) { const origGetSenders = window.RTCPeerConnection.prototype.getSenders; window.RTCPeerConnection.prototype.getSenders = function getSenders() { const senders = origGetSenders.apply(this, []); senders.forEach(sender => sender._pc = this); return senders; }; Object.defineProperty(window.RTCRtpSender.prototype, 'dtmf', { get() { if (this._dtmf === undefined) { if (this.track.kind === 'audio') { this._dtmf = this._pc.createDTMFSender(this.track); } else { this._dtmf = null; } } return this._dtmf; } }); } } function shimGetStats(window) { if (!window.RTCPeerConnection) { return; } const origGetStats = window.RTCPeerConnection.prototype.getStats; window.RTCPeerConnection.prototype.getStats = function getStats() { const [selector, onSucc, onErr] = arguments; // If selector is a function then we are in the old style stats so just // pass back the original getStats format to avoid breaking old users. if (arguments.length > 0 && typeof selector === 'function') { return origGetStats.apply(this, arguments); } // When spec-style getStats is supported, return those when called with // either no arguments or the selector argument is null. if (origGetStats.length === 0 && (arguments.length === 0 || typeof selector !== 'function')) { return origGetStats.apply(this, []); } const fixChromeStats_ = function(response) { const standardReport = {}; const reports = response.result(); reports.forEach(report => { const standardStats = { id: report.id, timestamp: report.timestamp, type: { localcandidate: 'local-candidate', remotecandidate: 'remote-candidate' }[report.type] || report.type }; report.names().forEach(name => { standardStats[name] = report.stat(name); }); standardReport[standardStats.id] = standardStats; }); return standardReport; }; // shim getStats with maplike support const makeMapStats = function(stats) { return new Map(Object.keys(stats).map(key => [key, stats[key]])); }; if (arguments.length >= 2) { const successCallbackWrapper_ = function(response) { onSucc(makeMapStats(fixChromeStats_(response))); }; return origGetStats.apply(this, [successCallbackWrapper_, selector]); } // promise-support return new Promise((resolve, reject) => { origGetStats.apply(this, [ function(response) { resolve(makeMapStats(fixChromeStats_(response))); }, reject]); }).then(onSucc, onErr); }; } function shimSenderReceiverGetStats(window) { if (!(typeof window === 'object' && window.RTCPeerConnection && window.RTCRtpSender && window.RTCRtpReceiver)) { return; } // shim sender stats. if (!('getStats' in window.RTCRtpSender.prototype)) { const origGetSenders = window.RTCPeerConnection.prototype.getSenders; if (origGetSenders) { window.RTCPeerConnection.prototype.getSenders = function getSenders() { const senders = origGetSenders.apply(this, []); senders.forEach(sender => sender._pc = this); return senders; }; } const origAddTrack = window.RTCPeerConnection.prototype.addTrack; if (origAddTrack) { window.RTCPeerConnection.prototype.addTrack = function addTrack() { const sender = origAddTrack.apply(this, arguments); sender._pc = this; return sender; }; } window.RTCRtpSender.prototype.getStats = function getStats() { const sender = this; return this._pc.getStats().then(result => /* Note: this will include stats of all senders that * send a track with the same id as sender.track as * it is not possible to identify the RTCRtpSender. */ filterStats(result, sender.track, true)); }; } // shim receiver stats. if (!('getStats' in window.RTCRtpReceiver.prototype)) { const origGetReceivers = window.RTCPeerConnection.prototype.getReceivers; if (origGetReceivers) { window.RTCPeerConnection.prototype.getReceivers = function getReceivers() { const receivers = origGetReceivers.apply(this, []); receivers.forEach(receiver => receiver._pc = this); return receivers; }; } wrapPeerConnectionEvent(window, 'track', e => { e.receiver._pc = e.srcElement; return e; }); window.RTCRtpReceiver.prototype.getStats = function getStats() { const receiver = this; return this._pc.getStats().then(result => filterStats(result, receiver.track, false)); }; } if (!('getStats' in window.RTCRtpSender.prototype && 'getStats' in window.RTCRtpReceiver.prototype)) { return; } // shim RTCPeerConnection.getStats(track). const origGetStats = window.RTCPeerConnection.prototype.getStats; window.RTCPeerConnection.prototype.getStats = function getStats() { if (arguments.length > 0 && arguments[0] instanceof window.MediaStreamTrack) { const track = arguments[0]; let sender; let receiver; let err; this.getSenders().forEach(s => { if (s.track === track) { if (sender) { err = true; } else { sender = s; } } }); this.getReceivers().forEach(r => { if (r.track === track) { if (receiver) { err = true; } else { receiver = r; } } return r.track === track; }); if (err || (sender && receiver)) { return Promise.reject(new DOMException( 'There are more than one sender or receiver for the track.', 'InvalidAccessError')); } else if (sender) { return sender.getStats(); } else if (receiver) { return receiver.getStats(); } return Promise.reject(new DOMException( 'There is no sender or receiver for the track.', 'InvalidAccessError')); } return origGetStats.apply(this, arguments); }; } function shimAddTrackRemoveTrackWithNative(window) { // shim addTrack/removeTrack with native variants in order to make // the interactions with legacy getLocalStreams behave as in other browsers. // Keeps a mapping stream.id => [stream, rtpsenders...] window.RTCPeerConnection.prototype.getLocalStreams = function getLocalStreams() { this._shimmedLocalStreams = this._shimmedLocalStreams || {}; return Object.keys(this._shimmedLocalStreams) .map(streamId => this._shimmedLocalStreams[streamId][0]); }; const origAddTrack = window.RTCPeerConnection.prototype.addTrack; window.RTCPeerConnection.prototype.addTrack = function addTrack(track, stream) { if (!stream) { return origAddTrack.apply(this, arguments); } this._shimmedLocalStreams = this._shimmedLocalStreams || {}; const sender = origAddTrack.apply(this, arguments); if (!this._shimmedLocalStreams[stream.id]) { this._shimmedLocalStreams[stream.id] = [stream, sender]; } else if (this._shimmedLocalStreams[stream.id].indexOf(sender) === -1) { this._shimmedLocalStreams[stream.id].push(sender); } return sender; }; const origAddStream = window.RTCPeerConnection.prototype.addStream; window.RTCPeerConnection.prototype.addStream = function addStream(stream) { this._shimmedLocalStreams = this._shimmedLocalStreams || {}; stream.getTracks().forEach(track => { const alreadyExists = this.getSenders().find(s => s.track === track); if (alreadyExists) { throw new DOMException('Track already exists.', 'InvalidAccessError'); } }); const existingSenders = this.getSenders(); origAddStream.apply(this, arguments); const newSenders = this.getSenders() .filter(newSender => existingSenders.indexOf(newSender) === -1); this._shimmedLocalStreams[stream.id] = [stream].concat(newSenders); }; const origRemoveStream = window.RTCPeerConnection.prototype.removeStream; window.RTCPeerConnection.prototype.removeStream = function removeStream(stream) { this._shimmedLocalStreams = this._shimmedLocalStreams || {}; delete this._shimmedLocalStreams[stream.id]; return origRemoveStream.apply(this, arguments); }; const origRemoveTrack = window.RTCPeerConnection.prototype.removeTrack; window.RTCPeerConnection.prototype.removeTrack = function removeTrack(sender) { this._shimmedLocalStreams = this._shimmedLocalStreams || {}; if (sender) { Object.keys(this._shimmedLocalStreams).forEach(streamId => { const idx = this._shimmedLocalStreams[streamId].indexOf(sender); if (idx !== -1) { this._shimmedLocalStreams[streamId].splice(idx, 1); } if (this._shimmedLocalStreams[streamId].length === 1) { delete this._shimmedLocalStreams[streamId]; } }); } return origRemoveTrack.apply(this, arguments); }; } function shimAddTrackRemoveTrack(window, browserDetails) { if (!window.RTCPeerConnection) { return; } // shim addTrack and removeTrack. if (window.RTCPeerConnection.prototype.addTrack && browserDetails.version >= 65) { return shimAddTrackRemoveTrackWithNative(window); } // also shim pc.getLocalStreams when addTrack is shimmed // to return the original streams. const origGetLocalStreams = window.RTCPeerConnection.prototype .getLocalStreams; window.RTCPeerConnection.prototype.getLocalStreams = function getLocalStreams() { const nativeStreams = origGetLocalStreams.apply(this); this._reverseStreams = this._reverseStreams || {}; return nativeStreams.map(stream => this._reverseStreams[stream.id]); }; const origAddStream = window.RTCPeerConnection.prototype.addStream; window.RTCPeerConnection.prototype.addStream = function addStream(stream) { this._streams = this._streams || {}; this._reverseStreams = this._reverseStreams || {}; stream.getTracks().forEach(track => { const alreadyExists = this.getSenders().find(s => s.track === track); if (alreadyExists) { throw new DOMException('Track already exists.', 'InvalidAccessError'); } }); // Add identity mapping for consistency with addTrack. // Unless this is being used with a stream from addTrack. if (!this._reverseStreams[stream.id]) { const newStream = new window.MediaStream(stream.getTracks()); this._streams[stream.id] = newStream; this._reverseStreams[newStream.id] = stream; stream = newStream; } origAddStream.apply(this, [stream]); }; const origRemoveStream = window.RTCPeerConnection.prototype.removeStream; window.RTCPeerConnection.prototype.removeStream = function removeStream(stream) { this._streams = this._streams || {}; this._reverseStreams = this._reverseStreams || {}; origRemoveStream.apply(this, [(this._streams[stream.id] || stream)]); delete this._reverseStreams[(this._streams[stream.id] ? this._streams[stream.id].id : stream.id)]; delete this._streams[stream.id]; }; window.RTCPeerConnection.prototype.addTrack = function addTrack(track, stream) { if (this.signalingState === 'closed') { throw new DOMException( 'The RTCPeerConnection\'s signalingState is \'closed\'.', 'InvalidStateError'); } const streams = [].slice.call(arguments, 1); if (streams.length !== 1 || !streams[0].getTracks().find(t => t === track)) { // this is not fully correct but all we can manage without // [[associated MediaStreams]] internal slot. throw new DOMException( 'The adapter.js addTrack polyfill only supports a single ' + ' stream which is associated with the specified track.', 'NotSupportedError'); } const alreadyExists = this.getSenders().find(s => s.track === track); if (alreadyExists) { throw new DOMException('Track already exists.', 'InvalidAccessError'); } this._streams = this._streams || {}; this._reverseStreams = this._reverseStreams || {}; const oldStream = this._streams[stream.id]; if (oldStream) { // this is using odd Chrome behaviour, use with caution: // https://bugs.chromium.org/p/webrtc/issues/detail?id=7815 // Note: we rely on the high-level addTrack/dtmf shim to // create the sender with a dtmf sender. oldStream.addTrack(track); // Trigger ONN async. Promise.resolve().then(() => { this.dispatchEvent(new Event('negotiationneeded')); }); } else { const newStream = new window.MediaStream([track]); this._streams[stream.id] = newStream; this._reverseStreams[newStream.id] = stream; this.addStream(newStream); } return this.getSenders().find(s => s.track === track); }; // replace the internal stream id with the external one and // vice versa. function replaceInternalStreamId(pc, description) { let sdp = description.sdp; Object.keys(pc._reverseStreams || []).forEach(internalId => { const externalStream = pc._reverseStreams[internalId]; const internalStream = pc._streams[externalStream.id]; sdp = sdp.replace(new RegExp(internalStream.id, 'g'), externalStream.id); }); return new RTCSessionDescription({ type: description.type, sdp }); } function replaceExternalStreamId(pc, description) { let sdp = description.sdp; Object.keys(pc._reverseStreams || []).forEach(internalId => { const externalStream = pc._reverseStreams[internalId]; const internalStream = pc._streams[externalStream.id]; sdp = sdp.replace(new RegExp(externalStream.id, 'g'), internalStream.id); }); return new RTCSessionDescription({ type: description.type, sdp }); } ['createOffer', 'createAnswer'].forEach(function(method) { const nativeMethod = window.RTCPeerConnection.prototype[method]; const methodObj = {[method]() { const args = arguments; const isLegacyCall = arguments.length && typeof arguments[0] === 'function'; if (isLegacyCall) { return nativeMethod.apply(this, [ (description) => { const desc = replaceInternalStreamId(this, description); args[0].apply(null, [desc]); }, (err) => { if (args[1]) { args[1].apply(null, err); } }, arguments[2] ]); } return nativeMethod.apply(this, arguments) .then(description => replaceInternalStreamId(this, description)); }}; window.RTCPeerConnection.prototype[method] = methodObj[method]; }); const origSetLocalDescription = window.RTCPeerConnection.prototype.setLocalDescription; window.RTCPeerConnection.prototype.setLocalDescription = function setLocalDescription() { if (!arguments.length || !arguments[0].type) { return origSetLocalDescription.apply(this, arguments); } arguments[0] = replaceExternalStreamId(this, arguments[0]); return origSetLocalDescription.apply(this, arguments); }; // TODO: mangle getStats: https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamstats-streamidentifier const origLocalDescription = Object.getOwnPropertyDescriptor( window.RTCPeerConnection.prototype, 'localDescription'); Object.defineProperty(window.RTCPeerConnection.prototype, 'localDescription', { get() { const description = origLocalDescription.get.apply(this); if (description.type === '') { return description; } return replaceInternalStreamId(this, description); } }); window.RTCPeerConnection.prototype.removeTrack = function removeTrack(sender) { if (this.signalingState === 'closed') { throw new DOMException( 'The RTCPeerConnection\'s signalingState is \'closed\'.', 'InvalidStateError'); } // We can not yet check for sender instanceof RTCRtpSender // since we shim RTPSender. So we check if sender._pc is set. if (!sender._pc) { throw new DOMException('Argument 1 of RTCPeerConnection.removeTrack ' + 'does not implement interface RTCRtpSender.', 'TypeError'); } const isLocal = sender._pc === this; if (!isLocal) { throw new DOMException('Sender was not created by this connection.', 'InvalidAccessError'); } // Search for the native stream the senders track belongs to. this._streams = this._streams || {}; let stream; Object.keys(this._streams).forEach(streamid => { const hasTrack = this._streams[streamid].getTracks() .find(track => sender.track === track); if (hasTrack) { stream = this._streams[streamid]; } }); if (stream) { if (stream.getTracks().length === 1) { // if this is the last track of the stream, remove the stream. This // takes care of any shimmed _senders. this.removeStream(this._reverseStreams[stream.id]); } else { // relying on the same odd chrome behaviour as above. stream.removeTrack(sender.track); } this.dispatchEvent(new Event('negotiationneeded')); } }; } function shimPeerConnection$2(window, browserDetails) { if (!window.RTCPeerConnection && window.webkitRTCPeerConnection) { // very basic support for old versions. window.RTCPeerConnection = window.webkitRTCPeerConnection; } if (!window.RTCPeerConnection) { return; } // shim implicit creation of RTCSessionDescription/RTCIceCandidate if (browserDetails.version < 53) { ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'] .forEach(function(method) { const nativeMethod = window.RTCPeerConnection.prototype[method]; const methodObj = {[method]() { arguments[0] = new ((method === 'addIceCandidate') ? window.RTCIceCandidate : window.RTCSessionDescription)(arguments[0]); return nativeMethod.apply(this, arguments); }}; window.RTCPeerConnection.prototype[method] = methodObj[method]; }); } } // Attempt to fix ONN in plan-b mode. function fixNegotiationNeeded(window, browserDetails) { wrapPeerConnectionEvent(window, 'negotiationneeded', e => { const pc = e.target; if (browserDetails.version < 72 || (pc.getConfiguration && pc.getConfiguration().sdpSemantics === 'plan-b')) { if (pc.signalingState !== 'stable') { return; } } return e; }); } var chromeShim = /*#__PURE__*/Object.freeze({ __proto__: null, shimMediaStream: shimMediaStream, shimOnTrack: shimOnTrack$1, shimGetSendersWithDtmf: shimGetSendersWithDtmf, shimGetStats: shimGetStats, shimSenderReceiverGetStats: shimSenderReceiverGetStats, shimAddTrackRemoveTrackWithNative: shimAddTrackRemoveTrackWithNative, shimAddTrackRemoveTrack: shimAddTrackRemoveTrack, shimPeerConnection: shimPeerConnection$2, fixNegotiationNeeded: fixNegotiationNeeded, shimGetUserMedia: shimGetUserMedia$3, shimGetDisplayMedia: shimGetDisplayMedia$2 }); /* * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. */ // Edge does not like // 1) stun: filtered after 14393 unless ?transport=udp is present // 2) turn: that does not have all of turn:host:port?transport=udp // 3) turn: with ipv6 addresses // 4) turn: occurring muliple times function filterIceServers$1(iceServers, edgeVersion) { let hasTurn = false; iceServers = JSON.parse(JSON.stringify(iceServers)); return iceServers.filter(server => { if (server && (server.urls || server.url)) { let urls = server.urls || server.url; if (server.url && !server.urls) { deprecated('RTCIceServer.url', 'RTCIceServer.urls'); } const isString = typeof urls === 'string'; if (isString) { urls = [urls]; } urls = urls.filter(url => { // filter STUN unconditionally. if (url.indexOf('stun:') === 0) { return false; } const validTurn = url.startsWith('turn') && !url.startsWith('turn:[') && url.includes('transport=udp'); if (validTurn && !hasTurn) { hasTurn = true; return true; } return validTurn && !hasTurn; }); delete server.url; server.urls = isString ? urls[0] : urls; return !!urls.length; } }); } function createCommonjsModule(fn) { var module = { exports: {} }; return fn(module, module.exports), module.exports; } /* eslint-env node */ var sdp = createCommonjsModule(function (module) { // SDP helpers. var SDPUtils = {}; // Generate an alphanumeric identifier for cname or mids. // TODO: use UUIDs instead? https://gist.github.com/jed/982883 SDPUtils.generateIdentifier = function() { return Math.random().toString(36).substr(2, 10); }; // The RTCP CNAME used by all peerconnections from the same JS. SDPUtils.localCName = SDPUtils.generateIdentifier(); // Splits SDP into lines, dealing with both CRLF and LF. SDPUtils.splitLines = function(blob) { return blob.trim().split('\n').map(function(line) { return line.trim(); }); }; // Splits SDP into sessionpart and mediasections. Ensures CRLF. SDPUtils.splitSections = function(blob) { var parts = blob.split('\nm='); return parts.map(function(part, index) { return (index > 0 ? 'm=' + part : part).trim() + '\r\n'; }); }; // returns the session description. SDPUtils.getDescription = function(blob) { var sections = SDPUtils.splitSections(blob); return sections && sections[0]; }; // returns the individual media sections. SDPUtils.getMediaSections = function(blob) { var sections = SDPUtils.splitSections(blob); sections.shift(); return sections; }; // Returns lines that start with a certain prefix. SDPUtils.matchPrefix = function(blob, prefix) { return SDPUtils.splitLines(blob).filter(function(line) { return line.indexOf(prefix) === 0; }); }; // Parses an ICE candidate line. Sample input: // candidate:702786350 2 udp 41819902 8.8.8.8 60769 typ relay raddr 8.8.8.8 // rport 55996" SDPUtils.parseCandidate = function(line) { var parts; // Parse both variants. if (line.indexOf('a=candidate:') === 0) { parts = line.substring(12).split(' '); } else { parts = line.substring(10).split(' '); } var candidate = { foundation: parts[0], component: parseInt(parts[1], 10), protocol: parts[2].toLowerCase(), priority: parseInt(parts[3], 10), ip: parts[4], address: parts[4], // address is an alias for ip. port: parseInt(parts[5], 10), // skip parts[6] == 'typ' type: parts[7] }; for (var i = 8; i < parts.length; i += 2) { switch (parts[i]) { case 'raddr': candidate.relatedAddress = parts[i + 1]; break; case 'rport': candidate.relatedPort = parseInt(parts[i + 1], 10); break; case 'tcptype': candidate.tcpType = parts[i + 1]; break; case 'ufrag': candidate.ufrag = parts[i + 1]; // for backward compability. candidate.usernameFragment = parts[i + 1]; break; default: // extension handling, in particular ufrag candidate[parts[i]] = parts[i + 1]; break; } } return candidate; }; // Translates a candidate object into SDP candidate attribute. SDPUtils.writeCandidate = function(candidate) { var sdp = []; sdp.push(candidate.foundation); sdp.push(candidate.component); sdp.push(candidate.protocol.toUpperCase()); sdp.push(candidate.priority); sdp.push(candidate.address || candidate.ip); sdp.push(candidate.port); var type = candidate.type; sdp.push('typ'); sdp.push(type); if (type !== 'host' && candidate.relatedAddress && candidate.relatedPort) { sdp.push('raddr'); sdp.push(candidate.relatedAddress); sdp.push('rport'); sdp.push(candidate.relatedPort); } if (candidate.tcpType && candidate.protocol.toLowerCase() === 'tcp') { sdp.push('tcptype'); sdp.push(candidate.tcpType); } if (candidate.usernameFragment || candidate.ufrag) { sdp.push('ufrag'); sdp.push(candidate.usernameFragment || candidate.ufrag); } return 'candidate:' + sdp.join(' '); }; // Parses an ice-options line, returns an array of option tags. // a=ice-options:foo bar SDPUtils.parseIceOptions = function(line) { return line.substr(14).split(' '); }; // Parses an rtpmap line, returns RTCRtpCoddecParameters. Sample input: // a=rtpmap:111 opus/48000/2 SDPUtils.parseRtpMap = function(line) { var parts = line.substr(9).split(' '); var parsed = { payloadType: parseInt(parts.shift(), 10) // was: id }; parts = parts[0].split('/'); parsed.name = parts[0]; parsed.clockRate = parseInt(parts[1], 10); // was: clockrate parsed.channels = parts.length === 3 ? parseInt(parts[2], 10) : 1; // legacy alias, got renamed back to channels in ORTC. parsed.numChannels = parsed.channels; return parsed; }; // Generate an a=rtpmap line from RTCRtpCodecCapability or // RTCRtpCodecParameters. SDPUtils.writeRtpMap = function(codec) { var pt = codec.payloadType; if (codec.preferredPayloadType !== undefined) { pt = codec.preferredPayloadType; } var channels = codec.channels || codec.numChannels || 1; return 'a=rtpmap:' + pt + ' ' + codec.name + '/' + codec.clockRate + (channels !== 1 ? '/' + channels : '') + '\r\n'; }; // Parses an a=extmap line (headerextension from RFC 5285). Sample input: // a=extmap:2 urn:ietf:params:rtp-hdrext:toffset // a=extmap:2/sendonly urn:ietf:params:rtp-hdrext:toffset SDPUtils.parseExtmap = function(line) { var parts = line.substr(9).split(' '); return { id: parseInt(parts[0], 10), direction: parts[0].indexOf('/') > 0 ? parts[0].split('/')[1] : 'sendrecv', uri: parts[1] }; }; // Generates a=extmap line from RTCRtpHeaderExtensionParameters or // RTCRtpHeaderExtension. SDPUtils.writeExtmap = function(headerExtension) { return 'a=extmap:' + (headerExtension.id || headerExtension.preferredId) + (headerExtension.direction && headerExtension.direction !== 'sendrecv' ? '/' + headerExtension.direction : '') + ' ' + headerExtension.uri + '\r\n'; }; // Parses an ftmp line, returns dictionary. Sample input: // a=fmtp:96 vbr=on;cng=on // Also deals with vbr=on; cng=on SDPUtils.parseFmtp = function(line) { var parsed = {}; var kv; var parts = line.substr(line.indexOf(' ') + 1).split(';'); for (var j = 0; j < parts.length; j++) { kv = parts[j].trim().split('='); parsed[kv[0].trim()] = kv[1]; } return parsed; }; // Generates an a=ftmp line from RTCRtpCodecCapability or RTCRtpCodecParameters. SDPUtils.writeFmtp = function(codec) { var line = ''; var pt = codec.payloadType; if (codec.preferredPayloadType !== undefined) { pt = codec.preferredPayloadType; } if (codec.parameters && Object.keys(codec.parameters).length) { var params = []; Object.keys(codec.parameters).forEach(function(param) { if (codec.parameters[param]) { params.push(param + '=' + codec.parameters[param]); } else { params.push(param); } }); line += 'a=fmtp:' + pt + ' ' + params.join(';') + '\r\n'; } return line; }; // Parses an rtcp-fb line, returns RTCPRtcpFeedback object. Sample input: // a=rtcp-fb:98 nack rpsi SDPUtils.parseRtcpFb = function(line) { var parts = line.substr(line.indexOf(' ') + 1).split(' '); return { type: parts.shift(), parameter: parts.join(' ') }; }; // Generate a=rtcp-fb lines from RTCRtpCodecCapability or RTCRtpCodecParameters. SDPUtils.writeRtcpFb = function(codec) { var lines = ''; var pt = codec.payloadType; if (codec.preferredPayloadType !== undefined) { pt = codec.preferredPayloadType; } if (codec.rtcpFeedback && codec.rtcpFeedback.length) { // FIXME: special handling for trr-int? codec.rtcpFeedback.forEach(function(fb) { lines += 'a=rtcp-fb:' + pt + ' ' + fb.type + (fb.parameter && fb.parameter.length ? ' ' + fb.parameter : '') + '\r\n'; }); } return lines; }; // Parses an RFC 5576 ssrc media attribute. Sample input: // a=ssrc:3735928559 cname:something SDPUtils.parseSsrcMedia = function(line) { var sp = line.indexOf(' '); var parts = { ssrc: parseInt(line.substr(7, sp - 7), 10) }; var colon = line.indexOf(':', sp); if (colon > -1) { parts.attribute = line.substr(sp + 1, colon - sp - 1); parts.value = line.substr(colon + 1); } else { parts.attribute = line.substr(sp + 1); } return parts; }; SDPUtils.parseSsrcGroup = function(line) { var parts = line.substr(13).split(' '); return { semantics: parts.shift(), ssrcs: parts.map(function(ssrc) { return parseInt(ssrc, 10); }) }; }; // Extracts the MID (RFC 5888) from a media section. // returns the MID or undefined if no mid line was found. SDPUtils.getMid = function(mediaSection) { var mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:')[0]; if (mid) { return mid.substr(6); } }; SDPUtils.parseFingerprint = function(line) { var parts = line.substr(14).split(' '); return { algorithm: parts[0].toLowerCase(), // algorithm is case-sensitive in Edge. value: parts[1] }; }; // Extracts DTLS parameters from SDP media section or sessionpart. // FIXME: for consistency with other functions this should only // get the fingerprint line as input. See also getIceParameters. SDPUtils.getDtlsParameters = function(mediaSection, sessionpart) { var lines = SDPUtils.matchPrefix(mediaSection + sessionpart, 'a=fingerprint:'); // Note: a=setup line is ignored since we use the 'auto' role. // Note2: 'algorithm' is not case sensitive except in Edge. return { role: 'auto', fingerprints: lines.map(SDPUtils.parseFingerprint) }; }; // Serializes DTLS parameters to SDP. SDPUtils.writeDtlsParameters = function(params, setupType) { var sdp = 'a=setup:' + setupType + '\r\n'; params.fingerprints.forEach(function(fp) { sdp += 'a=fingerprint:' + fp.algorithm + ' ' + fp.value + '\r\n'; }); return sdp; }; // Parses a=crypto lines into // https://rawgit.com/aboba/edgertc/master/msortc-rs4.html#dictionary-rtcsrtpsdesparameters-members SDPUtils.parseCryptoLine = function(line) { var parts = line.substr(9).split(' '); return { tag: parseInt(parts[0], 10), cryptoSuite: parts[1], keyParams: parts[2], sessionParams: parts.slice(3), }; }; SDPUtils.writeCryptoLine = function(parameters) { return 'a=crypto:' + parameters.tag + ' ' + parameters.cryptoSuite + ' ' + (typeof parameters.keyParams === 'object' ? SDPUtils.writeCryptoKeyParams(parameters.keyParams) : parameters.keyParams) + (parameters.sessionParams ? ' ' + parameters.sessionParams.join(' ') : '') + '\r\n'; }; // Parses the crypto key parameters into // https://rawgit.com/aboba/edgertc/master/msortc-rs4.html#rtcsrtpkeyparam* SDPUtils.parseCryptoKeyParams = function(keyParams) { if (keyParams.indexOf('inline:') !== 0) { return null; } var parts = keyParams.substr(7).split('|'); return { keyMethod: 'inline', keySalt: parts[0], lifeTime: parts[1], mkiValue: parts[2] ? parts[2].split(':')[0] : undefined, mkiLength: parts[2] ? parts[2].split(':')[1] : undefined, }; }; SDPUtils.writeCryptoKeyParams = function(keyParams) { return keyParams.keyMethod + ':' + keyParams.keySalt + (keyParams.lifeTime ? '|' + keyParams.lifeTime : '') + (keyParams.mkiValue && keyParams.mkiLength ? '|' + keyParams.mkiValue + ':' + keyParams.mkiLength : ''); }; // Extracts all SDES paramters. SDPUtils.getCryptoParameters = function(mediaSection, sessionpart) { var lines = SDPUtils.matchPrefix(mediaSection + sessionpart, 'a=crypto:'); return lines.map(SDPUtils.parseCryptoLine); }; // Parses ICE information from SDP media section or sessionpart. // FIXME: for consistency with other functions this should only // get the ice-ufrag and ice-pwd lines as input. SDPUtils.getIceParameters = function(mediaSection, sessionpart) { var ufrag = SDPUtils.matchPrefix(mediaSection + sessionpart, 'a=ice-ufrag:')[0]; var pwd = SDPUtils.matchPrefix(mediaSection + sessionpart, 'a=ice-pwd:')[0]; if (!(ufrag && pwd)) { return null; } return { usernameFragment: ufrag.substr(12), password: pwd.substr(10), }; }; // Serializes ICE parameters to SDP. SDPUtils.writeIceParameters = function(params) { return 'a=ice-ufrag:' + params.usernameFragment + '\r\n' + 'a=ice-pwd:' + params.password + '\r\n'; }; // Parses the SDP media section and returns RTCRtpParameters. SDPUtils.parseRtpParameters = function(mediaSection) { var description = { codecs: [], headerExtensions: [], fecMechanisms: [], rtcp: [] }; var lines = SDPUtils.splitLines(mediaSection); var mline = lines[0].split(' '); for (var i = 3; i < mline.length; i++) { // find all codecs from mline[3..] var pt = mline[i]; var rtpmapline = SDPUtils.matchPrefix( mediaSection, 'a=rtpmap:' + pt + ' ')[0]; if (rtpmapline) { var codec = SDPUtils.parseRtpMap(rtpmapline); var fmtps = SDPUtils.matchPrefix( mediaSection, 'a=fmtp:' + pt + ' '); // Only the first a=fmtp: is considered. codec.parameters = fmtps.length ? SDPUtils.parseFmtp(fmtps[0]) : {}; codec.rtcpFeedback = SDPUtils.matchPrefix( mediaSection, 'a=rtcp-fb:' + pt + ' ') .map(SDPUtils.parseRtcpFb); description.codecs.push(codec); // parse FEC mechanisms from rtpmap lines. switch (codec.name.toUpperCase()) { case 'RED': case 'ULPFEC': description.fecMechanisms.push(codec.name.toUpperCase()); break; } } } SDPUtils.matchPrefix(mediaSection, 'a=extmap:').forEach(function(line) { description.headerExtensions.push(SDPUtils.parseExtmap(line)); }); // FIXME: parse rtcp. return description; }; // Generates parts of the SDP media section describing the capabilities / // parameters. SDPUtils.writeRtpDescription = function(kind, caps) { var sdp = ''; // Build the mline. sdp += 'm=' + kind + ' '; sdp += caps.codecs.length > 0 ? '9' : '0'; // reject if no codecs. sdp += ' UDP/TLS/RTP/SAVPF '; sdp += caps.codecs.map(function(codec) { if (codec.preferredPayloadType !== undefined) { return codec.preferredPayloadType; } return codec.payloadType; }).join(' ') + '\r\n'; sdp += 'c=IN IP4 0.0.0.0\r\n'; sdp += 'a=rtcp:9 IN IP4 0.0.0.0\r\n'; // Add a=rtpmap lines for each codec. Also fmtp and rtcp-fb. caps.codecs.forEach(function(codec) { sdp += SDPUtils.writeRtpMap(codec); sdp += SDPUtils.writeFmtp(codec); sdp += SDPUtils.writeRtcpFb(codec); }); var maxptime = 0; caps.codecs.forEach(function(codec) { if (codec.maxptime > maxptime) { maxptime = codec.maxptime; } }); if (maxptime > 0) { sdp += 'a=maxptime:' + maxptime + '\r\n'; } sdp += 'a=rtcp-mux\r\n'; if (caps.headerExtensions) { caps.headerExtensions.forEach(function(extension) { sdp += SDPUtils.writeExtmap(extension); }); } // FIXME: write fecMechanisms. return sdp; }; // Parses the SDP media section and returns an array of // RTCRtpEncodingParameters. SDPUtils.parseRtpEncodingParameters = function(mediaSection) { var encodingParameters = []; var description = SDPUtils.parseRtpParameters(mediaSection); var hasRed = description.fecMechanisms.indexOf('RED') !== -1; var hasUlpfec = description.fecMechanisms.indexOf('ULPFEC') !== -1; // filter a=ssrc:... cname:, ignore PlanB-msid var ssrcs = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:') .map(function(line) { return SDPUtils.parseSsrcMedia(line); }) .filter(function(parts) { return parts.attribute === 'cname'; }); var primarySsrc = ssrcs.length > 0 && ssrcs[0].ssrc; var secondarySsrc; var flows = SDPUtils.matchPrefix(mediaSection, 'a=ssrc-group:FID') .map(function(line) { var parts = line.substr(17).split(' '); return parts.map(function(part) { return parseInt(part, 10); }); }); if (flows.length > 0 && flows[0].length > 1 && flows[0][0] === primarySsrc) { secondarySsrc = flows[0][1]; } description.codecs.forEach(function(codec) { if (codec.name.toUpperCase() === 'RTX' && codec.parameters.apt) { var encParam = { ssrc: primarySsrc, codecPayloadType: parseInt(codec.parameters.apt, 10) }; if (primarySsrc && secondarySsrc) { encParam.rtx = {ssrc: secondarySsrc}; } encodingParameters.push(encParam); if (hasRed) { encParam = JSON.parse(JSON.stringify(encParam)); encParam.fec = { ssrc: primarySsrc, mechanism: hasUlpfec ? 'red+ulpfec' : 'red' }; encodingParameters.push(encParam); } } }); if (encodingParameters.length === 0 && primarySsrc) { encodingParameters.push({ ssrc: primarySsrc }); } // we support both b=AS and b=TIAS but interpret AS as TIAS. var bandwidth = SDPUtils.matchPrefix(mediaSection, 'b='); if (bandwidth.length) { if (bandwidth[0].indexOf('b=TIAS:') === 0) { bandwidth = parseInt(bandwidth[0].substr(7), 10); } else if (bandwidth[0].indexOf('b=AS:') === 0) { // use formula from JSEP to convert b=AS to TIAS value. bandwidth = parseInt(bandwidth[0].substr(5), 10) * 1000 * 0.95 - (50 * 40 * 8); } else { bandwidth = undefined; } encodingParameters.forEach(function(params) { params.maxBitrate = bandwidth; }); } return encodingParameters; }; // parses http://draft.ortc.org/#rtcrtcpparameters* SDPUtils.parseRtcpParameters = function(mediaSection) { var rtcpParameters = {}; // Gets the first SSRC. Note tha with RTX there might be multiple // SSRCs. var remoteSsrc = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:') .map(function(line) { return SDPUtils.parseSsrcMedia(line); }) .filter(function(obj) { return obj.attribute === 'cname'; })[0]; if (remoteSsrc) { rtcpParameters.cname = remoteSsrc.value; rtcpParameters.ssrc = remoteSsrc.ssrc; } // Edge uses the compound attribute instead of reducedSize // compound is !reducedSize var rsize = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-rsize'); rtcpParameters.reducedSize = rsize.length > 0; rtcpParameters.compound = rsize.length === 0; // parses the rtcp-mux attrіbute. // Note that Edge does not support unmuxed RTCP. var mux = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-mux'); rtcpParameters.mux = mux.length > 0; return rtcpParameters; }; // parses either a=msid: or a=ssrc:... msid lines and returns // the id of the MediaStream and MediaStreamTrack. SDPUtils.parseMsid = function(mediaSection) { var parts; var spec = SDPUtils.matchPrefix(mediaSection, 'a=msid:'); if (spec.length === 1) { parts = spec[0].substr(7).split(' '); return {stream: parts[0], track: parts[1]}; } var planB = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:') .map(function(line) { return SDPUtils.parseSsrcMedia(line); }) .filter(function(msidParts) { return msidParts.attribute === 'msid'; }); if (planB.length > 0) { parts = planB[0].value.split(' '); return {stream: parts[0], track: parts[1]}; } }; // SCTP // parses draft-ietf-mmusic-sctp-sdp-26 first and falls back // to draft-ietf-mmusic-sctp-sdp-05 SDPUtils.parseSctpDescription = function(mediaSection) { var mline = SDPUtils.parseMLine(mediaSection); var maxSizeLine = SDPUtils.matchPrefix(mediaSection, 'a=max-message-size:'); var maxMessageSize; if (maxSizeLine.length > 0) { maxMessageSize = parseInt(maxSizeLine[0].substr(19), 10); } if (isNaN(maxMessageSize)) { maxMessageSize = 65536; } var sctpPort = SDPUtils.matchPrefix(mediaSection, 'a=sctp-port:'); if (sctpPort.length > 0) { return { port: parseInt(sctpPort[0].substr(12), 10), protocol: mline.fmt, maxMessageSize: maxMessageSize }; } var sctpMapLines = SDPUtils.matchPrefix(mediaSection, 'a=sctpmap:'); if (sctpMapLines.length > 0) { var parts = SDPUtils.matchPrefix(mediaSection, 'a=sctpmap:')[0] .substr(10) .split(' '); return { port: parseInt(parts[0], 10), protocol: parts[1], maxMessageSize: maxMessageSize }; } }; // SCTP // outputs the draft-ietf-mmusic-sctp-sdp-26 version that all browsers // support by now receiving in this format, unless we originally parsed // as the draft-ietf-mmusic-sctp-sdp-05 format (indicated by the m-line // protocol of DTLS/SCTP -- without UDP/ or TCP/) SDPUtils.writeSctpDescription = function(media, sctp) { var output = []; if (media.protocol !== 'DTLS/SCTP') { output = [ 'm=' + media.kind + ' 9 ' + media.protocol + ' ' + sctp.protocol + '\r\n', 'c=IN IP4 0.0.0.0\r\n', 'a=sctp-port:' + sctp.port + '\r\n' ]; } else { output = [ 'm=' + media.kind + ' 9 ' + media.protocol + ' ' + sctp.port + '\r\n', 'c=IN IP4 0.0.0.0\r\n', 'a=sctpmap:' + sctp.port + ' ' + sctp.protocol + ' 65535\r\n' ]; } if (sctp.maxMessageSize !== undefined) { output.push('a=max-message-size:' + sctp.maxMessageSize + '\r\n'); } return output.join(''); }; // Generate a session ID for SDP. // https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-20#section-5.2.1 // recommends using a cryptographically random +ve 64-bit value // but right now this should be acceptable and within the right range SDPUtils.generateSessionId = function() { return Math.random().toString().substr(2, 21); }; // Write boilder plate for start of SDP // sessId argument is optional - if not supplied it will // be generated randomly // sessVersion is optional and defaults to 2 // sessUser is optional and defaults to 'thisisadapterortc' SDPUtils.writeSessionBoilerplate = function(sessId, sessVer, sessUser) { var sessionId; var version = sessVer !== undefined ? sessVer : 2; if (sessId) { sessionId = sessId; } else { sessionId = SDPUtils.generateSessionId(); } var user = sessUser || 'thisisadapterortc'; // FIXME: sess-id should be an NTP timestamp. return 'v=0\r\n' + 'o=' + user + ' ' + sessionId + ' ' + version + ' IN IP4 127.0.0.1\r\n' + 's=-\r\n' + 't=0 0\r\n'; }; SDPUtils.writeMediaSection = function(transceiver, caps, type, stream) { var sdp = SDPUtils.writeRtpDescription(transceiver.kind, caps); // Map ICE parameters (ufrag, pwd) to SDP. sdp += SDPUtils.writeIceParameters( transceiver.iceGatherer.getLocalParameters()); // Map DTLS parameters to SDP. sdp += SDPUtils.writeDtlsParameters( transceiver.dtlsTransport.getLocalParameters(), type === 'offer' ? 'actpass' : 'active'); sdp += 'a=mid:' + transceiver.mid + '\r\n'; if (transceiver.direction) { sdp += 'a=' + transceiver.direction + '\r\n'; } else if (transceiver.rtpSender && transceiver.rtpReceiver) { sdp += 'a=sendrecv\r\n'; } else if (transceiver.rtpSender) { sdp += 'a=sendonly\r\n'; } else if (transceiver.rtpReceiver) { sdp += 'a=recvonly\r\n'; } else { sdp += 'a=inactive\r\n'; } if (transceiver.rtpSender) { // spec. var msid = 'msid:' + stream.id + ' ' + transceiver.rtpSender.track.id + '\r\n'; sdp += 'a=' + msid; // for Chrome. sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc + ' ' + msid; if (transceiver.sendEncodingParameters[0].rtx) { sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc + ' ' + msid; sdp += 'a=ssrc-group:FID ' + transceiver.sendEncodingParameters[0].ssrc + ' ' + transceiver.sendEncodingParameters[0].rtx.ssrc + '\r\n'; } } // FIXME: this should be written by writeRtpDescription. sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc + ' cname:' + SDPUtils.localCName + '\r\n'; if (transceiver.rtpSender && transceiver.sendEncodingParameters[0].rtx) { sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc + ' cname:' + SDPUtils.localCName + '\r\n'; } return sdp; }; // Gets the direction from the mediaSection or the sessionpart. SDPUtils.getDirection = function(mediaSection, sessionpart) { // Look for sendrecv, sendonly, recvonly, inactive, default to sendrecv. var lines = SDPUtils.splitLines(mediaSection); for (var i = 0; i < lines.length; i++) { switch (lines[i]) { case 'a=sendrecv': case 'a=sendonly': case 'a=recvonly': case 'a=inactive': return lines[i].substr(2); // FIXME: What should happen here? } } if (sessionpart) { return SDPUtils.getDirection(sessionpart); } return 'sendrecv'; }; SDPUtils.getKind = function(mediaSection) { var lines = SDPUtils.splitLines(mediaSection); var mline = lines[0].split(' '); return mline[0].substr(2); }; SDPUtils.isRejected = function(mediaSection) { return mediaSection.split(' ', 2)[1] === '0'; }; SDPUtils.parseMLine = function(mediaSection) { var lines = SDPUtils.splitLines(mediaSection); var parts = lines[0].substr(2).split(' '); return { kind: parts[0], port: parseInt(parts[1], 10), protocol: parts[2], fmt: parts.slice(3).join(' ') }; }; SDPUtils.parseOLine = function(mediaSection) { var line = SDPUtils.matchPrefix(mediaSection, 'o=')[0]; var parts = line.substr(2).split(' '); return { username: parts[0], sessionId: parts[1], sessionVersion: parseInt(parts[2], 10), netType: parts[3], addressType: parts[4], address: parts[5] }; }; // a very naive interpretation of a valid SDP. SDPUtils.isValidSDP = function(blob) { if (typeof blob !== 'string' || blob.length === 0) { return false; } var lines = SDPUtils.splitLines(blob); for (var i = 0; i < lines.length; i++) { if (lines[i].length < 2 || lines[i].charAt(1) !== '=') { return false; } // TODO: check the modifier a bit more. } return true; }; // Expose public methods. { module.exports = SDPUtils; } }); /* * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. */ function fixStatsType(stat) { return { inboundrtp: 'inbound-rtp', outboundrtp: 'outbound-rtp', candidatepair: 'candidate-pair', localcandidate: 'local-candidate', remotecandidate: 'remote-candidate' }[stat.type] || stat.type; } function writeMediaSection(transceiver, caps, type, stream, dtlsRole) { var sdp$1 = sdp.writeRtpDescription(transceiver.kind, caps); // Map ICE parameters (ufrag, pwd) to SDP. sdp$1 += sdp.writeIceParameters( transceiver.iceGatherer.getLocalParameters()); // Map DTLS parameters to SDP. sdp$1 += sdp.writeDtlsParameters( transceiver.dtlsTransport.getLocalParameters(), type === 'offer' ? 'actpass' : dtlsRole || 'active'); sdp$1 += 'a=mid:' + transceiver.mid + '\r\n'; if (transceiver.rtpSender && transceiver.rtpReceiver) { sdp$1 += 'a=sendrecv\r\n'; } else if (transceiver.rtpSender) { sdp$1 += 'a=sendonly\r\n'; } else if (transceiver.rtpReceiver) { sdp$1 += 'a=recvonly\r\n'; } else { sdp$1 += 'a=inactive\r\n'; } if (transceiver.rtpSender) { var trackId = transceiver.rtpSender._initialTrackId || transceiver.rtpSender.track.id; transceiver.rtpSender._initialTrackId = trackId; // spec. var msid = 'msid:' + (stream ? stream.id : '-') + ' ' + trackId + '\r\n'; sdp$1 += 'a=' + msid; // for Chrome. Legacy should no longer be required. sdp$1 += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc + ' ' + msid; // RTX if (transceiver.sendEncodingParameters[0].rtx) { sdp$1 += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc + ' ' + msid; sdp$1 += 'a=ssrc-group:FID ' + transceiver.sendEncodingParameters[0].ssrc + ' ' + transceiver.sendEncodingParameters[0].rtx.ssrc + '\r\n'; } } // FIXME: this should be written by writeRtpDescription. sdp$1 += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc + ' cname:' + sdp.localCName + '\r\n'; if (transceiver.rtpSender && transceiver.sendEncodingParameters[0].rtx) { sdp$1 += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc + ' cname:' + sdp.localCName + '\r\n'; } return sdp$1; } // Edge does not like // 1) stun: filtered after 14393 unless ?transport=udp is present // 2) turn: that does not have all of turn:host:port?transport=udp // 3) turn: with ipv6 addresses // 4) turn: occurring muliple times function filterIceServers(iceServers, edgeVersion) { var hasTurn = false; iceServers = JSON.parse(JSON.stringify(iceServers)); return iceServers.filter(function(server) { if (server && (server.urls || server.url)) { var urls = server.urls || server.url; if (server.url && !server.urls) { console.warn('RTCIceServer.url is deprecated! Use urls instead.'); } var isString = typeof urls === 'string'; if (isString) { urls = [urls]; } urls = urls.filter(function(url) { var validTurn = url.indexOf('turn:') === 0 && url.indexOf('transport=udp') !== -1 && url.indexOf('turn:[') === -1 && !hasTurn; if (validTurn) { hasTurn = true; return true; } return url.indexOf('stun:') === 0 && edgeVersion >= 14393 && url.indexOf('?transport=udp') === -1; }); delete server.url; server.urls = isString ? urls[0] : urls; return !!urls.length; } }); } // Determines the intersection of local and remote capabilities. function getCommonCapabilities(localCapabilities, remoteCapabilities) { var commonCapabilities = { codecs: [], headerExtensions: [], fecMechanisms: [] }; var findCodecByPayloadType = function(pt, codecs) { pt = parseInt(pt, 10); for (var i = 0; i < codecs.length; i++) { if (codecs[i].payloadType === pt || codecs[i].preferredPayloadType === pt) { return codecs[i]; } } }; var rtxCapabilityMatches = function(lRtx, rRtx, lCodecs, rCodecs) { var lCodec = findCodecByPayloadType(lRtx.parameters.apt, lCodecs); var rCodec = findCodecByPayloadType(rRtx.parameters.apt, rCodecs); return lCodec && rCodec && lCodec.name.toLowerCase() === rCodec.name.toLowerCase(); }; localCapabilities.codecs.forEach(function(lCodec) { for (var i = 0; i < remoteCapabilities.codecs.length; i++) { var rCodec = remoteCapabilities.codecs[i]; if (lCodec.name.toLowerCase() === rCodec.name.toLowerCase() && lCodec.clockRate === rCodec.clockRate) { if (lCodec.name.toLowerCase() === 'rtx' && lCodec.parameters && rCodec.parameters.apt) { // for RTX we need to find the local rtx that has a apt // which points to the same local codec as the remote one. if (!rtxCapabilityMatches(lCodec, rCodec, localCapabilities.codecs, remoteCapabilities.codecs)) { continue; } } rCodec = JSON.parse(JSON.stringify(rCodec)); // deepcopy // number of channels is the highest common number of channels rCodec.numChannels = Math.min(lCodec.numChannels, rCodec.numChannels); // push rCodec so we reply with offerer payload type commonCapabilities.codecs.push(rCodec); // determine common feedback mechanisms rCodec.rtcpFeedback = rCodec.rtcpFeedback.filter(function(fb) { for (var j = 0; j < lCodec.rtcpFeedback.length; j++) { if (lCodec.rtcpFeedback[j].type === fb.type && lCodec.rtcpFeedback[j].parameter === fb.parameter) { return true; } } return false; }); // FIXME: also need to determine .parameters // see https://github.com/openpeer/ortc/issues/569 break; } } }); localCapabilities.headerExtensions.forEach(function(lHeaderExtension) { for (var i = 0; i < remoteCapabilities.headerExtensions.length; i++) { var rHeaderExtension = remoteCapabilities.headerExtensions[i]; if (lHeaderExtension.uri === rHeaderExtension.uri) { commonCapabilities.headerExtensions.push(rHeaderExtension); break; } } }); // FIXME: fecMechanisms return commonCapabilities; } // is action=setLocalDescription with type allowed in signalingState function isActionAllowedInSignalingState(action, type, signalingState) { return { offer: { setLocalDescription: ['stable', 'have-local-offer'], setRemoteDescription: ['stable', 'have-remote-offer'] }, answer: { setLocalDescription: ['have-remote-offer', 'have-local-pranswer'], setRemoteDescription: ['have-local-offer', 'have-remote-pranswer'] } }[type][action].indexOf(signalingState) !== -1; } function maybeAddCandidate(iceTransport, candidate) { // Edge's internal representation adds some fields therefore // not all fieldѕ are taken into account. var alreadyAdded = iceTransport.getRemoteCandidates() .find(function(remoteCandidate) { return candidate.foundation === remoteCandidate.foundation && candidate.ip === remoteCandidate.ip && candidate.port === remoteCandidate.port && candidate.priority === remoteCandidate.priority && candidate.protocol === remoteCandidate.protocol && candidate.type === remoteCandidate.type; }); if (!alreadyAdded) { iceTransport.addRemoteCandidate(candidate); } return !alreadyAdded; } function makeError(name, description) { var e = new Error(description); e.name = name; // legacy error codes from https://heycam.github.io/webidl/#idl-DOMException-error-names e.code = { NotSupportedError: 9, InvalidStateError: 11, InvalidAccessError: 15, TypeError: undefined, OperationError: undefined }[name]; return e; } var rtcpeerconnection = function(window, edgeVersion) { // https://w3c.github.io/mediacapture-main/#mediastream // Helper function to add the track to the stream and // dispatch the event ourselves. function addTrackToStreamAndFireEvent(track, stream) { stream.addTrack(track); stream.dispatchEvent(new window.MediaStreamTrackEvent('addtrack', {track: track})); } function removeTrackFromStreamAndFireEvent(track, stream) { stream.removeTrack(track); stream.dispatchEvent(new window.MediaStreamTrackEvent('removetrack', {track: track})); } function fireAddTrack(pc, track, receiver, streams) { var trackEvent = new Event('track'); trackEvent.track = track; trackEvent.receiver = receiver; trackEvent.transceiver = {receiver: receiver}; trackEvent.streams = streams; window.setTimeout(function() { pc._dispatchEvent('track', trackEvent); }); } var RTCPeerConnection = function(config) { var pc = this; var _eventTarget = document.createDocumentFragment(); ['addEventListener', 'removeEventListener', 'dispatchEvent'] .forEach(function(method) { pc[method] = _eventTarget[method].bind(_eventTarget); }); this.canTrickleIceCandidates = null; this.needNegotiation = false; this.localStreams = []; this.remoteStreams = []; this._localDescription = null; this._remoteDescription = null; this.signalingState = 'stable'; this.iceConnectionState = 'new'; this.connectionState = 'new'; this.iceGatheringState = 'new'; config = JSON.parse(JSON.stringify(config || {})); this.usingBundle = config.bundlePolicy === 'max-bundle'; if (config.rtcpMuxPolicy === 'negotiate') { throw(makeError('NotSupportedError', 'rtcpMuxPolicy \'negotiate\' is not supported')); } else if (!config.rtcpMuxPolicy) { config.rtcpMuxPolicy = 'require'; } switch (config.iceTransportPolicy) { case 'all': case 'relay': break; default: config.iceTransportPolicy = 'all'; break; } switch (config.bundlePolicy) { case 'balanced': case 'max-compat': case 'max-bundle': break; default: config.bundlePolicy = 'balanced'; break; } config.iceServers = filterIceServers(config.iceServers || [], edgeVersion); this._iceGatherers = []; if (config.iceCandidatePoolSize) { for (var i = config.iceCandidatePoolSize; i > 0; i--) { this._iceGatherers.push(new window.RTCIceGatherer({ iceServers: config.iceServers, gatherPolicy: config.iceTransportPolicy })); } } else { config.iceCandidatePoolSize = 0; } this._config = config; // per-track iceGathers, iceTransports, dtlsTransports, rtpSenders, ... // everything that is needed to describe a SDP m-line. this.transceivers = []; this._sdpSessionId = sdp.generateSessionId(); this._sdpSessionVersion = 0; this._dtlsRole = undefined; // role for a=setup to use in answers. this._isClosed = false; }; Object.defineProperty(RTCPeerConnection.prototype, 'localDescription', { configurable: true, get: function() { return this._localDescription; } }); Object.defineProperty(RTCPeerConnection.prototype, 'remoteDescription', { configurable: true, get: function() { return this._remoteDescription; } }); // set up event handlers on prototype RTCPeerConnection.prototype.onicecandidate = null; RTCPeerConnection.prototype.onaddstream = null; RTCPeerConnection.prototype.ontrack = null; RTCPeerConnection.prototype.onremovestream = null; RTCPeerConnection.prototype.onsignalingstatechange = null; RTCPeerConnection.prototype.oniceconnectionstatechange = null; RTCPeerConnection.prototype.onconnectionstatechange = null; RTCPeerConnection.prototype.onicegatheringstatechange = null; RTCPeerConnection.prototype.onnegotiationneeded = null; RTCPeerConnection.prototype.ondatachannel = null; RTCPeerConnection.prototype._dispatchEvent = function(name, event) { if (this._isClosed) { return; } this.dispatchEvent(event); if (typeof this['on' + name] === 'function') { this['on' + name](event); } }; RTCPeerConnection.prototype._emitGatheringStateChange = function() { var event = new Event('icegatheringstatechange'); this._dispatchEvent('icegatheringstatechange', event); }; RTCPeerConnection.prototype.getConfiguration = function() { return this._config; }; RTCPeerConnection.prototype.getLocalStreams = function() { return this.localStreams; }; RTCPeerConnection.prototype.getRemoteStreams = function() { return this.remoteStreams; }; // internal helper to create a transceiver object. // (which is not yet the same as the WebRTC 1.0 transceiver) RTCPeerConnection.prototype._createTransceiver = function(kind, doNotAdd) { var hasBundleTransport = this.transceivers.length > 0; var transceiver = { track: null, iceGatherer: null, iceTransport: null, dtlsTransport: null, localCapabilities: null, remoteCapabilities: null, rtpSender: null, rtpReceiver: null, kind: kind, mid: null, sendEncodingParameters: null, recvEncodingParameters: null, stream: null, associatedRemoteMediaStreams: [], wantReceive: true }; if (this.usingBundle && hasBundleTransport) { transceiver.iceTransport = this.transceivers[0].iceTransport; transceiver.dtlsTransport = this.transceivers[0].dtlsTransport; } else { var transports = this._createIceAndDtlsTransports(); transceiver.iceTransport = transports.iceTransport; transceiver.dtlsTransport = transports.dtlsTransport; } if (!doNotAdd) { this.transceivers.push(transceiver); } return transceiver; }; RTCPeerConnection.prototype.addTrack = function(track, stream) { if (this._isClosed) { throw makeError('InvalidStateError', 'Attempted to call addTrack on a closed peerconnection.'); } var alreadyExists = this.transceivers.find(function(s) { return s.track === track; }); if (alreadyExists) { throw makeError('InvalidAccessError', 'Track already exists.'); } var transceiver; for (var i = 0; i < this.transceivers.length; i++) { if (!this.transceivers[i].track && this.transceivers[i].kind === track.kind) { transceiver = this.transceivers[i]; } } if (!transceiver) { transceiver = this._createTransceiver(track.kind); } this._maybeFireNegotiationNeeded(); if (this.localStreams.indexOf(stream) === -1) { this.localStreams.push(stream); } transceiver.track = track; transceiver.stream = stream; transceiver.rtpSender = new window.RTCRtpSender(track, transceiver.dtlsTransport); return transceiver.rtpSender; }; RTCPeerConnection.prototype.addStream = function(stream) { var pc = this; if (edgeVersion >= 15025) { stream.getTracks().forEach(function(track) { pc.addTrack(track, stream); }); } else { // Clone is necessary for local demos mostly, attaching directly // to two different senders does not work (build 10547). // Fixed in 15025 (or earlier) var clonedStream = stream.clone(); stream.getTracks().forEach(function(track, idx) { var clonedTrack = clonedStream.getTracks()[idx]; track.addEventListener('enabled', function(event) { clonedTrack.enabled = event.enabled; }); }); clonedStream.getTracks().forEach(function(track) { pc.addTrack(track, clonedStream); }); } }; RTCPeerConnection.prototype.removeTrack = function(sender) { if (this._isClosed) { throw makeError('InvalidStateError', 'Attempted to call removeTrack on a closed peerconnection.'); } if (!(sender instanceof window.RTCRtpSender)) { throw new TypeError('Argument 1 of RTCPeerConnection.removeTrack ' + 'does not implement interface RTCRtpSender.'); } var transceiver = this.transceivers.find(function(t) { return t.rtpSender === sender; }); if (!transceiver) { throw makeError('InvalidAccessError', 'Sender was not created by this connection.'); } var stream = transceiver.stream; transceiver.rtpSender.stop(); transceiver.rtpSender = null; transceiver.track = null; transceiver.stream = null; // remove the stream from the set of local streams var localStreams = this.transceivers.map(function(t) { return t.stream; }); if (localStreams.indexOf(stream) === -1 && this.localStreams.indexOf(stream) > -1) { this.localStreams.splice(this.localStreams.indexOf(stream), 1); } this._maybeFireNegotiationNeeded(); }; RTCPeerConnection.prototype.removeStream = function(stream) { var pc = this; stream.getTracks().forEach(function(track) { var sender = pc.getSenders().find(function(s) { return s.track === track; }); if (sender) { pc.removeTrack(sender); } }); }; RTCPeerConnection.prototype.getSenders = function() { return this.transceivers.filter(function(transceiver) { return !!transceiver.rtpSender; }) .map(function(transceiver) { return transceiver.rtpSender; }); }; RTCPeerConnection.prototype.getReceivers = function() { return this.transceivers.filter(function(transceiver) { return !!transceiver.rtpReceiver; }) .map(function(transceiver) { return transceiver.rtpReceiver; }); }; RTCPeerConnection.prototype._createIceGatherer = function(sdpMLineIndex, usingBundle) { var pc = this; if (usingBundle && sdpMLineIndex > 0) { return this.transceivers[0].iceGatherer; } else if (this._iceGatherers.length) { return this._iceGatherers.shift(); } var iceGatherer = new window.RTCIceGatherer({ iceServers: this._config.iceServers, gatherPolicy: this._config.iceTransportPolicy }); Object.defineProperty(iceGatherer, 'state', {value: 'new', writable: true} ); this.transceivers[sdpMLineIndex].bufferedCandidateEvents = []; this.transceivers[sdpMLineIndex].bufferCandidates = function(event) { var end = !event.candidate || Object.keys(event.candidate).length === 0; // polyfill since RTCIceGatherer.state is not implemented in // Edge 10547 yet. iceGatherer.state = end ? 'completed' : 'gathering'; if (pc.transceivers[sdpMLineIndex].bufferedCandidateEvents !== null) { pc.transceivers[sdpMLineIndex].bufferedCandidateEvents.push(event); } }; iceGatherer.addEventListener('localcandidate', this.transceivers[sdpMLineIndex].bufferCandidates); return iceGatherer; }; // start gathering from an RTCIceGatherer. RTCPeerConnection.prototype._gather = function(mid, sdpMLineIndex) { var pc = this; var iceGatherer = this.transceivers[sdpMLineIndex].iceGatherer; if (iceGatherer.onlocalcandidate) { return; } var bufferedCandidateEvents = this.transceivers[sdpMLineIndex].bufferedCandidateEvents; this.transceivers[sdpMLineIndex].bufferedCandidateEvents = null; iceGatherer.removeEventListener('localcandidate', this.transceivers[sdpMLineIndex].bufferCandidates); iceGatherer.onlocalcandidate = function(evt) { if (pc.usingBundle && sdpMLineIndex > 0) { // if we know that we use bundle we can drop candidates with // ѕdpMLineIndex > 0. If we don't do this then our state gets // confused since we dispose the extra ice gatherer. return; } var event = new Event('icecandidate'); event.candidate = {sdpMid: mid, sdpMLineIndex: sdpMLineIndex}; var cand = evt.candidate; // Edge emits an empty object for RTCIceCandidateComplete‥ var end = !cand || Object.keys(cand).length === 0; if (end) { // polyfill since RTCIceGatherer.state is not implemented in // Edge 10547 yet. if (iceGatherer.state === 'new' || iceGatherer.state === 'gathering') { iceGatherer.state = 'completed'; } } else { if (iceGatherer.state === 'new') { iceGatherer.state = 'gathering'; } // RTCIceCandidate doesn't have a component, needs to be added cand.component = 1; // also the usernameFragment. TODO: update SDP to take both variants. cand.ufrag = iceGatherer.getLocalParameters().usernameFragment; var serializedCandidate = sdp.writeCandidate(cand); event.candidate = Object.assign(event.candidate, sdp.parseCandidate(serializedCandidate)); event.candidate.candidate = serializedCandidate; event.candidate.toJSON = function() { return { candidate: event.candidate.candidate, sdpMid: event.candidate.sdpMid, sdpMLineIndex: event.candidate.sdpMLineIndex, usernameFragment: event.candidate.usernameFragment }; }; } // update local description. var sections = sdp.getMediaSections(pc._localDescription.sdp); if (!end) { sections[event.candidate.sdpMLineIndex] += 'a=' + event.candidate.candidate + '\r\n'; } else { sections[event.candidate.sdpMLineIndex] += 'a=end-of-candidates\r\n'; } pc._localDescription.sdp = sdp.getDescription(pc._localDescription.sdp) + sections.join(''); var complete = pc.transceivers.every(function(transceiver) { return transceiver.iceGatherer && transceiver.iceGatherer.state === 'completed'; }); if (pc.iceGatheringState !== 'gathering') { pc.iceGatheringState = 'gathering'; pc._emitGatheringStateChange(); } // Emit candidate. Also emit null candidate when all gatherers are // complete. if (!end) { pc._dispatchEvent('icecandidate', event); } if (complete) { pc._dispatchEvent('icecandidate', new Event('icecandidate')); pc.iceGatheringState = 'complete'; pc._emitGatheringStateChange(); } }; // emit already gathered candidates. window.setTimeout(function() { bufferedCandidateEvents.forEach(function(e) { iceGatherer.onlocalcandidate(e); }); }, 0); }; // Create ICE transport and DTLS transport. RTCPeerConnection.prototype._createIceAndDtlsTransports = function() { var pc = this; var iceTransport = new window.RTCIceTransport(null); iceTransport.onicestatechange = function() { pc._updateIceConnectionState(); pc._updateConnectionState(); }; var dtlsTransport = new window.RTCDtlsTransport(iceTransport); dtlsTransport.ondtlsstatechange = function() { pc._updateConnectionState(); }; dtlsTransport.onerror = function() { // onerror does not set state to failed by itself. Object.defineProperty(dtlsTransport, 'state', {value: 'failed', writable: true}); pc._updateConnectionState(); }; return { iceTransport: iceTransport, dtlsTransport: dtlsTransport }; }; // Destroy ICE gatherer, ICE transport and DTLS transport. // Without triggering the callbacks. RTCPeerConnection.prototype._disposeIceAndDtlsTransports = function( sdpMLineIndex) { var iceGatherer = this.transceivers[sdpMLineIndex].iceGatherer; if (iceGatherer) { delete iceGatherer.onlocalcandidate; delete this.transceivers[sdpMLineIndex].iceGatherer; } var iceTransport = this.transceivers[sdpMLineIndex].iceTransport; if (iceTransport) { delete iceTransport.onicestatechange; delete this.transceivers[sdpMLineIndex].iceTransport; } var dtlsTransport = this.transceivers[sdpMLineIndex].dtlsTransport; if (dtlsTransport) { delete dtlsTransport.ondtlsstatechange; delete dtlsTransport.onerror; delete this.transceivers[sdpMLineIndex].dtlsTransport; } }; // Start the RTP Sender and Receiver for a transceiver. RTCPeerConnection.prototype._transceive = function(transceiver, send, recv) { var params = getCommonCapabilities(transceiver.localCapabilities, transceiver.remoteCapabilities); if (send && transceiver.rtpSender) { params.encodings = transceiver.sendEncodingParameters; params.rtcp = { cname: sdp.localCName, compound: transceiver.rtcpParameters.compound }; if (transceiver.recvEncodingParameters.length) { params.rtcp.ssrc = transceiver.recvEncodingParameters[0].ssrc; } transceiver.rtpSender.send(params); } if (recv && transceiver.rtpReceiver && params.codecs.length > 0) { // remove RTX field in Edge 14942 if (transceiver.kind === 'video' && transceiver.recvEncodingParameters && edgeVersion < 15019) { transceiver.recvEncodingParameters.forEach(function(p) { delete p.rtx; }); } if (transceiver.recvEncodingParameters.length) { params.encodings = transceiver.recvEncodingParameters; } else { params.encodings = [{}]; } params.rtcp = { compound: transceiver.rtcpParameters.compound }; if (transceiver.rtcpParameters.cname) { params.rtcp.cname = transceiver.rtcpParameters.cname; } if (transceiver.sendEncodingParameters.length) { params.rtcp.ssrc = transceiver.sendEncodingParameters[0].ssrc; } transceiver.rtpReceiver.receive(params); } }; RTCPeerConnection.prototype.setLocalDescription = function(description) { var pc = this; // Note: pranswer is not supported. if (['offer', 'answer'].indexOf(description.type) === -1) { return Promise.reject(makeError('TypeError', 'Unsupported type "' + description.type + '"')); } if (!isActionAllowedInSignalingState('setLocalDescription', description.type, pc.signalingState) || pc._isClosed) { return Promise.reject(makeError('InvalidStateError', 'Can not set local ' + description.type + ' in state ' + pc.signalingState)); } var sections; var sessionpart; if (description.type === 'offer') { // VERY limited support for SDP munging. Limited to: // * changing the order of codecs sections = sdp.splitSections(description.sdp); sessionpart = sections.shift(); sections.forEach(function(mediaSection, sdpMLineIndex) { var caps = sdp.parseRtpParameters(mediaSection); pc.transceivers[sdpMLineIndex].localCapabilities = caps; }); pc.transceivers.forEach(function(transceiver, sdpMLineIndex) { pc._gather(transceiver.mid, sdpMLineIndex); }); } else if (description.type === 'answer') { sections = sdp.splitSections(pc._remoteDescription.sdp); sessionpart = sections.shift(); var isIceLite = sdp.matchPrefix(sessionpart, 'a=ice-lite').length > 0; sections.forEach(function(mediaSection, sdpMLineIndex) { var transceiver = pc.transceivers[sdpMLineIndex]; var iceGatherer = transceiver.iceGatherer; var iceTransport = transceiver.iceTransport; var dtlsTransport = transceiver.dtlsTransport; var localCapabilities = transceiver.localCapabilities; var remoteCapabilities = transceiver.remoteCapabilities; // treat bundle-only as not-rejected. var rejected = sdp.isRejected(mediaSection) && sdp.matchPrefix(mediaSection, 'a=bundle-only').length === 0; if (!rejected && !transceiver.rejected) { var remoteIceParameters = sdp.getIceParameters( mediaSection, sessionpart); var remoteDtlsParameters = sdp.getDtlsParameters( mediaSection, sessionpart); if (isIceLite) { remoteDtlsParameters.role = 'server'; } if (!pc.usingBundle || sdpMLineIndex === 0) { pc._gather(transceiver.mid, sdpMLineIndex); if (iceTransport.state === 'new') { iceTransport.start(iceGatherer, remoteIceParameters, isIceLite ? 'controlling' : 'controlled'); } if (dtlsTransport.state === 'new') { dtlsTransport.start(remoteDtlsParameters); } } // Calculate intersection of capabilities. var params = getCommonCapabilities(localCapabilities, remoteCapabilities); // Start the RTCRtpSender. The RTCRtpReceiver for this // transceiver has already been started in setRemoteDescription. pc._transceive(transceiver, params.codecs.length > 0, false); } }); } pc._localDescription = { type: description.type, sdp: description.sdp }; if (description.type === 'offer') { pc._updateSignalingState('have-local-offer'); } else { pc._updateSignalingState('stable'); } return Promise.resolve(); }; RTCPeerConnection.prototype.setRemoteDescription = function(description) { var pc = this; // Note: pranswer is not supported. if (['offer', 'answer'].indexOf(description.type) === -1) { return Promise.reject(makeError('TypeError', 'Unsupported type "' + description.type + '"')); } if (!isActionAllowedInSignalingState('setRemoteDescription', description.type, pc.signalingState) || pc._isClosed) { return Promise.reject(makeError('InvalidStateError', 'Can not set remote ' + description.type + ' in state ' + pc.signalingState)); } var streams = {}; pc.remoteStreams.forEach(function(stream) { streams[stream.id] = stream; }); var receiverList = []; var sections = sdp.splitSections(description.sdp); var sessionpart = sections.shift(); var isIceLite = sdp.matchPrefix(sessionpart, 'a=ice-lite').length > 0; var usingBundle = sdp.matchPrefix(sessionpart, 'a=group:BUNDLE ').length > 0; pc.usingBundle = usingBundle; var iceOptions = sdp.matchPrefix(sessionpart, 'a=ice-options:')[0]; if (iceOptions) { pc.canTrickleIceCandidates = iceOptions.substr(14).split(' ') .indexOf('trickle') >= 0; } else { pc.canTrickleIceCandidates = false; } sections.forEach(function(mediaSection, sdpMLineIndex) { var lines = sdp.splitLines(mediaSection); var kind = sdp.getKind(mediaSection); // treat bundle-only as not-rejected. var rejected = sdp.isRejected(mediaSection) && sdp.matchPrefix(mediaSection, 'a=bundle-only').length === 0; var protocol = lines[0].substr(2).split(' ')[2]; var direction = sdp.getDirection(mediaSection, sessionpart); var remoteMsid = sdp.parseMsid(mediaSection); var mid = sdp.getMid(mediaSection) || sdp.generateIdentifier(); // Reject datachannels which are not implemented yet. if (rejected || (kind === 'application' && (protocol === 'DTLS/SCTP' || protocol === 'UDP/DTLS/SCTP'))) { // TODO: this is dangerous in the case where a non-rejected m-line // becomes rejected. pc.transceivers[sdpMLineIndex] = { mid: mid, kind: kind, protocol: protocol, rejected: true }; return; } if (!rejected && pc.transceivers[sdpMLineIndex] && pc.transceivers[sdpMLineIndex].rejected) { // recycle a rejected transceiver. pc.transceivers[sdpMLineIndex] = pc._createTransceiver(kind, true); } var transceiver; var iceGatherer; var iceTransport; var dtlsTransport; var rtpReceiver; var sendEncodingParameters; var recvEncodingParameters; var localCapabilities; var track; // FIXME: ensure the mediaSection has rtcp-mux set. var remoteCapabilities = sdp.parseRtpParameters(mediaSection); var remoteIceParameters; var remoteDtlsParameters; if (!rejected) { remoteIceParameters = sdp.getIceParameters(mediaSection, sessionpart); remoteDtlsParameters = sdp.getDtlsParameters(mediaSection, sessionpart); remoteDtlsParameters.role = 'client'; } recvEncodingParameters = sdp.parseRtpEncodingParameters(mediaSection); var rtcpParameters = sdp.parseRtcpParameters(mediaSection); var isComplete = sdp.matchPrefix(mediaSection, 'a=end-of-candidates', sessionpart).length > 0; var cands = sdp.matchPrefix(mediaSection, 'a=candidate:') .map(function(cand) { return sdp.parseCandidate(cand); }) .filter(function(cand) { return cand.component === 1; }); // Check if we can use BUNDLE and dispose transports. if ((description.type === 'offer' || description.type === 'answer') && !rejected && usingBundle && sdpMLineIndex > 0 && pc.transceivers[sdpMLineIndex]) { pc._disposeIceAndDtlsTransports(sdpMLineIndex); pc.transceivers[sdpMLineIndex].iceGatherer = pc.transceivers[0].iceGatherer; pc.transceivers[sdpMLineIndex].iceTransport = pc.transceivers[0].iceTransport; pc.transceivers[sdpMLineIndex].dtlsTransport = pc.transceivers[0].dtlsTransport; if (pc.transceivers[sdpMLineIndex].rtpSender) { pc.transceivers[sdpMLineIndex].rtpSender.setTransport( pc.transceivers[0].dtlsTransport); } if (pc.transceivers[sdpMLineIndex].rtpReceiver) { pc.transceivers[sdpMLineIndex].rtpReceiver.setTransport( pc.transceivers[0].dtlsTransport); } } if (description.type === 'offer' && !rejected) { transceiver = pc.transceivers[sdpMLineIndex] || pc._createTransceiver(kind); transceiver.mid = mid; if (!transceiver.iceGatherer) { transceiver.iceGatherer = pc._createIceGatherer(sdpMLineIndex, usingBundle); } if (cands.length && transceiver.iceTransport.state === 'new') { if (isComplete && (!usingBundle || sdpMLineIndex === 0)) { transceiver.iceTransport.setRemoteCandidates(cands); } else { cands.forEach(function(candidate) { maybeAddCandidate(transceiver.iceTransport, candidate); }); } } localCapabilities = window.RTCRtpReceiver.getCapabilities(kind); // filter RTX until additional stuff needed for RTX is implemented // in adapter.js if (edgeVersion < 15019) { localCapabilities.codecs = localCapabilities.codecs.filter( function(codec) { return codec.name !== 'rtx'; }); } sendEncodingParameters = transceiver.sendEncodingParameters || [{ ssrc: (2 * sdpMLineIndex + 2) * 1001 }]; // TODO: rewrite to use http://w3c.github.io/webrtc-pc/#set-associated-remote-streams var isNewTrack = false; if (direction === 'sendrecv' || direction === 'sendonly') { isNewTrack = !transceiver.rtpReceiver; rtpReceiver = transceiver.rtpReceiver || new window.RTCRtpReceiver(transceiver.dtlsTransport, kind); if (isNewTrack) { var stream; track = rtpReceiver.track; // FIXME: does not work with Plan B. if (remoteMsid && remoteMsid.stream === '-') ; else if (remoteMsid) { if (!streams[remoteMsid.stream]) { streams[remoteMsid.stream] = new window.MediaStream(); Object.defineProperty(streams[remoteMsid.stream], 'id', { get: function() { return remoteMsid.stream; } }); } Object.defineProperty(track, 'id', { get: function() { return remoteMsid.track; } }); stream = streams[remoteMsid.stream]; } else { if (!streams.default) { streams.default = new window.MediaStream(); } stream = streams.default; } if (stream) { addTrackToStreamAndFireEvent(track, stream); transceiver.associatedRemoteMediaStreams.push(stream); } receiverList.push([track, rtpReceiver, stream]); } } else if (transceiver.rtpReceiver && transceiver.rtpReceiver.track) { transceiver.associatedRemoteMediaStreams.forEach(function(s) { var nativeTrack = s.getTracks().find(function(t) { return t.id === transceiver.rtpReceiver.track.id; }); if (nativeTrack) { removeTrackFromStreamAndFireEvent(nativeTrack, s); } }); transceiver.associatedRemoteMediaStreams = []; } transceiver.localCapabilities = localCapabilities; transceiver.remoteCapabilities = remoteCapabilities; transceiver.rtpReceiver = rtpReceiver; transceiver.rtcpParameters = rtcpParameters; transceiver.sendEncodingParameters = sendEncodingParameters; transceiver.recvEncodingParameters = recvEncodingParameters; // Start the RTCRtpReceiver now. The RTPSender is started in // setLocalDescription. pc._transceive(pc.transceivers[sdpMLineIndex], false, isNewTrack); } else if (description.type === 'answer' && !rejected) { transceiver = pc.transceivers[sdpMLineIndex]; iceGatherer = transceiver.iceGatherer; iceTransport = transceiver.iceTransport; dtlsTransport = transceiver.dtlsTransport; rtpReceiver = transceiver.rtpReceiver; sendEncodingParameters = transceiver.sendEncodingParameters; localCapabilities = transceiver.localCapabilities; pc.transceivers[sdpMLineIndex].recvEncodingParameters = recvEncodingParameters; pc.transceivers[sdpMLineIndex].remoteCapabilities = remoteCapabilities; pc.transceivers[sdpMLineIndex].rtcpParameters = rtcpParameters; if (cands.length && iceTransport.state === 'new') { if ((isIceLite || isComplete) && (!usingBundle || sdpMLineIndex === 0)) { iceTransport.setRemoteCandidates(cands); } else { cands.forEach(function(candidate) { maybeAddCandidate(transceiver.iceTransport, candidate); }); } } if (!usingBundle || sdpMLineIndex === 0) { if (iceTransport.state === 'new') { iceTransport.start(iceGatherer, remoteIceParameters, 'controlling'); } if (dtlsTransport.state === 'new') { dtlsTransport.start(remoteDtlsParameters); } } // If the offer contained RTX but the answer did not, // remove RTX from sendEncodingParameters. var commonCapabilities = getCommonCapabilities( transceiver.localCapabilities, transceiver.remoteCapabilities); var hasRtx = commonCapabilities.codecs.filter(function(c) { return c.name.toLowerCase() === 'rtx'; }).length; if (!hasRtx && transceiver.sendEncodingParameters[0].rtx) { delete transceiver.sendEncodingParameters[0].rtx; } pc._transceive(transceiver, direction === 'sendrecv' || direction === 'recvonly', direction === 'sendrecv' || direction === 'sendonly'); // TODO: rewrite to use http://w3c.github.io/webrtc-pc/#set-associated-remote-streams if (rtpReceiver && (direction === 'sendrecv' || direction === 'sendonly')) { track = rtpReceiver.track; if (remoteMsid) { if (!streams[remoteMsid.stream]) { streams[remoteMsid.stream] = new window.MediaStream(); } addTrackToStreamAndFireEvent(track, streams[remoteMsid.stream]); receiverList.push([track, rtpReceiver, streams[remoteMsid.stream]]); } else { if (!streams.default) { streams.default = new window.MediaStream(); } addTrackToStreamAndFireEvent(track, streams.default); receiverList.push([track, rtpReceiver, streams.default]); } } else { // FIXME: actually the receiver should be created later. delete transceiver.rtpReceiver; } } }); if (pc._dtlsRole === undefined) { pc._dtlsRole = description.type === 'offer' ? 'active' : 'passive'; } pc._remoteDescription = { type: description.type, sdp: description.sdp }; if (description.type === 'offer') { pc._updateSignalingState('have-remote-offer'); } else { pc._updateSignalingState('stable'); } Object.keys(streams).forEach(function(sid) { var stream = streams[sid]; if (stream.getTracks().length) { if (pc.remoteStreams.indexOf(stream) === -1) { pc.remoteStreams.push(stream); var event = new Event('addstream'); event.stream = stream; window.setTimeout(function() { pc._dispatchEvent('addstream', event); }); } receiverList.forEach(function(item) { var track = item[0]; var receiver = item[1]; if (stream.id !== item[2].id) { return; } fireAddTrack(pc, track, receiver, [stream]); }); } }); receiverList.forEach(function(item) { if (item[2]) { return; } fireAddTrack(pc, item[0], item[1], []); }); // check whether addIceCandidate({}) was called within four seconds after // setRemoteDescription. window.setTimeout(function() { if (!(pc && pc.transceivers)) { return; } pc.transceivers.forEach(function(transceiver) { if (transceiver.iceTransport && transceiver.iceTransport.state === 'new' && transceiver.iceTransport.getRemoteCandidates().length > 0) { console.warn('Timeout for addRemoteCandidate. Consider sending ' + 'an end-of-candidates notification'); transceiver.iceTransport.addRemoteCandidate({}); } }); }, 4000); return Promise.resolve(); }; RTCPeerConnection.prototype.close = function() { this.transceivers.forEach(function(transceiver) { /* not yet if (transceiver.iceGatherer) { transceiver.iceGatherer.close(); } */ if (transceiver.iceTransport) { transceiver.iceTransport.stop(); } if (transceiver.dtlsTransport) { transceiver.dtlsTransport.stop(); } if (transceiver.rtpSender) { transceiver.rtpSender.stop(); } if (transceiver.rtpReceiver) { transceiver.rtpReceiver.stop(); } }); // FIXME: clean up tracks, local streams, remote streams, etc this._isClosed = true; this._updateSignalingState('closed'); }; // Update the signaling state. RTCPeerConnection.prototype._updateSignalingState = function(newState) { this.signalingState = newState; var event = new Event('signalingstatechange'); this._dispatchEvent('signalingstatechange', event); }; // Determine whether to fire the negotiationneeded event. RTCPeerConnection.prototype._maybeFireNegotiationNeeded = function() { var pc = this; if (this.signalingState !== 'stable' || this.needNegotiation === true) { return; } this.needNegotiation = true; window.setTimeout(function() { if (pc.needNegotiation) { pc.needNegotiation = false; var event = new Event('negotiationneeded'); pc._dispatchEvent('negotiationneeded', event); } }, 0); }; // Update the ice connection state. RTCPeerConnection.prototype._updateIceConnectionState = function() { var newState; var states = { 'new': 0, closed: 0, checking: 0, connected: 0, completed: 0, disconnected: 0, failed: 0 }; this.transceivers.forEach(function(transceiver) { if (transceiver.iceTransport && !transceiver.rejected) { states[transceiver.iceTransport.state]++; } }); newState = 'new'; if (states.failed > 0) { newState = 'failed'; } else if (states.checking > 0) { newState = 'checking'; } else if (states.disconnected > 0) { newState = 'disconnected'; } else if (states.new > 0) { newState = 'new'; } else if (states.connected > 0) { newState = 'connected'; } else if (states.completed > 0) { newState = 'completed'; } if (newState !== this.iceConnectionState) { this.iceConnectionState = newState; var event = new Event('iceconnectionstatechange'); this._dispatchEvent('iceconnectionstatechange', event); } }; // Update the connection state. RTCPeerConnection.prototype._updateConnectionState = function() { var newState; var states = { 'new': 0, closed: 0, connecting: 0, connected: 0, completed: 0, disconnected: 0, failed: 0 }; this.transceivers.forEach(function(transceiver) { if (transceiver.iceTransport && transceiver.dtlsTransport && !transceiver.rejected) { states[transceiver.iceTransport.state]++; states[transceiver.dtlsTransport.state]++; } }); // ICETransport.completed and connected are the same for this purpose. states.connected += states.completed; newState = 'new'; if (states.failed > 0) { newState = 'failed'; } else if (states.connecting > 0) { newState = 'connecting'; } else if (states.disconnected > 0) { newState = 'disconnected'; } else if (states.new > 0) { newState = 'new'; } else if (states.connected > 0) { newState = 'connected'; } if (newState !== this.connectionState) { this.connectionState = newState; var event = new Event('connectionstatechange'); this._dispatchEvent('connectionstatechange', event); } }; RTCPeerConnection.prototype.createOffer = function() { var pc = this; if (pc._isClosed) { return Promise.reject(makeError('InvalidStateError', 'Can not call createOffer after close')); } var numAudioTracks = pc.transceivers.filter(function(t) { return t.kind === 'audio'; }).length; var numVideoTracks = pc.transceivers.filter(function(t) { return t.kind === 'video'; }).length; // Determine number of audio and video tracks we need to send/recv. var offerOptions = arguments[0]; if (offerOptions) { // Reject Chrome legacy constraints. if (offerOptions.mandatory || offerOptions.optional) { throw new TypeError( 'Legacy mandatory/optional constraints not supported.'); } if (offerOptions.offerToReceiveAudio !== undefined) { if (offerOptions.offerToReceiveAudio === true) { numAudioTracks = 1; } else if (offerOptions.offerToReceiveAudio === false) { numAudioTracks = 0; } else { numAudioTracks = offerOptions.offerToReceiveAudio; } } if (offerOptions.offerToReceiveVideo !== undefined) { if (offerOptions.offerToReceiveVideo === true) { numVideoTracks = 1; } else if (offerOptions.offerToReceiveVideo === false) { numVideoTracks = 0; } else { numVideoTracks = offerOptions.offerToReceiveVideo; } } } pc.transceivers.forEach(function(transceiver) { if (transceiver.kind === 'audio') { numAudioTracks--; if (numAudioTracks < 0) { transceiver.wantReceive = false; } } else if (transceiver.kind === 'video') { numVideoTracks--; if (numVideoTracks < 0) { transceiver.wantReceive = false; } } }); // Create M-lines for recvonly streams. while (numAudioTracks > 0 || numVideoTracks > 0) { if (numAudioTracks > 0) { pc._createTransceiver('audio'); numAudioTracks--; } if (numVideoTracks > 0) { pc._createTransceiver('video'); numVideoTracks--; } } var sdp$1 = sdp.writeSessionBoilerplate(pc._sdpSessionId, pc._sdpSessionVersion++); pc.transceivers.forEach(function(transceiver, sdpMLineIndex) { // For each track, create an ice gatherer, ice transport, // dtls transport, potentially rtpsender and rtpreceiver. var track = transceiver.track; var kind = transceiver.kind; var mid = transceiver.mid || sdp.generateIdentifier(); transceiver.mid = mid; if (!transceiver.iceGatherer) { transceiver.iceGatherer = pc._createIceGatherer(sdpMLineIndex, pc.usingBundle); } var localCapabilities = window.RTCRtpSender.getCapabilities(kind); // filter RTX until additional stuff needed for RTX is implemented // in adapter.js if (edgeVersion < 15019) { localCapabilities.codecs = localCapabilities.codecs.filter( function(codec) { return codec.name !== 'rtx'; }); } localCapabilities.codecs.forEach(function(codec) { // work around https://bugs.chromium.org/p/webrtc/issues/detail?id=6552 // by adding level-asymmetry-allowed=1 if (codec.name === 'H264' && codec.parameters['level-asymmetry-allowed'] === undefined) { codec.parameters['level-asymmetry-allowed'] = '1'; } // for subsequent offers, we might have to re-use the payload // type of the last offer. if (transceiver.remoteCapabilities && transceiver.remoteCapabilities.codecs) { transceiver.remoteCapabilities.codecs.forEach(function(remoteCodec) { if (codec.name.toLowerCase() === remoteCodec.name.toLowerCase() && codec.clockRate === remoteCodec.clockRate) { codec.preferredPayloadType = remoteCodec.payloadType; } }); } }); localCapabilities.headerExtensions.forEach(function(hdrExt) { var remoteExtensions = transceiver.remoteCapabilities && transceiver.remoteCapabilities.headerExtensions || []; remoteExtensions.forEach(function(rHdrExt) { if (hdrExt.uri === rHdrExt.uri) { hdrExt.id = rHdrExt.id; } }); }); // generate an ssrc now, to be used later in rtpSender.send var sendEncodingParameters = transceiver.sendEncodingParameters || [{ ssrc: (2 * sdpMLineIndex + 1) * 1001 }]; if (track) { // add RTX if (edgeVersion >= 15019 && kind === 'video' && !sendEncodingParameters[0].rtx) { sendEncodingParameters[0].rtx = { ssrc: sendEncodingParameters[0].ssrc + 1 }; } } if (transceiver.wantReceive) { transceiver.rtpReceiver = new window.RTCRtpReceiver( transceiver.dtlsTransport, kind); } transceiver.localCapabilities = localCapabilities; transceiver.sendEncodingParameters = sendEncodingParameters; }); // always offer BUNDLE and dispose on return if not supported. if (pc._config.bundlePolicy !== 'max-compat') { sdp$1 += 'a=group:BUNDLE ' + pc.transceivers.map(function(t) { return t.mid; }).join(' ') + '\r\n'; } sdp$1 += 'a=ice-options:trickle\r\n'; pc.transceivers.forEach(function(transceiver, sdpMLineIndex) { sdp$1 += writeMediaSection(transceiver, transceiver.localCapabilities, 'offer', transceiver.stream, pc._dtlsRole); sdp$1 += 'a=rtcp-rsize\r\n'; if (transceiver.iceGatherer && pc.iceGatheringState !== 'new' && (sdpMLineIndex === 0 || !pc.usingBundle)) { transceiver.iceGatherer.getLocalCandidates().forEach(function(cand) { cand.component = 1; sdp$1 += 'a=' + sdp.writeCandidate(cand) + '\r\n'; }); if (transceiver.iceGatherer.state === 'completed') { sdp$1 += 'a=end-of-candidates\r\n'; } } }); var desc = new window.RTCSessionDescription({ type: 'offer', sdp: sdp$1 }); return Promise.resolve(desc); }; RTCPeerConnection.prototype.createAnswer = function() { var pc = this; if (pc._isClosed) { return Promise.reject(makeError('InvalidStateError', 'Can not call createAnswer after close')); } if (!(pc.signalingState === 'have-remote-offer' || pc.signalingState === 'have-local-pranswer')) { return Promise.reject(makeError('InvalidStateError', 'Can not call createAnswer in signalingState ' + pc.signalingState)); } var sdp$1 = sdp.writeSessionBoilerplate(pc._sdpSessionId, pc._sdpSessionVersion++); if (pc.usingBundle) { sdp$1 += 'a=group:BUNDLE ' + pc.transceivers.map(function(t) { return t.mid; }).join(' ') + '\r\n'; } sdp$1 += 'a=ice-options:trickle\r\n'; var mediaSectionsInOffer = sdp.getMediaSections( pc._remoteDescription.sdp).length; pc.transceivers.forEach(function(transceiver, sdpMLineIndex) { if (sdpMLineIndex + 1 > mediaSectionsInOffer) { return; } if (transceiver.rejected) { if (transceiver.kind === 'application') { if (transceiver.protocol === 'DTLS/SCTP') { // legacy fmt sdp$1 += 'm=application 0 DTLS/SCTP 5000\r\n'; } else { sdp$1 += 'm=application 0 ' + transceiver.protocol + ' webrtc-datachannel\r\n'; } } else if (transceiver.kind === 'audio') { sdp$1 += 'm=audio 0 UDP/TLS/RTP/SAVPF 0\r\n' + 'a=rtpmap:0 PCMU/8000\r\n'; } else if (transceiver.kind === 'video') { sdp$1 += 'm=video 0 UDP/TLS/RTP/SAVPF 120\r\n' + 'a=rtpmap:120 VP8/90000\r\n'; } sdp$1 += 'c=IN IP4 0.0.0.0\r\n' + 'a=inactive\r\n' + 'a=mid:' + transceiver.mid + '\r\n'; return; } // FIXME: look at direction. if (transceiver.stream) { var localTrack; if (transceiver.kind === 'audio') { localTrack = transceiver.stream.getAudioTracks()[0]; } else if (transceiver.kind === 'video') { localTrack = transceiver.stream.getVideoTracks()[0]; } if (localTrack) { // add RTX if (edgeVersion >= 15019 && transceiver.kind === 'video' && !transceiver.sendEncodingParameters[0].rtx) { transceiver.sendEncodingParameters[0].rtx = { ssrc: transceiver.sendEncodingParameters[0].ssrc + 1 }; } } } // Calculate intersection of capabilities. var commonCapabilities = getCommonCapabilities( transceiver.localCapabilities, transceiver.remoteCapabilities); var hasRtx = commonCapabilities.codecs.filter(function(c) { return c.name.toLowerCase() === 'rtx'; }).length; if (!hasRtx && transceiver.sendEncodingParameters[0].rtx) { delete transceiver.sendEncodingParameters[0].rtx; } sdp$1 += writeMediaSection(transceiver, commonCapabilities, 'answer', transceiver.stream, pc._dtlsRole); if (transceiver.rtcpParameters && transceiver.rtcpParameters.reducedSize) { sdp$1 += 'a=rtcp-rsize\r\n'; } }); var desc = new window.RTCSessionDescription({ type: 'answer', sdp: sdp$1 }); return Promise.resolve(desc); }; RTCPeerConnection.prototype.addIceCandidate = function(candidate) { var pc = this; var sections; if (candidate && !(candidate.sdpMLineIndex !== undefined || candidate.sdpMid)) { return Promise.reject(new TypeError('sdpMLineIndex or sdpMid required')); } // TODO: needs to go into ops queue. return new Promise(function(resolve, reject) { if (!pc._remoteDescription) { return reject(makeError('InvalidStateError', 'Can not add ICE candidate without a remote description')); } else if (!candidate || candidate.candidate === '') { for (var j = 0; j < pc.transceivers.length; j++) { if (pc.transceivers[j].rejected) { continue; } pc.transceivers[j].iceTransport.addRemoteCandidate({}); sections = sdp.getMediaSections(pc._remoteDescription.sdp); sections[j] += 'a=end-of-candidates\r\n'; pc._remoteDescription.sdp = sdp.getDescription(pc._remoteDescription.sdp) + sections.join(''); if (pc.usingBundle) { break; } } } else { var sdpMLineIndex = candidate.sdpMLineIndex; if (candidate.sdpMid) { for (var i = 0; i < pc.transceivers.length; i++) { if (pc.transceivers[i].mid === candidate.sdpMid) { sdpMLineIndex = i; break; } } } var transceiver = pc.transceivers[sdpMLineIndex]; if (transceiver) { if (transceiver.rejected) { return resolve(); } var cand = Object.keys(candidate.candidate).length > 0 ? sdp.parseCandidate(candidate.candidate) : {}; // Ignore Chrome's invalid candidates since Edge does not like them. if (cand.protocol === 'tcp' && (cand.port === 0 || cand.port === 9)) { return resolve(); } // Ignore RTCP candidates, we assume RTCP-MUX. if (cand.component && cand.component !== 1) { return resolve(); } // when using bundle, avoid adding candidates to the wrong // ice transport. And avoid adding candidates added in the SDP. if (sdpMLineIndex === 0 || (sdpMLineIndex > 0 && transceiver.iceTransport !== pc.transceivers[0].iceTransport)) { if (!maybeAddCandidate(transceiver.iceTransport, cand)) { return reject(makeError('OperationError', 'Can not add ICE candidate')); } } // update the remoteDescription. var candidateString = candidate.candidate.trim(); if (candidateString.indexOf('a=') === 0) { candidateString = candidateString.substr(2); } sections = sdp.getMediaSections(pc._remoteDescription.sdp); sections[sdpMLineIndex] += 'a=' + (cand.type ? candidateString : 'end-of-candidates') + '\r\n'; pc._remoteDescription.sdp = sdp.getDescription(pc._remoteDescription.sdp) + sections.join(''); } else { return reject(makeError('OperationError', 'Can not add ICE candidate')); } } resolve(); }); }; RTCPeerConnection.prototype.getStats = function(selector) { if (selector && selector instanceof window.MediaStreamTrack) { var senderOrReceiver = null; this.transceivers.forEach(function(transceiver) { if (transceiver.rtpSender && transceiver.rtpSender.track === selector) { senderOrReceiver = transceiver.rtpSender; } else if (transceiver.rtpReceiver && transceiver.rtpReceiver.track === selector) { senderOrReceiver = transceiver.rtpReceiver; } }); if (!senderOrReceiver) { throw makeError('InvalidAccessError', 'Invalid selector.'); } return senderOrReceiver.getStats(); } var promises = []; this.transceivers.forEach(function(transceiver) { ['rtpSender', 'rtpReceiver', 'iceGatherer', 'iceTransport', 'dtlsTransport'].forEach(function(method) { if (transceiver[method]) { promises.push(transceiver[method].getStats()); } }); }); return Promise.all(promises).then(function(allStats) { var results = new Map(); allStats.forEach(function(stats) { stats.forEach(function(stat) { results.set(stat.id, stat); }); }); return results; }); }; // fix low-level stat names and return Map instead of object. var ortcObjects = ['RTCRtpSender', 'RTCRtpReceiver', 'RTCIceGatherer', 'RTCIceTransport', 'RTCDtlsTransport']; ortcObjects.forEach(function(ortcObjectName) { var obj = window[ortcObjectName]; if (obj && obj.prototype && obj.prototype.getStats) { var nativeGetstats = obj.prototype.getStats; obj.prototype.getStats = function() { return nativeGetstats.apply(this) .then(function(nativeStats) { var mapStats = new Map(); Object.keys(nativeStats).forEach(function(id) { nativeStats[id].type = fixStatsType(nativeStats[id]); mapStats.set(id, nativeStats[id]); }); return mapStats; }); }; } }); // legacy callback shims. Should be moved to adapter.js some days. var methods = ['createOffer', 'createAnswer']; methods.forEach(function(method) { var nativeMethod = RTCPeerConnection.prototype[method]; RTCPeerConnection.prototype[method] = function() { var args = arguments; if (typeof args[0] === 'function' || typeof args[1] === 'function') { // legacy return nativeMethod.apply(this, [arguments[2]]) .then(function(description) { if (typeof args[0] === 'function') { args[0].apply(null, [description]); } }, function(error) { if (typeof args[1] === 'function') { args[1].apply(null, [error]); } }); } return nativeMethod.apply(this, arguments); }; }); methods = ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']; methods.forEach(function(method) { var nativeMethod = RTCPeerConnection.prototype[method]; RTCPeerConnection.prototype[method] = function() { var args = arguments; if (typeof args[1] === 'function' || typeof args[2] === 'function') { // legacy return nativeMethod.apply(this, arguments) .then(function() { if (typeof args[1] === 'function') { args[1].apply(null); } }, function(error) { if (typeof args[2] === 'function') { args[2].apply(null, [error]); } }); } return nativeMethod.apply(this, arguments); }; }); // getStats is special. It doesn't have a spec legacy method yet we support // getStats(something, cb) without error callbacks. ['getStats'].forEach(function(method) { var nativeMethod = RTCPeerConnection.prototype[method]; RTCPeerConnection.prototype[method] = function() { var args = arguments; if (typeof args[1] === 'function') { return nativeMethod.apply(this, arguments) .then(function() { if (typeof args[1] === 'function') { args[1].apply(null); } }); } return nativeMethod.apply(this, arguments); }; }); return RTCPeerConnection; }; /* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. */ function shimGetUserMedia$2(window) { const navigator = window && window.navigator; const shimError_ = function(e) { return { name: {PermissionDeniedError: 'NotAllowedError'}[e.name] || e.name, message: e.message, constraint: e.constraint, toString() { return this.name; } }; }; // getUserMedia error shim. const origGetUserMedia = navigator.mediaDevices.getUserMedia. bind(navigator.mediaDevices); navigator.mediaDevices.getUserMedia = function(c) { return origGetUserMedia(c).catch(e => Promise.reject(shimError_(e))); }; } /* * Copyright (c) 2018 The adapter.js project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. */ function shimGetDisplayMedia$1(window) { if (!('getDisplayMedia' in window.navigator)) { return; } if (!(window.navigator.mediaDevices)) { return; } if (window.navigator.mediaDevices && 'getDisplayMedia' in window.navigator.mediaDevices) { return; } window.navigator.mediaDevices.getDisplayMedia = window.navigator.getDisplayMedia.bind(window.navigator); } /* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. */ function shimPeerConnection$1(window, browserDetails) { if (window.RTCIceGatherer) { if (!window.RTCIceCandidate) { window.RTCIceCandidate = function RTCIceCandidate(args) { return args; }; } if (!window.RTCSessionDescription) { window.RTCSessionDescription = function RTCSessionDescription(args) { return args; }; } // this adds an additional event listener to MediaStrackTrack that signals // when a tracks enabled property was changed. Workaround for a bug in // addStream, see below. No longer required in 15025+ if (browserDetails.version < 15025) { const origMSTEnabled = Object.getOwnPropertyDescriptor( window.MediaStreamTrack.prototype, 'enabled'); Object.defineProperty(window.MediaStreamTrack.prototype, 'enabled', { set(value) { origMSTEnabled.set.call(this, value); const ev = new Event('enabled'); ev.enabled = value; this.dispatchEvent(ev); } }); } } // ORTC defines the DTMF sender a bit different. // https://github.com/w3c/ortc/issues/714 if (window.RTCRtpSender && !('dtmf' in window.RTCRtpSender.prototype)) { Object.defineProperty(window.RTCRtpSender.prototype, 'dtmf', { get() { if (this._dtmf === undefined) { if (this.track.kind === 'audio') { this._dtmf = new window.RTCDtmfSender(this); } else if (this.track.kind === 'video') { this._dtmf = null; } } return this._dtmf; } }); } // Edge currently only implements the RTCDtmfSender, not the // RTCDTMFSender alias. See http://draft.ortc.org/#rtcdtmfsender2* if (window.RTCDtmfSender && !window.RTCDTMFSender) { window.RTCDTMFSender = window.RTCDtmfSender; } const RTCPeerConnectionShim = rtcpeerconnection(window, browserDetails.version); window.RTCPeerConnection = function RTCPeerConnection(config) { if (config && config.iceServers) { config.iceServers = filterIceServers$1(config.iceServers, browserDetails.version); log$1('ICE servers after filtering:', config.iceServers); } return new RTCPeerConnectionShim(config); }; window.RTCPeerConnection.prototype = RTCPeerConnectionShim.prototype; } function shimReplaceTrack(window) { // ORTC has replaceTrack -- https://github.com/w3c/ortc/issues/614 if (window.RTCRtpSender && !('replaceTrack' in window.RTCRtpSender.prototype)) { window.RTCRtpSender.prototype.replaceTrack = window.RTCRtpSender.prototype.setTrack; } } var edgeShim = /*#__PURE__*/Object.freeze({ __proto__: null, shimPeerConnection: shimPeerConnection$1, shimReplaceTrack: shimReplaceTrack, shimGetUserMedia: shimGetUserMedia$2, shimGetDisplayMedia: shimGetDisplayMedia$1 }); /* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. */ function shimGetUserMedia$1(window, browserDetails) { const navigator = window && window.navigator; const MediaStreamTrack = window && window.MediaStreamTrack; navigator.getUserMedia = function(constraints, onSuccess, onError) { // Replace Firefox 44+'s deprecation warning with unprefixed version. deprecated('navigator.getUserMedia', 'navigator.mediaDevices.getUserMedia'); navigator.mediaDevices.getUserMedia(constraints).then(onSuccess, onError); }; if (!(browserDetails.version > 55 && 'autoGainControl' in navigator.mediaDevices.getSupportedConstraints())) { const remap = function(obj, a, b) { if (a in obj && !(b in obj)) { obj[b] = obj[a]; delete obj[a]; } }; const nativeGetUserMedia = navigator.mediaDevices.getUserMedia. bind(navigator.mediaDevices); navigator.mediaDevices.getUserMedia = function(c) { if (typeof c === 'object' && typeof c.audio === 'object') { c = JSON.parse(JSON.stringify(c)); remap(c.audio, 'autoGainControl', 'mozAutoGainControl'); remap(c.audio, 'noiseSuppression', 'mozNoiseSuppression'); } return nativeGetUserMedia(c); }; if (MediaStreamTrack && MediaStreamTrack.prototype.getSettings) { const nativeGetSettings = MediaStreamTrack.prototype.getSettings; MediaStreamTrack.prototype.getSettings = function() { const obj = nativeGetSettings.apply(this, arguments); remap(obj, 'mozAutoGainControl', 'autoGainControl'); remap(obj, 'mozNoiseSuppression', 'noiseSuppression'); return obj; }; } if (MediaStreamTrack && MediaStreamTrack.prototype.applyConstraints) { const nativeApplyConstraints = MediaStreamTrack.prototype.applyConstraints; MediaStreamTrack.prototype.applyConstraints = function(c) { if (this.kind === 'audio' && typeof c === 'object') { c = JSON.parse(JSON.stringify(c)); remap(c, 'autoGainControl', 'mozAutoGainControl'); remap(c, 'noiseSuppression', 'mozNoiseSuppression'); } return nativeApplyConstraints.apply(this, [c]); }; } } } /* * Copyright (c) 2018 The adapter.js project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. */ function shimGetDisplayMedia(window, preferredMediaSource) { if (window.navigator.mediaDevices && 'getDisplayMedia' in window.navigator.mediaDevices) { return; } if (!(window.navigator.mediaDevices)) { return; } window.navigator.mediaDevices.getDisplayMedia = function getDisplayMedia(constraints) { if (!(constraints && constraints.video)) { const err = new DOMException('getDisplayMedia without video ' + 'constraints is undefined'); err.name = 'NotFoundError'; // from https://heycam.github.io/webidl/#idl-DOMException-error-names err.code = 8; return Promise.reject(err); } if (constraints.video === true) { constraints.video = {mediaSource: preferredMediaSource}; } else { constraints.video.mediaSource = preferredMediaSource; } return window.navigator.mediaDevices.getUserMedia(constraints); }; } /* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. */ function shimOnTrack(window) { if (typeof window === 'object' && window.RTCTrackEvent && ('receiver' in window.RTCTrackEvent.prototype) && !('transceiver' in window.RTCTrackEvent.prototype)) { Object.defineProperty(window.RTCTrackEvent.prototype, 'transceiver', { get() { return {receiver: this.receiver}; } }); } } function shimPeerConnection(window, browserDetails) { if (typeof window !== 'object' || !(window.RTCPeerConnection || window.mozRTCPeerConnection)) { return; // probably media.peerconnection.enabled=false in about:config } if (!window.RTCPeerConnection && window.mozRTCPeerConnection) { // very basic support for old versions. window.RTCPeerConnection = window.mozRTCPeerConnection; } if (browserDetails.version < 53) { // shim away need for obsolete RTCIceCandidate/RTCSessionDescription. ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'] .forEach(function(method) { const nativeMethod = window.RTCPeerConnection.prototype[method]; const methodObj = {[method]() { arguments[0] = new ((method === 'addIceCandidate') ? window.RTCIceCandidate : window.RTCSessionDescription)(arguments[0]); return nativeMethod.apply(this, arguments); }}; window.RTCPeerConnection.prototype[method] = methodObj[method]; }); } const modernStatsTypes = { inboundrtp: 'inbound-rtp', outboundrtp: 'outbound-rtp', candidatepair: 'candidate-pair', localcandidate: 'local-candidate', remotecandidate: 'remote-candidate' }; const nativeGetStats = window.RTCPeerConnection.prototype.getStats; window.RTCPeerConnection.prototype.getStats = function getStats() { const [selector, onSucc, onErr] = arguments; return nativeGetStats.apply(this, [selector || null]) .then(stats => { if (browserDetails.version < 53 && !onSucc) { // Shim only promise getStats with spec-hyphens in type names // Leave callback version alone; misc old uses of forEach before Map try { stats.forEach(stat => { stat.type = modernStatsTypes[stat.type] || stat.type; }); } catch (e) { if (e.name !== 'TypeError') { throw e; } // Avoid TypeError: "type" is read-only, in old versions. 34-43ish stats.forEach((stat, i) => { stats.set(i, Object.assign({}, stat, { type: modernStatsTypes[stat.type] || stat.type })); }); } } return stats; }) .then(onSucc, onErr); }; } function shimSenderGetStats(window) { if (!(typeof window === 'object' && window.RTCPeerConnection && window.RTCRtpSender)) { return; } if (window.RTCRtpSender && 'getStats' in window.RTCRtpSender.prototype) { return; } const origGetSenders = window.RTCPeerConnection.prototype.getSenders; if (origGetSenders) { window.RTCPeerConnection.prototype.getSenders = function getSenders() { const senders = origGetSenders.apply(this, []); senders.forEach(sender => sender._pc = this); return senders; }; } const origAddTrack = window.RTCPeerConnection.prototype.addTrack; if (origAddTrack) { window.RTCPeerConnection.prototype.addTrack = function addTrack() { const sender = origAddTrack.apply(this, arguments); sender._pc = this; return sender; }; } window.RTCRtpSender.prototype.getStats = function getStats() { return this.track ? this._pc.getStats(this.track) : Promise.resolve(new Map()); }; } function shimReceiverGetStats(window) { if (!(typeof window === 'object' && window.RTCPeerConnection && window.RTCRtpSender)) { return; } if (window.RTCRtpSender && 'getStats' in window.RTCRtpReceiver.prototype) { return; } const origGetReceivers = window.RTCPeerConnection.prototype.getReceivers; if (origGetReceivers) { window.RTCPeerConnection.prototype.getReceivers = function getReceivers() { const receivers = origGetReceivers.apply(this, []); receivers.forEach(receiver => receiver._pc = this); return receivers; }; } wrapPeerConnectionEvent(window, 'track', e => { e.receiver._pc = e.srcElement; return e; }); window.RTCRtpReceiver.prototype.getStats = function getStats() { return this._pc.getStats(this.track); }; } function shimRemoveStream(window) { if (!window.RTCPeerConnection || 'removeStream' in window.RTCPeerConnection.prototype) { return; } window.RTCPeerConnection.prototype.removeStream = function removeStream(stream) { deprecated('removeStream', 'removeTrack'); this.getSenders().forEach(sender => { if (sender.track && stream.getTracks().includes(sender.track)) { this.removeTrack(sender); } }); }; } function shimRTCDataChannel(window) { // rename DataChannel to RTCDataChannel (native fix in FF60): // https://bugzilla.mozilla.org/show_bug.cgi?id=1173851 if (window.DataChannel && !window.RTCDataChannel) { window.RTCDataChannel = window.DataChannel; } } function shimAddTransceiver(window) { // https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647 // Firefox ignores the init sendEncodings options passed to addTransceiver // https://bugzilla.mozilla.org/show_bug.cgi?id=1396918 if (!(typeof window === 'object' && window.RTCPeerConnection)) { return; } const origAddTransceiver = window.RTCPeerConnection.prototype.addTransceiver; if (origAddTransceiver) { window.RTCPeerConnection.prototype.addTransceiver = function addTransceiver() { this.setParametersPromises = []; const initParameters = arguments[1]; const shouldPerformCheck = initParameters && 'sendEncodings' in initParameters; if (shouldPerformCheck) { // If sendEncodings params are provided, validate grammar initParameters.sendEncodings.forEach((encodingParam) => { if ('rid' in encodingParam) { const ridRegex = /^[a-z0-9]{0,16}$/i; if (!ridRegex.test(encodingParam.rid)) { throw new TypeError('Invalid RID value provided.'); } } if ('scaleResolutionDownBy' in encodingParam) { if (!(parseFloat(encodingParam.scaleResolutionDownBy) >= 1.0)) { throw new RangeError('scale_resolution_down_by must be >= 1.0'); } } if ('maxFramerate' in encodingParam) { if (!(parseFloat(encodingParam.maxFramerate) >= 0)) { throw new RangeError('max_framerate must be >= 0.0'); } } }); } const transceiver = origAddTransceiver.apply(this, arguments); if (shouldPerformCheck) { // Check if the init options were applied. If not we do this in an // asynchronous way and save the promise reference in a global object. // This is an ugly hack, but at the same time is way more robust than // checking the sender parameters before and after the createOffer // Also note that after the createoffer we are not 100% sure that // the params were asynchronously applied so we might miss the // opportunity to recreate offer. const {sender} = transceiver; const params = sender.getParameters(); if (!('encodings' in params) || // Avoid being fooled by patched getParameters() below. (params.encodings.length === 1 && Object.keys(params.encodings[0]).length === 0)) { params.encodings = initParameters.sendEncodings; sender.sendEncodings = initParameters.sendEncodings; this.setParametersPromises.push(sender.setParameters(params) .then(() => { delete sender.sendEncodings; }).catch(() => { delete sender.sendEncodings; }) ); } } return transceiver; }; } } function shimGetParameters(window) { if (!(typeof window === 'object' && window.RTCRtpSender)) { return; } const origGetParameters = window.RTCRtpSender.prototype.getParameters; if (origGetParameters) { window.RTCRtpSender.prototype.getParameters = function getParameters() { const params = origGetParameters.apply(this, arguments); if (!('encodings' in params)) { params.encodings = [].concat(this.sendEncodings || [{}]); } return params; }; } } function shimCreateOffer(window) { // https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647 // Firefox ignores the init sendEncodings options passed to addTransceiver // https://bugzilla.mozilla.org/show_bug.cgi?id=1396918 if (!(typeof window === 'object' && window.RTCPeerConnection)) { return; } const origCreateOffer = window.RTCPeerConnection.prototype.createOffer; window.RTCPeerConnection.prototype.createOffer = function createOffer() { if (this.setParametersPromises && this.setParametersPromises.length) { return Promise.all(this.setParametersPromises) .then(() => { return origCreateOffer.apply(this, arguments); }) .finally(() => { this.setParametersPromises = []; }); } return origCreateOffer.apply(this, arguments); }; } function shimCreateAnswer(window) { // https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647 // Firefox ignores the init sendEncodings options passed to addTransceiver // https://bugzilla.mozilla.org/show_bug.cgi?id=1396918 if (!(typeof window === 'object' && window.RTCPeerConnection)) { return; } const origCreateAnswer = window.RTCPeerConnection.prototype.createAnswer; window.RTCPeerConnection.prototype.createAnswer = function createAnswer() { if (this.setParametersPromises && this.setParametersPromises.length) { return Promise.all(this.setParametersPromises) .then(() => { return origCreateAnswer.apply(this, arguments); }) .finally(() => { this.setParametersPromises = []; }); } return origCreateAnswer.apply(this, arguments); }; } var firefoxShim = /*#__PURE__*/Object.freeze({ __proto__: null, shimOnTrack: shimOnTrack, shimPeerConnection: shimPeerConnection, shimSenderGetStats: shimSenderGetStats, shimReceiverGetStats: shimReceiverGetStats, shimRemoveStream: shimRemoveStream, shimRTCDataChannel: shimRTCDataChannel, shimAddTransceiver: shimAddTransceiver, shimGetParameters: shimGetParameters, shimCreateOffer: shimCreateOffer, shimCreateAnswer: shimCreateAnswer, shimGetUserMedia: shimGetUserMedia$1, shimGetDisplayMedia: shimGetDisplayMedia }); /* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. */ function shimLocalStreamsAPI(window) { if (typeof window !== 'object' || !window.RTCPeerConnection) { return; } if (!('getLocalStreams' in window.RTCPeerConnection.prototype)) { window.RTCPeerConnection.prototype.getLocalStreams = function getLocalStreams() { if (!this._localStreams) { this._localStreams = []; } return this._localStreams; }; } if (!('addStream' in window.RTCPeerConnection.prototype)) { const _addTrack = window.RTCPeerConnection.prototype.addTrack; window.RTCPeerConnection.prototype.addStream = function addStream(stream) { if (!this._localStreams) { this._localStreams = []; } if (!this._localStreams.includes(stream)) { this._localStreams.push(stream); } // Try to emulate Chrome's behaviour of adding in audio-video order. // Safari orders by track id. stream.getAudioTracks().forEach(track => _addTrack.call(this, track, stream)); stream.getVideoTracks().forEach(track => _addTrack.call(this, track, stream)); }; window.RTCPeerConnection.prototype.addTrack = function addTrack(track, ...streams) { if (streams) { streams.forEach((stream) => { if (!this._localStreams) { this._localStreams = [stream]; } else if (!this._localStreams.includes(stream)) { this._localStreams.push(stream); } }); } return _addTrack.apply(this, arguments); }; } if (!('removeStream' in window.RTCPeerConnection.prototype)) { window.RTCPeerConnection.prototype.removeStream = function removeStream(stream) { if (!this._localStreams) { this._localStreams = []; } const index = this._localStreams.indexOf(stream); if (index === -1) { return; } this._localStreams.splice(index, 1); const tracks = stream.getTracks(); this.getSenders().forEach(sender => { if (tracks.includes(sender.track)) { this.removeTrack(sender); } }); }; } } function shimRemoteStreamsAPI(window) { if (typeof window !== 'object' || !window.RTCPeerConnection) { return; } if (!('getRemoteStreams' in window.RTCPeerConnection.prototype)) { window.RTCPeerConnection.prototype.getRemoteStreams = function getRemoteStreams() { return this._remoteStreams ? this._remoteStreams : []; }; } if (!('onaddstream' in window.RTCPeerConnection.prototype)) { Object.defineProperty(window.RTCPeerConnection.prototype, 'onaddstream', { get() { return this._onaddstream; }, set(f) { if (this._onaddstream) { this.removeEventListener('addstream', this._onaddstream); this.removeEventListener('track', this._onaddstreampoly); } this.addEventListener('addstream', this._onaddstream = f); this.addEventListener('track', this._onaddstreampoly = (e) => { e.streams.forEach(stream => { if (!this._remoteStreams) { this._remoteStreams = []; } if (this._remoteStreams.includes(stream)) { return; } this._remoteStreams.push(stream); const event = new Event('addstream'); event.stream = stream; this.dispatchEvent(event); }); }); } }); const origSetRemoteDescription = window.RTCPeerConnection.prototype.setRemoteDescription; window.RTCPeerConnection.prototype.setRemoteDescription = function setRemoteDescription() { const pc = this; if (!this._onaddstreampoly) { this.addEventListener('track', this._onaddstreampoly = function(e) { e.streams.forEach(stream => { if (!pc._remoteStreams) { pc._remoteStreams = []; } if (pc._remoteStreams.indexOf(stream) >= 0) { return; } pc._remoteStreams.push(stream); const event = new Event('addstream'); event.stream = stream; pc.dispatchEvent(event); }); }); } return origSetRemoteDescription.apply(pc, arguments); }; } } function shimCallbacksAPI(window) { if (typeof window !== 'object' || !window.RTCPeerConnection) { return; } const prototype = window.RTCPeerConnection.prototype; const origCreateOffer = prototype.createOffer; const origCreateAnswer = prototype.createAnswer; const setLocalDescription = prototype.setLocalDescription; const setRemoteDescription = prototype.setRemoteDescription; const addIceCandidate = prototype.addIceCandidate; prototype.createOffer = function createOffer(successCallback, failureCallback) { const options = (arguments.length >= 2) ? arguments[2] : arguments[0]; const promise = origCreateOffer.apply(this, [options]); if (!failureCallback) { return promise; } promise.then(successCallback, failureCallback); return Promise.resolve(); }; prototype.createAnswer = function createAnswer(successCallback, failureCallback) { const options = (arguments.length >= 2) ? arguments[2] : arguments[0]; const promise = origCreateAnswer.apply(this, [options]); if (!failureCallback) { return promise; } promise.then(successCallback, failureCallback); return Promise.resolve(); }; let withCallback = function(description, successCallback, failureCallback) { const promise = setLocalDescription.apply(this, [description]); if (!failureCallback) { return promise; } promise.then(successCallback, failureCallback); return Promise.resolve(); }; prototype.setLocalDescription = withCallback; withCallback = function(description, successCallback, failureCallback) { const promise = setRemoteDescription.apply(this, [description]); if (!failureCallback) { return promise; } promise.then(successCallback, failureCallback); return Promise.resolve(); }; prototype.setRemoteDescription = withCallback; withCallback = function(candidate, successCallback, failureCallback) { const promise = addIceCandidate.apply(this, [candidate]); if (!failureCallback) { return promise; } promise.then(successCallback, failureCallback); return Promise.resolve(); }; prototype.addIceCandidate = withCallback; } function shimGetUserMedia(window) { const navigator = window && window.navigator; if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { // shim not needed in Safari 12.1 const mediaDevices = navigator.mediaDevices; const _getUserMedia = mediaDevices.getUserMedia.bind(mediaDevices); navigator.mediaDevices.getUserMedia = (constraints) => { return _getUserMedia(shimConstraints(constraints)); }; } if (!navigator.getUserMedia && navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { navigator.getUserMedia = function getUserMedia(constraints, cb, errcb) { navigator.mediaDevices.getUserMedia(constraints) .then(cb, errcb); }.bind(navigator); } } function shimConstraints(constraints) { if (constraints && constraints.video !== undefined) { return Object.assign({}, constraints, {video: compactObject(constraints.video)} ); } return constraints; } function shimRTCIceServerUrls(window) { if (!window.RTCPeerConnection) { return; } // migrate from non-spec RTCIceServer.url to RTCIceServer.urls const OrigPeerConnection = window.RTCPeerConnection; window.RTCPeerConnection = function RTCPeerConnection(pcConfig, pcConstraints) { if (pcConfig && pcConfig.iceServers) { const newIceServers = []; for (let i = 0; i < pcConfig.iceServers.length; i++) { let server = pcConfig.iceServers[i]; if (!server.hasOwnProperty('urls') && server.hasOwnProperty('url')) { deprecated('RTCIceServer.url', 'RTCIceServer.urls'); server = JSON.parse(JSON.stringify(server)); server.urls = server.url; delete server.url; newIceServers.push(server); } else { newIceServers.push(pcConfig.iceServers[i]); } } pcConfig.iceServers = newIceServers; } return new OrigPeerConnection(pcConfig, pcConstraints); }; window.RTCPeerConnection.prototype = OrigPeerConnection.prototype; // wrap static methods. Currently just generateCertificate. if ('generateCertificate' in OrigPeerConnection) { Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', { get() { return OrigPeerConnection.generateCertificate; } }); } } function shimTrackEventTransceiver(window) { // Add event.transceiver member over deprecated event.receiver if (typeof window === 'object' && window.RTCTrackEvent && 'receiver' in window.RTCTrackEvent.prototype && !('transceiver' in window.RTCTrackEvent.prototype)) { Object.defineProperty(window.RTCTrackEvent.prototype, 'transceiver', { get() { return {receiver: this.receiver}; } }); } } function shimCreateOfferLegacy(window) { const origCreateOffer = window.RTCPeerConnection.prototype.createOffer; window.RTCPeerConnection.prototype.createOffer = function createOffer(offerOptions) { if (offerOptions) { if (typeof offerOptions.offerToReceiveAudio !== 'undefined') { // support bit values offerOptions.offerToReceiveAudio = !!offerOptions.offerToReceiveAudio; } const audioTransceiver = this.getTransceivers().find(transceiver => transceiver.receiver.track.kind === 'audio'); if (offerOptions.offerToReceiveAudio === false && audioTransceiver) { if (audioTransceiver.direction === 'sendrecv') { if (audioTransceiver.setDirection) { audioTransceiver.setDirection('sendonly'); } else { audioTransceiver.direction = 'sendonly'; } } else if (audioTransceiver.direction === 'recvonly') { if (audioTransceiver.setDirection) { audioTransceiver.setDirection('inactive'); } else { audioTransceiver.direction = 'inactive'; } } } else if (offerOptions.offerToReceiveAudio === true && !audioTransceiver) { this.addTransceiver('audio'); } if (typeof offerOptions.offerToReceiveVideo !== 'undefined') { // support bit values offerOptions.offerToReceiveVideo = !!offerOptions.offerToReceiveVideo; } const videoTransceiver = this.getTransceivers().find(transceiver => transceiver.receiver.track.kind === 'video'); if (offerOptions.offerToReceiveVideo === false && videoTransceiver) { if (videoTransceiver.direction === 'sendrecv') { if (videoTransceiver.setDirection) { videoTransceiver.setDirection('sendonly'); } else { videoTransceiver.direction = 'sendonly'; } } else if (videoTransceiver.direction === 'recvonly') { if (videoTransceiver.setDirection) { videoTransceiver.setDirection('inactive'); } else { videoTransceiver.direction = 'inactive'; } } } else if (offerOptions.offerToReceiveVideo === true && !videoTransceiver) { this.addTransceiver('video'); } } return origCreateOffer.apply(this, arguments); }; } function shimAudioContext(window) { if (typeof window !== 'object' || window.AudioContext) { return; } window.AudioContext = window.webkitAudioContext; } var safariShim = /*#__PURE__*/Object.freeze({ __proto__: null, shimLocalStreamsAPI: shimLocalStreamsAPI, shimRemoteStreamsAPI: shimRemoteStreamsAPI, shimCallbacksAPI: shimCallbacksAPI, shimGetUserMedia: shimGetUserMedia, shimConstraints: shimConstraints, shimRTCIceServerUrls: shimRTCIceServerUrls, shimTrackEventTransceiver: shimTrackEventTransceiver, shimCreateOfferLegacy: shimCreateOfferLegacy, shimAudioContext: shimAudioContext }); /* * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. */ function shimRTCIceCandidate(window) { // foundation is arbitrarily chosen as an indicator for full support for // https://w3c.github.io/webrtc-pc/#rtcicecandidate-interface if (!window.RTCIceCandidate || (window.RTCIceCandidate && 'foundation' in window.RTCIceCandidate.prototype)) { return; } const NativeRTCIceCandidate = window.RTCIceCandidate; window.RTCIceCandidate = function RTCIceCandidate(args) { // Remove the a= which shouldn't be part of the candidate string. if (typeof args === 'object' && args.candidate && args.candidate.indexOf('a=') === 0) { args = JSON.parse(JSON.stringify(args)); args.candidate = args.candidate.substr(2); } if (args.candidate && args.candidate.length) { // Augment the native candidate with the parsed fields. const nativeCandidate = new NativeRTCIceCandidate(args); const parsedCandidate = sdp.parseCandidate(args.candidate); const augmentedCandidate = Object.assign(nativeCandidate, parsedCandidate); // Add a serializer that does not serialize the extra attributes. augmentedCandidate.toJSON = function toJSON() { return { candidate: augmentedCandidate.candidate, sdpMid: augmentedCandidate.sdpMid, sdpMLineIndex: augmentedCandidate.sdpMLineIndex, usernameFragment: augmentedCandidate.usernameFragment, }; }; return augmentedCandidate; } return new NativeRTCIceCandidate(args); }; window.RTCIceCandidate.prototype = NativeRTCIceCandidate.prototype; // Hook up the augmented candidate in onicecandidate and // addEventListener('icecandidate', ...) wrapPeerConnectionEvent(window, 'icecandidate', e => { if (e.candidate) { Object.defineProperty(e, 'candidate', { value: new window.RTCIceCandidate(e.candidate), writable: 'false' }); } return e; }); } function shimMaxMessageSize(window, browserDetails) { if (!window.RTCPeerConnection) { return; } if (!('sctp' in window.RTCPeerConnection.prototype)) { Object.defineProperty(window.RTCPeerConnection.prototype, 'sctp', { get() { return typeof this._sctp === 'undefined' ? null : this._sctp; } }); } const sctpInDescription = function(description) { if (!description || !description.sdp) { return false; } const sections = sdp.splitSections(description.sdp); sections.shift(); return sections.some(mediaSection => { const mLine = sdp.parseMLine(mediaSection); return mLine && mLine.kind === 'application' && mLine.protocol.indexOf('SCTP') !== -1; }); }; const getRemoteFirefoxVersion = function(description) { // TODO: Is there a better solution for detecting Firefox? const match = description.sdp.match(/mozilla...THIS_IS_SDPARTA-(\d+)/); if (match === null || match.length < 2) { return -1; } const version = parseInt(match[1], 10); // Test for NaN (yes, this is ugly) return version !== version ? -1 : version; }; const getCanSendMaxMessageSize = function(remoteIsFirefox) { // Every implementation we know can send at least 64 KiB. // Note: Although Chrome is technically able to send up to 256 KiB, the // data does not reach the other peer reliably. // See: https://bugs.chromium.org/p/webrtc/issues/detail?id=8419 let canSendMaxMessageSize = 65536; if (browserDetails.browser === 'firefox') { if (browserDetails.version < 57) { if (remoteIsFirefox === -1) { // FF < 57 will send in 16 KiB chunks using the deprecated PPID // fragmentation. canSendMaxMessageSize = 16384; } else { // However, other FF (and RAWRTC) can reassemble PPID-fragmented // messages. Thus, supporting ~2 GiB when sending. canSendMaxMessageSize = 2147483637; } } else if (browserDetails.version < 60) { // Currently, all FF >= 57 will reset the remote maximum message size // to the default value when a data channel is created at a later // stage. :( // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831 canSendMaxMessageSize = browserDetails.version === 57 ? 65535 : 65536; } else { // FF >= 60 supports sending ~2 GiB canSendMaxMessageSize = 2147483637; } } return canSendMaxMessageSize; }; const getMaxMessageSize = function(description, remoteIsFirefox) { // Note: 65536 bytes is the default value from the SDP spec. Also, // every implementation we know supports receiving 65536 bytes. let maxMessageSize = 65536; // FF 57 has a slightly incorrect default remote max message size, so // we need to adjust it here to avoid a failure when sending. // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1425697 if (browserDetails.browser === 'firefox' && browserDetails.version === 57) { maxMessageSize = 65535; } const match = sdp.matchPrefix(description.sdp, 'a=max-message-size:'); if (match.length > 0) { maxMessageSize = parseInt(match[0].substr(19), 10); } else if (browserDetails.browser === 'firefox' && remoteIsFirefox !== -1) { // If the maximum message size is not present in the remote SDP and // both local and remote are Firefox, the remote peer can receive // ~2 GiB. maxMessageSize = 2147483637; } return maxMessageSize; }; const origSetRemoteDescription = window.RTCPeerConnection.prototype.setRemoteDescription; window.RTCPeerConnection.prototype.setRemoteDescription = function setRemoteDescription() { this._sctp = null; // Chrome decided to not expose .sctp in plan-b mode. // As usual, adapter.js has to do an 'ugly worakaround' // to cover up the mess. if (browserDetails.browser === 'chrome' && browserDetails.version >= 76) { const {sdpSemantics} = this.getConfiguration(); if (sdpSemantics === 'plan-b') { Object.defineProperty(this, 'sctp', { get() { return typeof this._sctp === 'undefined' ? null : this._sctp; }, enumerable: true, configurable: true, }); } } if (sctpInDescription(arguments[0])) { // Check if the remote is FF. const isFirefox = getRemoteFirefoxVersion(arguments[0]); // Get the maximum message size the local peer is capable of sending const canSendMMS = getCanSendMaxMessageSize(isFirefox); // Get the maximum message size of the remote peer. const remoteMMS = getMaxMessageSize(arguments[0], isFirefox); // Determine final maximum message size let maxMessageSize; if (canSendMMS === 0 && remoteMMS === 0) { maxMessageSize = Number.POSITIVE_INFINITY; } else if (canSendMMS === 0 || remoteMMS === 0) { maxMessageSize = Math.max(canSendMMS, remoteMMS); } else { maxMessageSize = Math.min(canSendMMS, remoteMMS); } // Create a dummy RTCSctpTransport object and the 'maxMessageSize' // attribute. const sctp = {}; Object.defineProperty(sctp, 'maxMessageSize', { get() { return maxMessageSize; } }); this._sctp = sctp; } return origSetRemoteDescription.apply(this, arguments); }; } function shimSendThrowTypeError(window) { if (!(window.RTCPeerConnection && 'createDataChannel' in window.RTCPeerConnection.prototype)) { return; } // Note: Although Firefox >= 57 has a native implementation, the maximum // message size can be reset for all data channels at a later stage. // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831 function wrapDcSend(dc, pc) { const origDataChannelSend = dc.send; dc.send = function send() { const data = arguments[0]; const length = data.length || data.size || data.byteLength; if (dc.readyState === 'open' && pc.sctp && length > pc.sctp.maxMessageSize) { throw new TypeError('Message too large (can send a maximum of ' + pc.sctp.maxMessageSize + ' bytes)'); } return origDataChannelSend.apply(dc, arguments); }; } const origCreateDataChannel = window.RTCPeerConnection.prototype.createDataChannel; window.RTCPeerConnection.prototype.createDataChannel = function createDataChannel() { const dataChannel = origCreateDataChannel.apply(this, arguments); wrapDcSend(dataChannel, this); return dataChannel; }; wrapPeerConnectionEvent(window, 'datachannel', e => { wrapDcSend(e.channel, e.target); return e; }); } /* shims RTCConnectionState by pretending it is the same as iceConnectionState. * See https://bugs.chromium.org/p/webrtc/issues/detail?id=6145#c12 * for why this is a valid hack in Chrome. In Firefox it is slightly incorrect * since DTLS failures would be hidden. See * https://bugzilla.mozilla.org/show_bug.cgi?id=1265827 * for the Firefox tracking bug. */ function shimConnectionState(window) { if (!window.RTCPeerConnection || 'connectionState' in window.RTCPeerConnection.prototype) { return; } const proto = window.RTCPeerConnection.prototype; Object.defineProperty(proto, 'connectionState', { get() { return { completed: 'connected', checking: 'connecting' }[this.iceConnectionState] || this.iceConnectionState; }, enumerable: true, configurable: true }); Object.defineProperty(proto, 'onconnectionstatechange', { get() { return this._onconnectionstatechange || null; }, set(cb) { if (this._onconnectionstatechange) { this.removeEventListener('connectionstatechange', this._onconnectionstatechange); delete this._onconnectionstatechange; } if (cb) { this.addEventListener('connectionstatechange', this._onconnectionstatechange = cb); } }, enumerable: true, configurable: true }); ['setLocalDescription', 'setRemoteDescription'].forEach((method) => { const origMethod = proto[method]; proto[method] = function() { if (!this._connectionstatechangepoly) { this._connectionstatechangepoly = e => { const pc = e.target; if (pc._lastConnectionState !== pc.connectionState) { pc._lastConnectionState = pc.connectionState; const newEvent = new Event('connectionstatechange', e); pc.dispatchEvent(newEvent); } return e; }; this.addEventListener('iceconnectionstatechange', this._connectionstatechangepoly); } return origMethod.apply(this, arguments); }; }); } function removeExtmapAllowMixed(window, browserDetails) { /* remove a=extmap-allow-mixed for webrtc.org < M71 */ if (!window.RTCPeerConnection) { return; } if (browserDetails.browser === 'chrome' && browserDetails.version >= 71) { return; } if (browserDetails.browser === 'safari' && browserDetails.version >= 605) { return; } const nativeSRD = window.RTCPeerConnection.prototype.setRemoteDescription; window.RTCPeerConnection.prototype.setRemoteDescription = function setRemoteDescription(desc) { if (desc && desc.sdp && desc.sdp.indexOf('\na=extmap-allow-mixed') !== -1) { const sdp = desc.sdp.split('\n').filter((line) => { return line.trim() !== 'a=extmap-allow-mixed'; }).join('\n'); // Safari enforces read-only-ness of RTCSessionDescription fields. if (window.RTCSessionDescription && desc instanceof window.RTCSessionDescription) { arguments[0] = new window.RTCSessionDescription({ type: desc.type, sdp, }); } else { desc.sdp = sdp; } } return nativeSRD.apply(this, arguments); }; } function shimAddIceCandidateNullOrEmpty(window, browserDetails) { // Support for addIceCandidate(null or undefined) // as well as addIceCandidate({candidate: "", ...}) // https://bugs.chromium.org/p/chromium/issues/detail?id=978582 // Note: must be called before other polyfills which change the signature. if (!(window.RTCPeerConnection && window.RTCPeerConnection.prototype)) { return; } const nativeAddIceCandidate = window.RTCPeerConnection.prototype.addIceCandidate; if (!nativeAddIceCandidate || nativeAddIceCandidate.length === 0) { return; } window.RTCPeerConnection.prototype.addIceCandidate = function addIceCandidate() { if (!arguments[0]) { if (arguments[1]) { arguments[1].apply(null); } return Promise.resolve(); } // Firefox 68+ emits and processes {candidate: "", ...}, ignore // in older versions. // Native support for ignoring exists for Chrome M77+. // Safari ignores as well, exact version unknown but works in the same // version that also ignores addIceCandidate(null). if (((browserDetails.browser === 'chrome' && browserDetails.version < 78) || (browserDetails.browser === 'firefox' && browserDetails.version < 68) || (browserDetails.browser === 'safari')) && arguments[0] && arguments[0].candidate === '') { return Promise.resolve(); } return nativeAddIceCandidate.apply(this, arguments); }; } var commonShim = /*#__PURE__*/Object.freeze({ __proto__: null, shimRTCIceCandidate: shimRTCIceCandidate, shimMaxMessageSize: shimMaxMessageSize, shimSendThrowTypeError: shimSendThrowTypeError, shimConnectionState: shimConnectionState, removeExtmapAllowMixed: removeExtmapAllowMixed, shimAddIceCandidateNullOrEmpty: shimAddIceCandidateNullOrEmpty }); /* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. */ // Shimming starts here. function adapterFactory({window} = {}, options = { shimChrome: true, shimFirefox: true, shimEdge: true, shimSafari: true, }) { // Utils. const logging = log$1; const browserDetails = detectBrowser(window); const adapter = { browserDetails, commonShim, extractVersion: extractVersion, disableLog: disableLog, disableWarnings: disableWarnings }; // Shim browser if found. switch (browserDetails.browser) { case 'chrome': if (!chromeShim || !shimPeerConnection$2 || !options.shimChrome) { logging('Chrome shim is not included in this adapter release.'); return adapter; } if (browserDetails.version === null) { logging('Chrome shim can not determine version, not shimming.'); return adapter; } logging('adapter.js shimming chrome.'); // Export to the adapter global object visible in the browser. adapter.browserShim = chromeShim; // Must be called before shimPeerConnection. shimAddIceCandidateNullOrEmpty(window, browserDetails); shimGetUserMedia$3(window, browserDetails); shimMediaStream(window); shimPeerConnection$2(window, browserDetails); shimOnTrack$1(window); shimAddTrackRemoveTrack(window, browserDetails); shimGetSendersWithDtmf(window); shimGetStats(window); shimSenderReceiverGetStats(window); fixNegotiationNeeded(window, browserDetails); shimRTCIceCandidate(window); shimConnectionState(window); shimMaxMessageSize(window, browserDetails); shimSendThrowTypeError(window); removeExtmapAllowMixed(window, browserDetails); break; case 'firefox': if (!firefoxShim || !shimPeerConnection || !options.shimFirefox) { logging('Firefox shim is not included in this adapter release.'); return adapter; } logging('adapter.js shimming firefox.'); // Export to the adapter global object visible in the browser. adapter.browserShim = firefoxShim; // Must be called before shimPeerConnection. shimAddIceCandidateNullOrEmpty(window, browserDetails); shimGetUserMedia$1(window, browserDetails); shimPeerConnection(window, browserDetails); shimOnTrack(window); shimRemoveStream(window); shimSenderGetStats(window); shimReceiverGetStats(window); shimRTCDataChannel(window); shimAddTransceiver(window); shimGetParameters(window); shimCreateOffer(window); shimCreateAnswer(window); shimRTCIceCandidate(window); shimConnectionState(window); shimMaxMessageSize(window, browserDetails); shimSendThrowTypeError(window); break; case 'edge': if (!edgeShim || !shimPeerConnection$1 || !options.shimEdge) { logging('MS edge shim is not included in this adapter release.'); return adapter; } logging('adapter.js shimming edge.'); // Export to the adapter global object visible in the browser. adapter.browserShim = edgeShim; shimGetUserMedia$2(window); shimGetDisplayMedia$1(window); shimPeerConnection$1(window, browserDetails); shimReplaceTrack(window); // the edge shim implements the full RTCIceCandidate object. shimMaxMessageSize(window, browserDetails); shimSendThrowTypeError(window); break; case 'safari': if (!safariShim || !options.shimSafari) { logging('Safari shim is not included in this adapter release.'); return adapter; } logging('adapter.js shimming safari.'); // Export to the adapter global object visible in the browser. adapter.browserShim = safariShim; // Must be called before shimCallbackAPI. shimAddIceCandidateNullOrEmpty(window, browserDetails); shimRTCIceServerUrls(window); shimCreateOfferLegacy(window); shimCallbacksAPI(window); shimLocalStreamsAPI(window); shimRemoteStreamsAPI(window); shimTrackEventTransceiver(window); shimGetUserMedia(window); shimAudioContext(window); shimRTCIceCandidate(window); shimMaxMessageSize(window, browserDetails); shimSendThrowTypeError(window); removeExtmapAllowMixed(window, browserDetails); break; default: logging('Unsupported browser!'); break; } return adapter; } /* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. */ adapterFactory({window: typeof window === 'undefined' ? undefined : window}); /** * @class AudioTrackConstraints * @classDesc Constraints for creating an audio MediaStreamTrack. * @memberof Owt.Base * @constructor * @param {Owt.Base.AudioSourceInfo} source Source info of this audio track. */ class AudioTrackConstraints { // eslint-disable-next-line require-jsdoc constructor(source) { if (!Object.values(AudioSourceInfo).some(v => v === source)) { throw new TypeError('Invalid source.'); } /** * @member {string} source * @memberof Owt.Base.AudioTrackConstraints * @desc Values could be "mic", "screen-cast", "file" or "mixed". * @instance */ this.source = source; /** * @member {string} deviceId * @memberof Owt.Base.AudioTrackConstraints * @desc Do not provide deviceId if source is not "mic". * @instance * @see https://w3c.github.io/mediacapture-main/#def-constraint-deviceId */ this.deviceId = undefined; } } /** * @class VideoTrackConstraints * @classDesc Constraints for creating a video MediaStreamTrack. * @memberof Owt.Base * @constructor * @param {Owt.Base.VideoSourceInfo} source Source info of this video track. */ class VideoTrackConstraints { // eslint-disable-next-line require-jsdoc constructor(source) { if (!Object.values(VideoSourceInfo).some(v => v === source)) { throw new TypeError('Invalid source.'); } /** * @member {string} source * @memberof Owt.Base.VideoTrackConstraints * @desc Values could be "camera", "screen-cast", "file" or "mixed". * @instance */ this.source = source; /** * @member {string} deviceId * @memberof Owt.Base.VideoTrackConstraints * @desc Do not provide deviceId if source is not "camera". * @instance * @see https://w3c.github.io/mediacapture-main/#def-constraint-deviceId */ this.deviceId = undefined; /** * @member {Owt.Base.Resolution} resolution * @memberof Owt.Base.VideoTrackConstraints * @instance */ this.resolution = undefined; /** * @member {number} frameRate * @memberof Owt.Base.VideoTrackConstraints * @instance */ this.frameRate = undefined; } } /** * @class StreamConstraints * @classDesc Constraints for creating a MediaStream from screen mic and camera. * @memberof Owt.Base * @constructor * @param {?Owt.Base.AudioTrackConstraints} audioConstraints * @param {?Owt.Base.VideoTrackConstraints} videoConstraints */ class StreamConstraints { // eslint-disable-next-line require-jsdoc constructor(audioConstraints = false, videoConstraints = false) { /** * @member {Owt.Base.MediaStreamTrackDeviceConstraintsForAudio} audio * @memberof Owt.Base.MediaStreamDeviceConstraints * @instance */ this.audio = audioConstraints; /** * @member {Owt.Base.MediaStreamTrackDeviceConstraintsForVideo} Video * @memberof Owt.Base.MediaStreamDeviceConstraints * @instance */ this.video = videoConstraints; } } // eslint-disable-next-line require-jsdoc function isVideoConstrainsForScreenCast(constraints) { return typeof constraints.video === 'object' && constraints.video.source === VideoSourceInfo.SCREENCAST; } /** * @class MediaStreamFactory * @classDesc A factory to create MediaStream. You can also create MediaStream by yourself. * @memberof Owt.Base */ class MediaStreamFactory { /** * @function createMediaStream * @static * @desc Create a MediaStream with given constraints. If you want to create a MediaStream for screen cast, please make sure both audio and video's source are "screen-cast". * @memberof Owt.Base.MediaStreamFactory * @return {Promise} Return a promise that is resolved when stream is successfully created, or rejected if one of the following error happened: * - One or more parameters cannot be satisfied. * - Specified device is busy. * - Cannot obtain necessary permission or operation is canceled by user. * - Video source is screen cast, while audio source is not. * - Audio source is screen cast, while video source is disabled. * @param {Owt.Base.StreamConstraints} constraints */ static createMediaStream(constraints) { if (typeof constraints !== 'object' || !constraints.audio && !constraints.video) { return Promise.reject(new TypeError('Invalid constrains')); } if (!isVideoConstrainsForScreenCast(constraints) && typeof constraints.audio === 'object' && constraints.audio.source === AudioSourceInfo.SCREENCAST) { return Promise.reject(new TypeError('Cannot share screen without video.')); } if (isVideoConstrainsForScreenCast(constraints) && !isChrome() && !isFirefox()) { return Promise.reject(new TypeError('Screen sharing only supports Chrome and Firefox.')); } if (isVideoConstrainsForScreenCast(constraints) && typeof constraints.audio === 'object' && constraints.audio.source !== AudioSourceInfo.SCREENCAST) { return Promise.reject(new TypeError('Cannot capture video from screen cast while capture audio from' + ' other source.')); } // Check and convert constraints. if (!constraints.audio && !constraints.video) { return Promise.reject(new TypeError('At least one of audio and video must be requested.')); } const mediaConstraints = Object.create({}); if (typeof constraints.audio === 'object' && constraints.audio.source === AudioSourceInfo.MIC) { mediaConstraints.audio = Object.create({}); if (isEdge()) { mediaConstraints.audio.deviceId = constraints.audio.deviceId; } else { mediaConstraints.audio.deviceId = { exact: constraints.audio.deviceId }; } } else { if (constraints.audio.source === AudioSourceInfo.SCREENCAST) { mediaConstraints.audio = true; } else { mediaConstraints.audio = constraints.audio; } } if (typeof constraints.video === 'object') { mediaConstraints.video = Object.create({}); if (typeof constraints.video.frameRate === 'number') { mediaConstraints.video.frameRate = constraints.video.frameRate; } if (constraints.video.resolution && constraints.video.resolution.width && constraints.video.resolution.height) { if (constraints.video.source === VideoSourceInfo.SCREENCAST) { mediaConstraints.video.width = constraints.video.resolution.width; mediaConstraints.video.height = constraints.video.resolution.height; } else { mediaConstraints.video.width = Object.create({}); mediaConstraints.video.width.exact = constraints.video.resolution.width; mediaConstraints.video.height = Object.create({}); mediaConstraints.video.height.exact = constraints.video.resolution.height; } } if (typeof constraints.video.deviceId === 'string') { mediaConstraints.video.deviceId = { exact: constraints.video.deviceId }; } if (isFirefox() && constraints.video.source === VideoSourceInfo.SCREENCAST) { mediaConstraints.video.mediaSource = 'screen'; } } else { mediaConstraints.video = constraints.video; } if (isVideoConstrainsForScreenCast(constraints)) { return navigator.mediaDevices.getDisplayMedia(mediaConstraints); } else { return navigator.mediaDevices.getUserMedia(mediaConstraints); } } } // Copyright (C) <2018> Intel Corporation var media = /*#__PURE__*/Object.freeze({ __proto__: null, AudioTrackConstraints: AudioTrackConstraints, VideoTrackConstraints: VideoTrackConstraints, StreamConstraints: StreamConstraints, MediaStreamFactory: MediaStreamFactory, AudioSourceInfo: AudioSourceInfo, VideoSourceInfo: VideoSourceInfo, TrackKind: TrackKind, Resolution: Resolution }); let logger; let errorLogger; function setLogger() { /*eslint-disable */ logger = console.log; errorLogger = console.error; /*eslint-enable */ } function log(message, ...optionalParams) { if (logger) { logger(message, ...optionalParams); } } function error(message, ...optionalParams) { if (errorLogger) { errorLogger(message, ...optionalParams); } } class Event$1 { constructor(type) { this.listener = {}; this.type = type | ''; } on(event, fn) { if (!this.listener[event]) { this.listener[event] = []; } this.listener[event].push(fn); return true; } off(event, fn) { if (this.listener[event]) { var index = this.listener[event].indexOf(fn); if (index > -1) { this.listener[event].splice(index, 1); } return true; } return false; } offAll() { this.listener = {}; } dispatch(event, data) { if (this.listener[event]) { this.listener[event].map(each => { each.apply(null, [data]); }); return true; } return false; } } var bind = function bind(fn, thisArg) { return function wrap() { var args = new Array(arguments.length); for (var i = 0; i < args.length; i++) { args[i] = arguments[i]; } return fn.apply(thisArg, args); }; }; // utils is a library of generic helper functions non-specific to axios var toString = Object.prototype.toString; // eslint-disable-next-line func-names var kindOf = (function(cache) { // eslint-disable-next-line func-names return function(thing) { var str = toString.call(thing); return cache[str] || (cache[str] = str.slice(8, -1).toLowerCase()); }; })(Object.create(null)); function kindOfTest(type) { type = type.toLowerCase(); return function isKindOf(thing) { return kindOf(thing) === type; }; } /** * Determine if a value is an Array * * @param {Object} val The value to test * @returns {boolean} True if value is an Array, otherwise false */ function isArray(val) { return Array.isArray(val); } /** * Determine if a value is undefined * * @param {Object} val The value to test * @returns {boolean} True if the value is undefined, otherwise false */ function isUndefined(val) { return typeof val === 'undefined'; } /** * Determine if a value is a Buffer * * @param {Object} val The value to test * @returns {boolean} True if value is a Buffer, otherwise false */ function isBuffer(val) { return val !== null && !isUndefined(val) && val.constructor !== null && !isUndefined(val.constructor) && typeof val.constructor.isBuffer === 'function' && val.constructor.isBuffer(val); } /** * Determine if a value is an ArrayBuffer * * @function * @param {Object} val The value to test * @returns {boolean} True if value is an ArrayBuffer, otherwise false */ var isArrayBuffer = kindOfTest('ArrayBuffer'); /** * Determine if a value is a view on an ArrayBuffer * * @param {Object} val The value to test * @returns {boolean} True if value is a view on an ArrayBuffer, otherwise false */ function isArrayBufferView(val) { var result; if ((typeof ArrayBuffer !== 'undefined') && (ArrayBuffer.isView)) { result = ArrayBuffer.isView(val); } else { result = (val) && (val.buffer) && (isArrayBuffer(val.buffer)); } return result; } /** * Determine if a value is a String * * @param {Object} val The value to test * @returns {boolean} True if value is a String, otherwise false */ function isString(val) { return typeof val === 'string'; } /** * Determine if a value is a Number * * @param {Object} val The value to test * @returns {boolean} True if value is a Number, otherwise false */ function isNumber(val) { return typeof val === 'number'; } /** * Determine if a value is an Object * * @param {Object} val The value to test * @returns {boolean} True if value is an Object, otherwise false */ function isObject(val) { return val !== null && typeof val === 'object'; } /** * Determine if a value is a plain Object * * @param {Object} val The value to test * @return {boolean} True if value is a plain Object, otherwise false */ function isPlainObject(val) { if (kindOf(val) !== 'object') { return false; } var prototype = Object.getPrototypeOf(val); return prototype === null || prototype === Object.prototype; } /** * Determine if a value is a Date * * @function * @param {Object} val The value to test * @returns {boolean} True if value is a Date, otherwise false */ var isDate = kindOfTest('Date'); /** * Determine if a value is a File * * @function * @param {Object} val The value to test * @returns {boolean} True if value is a File, otherwise false */ var isFile = kindOfTest('File'); /** * Determine if a value is a Blob * * @function * @param {Object} val The value to test * @returns {boolean} True if value is a Blob, otherwise false */ var isBlob = kindOfTest('Blob'); /** * Determine if a value is a FileList * * @function * @param {Object} val The value to test * @returns {boolean} True if value is a File, otherwise false */ var isFileList = kindOfTest('FileList'); /** * Determine if a value is a Function * * @param {Object} val The value to test * @returns {boolean} True if value is a Function, otherwise false */ function isFunction(val) { return toString.call(val) === '[object Function]'; } /** * Determine if a value is a Stream * * @param {Object} val The value to test * @returns {boolean} True if value is a Stream, otherwise false */ function isStream(val) { return isObject(val) && isFunction(val.pipe); } /** * Determine if a value is a FormData * * @param {Object} thing The value to test * @returns {boolean} True if value is an FormData, otherwise false */ function isFormData(thing) { var pattern = '[object FormData]'; return thing && ( (typeof FormData === 'function' && thing instanceof FormData) || toString.call(thing) === pattern || (isFunction(thing.toString) && thing.toString() === pattern) ); } /** * Determine if a value is a URLSearchParams object * @function * @param {Object} val The value to test * @returns {boolean} True if value is a URLSearchParams object, otherwise false */ var isURLSearchParams = kindOfTest('URLSearchParams'); /** * Trim excess whitespace off the beginning and end of a string * * @param {String} str The String to trim * @returns {String} The String freed of excess whitespace */ function trim(str) { return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, ''); } /** * Determine if we're running in a standard browser environment * * This allows axios to run in a web worker, and react-native. * Both environments support XMLHttpRequest, but not fully standard globals. * * web workers: * typeof window -> undefined * typeof document -> undefined * * react-native: * navigator.product -> 'ReactNative' * nativescript * navigator.product -> 'NativeScript' or 'NS' */ function isStandardBrowserEnv() { if (typeof navigator !== 'undefined' && (navigator.product === 'ReactNative' || navigator.product === 'NativeScript' || navigator.product === 'NS')) { return false; } return ( typeof window !== 'undefined' && typeof document !== 'undefined' ); } /** * Iterate over an Array or an Object invoking a function for each item. * * If `obj` is an Array callback will be called passing * the value, index, and complete array for each item. * * If 'obj' is an Object callback will be called passing * the value, key, and complete object for each property. * * @param {Object|Array} obj The object to iterate * @param {Function} fn The callback to invoke for each item */ function forEach(obj, fn) { // Don't bother if no value provided if (obj === null || typeof obj === 'undefined') { return; } // Force an array if not already something iterable if (typeof obj !== 'object') { /*eslint no-param-reassign:0*/ obj = [obj]; } if (isArray(obj)) { // Iterate over array values for (var i = 0, l = obj.length; i < l; i++) { fn.call(null, obj[i], i, obj); } } else { // Iterate over object keys for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { fn.call(null, obj[key], key, obj); } } } } /** * Accepts varargs expecting each argument to be an object, then * immutably merges the properties of each object and returns result. * * When multiple objects contain the same key the later object in * the arguments list will take precedence. * * Example: * * ```js * var result = merge({foo: 123}, {foo: 456}); * console.log(result.foo); // outputs 456 * ``` * * @param {Object} obj1 Object to merge * @returns {Object} Result of all merge properties */ function merge(/* obj1, obj2, obj3, ... */) { var result = {}; function assignValue(val, key) { if (isPlainObject(result[key]) && isPlainObject(val)) { result[key] = merge(result[key], val); } else if (isPlainObject(val)) { result[key] = merge({}, val); } else if (isArray(val)) { result[key] = val.slice(); } else { result[key] = val; } } for (var i = 0, l = arguments.length; i < l; i++) { forEach(arguments[i], assignValue); } return result; } /** * Extends object a by mutably adding to it the properties of object b. * * @param {Object} a The object to be extended * @param {Object} b The object to copy properties from * @param {Object} thisArg The object to bind function to * @return {Object} The resulting value of object a */ function extend(a, b, thisArg) { forEach(b, function assignValue(val, key) { if (thisArg && typeof val === 'function') { a[key] = bind(val, thisArg); } else { a[key] = val; } }); return a; } /** * Remove byte order marker. This catches EF BB BF (the UTF-8 BOM) * * @param {string} content with BOM * @return {string} content value without BOM */ function stripBOM(content) { if (content.charCodeAt(0) === 0xFEFF) { content = content.slice(1); } return content; } /** * Inherit the prototype methods from one constructor into another * @param {function} constructor * @param {function} superConstructor * @param {object} [props] * @param {object} [descriptors] */ function inherits(constructor, superConstructor, props, descriptors) { constructor.prototype = Object.create(superConstructor.prototype, descriptors); constructor.prototype.constructor = constructor; props && Object.assign(constructor.prototype, props); } /** * Resolve object with deep prototype chain to a flat object * @param {Object} sourceObj source object * @param {Object} [destObj] * @param {Function} [filter] * @returns {Object} */ function toFlatObject(sourceObj, destObj, filter) { var props; var i; var prop; var merged = {}; destObj = destObj || {}; do { props = Object.getOwnPropertyNames(sourceObj); i = props.length; while (i-- > 0) { prop = props[i]; if (!merged[prop]) { destObj[prop] = sourceObj[prop]; merged[prop] = true; } } sourceObj = Object.getPrototypeOf(sourceObj); } while (sourceObj && (!filter || filter(sourceObj, destObj)) && sourceObj !== Object.prototype); return destObj; } /* * determines whether a string ends with the characters of a specified string * @param {String} str * @param {String} searchString * @param {Number} [position= 0] * @returns {boolean} */ function endsWith(str, searchString, position) { str = String(str); if (position === undefined || position > str.length) { position = str.length; } position -= searchString.length; var lastIndex = str.indexOf(searchString, position); return lastIndex !== -1 && lastIndex === position; } /** * Returns new array from array like object * @param {*} [thing] * @returns {Array} */ function toArray(thing) { if (!thing) return null; var i = thing.length; if (isUndefined(i)) return null; var arr = new Array(i); while (i-- > 0) { arr[i] = thing[i]; } return arr; } // eslint-disable-next-line func-names var isTypedArray = (function(TypedArray) { // eslint-disable-next-line func-names return function(thing) { return TypedArray && thing instanceof TypedArray; }; })(typeof Uint8Array !== 'undefined' && Object.getPrototypeOf(Uint8Array)); var utils = { isArray: isArray, isArrayBuffer: isArrayBuffer, isBuffer: isBuffer, isFormData: isFormData, isArrayBufferView: isArrayBufferView, isString: isString, isNumber: isNumber, isObject: isObject, isPlainObject: isPlainObject, isUndefined: isUndefined, isDate: isDate, isFile: isFile, isBlob: isBlob, isFunction: isFunction, isStream: isStream, isURLSearchParams: isURLSearchParams, isStandardBrowserEnv: isStandardBrowserEnv, forEach: forEach, merge: merge, extend: extend, trim: trim, stripBOM: stripBOM, inherits: inherits, toFlatObject: toFlatObject, kindOf: kindOf, kindOfTest: kindOfTest, endsWith: endsWith, toArray: toArray, isTypedArray: isTypedArray, isFileList: isFileList }; function encode(val) { return encodeURIComponent(val). replace(/%3A/gi, ':'). replace(/%24/g, '$'). replace(/%2C/gi, ','). replace(/%20/g, '+'). replace(/%5B/gi, '['). replace(/%5D/gi, ']'); } /** * Build a URL by appending params to the end * * @param {string} url The base of the url (e.g., http://www.google.com) * @param {object} [params] The params to be appended * @returns {string} The formatted url */ var buildURL = function buildURL(url, params, paramsSerializer) { /*eslint no-param-reassign:0*/ if (!params) { return url; } var serializedParams; if (paramsSerializer) { serializedParams = paramsSerializer(params); } else if (utils.isURLSearchParams(params)) { serializedParams = params.toString(); } else { var parts = []; utils.forEach(params, function serialize(val, key) { if (val === null || typeof val === 'undefined') { return; } if (utils.isArray(val)) { key = key + '[]'; } else { val = [val]; } utils.forEach(val, function parseValue(v) { if (utils.isDate(v)) { v = v.toISOString(); } else if (utils.isObject(v)) { v = JSON.stringify(v); } parts.push(encode(key) + '=' + encode(v)); }); }); serializedParams = parts.join('&'); } if (serializedParams) { var hashmarkIndex = url.indexOf('#'); if (hashmarkIndex !== -1) { url = url.slice(0, hashmarkIndex); } url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams; } return url; }; function InterceptorManager() { this.handlers = []; } /** * Add a new interceptor to the stack * * @param {Function} fulfilled The function to handle `then` for a `Promise` * @param {Function} rejected The function to handle `reject` for a `Promise` * * @return {Number} An ID used to remove interceptor later */ InterceptorManager.prototype.use = function use(fulfilled, rejected, options) { this.handlers.push({ fulfilled: fulfilled, rejected: rejected, synchronous: options ? options.synchronous : false, runWhen: options ? options.runWhen : null }); return this.handlers.length - 1; }; /** * Remove an interceptor from the stack * * @param {Number} id The ID that was returned by `use` */ InterceptorManager.prototype.eject = function eject(id) { if (this.handlers[id]) { this.handlers[id] = null; } }; /** * Iterate over all the registered interceptors * * This method is particularly useful for skipping over any * interceptors that may have become `null` calling `eject`. * * @param {Function} fn The function to call for each interceptor */ InterceptorManager.prototype.forEach = function forEach(fn) { utils.forEach(this.handlers, function forEachHandler(h) { if (h !== null) { fn(h); } }); }; var InterceptorManager_1 = InterceptorManager; var normalizeHeaderName = function normalizeHeaderName(headers, normalizedName) { utils.forEach(headers, function processHeader(value, name) { if (name !== normalizedName && name.toUpperCase() === normalizedName.toUpperCase()) { headers[normalizedName] = value; delete headers[name]; } }); }; /** * Create an Error with the specified message, config, error code, request and response. * * @param {string} message The error message. * @param {string} [code] The error code (for example, 'ECONNABORTED'). * @param {Object} [config] The config. * @param {Object} [request] The request. * @param {Object} [response] The response. * @returns {Error} The created error. */ function AxiosError(message, code, config, request, response) { Error.call(this); this.message = message; this.name = 'AxiosError'; code && (this.code = code); config && (this.config = config); request && (this.request = request); response && (this.response = response); } utils.inherits(AxiosError, Error, { toJSON: function toJSON() { return { // Standard message: this.message, name: this.name, // Microsoft description: this.description, number: this.number, // Mozilla fileName: this.fileName, lineNumber: this.lineNumber, columnNumber: this.columnNumber, stack: this.stack, // Axios config: this.config, code: this.code, status: this.response && this.response.status ? this.response.status : null }; } }); var prototype = AxiosError.prototype; var descriptors = {}; [ 'ERR_BAD_OPTION_VALUE', 'ERR_BAD_OPTION', 'ECONNABORTED', 'ETIMEDOUT', 'ERR_NETWORK', 'ERR_FR_TOO_MANY_REDIRECTS', 'ERR_DEPRECATED', 'ERR_BAD_RESPONSE', 'ERR_BAD_REQUEST', 'ERR_CANCELED' // eslint-disable-next-line func-names ].forEach(function(code) { descriptors[code] = {value: code}; }); Object.defineProperties(AxiosError, descriptors); Object.defineProperty(prototype, 'isAxiosError', {value: true}); // eslint-disable-next-line func-names AxiosError.from = function(error, code, config, request, response, customProps) { var axiosError = Object.create(prototype); utils.toFlatObject(error, axiosError, function filter(obj) { return obj !== Error.prototype; }); AxiosError.call(axiosError, error.message, code, config, request, response); axiosError.name = error.name; customProps && Object.assign(axiosError, customProps); return axiosError; }; var AxiosError_1 = AxiosError; var transitional = { silentJSONParsing: true, forcedJSONParsing: true, clarifyTimeoutError: false }; /** * Convert a data object to FormData * @param {Object} obj * @param {?Object} [formData] * @returns {Object} **/ function toFormData(obj, formData) { // eslint-disable-next-line no-param-reassign formData = formData || new FormData(); var stack = []; function convertValue(value) { if (value === null) return ''; if (utils.isDate(value)) { return value.toISOString(); } if (utils.isArrayBuffer(value) || utils.isTypedArray(value)) { return typeof Blob === 'function' ? new Blob([value]) : Buffer.from(value); } return value; } function build(data, parentKey) { if (utils.isPlainObject(data) || utils.isArray(data)) { if (stack.indexOf(data) !== -1) { throw Error('Circular reference detected in ' + parentKey); } stack.push(data); utils.forEach(data, function each(value, key) { if (utils.isUndefined(value)) return; var fullKey = parentKey ? parentKey + '.' + key : key; var arr; if (value && !parentKey && typeof value === 'object') { if (utils.endsWith(key, '{}')) { // eslint-disable-next-line no-param-reassign value = JSON.stringify(value); } else if (utils.endsWith(key, '[]') && (arr = utils.toArray(value))) { // eslint-disable-next-line func-names arr.forEach(function(el) { !utils.isUndefined(el) && formData.append(fullKey, convertValue(el)); }); return; } } build(value, fullKey); }); stack.pop(); } else { formData.append(parentKey, convertValue(data)); } } build(obj); return formData; } var toFormData_1 = toFormData; /** * Resolve or reject a Promise based on response status. * * @param {Function} resolve A function that resolves the promise. * @param {Function} reject A function that rejects the promise. * @param {object} response The response. */ var settle = function settle(resolve, reject, response) { var validateStatus = response.config.validateStatus; if (!response.status || !validateStatus || validateStatus(response.status)) { resolve(response); } else { reject(new AxiosError_1( '请求失败, 状态码: ' + response.status, [AxiosError_1.ERR_BAD_REQUEST, AxiosError_1.ERR_BAD_RESPONSE][Math.floor(response.status / 100) - 4], response.config, response.request, response )); } }; var cookies = ( utils.isStandardBrowserEnv() ? // Standard browser envs support document.cookie (function standardBrowserEnv() { return { write: function write(name, value, expires, path, domain, secure) { var cookie = []; cookie.push(name + '=' + encodeURIComponent(value)); if (utils.isNumber(expires)) { cookie.push('expires=' + new Date(expires).toGMTString()); } if (utils.isString(path)) { cookie.push('path=' + path); } if (utils.isString(domain)) { cookie.push('domain=' + domain); } if (secure === true) { cookie.push('secure'); } document.cookie = cookie.join('; '); }, read: function read(name) { var match = document.cookie.match(new RegExp('(^|;\\s*)(' + name + ')=([^;]*)')); return (match ? decodeURIComponent(match[3]) : null); }, remove: function remove(name) { this.write(name, '', Date.now() - 86400000); } }; })() : // Non standard browser env (web workers, react-native) lack needed support. (function nonStandardBrowserEnv() { return { write: function write() {}, read: function read() { return null; }, remove: function remove() {} }; })() ); /** * Determines whether the specified URL is absolute * * @param {string} url The URL to test * @returns {boolean} True if the specified URL is absolute, otherwise false */ var isAbsoluteURL = function isAbsoluteURL(url) { // A URL is considered absolute if it begins with "://" or "//" (protocol-relative URL). // RFC 3986 defines scheme name as a sequence of characters beginning with a letter and followed // by any combination of letters, digits, plus, period, or hyphen. return /^([a-z][a-z\d+\-.]*:)?\/\//i.test(url); }; /** * Creates a new URL by combining the specified URLs * * @param {string} baseURL The base URL * @param {string} relativeURL The relative URL * @returns {string} The combined URL */ var combineURLs = function combineURLs(baseURL, relativeURL) { return relativeURL ? baseURL.replace(/\/+$/, '') + '/' + relativeURL.replace(/^\/+/, '') : baseURL; }; /** * Creates a new URL by combining the baseURL with the requestedURL, * only when the requestedURL is not already an absolute URL. * If the requestURL is absolute, this function returns the requestedURL untouched. * * @param {string} baseURL The base URL * @param {string} requestedURL Absolute or relative URL to combine * @returns {string} The combined full path */ var buildFullPath = function buildFullPath(baseURL, requestedURL) { if (baseURL && !isAbsoluteURL(requestedURL)) { return combineURLs(baseURL, requestedURL); } return requestedURL; }; // Headers whose duplicates are ignored by node // c.f. https://nodejs.org/api/http.html#http_message_headers var ignoreDuplicateOf = [ 'age', 'authorization', 'content-length', 'content-type', 'etag', 'expires', 'from', 'host', 'if-modified-since', 'if-unmodified-since', 'last-modified', 'location', 'max-forwards', 'proxy-authorization', 'referer', 'retry-after', 'user-agent' ]; /** * Parse headers into an object * * ``` * Date: Wed, 27 Aug 2014 08:58:49 GMT * Content-Type: application/json * Connection: keep-alive * Transfer-Encoding: chunked * ``` * * @param {String} headers Headers needing to be parsed * @returns {Object} Headers parsed into an object */ var parseHeaders = function parseHeaders(headers) { var parsed = {}; var key; var val; var i; if (!headers) { return parsed; } utils.forEach(headers.split('\n'), function parser(line) { i = line.indexOf(':'); key = utils.trim(line.substr(0, i)).toLowerCase(); val = utils.trim(line.substr(i + 1)); if (key) { if (parsed[key] && ignoreDuplicateOf.indexOf(key) >= 0) { return; } if (key === 'set-cookie') { parsed[key] = (parsed[key] ? parsed[key] : []).concat([val]); } else { parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val; } } }); return parsed; }; var isURLSameOrigin = ( utils.isStandardBrowserEnv() ? // Standard browser envs have full support of the APIs needed to test // whether the request URL is of the same origin as current location. (function standardBrowserEnv() { var msie = /(msie|trident)/i.test(navigator.userAgent); var urlParsingNode = document.createElement('a'); var originURL; /** * Parse a URL to discover it's components * * @param {String} url The URL to be parsed * @returns {Object} */ function resolveURL(url) { var href = url; if (msie) { // IE needs attribute set twice to normalize properties urlParsingNode.setAttribute('href', href); href = urlParsingNode.href; } urlParsingNode.setAttribute('href', href); // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils return { href: urlParsingNode.href, protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '', host: urlParsingNode.host, search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '', hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '', hostname: urlParsingNode.hostname, port: urlParsingNode.port, pathname: (urlParsingNode.pathname.charAt(0) === '/') ? urlParsingNode.pathname : '/' + urlParsingNode.pathname }; } originURL = resolveURL(window.location.href); /** * Determine if a URL shares the same origin as the current location * * @param {String} requestURL The URL to test * @returns {boolean} True if URL shares the same origin, otherwise false */ return function isURLSameOrigin(requestURL) { var parsed = (utils.isString(requestURL)) ? resolveURL(requestURL) : requestURL; return (parsed.protocol === originURL.protocol && parsed.host === originURL.host); }; })() : // Non standard browser envs (web workers, react-native) lack needed support. (function nonStandardBrowserEnv() { return function isURLSameOrigin() { return true; }; })() ); /** * A `CanceledError` is an object that is thrown when an operation is canceled. * * @class * @param {string=} message The message. */ function CanceledError(message) { // eslint-disable-next-line no-eq-null,eqeqeq AxiosError_1.call(this, message == null ? 'canceled' : message, AxiosError_1.ERR_CANCELED); this.name = 'CanceledError'; } utils.inherits(CanceledError, AxiosError_1, { __CANCEL__: true }); var CanceledError_1 = CanceledError; var parseProtocol = function parseProtocol(url) { var match = /^([-+\w]{1,25})(:?\/\/|:)/.exec(url); return match && match[1] || ''; }; var xhr = function xhrAdapter(config) { return new Promise(function dispatchXhrRequest(resolve, reject) { var requestData = config.data; var requestHeaders = config.headers; var responseType = config.responseType; var onCanceled; function done() { if (config.cancelToken) { config.cancelToken.unsubscribe(onCanceled); } if (config.signal) { config.signal.removeEventListener('abort', onCanceled); } } if (utils.isFormData(requestData) && utils.isStandardBrowserEnv()) { delete requestHeaders['Content-Type']; // Let the browser set it } var request = new XMLHttpRequest(); // HTTP basic authentication if (config.auth) { var username = config.auth.username || ''; var password = config.auth.password ? unescape(encodeURIComponent(config.auth.password)) : ''; requestHeaders.Authorization = 'Basic ' + btoa(username + ':' + password); } var fullPath = buildFullPath(config.baseURL, config.url); request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true); // Set the request timeout in MS request.timeout = config.timeout; function onloadend() { if (!request) { return; } // Prepare the response var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null; var responseData = !responseType || responseType === 'text' || responseType === 'json' ? request.responseText : request.response; var response = { data: responseData, status: request.status, statusText: request.statusText, headers: responseHeaders, config: config, request: request }; settle(function _resolve(value) { resolve(value); done(); }, function _reject(err) { reject(err); done(); }, response); // Clean up request request = null; } if ('onloadend' in request) { // Use onloadend if available request.onloadend = onloadend; } else { // Listen for ready state to emulate onloadend request.onreadystatechange = function handleLoad() { if (!request || request.readyState !== 4) { return; } // The request errored out and we didn't get a response, this will be // handled by onerror instead // With one exception: request that using file: protocol, most browsers // will return status as 0 even though it's a successful request if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) { return; } // readystate handler is calling before onerror or ontimeout handlers, // so we should call onloadend on the next 'tick' setTimeout(onloadend); }; } // Handle browser request cancellation (as opposed to a manual cancellation) request.onabort = function handleAbort() { if (!request) { return; } reject(new AxiosError_1('Request aborted', AxiosError_1.ECONNABORTED, config, request)); // Clean up request request = null; }; // Handle low level network errors request.onerror = function handleError() { // Real errors are hidden from us by the browser // onerror should only fire if it's a network error reject(new AxiosError_1('Network Error', AxiosError_1.ERR_NETWORK, config, request, request)); // Clean up request request = null; }; // Handle timeout request.ontimeout = function handleTimeout() { var timeoutErrorMessage = config.timeout ? 'timeout of ' + config.timeout + 'ms exceeded' : 'timeout exceeded'; var transitional$1 = config.transitional || transitional; if (config.timeoutErrorMessage) { timeoutErrorMessage = config.timeoutErrorMessage; } reject(new AxiosError_1( timeoutErrorMessage, transitional$1.clarifyTimeoutError ? AxiosError_1.ETIMEDOUT : AxiosError_1.ECONNABORTED, config, request)); // Clean up request request = null; }; // Add xsrf header // This is only done if running in a standard browser environment. // Specifically not if we're in a web worker, or react-native. if (utils.isStandardBrowserEnv()) { // Add xsrf header var xsrfValue = (config.withCredentials || isURLSameOrigin(fullPath)) && config.xsrfCookieName ? cookies.read(config.xsrfCookieName) : undefined; if (xsrfValue) { requestHeaders[config.xsrfHeaderName] = xsrfValue; } } // Add headers to the request if ('setRequestHeader' in request) { utils.forEach(requestHeaders, function setRequestHeader(val, key) { if (typeof requestData === 'undefined' && key.toLowerCase() === 'content-type') { // Remove Content-Type if data is undefined delete requestHeaders[key]; } else { // Otherwise add header to the request request.setRequestHeader(key, val); } }); } // Add withCredentials to request if needed if (!utils.isUndefined(config.withCredentials)) { request.withCredentials = !!config.withCredentials; } // Add responseType to request if needed if (responseType && responseType !== 'json') { request.responseType = config.responseType; } // Handle progress if needed if (typeof config.onDownloadProgress === 'function') { request.addEventListener('progress', config.onDownloadProgress); } // Not all browsers support upload events if (typeof config.onUploadProgress === 'function' && request.upload) { request.upload.addEventListener('progress', config.onUploadProgress); } if (config.cancelToken || config.signal) { // Handle cancellation // eslint-disable-next-line func-names onCanceled = function(cancel) { if (!request) { return; } reject(!cancel || (cancel && cancel.type) ? new CanceledError_1() : cancel); request.abort(); request = null; }; config.cancelToken && config.cancelToken.subscribe(onCanceled); if (config.signal) { config.signal.aborted ? onCanceled() : config.signal.addEventListener('abort', onCanceled); } } if (!requestData) { requestData = null; } var protocol = parseProtocol(fullPath); if (protocol && [ 'http', 'https', 'file' ].indexOf(protocol) === -1) { reject(new AxiosError_1('Unsupported protocol ' + protocol + ':', AxiosError_1.ERR_BAD_REQUEST, config)); return; } // Send the request request.send(requestData); }); }; // eslint-disable-next-line strict var _null = null; var DEFAULT_CONTENT_TYPE = { 'Content-Type': 'application/x-www-form-urlencoded' }; function setContentTypeIfUnset(headers, value) { if (!utils.isUndefined(headers) && utils.isUndefined(headers['Content-Type'])) { headers['Content-Type'] = value; } } function getDefaultAdapter() { var adapter; if (typeof XMLHttpRequest !== 'undefined') { // For browsers use XHR adapter adapter = xhr; } else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') { // For node use HTTP adapter adapter = xhr; } return adapter; } function stringifySafely(rawValue, parser, encoder) { if (utils.isString(rawValue)) { try { (parser || JSON.parse)(rawValue); return utils.trim(rawValue); } catch (e) { if (e.name !== 'SyntaxError') { throw e; } } } return (encoder || JSON.stringify)(rawValue); } var defaults = { transitional: transitional, adapter: getDefaultAdapter(), transformRequest: [function transformRequest(data, headers) { normalizeHeaderName(headers, 'Accept'); normalizeHeaderName(headers, 'Content-Type'); if (utils.isFormData(data) || utils.isArrayBuffer(data) || utils.isBuffer(data) || utils.isStream(data) || utils.isFile(data) || utils.isBlob(data) ) { return data; } if (utils.isArrayBufferView(data)) { return data.buffer; } if (utils.isURLSearchParams(data)) { setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8'); return data.toString(); } var isObjectPayload = utils.isObject(data); var contentType = headers && headers['Content-Type']; var isFileList; if ((isFileList = utils.isFileList(data)) || (isObjectPayload && contentType === 'multipart/form-data')) { var _FormData = this.env && this.env.FormData; return toFormData_1(isFileList ? {'files[]': data} : data, _FormData && new _FormData()); } else if (isObjectPayload || contentType === 'application/json') { setContentTypeIfUnset(headers, 'application/json'); return stringifySafely(data); } return data; }], transformResponse: [function transformResponse(data) { var transitional = this.transitional || defaults.transitional; var silentJSONParsing = transitional && transitional.silentJSONParsing; var forcedJSONParsing = transitional && transitional.forcedJSONParsing; var strictJSONParsing = !silentJSONParsing && this.responseType === 'json'; if (strictJSONParsing || (forcedJSONParsing && utils.isString(data) && data.length)) { try { return JSON.parse(data); } catch (e) { if (strictJSONParsing) { if (e.name === 'SyntaxError') { throw AxiosError_1.from(e, AxiosError_1.ERR_BAD_RESPONSE, this, null, this.response); } throw e; } } } return data; }], /** * A timeout in milliseconds to abort a request. If set to 0 (default) a * timeout is not created. */ timeout: 0, xsrfCookieName: 'XSRF-TOKEN', xsrfHeaderName: 'X-XSRF-TOKEN', maxContentLength: -1, maxBodyLength: -1, env: { FormData: _null }, validateStatus: function validateStatus(status) { return status >= 200 && status < 300; }, headers: { common: { 'Accept': 'application/json, text/plain, */*' } } }; utils.forEach(['delete', 'get', 'head'], function forEachMethodNoData(method) { defaults.headers[method] = {}; }); utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) { defaults.headers[method] = utils.merge(DEFAULT_CONTENT_TYPE); }); var defaults_1 = defaults; /** * Transform the data for a request or a response * * @param {Object|String} data The data to be transformed * @param {Array} headers The headers for the request or response * @param {Array|Function} fns A single function or Array of functions * @returns {*} The resulting transformed data */ var transformData = function transformData(data, headers, fns) { var context = this || defaults_1; /*eslint no-param-reassign:0*/ utils.forEach(fns, function transform(fn) { data = fn.call(context, data, headers); }); return data; }; var isCancel = function isCancel(value) { return !!(value && value.__CANCEL__); }; /** * Throws a `CanceledError` if cancellation has been requested. */ function throwIfCancellationRequested(config) { if (config.cancelToken) { config.cancelToken.throwIfRequested(); } if (config.signal && config.signal.aborted) { throw new CanceledError_1(); } } /** * Dispatch a request to the server using the configured adapter. * * @param {object} config The config that is to be used for the request * @returns {Promise} The Promise to be fulfilled */ var dispatchRequest = function dispatchRequest(config) { throwIfCancellationRequested(config); // Ensure headers exist config.headers = config.headers || {}; // Transform request data config.data = transformData.call( config, config.data, config.headers, config.transformRequest ); // Flatten headers config.headers = utils.merge( config.headers.common || {}, config.headers[config.method] || {}, config.headers ); utils.forEach( ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'], function cleanHeaderConfig(method) { delete config.headers[method]; } ); var adapter = config.adapter || defaults_1.adapter; return adapter(config).then(function onAdapterResolution(response) { throwIfCancellationRequested(config); // Transform response data response.data = transformData.call( config, response.data, response.headers, config.transformResponse ); return response; }, function onAdapterRejection(reason) { if (!isCancel(reason)) { throwIfCancellationRequested(config); // Transform response data if (reason && reason.response) { reason.response.data = transformData.call( config, reason.response.data, reason.response.headers, config.transformResponse ); } } return Promise.reject(reason); }); }; /** * Config-specific merge-function which creates a new config-object * by merging two configuration objects together. * * @param {Object} config1 * @param {Object} config2 * @returns {Object} New object resulting from merging config2 to config1 */ var mergeConfig = function mergeConfig(config1, config2) { // eslint-disable-next-line no-param-reassign config2 = config2 || {}; var config = {}; function getMergedValue(target, source) { if (utils.isPlainObject(target) && utils.isPlainObject(source)) { return utils.merge(target, source); } else if (utils.isPlainObject(source)) { return utils.merge({}, source); } else if (utils.isArray(source)) { return source.slice(); } return source; } // eslint-disable-next-line consistent-return function mergeDeepProperties(prop) { if (!utils.isUndefined(config2[prop])) { return getMergedValue(config1[prop], config2[prop]); } else if (!utils.isUndefined(config1[prop])) { return getMergedValue(undefined, config1[prop]); } } // eslint-disable-next-line consistent-return function valueFromConfig2(prop) { if (!utils.isUndefined(config2[prop])) { return getMergedValue(undefined, config2[prop]); } } // eslint-disable-next-line consistent-return function defaultToConfig2(prop) { if (!utils.isUndefined(config2[prop])) { return getMergedValue(undefined, config2[prop]); } else if (!utils.isUndefined(config1[prop])) { return getMergedValue(undefined, config1[prop]); } } // eslint-disable-next-line consistent-return function mergeDirectKeys(prop) { if (prop in config2) { return getMergedValue(config1[prop], config2[prop]); } else if (prop in config1) { return getMergedValue(undefined, config1[prop]); } } var mergeMap = { 'url': valueFromConfig2, 'method': valueFromConfig2, 'data': valueFromConfig2, 'baseURL': defaultToConfig2, 'transformRequest': defaultToConfig2, 'transformResponse': defaultToConfig2, 'paramsSerializer': defaultToConfig2, 'timeout': defaultToConfig2, 'timeoutMessage': defaultToConfig2, 'withCredentials': defaultToConfig2, 'adapter': defaultToConfig2, 'responseType': defaultToConfig2, 'xsrfCookieName': defaultToConfig2, 'xsrfHeaderName': defaultToConfig2, 'onUploadProgress': defaultToConfig2, 'onDownloadProgress': defaultToConfig2, 'decompress': defaultToConfig2, 'maxContentLength': defaultToConfig2, 'maxBodyLength': defaultToConfig2, 'beforeRedirect': defaultToConfig2, 'transport': defaultToConfig2, 'httpAgent': defaultToConfig2, 'httpsAgent': defaultToConfig2, 'cancelToken': defaultToConfig2, 'socketPath': defaultToConfig2, 'responseEncoding': defaultToConfig2, 'validateStatus': mergeDirectKeys }; utils.forEach(Object.keys(config1).concat(Object.keys(config2)), function computeConfigValue(prop) { var merge = mergeMap[prop] || mergeDeepProperties; var configValue = merge(prop); (utils.isUndefined(configValue) && merge !== mergeDirectKeys) || (config[prop] = configValue); }); return config; }; var data = { "version": "0.27.2" }; var VERSION = data.version; var validators$1 = {}; // eslint-disable-next-line func-names ['object', 'boolean', 'number', 'function', 'string', 'symbol'].forEach(function(type, i) { validators$1[type] = function validator(thing) { return typeof thing === type || 'a' + (i < 1 ? 'n ' : ' ') + type; }; }); var deprecatedWarnings = {}; /** * Transitional option validator * @param {function|boolean?} validator - set to false if the transitional option has been removed * @param {string?} version - deprecated version / removed since version * @param {string?} message - some message with additional info * @returns {function} */ validators$1.transitional = function transitional(validator, version, message) { function formatMessage(opt, desc) { return '[Axios v' + VERSION + '] Transitional option \'' + opt + '\'' + desc + (message ? '. ' + message : ''); } // eslint-disable-next-line func-names return function(value, opt, opts) { if (validator === false) { throw new AxiosError_1( formatMessage(opt, ' has been removed' + (version ? ' in ' + version : '')), AxiosError_1.ERR_DEPRECATED ); } if (version && !deprecatedWarnings[opt]) { deprecatedWarnings[opt] = true; // eslint-disable-next-line no-console console.warn( formatMessage( opt, ' has been deprecated since v' + version + ' and will be removed in the near future' ) ); } return validator ? validator(value, opt, opts) : true; }; }; /** * Assert object's properties type * @param {object} options * @param {object} schema * @param {boolean?} allowUnknown */ function assertOptions(options, schema, allowUnknown) { if (typeof options !== 'object') { throw new AxiosError_1('options must be an object', AxiosError_1.ERR_BAD_OPTION_VALUE); } var keys = Object.keys(options); var i = keys.length; while (i-- > 0) { var opt = keys[i]; var validator = schema[opt]; if (validator) { var value = options[opt]; var result = value === undefined || validator(value, opt, options); if (result !== true) { throw new AxiosError_1('option ' + opt + ' must be ' + result, AxiosError_1.ERR_BAD_OPTION_VALUE); } continue; } if (allowUnknown !== true) { throw new AxiosError_1('Unknown option ' + opt, AxiosError_1.ERR_BAD_OPTION); } } } var validator = { assertOptions: assertOptions, validators: validators$1 }; var validators = validator.validators; /** * Create a new instance of Axios * * @param {Object} instanceConfig The default config for the instance */ function Axios(instanceConfig) { this.defaults = instanceConfig; this.interceptors = { request: new InterceptorManager_1(), response: new InterceptorManager_1() }; } /** * Dispatch a request * * @param {Object} config The config specific for this request (merged with this.defaults) */ Axios.prototype.request = function request(configOrUrl, config) { /*eslint no-param-reassign:0*/ // Allow for axios('example/url'[, config]) a la fetch API if (typeof configOrUrl === 'string') { config = config || {}; config.url = configOrUrl; } else { config = configOrUrl || {}; } config = mergeConfig(this.defaults, config); // Set config.method if (config.method) { config.method = config.method.toLowerCase(); } else if (this.defaults.method) { config.method = this.defaults.method.toLowerCase(); } else { config.method = 'get'; } var transitional = config.transitional; if (transitional !== undefined) { validator.assertOptions(transitional, { silentJSONParsing: validators.transitional(validators.boolean), forcedJSONParsing: validators.transitional(validators.boolean), clarifyTimeoutError: validators.transitional(validators.boolean) }, false); } // filter out skipped interceptors var requestInterceptorChain = []; var synchronousRequestInterceptors = true; this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) { if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) { return; } synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous; requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected); }); var responseInterceptorChain = []; this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) { responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected); }); var promise; if (!synchronousRequestInterceptors) { var chain = [dispatchRequest, undefined]; Array.prototype.unshift.apply(chain, requestInterceptorChain); chain = chain.concat(responseInterceptorChain); promise = Promise.resolve(config); while (chain.length) { promise = promise.then(chain.shift(), chain.shift()); } return promise; } var newConfig = config; while (requestInterceptorChain.length) { var onFulfilled = requestInterceptorChain.shift(); var onRejected = requestInterceptorChain.shift(); try { newConfig = onFulfilled(newConfig); } catch (error) { onRejected(error); break; } } try { promise = dispatchRequest(newConfig); } catch (error) { return Promise.reject(error); } while (responseInterceptorChain.length) { promise = promise.then(responseInterceptorChain.shift(), responseInterceptorChain.shift()); } return promise; }; Axios.prototype.getUri = function getUri(config) { config = mergeConfig(this.defaults, config); var fullPath = buildFullPath(config.baseURL, config.url); return buildURL(fullPath, config.params, config.paramsSerializer); }; // Provide aliases for supported request methods utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) { /*eslint func-names:0*/ Axios.prototype[method] = function(url, config) { return this.request(mergeConfig(config || {}, { method: method, url: url, data: (config || {}).data })); }; }); utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) { /*eslint func-names:0*/ function generateHTTPMethod(isForm) { return function httpMethod(url, data, config) { return this.request(mergeConfig(config || {}, { method: method, headers: isForm ? { 'Content-Type': 'multipart/form-data' } : {}, url: url, data: data })); }; } Axios.prototype[method] = generateHTTPMethod(); Axios.prototype[method + 'Form'] = generateHTTPMethod(true); }); var Axios_1 = Axios; /** * A `CancelToken` is an object that can be used to request cancellation of an operation. * * @class * @param {Function} executor The executor function. */ function CancelToken(executor) { if (typeof executor !== 'function') { throw new TypeError('executor must be a function.'); } var resolvePromise; this.promise = new Promise(function promiseExecutor(resolve) { resolvePromise = resolve; }); var token = this; // eslint-disable-next-line func-names this.promise.then(function(cancel) { if (!token._listeners) return; var i; var l = token._listeners.length; for (i = 0; i < l; i++) { token._listeners[i](cancel); } token._listeners = null; }); // eslint-disable-next-line func-names this.promise.then = function(onfulfilled) { var _resolve; // eslint-disable-next-line func-names var promise = new Promise(function(resolve) { token.subscribe(resolve); _resolve = resolve; }).then(onfulfilled); promise.cancel = function reject() { token.unsubscribe(_resolve); }; return promise; }; executor(function cancel(message) { if (token.reason) { // Cancellation has already been requested return; } token.reason = new CanceledError_1(message); resolvePromise(token.reason); }); } /** * Throws a `CanceledError` if cancellation has been requested. */ CancelToken.prototype.throwIfRequested = function throwIfRequested() { if (this.reason) { throw this.reason; } }; /** * Subscribe to the cancel signal */ CancelToken.prototype.subscribe = function subscribe(listener) { if (this.reason) { listener(this.reason); return; } if (this._listeners) { this._listeners.push(listener); } else { this._listeners = [listener]; } }; /** * Unsubscribe from the cancel signal */ CancelToken.prototype.unsubscribe = function unsubscribe(listener) { if (!this._listeners) { return; } var index = this._listeners.indexOf(listener); if (index !== -1) { this._listeners.splice(index, 1); } }; /** * Returns an object that contains a new `CancelToken` and a function that, when called, * cancels the `CancelToken`. */ CancelToken.source = function source() { var cancel; var token = new CancelToken(function executor(c) { cancel = c; }); return { token: token, cancel: cancel }; }; var CancelToken_1 = CancelToken; /** * Syntactic sugar for invoking a function and expanding an array for arguments. * * Common use case would be to use `Function.prototype.apply`. * * ```js * function f(x, y, z) {} * var args = [1, 2, 3]; * f.apply(null, args); * ``` * * With `spread` this example can be re-written. * * ```js * spread(function(x, y, z) {})([1, 2, 3]); * ``` * * @param {Function} callback * @returns {Function} */ var spread = function spread(callback) { return function wrap(arr) { return callback.apply(null, arr); }; }; /** * Determines whether the payload is an error thrown by Axios * * @param {*} payload The value to test * @returns {boolean} True if the payload is an error thrown by Axios, otherwise false */ var isAxiosError = function isAxiosError(payload) { return utils.isObject(payload) && (payload.isAxiosError === true); }; /** * Create an instance of Axios * * @param {Object} defaultConfig The default config for the instance * @return {Axios} A new instance of Axios */ function createInstance(defaultConfig) { var context = new Axios_1(defaultConfig); var instance = bind(Axios_1.prototype.request, context); // Copy axios.prototype to instance utils.extend(instance, Axios_1.prototype, context); // Copy context to instance utils.extend(instance, context); // Factory for creating new instances instance.create = function create(instanceConfig) { return createInstance(mergeConfig(defaultConfig, instanceConfig)); }; return instance; } // Create the default instance to be exported var axios$1 = createInstance(defaults_1); // Expose Axios class to allow class inheritance axios$1.Axios = Axios_1; // Expose Cancel & CancelToken axios$1.CanceledError = CanceledError_1; axios$1.CancelToken = CancelToken_1; axios$1.isCancel = isCancel; axios$1.VERSION = data.version; axios$1.toFormData = toFormData_1; // Expose AxiosError class axios$1.AxiosError = AxiosError_1; // alias for CanceledError for backward compatibility axios$1.Cancel = axios$1.CanceledError; // Expose all/spread axios$1.all = function all(promises) { return Promise.all(promises); }; axios$1.spread = spread; // Expose isAxiosError axios$1.isAxiosError = isAxiosError; var axios_1 = axios$1; // Allow use of default import syntax in TypeScript var _default = axios$1; axios_1.default = _default; var axios = axios_1; class RTCEndpoint extends Event$1 { constructor(options) { super('RTCPusherPlayer'); this.TAG = '[RTCPusherPlayer]'; let defaults = { element: '', // html video element debug: false, // if output debug log zlmsdpUrl: '', simulcast: false, useCamera: true, audioEnable: true, videoEnable: true, recvOnly: false, resolution: { w: 0, h: 0 }, usedatachannel: false }; this.options = Object.assign({}, defaults, options); if (this.options.debug) { setLogger(); } this.e = { onicecandidate: this._onIceCandidate.bind(this), ontrack: this._onTrack.bind(this), onicecandidateerror: this._onIceCandidateError.bind(this), onconnectionstatechange: this._onconnectionstatechange.bind(this), ondatachannelopen: this._onDataChannelOpen.bind(this), ondatachannelmsg: this._onDataChannelMsg.bind(this), ondatachannelerr: this._onDataChannelErr.bind(this), ondatachannelclose: this._onDataChannelClose.bind(this) }; this._remoteStream = null; this._localStream = null; this._tracks = []; this.pc = new RTCPeerConnection(null); this.pc.onicecandidate = this.e.onicecandidate; this.pc.onicecandidateerror = this.e.onicecandidateerror; this.pc.ontrack = this.e.ontrack; this.pc.onconnectionstatechange = this.e.onconnectionstatechange; this.datachannel = null; if (this.options.usedatachannel) { this.datachannel = this.pc.createDataChannel('chat'); this.datachannel.onclose = this.e.ondatachannelclose; this.datachannel.onerror = this.e.ondatachannelerr; this.datachannel.onmessage = this.e.ondatachannelmsg; this.datachannel.onopen = this.e.ondatachannelopen; } if (!this.options.recvOnly && (this.options.audioEnable || this.options.videoEnable)) this.start();else this.receive(); } receive() { //debug.error(this.TAG,'this not implement'); const AudioTransceiverInit = { direction: 'recvonly', sendEncodings: [] }; const VideoTransceiverInit = { direction: 'recvonly', sendEncodings: [] }; if (this.options.videoEnable) { this.pc.addTransceiver('video', VideoTransceiverInit); } if (this.options.audioEnable) { this.pc.addTransceiver('audio', AudioTransceiverInit); } this.pc.createOffer().then(desc => { log(this.TAG, 'offer:', desc.sdp); this.pc.setLocalDescription(desc).then(() => { axios({ method: 'post', url: this.options.zlmsdpUrl, responseType: 'json', data: desc.sdp, headers: { 'Content-Type': 'text/plain;charset=utf-8' } }).then(response => { let ret = response.data; //JSON.parse(response.data); if (ret.code != 0) { // mean failed for offer/anwser exchange this.dispatch(Events$1.WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED, ret); return; } let anwser = {}; anwser.sdp = ret.sdp; anwser.type = 'answer'; log(this.TAG, 'answer:', ret.sdp); this.pc.setRemoteDescription(anwser).then(() => { log(this.TAG, 'set remote sucess'); }).catch(e => { error(this.TAG, e); }); }); }); }).catch(e => { error(this.TAG, e); }); } start() { let videoConstraints = false; let audioConstraints = false; if (this.options.useCamera) { if (this.options.videoEnable) videoConstraints = new VideoTrackConstraints(VideoSourceInfo.CAMERA); if (this.options.audioEnable) audioConstraints = new AudioTrackConstraints(AudioSourceInfo.MIC); } else { if (this.options.videoEnable) { videoConstraints = new VideoTrackConstraints(VideoSourceInfo.SCREENCAST); if (this.options.audioEnable) audioConstraints = new AudioTrackConstraints(AudioSourceInfo.SCREENCAST); } else { if (this.options.audioEnable) audioConstraints = new AudioTrackConstraints(AudioSourceInfo.MIC);else { // error shared display media not only audio error(this.TAG, 'error paramter'); } } } if (this.options.resolution.w != 0 && this.options.resolution.h != 0 && typeof videoConstraints == 'object') { videoConstraints.resolution = new Resolution(this.options.resolution.w, this.options.resolution.h); } MediaStreamFactory.createMediaStream(new StreamConstraints(audioConstraints, videoConstraints)).then(stream => { this._localStream = stream; this.dispatch(Events$1.WEBRTC_ON_LOCAL_STREAM, stream); const AudioTransceiverInit = { direction: 'sendrecv', sendEncodings: [] }; const VideoTransceiverInit = { direction: 'sendrecv', sendEncodings: [] }; if (this.options.simulcast && stream.getVideoTracks().length > 0) { VideoTransceiverInit.sendEncodings = [{ rid: 'h', active: true, maxBitrate: 1000000 }, { rid: 'm', active: true, maxBitrate: 500000, scaleResolutionDownBy: 2 }, { rid: 'l', active: true, maxBitrate: 200000, scaleResolutionDownBy: 4 }]; } if (this.options.audioEnable) { if (stream.getAudioTracks().length > 0) { this.pc.addTransceiver(stream.getAudioTracks()[0], AudioTransceiverInit); } else { AudioTransceiverInit.direction = 'recvonly'; this.pc.addTransceiver('audio', AudioTransceiverInit); } } if (this.options.videoEnable) { if (stream.getVideoTracks().length > 0) { this.pc.addTransceiver(stream.getVideoTracks()[0], VideoTransceiverInit); } else { VideoTransceiverInit.direction = 'recvonly'; this.pc.addTransceiver('video', VideoTransceiverInit); } } /* stream.getTracks().forEach((track,idx)=>{ debug.log(this.TAG,track); this.pc.addTrack(track); }); */ this.pc.createOffer().then(desc => { log(this.TAG, 'offer:', desc.sdp); this.pc.setLocalDescription(desc).then(() => { axios({ method: 'post', url: this.options.zlmsdpUrl, responseType: 'json', data: desc.sdp, headers: { 'Content-Type': 'text/plain;charset=utf-8' } }).then(response => { let ret = response.data; //JSON.parse(response.data); if (ret.code != 0) { // mean failed for offer/anwser exchange this.dispatch(Events$1.WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED, ret); return; } let anwser = {}; anwser.sdp = ret.sdp; anwser.type = 'answer'; log(this.TAG, 'answer:', ret.sdp); this.pc.setRemoteDescription(anwser).then(() => { log(this.TAG, 'set remote sucess'); }).catch(e => { error(this.TAG, e); }); }); }); }).catch(e => { error(this.TAG, e); }); }).catch(e => { this.dispatch(Events$1.CAPTURE_STREAM_FAILED); //debug.error(this.TAG,e); }); //const offerOptions = {}; /* if (typeof this.pc.addTransceiver === 'function') { // |direction| seems not working on Safari. this.pc.addTransceiver('audio', { direction: 'recvonly' }); this.pc.addTransceiver('video', { direction: 'recvonly' }); } else { offerOptions.offerToReceiveAudio = true; offerOptions.offerToReceiveVideo = true; } */ } _onIceCandidate(event) { if (event.candidate) { log(this.TAG, 'Remote ICE candidate: \n ' + event.candidate.candidate); // Send the candidate to the remote peer } } _onTrack(event) { this._tracks.push(event.track); if (this.options.element && event.streams && event.streams.length > 0) { this.options.element.srcObject = event.streams[0]; this._remoteStream = event.streams[0]; this.dispatch(Events$1.WEBRTC_ON_REMOTE_STREAMS, event); } else { if (this.pc.getReceivers().length == this._tracks.length) { log(this.TAG, 'play remote stream '); this._remoteStream = new MediaStream(this._tracks); this.options.element.srcObject = this._remoteStream; } else { error(this.TAG, 'wait stream track finish'); } } } _onIceCandidateError(event) { this.dispatch(Events$1.WEBRTC_ICE_CANDIDATE_ERROR, event); } _onconnectionstatechange(event) { this.dispatch(Events$1.WEBRTC_ON_CONNECTION_STATE_CHANGE, this.pc.connectionState); } _onDataChannelOpen(event) { log(this.TAG, 'ondatachannel open:', event); this.dispatch(Events$1.WEBRTC_ON_DATA_CHANNEL_OPEN, event); } _onDataChannelMsg(event) { log(this.TAG, 'ondatachannel msg:', event); this.dispatch(Events$1.WEBRTC_ON_DATA_CHANNEL_MSG, event); } _onDataChannelErr(event) { log(this.TAG, 'ondatachannel err:', event); this.dispatch(Events$1.WEBRTC_ON_DATA_CHANNEL_ERR, event); } _onDataChannelClose(event) { log(this.TAG, 'ondatachannel close:', event); this.dispatch(Events$1.WEBRTC_ON_DATA_CHANNEL_CLOSE, event); } sendMsg(data) { if (this.datachannel != null) { this.datachannel.send(data); } else { error(this.TAG, 'data channel is null'); } } closeDataChannel() { if (this.datachannel) { this.datachannel.close(); this.datachannel = null; } } close() { this.closeDataChannel(); if (this.pc) { this.pc.close(); this.pc = null; } if (this.options) { this.options = null; } if (this._localStream) { this._localStream.getTracks().forEach((track, idx) => { track.stop(); }); } if (this._remoteStream) { this._remoteStream.getTracks().forEach((track, idx) => { track.stop(); }); } this._tracks.forEach((track, idx) => { track.stop(); }); this._tracks = []; } get remoteStream() { return this._remoteStream; } get localStream() { return this._localStream; } } const quickScan = [{ 'label': '4K(UHD)', 'width': 3840, 'height': 2160 }, { 'label': '1080p(FHD)', 'width': 1920, 'height': 1080 }, { 'label': 'UXGA', 'width': 1600, 'height': 1200, 'ratio': '4:3' }, { 'label': '720p(HD)', 'width': 1280, 'height': 720 }, { 'label': 'SVGA', 'width': 800, 'height': 600 }, { 'label': 'VGA', 'width': 640, 'height': 480 }, { 'label': '360p(nHD)', 'width': 640, 'height': 360 }, { 'label': 'CIF', 'width': 352, 'height': 288 }, { 'label': 'QVGA', 'width': 320, 'height': 240 }, { 'label': 'QCIF', 'width': 176, 'height': 144 }, { 'label': 'QQVGA', 'width': 160, 'height': 120 }]; function GetSupportCameraResolutions$1() { return new Promise(function (resolve, reject) { let resolutions = []; let ok = 0; let err = 0; for (let i = 0; i < quickScan.length; ++i) { let videoConstraints = new VideoTrackConstraints(VideoSourceInfo.CAMERA); videoConstraints.resolution = new Resolution(quickScan[i].width, quickScan[i].height); MediaStreamFactory.createMediaStream(new StreamConstraints(false, videoConstraints)).then(stream => { resolutions.push(quickScan[i]); ok++; if (ok + err == quickScan.length) { resolve(resolutions); } }).catch(e => { err++; if (ok + err == quickScan.length) { resolve(resolutions); } }); } }); } function GetAllScanResolution$1() { return quickScan; } function isSupportResolution$1(w, h) { return new Promise(function (resolve, reject) { let videoConstraints = new VideoTrackConstraints(VideoSourceInfo.CAMERA); videoConstraints.resolution = new Resolution(w, h); MediaStreamFactory.createMediaStream(new StreamConstraints(false, videoConstraints)).then(stream => { resolve(); }).catch(e => { reject(e); }); }); } console.log('build date:', BUILD_DATE); console.log('version:', VERSION$1); const Events = Events$1; const Media = media; const Endpoint = RTCEndpoint; const GetSupportCameraResolutions = GetSupportCameraResolutions$1; const GetAllScanResolution = GetAllScanResolution$1; const isSupportResolution = isSupportResolution$1; exports.Endpoint = Endpoint; exports.Events = Events; exports.GetAllScanResolution = GetAllScanResolution; exports.GetSupportCameraResolutions = GetSupportCameraResolutions; exports.Media = Media; exports.isSupportResolution = isSupportResolution; Object.defineProperty(exports, '__esModule', { value: true }); return exports; })({}); //# sourceMappingURL=ZLMRTCClient.js.map ================================================ FILE: web/public/static/js/config.js ================================================ window.baseUrl = "" // map组件全局参数, 注释此内容可以关闭地图功能 window.mapParam = { // 开启/关闭地图功能 enable: true, // 坐标系 GCJ02 WGS84, coordinateSystem: "GCJ02", // 地图瓦片地址 tilesUrl: "http://webrd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scale=1&style=8", // 瓦片大小 tileSize: 256, // 默认层级 zoom:10, // 默认地图中心点 center:[116.41020, 39.915119], // 地图最大层级 maxZoom:18, // 地图最小层级 minZoom: 3 } ================================================ FILE: web/public/static/js/h265web/h265webjs-v20221106.js ================================================ !function e(t,i,n){function r(s,o){if(!i[s]){if(!t[s]){var u="function"==typeof require&&require;if(!o&&u)return u(s,!0);if(a)return a(s,!0);var l=new Error("Cannot find module '"+s+"'");throw l.code="MODULE_NOT_FOUND",l}var h=i[s]={exports:{}};t[s][0].call(h.exports,(function(e){return r(t[s][1][e]||e)}),h,h.exports,e,t,i,n)}return i[s].exports}for(var a="function"==typeof require&&require,s=0;sh&&(u-=h,u-=h,u-=c(2))}return Number(u)};i.numberToBytes=function(e,t){var i=(void 0===t?{}:t).le,n=void 0!==i&&i;("bigint"!=typeof e&&"number"!=typeof e||"number"==typeof e&&e!=e)&&(e=0),e=c(e);for(var r=s(e),a=new Uint8Array(new ArrayBuffer(r)),o=0;o=t.length&&u.call(t,(function(t,i){return t===(o[i]?o[i]&e[a+i]:e[a+i])}))};i.sliceBytes=function(e,t,i){return Uint8Array.prototype.slice?Uint8Array.prototype.slice.call(e,t,i):new Uint8Array(Array.prototype.slice.call(e,t,i))};i.reverseBytes=function(e){return e.reverse?e.reverse():Array.prototype.reverse.call(e)}},{"@babel/runtime/helpers/interopRequireDefault":6,"global/window":34}],10:[function(e,t,i){"use strict";Object.defineProperty(i,"__esModule",{value:!0}),i.getHvcCodec=i.getAvcCodec=i.getAv1Codec=void 0;var n=e("./byte-helpers.js");i.getAv1Codec=function(e){var t,i="",r=e[1]>>>3,a=31&e[1],s=e[2]>>>7,o=(64&e[2])>>6,u=(32&e[2])>>5,l=(16&e[2])>>4,h=(8&e[2])>>3,d=(4&e[2])>>2,c=3&e[2];return i+=r+"."+(0,n.padStart)(a,2,"0"),0===s?i+="M":1===s&&(i+="H"),t=2===r&&o?u?12:10:o?10:8,i+="."+(0,n.padStart)(t,2,"0"),i+="."+l,i+="."+h+d+c};i.getAvcCodec=function(e){return""+(0,n.toHexString)(e[1])+(0,n.toHexString)(252&e[2])+(0,n.toHexString)(e[3])};i.getHvcCodec=function(e){var t="",i=e[1]>>6,r=31&e[1],a=(32&e[1])>>5,s=e.subarray(2,6),o=e.subarray(6,12),u=e[12];1===i?t+="A":2===i?t+="B":3===i&&(t+="C"),t+=r+".";var l=parseInt((0,n.toBinaryString)(s).split("").reverse().join(""),2);l>255&&(l=parseInt((0,n.toBinaryString)(s),2)),t+=l.toString(16)+".",t+=0===a?"L":"H",t+=u;for(var h="",d=0;d=1)return 71===e[0];for(var t=0;t+1880}},{"./byte-helpers.js":9,"./ebml-helpers.js":14,"./id3-helpers.js":15,"./mp4-helpers.js":17,"./nal-helpers.js":18}],13:[function(e,t,i){(function(n){"use strict";var r=e("@babel/runtime/helpers/interopRequireDefault");Object.defineProperty(i,"__esModule",{value:!0}),i.default=function(e){for(var t=(s=e,a.default.atob?a.default.atob(s):n.from(s,"base64").toString("binary")),i=new Uint8Array(t.length),r=0;r=i.length)return i.length;var a=o(i,r,!1);if((0,n.bytesMatch)(t.bytes,a.bytes))return r;var s=o(i,r+a.length);return e(t,i,r+s.length+s.value+a.length)},h=function e(t,i){i=function(e){return Array.isArray(e)?e.map((function(e){return u(e)})):[u(e)]}(i),t=(0,n.toUint8)(t);var r=[];if(!i.length)return r;for(var a=0;at.length?t.length:d+h.value,f=t.subarray(d,c);(0,n.bytesMatch)(i[0],s.bytes)&&(1===i.length?r.push(f):r=r.concat(e(f,i.slice(1)))),a+=s.length+h.length+f.length}return r};i.findEbml=h;var d=function(e,t,i,r){var s;"group"===t&&((s=h(e,[a.BlockDuration])[0])&&(s=1/i*(s=(0,n.bytesToNumber)(s))*i/1e3),e=h(e,[a.Block])[0],t="block");var u=new DataView(e.buffer,e.byteOffset,e.byteLength),l=o(e,0),d=u.getInt16(l.length,!1),c=e[l.length+2],f=e.subarray(l.length+3),p=1/i*(r+d)*i/1e3,m={duration:s,trackNumber:l.value,keyframe:"simple"===t&&c>>7==1,invisible:(8&c)>>3==1,lacing:(6&c)>>1,discardable:"simple"===t&&1==(1&c),frames:[],pts:p,dts:p,timestamp:d};if(!m.lacing)return m.frames.push(f),m;var _=f[0]+1,g=[],v=1;if(2===m.lacing)for(var y=(f.length-v)/_,b=0;b<_;b++)g.push(y);if(1===m.lacing)for(var S=0;S<_-1;S++){var T=0;do{T+=f[v],v++}while(255===f[v-1]);g.push(T)}if(3===m.lacing)for(var E=0,w=0;w<_-1;w++){var A=0===w?o(f,v):o(f,v,!0,!0);E+=A.value,g.push(E),v+=A.length}return g.forEach((function(e){m.frames.push(f.subarray(v,v+e)),v+=e})),m};i.decodeBlock=d;var c=function(e){e=(0,n.toUint8)(e);var t=[],i=h(e,[a.Segment,a.Tracks,a.Track]);return i.length||(i=h(e,[a.Tracks,a.Track])),i.length||(i=h(e,[a.Track])),i.length?(i.forEach((function(e){var i=h(e,a.TrackType)[0];if(i&&i.length){if(1===i[0])i="video";else if(2===i[0])i="audio";else{if(17!==i[0])return;i="subtitle"}var s={rawCodec:(0,n.bytesToString)(h(e,[a.CodecID])[0]),type:i,codecPrivate:h(e,[a.CodecPrivate])[0],number:(0,n.bytesToNumber)(h(e,[a.TrackNumber])[0]),defaultDuration:(0,n.bytesToNumber)(h(e,[a.DefaultDuration])[0]),default:h(e,[a.FlagDefault])[0],rawData:e},o="";if(/V_MPEG4\/ISO\/AVC/.test(s.rawCodec))o="avc1."+(0,r.getAvcCodec)(s.codecPrivate);else if(/V_MPEGH\/ISO\/HEVC/.test(s.rawCodec))o="hev1."+(0,r.getHvcCodec)(s.codecPrivate);else if(/V_MPEG4\/ISO\/ASP/.test(s.rawCodec))o=s.codecPrivate?"mp4v.20."+s.codecPrivate[4].toString():"mp4v.20.9";else if(/^V_THEORA/.test(s.rawCodec))o="theora";else if(/^V_VP8/.test(s.rawCodec))o="vp8";else if(/^V_VP9/.test(s.rawCodec))if(s.codecPrivate){var u=function(e){for(var t=0,i={};t>>3).toString():"mp4a.40.2":/^A_AC3/.test(s.rawCodec)?o="ac-3":/^A_PCM/.test(s.rawCodec)?o="pcm":/^A_MS\/ACM/.test(s.rawCodec)?o="speex":/^A_EAC3/.test(s.rawCodec)?o="ec-3":/^A_VORBIS/.test(s.rawCodec)?o="vorbis":/^A_FLAC/.test(s.rawCodec)?o="flac":/^A_OPUS/.test(s.rawCodec)&&(o="opus");s.codec=o,t.push(s)}})),t.sort((function(e,t){return e.number-t.number}))):t};i.parseTracks=c;i.parseData=function(e,t){var i=[],r=h(e,[a.Segment])[0],s=h(r,[a.SegmentInfo,a.TimestampScale])[0];s=s&&s.length?(0,n.bytesToNumber)(s):1e6;var o=h(r,[a.Cluster]);return t||(t=c(r)),o.forEach((function(e,t){var r=h(e,[a.SimpleBlock]).map((function(e){return{type:"simple",data:e}})),o=h(e,[a.BlockGroup]).map((function(e){return{type:"group",data:e}})),u=h(e,[a.Timestamp])[0]||0;u&&u.length&&(u=(0,n.bytesToNumber)(u)),r.concat(o).sort((function(e,t){return e.data.byteOffset-t.data.byteOffset})).forEach((function(e,t){var n=d(e.data,e.type,s,u);i.push(n)}))})),{tracks:t,blocks:i}}},{"./byte-helpers":9,"./codec-helpers.js":10}],15:[function(e,t,i){"use strict";Object.defineProperty(i,"__esModule",{value:!0}),i.getId3Offset=i.getId3Size=void 0;var n=e("./byte-helpers.js"),r=(0,n.toUint8)([73,68,51]),a=function(e,t){void 0===t&&(t=0);var i=(e=(0,n.toUint8)(e))[t+5],r=e[t+6]<<21|e[t+7]<<14|e[t+8]<<7|e[t+9];return(16&i)>>4?r+20:r+10};i.getId3Size=a;i.getId3Offset=function e(t,i){return void 0===i&&(i=0),(t=(0,n.toUint8)(t)).length-i<10||!(0,n.bytesMatch)(t,r,{offset:i})?i:e(t,i+=a(t,i))}},{"./byte-helpers.js":9}],16:[function(e,t,i){"use strict";Object.defineProperty(i,"__esModule",{value:!0}),i.simpleTypeFromSourceType=void 0;var n=/^(audio|video|application)\/(x-|vnd\.apple\.)?mpegurl/i,r=/^application\/dash\+xml/i;i.simpleTypeFromSourceType=function(e){return n.test(e)?"hls":r.test(e)?"dash":"application/vnd.videojs.vhs+json"===e?"vhs-json":null}},{}],17:[function(e,t,i){"use strict";Object.defineProperty(i,"__esModule",{value:!0}),i.parseMediaInfo=i.parseTracks=i.addSampleDescription=i.buildFrameTable=i.findNamedBox=i.findBox=i.parseDescriptors=void 0;var n,r=e("./byte-helpers.js"),a=e("./codec-helpers.js"),s=e("./opus-helpers.js"),o=function(e){return"string"==typeof e?(0,r.stringToBytes)(e):e},u=function(e){e=(0,r.toUint8)(e);for(var t=[],i=0;e.length>i;){var a=e[i],s=0,o=0,u=e[++o];for(o++;128&u;)s=(127&u)<<7,u=e[o],o++;s+=127&u;for(var l=0;l>>0,l=t.subarray(s+4,s+8);if(0===u)break;var h=s+u;if(h>t.length){if(n)break;h=t.length}var d=t.subarray(s+8,h);(0,r.bytesMatch)(l,i[0])&&(1===i.length?a.push(d):a.push.apply(a,e(d,i.slice(1),n))),s=h}return a};i.findBox=l;var h=function(e,t){if(!(t=o(t)).length)return e.subarray(e.length);for(var i=0;i>>0,a=n>1?i+n:e.byteLength;return e.subarray(i+4,a)}i++}return e.subarray(e.length)};i.findNamedBox=h;var d=function(e,t,i){void 0===t&&(t=4),void 0===i&&(i=function(e){return(0,r.bytesToNumber)(e)});var n=[];if(!e||!e.length)return n;for(var a=(0,r.bytesToNumber)(e.subarray(4,8)),s=8;a;s+=t,a--)n.push(i(e.subarray(s,s+t)));return n},c=function(e,t){for(var i=d(l(e,["stss"])[0]),n=d(l(e,["stco"])[0]),a=d(l(e,["stts"])[0],8,(function(e){return{sampleCount:(0,r.bytesToNumber)(e.subarray(0,4)),sampleDelta:(0,r.bytesToNumber)(e.subarray(4,8))}})),s=d(l(e,["stsc"])[0],12,(function(e){return{firstChunk:(0,r.bytesToNumber)(e.subarray(0,4)),samplesPerChunk:(0,r.bytesToNumber)(e.subarray(4,8)),sampleDescriptionIndex:(0,r.bytesToNumber)(e.subarray(8,12))}})),o=l(e,["stsz"])[0],u=d(o&&o.length&&o.subarray(4)||null),h=[],c=0;c=m.firstChunk&&(p+1>=s.length||c+1>3).toString():32===d.oti?i+="."+d.descriptors[0].bytes[4].toString():221===d.oti&&(i="vorbis")):"audio"===e.type?i+=".40.2":i+=".20.9"}else if("av01"===i)i+="."+(0,a.getAv1Codec)(h(t,"av1C"));else if("vp09"===i){var c=h(t,"vpcC"),f=c[0],p=c[1],m=c[2]>>4,_=(15&c[2])>>1,g=(15&c[2])>>3,v=c[3],y=c[4],b=c[5];i+="."+(0,r.padStart)(f,2,"0"),i+="."+(0,r.padStart)(p,2,"0"),i+="."+(0,r.padStart)(m,2,"0"),i+="."+(0,r.padStart)(_,2,"0"),i+="."+(0,r.padStart)(v,2,"0"),i+="."+(0,r.padStart)(y,2,"0"),i+="."+(0,r.padStart)(b,2,"0"),i+="."+(0,r.padStart)(g,2,"0")}else if("theo"===i)i="theora";else if("spex"===i)i="speex";else if(".mp3"===i)i="mp4a.40.34";else if("msVo"===i)i="vorbis";else if("Opus"===i){i="opus";var S=h(t,"dOps");e.info.opus=(0,s.parseOpusHead)(S),e.info.codecDelay=65e5}else i=i.toLowerCase();e.codec=i};i.addSampleDescription=f;i.parseTracks=function(e,t){void 0===t&&(t=!0),e=(0,r.toUint8)(e);var i=l(e,["moov","trak"],!0),n=[];return i.forEach((function(e){var i={bytes:e},a=l(e,["mdia"])[0],s=l(a,["hdlr"])[0],o=(0,r.bytesToString)(s.subarray(8,12));i.type="soun"===o?"audio":"vide"===o?"video":o;var u=l(e,["tkhd"])[0];if(u){var h=new DataView(u.buffer,u.byteOffset,u.byteLength),d=h.getUint8(0);i.number=0===d?h.getUint32(12):h.getUint32(20)}var p=l(a,["mdhd"])[0];if(p){var m=0===p[0]?12:20;i.timescale=(p[m]<<24|p[m+1]<<16|p[m+2]<<8|p[m+3])>>>0}for(var _=l(a,["minf","stbl"])[0],g=l(_,["stsd"])[0],v=(0,r.bytesToNumber)(g.subarray(4,8)),y=8;v--;){var b=(0,r.bytesToNumber)(g.subarray(y,y+4)),S=g.subarray(y+4,y+4+b);f(i,S),y+=4+b}t&&(i.frameTable=c(_,i.timescale)),n.push(i)})),n};i.parseMediaInfo=function(e){var t=l(e,["moov","mvhd"],!0)[0];if(t&&t.length){var i={};return 1===t[0]?(i.timestampScale=(0,r.bytesToNumber)(t.subarray(20,24)),i.duration=(0,r.bytesToNumber)(t.subarray(24,32))):(i.timestampScale=(0,r.bytesToNumber)(t.subarray(12,16)),i.duration=(0,r.bytesToNumber)(t.subarray(16,20))),i.bytes=t,i}}},{"./byte-helpers.js":9,"./codec-helpers.js":10,"./opus-helpers.js":19}],18:[function(e,t,i){"use strict";Object.defineProperty(i,"__esModule",{value:!0}),i.findH265Nal=i.findH264Nal=i.findNal=i.discardEmulationPreventionBytes=i.EMULATION_PREVENTION=i.NAL_TYPE_TWO=i.NAL_TYPE_ONE=void 0;var n=e("./byte-helpers.js"),r=(0,n.toUint8)([0,0,0,1]);i.NAL_TYPE_ONE=r;var a=(0,n.toUint8)([0,0,1]);i.NAL_TYPE_TWO=a;var s=(0,n.toUint8)([0,0,3]);i.EMULATION_PREVENTION=s;var o=function(e){for(var t=[],i=1;i>1&63),-1!==i.indexOf(c)&&(u=l+d),l+=d+("h264"===t?1:2)}else l++}return e.subarray(0,0)};i.findNal=u;i.findH264Nal=function(e,t,i){return u(e,"h264",t,i)};i.findH265Nal=function(e,t,i){return u(e,"h265",t,i)}},{"./byte-helpers.js":9}],19:[function(e,t,i){"use strict";Object.defineProperty(i,"__esModule",{value:!0}),i.setOpusHead=i.parseOpusHead=i.OPUS_HEAD=void 0;var n=new Uint8Array([79,112,117,115,72,101,97,100]);i.OPUS_HEAD=n;i.parseOpusHead=function(e){var t=new DataView(e.buffer,e.byteOffset,e.byteLength),i=t.getUint8(0),n=0!==i,r={version:i,channels:t.getUint8(1),preSkip:t.getUint16(2,n),sampleRate:t.getUint32(4,n),outputGain:t.getUint16(8,n),channelMappingFamily:t.getUint8(10)};if(r.channelMappingFamily>0&&e.length>10){r.streamCount=t.getUint8(11),r.twoChannelStreamCount=t.getUint8(12),r.channelMapping=[];for(var a=0;a0&&(i.setUint8(11,e.streamCount),e.channelMapping.foreach((function(e,t){i.setUint8(12+t,e)}))),new Uint8Array(i.buffer)}},{}],20:[function(e,t,i){"use strict";var n=e("@babel/runtime/helpers/interopRequireDefault");Object.defineProperty(i,"__esModule",{value:!0}),i.default=void 0;var r=n(e("url-toolkit")),a=n(e("global/window")),s=function(e,t){if(/^[a-z]+:/i.test(t))return t;/^data:/.test(e)&&(e=a.default.location&&a.default.location.href||"");var i="function"==typeof a.default.URL,n=/^\/\//.test(e),s=!a.default.location&&!/\/\//i.test(e);if(i?e=new a.default.URL(e,a.default.location||"http://example.com"):/\/\//i.test(e)||(e=r.default.buildAbsoluteURL(a.default.location&&a.default.location.href||"",e)),i){var o=new URL(t,e);return s?o.href.slice("http://example.com".length):n?o.href.slice(o.protocol.length):o.href}return r.default.buildAbsoluteURL(e,t)};i.default=s,t.exports=i.default},{"@babel/runtime/helpers/interopRequireDefault":6,"global/window":34,"url-toolkit":46}],21:[function(e,t,i){"use strict";Object.defineProperty(i,"__esModule",{value:!0}),i.default=void 0;var n=function(){function e(){this.listeners={}}var t=e.prototype;return t.on=function(e,t){this.listeners[e]||(this.listeners[e]=[]),this.listeners[e].push(t)},t.off=function(e,t){if(!this.listeners[e])return!1;var i=this.listeners[e].indexOf(t);return this.listeners[e]=this.listeners[e].slice(0),this.listeners[e].splice(i,1),i>-1},t.trigger=function(e){var t=this.listeners[e];if(t)if(2===arguments.length)for(var i=t.length,n=0;n=400&&r.statusCode<=599){var s=a;if(t)if(n.TextDecoder){var o=function(e){void 0===e&&(e="");return e.toLowerCase().split(";").reduce((function(e,t){var i=t.split("="),n=i[0],r=i[1];return"charset"===n.trim()?r.trim():e}),"utf-8")}(r.headers&&r.headers["content-type"]);try{s=new TextDecoder(o).decode(a)}catch(e){}}else s=String.fromCharCode.apply(null,new Uint8Array(a));e({cause:s})}else e(null,a)}}},{"global/window":34}],23:[function(e,t,i){"use strict";var n=e("global/window"),r=e("@babel/runtime/helpers/extends"),a=e("is-function");o.httpHandler=e("./http-handler.js");function s(e,t,i){var n=e;return a(t)?(i=t,"string"==typeof e&&(n={uri:e})):n=r({},t,{uri:e}),n.callback=i,n}function o(e,t,i){return u(t=s(e,t,i))}function u(e){if(void 0===e.callback)throw new Error("callback argument missing");var t=!1,i=function(i,n,r){t||(t=!0,e.callback(i,n,r))};function n(){var e=void 0;if(e=l.response?l.response:l.responseText||function(e){try{if("document"===e.responseType)return e.responseXML;var t=e.responseXML&&"parsererror"===e.responseXML.documentElement.nodeName;if(""===e.responseType&&!t)return e.responseXML}catch(e){}return null}(l),_)try{e=JSON.parse(e)}catch(e){}return e}function r(e){return clearTimeout(h),e instanceof Error||(e=new Error(""+(e||"Unknown XMLHttpRequest Error"))),e.statusCode=0,i(e,g)}function a(){if(!u){var t;clearTimeout(h),t=e.useXDR&&void 0===l.status?200:1223===l.status?204:l.status;var r=g,a=null;return 0!==t?(r={body:n(),statusCode:t,method:c,headers:{},url:d,rawRequest:l},l.getAllResponseHeaders&&(r.headers=function(e){var t={};return e?(e.trim().split("\n").forEach((function(e){var i=e.indexOf(":"),n=e.slice(0,i).trim().toLowerCase(),r=e.slice(i+1).trim();void 0===t[n]?t[n]=r:Array.isArray(t[n])?t[n].push(r):t[n]=[t[n],r]})),t):t}(l.getAllResponseHeaders()))):a=new Error("Internal XMLHttpRequest Error"),i(a,r,r.body)}}var s,u,l=e.xhr||null;l||(l=e.cors||e.useXDR?new o.XDomainRequest:new o.XMLHttpRequest);var h,d=l.url=e.uri||e.url,c=l.method=e.method||"GET",f=e.body||e.data,p=l.headers=e.headers||{},m=!!e.sync,_=!1,g={body:void 0,headers:{},statusCode:0,method:c,url:d,rawRequest:l};if("json"in e&&!1!==e.json&&(_=!0,p.accept||p.Accept||(p.Accept="application/json"),"GET"!==c&&"HEAD"!==c&&(p["content-type"]||p["Content-Type"]||(p["Content-Type"]="application/json"),f=JSON.stringify(!0===e.json?f:e.json))),l.onreadystatechange=function(){4===l.readyState&&setTimeout(a,0)},l.onload=a,l.onerror=r,l.onprogress=function(){},l.onabort=function(){u=!0},l.ontimeout=r,l.open(c,d,!m,e.username,e.password),m||(l.withCredentials=!!e.withCredentials),!m&&e.timeout>0&&(h=setTimeout((function(){if(!u){u=!0,l.abort("timeout");var e=new Error("XMLHttpRequest timeout");e.code="ETIMEDOUT",r(e)}}),e.timeout)),l.setRequestHeader)for(s in p)p.hasOwnProperty(s)&&l.setRequestHeader(s,p[s]);else if(e.headers&&!function(e){for(var t in e)if(e.hasOwnProperty(t))return!1;return!0}(e.headers))throw new Error("Headers cannot be set on an XDomainRequest object");return"responseType"in e&&(l.responseType=e.responseType),"beforeSend"in e&&"function"==typeof e.beforeSend&&e.beforeSend(l),l.send(f||null),l}t.exports=o,t.exports.default=o,o.XMLHttpRequest=n.XMLHttpRequest||function(){},o.XDomainRequest="withCredentials"in new o.XMLHttpRequest?o.XMLHttpRequest:n.XDomainRequest,function(e,t){for(var i=0;i=t+i||t?new java.lang.String(e,t,i)+"":e}function _(e,t){e.currentElement?e.currentElement.appendChild(t):e.doc.appendChild(t)}d.prototype.parseFromString=function(e,t){var i=this.options,n=new h,r=i.domBuilder||new c,s=i.errorHandler,o=i.locator,l=i.xmlns||{},d=/\/x?html?$/.test(t),f=d?a.HTML_ENTITIES:a.XML_ENTITIES;return o&&r.setDocumentLocator(o),n.errorHandler=function(e,t,i){if(!e){if(t instanceof c)return t;e=t}var n={},r=e instanceof Function;function a(t){var a=e[t];!a&&r&&(a=2==e.length?function(i){e(t,i)}:e),n[t]=a&&function(e){a("[xmldom "+t+"]\t"+e+p(i))}||function(){}}return i=i||{},a("warning"),a("error"),a("fatalError"),n}(s,r,o),n.domBuilder=i.domBuilder||r,d&&(l[""]=u.HTML),l.xml=l.xml||u.XML,e&&"string"==typeof e?n.parse(e,l,f):n.errorHandler.error("invalid doc source"),r.doc},c.prototype={startDocument:function(){this.doc=(new o).createDocument(null,null,null),this.locator&&(this.doc.documentURI=this.locator.systemId)},startElement:function(e,t,i,n){var r=this.doc,a=r.createElementNS(e,i||t),s=n.length;_(this,a),this.currentElement=a,this.locator&&f(this.locator,a);for(var o=0;o=0))throw k(A,new Error(e.tagName+"@"+i));for(var r=t.length-1;n"==e&&">")||"&"==e&&"&"||'"'==e&&"""||"&#"+e.charCodeAt()+";"}function B(e,t){if(t(e))return!0;if(e=e.firstChild)do{if(B(e,t))return!0}while(e=e.nextSibling)}function N(){}function j(e,t,i,r){e&&e._inc++,i.namespaceURI===n.XMLNS&&delete t._nsMap[i.prefix?i.localName:""]}function V(e,t,i){if(e&&e._inc){e._inc++;var n=t.childNodes;if(i)n[n.length++]=i;else{for(var r=t.firstChild,a=0;r;)n[a++]=r,r=r.nextSibling;n.length=a}}}function H(e,t){var i=t.previousSibling,n=t.nextSibling;return i?i.nextSibling=n:e.firstChild=n,n?n.previousSibling=i:e.lastChild=i,V(e.ownerDocument,e),t}function z(e,t,i){var n=t.parentNode;if(n&&n.removeChild(t),t.nodeType===b){var r=t.firstChild;if(null==r)return t;var a=t.lastChild}else r=a=t;var s=i?i.previousSibling:e.lastChild;r.previousSibling=s,a.nextSibling=i,s?s.nextSibling=r:e.firstChild=r,null==i?e.lastChild=a:i.previousSibling=a;do{r.parentNode=e}while(r!==a&&(r=r.nextSibling));return V(e.ownerDocument||e,e),t.nodeType==b&&(t.firstChild=t.lastChild=null),t}function G(){this._nsMap={}}function W(){}function Y(){}function q(){}function K(){}function X(){}function Q(){}function $(){}function J(){}function Z(){}function ee(){}function te(){}function ie(){}function ne(e,t){var i=[],n=9==this.nodeType&&this.documentElement||this,r=n.prefix,a=n.namespaceURI;if(a&&null==r&&null==(r=n.lookupPrefix(a)))var s=[{namespace:a,prefix:null}];return se(this,i,e,t,s),i.join("")}function re(e,t,i){var r=e.prefix||"",a=e.namespaceURI;if(!a)return!1;if("xml"===r&&a===n.XML||a===n.XMLNS)return!1;for(var s=i.length;s--;){var o=i[s];if(o.prefix===r)return o.namespace!==a}return!0}function ae(e,t,i){e.push(" ",t,'="',i.replace(/[<&"]/g,F),'"')}function se(e,t,i,r,a){if(a||(a=[]),r){if(!(e=r(e)))return;if("string"==typeof e)return void t.push(e)}switch(e.nodeType){case h:var s=e.attributes,o=s.length,u=e.firstChild,l=e.tagName,m=l;if(!(i=n.isHTML(e.namespaceURI)||i)&&!e.prefix&&e.namespaceURI){for(var S,T=0;T=0;E--){if(""===(w=a[E]).prefix&&w.namespace===e.namespaceURI){S=w.namespace;break}}if(S!==e.namespaceURI)for(E=a.length-1;E>=0;E--){var w;if((w=a[E]).namespace===e.namespaceURI){w.prefix&&(m=w.prefix+":"+l);break}}}t.push("<",m);for(var A=0;A"),i&&/^script$/i.test(l))for(;u;)u.data?t.push(u.data):se(u,t,i,r,a.slice()),u=u.nextSibling;else for(;u;)se(u,t,i,r,a.slice()),u=u.nextSibling;t.push("")}else t.push("/>");return;case v:case b:for(u=e.firstChild;u;)se(u,t,i,r,a.slice()),u=u.nextSibling;return;case d:return ae(t,e.name,e.value);case c:return t.push(e.data.replace(/[<&]/g,F).replace(/]]>/g,"]]>"));case f:return t.push("");case g:return t.push("\x3c!--",e.data,"--\x3e");case y:var I=e.publicId,L=e.systemId;if(t.push("");else if(L&&"."!=L)t.push(" SYSTEM ",L,">");else{var x=e.internalSubset;x&&t.push(" [",x,"]"),t.push(">")}return;case _:return t.push("");case p:return t.push("&",e.nodeName,";");default:t.push("??",e.nodeName)}}function oe(e,t,i){e[t]=i}k.prototype=Error.prototype,o(T,k),P.prototype={length:0,item:function(e){return this[e]||null},toString:function(e,t){for(var i=[],n=0;n0},lookupPrefix:function(e){for(var t=this;t;){var i=t._nsMap;if(i)for(var n in i)if(i[n]==e)return n;t=t.nodeType==d?t.ownerDocument:t.parentNode}return null},lookupNamespaceURI:function(e){for(var t=this;t;){var i=t._nsMap;if(i&&e in i)return i[e];t=t.nodeType==d?t.ownerDocument:t.parentNode}return null},isDefaultNamespace:function(e){return null==this.lookupPrefix(e)}},o(l,M),o(l,M.prototype),N.prototype={nodeName:"#document",nodeType:v,doctype:null,documentElement:null,_inc:1,insertBefore:function(e,t){if(e.nodeType==b){for(var i=e.firstChild;i;){var n=i.nextSibling;this.insertBefore(i,t),i=n}return e}return null==this.documentElement&&e.nodeType==h&&(this.documentElement=e),z(this,e,t),e.ownerDocument=this,e},removeChild:function(e){return this.documentElement==e&&(this.documentElement=null),H(this,e)},importNode:function(e,t){return function e(t,i,n){var r;switch(i.nodeType){case h:(r=i.cloneNode(!1)).ownerDocument=t;case b:break;case d:n=!0}r||(r=i.cloneNode(!1));if(r.ownerDocument=t,r.parentNode=null,n)for(var a=i.firstChild;a;)r.appendChild(e(t,a,n)),a=a.nextSibling;return r}(this,e,t)},getElementById:function(e){var t=null;return B(this.documentElement,(function(i){if(i.nodeType==h&&i.getAttribute("id")==e)return t=i,!0})),t},getElementsByClassName:function(e){var t=s(e);return new I(this,(function(i){var n=[];return t.length>0&&B(i.documentElement,(function(r){if(r!==i&&r.nodeType===h){var a=r.getAttribute("class");if(a){var o=e===a;if(!o){var u=s(a);o=t.every((l=u,function(e){return l&&-1!==l.indexOf(e)}))}o&&n.push(r)}}var l})),n}))},createElement:function(e){var t=new G;return t.ownerDocument=this,t.nodeName=e,t.tagName=e,t.localName=e,t.childNodes=new P,(t.attributes=new x)._ownerElement=t,t},createDocumentFragment:function(){var e=new ee;return e.ownerDocument=this,e.childNodes=new P,e},createTextNode:function(e){var t=new q;return t.ownerDocument=this,t.appendData(e),t},createComment:function(e){var t=new K;return t.ownerDocument=this,t.appendData(e),t},createCDATASection:function(e){var t=new X;return t.ownerDocument=this,t.appendData(e),t},createProcessingInstruction:function(e,t){var i=new te;return i.ownerDocument=this,i.tagName=i.target=e,i.nodeValue=i.data=t,i},createAttribute:function(e){var t=new W;return t.ownerDocument=this,t.name=e,t.nodeName=e,t.localName=e,t.specified=!0,t},createEntityReference:function(e){var t=new Z;return t.ownerDocument=this,t.nodeName=e,t},createElementNS:function(e,t){var i=new G,n=t.split(":"),r=i.attributes=new x;return i.childNodes=new P,i.ownerDocument=this,i.nodeName=t,i.tagName=t,i.namespaceURI=e,2==n.length?(i.prefix=n[0],i.localName=n[1]):i.localName=t,r._ownerElement=i,i},createAttributeNS:function(e,t){var i=new W,n=t.split(":");return i.ownerDocument=this,i.nodeName=t,i.name=t,i.namespaceURI=e,i.specified=!0,2==n.length?(i.prefix=n[0],i.localName=n[1]):i.localName=t,i}},u(N,M),G.prototype={nodeType:h,hasAttribute:function(e){return null!=this.getAttributeNode(e)},getAttribute:function(e){var t=this.getAttributeNode(e);return t&&t.value||""},getAttributeNode:function(e){return this.attributes.getNamedItem(e)},setAttribute:function(e,t){var i=this.ownerDocument.createAttribute(e);i.value=i.nodeValue=""+t,this.setAttributeNode(i)},removeAttribute:function(e){var t=this.getAttributeNode(e);t&&this.removeAttributeNode(t)},appendChild:function(e){return e.nodeType===b?this.insertBefore(e,null):function(e,t){var i=t.parentNode;if(i){var n=e.lastChild;i.removeChild(t);n=e.lastChild}return n=e.lastChild,t.parentNode=e,t.previousSibling=n,t.nextSibling=null,n?n.nextSibling=t:e.firstChild=t,e.lastChild=t,V(e.ownerDocument,e,t),t}(this,e)},setAttributeNode:function(e){return this.attributes.setNamedItem(e)},setAttributeNodeNS:function(e){return this.attributes.setNamedItemNS(e)},removeAttributeNode:function(e){return this.attributes.removeNamedItem(e.nodeName)},removeAttributeNS:function(e,t){var i=this.getAttributeNodeNS(e,t);i&&this.removeAttributeNode(i)},hasAttributeNS:function(e,t){return null!=this.getAttributeNodeNS(e,t)},getAttributeNS:function(e,t){var i=this.getAttributeNodeNS(e,t);return i&&i.value||""},setAttributeNS:function(e,t,i){var n=this.ownerDocument.createAttributeNS(e,t);n.value=n.nodeValue=""+i,this.setAttributeNode(n)},getAttributeNodeNS:function(e,t){return this.attributes.getNamedItemNS(e,t)},getElementsByTagName:function(e){return new I(this,(function(t){var i=[];return B(t,(function(n){n===t||n.nodeType!=h||"*"!==e&&n.tagName!=e||i.push(n)})),i}))},getElementsByTagNameNS:function(e,t){return new I(this,(function(i){var n=[];return B(i,(function(r){r===i||r.nodeType!==h||"*"!==e&&r.namespaceURI!==e||"*"!==t&&r.localName!=t||n.push(r)})),n}))}},N.prototype.getElementsByTagName=G.prototype.getElementsByTagName,N.prototype.getElementsByTagNameNS=G.prototype.getElementsByTagNameNS,u(G,M),W.prototype.nodeType=d,u(W,M),Y.prototype={data:"",substringData:function(e,t){return this.data.substring(e,e+t)},appendData:function(e){e=this.data+e,this.nodeValue=this.data=e,this.length=e.length},insertData:function(e,t){this.replaceData(e,0,t)},appendChild:function(e){throw new Error(E[w])},deleteData:function(e,t){this.replaceData(e,t,"")},replaceData:function(e,t,i){i=this.data.substring(0,e)+i+this.data.substring(e+t),this.nodeValue=this.data=i,this.length=i.length}},u(Y,M),q.prototype={nodeName:"#text",nodeType:c,splitText:function(e){var t=this.data,i=t.substring(e);t=t.substring(0,e),this.data=this.nodeValue=t,this.length=t.length;var n=this.ownerDocument.createTextNode(i);return this.parentNode&&this.parentNode.insertBefore(n,this.nextSibling),n}},u(q,Y),K.prototype={nodeName:"#comment",nodeType:g},u(K,Y),X.prototype={nodeName:"#cdata-section",nodeType:f},u(X,Y),Q.prototype.nodeType=y,u(Q,M),$.prototype.nodeType=S,u($,M),J.prototype.nodeType=m,u(J,M),Z.prototype.nodeType=p,u(Z,M),ee.prototype.nodeName="#document-fragment",ee.prototype.nodeType=b,u(ee,M),te.prototype.nodeType=_,u(te,M),ie.prototype.serializeToString=function(e,t,i){return ne.call(e,t,i)},M.prototype.toString=ne;try{if(Object.defineProperty){Object.defineProperty(I.prototype,"length",{get:function(){return L(this),this.$$length}}),Object.defineProperty(M.prototype,"textContent",{get:function(){return function e(t){switch(t.nodeType){case h:case b:var i=[];for(t=t.firstChild;t;)7!==t.nodeType&&8!==t.nodeType&&i.push(e(t)),t=t.nextSibling;return i.join("");default:return t.nodeValue}}(this)},set:function(e){switch(this.nodeType){case h:case b:for(;this.firstChild;)this.removeChild(this.firstChild);(e||String(e))&&this.appendChild(this.ownerDocument.createTextNode(e));break;default:this.data=e,this.value=e,this.nodeValue=e}}}),oe=function(e,t,i){e["$$"+t]=i}}}catch(e){}i.DocumentType=Q,i.DOMException=k,i.DOMImplementation=U,i.Element=G,i.Node=M,i.NodeList=P,i.XMLSerializer=ie},{"./conventions":24}],27:[function(e,t,i){var n=e("./conventions").freeze;i.XML_ENTITIES=n({amp:"&",apos:"'",gt:">",lt:"<",quot:'"'}),i.HTML_ENTITIES=n({lt:"<",gt:">",amp:"&",quot:'"',apos:"'",Agrave:"À",Aacute:"Á",Acirc:"Â",Atilde:"Ã",Auml:"Ä",Aring:"Å",AElig:"Æ",Ccedil:"Ç",Egrave:"È",Eacute:"É",Ecirc:"Ê",Euml:"Ë",Igrave:"Ì",Iacute:"Í",Icirc:"Î",Iuml:"Ï",ETH:"Ð",Ntilde:"Ñ",Ograve:"Ò",Oacute:"Ó",Ocirc:"Ô",Otilde:"Õ",Ouml:"Ö",Oslash:"Ø",Ugrave:"Ù",Uacute:"Ú",Ucirc:"Û",Uuml:"Ü",Yacute:"Ý",THORN:"Þ",szlig:"ß",agrave:"à",aacute:"á",acirc:"â",atilde:"ã",auml:"ä",aring:"å",aelig:"æ",ccedil:"ç",egrave:"è",eacute:"é",ecirc:"ê",euml:"ë",igrave:"ì",iacute:"í",icirc:"î",iuml:"ï",eth:"ð",ntilde:"ñ",ograve:"ò",oacute:"ó",ocirc:"ô",otilde:"õ",ouml:"ö",oslash:"ø",ugrave:"ù",uacute:"ú",ucirc:"û",uuml:"ü",yacute:"ý",thorn:"þ",yuml:"ÿ",nbsp:" ",iexcl:"¡",cent:"¢",pound:"£",curren:"¤",yen:"¥",brvbar:"¦",sect:"§",uml:"¨",copy:"©",ordf:"ª",laquo:"«",not:"¬",shy:"­­",reg:"®",macr:"¯",deg:"°",plusmn:"±",sup2:"²",sup3:"³",acute:"´",micro:"µ",para:"¶",middot:"·",cedil:"¸",sup1:"¹",ordm:"º",raquo:"»",frac14:"¼",frac12:"½",frac34:"¾",iquest:"¿",times:"×",divide:"÷",forall:"∀",part:"∂",exist:"∃",empty:"∅",nabla:"∇",isin:"∈",notin:"∉",ni:"∋",prod:"∏",sum:"∑",minus:"−",lowast:"∗",radic:"√",prop:"∝",infin:"∞",ang:"∠",and:"∧",or:"∨",cap:"∩",cup:"∪",int:"∫",there4:"∴",sim:"∼",cong:"≅",asymp:"≈",ne:"≠",equiv:"≡",le:"≤",ge:"≥",sub:"⊂",sup:"⊃",nsub:"⊄",sube:"⊆",supe:"⊇",oplus:"⊕",otimes:"⊗",perp:"⊥",sdot:"⋅",Alpha:"Α",Beta:"Β",Gamma:"Γ",Delta:"Δ",Epsilon:"Ε",Zeta:"Ζ",Eta:"Η",Theta:"Θ",Iota:"Ι",Kappa:"Κ",Lambda:"Λ",Mu:"Μ",Nu:"Ν",Xi:"Ξ",Omicron:"Ο",Pi:"Π",Rho:"Ρ",Sigma:"Σ",Tau:"Τ",Upsilon:"Υ",Phi:"Φ",Chi:"Χ",Psi:"Ψ",Omega:"Ω",alpha:"α",beta:"β",gamma:"γ",delta:"δ",epsilon:"ε",zeta:"ζ",eta:"η",theta:"θ",iota:"ι",kappa:"κ",lambda:"λ",mu:"μ",nu:"ν",xi:"ξ",omicron:"ο",pi:"π",rho:"ρ",sigmaf:"ς",sigma:"σ",tau:"τ",upsilon:"υ",phi:"φ",chi:"χ",psi:"ψ",omega:"ω",thetasym:"ϑ",upsih:"ϒ",piv:"ϖ",OElig:"Œ",oelig:"œ",Scaron:"Š",scaron:"š",Yuml:"Ÿ",fnof:"ƒ",circ:"ˆ",tilde:"˜",ensp:" ",emsp:" ",thinsp:" ",zwnj:"‌",zwj:"‍",lrm:"‎",rlm:"‏",ndash:"–",mdash:"—",lsquo:"‘",rsquo:"’",sbquo:"‚",ldquo:"“",rdquo:"”",bdquo:"„",dagger:"†",Dagger:"‡",bull:"•",hellip:"…",permil:"‰",prime:"′",Prime:"″",lsaquo:"‹",rsaquo:"›",oline:"‾",euro:"€",trade:"™",larr:"←",uarr:"↑",rarr:"→",darr:"↓",harr:"↔",crarr:"↵",lceil:"⌈",rceil:"⌉",lfloor:"⌊",rfloor:"⌋",loz:"◊",spades:"♠",clubs:"♣",hearts:"♥",diams:"♦"}),i.entityMap=i.HTML_ENTITIES},{"./conventions":24}],28:[function(e,t,i){var n=e("./dom");i.DOMImplementation=n.DOMImplementation,i.XMLSerializer=n.XMLSerializer,i.DOMParser=e("./dom-parser").DOMParser},{"./dom":26,"./dom-parser":25}],29:[function(e,t,i){var n=e("./conventions").NAMESPACE,r=/[A-Z_a-z\xC0-\xD6\xD8-\xF6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD]/,a=new RegExp("[\\-\\.0-9"+r.source.slice(1,-1)+"\\u00B7\\u0300-\\u036F\\u203F-\\u2040]"),s=new RegExp("^"+r.source+a.source+"*(?::"+r.source+a.source+"*)?$");function o(e,t){this.message=e,this.locator=t,Error.captureStackTrace&&Error.captureStackTrace(this,o)}function u(){}function l(e,t){return t.lineNumber=e.lineNumber,t.columnNumber=e.columnNumber,t}function h(e,t,i,r,a,s){function o(e,t,n){i.attributeNames.hasOwnProperty(e)&&s.fatalError("Attribute "+e+" redefined"),i.addValue(e,t,n)}for(var u,l=++t,h=0;;){var d=e.charAt(l);switch(d){case"=":if(1===h)u=e.slice(t,l),h=3;else{if(2!==h)throw new Error("attribute equal must after attrName");h=3}break;case"'":case'"':if(3===h||1===h){if(1===h&&(s.warning('attribute value must after "="'),u=e.slice(t,l)),t=l+1,!((l=e.indexOf(d,t))>0))throw new Error("attribute value no end '"+d+"' match");o(u,c=e.slice(t,l).replace(/&#?\w+;/g,a),t-1),h=5}else{if(4!=h)throw new Error('attribute value must after "="');o(u,c=e.slice(t,l).replace(/&#?\w+;/g,a),t),s.warning('attribute "'+u+'" missed start quot('+d+")!!"),t=l+1,h=5}break;case"/":switch(h){case 0:i.setTagName(e.slice(t,l));case 5:case 6:case 7:h=7,i.closed=!0;case 4:case 1:case 2:break;default:throw new Error("attribute invalid close char('/')")}break;case"":return s.error("unexpected end of input"),0==h&&i.setTagName(e.slice(t,l)),l;case">":switch(h){case 0:i.setTagName(e.slice(t,l));case 5:case 6:case 7:break;case 4:case 1:"/"===(c=e.slice(t,l)).slice(-1)&&(i.closed=!0,c=c.slice(0,-1));case 2:2===h&&(c=u),4==h?(s.warning('attribute "'+c+'" missed quot(")!'),o(u,c.replace(/&#?\w+;/g,a),t)):(n.isHTML(r[""])&&c.match(/^(?:disabled|checked|selected)$/i)||s.warning('attribute "'+c+'" missed value!! "'+c+'" instead!!'),o(c,c,t));break;case 3:throw new Error("attribute value missed!!")}return l;case"€":d=" ";default:if(d<=" ")switch(h){case 0:i.setTagName(e.slice(t,l)),h=6;break;case 1:u=e.slice(t,l),h=2;break;case 4:var c=e.slice(t,l).replace(/&#?\w+;/g,a);s.warning('attribute "'+c+'" missed quot(")!!'),o(u,c,t);case 5:h=6}else switch(h){case 2:i.tagName;n.isHTML(r[""])&&u.match(/^(?:disabled|checked|selected)$/i)||s.warning('attribute "'+u+'" missed value!! "'+u+'" instead2!!'),o(u,u,t),t=l,h=1;break;case 5:s.warning('attribute space is required"'+u+'"!!');case 6:h=1,t=l;break;case 3:h=4,t=l;break;case 7:throw new Error("elements closed character '/' and '>' must be connected to")}}l++}}function d(e,t,i){for(var r=e.tagName,a=null,s=e.length;s--;){var o=e[s],u=o.qName,l=o.value;if((f=u.indexOf(":"))>0)var h=o.prefix=u.slice(0,f),d=u.slice(f+1),c="xmlns"===h&&d;else d=u,h=null,c="xmlns"===u&&"";o.localName=d,!1!==c&&(null==a&&(a={},p(i,i={})),i[c]=a[c]=l,o.uri=n.XMLNS,t.startPrefixMapping(c,l))}for(s=e.length;s--;){(h=(o=e[s]).prefix)&&("xml"===h&&(o.uri=n.XML),"xmlns"!==h&&(o.uri=i[h||""]))}var f;(f=r.indexOf(":"))>0?(h=e.prefix=r.slice(0,f),d=e.localName=r.slice(f+1)):(h=null,d=e.localName=r);var m=e.uri=i[h||""];if(t.startElement(m,d,r,e),!e.closed)return e.currentNSMap=i,e.localNSMap=a,!0;if(t.endElement(m,d,r),a)for(h in a)t.endPrefixMapping(h)}function c(e,t,i,n,r){if(/^(?:script|textarea)$/i.test(i)){var a=e.indexOf("",t),s=e.substring(t+1,a);if(/[&<]/.test(s))return/^script$/i.test(i)?(r.characters(s,0,s.length),a):(s=s.replace(/&#?\w+;/g,n),r.characters(s,0,s.length),a)}return t+1}function f(e,t,i,n){var r=n[i];return null==r&&((r=e.lastIndexOf(""))t?(i.comment(e,t+4,r-t-4),r+3):(n.error("Unclosed comment"),-1):-1;default:if("CDATA["==e.substr(t+3,6)){var r=e.indexOf("]]>",t+9);return i.startCDATA(),i.characters(e,t+9,r-t-9),i.endCDATA(),r+3}var a=function(e,t){var i,n=[],r=/'[^']+'|"[^"]+"|[^\s<>\/=]+=?|(\/?\s*>|<)/g;r.lastIndex=t,r.exec(e);for(;i=r.exec(e);)if(n.push(i),i[1])return n}(e,t),s=a.length;if(s>1&&/!doctype/i.test(a[0][0])){var o=a[1][0],u=!1,l=!1;s>3&&(/^public$/i.test(a[2][0])?(u=a[3][0],l=s>4&&a[4][0]):/^system$/i.test(a[2][0])&&(l=a[3][0]));var h=a[s-1];return i.startDTD(o,u,l),i.endDTD(),h.index+h[0].length}}return-1}function _(e,t,i){var n=e.indexOf("?>",t);if(n){var r=e.substring(t,n).match(/^<\?(\S*)\s*([\s\S]*?)\s*$/);if(r){r[0].length;return i.processingInstruction(r[1],r[2]),n+2}return-1}return-1}function g(){this.attributeNames={}}o.prototype=new Error,o.prototype.name=o.name,u.prototype={parse:function(e,t,i){var r=this.domBuilder;r.startDocument(),p(t,t={}),function(e,t,i,r,a){function s(e){var t=e.slice(1,-1);return t in i?i[t]:"#"===t.charAt(0)?function(e){if(e>65535){var t=55296+((e-=65536)>>10),i=56320+(1023&e);return String.fromCharCode(t,i)}return String.fromCharCode(e)}(parseInt(t.substr(1).replace("x","0x"))):(a.error("entity not found:"+e),e)}function u(t){if(t>w){var i=e.substring(w,t).replace(/&#?\w+;/g,s);S&&p(w),r.characters(i,0,t-w),w=t}}function p(t,i){for(;t>=y&&(i=b.exec(e));)v=i.index,y=v+i[0].length,S.lineNumber++;S.columnNumber=t-v+1}var v=0,y=0,b=/.*(?:\r\n?|\n)|.*$/g,S=r.locator,T=[{currentNSMap:t}],E={},w=0;for(;;){try{var A=e.indexOf("<",w);if(A<0){if(!e.substr(w).match(/^\s*$/)){var C=r.doc,k=C.createTextNode(e.substr(w));C.appendChild(k),r.currentElement=k}return}switch(A>w&&u(A),e.charAt(A+1)){case"/":var P=e.indexOf(">",A+3),I=e.substring(A+2,P).replace(/[ \t\n\r]+$/g,""),L=T.pop();P<0?(I=e.substring(A+2).replace(/[\s<].*/,""),a.error("end tag name: "+I+" is not complete:"+L.tagName),P=A+1+I.length):I.match(/\sw?w=P:u(Math.max(A,w)+1)}}(e,t,i,r,this.errorHandler),r.endDocument()}},g.prototype={setTagName:function(e){if(!s.test(e))throw new Error("invalid tagName:"+e);this.tagName=e},addValue:function(e,t,i){if(!s.test(e))throw new Error("invalid attribute:"+e);this.attributeNames[e]=this.length,this[this.length++]={qName:e,value:t,offset:i}},length:0,getLocalName:function(e){return this[e].localName},getLocator:function(e){return this[e].locator},getQName:function(e){return this[e].qName},getURI:function(e){return this[e].uri},getValue:function(e){return this[e].value}},i.XMLReader=u,i.ParseError=o},{"./conventions":24}],30:[function(e,t,i){"use strict";i.byteLength=function(e){var t=l(e),i=t[0],n=t[1];return 3*(i+n)/4-n},i.toByteArray=function(e){var t,i,n=l(e),s=n[0],o=n[1],u=new a(function(e,t,i){return 3*(t+i)/4-i}(0,s,o)),h=0,d=o>0?s-4:s;for(i=0;i>16&255,u[h++]=t>>8&255,u[h++]=255&t;2===o&&(t=r[e.charCodeAt(i)]<<2|r[e.charCodeAt(i+1)]>>4,u[h++]=255&t);1===o&&(t=r[e.charCodeAt(i)]<<10|r[e.charCodeAt(i+1)]<<4|r[e.charCodeAt(i+2)]>>2,u[h++]=t>>8&255,u[h++]=255&t);return u},i.fromByteArray=function(e){for(var t,i=e.length,r=i%3,a=[],s=0,o=i-r;so?o:s+16383));1===r?(t=e[i-1],a.push(n[t>>2]+n[t<<4&63]+"==")):2===r&&(t=(e[i-2]<<8)+e[i-1],a.push(n[t>>10]+n[t>>4&63]+n[t<<2&63]+"="));return a.join("")};for(var n=[],r=[],a="undefined"!=typeof Uint8Array?Uint8Array:Array,s="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",o=0,u=s.length;o0)throw new Error("Invalid string. Length must be a multiple of 4");var i=e.indexOf("=");return-1===i&&(i=t),[i,i===t?0:4-i%4]}function h(e,t,i){for(var r,a,s=[],o=t;o>18&63]+n[a>>12&63]+n[a>>6&63]+n[63&a]);return s.join("")}r["-".charCodeAt(0)]=62,r["_".charCodeAt(0)]=63},{}],31:[function(e,t,i){},{}],32:[function(e,t,i){(function(t){ /*! * The buffer module from node.js, for the browser. * * @author Feross Aboukhadijeh * @license MIT */ "use strict";var n=e("base64-js"),r=e("ieee754");i.Buffer=t,i.SlowBuffer=function(e){+e!=e&&(e=0);return t.alloc(+e)},i.INSPECT_MAX_BYTES=50;function a(e){if(e>2147483647)throw new RangeError('The value "'+e+'" is invalid for option "size"');var i=new Uint8Array(e);return i.__proto__=t.prototype,i}function t(e,t,i){if("number"==typeof e){if("string"==typeof t)throw new TypeError('The "string" argument must be of type string. Received type number');return u(e)}return s(e,t,i)}function s(e,i,n){if("string"==typeof e)return function(e,i){"string"==typeof i&&""!==i||(i="utf8");if(!t.isEncoding(i))throw new TypeError("Unknown encoding: "+i);var n=0|d(e,i),r=a(n),s=r.write(e,i);s!==n&&(r=r.slice(0,s));return r}(e,i);if(ArrayBuffer.isView(e))return l(e);if(null==e)throw TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof e);if(B(e,ArrayBuffer)||e&&B(e.buffer,ArrayBuffer))return function(e,i,n){if(i<0||e.byteLength=2147483647)throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+2147483647..toString(16)+" bytes");return 0|e}function d(e,i){if(t.isBuffer(e))return e.length;if(ArrayBuffer.isView(e)||B(e,ArrayBuffer))return e.byteLength;if("string"!=typeof e)throw new TypeError('The "string" argument must be one of type string, Buffer, or ArrayBuffer. Received type '+typeof e);var n=e.length,r=arguments.length>2&&!0===arguments[2];if(!r&&0===n)return 0;for(var a=!1;;)switch(i){case"ascii":case"latin1":case"binary":return n;case"utf8":case"utf-8":return U(e).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*n;case"hex":return n>>>1;case"base64":return M(e).length;default:if(a)return r?-1:U(e).length;i=(""+i).toLowerCase(),a=!0}}function c(e,t,i){var n=!1;if((void 0===t||t<0)&&(t=0),t>this.length)return"";if((void 0===i||i>this.length)&&(i=this.length),i<=0)return"";if((i>>>=0)<=(t>>>=0))return"";for(e||(e="utf8");;)switch(e){case"hex":return C(this,t,i);case"utf8":case"utf-8":return E(this,t,i);case"ascii":return w(this,t,i);case"latin1":case"binary":return A(this,t,i);case"base64":return T(this,t,i);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return k(this,t,i);default:if(n)throw new TypeError("Unknown encoding: "+e);e=(e+"").toLowerCase(),n=!0}}function f(e,t,i){var n=e[t];e[t]=e[i],e[i]=n}function p(e,i,n,r,a){if(0===e.length)return-1;if("string"==typeof n?(r=n,n=0):n>2147483647?n=2147483647:n<-2147483648&&(n=-2147483648),N(n=+n)&&(n=a?0:e.length-1),n<0&&(n=e.length+n),n>=e.length){if(a)return-1;n=e.length-1}else if(n<0){if(!a)return-1;n=0}if("string"==typeof i&&(i=t.from(i,r)),t.isBuffer(i))return 0===i.length?-1:m(e,i,n,r,a);if("number"==typeof i)return i&=255,"function"==typeof Uint8Array.prototype.indexOf?a?Uint8Array.prototype.indexOf.call(e,i,n):Uint8Array.prototype.lastIndexOf.call(e,i,n):m(e,[i],n,r,a);throw new TypeError("val must be string, number or Buffer")}function m(e,t,i,n,r){var a,s=1,o=e.length,u=t.length;if(void 0!==n&&("ucs2"===(n=String(n).toLowerCase())||"ucs-2"===n||"utf16le"===n||"utf-16le"===n)){if(e.length<2||t.length<2)return-1;s=2,o/=2,u/=2,i/=2}function l(e,t){return 1===s?e[t]:e.readUInt16BE(t*s)}if(r){var h=-1;for(a=i;ao&&(i=o-u),a=i;a>=0;a--){for(var d=!0,c=0;cr&&(n=r):n=r;var a=t.length;n>a/2&&(n=a/2);for(var s=0;s>8,r=i%256,a.push(r),a.push(n);return a}(t,e.length-i),e,i,n)}function T(e,t,i){return 0===t&&i===e.length?n.fromByteArray(e):n.fromByteArray(e.slice(t,i))}function E(e,t,i){i=Math.min(e.length,i);for(var n=[],r=t;r239?4:l>223?3:l>191?2:1;if(r+d<=i)switch(d){case 1:l<128&&(h=l);break;case 2:128==(192&(a=e[r+1]))&&(u=(31&l)<<6|63&a)>127&&(h=u);break;case 3:a=e[r+1],s=e[r+2],128==(192&a)&&128==(192&s)&&(u=(15&l)<<12|(63&a)<<6|63&s)>2047&&(u<55296||u>57343)&&(h=u);break;case 4:a=e[r+1],s=e[r+2],o=e[r+3],128==(192&a)&&128==(192&s)&&128==(192&o)&&(u=(15&l)<<18|(63&a)<<12|(63&s)<<6|63&o)>65535&&u<1114112&&(h=u)}null===h?(h=65533,d=1):h>65535&&(h-=65536,n.push(h>>>10&1023|55296),h=56320|1023&h),n.push(h),r+=d}return function(e){var t=e.length;if(t<=4096)return String.fromCharCode.apply(String,e);var i="",n=0;for(;nt&&(e+=" ... "),""},t.prototype.compare=function(e,i,n,r,a){if(B(e,Uint8Array)&&(e=t.from(e,e.offset,e.byteLength)),!t.isBuffer(e))throw new TypeError('The "target" argument must be one of type Buffer or Uint8Array. Received type '+typeof e);if(void 0===i&&(i=0),void 0===n&&(n=e?e.length:0),void 0===r&&(r=0),void 0===a&&(a=this.length),i<0||n>e.length||r<0||a>this.length)throw new RangeError("out of range index");if(r>=a&&i>=n)return 0;if(r>=a)return-1;if(i>=n)return 1;if(this===e)return 0;for(var s=(a>>>=0)-(r>>>=0),o=(n>>>=0)-(i>>>=0),u=Math.min(s,o),l=this.slice(r,a),h=e.slice(i,n),d=0;d>>=0,isFinite(i)?(i>>>=0,void 0===n&&(n="utf8")):(n=i,i=void 0)}var r=this.length-t;if((void 0===i||i>r)&&(i=r),e.length>0&&(i<0||t<0)||t>this.length)throw new RangeError("Attempt to write outside buffer bounds");n||(n="utf8");for(var a=!1;;)switch(n){case"hex":return _(this,e,t,i);case"utf8":case"utf-8":return g(this,e,t,i);case"ascii":return v(this,e,t,i);case"latin1":case"binary":return y(this,e,t,i);case"base64":return b(this,e,t,i);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return S(this,e,t,i);default:if(a)throw new TypeError("Unknown encoding: "+n);n=(""+n).toLowerCase(),a=!0}},t.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};function w(e,t,i){var n="";i=Math.min(e.length,i);for(var r=t;rn)&&(i=n);for(var r="",a=t;ai)throw new RangeError("Trying to access beyond buffer length")}function I(e,i,n,r,a,s){if(!t.isBuffer(e))throw new TypeError('"buffer" argument must be a Buffer instance');if(i>a||ie.length)throw new RangeError("Index out of range")}function L(e,t,i,n,r,a){if(i+n>e.length)throw new RangeError("Index out of range");if(i<0)throw new RangeError("Index out of range")}function x(e,t,i,n,a){return t=+t,i>>>=0,a||L(e,0,i,4),r.write(e,t,i,n,23,4),i+4}function R(e,t,i,n,a){return t=+t,i>>>=0,a||L(e,0,i,8),r.write(e,t,i,n,52,8),i+8}t.prototype.slice=function(e,i){var n=this.length;(e=~~e)<0?(e+=n)<0&&(e=0):e>n&&(e=n),(i=void 0===i?n:~~i)<0?(i+=n)<0&&(i=0):i>n&&(i=n),i>>=0,t>>>=0,i||P(e,t,this.length);for(var n=this[e],r=1,a=0;++a>>=0,t>>>=0,i||P(e,t,this.length);for(var n=this[e+--t],r=1;t>0&&(r*=256);)n+=this[e+--t]*r;return n},t.prototype.readUInt8=function(e,t){return e>>>=0,t||P(e,1,this.length),this[e]},t.prototype.readUInt16LE=function(e,t){return e>>>=0,t||P(e,2,this.length),this[e]|this[e+1]<<8},t.prototype.readUInt16BE=function(e,t){return e>>>=0,t||P(e,2,this.length),this[e]<<8|this[e+1]},t.prototype.readUInt32LE=function(e,t){return e>>>=0,t||P(e,4,this.length),(this[e]|this[e+1]<<8|this[e+2]<<16)+16777216*this[e+3]},t.prototype.readUInt32BE=function(e,t){return e>>>=0,t||P(e,4,this.length),16777216*this[e]+(this[e+1]<<16|this[e+2]<<8|this[e+3])},t.prototype.readIntLE=function(e,t,i){e>>>=0,t>>>=0,i||P(e,t,this.length);for(var n=this[e],r=1,a=0;++a=(r*=128)&&(n-=Math.pow(2,8*t)),n},t.prototype.readIntBE=function(e,t,i){e>>>=0,t>>>=0,i||P(e,t,this.length);for(var n=t,r=1,a=this[e+--n];n>0&&(r*=256);)a+=this[e+--n]*r;return a>=(r*=128)&&(a-=Math.pow(2,8*t)),a},t.prototype.readInt8=function(e,t){return e>>>=0,t||P(e,1,this.length),128&this[e]?-1*(255-this[e]+1):this[e]},t.prototype.readInt16LE=function(e,t){e>>>=0,t||P(e,2,this.length);var i=this[e]|this[e+1]<<8;return 32768&i?4294901760|i:i},t.prototype.readInt16BE=function(e,t){e>>>=0,t||P(e,2,this.length);var i=this[e+1]|this[e]<<8;return 32768&i?4294901760|i:i},t.prototype.readInt32LE=function(e,t){return e>>>=0,t||P(e,4,this.length),this[e]|this[e+1]<<8|this[e+2]<<16|this[e+3]<<24},t.prototype.readInt32BE=function(e,t){return e>>>=0,t||P(e,4,this.length),this[e]<<24|this[e+1]<<16|this[e+2]<<8|this[e+3]},t.prototype.readFloatLE=function(e,t){return e>>>=0,t||P(e,4,this.length),r.read(this,e,!0,23,4)},t.prototype.readFloatBE=function(e,t){return e>>>=0,t||P(e,4,this.length),r.read(this,e,!1,23,4)},t.prototype.readDoubleLE=function(e,t){return e>>>=0,t||P(e,8,this.length),r.read(this,e,!0,52,8)},t.prototype.readDoubleBE=function(e,t){return e>>>=0,t||P(e,8,this.length),r.read(this,e,!1,52,8)},t.prototype.writeUIntLE=function(e,t,i,n){(e=+e,t>>>=0,i>>>=0,n)||I(this,e,t,i,Math.pow(2,8*i)-1,0);var r=1,a=0;for(this[t]=255&e;++a>>=0,i>>>=0,n)||I(this,e,t,i,Math.pow(2,8*i)-1,0);var r=i-1,a=1;for(this[t+r]=255&e;--r>=0&&(a*=256);)this[t+r]=e/a&255;return t+i},t.prototype.writeUInt8=function(e,t,i){return e=+e,t>>>=0,i||I(this,e,t,1,255,0),this[t]=255&e,t+1},t.prototype.writeUInt16LE=function(e,t,i){return e=+e,t>>>=0,i||I(this,e,t,2,65535,0),this[t]=255&e,this[t+1]=e>>>8,t+2},t.prototype.writeUInt16BE=function(e,t,i){return e=+e,t>>>=0,i||I(this,e,t,2,65535,0),this[t]=e>>>8,this[t+1]=255&e,t+2},t.prototype.writeUInt32LE=function(e,t,i){return e=+e,t>>>=0,i||I(this,e,t,4,4294967295,0),this[t+3]=e>>>24,this[t+2]=e>>>16,this[t+1]=e>>>8,this[t]=255&e,t+4},t.prototype.writeUInt32BE=function(e,t,i){return e=+e,t>>>=0,i||I(this,e,t,4,4294967295,0),this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e,t+4},t.prototype.writeIntLE=function(e,t,i,n){if(e=+e,t>>>=0,!n){var r=Math.pow(2,8*i-1);I(this,e,t,i,r-1,-r)}var a=0,s=1,o=0;for(this[t]=255&e;++a>0)-o&255;return t+i},t.prototype.writeIntBE=function(e,t,i,n){if(e=+e,t>>>=0,!n){var r=Math.pow(2,8*i-1);I(this,e,t,i,r-1,-r)}var a=i-1,s=1,o=0;for(this[t+a]=255&e;--a>=0&&(s*=256);)e<0&&0===o&&0!==this[t+a+1]&&(o=1),this[t+a]=(e/s>>0)-o&255;return t+i},t.prototype.writeInt8=function(e,t,i){return e=+e,t>>>=0,i||I(this,e,t,1,127,-128),e<0&&(e=255+e+1),this[t]=255&e,t+1},t.prototype.writeInt16LE=function(e,t,i){return e=+e,t>>>=0,i||I(this,e,t,2,32767,-32768),this[t]=255&e,this[t+1]=e>>>8,t+2},t.prototype.writeInt16BE=function(e,t,i){return e=+e,t>>>=0,i||I(this,e,t,2,32767,-32768),this[t]=e>>>8,this[t+1]=255&e,t+2},t.prototype.writeInt32LE=function(e,t,i){return e=+e,t>>>=0,i||I(this,e,t,4,2147483647,-2147483648),this[t]=255&e,this[t+1]=e>>>8,this[t+2]=e>>>16,this[t+3]=e>>>24,t+4},t.prototype.writeInt32BE=function(e,t,i){return e=+e,t>>>=0,i||I(this,e,t,4,2147483647,-2147483648),e<0&&(e=4294967295+e+1),this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e,t+4},t.prototype.writeFloatLE=function(e,t,i){return x(this,e,t,!0,i)},t.prototype.writeFloatBE=function(e,t,i){return x(this,e,t,!1,i)},t.prototype.writeDoubleLE=function(e,t,i){return R(this,e,t,!0,i)},t.prototype.writeDoubleBE=function(e,t,i){return R(this,e,t,!1,i)},t.prototype.copy=function(e,i,n,r){if(!t.isBuffer(e))throw new TypeError("argument should be a Buffer");if(n||(n=0),r||0===r||(r=this.length),i>=e.length&&(i=e.length),i||(i=0),r>0&&r=this.length)throw new RangeError("Index out of range");if(r<0)throw new RangeError("sourceEnd out of bounds");r>this.length&&(r=this.length),e.length-i=0;--s)e[s+i]=this[s+n];else Uint8Array.prototype.set.call(e,this.subarray(n,r),i);return a},t.prototype.fill=function(e,i,n,r){if("string"==typeof e){if("string"==typeof i?(r=i,i=0,n=this.length):"string"==typeof n&&(r=n,n=this.length),void 0!==r&&"string"!=typeof r)throw new TypeError("encoding must be a string");if("string"==typeof r&&!t.isEncoding(r))throw new TypeError("Unknown encoding: "+r);if(1===e.length){var a=e.charCodeAt(0);("utf8"===r&&a<128||"latin1"===r)&&(e=a)}}else"number"==typeof e&&(e&=255);if(i<0||this.length>>=0,n=void 0===n?this.length:n>>>0,e||(e=0),"number"==typeof e)for(s=i;s55295&&i<57344){if(!r){if(i>56319){(t-=3)>-1&&a.push(239,191,189);continue}if(s+1===n){(t-=3)>-1&&a.push(239,191,189);continue}r=i;continue}if(i<56320){(t-=3)>-1&&a.push(239,191,189),r=i;continue}i=65536+(r-55296<<10|i-56320)}else r&&(t-=3)>-1&&a.push(239,191,189);if(r=null,i<128){if((t-=1)<0)break;a.push(i)}else if(i<2048){if((t-=2)<0)break;a.push(i>>6|192,63&i|128)}else if(i<65536){if((t-=3)<0)break;a.push(i>>12|224,i>>6&63|128,63&i|128)}else{if(!(i<1114112))throw new Error("Invalid code point");if((t-=4)<0)break;a.push(i>>18|240,i>>12&63|128,i>>6&63|128,63&i|128)}}return a}function M(e){return n.toByteArray(function(e){if((e=(e=e.split("=")[0]).trim().replace(D,"")).length<2)return"";for(;e.length%4!=0;)e+="=";return e}(e))}function F(e,t,i,n){for(var r=0;r=t.length||r>=e.length);++r)t[r+i]=e[r];return r}function B(e,t){return e instanceof t||null!=e&&null!=e.constructor&&null!=e.constructor.name&&e.constructor.name===t.name}function N(e){return e!=e}}).call(this,e("buffer").Buffer)},{"base64-js":30,buffer:32,ieee754:35}],33:[function(e,t,i){(function(i){var n,r=void 0!==i?i:"undefined"!=typeof window?window:{},a=e("min-document");"undefined"!=typeof document?n=document:(n=r["__GLOBAL_DOCUMENT_CACHE@4"])||(n=r["__GLOBAL_DOCUMENT_CACHE@4"]=a),t.exports=n}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"min-document":31}],34:[function(e,t,i){(function(e){var i;i="undefined"!=typeof window?window:void 0!==e?e:"undefined"!=typeof self?self:{},t.exports=i}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],35:[function(e,t,i){i.read=function(e,t,i,n,r){var a,s,o=8*r-n-1,u=(1<>1,h=-7,d=i?r-1:0,c=i?-1:1,f=e[t+d];for(d+=c,a=f&(1<<-h)-1,f>>=-h,h+=o;h>0;a=256*a+e[t+d],d+=c,h-=8);for(s=a&(1<<-h)-1,a>>=-h,h+=n;h>0;s=256*s+e[t+d],d+=c,h-=8);if(0===a)a=1-l;else{if(a===u)return s?NaN:1/0*(f?-1:1);s+=Math.pow(2,n),a-=l}return(f?-1:1)*s*Math.pow(2,a-n)},i.write=function(e,t,i,n,r,a){var s,o,u,l=8*a-r-1,h=(1<>1,c=23===r?Math.pow(2,-24)-Math.pow(2,-77):0,f=n?0:a-1,p=n?1:-1,m=t<0||0===t&&1/t<0?1:0;for(t=Math.abs(t),isNaN(t)||t===1/0?(o=isNaN(t)?1:0,s=h):(s=Math.floor(Math.log(t)/Math.LN2),t*(u=Math.pow(2,-s))<1&&(s--,u*=2),(t+=s+d>=1?c/u:c*Math.pow(2,1-d))*u>=2&&(s++,u/=2),s+d>=h?(o=0,s=h):s+d>=1?(o=(t*u-1)*Math.pow(2,r),s+=d):(o=t*Math.pow(2,d-1)*Math.pow(2,r),s=0));r>=8;e[i+f]=255&o,f+=p,o/=256,r-=8);for(s=s<0;e[i+f]=255&s,f+=p,s/=256,l-=8);e[i+f-p]|=128*m}},{}],36:[function(e,t,i){t.exports=function(e){if(!e)return!1;var t=n.call(e);return"[object Function]"===t||"function"==typeof e&&"[object RegExp]"!==t||"undefined"!=typeof window&&(e===window.setTimeout||e===window.alert||e===window.confirm||e===window.prompt)};var n=Object.prototype.toString},{}],37:[function(e,t,i){function n(e){if(e&&"object"==typeof e){var t=e.which||e.keyCode||e.charCode;t&&(e=t)}if("number"==typeof e)return o[e];var i,n=String(e);return(i=r[n.toLowerCase()])?i:(i=a[n.toLowerCase()])||(1===n.length?n.charCodeAt(0):void 0)}n.isEventKey=function(e,t){if(e&&"object"==typeof e){var i=e.which||e.keyCode||e.charCode;if(null==i)return!1;if("string"==typeof t){var n;if(n=r[t.toLowerCase()])return n===i;if(n=a[t.toLowerCase()])return n===i}else if("number"==typeof t)return t===i;return!1}};var r=(i=t.exports=n).code=i.codes={backspace:8,tab:9,enter:13,shift:16,ctrl:17,alt:18,"pause/break":19,"caps lock":20,esc:27,space:32,"page up":33,"page down":34,end:35,home:36,left:37,up:38,right:39,down:40,insert:45,delete:46,command:91,"left command":91,"right command":93,"numpad *":106,"numpad +":107,"numpad -":109,"numpad .":110,"numpad /":111,"num lock":144,"scroll lock":145,"my computer":182,"my calculator":183,";":186,"=":187,",":188,"-":189,".":190,"/":191,"`":192,"[":219,"\\":220,"]":221,"'":222},a=i.aliases={windows:91,"⇧":16,"⌥":18,"⌃":17,"⌘":91,ctl:17,control:17,option:18,pause:19,break:19,caps:20,return:13,escape:27,spc:32,spacebar:32,pgup:33,pgdn:34,ins:45,del:46,cmd:91}; /*! * Programatically add the following */ for(s=97;s<123;s++)r[String.fromCharCode(s)]=s-32;for(var s=48;s<58;s++)r[s-48]=s;for(s=1;s<13;s++)r["f"+s]=s+111;for(s=0;s<10;s++)r["numpad "+s]=s+96;var o=i.names=i.title={};for(s in r)o[r[s]]=s;for(var u in a)r[u]=a[u]},{}],38:[function(e,t,i){ /*! @name m3u8-parser @version 4.7.0 @license Apache-2.0 */ "use strict";Object.defineProperty(i,"__esModule",{value:!0});var n=e("@babel/runtime/helpers/inheritsLoose"),r=e("@videojs/vhs-utils/cjs/stream.js"),a=e("@babel/runtime/helpers/extends"),s=e("@babel/runtime/helpers/assertThisInitialized"),o=e("@videojs/vhs-utils/cjs/decode-b64-to-uint8-array.js");function u(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var l=u(n),h=u(r),d=u(a),c=u(s),f=u(o),p=function(e){function t(){var t;return(t=e.call(this)||this).buffer="",t}return l.default(t,e),t.prototype.push=function(e){var t;for(this.buffer+=e,t=this.buffer.indexOf("\n");t>-1;t=this.buffer.indexOf("\n"))this.trigger("data",this.buffer.substring(0,t)),this.buffer=this.buffer.substring(t+1)},t}(h.default),m=String.fromCharCode(9),_=function(e){var t=/([0-9.]*)?@?([0-9.]*)?/.exec(e||""),i={};return t[1]&&(i.length=parseInt(t[1],10)),t[2]&&(i.offset=parseInt(t[2],10)),i},g=function(e){for(var t,i=e.split(new RegExp('(?:^|,)((?:[^=]*)=(?:"[^"]*"|[^,]*))')),n={},r=i.length;r--;)""!==i[r]&&((t=/([^=]*)=(.*)/.exec(i[r]).slice(1))[0]=t[0].replace(/^\s+|\s+$/g,""),t[1]=t[1].replace(/^\s+|\s+$/g,""),t[1]=t[1].replace(/^['"](.*)['"]$/g,"$1"),n[t[0]]=t[1]);return n},v=function(e){function t(){var t;return(t=e.call(this)||this).customParsers=[],t.tagMappers=[],t}l.default(t,e);var i=t.prototype;return i.push=function(e){var t,i,n=this;0!==(e=e.trim()).length&&("#"===e[0]?this.tagMappers.reduce((function(t,i){var n=i(e);return n===e?t:t.concat([n])}),[e]).forEach((function(e){for(var r=0;r0&&(s.duration=e.duration),0===e.duration&&(s.duration=.01,this.trigger("info",{message:"updating zero segment duration to a small value"})),this.manifest.segments=a},key:function(){if(e.attributes)if("NONE"!==e.attributes.METHOD)if(e.attributes.URI){if("com.apple.streamingkeydelivery"===e.attributes.KEYFORMAT)return this.manifest.contentProtection=this.manifest.contentProtection||{},void(this.manifest.contentProtection["com.apple.fps.1_0"]={attributes:e.attributes});if("urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"===e.attributes.KEYFORMAT){return-1===["SAMPLE-AES","SAMPLE-AES-CTR","SAMPLE-AES-CENC"].indexOf(e.attributes.METHOD)?void this.trigger("warn",{message:"invalid key method provided for Widevine"}):("SAMPLE-AES-CENC"===e.attributes.METHOD&&this.trigger("warn",{message:"SAMPLE-AES-CENC is deprecated, please use SAMPLE-AES-CTR instead"}),"data:text/plain;base64,"!==e.attributes.URI.substring(0,23)?void this.trigger("warn",{message:"invalid key URI provided for Widevine"}):e.attributes.KEYID&&"0x"===e.attributes.KEYID.substring(0,2)?(this.manifest.contentProtection=this.manifest.contentProtection||{},void(this.manifest.contentProtection["com.widevine.alpha"]={attributes:{schemeIdUri:e.attributes.KEYFORMAT,keyId:e.attributes.KEYID.substring(2)},pssh:f.default(e.attributes.URI.split(",")[1])})):void this.trigger("warn",{message:"invalid key ID provided for Widevine"}))}e.attributes.METHOD||this.trigger("warn",{message:"defaulting key method to AES-128"}),n={method:e.attributes.METHOD||"AES-128",uri:e.attributes.URI},void 0!==e.attributes.IV&&(n.iv=e.attributes.IV)}else this.trigger("warn",{message:"ignoring key declaration without URI"});else n=null;else this.trigger("warn",{message:"ignoring key declaration without attribute list"})},"media-sequence":function(){isFinite(e.number)?this.manifest.mediaSequence=e.number:this.trigger("warn",{message:"ignoring invalid media sequence: "+e.number})},"discontinuity-sequence":function(){isFinite(e.number)?(this.manifest.discontinuitySequence=e.number,h=e.number):this.trigger("warn",{message:"ignoring invalid discontinuity sequence: "+e.number})},"playlist-type":function(){/VOD|EVENT/.test(e.playlistType)?this.manifest.playlistType=e.playlistType:this.trigger("warn",{message:"ignoring unknown playlist type: "+e.playlist})},map:function(){i={},e.uri&&(i.uri=e.uri),e.byterange&&(i.byterange=e.byterange),n&&(i.key=n)},"stream-inf":function(){this.manifest.playlists=a,this.manifest.mediaGroups=this.manifest.mediaGroups||l,e.attributes?(s.attributes||(s.attributes={}),d.default(s.attributes,e.attributes)):this.trigger("warn",{message:"ignoring empty stream-inf attributes"})},media:function(){if(this.manifest.mediaGroups=this.manifest.mediaGroups||l,e.attributes&&e.attributes.TYPE&&e.attributes["GROUP-ID"]&&e.attributes.NAME){var i=this.manifest.mediaGroups[e.attributes.TYPE];i[e.attributes["GROUP-ID"]]=i[e.attributes["GROUP-ID"]]||{},t=i[e.attributes["GROUP-ID"]],(c={default:/yes/i.test(e.attributes.DEFAULT)}).default?c.autoselect=!0:c.autoselect=/yes/i.test(e.attributes.AUTOSELECT),e.attributes.LANGUAGE&&(c.language=e.attributes.LANGUAGE),e.attributes.URI&&(c.uri=e.attributes.URI),e.attributes["INSTREAM-ID"]&&(c.instreamId=e.attributes["INSTREAM-ID"]),e.attributes.CHARACTERISTICS&&(c.characteristics=e.attributes.CHARACTERISTICS),e.attributes.FORCED&&(c.forced=/yes/i.test(e.attributes.FORCED)),t[e.attributes.NAME]=c}else this.trigger("warn",{message:"ignoring incomplete or missing media group"})},discontinuity:function(){h+=1,s.discontinuity=!0,this.manifest.discontinuityStarts.push(a.length)},"program-date-time":function(){void 0===this.manifest.dateTimeString&&(this.manifest.dateTimeString=e.dateTimeString,this.manifest.dateTimeObject=e.dateTimeObject),s.dateTimeString=e.dateTimeString,s.dateTimeObject=e.dateTimeObject},targetduration:function(){!isFinite(e.duration)||e.duration<0?this.trigger("warn",{message:"ignoring invalid target duration: "+e.duration}):(this.manifest.targetDuration=e.duration,b.call(this,this.manifest))},start:function(){e.attributes&&!isNaN(e.attributes["TIME-OFFSET"])?this.manifest.start={timeOffset:e.attributes["TIME-OFFSET"],precise:e.attributes.PRECISE}:this.trigger("warn",{message:"ignoring start declaration without appropriate attribute list"})},"cue-out":function(){s.cueOut=e.data},"cue-out-cont":function(){s.cueOutCont=e.data},"cue-in":function(){s.cueIn=e.data},skip:function(){this.manifest.skip=y(e.attributes),this.warnOnMissingAttributes_("#EXT-X-SKIP",e.attributes,["SKIPPED-SEGMENTS"])},part:function(){var t=this;o=!0;var i=this.manifest.segments.length,n=y(e.attributes);s.parts=s.parts||[],s.parts.push(n),n.byterange&&(n.byterange.hasOwnProperty("offset")||(n.byterange.offset=_),_=n.byterange.offset+n.byterange.length);var r=s.parts.length-1;this.warnOnMissingAttributes_("#EXT-X-PART #"+r+" for segment #"+i,e.attributes,["URI","DURATION"]),this.manifest.renditionReports&&this.manifest.renditionReports.forEach((function(e,i){e.hasOwnProperty("lastPart")||t.trigger("warn",{message:"#EXT-X-RENDITION-REPORT #"+i+" lacks required attribute(s): LAST-PART"})}))},"server-control":function(){var t=this.manifest.serverControl=y(e.attributes);t.hasOwnProperty("canBlockReload")||(t.canBlockReload=!1,this.trigger("info",{message:"#EXT-X-SERVER-CONTROL defaulting CAN-BLOCK-RELOAD to false"})),b.call(this,this.manifest),t.canSkipDateranges&&!t.hasOwnProperty("canSkipUntil")&&this.trigger("warn",{message:"#EXT-X-SERVER-CONTROL lacks required attribute CAN-SKIP-UNTIL which is required when CAN-SKIP-DATERANGES is set"})},"preload-hint":function(){var t=this.manifest.segments.length,i=y(e.attributes),n=i.type&&"PART"===i.type;s.preloadHints=s.preloadHints||[],s.preloadHints.push(i),i.byterange&&(i.byterange.hasOwnProperty("offset")||(i.byterange.offset=n?_:0,n&&(_=i.byterange.offset+i.byterange.length)));var r=s.preloadHints.length-1;if(this.warnOnMissingAttributes_("#EXT-X-PRELOAD-HINT #"+r+" for segment #"+t,e.attributes,["TYPE","URI"]),i.type)for(var a=0;a=r&&console.debug("["+a.getDurationString(new Date-n,1e3)+"]","["+e+"]",t)},log:function(e,t){this.debug(e.msg)},info:function(e,t){2>=r&&console.info("["+a.getDurationString(new Date-n,1e3)+"]","["+e+"]",t)},warn:function(e,t){3>=r&&a.getDurationString(new Date-n,1e3)},error:function(e,t){4>=r&&console.error("["+a.getDurationString(new Date-n,1e3)+"]","["+e+"]",t)}});a.getDurationString=function(e,t){var i;function n(e,t){for(var i=(""+e).split(".");i[0].length0){for(var i="",n=0;n0&&(i+=","),i+="["+a.getDurationString(e.start(n))+","+a.getDurationString(e.end(n))+"]";return i}return"(empty)"},void 0!==i&&(i.Log=a);var s=function(e){if(!(e instanceof ArrayBuffer))throw"Needs an array buffer";this.buffer=e,this.dataview=new DataView(e),this.position=0};s.prototype.getPosition=function(){return this.position},s.prototype.getEndPosition=function(){return this.buffer.byteLength},s.prototype.getLength=function(){return this.buffer.byteLength},s.prototype.seek=function(e){var t=Math.max(0,Math.min(this.buffer.byteLength,e));return this.position=isNaN(t)||!isFinite(t)?0:t,!0},s.prototype.isEos=function(){return this.getPosition()>=this.getEndPosition()},s.prototype.readAnyInt=function(e,t){var i=0;if(this.position+e<=this.buffer.byteLength){switch(e){case 1:i=t?this.dataview.getInt8(this.position):this.dataview.getUint8(this.position);break;case 2:i=t?this.dataview.getInt16(this.position):this.dataview.getUint16(this.position);break;case 3:if(t)throw"No method for reading signed 24 bits values";i=this.dataview.getUint8(this.position)<<16,i|=this.dataview.getUint8(this.position)<<8,i|=this.dataview.getUint8(this.position);break;case 4:i=t?this.dataview.getInt32(this.position):this.dataview.getUint32(this.position);break;case 8:if(t)throw"No method for reading signed 64 bits values";i=this.dataview.getUint32(this.position)<<32,i|=this.dataview.getUint32(this.position);break;default:throw"readInt method not implemented for size: "+e}return this.position+=e,i}throw"Not enough bytes in buffer"},s.prototype.readUint8=function(){return this.readAnyInt(1,!1)},s.prototype.readUint16=function(){return this.readAnyInt(2,!1)},s.prototype.readUint24=function(){return this.readAnyInt(3,!1)},s.prototype.readUint32=function(){return this.readAnyInt(4,!1)},s.prototype.readUint64=function(){return this.readAnyInt(8,!1)},s.prototype.readString=function(e){if(this.position+e<=this.buffer.byteLength){for(var t="",i=0;ithis._byteLength&&(this._byteLength=t);else{for(i<1&&(i=1);t>i;)i*=2;var n=new ArrayBuffer(i),r=new Uint8Array(this._buffer);new Uint8Array(n,0,r.length).set(r),this.buffer=n,this._byteLength=t}}},o.prototype._trimAlloc=function(){if(this._byteLength!=this._buffer.byteLength){var e=new ArrayBuffer(this._byteLength),t=new Uint8Array(e),i=new Uint8Array(this._buffer,0,t.length);t.set(i),this.buffer=e}},o.BIG_ENDIAN=!1,o.LITTLE_ENDIAN=!0,o.prototype._byteLength=0,Object.defineProperty(o.prototype,"byteLength",{get:function(){return this._byteLength-this._byteOffset}}),Object.defineProperty(o.prototype,"buffer",{get:function(){return this._trimAlloc(),this._buffer},set:function(e){this._buffer=e,this._dataView=new DataView(this._buffer,this._byteOffset),this._byteLength=this._buffer.byteLength}}),Object.defineProperty(o.prototype,"byteOffset",{get:function(){return this._byteOffset},set:function(e){this._byteOffset=e,this._dataView=new DataView(this._buffer,this._byteOffset),this._byteLength=this._buffer.byteLength}}),Object.defineProperty(o.prototype,"dataView",{get:function(){return this._dataView},set:function(e){this._byteOffset=e.byteOffset,this._buffer=e.buffer,this._dataView=new DataView(this._buffer,this._byteOffset),this._byteLength=this._byteOffset+e.byteLength}}),o.prototype.seek=function(e){var t=Math.max(0,Math.min(this.byteLength,e));this.position=isNaN(t)||!isFinite(t)?0:t},o.prototype.isEof=function(){return this.position>=this._byteLength},o.prototype.mapUint8Array=function(e){this._realloc(1*e);var t=new Uint8Array(this._buffer,this.byteOffset+this.position,e);return this.position+=1*e,t},o.prototype.readInt32Array=function(e,t){e=null==e?this.byteLength-this.position/4:e;var i=new Int32Array(e);return o.memcpy(i.buffer,0,this.buffer,this.byteOffset+this.position,e*i.BYTES_PER_ELEMENT),o.arrayToNative(i,null==t?this.endianness:t),this.position+=i.byteLength,i},o.prototype.readInt16Array=function(e,t){e=null==e?this.byteLength-this.position/2:e;var i=new Int16Array(e);return o.memcpy(i.buffer,0,this.buffer,this.byteOffset+this.position,e*i.BYTES_PER_ELEMENT),o.arrayToNative(i,null==t?this.endianness:t),this.position+=i.byteLength,i},o.prototype.readInt8Array=function(e){e=null==e?this.byteLength-this.position:e;var t=new Int8Array(e);return o.memcpy(t.buffer,0,this.buffer,this.byteOffset+this.position,e*t.BYTES_PER_ELEMENT),this.position+=t.byteLength,t},o.prototype.readUint32Array=function(e,t){e=null==e?this.byteLength-this.position/4:e;var i=new Uint32Array(e);return o.memcpy(i.buffer,0,this.buffer,this.byteOffset+this.position,e*i.BYTES_PER_ELEMENT),o.arrayToNative(i,null==t?this.endianness:t),this.position+=i.byteLength,i},o.prototype.readUint16Array=function(e,t){e=null==e?this.byteLength-this.position/2:e;var i=new Uint16Array(e);return o.memcpy(i.buffer,0,this.buffer,this.byteOffset+this.position,e*i.BYTES_PER_ELEMENT),o.arrayToNative(i,null==t?this.endianness:t),this.position+=i.byteLength,i},o.prototype.readUint8Array=function(e){e=null==e?this.byteLength-this.position:e;var t=new Uint8Array(e);return o.memcpy(t.buffer,0,this.buffer,this.byteOffset+this.position,e*t.BYTES_PER_ELEMENT),this.position+=t.byteLength,t},o.prototype.readFloat64Array=function(e,t){e=null==e?this.byteLength-this.position/8:e;var i=new Float64Array(e);return o.memcpy(i.buffer,0,this.buffer,this.byteOffset+this.position,e*i.BYTES_PER_ELEMENT),o.arrayToNative(i,null==t?this.endianness:t),this.position+=i.byteLength,i},o.prototype.readFloat32Array=function(e,t){e=null==e?this.byteLength-this.position/4:e;var i=new Float32Array(e);return o.memcpy(i.buffer,0,this.buffer,this.byteOffset+this.position,e*i.BYTES_PER_ELEMENT),o.arrayToNative(i,null==t?this.endianness:t),this.position+=i.byteLength,i},o.prototype.readInt32=function(e){var t=this._dataView.getInt32(this.position,null==e?this.endianness:e);return this.position+=4,t},o.prototype.readInt16=function(e){var t=this._dataView.getInt16(this.position,null==e?this.endianness:e);return this.position+=2,t},o.prototype.readInt8=function(){var e=this._dataView.getInt8(this.position);return this.position+=1,e},o.prototype.readUint32=function(e){var t=this._dataView.getUint32(this.position,null==e?this.endianness:e);return this.position+=4,t},o.prototype.readUint16=function(e){var t=this._dataView.getUint16(this.position,null==e?this.endianness:e);return this.position+=2,t},o.prototype.readUint8=function(){var e=this._dataView.getUint8(this.position);return this.position+=1,e},o.prototype.readFloat32=function(e){var t=this._dataView.getFloat32(this.position,null==e?this.endianness:e);return this.position+=4,t},o.prototype.readFloat64=function(e){var t=this._dataView.getFloat64(this.position,null==e?this.endianness:e);return this.position+=8,t},o.endianness=new Int8Array(new Int16Array([1]).buffer)[0]>0,o.memcpy=function(e,t,i,n,r){var a=new Uint8Array(e,t,r),s=new Uint8Array(i,n,r);a.set(s)},o.arrayToNative=function(e,t){return t==this.endianness?e:this.flipArrayEndianness(e)},o.nativeToEndian=function(e,t){return this.endianness==t?e:this.flipArrayEndianness(e)},o.flipArrayEndianness=function(e){for(var t=new Uint8Array(e.buffer,e.byteOffset,e.byteLength),i=0;ir;n--,r++){var a=t[r];t[r]=t[n],t[n]=a}return e},o.prototype.failurePosition=0,String.fromCharCodeUint8=function(e){for(var t=[],i=0;i>16),this.writeUint8((65280&e)>>8),this.writeUint8(255&e)},o.prototype.adjustUint32=function(e,t){var i=this.position;this.seek(e),this.writeUint32(t),this.seek(i)},o.prototype.mapInt32Array=function(e,t){this._realloc(4*e);var i=new Int32Array(this._buffer,this.byteOffset+this.position,e);return o.arrayToNative(i,null==t?this.endianness:t),this.position+=4*e,i},o.prototype.mapInt16Array=function(e,t){this._realloc(2*e);var i=new Int16Array(this._buffer,this.byteOffset+this.position,e);return o.arrayToNative(i,null==t?this.endianness:t),this.position+=2*e,i},o.prototype.mapInt8Array=function(e){this._realloc(1*e);var t=new Int8Array(this._buffer,this.byteOffset+this.position,e);return this.position+=1*e,t},o.prototype.mapUint32Array=function(e,t){this._realloc(4*e);var i=new Uint32Array(this._buffer,this.byteOffset+this.position,e);return o.arrayToNative(i,null==t?this.endianness:t),this.position+=4*e,i},o.prototype.mapUint16Array=function(e,t){this._realloc(2*e);var i=new Uint16Array(this._buffer,this.byteOffset+this.position,e);return o.arrayToNative(i,null==t?this.endianness:t),this.position+=2*e,i},o.prototype.mapFloat64Array=function(e,t){this._realloc(8*e);var i=new Float64Array(this._buffer,this.byteOffset+this.position,e);return o.arrayToNative(i,null==t?this.endianness:t),this.position+=8*e,i},o.prototype.mapFloat32Array=function(e,t){this._realloc(4*e);var i=new Float32Array(this._buffer,this.byteOffset+this.position,e);return o.arrayToNative(i,null==t?this.endianness:t),this.position+=4*e,i};var l=function(e){this.buffers=[],this.bufferIndex=-1,e&&(this.insertBuffer(e),this.bufferIndex=0)};(l.prototype=new o(new ArrayBuffer,0,o.BIG_ENDIAN)).initialized=function(){var e;return this.bufferIndex>-1||(this.buffers.length>0?0===(e=this.buffers[0]).fileStart?(this.buffer=e,this.bufferIndex=0,a.debug("MultiBufferStream","Stream ready for parsing"),!0):(a.warn("MultiBufferStream","The first buffer should have a fileStart of 0"),this.logBufferLevel(),!1):(a.warn("MultiBufferStream","No buffer to start parsing from"),this.logBufferLevel(),!1))},ArrayBuffer.concat=function(e,t){a.debug("ArrayBuffer","Trying to create a new buffer of size: "+(e.byteLength+t.byteLength));var i=new Uint8Array(e.byteLength+t.byteLength);return i.set(new Uint8Array(e),0),i.set(new Uint8Array(t),e.byteLength),i.buffer},l.prototype.reduceBuffer=function(e,t,i){var n;return(n=new Uint8Array(i)).set(new Uint8Array(e,t,i)),n.buffer.fileStart=e.fileStart+t,n.buffer.usedBytes=0,n.buffer},l.prototype.insertBuffer=function(e){for(var t=!0,i=0;in.byteLength){this.buffers.splice(i,1),i--;continue}a.warn("MultiBufferStream","Buffer (fileStart: "+e.fileStart+" - Length: "+e.byteLength+") already appended, ignoring")}else e.fileStart+e.byteLength<=n.fileStart||(e=this.reduceBuffer(e,0,n.fileStart-e.fileStart)),a.debug("MultiBufferStream","Appending new buffer (fileStart: "+e.fileStart+" - Length: "+e.byteLength+")"),this.buffers.splice(i,0,e),0===i&&(this.buffer=e);t=!1;break}if(e.fileStart0)){t=!1;break}e=this.reduceBuffer(e,r,s)}}t&&(a.debug("MultiBufferStream","Appending new buffer (fileStart: "+e.fileStart+" - Length: "+e.byteLength+")"),this.buffers.push(e),0===i&&(this.buffer=e))},l.prototype.logBufferLevel=function(e){var t,i,n,r,s,o=[],u="";for(n=0,r=0,t=0;t0&&(u+=s.end-1+"]");var l=e?a.info:a.debug;0===this.buffers.length?l("MultiBufferStream","No more buffer in memory"):l("MultiBufferStream",this.buffers.length+" stored buffer(s) ("+n+"/"+r+" bytes): "+u)},l.prototype.cleanBuffers=function(){var e,t;for(e=0;e"+this.buffer.byteLength+")"),!0}return!1}return!1},l.prototype.findPosition=function(e,t,i){var n,r=null,s=-1;for(n=!0===e?0:this.bufferIndex;n=t?(a.debug("MultiBufferStream","Found position in existing buffer #"+s),s):-1},l.prototype.findEndContiguousBuf=function(e){var t,i,n,r=void 0!==e?e:this.bufferIndex;if(i=this.buffers[r],this.buffers.length>r+1)for(t=r+1;t>3;return 31===n&&i.data.length>=2&&(n=32+((7&i.data[0])<<3)+((224&i.data[1])>>5)),n}return null},i.DecoderConfigDescriptor=function(e){i.Descriptor.call(this,4,e)},i.DecoderConfigDescriptor.prototype=new i.Descriptor,i.DecoderConfigDescriptor.prototype.parse=function(e){this.oti=e.readUint8(),this.streamType=e.readUint8(),this.bufferSize=e.readUint24(),this.maxBitrate=e.readUint32(),this.avgBitrate=e.readUint32(),this.size-=13,this.parseRemainingDescriptors(e)},i.DecoderSpecificInfo=function(e){i.Descriptor.call(this,5,e)},i.DecoderSpecificInfo.prototype=new i.Descriptor,i.SLConfigDescriptor=function(e){i.Descriptor.call(this,6,e)},i.SLConfigDescriptor.prototype=new i.Descriptor,this};void 0!==i&&(i.MPEG4DescriptorParser=h);var d={ERR_INVALID_DATA:-1,ERR_NOT_ENOUGH_DATA:0,OK:1,BASIC_BOXES:["mdat","idat","free","skip","meco","strk"],FULL_BOXES:["hmhd","nmhd","iods","xml ","bxml","ipro","mere"],CONTAINER_BOXES:[["moov",["trak","pssh"]],["trak"],["edts"],["mdia"],["minf"],["dinf"],["stbl",["sgpd","sbgp"]],["mvex",["trex"]],["moof",["traf"]],["traf",["trun","sgpd","sbgp"]],["vttc"],["tref"],["iref"],["mfra",["tfra"]],["meco"],["hnti"],["hinf"],["strk"],["strd"],["sinf"],["rinf"],["schi"],["trgr"],["udta",["kind"]],["iprp",["ipma"]],["ipco"]],boxCodes:[],fullBoxCodes:[],containerBoxCodes:[],sampleEntryCodes:{},sampleGroupEntryCodes:[],trackGroupTypes:[],UUIDBoxes:{},UUIDs:[],initialize:function(){d.FullBox.prototype=new d.Box,d.ContainerBox.prototype=new d.Box,d.SampleEntry.prototype=new d.Box,d.TrackGroupTypeBox.prototype=new d.FullBox,d.BASIC_BOXES.forEach((function(e){d.createBoxCtor(e)})),d.FULL_BOXES.forEach((function(e){d.createFullBoxCtor(e)})),d.CONTAINER_BOXES.forEach((function(e){d.createContainerBoxCtor(e[0],null,e[1])}))},Box:function(e,t,i){this.type=e,this.size=t,this.uuid=i},FullBox:function(e,t,i){d.Box.call(this,e,t,i),this.flags=0,this.version=0},ContainerBox:function(e,t,i){d.Box.call(this,e,t,i),this.boxes=[]},SampleEntry:function(e,t,i,n){d.ContainerBox.call(this,e,t),this.hdr_size=i,this.start=n},SampleGroupEntry:function(e){this.grouping_type=e},TrackGroupTypeBox:function(e,t){d.FullBox.call(this,e,t)},createBoxCtor:function(e,t){d.boxCodes.push(e),d[e+"Box"]=function(t){d.Box.call(this,e,t)},d[e+"Box"].prototype=new d.Box,t&&(d[e+"Box"].prototype.parse=t)},createFullBoxCtor:function(e,t){d[e+"Box"]=function(t){d.FullBox.call(this,e,t)},d[e+"Box"].prototype=new d.FullBox,d[e+"Box"].prototype.parse=function(e){this.parseFullHeader(e),t&&t.call(this,e)}},addSubBoxArrays:function(e){if(e){this.subBoxNames=e;for(var t=e.length,i=0;ii?(a.error("BoxParser","Box of type '"+h+"' has a size "+l+" greater than its container size "+i),{code:d.ERR_NOT_ENOUGH_DATA,type:h,size:l,hdr_size:u,start:o}):o+l>e.getEndPosition()?(e.seek(o),a.info("BoxParser","Not enough data in stream to parse the entire '"+h+"' box"),{code:d.ERR_NOT_ENOUGH_DATA,type:h,size:l,hdr_size:u,start:o}):t?{code:d.OK,type:h,size:l,hdr_size:u,start:o}:(d[h+"Box"]?n=new d[h+"Box"](l):"uuid"!==h?(a.warn("BoxParser","Unknown box type: '"+h+"'"),(n=new d.Box(h,l)).has_unparsed_data=!0):d.UUIDBoxes[s]?n=new d.UUIDBoxes[s](l):(a.warn("BoxParser","Unknown uuid type: '"+s+"'"),(n=new d.Box(h,l)).uuid=s,n.has_unparsed_data=!0),n.hdr_size=u,n.start=o,n.write===d.Box.prototype.write&&"mdat"!==n.type&&(a.info("BoxParser","'"+c+"' box writing not yet implemented, keeping unparsed data in memory for later write"),n.parseDataAndRewind(e)),n.parse(e),(r=e.getPosition()-(n.start+n.size))<0?(a.warn("BoxParser","Parsing of box '"+c+"' did not read the entire indicated box data size (missing "+-r+" bytes), seeking forward"),e.seek(n.start+n.size)):r>0&&(a.error("BoxParser","Parsing of box '"+c+"' read "+r+" more bytes than the indicated box data size, seeking backwards"),e.seek(n.start+n.size)),{code:d.OK,box:n,size:n.size})},d.Box.prototype.parse=function(e){"mdat"!=this.type?this.data=e.readUint8Array(this.size-this.hdr_size):0===this.size?e.seek(e.getEndPosition()):e.seek(this.start+this.size)},d.Box.prototype.parseDataAndRewind=function(e){this.data=e.readUint8Array(this.size-this.hdr_size),e.position-=this.size-this.hdr_size},d.FullBox.prototype.parseDataAndRewind=function(e){this.parseFullHeader(e),this.data=e.readUint8Array(this.size-this.hdr_size),this.hdr_size-=4,e.position-=this.size-this.hdr_size},d.FullBox.prototype.parseFullHeader=function(e){this.version=e.readUint8(),this.flags=e.readUint24(),this.hdr_size+=4},d.FullBox.prototype.parse=function(e){this.parseFullHeader(e),this.data=e.readUint8Array(this.size-this.hdr_size)},d.ContainerBox.prototype.parse=function(e){for(var t,i;e.getPosition()>10&31,t[1]=this.language>>5&31,t[2]=31&this.language,this.languageString=String.fromCharCode(t[0]+96,t[1]+96,t[2]+96)},d.SAMPLE_ENTRY_TYPE_VISUAL="Visual",d.SAMPLE_ENTRY_TYPE_AUDIO="Audio",d.SAMPLE_ENTRY_TYPE_HINT="Hint",d.SAMPLE_ENTRY_TYPE_METADATA="Metadata",d.SAMPLE_ENTRY_TYPE_SUBTITLE="Subtitle",d.SAMPLE_ENTRY_TYPE_SYSTEM="System",d.SAMPLE_ENTRY_TYPE_TEXT="Text",d.SampleEntry.prototype.parseHeader=function(e){e.readUint8Array(6),this.data_reference_index=e.readUint16(),this.hdr_size+=8},d.SampleEntry.prototype.parse=function(e){this.parseHeader(e),this.data=e.readUint8Array(this.size-this.hdr_size)},d.SampleEntry.prototype.parseDataAndRewind=function(e){this.parseHeader(e),this.data=e.readUint8Array(this.size-this.hdr_size),this.hdr_size-=8,e.position-=this.size-this.hdr_size},d.SampleEntry.prototype.parseFooter=function(e){d.ContainerBox.prototype.parse.call(this,e)},d.createMediaSampleEntryCtor(d.SAMPLE_ENTRY_TYPE_HINT),d.createMediaSampleEntryCtor(d.SAMPLE_ENTRY_TYPE_METADATA),d.createMediaSampleEntryCtor(d.SAMPLE_ENTRY_TYPE_SUBTITLE),d.createMediaSampleEntryCtor(d.SAMPLE_ENTRY_TYPE_SYSTEM),d.createMediaSampleEntryCtor(d.SAMPLE_ENTRY_TYPE_TEXT),d.createMediaSampleEntryCtor(d.SAMPLE_ENTRY_TYPE_VISUAL,(function(e){var t;this.parseHeader(e),e.readUint16(),e.readUint16(),e.readUint32Array(3),this.width=e.readUint16(),this.height=e.readUint16(),this.horizresolution=e.readUint32(),this.vertresolution=e.readUint32(),e.readUint32(),this.frame_count=e.readUint16(),t=Math.min(31,e.readUint8()),this.compressorname=e.readString(t),t<31&&e.readString(31-t),this.depth=e.readUint16(),e.readUint16(),this.parseFooter(e)})),d.createMediaSampleEntryCtor(d.SAMPLE_ENTRY_TYPE_AUDIO,(function(e){this.parseHeader(e),e.readUint32Array(2),this.channel_count=e.readUint16(),this.samplesize=e.readUint16(),e.readUint16(),e.readUint16(),this.samplerate=e.readUint32()/65536,this.parseFooter(e)})),d.createSampleEntryCtor(d.SAMPLE_ENTRY_TYPE_VISUAL,"avc1"),d.createSampleEntryCtor(d.SAMPLE_ENTRY_TYPE_VISUAL,"avc2"),d.createSampleEntryCtor(d.SAMPLE_ENTRY_TYPE_VISUAL,"avc3"),d.createSampleEntryCtor(d.SAMPLE_ENTRY_TYPE_VISUAL,"avc4"),d.createSampleEntryCtor(d.SAMPLE_ENTRY_TYPE_VISUAL,"av01"),d.createSampleEntryCtor(d.SAMPLE_ENTRY_TYPE_VISUAL,"hvc1"),d.createSampleEntryCtor(d.SAMPLE_ENTRY_TYPE_VISUAL,"hev1"),d.createSampleEntryCtor(d.SAMPLE_ENTRY_TYPE_AUDIO,"mp4a"),d.createSampleEntryCtor(d.SAMPLE_ENTRY_TYPE_AUDIO,"ac-3"),d.createSampleEntryCtor(d.SAMPLE_ENTRY_TYPE_AUDIO,"ec-3"),d.createEncryptedSampleEntryCtor(d.SAMPLE_ENTRY_TYPE_VISUAL,"encv"),d.createEncryptedSampleEntryCtor(d.SAMPLE_ENTRY_TYPE_AUDIO,"enca"),d.createEncryptedSampleEntryCtor(d.SAMPLE_ENTRY_TYPE_SUBTITLE,"encu"),d.createEncryptedSampleEntryCtor(d.SAMPLE_ENTRY_TYPE_SYSTEM,"encs"),d.createEncryptedSampleEntryCtor(d.SAMPLE_ENTRY_TYPE_TEXT,"enct"),d.createEncryptedSampleEntryCtor(d.SAMPLE_ENTRY_TYPE_METADATA,"encm"),d.createBoxCtor("av1C",(function(e){var t=e.readUint8();if(t>>7&!1)a.error("av1C marker problem");else if(this.version=127&t,1===this.version)if(t=e.readUint8(),this.seq_profile=t>>5&7,this.seq_level_idx_0=31&t,t=e.readUint8(),this.seq_tier_0=t>>7&1,this.high_bitdepth=t>>6&1,this.twelve_bit=t>>5&1,this.monochrome=t>>4&1,this.chroma_subsampling_x=t>>3&1,this.chroma_subsampling_y=t>>2&1,this.chroma_sample_position=3&t,t=e.readUint8(),this.reserved_1=t>>5&7,0===this.reserved_1){if(this.initial_presentation_delay_present=t>>4&1,1===this.initial_presentation_delay_present)this.initial_presentation_delay_minus_one=15&t;else if(this.reserved_2=15&t,0!==this.reserved_2)return void a.error("av1C reserved_2 parsing problem");var i=this.size-this.hdr_size-4;this.configOBUs=e.readUint8Array(i)}else a.error("av1C reserved_1 parsing problem");else a.error("av1C version "+this.version+" not supported")})),d.createBoxCtor("avcC",(function(e){var t,i;for(this.configurationVersion=e.readUint8(),this.AVCProfileIndication=e.readUint8(),this.profile_compatibility=e.readUint8(),this.AVCLevelIndication=e.readUint8(),this.lengthSizeMinusOne=3&e.readUint8(),this.nb_SPS_nalus=31&e.readUint8(),i=this.size-this.hdr_size-6,this.SPS=[],t=0;t0&&(this.ext=e.readUint8Array(i))})),d.createBoxCtor("btrt",(function(e){this.bufferSizeDB=e.readUint32(),this.maxBitrate=e.readUint32(),this.avgBitrate=e.readUint32()})),d.createBoxCtor("clap",(function(e){this.cleanApertureWidthN=e.readUint32(),this.cleanApertureWidthD=e.readUint32(),this.cleanApertureHeightN=e.readUint32(),this.cleanApertureHeightD=e.readUint32(),this.horizOffN=e.readUint32(),this.horizOffD=e.readUint32(),this.vertOffN=e.readUint32(),this.vertOffD=e.readUint32()})),d.createBoxCtor("clli",(function(e){this.max_content_light_level=e.readUint16(),this.max_pic_average_light_level=e.readUint16()})),d.createFullBoxCtor("co64",(function(e){var t,i;if(t=e.readUint32(),this.chunk_offsets=[],0===this.version)for(i=0;i>7}else("rICC"===this.colour_type||"prof"===this.colour_type)&&(this.ICC_profile=e.readUint8Array(this.size-4))})),d.createFullBoxCtor("cprt",(function(e){this.parseLanguage(e),this.notice=e.readCString()})),d.createFullBoxCtor("cslg",(function(e){0===this.version&&(this.compositionToDTSShift=e.readInt32(),this.leastDecodeToDisplayDelta=e.readInt32(),this.greatestDecodeToDisplayDelta=e.readInt32(),this.compositionStartTime=e.readInt32(),this.compositionEndTime=e.readInt32())})),d.createFullBoxCtor("ctts",(function(e){var t,i;if(t=e.readUint32(),this.sample_counts=[],this.sample_offsets=[],0===this.version)for(i=0;i>6,this.bsid=t>>1&31,this.bsmod=(1&t)<<2|i>>6&3,this.acmod=i>>3&7,this.lfeon=i>>2&1,this.bit_rate_code=3&i|n>>5&7})),d.createBoxCtor("dec3",(function(e){var t=e.readUint16();this.data_rate=t>>3,this.num_ind_sub=7&t,this.ind_subs=[];for(var i=0;i>6,n.bsid=r>>1&31,n.bsmod=(1&r)<<4|a>>4&15,n.acmod=a>>1&7,n.lfeon=1&a,n.num_dep_sub=s>>1&15,n.num_dep_sub>0&&(n.chan_loc=(1&s)<<8|e.readUint8())}})),d.createFullBoxCtor("dfLa",(function(e){var t=[],i=["STREAMINFO","PADDING","APPLICATION","SEEKTABLE","VORBIS_COMMENT","CUESHEET","PICTURE","RESERVED"];for(this.parseFullHeader(e);;){var n=e.readUint8(),r=Math.min(127&n,i.length-1);if(r?e.readUint8Array(e.readUint24()):(e.readUint8Array(13),this.samplerate=e.readUint32()>>12,e.readUint8Array(20)),t.push(i[r]),128&n)break}this.numMetadataBlocks=t.length+" ("+t.join(", ")+")"})),d.createBoxCtor("dimm",(function(e){this.bytessent=e.readUint64()})),d.createBoxCtor("dmax",(function(e){this.time=e.readUint32()})),d.createBoxCtor("dmed",(function(e){this.bytessent=e.readUint64()})),d.createFullBoxCtor("dref",(function(e){var t,i;this.entries=[];for(var n=e.readUint32(),r=0;r=4;)this.compatible_brands[i]=e.readString(4),t-=4,i++})),d.createFullBoxCtor("hdlr",(function(e){0===this.version&&(e.readUint32(),this.handler=e.readString(4),e.readUint32Array(3),this.name=e.readString(this.size-this.hdr_size-20),"\0"===this.name[this.name.length-1]&&(this.name=this.name.slice(0,-1)))})),d.createBoxCtor("hvcC",(function(e){var t,i,n,r;this.configurationVersion=e.readUint8(),r=e.readUint8(),this.general_profile_space=r>>6,this.general_tier_flag=(32&r)>>5,this.general_profile_idc=31&r,this.general_profile_compatibility=e.readUint32(),this.general_constraint_indicator=e.readUint8Array(6),this.general_level_idc=e.readUint8(),this.min_spatial_segmentation_idc=4095&e.readUint16(),this.parallelismType=3&e.readUint8(),this.chroma_format_idc=3&e.readUint8(),this.bit_depth_luma_minus8=7&e.readUint8(),this.bit_depth_chroma_minus8=7&e.readUint8(),this.avgFrameRate=e.readUint16(),r=e.readUint8(),this.constantFrameRate=r>>6,this.numTemporalLayers=(13&r)>>3,this.temporalIdNested=(4&r)>>2,this.lengthSizeMinusOne=3&r,this.nalu_arrays=[];var a=e.readUint8();for(t=0;t>7,s.nalu_type=63&r;var o=e.readUint16();for(i=0;i>4&15,this.length_size=15&t,t=e.readUint8(),this.base_offset_size=t>>4&15,1===this.version||2===this.version?this.index_size=15&t:this.index_size=0,this.items=[];var i=0;if(this.version<2)i=e.readUint16();else{if(2!==this.version)throw"version of iloc box not supported";i=e.readUint32()}for(var n=0;n=2&&(2===this.version?this.item_ID=e.readUint16():3===this.version&&(this.item_ID=e.readUint32()),this.item_protection_index=e.readUint16(),this.item_type=e.readString(4),this.item_name=e.readCString(),"mime"===this.item_type?(this.content_type=e.readCString(),this.content_encoding=e.readCString()):"uri "===this.item_type&&(this.item_uri_type=e.readCString()))})),d.createFullBoxCtor("ipma",(function(e){var t,i;for(entry_count=e.readUint32(),this.associations=[],t=0;t>7==1,1&this.flags?s.property_index=(127&a)<<8|e.readUint8():s.property_index=127&a}}})),d.createFullBoxCtor("iref",(function(e){var t,i;for(this.references=[];e.getPosition()>7,n.assignment_type=127&r,n.assignment_type){case 0:n.grouping_type=e.readString(4);break;case 1:n.grouping_type=e.readString(4),n.grouping_type_parameter=e.readUint32();break;case 2:case 3:break;case 4:n.sub_track_id=e.readUint32();break;default:a.warn("BoxParser","Unknown leva assignement type")}}})),d.createBoxCtor("maxr",(function(e){this.period=e.readUint32(),this.bytes=e.readUint32()})),d.createBoxCtor("mdcv",(function(e){this.display_primaries=[],this.display_primaries[0]={},this.display_primaries[0].x=e.readUint16(),this.display_primaries[0].y=e.readUint16(),this.display_primaries[1]={},this.display_primaries[1].x=e.readUint16(),this.display_primaries[1].y=e.readUint16(),this.display_primaries[2]={},this.display_primaries[2].x=e.readUint16(),this.display_primaries[2].y=e.readUint16(),this.white_point={},this.white_point.x=e.readUint16(),this.white_point.y=e.readUint16(),this.max_display_mastering_luminance=e.readUint32(),this.min_display_mastering_luminance=e.readUint32()})),d.createFullBoxCtor("mdhd",(function(e){1==this.version?(this.creation_time=e.readUint64(),this.modification_time=e.readUint64(),this.timescale=e.readUint32(),this.duration=e.readUint64()):(this.creation_time=e.readUint32(),this.modification_time=e.readUint32(),this.timescale=e.readUint32(),this.duration=e.readUint32()),this.parseLanguage(e),e.readUint16()})),d.createFullBoxCtor("mehd",(function(e){1&this.flags&&(a.warn("BoxParser","mehd box incorrectly uses flags set to 1, converting version to 1"),this.version=1),1==this.version?this.fragment_duration=e.readUint64():this.fragment_duration=e.readUint32()})),d.createFullBoxCtor("meta",(function(e){this.boxes=[],d.ContainerBox.prototype.parse.call(this,e)})),d.createFullBoxCtor("mfhd",(function(e){this.sequence_number=e.readUint32()})),d.createFullBoxCtor("mfro",(function(e){this._size=e.readUint32()})),d.createFullBoxCtor("mvhd",(function(e){1==this.version?(this.creation_time=e.readUint64(),this.modification_time=e.readUint64(),this.timescale=e.readUint32(),this.duration=e.readUint64()):(this.creation_time=e.readUint32(),this.modification_time=e.readUint32(),this.timescale=e.readUint32(),this.duration=e.readUint32()),this.rate=e.readUint32(),this.volume=e.readUint16()>>8,e.readUint16(),e.readUint32Array(2),this.matrix=e.readUint32Array(9),e.readUint32Array(6),this.next_track_id=e.readUint32()})),d.createBoxCtor("npck",(function(e){this.packetssent=e.readUint32()})),d.createBoxCtor("nump",(function(e){this.packetssent=e.readUint64()})),d.createFullBoxCtor("padb",(function(e){var t=e.readUint32();this.padbits=[];for(var i=0;i0){var t=e.readUint32();this.kid=[];for(var i=0;i0&&(this.data=e.readUint8Array(n))})),d.createFullBoxCtor("clef",(function(e){this.width=e.readUint32(),this.height=e.readUint32()})),d.createFullBoxCtor("enof",(function(e){this.width=e.readUint32(),this.height=e.readUint32()})),d.createFullBoxCtor("prof",(function(e){this.width=e.readUint32(),this.height=e.readUint32()})),d.createContainerBoxCtor("tapt",null,["clef","prof","enof"]),d.createBoxCtor("rtp ",(function(e){this.descriptionformat=e.readString(4),this.sdptext=e.readString(this.size-this.hdr_size-4)})),d.createFullBoxCtor("saio",(function(e){1&this.flags&&(this.aux_info_type=e.readUint32(),this.aux_info_type_parameter=e.readUint32());var t=e.readUint32();this.offset=[];for(var i=0;i>7,this.avgRateFlag=t>>6&1,this.durationFlag&&(this.duration=e.readUint32()),this.avgRateFlag&&(this.accurateStatisticsFlag=e.readUint8(),this.avgBitRate=e.readUint16(),this.avgFrameRate=e.readUint16()),this.dependency=[];for(var i=e.readUint8(),n=0;n>7,this.num_leading_samples=127&t})),d.createSampleGroupCtor("rash",(function(e){if(this.operation_point_count=e.readUint16(),this.description_length!==2+(1===this.operation_point_count?2:6*this.operation_point_count)+9)a.warn("BoxParser","Mismatch in "+this.grouping_type+" sample group length"),this.data=e.readUint8Array(this.description_length-2);else{if(1===this.operation_point_count)this.target_rate_share=e.readUint16();else{this.target_rate_share=[],this.available_bitrate=[];for(var t=0;t>4,this.skip_byte_block=15&t,this.isProtected=e.readUint8(),this.Per_Sample_IV_Size=e.readUint8(),this.KID=d.parseHex16(e),this.constant_IV_size=0,this.constant_IV=0,1===this.isProtected&&0===this.Per_Sample_IV_Size&&(this.constant_IV_size=e.readUint8(),this.constant_IV=e.readUint8Array(this.constant_IV_size))})),d.createSampleGroupCtor("stsa",(function(e){a.warn("BoxParser","Sample Group type: "+this.grouping_type+" not fully parsed")})),d.createSampleGroupCtor("sync",(function(e){var t=e.readUint8();this.NAL_unit_type=63&t})),d.createSampleGroupCtor("tele",(function(e){var t=e.readUint8();this.level_independently_decodable=t>>7})),d.createSampleGroupCtor("tsas",(function(e){a.warn("BoxParser","Sample Group type: "+this.grouping_type+" not fully parsed")})),d.createSampleGroupCtor("tscl",(function(e){a.warn("BoxParser","Sample Group type: "+this.grouping_type+" not fully parsed")})),d.createSampleGroupCtor("vipr",(function(e){a.warn("BoxParser","Sample Group type: "+this.grouping_type+" not fully parsed")})),d.createFullBoxCtor("sbgp",(function(e){this.grouping_type=e.readString(4),1===this.version?this.grouping_type_parameter=e.readUint32():this.grouping_type_parameter=0,this.entries=[];for(var t=e.readUint32(),i=0;i>6,this.sample_depends_on[n]=t>>4&3,this.sample_is_depended_on[n]=t>>2&3,this.sample_has_redundancy[n]=3&t})),d.createFullBoxCtor("senc"),d.createFullBoxCtor("sgpd",(function(e){this.grouping_type=e.readString(4),a.debug("BoxParser","Found Sample Groups of type "+this.grouping_type),1===this.version?this.default_length=e.readUint32():this.default_length=0,this.version>=2&&(this.default_group_description_index=e.readUint32()),this.entries=[];for(var t=e.readUint32(),i=0;i>31&1,n.referenced_size=2147483647&r,n.subsegment_duration=e.readUint32(),r=e.readUint32(),n.starts_with_SAP=r>>31&1,n.SAP_type=r>>28&7,n.SAP_delta_time=268435455&r}})),d.SingleItemTypeReferenceBox=function(e,t,i,n){d.Box.call(this,e,t),this.hdr_size=i,this.start=n},d.SingleItemTypeReferenceBox.prototype=new d.Box,d.SingleItemTypeReferenceBox.prototype.parse=function(e){this.from_item_ID=e.readUint16();var t=e.readUint16();this.references=[];for(var i=0;i>4&15,this.sample_sizes[t+1]=15&n}else if(8===this.field_size)for(t=0;t0)for(i=0;i>4&15,this.default_skip_byte_block=15&t}this.default_isProtected=e.readUint8(),this.default_Per_Sample_IV_Size=e.readUint8(),this.default_KID=d.parseHex16(e),1===this.default_isProtected&&0===this.default_Per_Sample_IV_Size&&(this.default_constant_IV_size=e.readUint8(),this.default_constant_IV=e.readUint8Array(this.default_constant_IV_size))})),d.createFullBoxCtor("tfdt",(function(e){1==this.version?this.baseMediaDecodeTime=e.readUint64():this.baseMediaDecodeTime=e.readUint32()})),d.createFullBoxCtor("tfhd",(function(e){var t=0;this.track_id=e.readUint32(),this.size-this.hdr_size>t&&this.flags&d.TFHD_FLAG_BASE_DATA_OFFSET?(this.base_data_offset=e.readUint64(),t+=8):this.base_data_offset=0,this.size-this.hdr_size>t&&this.flags&d.TFHD_FLAG_SAMPLE_DESC?(this.default_sample_description_index=e.readUint32(),t+=4):this.default_sample_description_index=0,this.size-this.hdr_size>t&&this.flags&d.TFHD_FLAG_SAMPLE_DUR?(this.default_sample_duration=e.readUint32(),t+=4):this.default_sample_duration=0,this.size-this.hdr_size>t&&this.flags&d.TFHD_FLAG_SAMPLE_SIZE?(this.default_sample_size=e.readUint32(),t+=4):this.default_sample_size=0,this.size-this.hdr_size>t&&this.flags&d.TFHD_FLAG_SAMPLE_FLAGS?(this.default_sample_flags=e.readUint32(),t+=4):this.default_sample_flags=0})),d.createFullBoxCtor("tfra",(function(e){this.track_ID=e.readUint32(),e.readUint24();var t=e.readUint8();this.length_size_of_traf_num=t>>4&3,this.length_size_of_trun_num=t>>2&3,this.length_size_of_sample_num=3&t,this.entries=[];for(var i=e.readUint32(),n=0;n>8,e.readUint16(),this.matrix=e.readInt32Array(9),this.width=e.readUint32(),this.height=e.readUint32()})),d.createBoxCtor("tmax",(function(e){this.time=e.readUint32()})),d.createBoxCtor("tmin",(function(e){this.time=e.readUint32()})),d.createBoxCtor("totl",(function(e){this.bytessent=e.readUint32()})),d.createBoxCtor("tpay",(function(e){this.bytessent=e.readUint32()})),d.createBoxCtor("tpyl",(function(e){this.bytessent=e.readUint64()})),d.TrackGroupTypeBox.prototype.parse=function(e){this.parseFullHeader(e),this.track_group_id=e.readUint32()},d.createTrackGroupCtor("msrc"),d.TrackReferenceTypeBox=function(e,t,i,n){d.Box.call(this,e,t),this.hdr_size=i,this.start=n},d.TrackReferenceTypeBox.prototype=new d.Box,d.TrackReferenceTypeBox.prototype.parse=function(e){this.track_ids=e.readUint32Array((this.size-this.hdr_size)/4)},d.trefBox.prototype.parse=function(e){for(var t,i;e.getPosition()t&&this.flags&d.TRUN_FLAGS_DATA_OFFSET?(this.data_offset=e.readInt32(),t+=4):this.data_offset=0,this.size-this.hdr_size>t&&this.flags&d.TRUN_FLAGS_FIRST_FLAG?(this.first_sample_flags=e.readUint32(),t+=4):this.first_sample_flags=0,this.sample_duration=[],this.sample_size=[],this.sample_flags=[],this.sample_composition_time_offset=[],this.size-this.hdr_size>t)for(var i=0;i0&&(this.location=e.readCString())})),d.createUUIDBox("a5d40b30e81411ddba2f0800200c9a66",!0,!1,(function(e){this.LiveServerManifest=e.readString(this.size-this.hdr_size).replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")})),d.createUUIDBox("d08a4f1810f34a82b6c832d8aba183d3",!0,!1,(function(e){this.system_id=d.parseHex16(e);var t=e.readUint32();t>0&&(this.data=e.readUint8Array(t))})),d.createUUIDBox("a2394f525a9b4f14a2446c427c648df4",!0,!1),d.createUUIDBox("8974dbce7be74c5184f97148f9882554",!0,!1,(function(e){this.default_AlgorithmID=e.readUint24(),this.default_IV_size=e.readUint8(),this.default_KID=d.parseHex16(e)})),d.createUUIDBox("d4807ef2ca3946958e5426cb9e46a79f",!0,!1,(function(e){this.fragment_count=e.readUint8(),this.entries=[];for(var t=0;t>4,this.chromaSubsampling=t>>1&7,this.videoFullRangeFlag=1&t,this.colourPrimaries=e.readUint8(),this.transferCharacteristics=e.readUint8(),this.matrixCoefficients=e.readUint8(),this.codecIntializationDataSize=e.readUint16(),this.codecIntializationData=e.readUint8Array(this.codecIntializationDataSize)):(this.profile=e.readUint8(),this.level=e.readUint8(),t=e.readUint8(),this.bitDepth=t>>4&15,this.colorSpace=15&t,t=e.readUint8(),this.chromaSubsampling=t>>4&15,this.transferFunction=t>>1&7,this.videoFullRangeFlag=1&t,this.codecIntializationDataSize=e.readUint16(),this.codecIntializationData=e.readUint8Array(this.codecIntializationDataSize))})),d.createBoxCtor("vttC",(function(e){this.text=e.readString(this.size-this.hdr_size)})),d.SampleEntry.prototype.isVideo=function(){return!1},d.SampleEntry.prototype.isAudio=function(){return!1},d.SampleEntry.prototype.isSubtitle=function(){return!1},d.SampleEntry.prototype.isMetadata=function(){return!1},d.SampleEntry.prototype.isHint=function(){return!1},d.SampleEntry.prototype.getCodec=function(){return this.type.replace(".","")},d.SampleEntry.prototype.getWidth=function(){return""},d.SampleEntry.prototype.getHeight=function(){return""},d.SampleEntry.prototype.getChannelCount=function(){return""},d.SampleEntry.prototype.getSampleRate=function(){return""},d.SampleEntry.prototype.getSampleSize=function(){return""},d.VisualSampleEntry.prototype.isVideo=function(){return!0},d.VisualSampleEntry.prototype.getWidth=function(){return this.width},d.VisualSampleEntry.prototype.getHeight=function(){return this.height},d.AudioSampleEntry.prototype.isAudio=function(){return!0},d.AudioSampleEntry.prototype.getChannelCount=function(){return this.channel_count},d.AudioSampleEntry.prototype.getSampleRate=function(){return this.samplerate},d.AudioSampleEntry.prototype.getSampleSize=function(){return this.samplesize},d.SubtitleSampleEntry.prototype.isSubtitle=function(){return!0},d.MetadataSampleEntry.prototype.isMetadata=function(){return!0},d.decimalToHex=function(e,t){var i=Number(e).toString(16);for(t=null==t?t=2:t;i.length>=1;t+=d.decimalToHex(n,0),t+=".",0===this.hvcC.general_tier_flag?t+="L":t+="H",t+=this.hvcC.general_level_idc;var r=!1,a="";for(e=5;e>=0;e--)(this.hvcC.general_constraint_indicator[e]||r)&&(a="."+d.decimalToHex(this.hvcC.general_constraint_indicator[e],0)+a,r=!0);t+=a}return t},d.mp4aSampleEntry.prototype.getCodec=function(){var e=d.SampleEntry.prototype.getCodec.call(this);if(this.esds&&this.esds.esd){var t=this.esds.esd.getOTI(),i=this.esds.esd.getAudioConfig();return e+"."+d.decimalToHex(t)+(i?"."+i:"")}return e},d.stxtSampleEntry.prototype.getCodec=function(){var e=d.SampleEntry.prototype.getCodec.call(this);return this.mime_format?e+"."+this.mime_format:e},d.av01SampleEntry.prototype.getCodec=function(){var e,t=d.SampleEntry.prototype.getCodec.call(this);return 2===this.av1C.seq_profile&&1===this.av1C.high_bitdepth?e=1===this.av1C.twelve_bit?"12":"10":this.av1C.seq_profile<=2&&(e=1===this.av1C.high_bitdepth?"10":"08"),t+"."+this.av1C.seq_profile+"."+this.av1C.seq_level_idx_0+(this.av1C.seq_tier_0?"H":"M")+"."+e},d.Box.prototype.writeHeader=function(e,t){this.size+=8,this.size>u&&(this.size+=8),"uuid"===this.type&&(this.size+=16),a.debug("BoxWriter","Writing box "+this.type+" of size: "+this.size+" at position "+e.getPosition()+(t||"")),this.size>u?e.writeUint32(1):(this.sizePosition=e.getPosition(),e.writeUint32(this.size)),e.writeString(this.type,null,4),"uuid"===this.type&&e.writeUint8Array(this.uuid),this.size>u&&e.writeUint64(this.size)},d.FullBox.prototype.writeHeader=function(e){this.size+=4,d.Box.prototype.writeHeader.call(this,e," v="+this.version+" f="+this.flags),e.writeUint8(this.version),e.writeUint24(this.flags)},d.Box.prototype.write=function(e){"mdat"===this.type?this.data&&(this.size=this.data.length,this.writeHeader(e),e.writeUint8Array(this.data)):(this.size=this.data?this.data.length:0,this.writeHeader(e),this.data&&e.writeUint8Array(this.data))},d.ContainerBox.prototype.write=function(e){this.size=0,this.writeHeader(e);for(var t=0;t=2&&e.writeUint32(this.default_sample_description_index),e.writeUint32(this.entries.length),t=0;t0)for(t=0;t+1-1||e[i]instanceof d.Box||t[i]instanceof d.Box||void 0===e[i]||void 0===t[i]||"function"==typeof e[i]||"function"==typeof t[i]||e.subBoxNames&&e.subBoxNames.indexOf(i.slice(0,4))>-1||t.subBoxNames&&t.subBoxNames.indexOf(i.slice(0,4))>-1||"data"===i||"start"===i||"size"===i||"creation_time"===i||"modification_time"===i||d.DIFF_PRIMITIVE_ARRAY_PROP_NAMES.indexOf(i)>-1||e[i]===t[i]))return!1;return!0},d.boxEqual=function(e,t){if(!d.boxEqualFields(e,t))return!1;for(var i=0;i=t?e:new Array(t-e.length+1).join(i)+e}function r(e){var t=Math.floor(e/3600),i=Math.floor((e-3600*t)/60),r=Math.floor(e-3600*t-60*i),a=Math.floor(1e3*(e-3600*t-60*i-r));return n(t,2)+":"+n(i,2)+":"+n(r,2)+"."+n(a,3)}for(var a=this.parseSample(i),s="",o=0;o1)for(t=1;t-1&&this.fragmentedTracks.splice(t,1)},m.prototype.setExtractionOptions=function(e,t,i){var n=this.getTrackById(e);if(n){var r={};this.extractedTracks.push(r),r.id=e,r.user=t,r.trak=n,n.nextSample=0,r.nb_samples=1e3,r.samples=[],i&&i.nbSamples&&(r.nb_samples=i.nbSamples)}},m.prototype.unsetExtractionOptions=function(e){for(var t=-1,i=0;i-1&&this.extractedTracks.splice(t,1)},m.prototype.parse=function(){var e,t;if(!this.restoreParsePosition||this.restoreParsePosition())for(;;){if(this.hasIncompleteMdat&&this.hasIncompleteMdat()){if(this.processIncompleteMdat())continue;return}if(this.saveParsePosition&&this.saveParsePosition(),(e=d.parseOneBox(this.stream,!1)).code===d.ERR_NOT_ENOUGH_DATA){if(this.processIncompleteBox){if(this.processIncompleteBox(e))continue;return}return}var i;switch(i="uuid"!==(t=e.box).type?t.type:t.uuid,this.boxes.push(t),i){case"mdat":this.mdats.push(t);break;case"moof":this.moofs.push(t);break;case"moov":this.moovStartFound=!0,0===this.mdats.length&&(this.isProgressive=!0);default:void 0!==this[i]&&a.warn("ISOFile","Duplicate Box of type: "+i+", overriding previous occurrence"),this[i]=t}this.updateUsedBytes&&this.updateUsedBytes(t,e)}},m.prototype.checkBuffer=function(e){if(null==e)throw"Buffer must be defined and non empty";if(void 0===e.fileStart)throw"Buffer must have a fileStart property";return 0===e.byteLength?(a.warn("ISOFile","Ignoring empty buffer (fileStart: "+e.fileStart+")"),this.stream.logBufferLevel(),!1):(a.info("ISOFile","Processing buffer (fileStart: "+e.fileStart+")"),e.usedBytes=0,this.stream.insertBuffer(e),this.stream.logBufferLevel(),!!this.stream.initialized()||(a.warn("ISOFile","Not ready to start parsing"),!1))},m.prototype.appendBuffer=function(e,t){var i;if(this.checkBuffer(e))return this.parse(),this.moovStartFound&&!this.moovStartSent&&(this.moovStartSent=!0,this.onMoovStart&&this.onMoovStart()),this.moov?(this.sampleListBuilt||(this.buildSampleLists(),this.sampleListBuilt=!0),this.updateSampleLists(),this.onReady&&!this.readySent&&(this.readySent=!0,this.onReady(this.getInfo())),this.processSamples(t),this.nextSeekPosition?(i=this.nextSeekPosition,this.nextSeekPosition=void 0):i=this.nextParsePosition,this.stream.getEndFilePositionAfter&&(i=this.stream.getEndFilePositionAfter(i))):i=this.nextParsePosition?this.nextParsePosition:0,this.sidx&&this.onSidx&&!this.sidxSent&&(this.onSidx(this.sidx),this.sidxSent=!0),this.meta&&(this.flattenItemInfo&&!this.itemListBuilt&&(this.flattenItemInfo(),this.itemListBuilt=!0),this.processItems&&this.processItems(this.onItem)),this.stream.cleanBuffers&&(a.info("ISOFile","Done processing buffer (fileStart: "+e.fileStart+") - next buffer to fetch should have a fileStart position of "+i),this.stream.logBufferLevel(),this.stream.cleanBuffers(),this.stream.logBufferLevel(!0),a.info("ISOFile","Sample data size in memory: "+this.getAllocatedSampleDataSize())),i},m.prototype.getInfo=function(){var e,t,i,n,r,a={},s=new Date("1904-01-01T00:00:00Z").getTime();if(this.moov)for(a.hasMoov=!0,a.duration=this.moov.mvhd.duration,a.timescale=this.moov.mvhd.timescale,a.isFragmented=null!=this.moov.mvex,a.isFragmented&&this.moov.mvex.mehd&&(a.fragment_duration=this.moov.mvex.mehd.fragment_duration),a.isProgressive=this.isProgressive,a.hasIOD=null!=this.moov.iods,a.brands=[],a.brands.push(this.ftyp.major_brand),a.brands=a.brands.concat(this.ftyp.compatible_brands),a.created=new Date(s+1e3*this.moov.mvhd.creation_time),a.modified=new Date(s+1e3*this.moov.mvhd.modification_time),a.tracks=[],a.audioTracks=[],a.videoTracks=[],a.subtitleTracks=[],a.metadataTracks=[],a.hintTracks=[],a.otherTracks=[],e=0;e0?a.mime+='video/mp4; codecs="':a.audioTracks&&a.audioTracks.length>0?a.mime+='audio/mp4; codecs="':a.mime+='application/mp4; codecs="',e=0;e=i.samples.length)&&(a.info("ISOFile","Sending fragmented data on track #"+n.id+" for samples ["+Math.max(0,i.nextSample-n.nb_samples)+","+(i.nextSample-1)+"]"),a.info("ISOFile","Sample data size in memory: "+this.getAllocatedSampleDataSize()),this.onSegment&&this.onSegment(n.id,n.user,n.segmentStream.buffer,i.nextSample,e||i.nextSample>=i.samples.length),n.segmentStream=null,n!==this.fragmentedTracks[t]))break}}if(null!==this.onSamples)for(t=0;t=i.samples.length)&&(a.debug("ISOFile","Sending samples on track #"+s.id+" for sample "+i.nextSample),this.onSamples&&this.onSamples(s.id,s.user,s.samples),s.samples=[],s!==this.extractedTracks[t]))break}}}},m.prototype.getBox=function(e){var t=this.getBoxes(e,!0);return t.length?t[0]:null},m.prototype.getBoxes=function(e,t){var i=[];return m._sweep.call(this,e,i,t),i},m._sweep=function(e,t,i){for(var n in this.type&&this.type==e&&t.push(this),this.boxes){if(t.length&&i)return;m._sweep.call(this.boxes[n],e,t,i)}},m.prototype.getTrackSamplesInfo=function(e){var t=this.getTrackById(e);return t?t.samples:void 0},m.prototype.getTrackSample=function(e,t){var i=this.getTrackById(e);return this.getSample(i,t)},m.prototype.releaseUsedSamples=function(e,t){var i=0,n=this.getTrackById(e);n.lastValidSample||(n.lastValidSample=0);for(var r=n.lastValidSample;re*r.timescale){l=n-1;break}t&&r.is_sync&&(u=n)}for(t&&(l=u),e=i.samples[l].cts,i.nextSample=l;i.samples[l].alreadyRead===i.samples[l].size&&i.samples[l+1];)l++;return s=i.samples[l].offset+i.samples[l].alreadyRead,a.info("ISOFile","Seeking to "+(t?"RAP":"")+" sample #"+i.nextSample+" on track "+i.tkhd.track_id+", time "+a.getDurationString(e,o)+" and offset: "+s),{offset:s,time:e/o}},m.prototype.seek=function(e,t){var i,n,r,s=this.moov,o={offset:1/0,time:1/0};if(this.moov){for(r=0;r-1){s=o;break}switch(s){case"Visual":r.add("vmhd").set("graphicsmode",0).set("opcolor",[0,0,0]),a.set("width",t.width).set("height",t.height).set("horizresolution",72<<16).set("vertresolution",72<<16).set("frame_count",1).set("compressorname",t.type+" Compressor").set("depth",24);break;case"Audio":r.add("smhd").set("balance",t.balance||0),a.set("channel_count",t.channel_count||2).set("samplesize",t.samplesize||16).set("samplerate",t.samplerate||65536);break;case"Hint":r.add("hmhd");break;case"Subtitle":switch(r.add("sthd"),t.type){case"stpp":a.set("namespace",t.namespace||"nonamespace").set("schema_location",t.schema_location||"").set("auxiliary_mime_types",t.auxiliary_mime_types||"")}break;case"Metadata":case"System":default:r.add("nmhd")}t.description&&a.addBox(t.description),t.description_boxes&&t.description_boxes.forEach((function(e){a.addBox(e)})),r.add("dinf").add("dref").addEntry((new d["url Box"]).set("flags",1));var h=r.add("stbl");return h.add("stsd").addEntry(a),h.add("stts").set("sample_counts",[]).set("sample_deltas",[]),h.add("stsc").set("first_chunk",[]).set("samples_per_chunk",[]).set("sample_description_index",[]),h.add("stco").set("chunk_offsets",[]),h.add("stsz").set("sample_sizes",[]),this.moov.mvex.add("trex").set("track_id",t.id).set("default_sample_description_index",t.default_sample_description_index||1).set("default_sample_duration",t.default_sample_duration||0).set("default_sample_size",t.default_sample_size||0).set("default_sample_flags",t.default_sample_flags||0),this.buildTrakSampleLists(i),t.id}},d.Box.prototype.computeSize=function(e){var t=e||new o;t.endianness=o.BIG_ENDIAN,this.write(t)},m.prototype.addSample=function(e,t,i){var n=i||{},r={},a=this.getTrackById(e);if(null!==a){r.number=a.samples.length,r.track_id=a.tkhd.track_id,r.timescale=a.mdia.mdhd.timescale,r.description_index=n.sample_description_index?n.sample_description_index-1:0,r.description=a.mdia.minf.stbl.stsd.entries[r.description_index],r.data=t,r.size=t.length,r.alreadyRead=r.size,r.duration=n.duration||1,r.cts=n.cts||0,r.dts=n.dts||0,r.is_sync=n.is_sync||!1,r.is_leading=n.is_leading||0,r.depends_on=n.depends_on||0,r.is_depended_on=n.is_depended_on||0,r.has_redundancy=n.has_redundancy||0,r.degradation_priority=n.degradation_priority||0,r.offset=0,r.subsamples=n.subsamples,a.samples.push(r),a.samples_size+=r.size,a.samples_duration+=r.duration,this.processSamples();var s=m.createSingleSampleMoof(r);return this.addBox(s),s.computeSize(),s.trafs[0].truns[0].data_offset=s.size+8,this.add("mdat").data=t,r}},m.createSingleSampleMoof=function(e){var t=new d.moofBox;t.add("mfhd").set("sequence_number",this.nextMoofNumber),this.nextMoofNumber++;var i=t.add("traf");return i.add("tfhd").set("track_id",e.track_id).set("flags",d.TFHD_FLAG_DEFAULT_BASE_IS_MOOF),i.add("tfdt").set("baseMediaDecodeTime",e.dts),i.add("trun").set("flags",d.TRUN_FLAGS_DATA_OFFSET|d.TRUN_FLAGS_DURATION|d.TRUN_FLAGS_SIZE|d.TRUN_FLAGS_FLAGS|d.TRUN_FLAGS_CTS_OFFSET).set("data_offset",0).set("first_sample_flags",0).set("sample_count",1).set("sample_duration",[e.duration]).set("sample_size",[e.size]).set("sample_flags",[0]).set("sample_composition_time_offset",[e.cts-e.dts]),t},m.prototype.lastMoofIndex=0,m.prototype.samplesDataSize=0,m.prototype.resetTables=function(){var e,t,i,n,r,a;for(this.initial_duration=this.moov.mvhd.duration,this.moov.mvhd.duration=0,e=0;e=2&&(u=r[s].grouping_type+"/0",(o=new l(r[s].grouping_type,0)).is_fragment=!0,t.sample_groups_info[u]||(t.sample_groups_info[u]=o))}else for(s=0;s=2&&(u=n[s].grouping_type+"/0",o=new l(n[s].grouping_type,0),e.sample_groups_info[u]||(e.sample_groups_info[u]=o))},m.setSampleGroupProperties=function(e,t,i,n){var r,a;for(r in t.sample_groups=[],n){var s;if(t.sample_groups[r]={},t.sample_groups[r].grouping_type=n[r].grouping_type,t.sample_groups[r].grouping_type_parameter=n[r].grouping_type_parameter,i>=n[r].last_sample_in_run&&(n[r].last_sample_in_run<0&&(n[r].last_sample_in_run=0),n[r].entry_index++,n[r].entry_index<=n[r].sbgp.entries.length-1&&(n[r].last_sample_in_run+=n[r].sbgp.entries[n[r].entry_index].sample_count)),n[r].entry_index<=n[r].sbgp.entries.length-1?t.sample_groups[r].group_description_index=n[r].sbgp.entries[n[r].entry_index].group_description_index:t.sample_groups[r].group_description_index=-1,0!==t.sample_groups[r].group_description_index)s=n[r].fragment_description?n[r].fragment_description:n[r].description,t.sample_groups[r].group_description_index>0?(a=t.sample_groups[r].group_description_index>65535?(t.sample_groups[r].group_description_index>>16)-1:t.sample_groups[r].group_description_index-1,s&&a>=0&&(t.sample_groups[r].description=s.entries[a])):s&&s.version>=2&&s.default_group_description_index>0&&(t.sample_groups[r].description=s.entries[s.default_group_description_index-1])}},m.process_sdtp=function(e,t,i){t&&(e?(t.is_leading=e.is_leading[i],t.depends_on=e.sample_depends_on[i],t.is_depended_on=e.sample_is_depended_on[i],t.has_redundancy=e.sample_has_redundancy[i]):(t.is_leading=0,t.depends_on=0,t.is_depended_on=0,t.has_redundancy=0))},m.prototype.buildSampleLists=function(){var e,t;for(e=0;ey&&(b++,y<0&&(y=0),y+=a.sample_counts[b]),t>0?(e.samples[t-1].duration=a.sample_deltas[b],e.samples_duration+=e.samples[t-1].duration,C.dts=e.samples[t-1].dts+e.samples[t-1].duration):C.dts=0,s?(t>=S&&(T++,S<0&&(S=0),S+=s.sample_counts[T]),C.cts=e.samples[t].dts+s.sample_offsets[T]):C.cts=C.dts,o?(t==o.sample_numbers[E]-1?(C.is_sync=!0,E++):(C.is_sync=!1,C.degradation_priority=0),l&&l.entries[w].sample_delta+A==t+1&&(C.subsamples=l.entries[w].subsamples,A+=l.entries[w].sample_delta,w++)):C.is_sync=!0,m.process_sdtp(e.mdia.minf.stbl.sdtp,C,C.number),C.degradation_priority=c?c.priority[t]:0,l&&l.entries[w].sample_delta+A==t&&(C.subsamples=l.entries[w].subsamples,A+=l.entries[w].sample_delta),(h.length>0||d.length>0)&&m.setSampleGroupProperties(e,C,t,e.sample_groups_info)}t>0&&(e.samples[t-1].duration=Math.max(e.mdia.mdhd.duration-e.samples[t-1].dts,0),e.samples_duration+=e.samples[t-1].duration)}},m.prototype.updateSampleLists=function(){var e,t,i,n,r,a,s,o,u,l,h,c,f,p,_;if(void 0!==this.moov)for(;this.lastMoofIndex0&&m.initSampleGroups(c,h,h.sbgps,c.mdia.minf.stbl.sgpds,h.sgpds),t=0;t0?p.dts=c.samples[c.samples.length-2].dts+c.samples[c.samples.length-2].duration:(h.tfdt?p.dts=h.tfdt.baseMediaDecodeTime:p.dts=0,c.first_traf_merged=!0),p.cts=p.dts,g.flags&d.TRUN_FLAGS_CTS_OFFSET&&(p.cts=p.dts+g.sample_composition_time_offset[i]),_=s,g.flags&d.TRUN_FLAGS_FLAGS?_=g.sample_flags[i]:0===i&&g.flags&d.TRUN_FLAGS_FIRST_FLAG&&(_=g.first_sample_flags),p.is_sync=!(_>>16&1),p.is_leading=_>>26&3,p.depends_on=_>>24&3,p.is_depended_on=_>>22&3,p.has_redundancy=_>>20&3,p.degradation_priority=65535&_;var v=!!(h.tfhd.flags&d.TFHD_FLAG_BASE_DATA_OFFSET),y=!!(h.tfhd.flags&d.TFHD_FLAG_DEFAULT_BASE_IS_MOOF),b=!!(g.flags&d.TRUN_FLAGS_DATA_OFFSET),S=0;S=v?h.tfhd.base_data_offset:y||0===t?l.start:o,p.offset=0===t&&0===i?b?S+g.data_offset:S:o,o=p.offset+p.size,(h.sbgps.length>0||h.sgpds.length>0||c.mdia.minf.stbl.sbgps.length>0||c.mdia.minf.stbl.sgpds.length>0)&&m.setSampleGroupProperties(c,p,p.number_in_traf,h.sample_groups_info)}}if(h.subs){c.has_fragment_subsamples=!0;var T=h.first_sample_index;for(t=0;t-1))return null;var s=(i=this.stream.buffers[r]).byteLength-(n.offset+n.alreadyRead-i.fileStart);if(n.size-n.alreadyRead<=s)return a.debug("ISOFile","Getting sample #"+t+" data (alreadyRead: "+n.alreadyRead+" offset: "+(n.offset+n.alreadyRead-i.fileStart)+" read size: "+(n.size-n.alreadyRead)+" full size: "+n.size+")"),o.memcpy(n.data.buffer,n.alreadyRead,i,n.offset+n.alreadyRead-i.fileStart,n.size-n.alreadyRead),i.usedBytes+=n.size-n.alreadyRead,this.stream.logBufferLevel(),n.alreadyRead=n.size,n;if(0===s)return null;a.debug("ISOFile","Getting sample #"+t+" partial data (alreadyRead: "+n.alreadyRead+" offset: "+(n.offset+n.alreadyRead-i.fileStart)+" read size: "+s+" full size: "+n.size+")"),o.memcpy(n.data.buffer,n.alreadyRead,i,n.offset+n.alreadyRead-i.fileStart,s),n.alreadyRead+=s,i.usedBytes+=s,this.stream.logBufferLevel()}},m.prototype.releaseSample=function(e,t){var i=e.samples[t];return i.data?(this.samplesDataSize-=i.size,i.data=null,i.alreadyRead=0,i.size):0},m.prototype.getAllocatedSampleDataSize=function(){return this.samplesDataSize},m.prototype.getCodecs=function(){var e,t="";for(e=0;e0&&(t+=","),t+=this.moov.traks[e].mdia.minf.stbl.stsd.entries[0].getCodec()}return t},m.prototype.getTrexById=function(e){var t;if(!this.moov||!this.moov.mvex)return null;for(t=0;t0&&(i.protection=r.ipro.protections[r.iinf.item_infos[e].protection_index-1]),r.iinf.item_infos[e].item_type?i.type=r.iinf.item_infos[e].item_type:i.type="mime",i.content_type=r.iinf.item_infos[e].content_type,i.content_encoding=r.iinf.item_infos[e].content_encoding;if(r.iloc)for(e=0;e0){var c=r.iprp.ipco.boxes[d.property_index-1];i.properties[c.type]=c,i.properties.boxes.push(c)}}}}}},m.prototype.getItem=function(e){var t,i;if(!this.meta)return null;if(!(i=this.items[e]).data&&i.size)i.data=new Uint8Array(i.size),i.alreadyRead=0,this.itemsDataSize+=i.size,a.debug("ISOFile","Allocating item #"+e+" of size "+i.size+" (total: "+this.itemsDataSize+")");else if(i.alreadyRead===i.size)return i;for(var n=0;n-1))return null;var u=(t=this.stream.buffers[s]).byteLength-(r.offset+r.alreadyRead-t.fileStart);if(!(r.length-r.alreadyRead<=u))return a.debug("ISOFile","Getting item #"+e+" extent #"+n+" partial data (alreadyRead: "+r.alreadyRead+" offset: "+(r.offset+r.alreadyRead-t.fileStart)+" read size: "+u+" full extent size: "+r.length+" full item size: "+i.size+")"),o.memcpy(i.data.buffer,i.alreadyRead,t,r.offset+r.alreadyRead-t.fileStart,u),r.alreadyRead+=u,i.alreadyRead+=u,t.usedBytes+=u,this.stream.logBufferLevel(),null;a.debug("ISOFile","Getting item #"+e+" extent #"+n+" data (alreadyRead: "+r.alreadyRead+" offset: "+(r.offset+r.alreadyRead-t.fileStart)+" read size: "+(r.length-r.alreadyRead)+" full extent size: "+r.length+" full item size: "+i.size+")"),o.memcpy(i.data.buffer,i.alreadyRead,t,r.offset+r.alreadyRead-t.fileStart,r.length-r.alreadyRead),t.usedBytes+=r.length-r.alreadyRead,this.stream.logBufferLevel(),i.alreadyRead+=r.length-r.alreadyRead,r.alreadyRead=r.length}}return i.alreadyRead===i.size?i:null},m.prototype.releaseItem=function(e){var t=this.items[e];if(t.data){this.itemsDataSize-=t.size,t.data=null,t.alreadyRead=0;for(var i=0;i0?this.moov.traks[e].samples[0].duration:0),t.push(n)}return t},d.Box.prototype.printHeader=function(e){this.size+=8,this.size>u&&(this.size+=8),"uuid"===this.type&&(this.size+=16),e.log(e.indent+"size:"+this.size),e.log(e.indent+"type:"+this.type)},d.FullBox.prototype.printHeader=function(e){this.size+=4,d.Box.prototype.printHeader.call(this,e),e.log(e.indent+"version:"+this.version),e.log(e.indent+"flags:"+this.flags)},d.Box.prototype.print=function(e){this.printHeader(e)},d.ContainerBox.prototype.print=function(e){this.printHeader(e);for(var t=0;t>8)),e.log(e.indent+"matrix: "+this.matrix.join(", ")),e.log(e.indent+"next_track_id: "+this.next_track_id)},d.tkhdBox.prototype.print=function(e){d.FullBox.prototype.printHeader.call(this,e),e.log(e.indent+"creation_time: "+this.creation_time),e.log(e.indent+"modification_time: "+this.modification_time),e.log(e.indent+"track_id: "+this.track_id),e.log(e.indent+"duration: "+this.duration),e.log(e.indent+"volume: "+(this.volume>>8)),e.log(e.indent+"matrix: "+this.matrix.join(", ")),e.log(e.indent+"layer: "+this.layer),e.log(e.indent+"alternate_group: "+this.alternate_group),e.log(e.indent+"width: "+this.width),e.log(e.indent+"height: "+this.height)};var _={createFile:function(e,t){var i=void 0===e||e,n=new m(t);return n.discardMdatData=!i,n}};void 0!==i&&(i.createFile=_.createFile)},{}],40:[function(e,t,i){ /*! @name mpd-parser @version 0.19.0 @license Apache-2.0 */ "use strict";Object.defineProperty(i,"__esModule",{value:!0});var n=e("@videojs/vhs-utils/cjs/resolve-url"),r=e("global/window"),a=e("@videojs/vhs-utils/cjs/decode-b64-to-uint8-array"),s=e("@xmldom/xmldom");function o(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var u=o(n),l=o(r),h=o(a),d=function(e){return!!e&&"object"==typeof e},c=function e(){for(var t=arguments.length,i=new Array(t),n=0;n=0&&(f.minimumUpdatePeriod=1e3*u),t&&(f.locations=t),"dynamic"===s&&(f.suggestedPresentationDelay=o);var p=0===f.playlists.length;return h.length&&(f.mediaGroups.AUDIO.audio=function(e,t,i){var n;void 0===t&&(t={}),void 0===i&&(i=!1);var r=e.reduce((function(e,r){var a=r.attributes.role&&r.attributes.role.value||"",s=r.attributes.lang||"",o=r.attributes.label||"main";if(s&&!r.attributes.label){var u=a?" ("+a+")":"";o=""+r.attributes.lang+u}e[o]||(e[o]={language:s,autoselect:!0,default:"main"===a,playlists:[],uri:""});var l=I(function(e,t){var i,n=e.attributes,r=e.segments,a=e.sidx,s={attributes:(i={NAME:n.id,BANDWIDTH:n.bandwidth,CODECS:n.codecs},i["PROGRAM-ID"]=1,i),uri:"",endList:"static"===n.type,timeline:n.periodIndex,resolvedUri:"",targetDuration:n.duration,segments:r,mediaSequence:r.length?r[0].number:1};return n.contentProtection&&(s.contentProtection=n.contentProtection),a&&(s.sidx=a),t&&(s.attributes.AUDIO="audio",s.attributes.SUBTITLES="subs"),s}(r,i),t);return e[o].playlists.push(l),void 0===n&&"main"===a&&((n=r).default=!0),e}),{});n||(r[Object.keys(r)[0]].default=!0);return r}(h,i,p)),d.length&&(f.mediaGroups.SUBTITLES.subs=function(e,t){return void 0===t&&(t={}),e.reduce((function(e,i){var n=i.attributes.lang||"text";return e[n]||(e[n]={language:n,default:!1,autoselect:!1,playlists:[],uri:""}),e[n].playlists.push(I(function(e){var t,i=e.attributes,n=e.segments;void 0===n&&(n=[{uri:i.baseUrl,timeline:i.periodIndex,resolvedUri:i.baseUrl||"",duration:i.sourceDuration,number:0}],i.duration=i.sourceDuration);var r=((t={NAME:i.id,BANDWIDTH:i.bandwidth})["PROGRAM-ID"]=1,t);return i.codecs&&(r.CODECS=i.codecs),{attributes:r,uri:"",endList:"static"===i.type,timeline:i.periodIndex,resolvedUri:i.baseUrl||"",targetDuration:i.duration,segments:n,mediaSequence:n.length?n[0].number:1}}(i),t)),e}),{})}(d,i)),c.length&&(f.mediaGroups["CLOSED-CAPTIONS"].cc=c.reduce((function(e,t){return t?(t.forEach((function(t){var i=t.channel,n=t.language;e[n]={autoselect:!1,default:!1,instreamId:i,language:n},t.hasOwnProperty("aspectRatio")&&(e[n].aspectRatio=t.aspectRatio),t.hasOwnProperty("easyReader")&&(e[n].easyReader=t.easyReader),t.hasOwnProperty("3D")&&(e[n]["3D"]=t["3D"])})),e):e}),{})),f},M=function(e,t,i){var n=e.NOW,r=e.clientOffset,a=e.availabilityStartTime,s=e.timescale,o=void 0===s?1:s,u=e.start,l=void 0===u?0:u,h=e.minimumUpdatePeriod,d=(n+r)/1e3+(void 0===h?0:h)-(a+l);return Math.ceil((d*o-t)/i)},F=function(e,t){for(var i=e.type,n=e.minimumUpdatePeriod,r=void 0===n?0:n,a=e.media,s=void 0===a?"":a,o=e.sourceDuration,u=e.timescale,l=void 0===u?1:u,h=e.startNumber,d=void 0===h?1:h,c=e.periodIndex,f=[],p=-1,m=0;mp&&(p=y);var b=void 0;if(v<0){var S=m+1;b=S===t.length?"dynamic"===i&&r>0&&s.indexOf("$Number$")>0?M(e,p,g):(o*l-p)/g:(t[S].t-p)/g}else b=v+1;for(var T=d+f.length+b,E=d+f.length;E=r?a:""+new Array(r-a.length+1).join("0")+a)}}(t))},j=function(e,t){var i={RepresentationID:e.id,Bandwidth:e.bandwidth||0},n=e.initialization,r=void 0===n?{sourceURL:"",range:""}:n,a=S({baseUrl:e.baseUrl,source:N(r.sourceURL,i),range:r.range});return function(e,t){return e.duration||t?e.duration?w(e):F(e,t):[{number:e.startNumber||1,duration:e.sourceDuration,time:0,timeline:e.periodIndex}]}(e,t).map((function(t){i.Number=t.number,i.Time=t.time;var n=N(e.media||"",i),r=e.timescale||1,s=e.presentationTimeOffset||0,o=e.periodStart+(t.time-s)/r;return{uri:n,timeline:t.timeline,duration:t.duration,resolvedUri:u.default(e.baseUrl||"",n),map:a,number:t.number,presentationTime:o}}))},V=function(e,t){var i=e.duration,n=e.segmentUrls,r=void 0===n?[]:n,a=e.periodStart;if(!i&&!t||i&&t)throw new Error(y);var s,o=r.map((function(t){return function(e,t){var i=e.baseUrl,n=e.initialization,r=void 0===n?{}:n,a=S({baseUrl:i,source:r.sourceURL,range:r.range}),s=S({baseUrl:i,source:t.media,range:t.mediaRange});return s.map=a,s}(e,t)}));return i&&(s=w(e)),t&&(s=F(e,t)),s.map((function(t,i){if(o[i]){var n=o[i],r=e.timescale||1,s=e.presentationTimeOffset||0;return n.timeline=t.timeline,n.duration=t.duration,n.number=t.number,n.presentationTime=a+(t.time-s)/r,n}})).filter((function(e){return e}))},H=function(e){var t,i,n=e.attributes,r=e.segmentInfo;r.template?(i=j,t=c(n,r.template)):r.base?(i=A,t=c(n,r.base)):r.list&&(i=V,t=c(n,r.list));var a={attributes:n};if(!i)return a;var s=i(t,r.segmentTimeline);if(t.duration){var o=t,u=o.duration,l=o.timescale,h=void 0===l?1:l;t.duration=u/h}else s.length?t.duration=s.reduce((function(e,t){return Math.max(e,Math.ceil(t.duration))}),0):t.duration=0;return a.attributes=t,a.segments=s,r.base&&t.indexRange&&(a.sidx=s[0],a.segments=[]),a},z=function(e){return e.map(H)},G=function(e,t){return p(e.childNodes).filter((function(e){return e.tagName===t}))},W=function(e){return e.textContent.trim()},Y=function(e){var t=/P(?:(\d*)Y)?(?:(\d*)M)?(?:(\d*)D)?(?:T(?:(\d*)H)?(?:(\d*)M)?(?:([\d.]*)S)?)?/.exec(e);if(!t)return 0;var i=t.slice(1),n=i[0],r=i[1],a=i[2],s=i[3],o=i[4],u=i[5];return 31536e3*parseFloat(n||0)+2592e3*parseFloat(r||0)+86400*parseFloat(a||0)+3600*parseFloat(s||0)+60*parseFloat(o||0)+parseFloat(u||0)},q={mediaPresentationDuration:function(e){return Y(e)},availabilityStartTime:function(e){return/^\d+-\d+-\d+T\d+:\d+:\d+(\.\d+)?$/.test(t=e)&&(t+="Z"),Date.parse(t)/1e3;var t},minimumUpdatePeriod:function(e){return Y(e)},suggestedPresentationDelay:function(e){return Y(e)},type:function(e){return e},timeShiftBufferDepth:function(e){return Y(e)},start:function(e){return Y(e)},width:function(e){return parseInt(e,10)},height:function(e){return parseInt(e,10)},bandwidth:function(e){return parseInt(e,10)},startNumber:function(e){return parseInt(e,10)},timescale:function(e){return parseInt(e,10)},presentationTimeOffset:function(e){return parseInt(e,10)},duration:function(e){var t=parseInt(e,10);return isNaN(t)?Y(e):t},d:function(e){return parseInt(e,10)},t:function(e){return parseInt(e,10)},r:function(e){return parseInt(e,10)},DEFAULT:function(e){return e}},K=function(e){return e&&e.attributes?p(e.attributes).reduce((function(e,t){var i=q[t.name]||q.DEFAULT;return e[t.name]=i(t.value),e}),{}):{}},X={"urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b":"org.w3.clearkey","urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed":"com.widevine.alpha","urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95":"com.microsoft.playready","urn:uuid:f239e769-efa3-4850-9c16-a903c6932efb":"com.adobe.primetime"},Q=function(e,t){return t.length?f(e.map((function(e){return t.map((function(t){return u.default(e,W(t))}))}))):e},$=function(e){var t=G(e,"SegmentTemplate")[0],i=G(e,"SegmentList")[0],n=i&&G(i,"SegmentURL").map((function(e){return c({tag:"SegmentURL"},K(e))})),r=G(e,"SegmentBase")[0],a=i||t,s=a&&G(a,"SegmentTimeline")[0],o=i||r||t,u=o&&G(o,"Initialization")[0],l=t&&K(t);l&&u?l.initialization=u&&K(u):l&&l.initialization&&(l.initialization={sourceURL:l.initialization});var h={template:l,segmentTimeline:s&&G(s,"S").map((function(e){return K(e)})),list:i&&c(K(i),{segmentUrls:n,initialization:K(u)}),base:r&&c(K(r),{initialization:K(u)})};return Object.keys(h).forEach((function(e){h[e]||delete h[e]})),h},J=function(e,t,i){return function(n){var r,a=K(n),s=Q(t,G(n,"BaseURL")),o=G(n,"Role")[0],u={role:K(o)},l=c(e,a,u),d=G(n,"Accessibility")[0],p="urn:scte:dash:cc:cea-608:2015"===(r=K(d)).schemeIdUri?r.value.split(";").map((function(e){var t,i;if(i=e,/^CC\d=/.test(e)){var n=e.split("=");t=n[0],i=n[1]}else/^CC\d$/.test(e)&&(t=e);return{channel:t,language:i}})):"urn:scte:dash:cc:cea-708:2015"===r.schemeIdUri?r.value.split(";").map((function(e){var t={channel:void 0,language:void 0,aspectRatio:1,easyReader:0,"3D":0};if(/=/.test(e)){var i=e.split("="),n=i[0],r=i[1],a=void 0===r?"":r;t.channel=n,t.language=e,a.split(",").forEach((function(e){var i=e.split(":"),n=i[0],r=i[1];"lang"===n?t.language=r:"er"===n?t.easyReader=Number(r):"war"===n?t.aspectRatio=Number(r):"3D"===n&&(t["3D"]=Number(r))}))}else t.language=e;return t.channel&&(t.channel="SERVICE"+t.channel),t})):void 0;p&&(l=c(l,{captionServices:p}));var m=G(n,"Label")[0];if(m&&m.childNodes.length){var _=m.childNodes[0].nodeValue.trim();l=c(l,{label:_})}var g=G(n,"ContentProtection").reduce((function(e,t){var i=K(t),n=X[i.schemeIdUri];if(n){e[n]={attributes:i};var r=G(t,"cenc:pssh")[0];if(r){var a=W(r),s=a&&h.default(a);e[n].pssh=s}}return e}),{});Object.keys(g).length&&(l=c(l,{contentProtection:g}));var v=$(n),y=G(n,"Representation"),b=c(i,v);return f(y.map(function(e,t,i){return function(n){var r=G(n,"BaseURL"),a=Q(t,r),s=c(e,K(n)),o=$(n);return a.map((function(e){return{segmentInfo:c(i,o),attributes:c(s,{baseUrl:e})}}))}}(l,s,b)))}},Z=function(e,t){return function(i,n){var r=Q(t,G(i.node,"BaseURL")),a=parseInt(i.attributes.id,10),s=l.default.isNaN(a)?n:a,o=c(e,{periodIndex:s,periodStart:i.attributes.start});"number"==typeof i.attributes.duration&&(o.periodDuration=i.attributes.duration);var u=G(i.node,"AdaptationSet"),h=$(i.node);return f(u.map(J(o,r,h)))}},ee=function(e,t){void 0===t&&(t={});var i=t,n=i.manifestUri,r=void 0===n?"":n,a=i.NOW,s=void 0===a?Date.now():a,o=i.clientOffset,u=void 0===o?0:o,l=G(e,"Period");if(!l.length)throw new Error(m);var h=G(e,"Location"),d=K(e),c=Q([r],G(e,"BaseURL"));d.type=d.type||"static",d.sourceDuration=d.mediaPresentationDuration||0,d.NOW=s,d.clientOffset=u,h.length&&(d.locations=h.map(W));var p=[];return l.forEach((function(e,t){var i=K(e),n=p[t-1];i.start=function(e){var t=e.attributes,i=e.priorPeriodAttributes,n=e.mpdType;return"number"==typeof t.start?t.start:i&&"number"==typeof i.start&&"number"==typeof i.duration?i.start+i.duration:i||"static"!==n?null:0}({attributes:i,priorPeriodAttributes:n?n.attributes:null,mpdType:d.type}),p.push({node:e,attributes:i})})),{locations:d.locations,representationInfo:f(p.map(Z(d,c)))}},te=function(e){if(""===e)throw new Error(_);var t,i,n=new s.DOMParser;try{i=(t=n.parseFromString(e,"application/xml"))&&"MPD"===t.documentElement.tagName?t.documentElement:null}catch(e){}if(!i||i&&i.getElementsByTagName("parsererror").length>0)throw new Error(g);return i};i.VERSION="0.19.0",i.addSidxSegmentsToPlaylist=C,i.generateSidxKey=k,i.inheritAttributes=ee,i.parse=function(e,t){void 0===t&&(t={});var i=ee(te(e),t),n=z(i.representationInfo);return U(n,i.locations,t.sidxMapping)},i.parseUTCTiming=function(e){return function(e){var t=G(e,"UTCTiming")[0];if(!t)return null;var i=K(t);switch(i.schemeIdUri){case"urn:mpeg:dash:utc:http-head:2014":case"urn:mpeg:dash:utc:http-head:2012":i.method="HEAD";break;case"urn:mpeg:dash:utc:http-xsdate:2014":case"urn:mpeg:dash:utc:http-iso:2014":case"urn:mpeg:dash:utc:http-xsdate:2012":case"urn:mpeg:dash:utc:http-iso:2012":i.method="GET";break;case"urn:mpeg:dash:utc:direct:2014":case"urn:mpeg:dash:utc:direct:2012":i.method="DIRECT",i.value=Date.parse(i.value);break;case"urn:mpeg:dash:utc:http-ntp:2014":case"urn:mpeg:dash:utc:ntp:2014":case"urn:mpeg:dash:utc:sntp:2014":default:throw new Error(b)}return i}(te(e))},i.stringToMpdXml=te,i.toM3u8=U,i.toPlaylists=z},{"@videojs/vhs-utils/cjs/decode-b64-to-uint8-array":13,"@videojs/vhs-utils/cjs/resolve-url":20,"@xmldom/xmldom":28,"global/window":34}],41:[function(e,t,i){var n,r;n=window,r=function(){return function(e){var t={};function i(n){if(t[n])return t[n].exports;var r=t[n]={i:n,l:!1,exports:{}};return e[n].call(r.exports,r,r.exports,i),r.l=!0,r.exports}return i.m=e,i.c=t,i.d=function(e,t,n){i.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,t){if(1&t&&(e=i(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(i.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)i.d(n,r,function(t){return e[t]}.bind(null,r));return n},i.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="",i(i.s=14)}([function(e,t,i){"use strict";var n=i(6),r=i.n(n),a=function(){function e(){}return e.e=function(t,i){t&&!e.FORCE_GLOBAL_TAG||(t=e.GLOBAL_TAG);var n="["+t+"] > "+i;e.ENABLE_CALLBACK&&e.emitter.emit("log","error",n),e.ENABLE_ERROR&&(console.error?console.error(n):console.warn)},e.i=function(t,i){t&&!e.FORCE_GLOBAL_TAG||(t=e.GLOBAL_TAG);var n="["+t+"] > "+i;e.ENABLE_CALLBACK&&e.emitter.emit("log","info",n),e.ENABLE_INFO&&console.info&&console.info(n)},e.w=function(t,i){t&&!e.FORCE_GLOBAL_TAG||(t=e.GLOBAL_TAG);var n="["+t+"] > "+i;e.ENABLE_CALLBACK&&e.emitter.emit("log","warn",n),e.ENABLE_WARN&&console.warn},e.d=function(t,i){t&&!e.FORCE_GLOBAL_TAG||(t=e.GLOBAL_TAG);var n="["+t+"] > "+i;e.ENABLE_CALLBACK&&e.emitter.emit("log","debug",n),e.ENABLE_DEBUG&&console.debug&&console.debug(n)},e.v=function(t,i){t&&!e.FORCE_GLOBAL_TAG||(t=e.GLOBAL_TAG);var n="["+t+"] > "+i;e.ENABLE_CALLBACK&&e.emitter.emit("log","verbose",n),e.ENABLE_VERBOSE},e}();a.GLOBAL_TAG="mpegts.js",a.FORCE_GLOBAL_TAG=!1,a.ENABLE_ERROR=!0,a.ENABLE_INFO=!0,a.ENABLE_WARN=!0,a.ENABLE_DEBUG=!0,a.ENABLE_VERBOSE=!0,a.ENABLE_CALLBACK=!1,a.emitter=new r.a,t.a=a},function(e,t,i){"use strict";t.a={IO_ERROR:"io_error",DEMUX_ERROR:"demux_error",INIT_SEGMENT:"init_segment",MEDIA_SEGMENT:"media_segment",LOADING_COMPLETE:"loading_complete",RECOVERED_EARLY_EOF:"recovered_early_eof",MEDIA_INFO:"media_info",METADATA_ARRIVED:"metadata_arrived",SCRIPTDATA_ARRIVED:"scriptdata_arrived",TIMED_ID3_METADATA_ARRIVED:"timed_id3_metadata_arrived",PES_PRIVATE_DATA_DESCRIPTOR:"pes_private_data_descriptor",PES_PRIVATE_DATA_ARRIVED:"pes_private_data_arrived",STATISTICS_INFO:"statistics_info",RECOMMEND_SEEKPOINT:"recommend_seekpoint"}},function(e,t,i){"use strict";i.d(t,"c",(function(){return r})),i.d(t,"b",(function(){return a})),i.d(t,"a",(function(){return s}));var n=i(3),r={kIdle:0,kConnecting:1,kBuffering:2,kError:3,kComplete:4},a={OK:"OK",EXCEPTION:"Exception",HTTP_STATUS_CODE_INVALID:"HttpStatusCodeInvalid",CONNECTING_TIMEOUT:"ConnectingTimeout",EARLY_EOF:"EarlyEof",UNRECOVERABLE_EARLY_EOF:"UnrecoverableEarlyEof"},s=function(){function e(e){this._type=e||"undefined",this._status=r.kIdle,this._needStash=!1,this._onContentLengthKnown=null,this._onURLRedirect=null,this._onDataArrival=null,this._onError=null,this._onComplete=null}return e.prototype.destroy=function(){this._status=r.kIdle,this._onContentLengthKnown=null,this._onURLRedirect=null,this._onDataArrival=null,this._onError=null,this._onComplete=null},e.prototype.isWorking=function(){return this._status===r.kConnecting||this._status===r.kBuffering},Object.defineProperty(e.prototype,"type",{get:function(){return this._type},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"status",{get:function(){return this._status},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"needStashBuffer",{get:function(){return this._needStash},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onContentLengthKnown",{get:function(){return this._onContentLengthKnown},set:function(e){this._onContentLengthKnown=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onURLRedirect",{get:function(){return this._onURLRedirect},set:function(e){this._onURLRedirect=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onDataArrival",{get:function(){return this._onDataArrival},set:function(e){this._onDataArrival=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onError",{get:function(){return this._onError},set:function(e){this._onError=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onComplete",{get:function(){return this._onComplete},set:function(e){this._onComplete=e},enumerable:!1,configurable:!0}),e.prototype.open=function(e,t){throw new n.c("Unimplemented abstract function!")},e.prototype.abort=function(){throw new n.c("Unimplemented abstract function!")},e}()},function(e,t,i){"use strict";i.d(t,"d",(function(){return a})),i.d(t,"a",(function(){return s})),i.d(t,"b",(function(){return o})),i.d(t,"c",(function(){return u}));var n,r=(n=function(e,t){return(n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var i in t)t.hasOwnProperty(i)&&(e[i]=t[i])})(e,t)},function(e,t){function i(){this.constructor=e}n(e,t),e.prototype=null===t?Object.create(t):(i.prototype=t.prototype,new i)}),a=function(){function e(e){this._message=e}return Object.defineProperty(e.prototype,"name",{get:function(){return"RuntimeException"},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"message",{get:function(){return this._message},enumerable:!1,configurable:!0}),e.prototype.toString=function(){return this.name+": "+this.message},e}(),s=function(e){function t(t){return e.call(this,t)||this}return r(t,e),Object.defineProperty(t.prototype,"name",{get:function(){return"IllegalStateException"},enumerable:!1,configurable:!0}),t}(a),o=function(e){function t(t){return e.call(this,t)||this}return r(t,e),Object.defineProperty(t.prototype,"name",{get:function(){return"InvalidArgumentException"},enumerable:!1,configurable:!0}),t}(a),u=function(e){function t(t){return e.call(this,t)||this}return r(t,e),Object.defineProperty(t.prototype,"name",{get:function(){return"NotImplementedException"},enumerable:!1,configurable:!0}),t}(a)},function(e,t,i){"use strict";var n={};!function(){var e=self.navigator.userAgent.toLowerCase(),t=/(edge)\/([\w.]+)/.exec(e)||/(opr)[\/]([\w.]+)/.exec(e)||/(chrome)[ \/]([\w.]+)/.exec(e)||/(iemobile)[\/]([\w.]+)/.exec(e)||/(version)(applewebkit)[ \/]([\w.]+).*(safari)[ \/]([\w.]+)/.exec(e)||/(webkit)[ \/]([\w.]+).*(version)[ \/]([\w.]+).*(safari)[ \/]([\w.]+)/.exec(e)||/(webkit)[ \/]([\w.]+)/.exec(e)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(e)||/(msie) ([\w.]+)/.exec(e)||e.indexOf("trident")>=0&&/(rv)(?::| )([\w.]+)/.exec(e)||e.indexOf("compatible")<0&&/(firefox)[ \/]([\w.]+)/.exec(e)||[],i=/(ipad)/.exec(e)||/(ipod)/.exec(e)||/(windows phone)/.exec(e)||/(iphone)/.exec(e)||/(kindle)/.exec(e)||/(android)/.exec(e)||/(windows)/.exec(e)||/(mac)/.exec(e)||/(linux)/.exec(e)||/(cros)/.exec(e)||[],r={browser:t[5]||t[3]||t[1]||"",version:t[2]||t[4]||"0",majorVersion:t[4]||t[2]||"0",platform:i[0]||""},a={};if(r.browser){a[r.browser]=!0;var s=r.majorVersion.split(".");a.version={major:parseInt(r.majorVersion,10),string:r.version},s.length>1&&(a.version.minor=parseInt(s[1],10)),s.length>2&&(a.version.build=parseInt(s[2],10))}for(var o in r.platform&&(a[r.platform]=!0),(a.chrome||a.opr||a.safari)&&(a.webkit=!0),(a.rv||a.iemobile)&&(a.rv&&delete a.rv,r.browser="msie",a.msie=!0),a.edge&&(delete a.edge,r.browser="msedge",a.msedge=!0),a.opr&&(r.browser="opera",a.opera=!0),a.safari&&a.android&&(r.browser="android",a.android=!0),a.name=r.browser,a.platform=r.platform,n)n.hasOwnProperty(o)&&delete n[o];Object.assign(n,a)}(),t.a=n},function(e,t,i){"use strict";t.a={OK:"OK",FORMAT_ERROR:"FormatError",FORMAT_UNSUPPORTED:"FormatUnsupported",CODEC_UNSUPPORTED:"CodecUnsupported"}},function(e,t,i){"use strict";var n,r="object"==typeof Reflect?Reflect:null,a=r&&"function"==typeof r.apply?r.apply:function(e,t,i){return Function.prototype.apply.call(e,t,i)};n=r&&"function"==typeof r.ownKeys?r.ownKeys:Object.getOwnPropertySymbols?function(e){return Object.getOwnPropertyNames(e).concat(Object.getOwnPropertySymbols(e))}:function(e){return Object.getOwnPropertyNames(e)};var s=Number.isNaN||function(e){return e!=e};function o(){o.init.call(this)}e.exports=o,e.exports.once=function(e,t){return new Promise((function(i,n){function r(i){e.removeListener(t,a),n(i)}function a(){"function"==typeof e.removeListener&&e.removeListener("error",r),i([].slice.call(arguments))}g(e,t,a,{once:!0}),"error"!==t&&function(e,t,i){"function"==typeof e.on&&g(e,"error",t,{once:!0})}(e,r)}))},o.EventEmitter=o,o.prototype._events=void 0,o.prototype._eventsCount=0,o.prototype._maxListeners=void 0;var u=10;function l(e){if("function"!=typeof e)throw new TypeError('The "listener" argument must be of type Function. Received type '+typeof e)}function h(e){return void 0===e._maxListeners?o.defaultMaxListeners:e._maxListeners}function d(e,t,i,n){var r,a,s;if(l(i),void 0===(a=e._events)?(a=e._events=Object.create(null),e._eventsCount=0):(void 0!==a.newListener&&(e.emit("newListener",t,i.listener?i.listener:i),a=e._events),s=a[t]),void 0===s)s=a[t]=i,++e._eventsCount;else if("function"==typeof s?s=a[t]=n?[i,s]:[s,i]:n?s.unshift(i):s.push(i),(r=h(e))>0&&s.length>r&&!s.warned){s.warned=!0;var o=new Error("Possible EventEmitter memory leak detected. "+s.length+" "+String(t)+" listeners added. Use emitter.setMaxListeners() to increase limit");o.name="MaxListenersExceededWarning",o.emitter=e,o.type=t,o.count=s.length,console&&console.warn}return e}function c(){if(!this.fired)return this.target.removeListener(this.type,this.wrapFn),this.fired=!0,0===arguments.length?this.listener.call(this.target):this.listener.apply(this.target,arguments)}function f(e,t,i){var n={fired:!1,wrapFn:void 0,target:e,type:t,listener:i},r=c.bind(n);return r.listener=i,n.wrapFn=r,r}function p(e,t,i){var n=e._events;if(void 0===n)return[];var r=n[t];return void 0===r?[]:"function"==typeof r?i?[r.listener||r]:[r]:i?function(e){for(var t=new Array(e.length),i=0;i0&&(s=t[0]),s instanceof Error)throw s;var o=new Error("Unhandled error."+(s?" ("+s.message+")":""));throw o.context=s,o}var u=r[e];if(void 0===u)return!1;if("function"==typeof u)a(u,this,t);else{var l=u.length,h=_(u,l);for(i=0;i=0;a--)if(i[a]===t||i[a].listener===t){s=i[a].listener,r=a;break}if(r<0)return this;0===r?i.shift():function(e,t){for(;t+1=0;n--)this.removeListener(e,t[n]);return this},o.prototype.listeners=function(e){return p(this,e,!0)},o.prototype.rawListeners=function(e){return p(this,e,!1)},o.listenerCount=function(e,t){return"function"==typeof e.listenerCount?e.listenerCount(t):m.call(e,t)},o.prototype.listenerCount=m,o.prototype.eventNames=function(){return this._eventsCount>0?n(this._events):[]}},function(e,t,i){"use strict";i.d(t,"d",(function(){return n})),i.d(t,"b",(function(){return r})),i.d(t,"a",(function(){return a})),i.d(t,"c",(function(){return s}));var n=function(e,t,i,n,r){this.dts=e,this.pts=t,this.duration=i,this.originalDts=n,this.isSyncPoint=r,this.fileposition=null},r=function(){function e(){this.beginDts=0,this.endDts=0,this.beginPts=0,this.endPts=0,this.originalBeginDts=0,this.originalEndDts=0,this.syncPoints=[],this.firstSample=null,this.lastSample=null}return e.prototype.appendSyncPoint=function(e){e.isSyncPoint=!0,this.syncPoints.push(e)},e}(),a=function(){function e(){this._list=[]}return e.prototype.clear=function(){this._list=[]},e.prototype.appendArray=function(e){var t=this._list;0!==e.length&&(t.length>0&&e[0].originalDts=t[r].dts&&et[n].lastSample.originalDts&&e=t[n].lastSample.originalDts&&(n===t.length-1||n0&&(r=this._searchNearestSegmentBefore(i.originalBeginDts)+1),this._lastAppendLocation=r,this._list.splice(r,0,i)},e.prototype.getLastSegmentBefore=function(e){var t=this._searchNearestSegmentBefore(e);return t>=0?this._list[t]:null},e.prototype.getLastSampleBefore=function(e){var t=this.getLastSegmentBefore(e);return null!=t?t.lastSample:null},e.prototype.getLastSyncPointBefore=function(e){for(var t=this._searchNearestSegmentBefore(e),i=this._list[t].syncPoints;0===i.length&&t>0;)t--,i=this._list[t].syncPoints;return i.length>0?i[i.length-1]:null},e}()},function(e,t,i){"use strict";var n=function(){function e(){this.mimeType=null,this.duration=null,this.hasAudio=null,this.hasVideo=null,this.audioCodec=null,this.videoCodec=null,this.audioDataRate=null,this.videoDataRate=null,this.audioSampleRate=null,this.audioChannelCount=null,this.width=null,this.height=null,this.fps=null,this.profile=null,this.level=null,this.refFrames=null,this.chromaFormat=null,this.sarNum=null,this.sarDen=null,this.metadata=null,this.segments=null,this.segmentCount=null,this.hasKeyframesIndex=null,this.keyframesIndex=null}return e.prototype.isComplete=function(){var e=!1===this.hasAudio||!0===this.hasAudio&&null!=this.audioCodec&&null!=this.audioSampleRate&&null!=this.audioChannelCount,t=!1===this.hasVideo||!0===this.hasVideo&&null!=this.videoCodec&&null!=this.width&&null!=this.height&&null!=this.fps&&null!=this.profile&&null!=this.level&&null!=this.refFrames&&null!=this.chromaFormat&&null!=this.sarNum&&null!=this.sarDen;return null!=this.mimeType&&e&&t},e.prototype.isSeekable=function(){return!0===this.hasKeyframesIndex},e.prototype.getNearestKeyframe=function(e){if(null==this.keyframesIndex)return null;var t=this.keyframesIndex,i=this._search(t.times,e);return{index:i,milliseconds:t.times[i],fileposition:t.filepositions[i]}},e.prototype._search=function(e,t){var i=0,n=e.length-1,r=0,a=0,s=n;for(t=e[r]&&t0){var i=e.getConfig();t.emit("change",i)}},e.registerListener=function(t){e.emitter.addListener("change",t)},e.removeListener=function(t){e.emitter.removeListener("change",t)},e.addLogListener=function(t){a.a.emitter.addListener("log",t),a.a.emitter.listenerCount("log")>0&&(a.a.ENABLE_CALLBACK=!0,e._notifyChange())},e.removeLogListener=function(t){a.a.emitter.removeListener("log",t),0===a.a.emitter.listenerCount("log")&&(a.a.ENABLE_CALLBACK=!1,e._notifyChange())},e}();s.emitter=new r.a,t.a=s},function(e,t,i){"use strict";var n=i(6),r=i.n(n),a=i(0),s=i(4),o=i(8);function u(e,t,i){var n=e;if(t+i=128){t.push(String.fromCharCode(65535&a)),n+=2;continue}}else if(i[n]<240){if(u(i,n,2)&&(a=(15&i[n])<<12|(63&i[n+1])<<6|63&i[n+2])>=2048&&55296!=(63488&a)){t.push(String.fromCharCode(65535&a)),n+=3;continue}}else if(i[n]<248){var a;if(u(i,n,3)&&(a=(7&i[n])<<18|(63&i[n+1])<<12|(63&i[n+2])<<6|63&i[n+3])>65536&&a<1114112){a-=65536,t.push(String.fromCharCode(a>>>10|55296)),t.push(String.fromCharCode(1023&a|56320)),n+=4;continue}}t.push(String.fromCharCode(65533)),++n}return t.join("")},c=i(3),f=(l=new ArrayBuffer(2),new DataView(l).setInt16(0,256,!0),256===new Int16Array(l)[0]),p=function(){function e(){}return e.parseScriptData=function(t,i,n){var r={};try{var s=e.parseValue(t,i,n),o=e.parseValue(t,i+s.size,n-s.size);r[s.data]=o.data}catch(e){a.a.e("AMF",e.toString())}return r},e.parseObject=function(t,i,n){if(n<3)throw new c.a("Data not enough when parse ScriptDataObject");var r=e.parseString(t,i,n),a=e.parseValue(t,i+r.size,n-r.size),s=a.objectEnd;return{data:{name:r.data,value:a.data},size:r.size+a.size,objectEnd:s}},e.parseVariable=function(t,i,n){return e.parseObject(t,i,n)},e.parseString=function(e,t,i){if(i<2)throw new c.a("Data not enough when parse String");var n=new DataView(e,t,i).getUint16(0,!f);return{data:n>0?d(new Uint8Array(e,t+2,n)):"",size:2+n}},e.parseLongString=function(e,t,i){if(i<4)throw new c.a("Data not enough when parse LongString");var n=new DataView(e,t,i).getUint32(0,!f);return{data:n>0?d(new Uint8Array(e,t+4,n)):"",size:4+n}},e.parseDate=function(e,t,i){if(i<10)throw new c.a("Data size invalid when parse Date");var n=new DataView(e,t,i),r=n.getFloat64(0,!f),a=n.getInt16(8,!f);return{data:new Date(r+=60*a*1e3),size:10}},e.parseValue=function(t,i,n){if(n<1)throw new c.a("Data not enough when parse Value");var r,s=new DataView(t,i,n),o=1,u=s.getUint8(0),l=!1;try{switch(u){case 0:r=s.getFloat64(1,!f),o+=8;break;case 1:r=!!s.getUint8(1),o+=1;break;case 2:var h=e.parseString(t,i+1,n-1);r=h.data,o+=h.size;break;case 3:r={};var d=0;for(9==(16777215&s.getUint32(n-4,!f))&&(d=3);o32)throw new c.b("ExpGolomb: readBits() bits exceeded max 32bits!");if(e<=this._current_word_bits_left){var t=this._current_word>>>32-e;return this._current_word<<=e,this._current_word_bits_left-=e,t}var i=this._current_word_bits_left?this._current_word:0;i>>>=32-this._current_word_bits_left;var n=e-this._current_word_bits_left;this._fillCurrentWord();var r=Math.min(n,this._current_word_bits_left),a=this._current_word>>>32-r;return this._current_word<<=r,this._current_word_bits_left-=r,i<>>e))return this._current_word<<=e,this._current_word_bits_left-=e,e;return this._fillCurrentWord(),e+this._skipLeadingZero()},e.prototype.readUEG=function(){var e=this._skipLeadingZero();return this.readBits(e+1)-1},e.prototype.readSEG=function(){var e=this.readUEG();return 1&e?e+1>>>1:-1*(e>>>1)},e}(),_=function(){function e(){}return e._ebsp2rbsp=function(e){for(var t=e,i=t.byteLength,n=new Uint8Array(i),r=0,a=0;a=2&&3===t[a]&&0===t[a-1]&&0===t[a-2]||(n[r]=t[a],r++);return new Uint8Array(n.buffer,0,r)},e.parseSPS=function(t){for(var i=t.subarray(1,4),n="avc1.",r=0;r<3;r++){var a=i[r].toString(16);a.length<2&&(a="0"+a),n+=a}var s=e._ebsp2rbsp(t),o=new m(s);o.readByte();var u=o.readByte();o.readByte();var l=o.readByte();o.readUEG();var h=e.getProfileString(u),d=e.getLevelString(l),c=1,f=420,p=8,_=8;if((100===u||110===u||122===u||244===u||44===u||83===u||86===u||118===u||128===u||138===u||144===u)&&(3===(c=o.readUEG())&&o.readBits(1),c<=3&&(f=[0,420,422,444][c]),p=o.readUEG()+8,_=o.readUEG()+8,o.readBits(1),o.readBool()))for(var g=3!==c?8:12,v=0;v0&&U<16?(I=[1,12,10,16,40,24,20,32,80,18,15,64,160,4,3,2][U-1],L=[1,11,11,11,33,11,11,11,33,11,11,33,99,3,2,1][U-1]):255===U&&(I=o.readByte()<<8|o.readByte(),L=o.readByte()<<8|o.readByte())}if(o.readBool()&&o.readBool(),o.readBool()&&(o.readBits(4),o.readBool()&&o.readBits(24)),o.readBool()&&(o.readUEG(),o.readUEG()),o.readBool()){var M=o.readBits(32),F=o.readBits(32);R=o.readBool(),x=(D=F)/(O=2*M)}}var B=1;1===I&&1===L||(B=I/L);var N=0,j=0;0===c?(N=1,j=2-w):(N=3===c?1:2,j=(1===c?2:1)*(2-w));var V=16*(T+1),H=16*(E+1)*(2-w);V-=(A+C)*N,H-=(k+P)*j;var z=Math.ceil(V*B);return o.destroy(),o=null,{codec_mimetype:n,profile_idc:u,level_idc:l,profile_string:h,level_string:d,chroma_format_idc:c,bit_depth:p,bit_depth_luma:p,bit_depth_chroma:_,ref_frames:S,chroma_format:f,chroma_format_string:e.getChromaFormatString(f),frame_rate:{fixed:R,fps:x,fps_den:O,fps_num:D},sar_ratio:{width:I,height:L},codec_size:{width:V,height:H},present_size:{width:z,height:H}}},e._skipScalingList=function(e,t){for(var i=8,n=8,r=0;r>>2!=0,a=0!=(1&t[4]),s=(n=t)[5]<<24|n[6]<<16|n[7]<<8|n[8];return s<9?i:{match:!0,consumed:s,dataOffset:s,hasAudioTrack:r,hasVideoTrack:a}},e.prototype.bindDataSource=function(e){return e.onDataArrival=this.parseChunks.bind(this),this},Object.defineProperty(e.prototype,"onTrackMetadata",{get:function(){return this._onTrackMetadata},set:function(e){this._onTrackMetadata=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onMediaInfo",{get:function(){return this._onMediaInfo},set:function(e){this._onMediaInfo=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onMetaDataArrived",{get:function(){return this._onMetaDataArrived},set:function(e){this._onMetaDataArrived=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onScriptDataArrived",{get:function(){return this._onScriptDataArrived},set:function(e){this._onScriptDataArrived=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onError",{get:function(){return this._onError},set:function(e){this._onError=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onDataAvailable",{get:function(){return this._onDataAvailable},set:function(e){this._onDataAvailable=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"timestampBase",{get:function(){return this._timestampBase},set:function(e){this._timestampBase=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"overridedDuration",{get:function(){return this._duration},set:function(e){this._durationOverrided=!0,this._duration=e,this._mediaInfo.duration=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"overridedHasAudio",{set:function(e){this._hasAudioFlagOverrided=!0,this._hasAudio=e,this._mediaInfo.hasAudio=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"overridedHasVideo",{set:function(e){this._hasVideoFlagOverrided=!0,this._hasVideo=e,this._mediaInfo.hasVideo=e},enumerable:!1,configurable:!0}),e.prototype.resetMediaInfo=function(){this._mediaInfo=new o.a},e.prototype._isInitialMetadataDispatched=function(){return this._hasAudio&&this._hasVideo?this._audioInitialMetadataDispatched&&this._videoInitialMetadataDispatched:this._hasAudio&&!this._hasVideo?this._audioInitialMetadataDispatched:!(this._hasAudio||!this._hasVideo)&&this._videoInitialMetadataDispatched},e.prototype.parseChunks=function(t,i){if(!(this._onError&&this._onMediaInfo&&this._onTrackMetadata&&this._onDataAvailable))throw new c.a("Flv: onError & onMediaInfo & onTrackMetadata & onDataAvailable callback must be specified");var n=0,r=this._littleEndian;if(0===i){if(!(t.byteLength>13))return 0;n=e.probe(t).dataOffset}for(this._firstParse&&(this._firstParse=!1,i+n!==this._dataOffset&&a.a.w(this.TAG,"First time parsing but chunk byteStart invalid!"),0!==(s=new DataView(t,n)).getUint32(0,!r)&&a.a.w(this.TAG,"PrevTagSize0 !== 0 !!!"),n+=4);nt.byteLength)break;var o=s.getUint8(0),u=16777215&s.getUint32(0,!r);if(n+11+u+4>t.byteLength)break;if(8===o||9===o||18===o){var l=s.getUint8(4),h=s.getUint8(5),d=s.getUint8(6)|h<<8|l<<16|s.getUint8(7)<<24;0!=(16777215&s.getUint32(7,!r))&&a.a.w(this.TAG,"Meet tag which has StreamID != 0!");var f=n+11;switch(o){case 8:this._parseAudioData(t,f,u,d);break;case 9:this._parseVideoData(t,f,u,d,i+n);break;case 18:this._parseScriptData(t,f,u)}var p=s.getUint32(11+u,!r);p!==11+u&&a.a.w(this.TAG,"Invalid PrevTagSize "+p),n+=11+u+4}else a.a.w(this.TAG,"Unsupported tag type "+o+", skipped"),n+=11+u+4}return this._isInitialMetadataDispatched()&&this._dispatch&&(this._audioTrack.length||this._videoTrack.length)&&this._onDataAvailable(this._audioTrack,this._videoTrack),n},e.prototype._parseScriptData=function(e,t,i){var n=p.parseScriptData(e,t,i);if(n.hasOwnProperty("onMetaData")){if(null==n.onMetaData||"object"!=typeof n.onMetaData)return void a.a.w(this.TAG,"Invalid onMetaData structure!");this._metadata&&a.a.w(this.TAG,"Found another onMetaData tag!"),this._metadata=n;var r=this._metadata.onMetaData;if(this._onMetaDataArrived&&this._onMetaDataArrived(Object.assign({},r)),"boolean"==typeof r.hasAudio&&!1===this._hasAudioFlagOverrided&&(this._hasAudio=r.hasAudio,this._mediaInfo.hasAudio=this._hasAudio),"boolean"==typeof r.hasVideo&&!1===this._hasVideoFlagOverrided&&(this._hasVideo=r.hasVideo,this._mediaInfo.hasVideo=this._hasVideo),"number"==typeof r.audiodatarate&&(this._mediaInfo.audioDataRate=r.audiodatarate),"number"==typeof r.videodatarate&&(this._mediaInfo.videoDataRate=r.videodatarate),"number"==typeof r.width&&(this._mediaInfo.width=r.width),"number"==typeof r.height&&(this._mediaInfo.height=r.height),"number"==typeof r.duration){if(!this._durationOverrided){var s=Math.floor(r.duration*this._timescale);this._duration=s,this._mediaInfo.duration=s}}else this._mediaInfo.duration=0;if("number"==typeof r.framerate){var o=Math.floor(1e3*r.framerate);if(o>0){var u=o/1e3;this._referenceFrameRate.fixed=!0,this._referenceFrameRate.fps=u,this._referenceFrameRate.fps_num=o,this._referenceFrameRate.fps_den=1e3,this._mediaInfo.fps=u}}if("object"==typeof r.keyframes){this._mediaInfo.hasKeyframesIndex=!0;var l=r.keyframes;this._mediaInfo.keyframesIndex=this._parseKeyframesIndex(l),r.keyframes=null}else this._mediaInfo.hasKeyframesIndex=!1;this._dispatch=!1,this._mediaInfo.metadata=r,a.a.v(this.TAG,"Parsed onMetaData"),this._mediaInfo.isComplete()&&this._onMediaInfo(this._mediaInfo)}Object.keys(n).length>0&&this._onScriptDataArrived&&this._onScriptDataArrived(Object.assign({},n))},e.prototype._parseKeyframesIndex=function(e){for(var t=[],i=[],n=1;n>>4;if(2===s||10===s){var o=0,u=(12&r)>>>2;if(u>=0&&u<=4){o=this._flvSoundRateTable[u];var l=1&r,h=this._audioMetadata,d=this._audioTrack;if(h||(!1===this._hasAudio&&!1===this._hasAudioFlagOverrided&&(this._hasAudio=!0,this._mediaInfo.hasAudio=!0),(h=this._audioMetadata={}).type="audio",h.id=d.id,h.timescale=this._timescale,h.duration=this._duration,h.audioSampleRate=o,h.channelCount=0===l?1:2),10===s){var c=this._parseAACAudioData(e,t+1,i-1);if(null==c)return;if(0===c.packetType){h.config&&a.a.w(this.TAG,"Found another AudioSpecificConfig!");var f=c.data;h.audioSampleRate=f.samplingRate,h.channelCount=f.channelCount,h.codec=f.codec,h.originalCodec=f.originalCodec,h.config=f.config,h.refSampleDuration=1024/h.audioSampleRate*h.timescale,a.a.v(this.TAG,"Parsed AudioSpecificConfig"),this._isInitialMetadataDispatched()?this._dispatch&&(this._audioTrack.length||this._videoTrack.length)&&this._onDataAvailable(this._audioTrack,this._videoTrack):this._audioInitialMetadataDispatched=!0,this._dispatch=!1,this._onTrackMetadata("audio",h),(_=this._mediaInfo).audioCodec=h.originalCodec,_.audioSampleRate=h.audioSampleRate,_.audioChannelCount=h.channelCount,_.hasVideo?null!=_.videoCodec&&(_.mimeType='video/x-flv; codecs="'+_.videoCodec+","+_.audioCodec+'"'):_.mimeType='video/x-flv; codecs="'+_.audioCodec+'"',_.isComplete()&&this._onMediaInfo(_)}else if(1===c.packetType){var p=this._timestampBase+n,m={unit:c.data,length:c.data.byteLength,dts:p,pts:p};d.samples.push(m),d.length+=c.data.length}else a.a.e(this.TAG,"Flv: Unsupported AAC data type "+c.packetType)}else if(2===s){if(!h.codec){var _;if(null==(f=this._parseMP3AudioData(e,t+1,i-1,!0)))return;h.audioSampleRate=f.samplingRate,h.channelCount=f.channelCount,h.codec=f.codec,h.originalCodec=f.originalCodec,h.refSampleDuration=1152/h.audioSampleRate*h.timescale,a.a.v(this.TAG,"Parsed MPEG Audio Frame Header"),this._audioInitialMetadataDispatched=!0,this._onTrackMetadata("audio",h),(_=this._mediaInfo).audioCodec=h.codec,_.audioSampleRate=h.audioSampleRate,_.audioChannelCount=h.channelCount,_.audioDataRate=f.bitRate,_.hasVideo?null!=_.videoCodec&&(_.mimeType='video/x-flv; codecs="'+_.videoCodec+","+_.audioCodec+'"'):_.mimeType='video/x-flv; codecs="'+_.audioCodec+'"',_.isComplete()&&this._onMediaInfo(_)}var v=this._parseMP3AudioData(e,t+1,i-1,!1);if(null==v)return;p=this._timestampBase+n;var y={unit:v,length:v.byteLength,dts:p,pts:p};d.samples.push(y),d.length+=v.length}}else this._onError(g.a.FORMAT_ERROR,"Flv: Invalid audio sample rate idx: "+u)}else this._onError(g.a.CODEC_UNSUPPORTED,"Flv: Unsupported audio codec idx: "+s)}},e.prototype._parseAACAudioData=function(e,t,i){if(!(i<=1)){var n={},r=new Uint8Array(e,t,i);return n.packetType=r[0],0===r[0]?n.data=this._parseAACAudioSpecificConfig(e,t+1,i-1):n.data=r.subarray(1),n}a.a.w(this.TAG,"Flv: Invalid AAC packet, missing AACPacketType or/and Data!")},e.prototype._parseAACAudioSpecificConfig=function(e,t,i){var n,r,a=new Uint8Array(e,t,i),s=null,o=0,u=null;if(o=n=a[0]>>>3,(r=(7&a[0])<<1|a[1]>>>7)<0||r>=this._mpegSamplingRates.length)this._onError(g.a.FORMAT_ERROR,"Flv: AAC invalid sampling frequency index!");else{var l=this._mpegSamplingRates[r],h=(120&a[1])>>>3;if(!(h<0||h>=8)){5===o&&(u=(7&a[1])<<1|a[2]>>>7,a[2]);var d=self.navigator.userAgent.toLowerCase();return-1!==d.indexOf("firefox")?r>=6?(o=5,s=new Array(4),u=r-3):(o=2,s=new Array(2),u=r):-1!==d.indexOf("android")?(o=2,s=new Array(2),u=r):(o=5,u=r,s=new Array(4),r>=6?u=r-3:1===h&&(o=2,s=new Array(2),u=r)),s[0]=o<<3,s[0]|=(15&r)>>>1,s[1]=(15&r)<<7,s[1]|=(15&h)<<3,5===o&&(s[1]|=(15&u)>>>1,s[2]=(1&u)<<7,s[2]|=8,s[3]=0),{config:s,samplingRate:l,channelCount:h,codec:"mp4a.40."+o,originalCodec:"mp4a.40."+n}}this._onError(g.a.FORMAT_ERROR,"Flv: AAC invalid channel configuration")}},e.prototype._parseMP3AudioData=function(e,t,i,n){if(!(i<4)){this._littleEndian;var r=new Uint8Array(e,t,i),s=null;if(n){if(255!==r[0])return;var o=r[1]>>>3&3,u=(6&r[1])>>1,l=(240&r[2])>>>4,h=(12&r[2])>>>2,d=3!=(r[3]>>>6&3)?2:1,c=0,f=0;switch(o){case 0:c=this._mpegAudioV25SampleRateTable[h];break;case 2:c=this._mpegAudioV20SampleRateTable[h];break;case 3:c=this._mpegAudioV10SampleRateTable[h]}switch(u){case 1:l>>4,u=15&s;7===u?this._parseAVCVideoPacket(e,t+1,i-1,n,r,o):this._onError(g.a.CODEC_UNSUPPORTED,"Flv: Unsupported codec in video frame: "+u)}},e.prototype._parseAVCVideoPacket=function(e,t,i,n,r,s){if(i<4)a.a.w(this.TAG,"Flv: Invalid AVC packet, missing AVCPacketType or/and CompositionTime");else{var o=this._littleEndian,u=new DataView(e,t,i),l=u.getUint8(0),h=(16777215&u.getUint32(0,!o))<<8>>8;if(0===l)this._parseAVCDecoderConfigurationRecord(e,t+4,i-4);else if(1===l)this._parseAVCVideoData(e,t+4,i-4,n,r,s,h);else if(2!==l)return void this._onError(g.a.FORMAT_ERROR,"Flv: Invalid video packet type "+l)}},e.prototype._parseAVCDecoderConfigurationRecord=function(e,t,i){if(i<7)a.a.w(this.TAG,"Flv: Invalid AVCDecoderConfigurationRecord, lack of data!");else{var n=this._videoMetadata,r=this._videoTrack,s=this._littleEndian,o=new DataView(e,t,i);n?void 0!==n.avcc&&a.a.w(this.TAG,"Found another AVCDecoderConfigurationRecord!"):(!1===this._hasVideo&&!1===this._hasVideoFlagOverrided&&(this._hasVideo=!0,this._mediaInfo.hasVideo=!0),(n=this._videoMetadata={}).type="video",n.id=r.id,n.timescale=this._timescale,n.duration=this._duration);var u=o.getUint8(0),l=o.getUint8(1);if(o.getUint8(2),o.getUint8(3),1===u&&0!==l)if(this._naluLengthSize=1+(3&o.getUint8(4)),3===this._naluLengthSize||4===this._naluLengthSize){var h=31&o.getUint8(5);if(0!==h){h>1&&a.a.w(this.TAG,"Flv: Strange AVCDecoderConfigurationRecord: SPS Count = "+h);for(var d=6,c=0;c1&&a.a.w(this.TAG,"Flv: Strange AVCDecoderConfigurationRecord: PPS Count = "+A),d++,c=0;c=i){a.a.w(this.TAG,"Malformed Nalu near timestamp "+p+", offset = "+c+", dataSize = "+i);break}var _=l.getUint32(c,!u);if(3===f&&(_>>>=8),_>i-f)return void a.a.w(this.TAG,"Malformed Nalus near timestamp "+p+", NaluSize > DataSize!");var g=31&l.getUint8(c+f);5===g&&(m=!0);var v=new Uint8Array(e,t+c,f+_),y={type:g,data:v};h.push(y),d+=v.byteLength,c+=f+_}if(h.length){var b=this._videoTrack,S={units:h,length:d,isKeyframe:m,dts:p,cts:o,pts:p+o};m&&(S.fileposition=r),b.samples.push(S),b.length+=d}},e}(),y=function(){function e(){}return e.prototype.destroy=function(){this.onError=null,this.onMediaInfo=null,this.onMetaDataArrived=null,this.onTrackMetadata=null,this.onDataAvailable=null,this.onTimedID3Metadata=null,this.onPESPrivateData=null,this.onPESPrivateDataDescriptor=null},e}(),b=function(){this.program_pmt_pid={}};!function(e){e[e.kMPEG1Audio=3]="kMPEG1Audio",e[e.kMPEG2Audio=4]="kMPEG2Audio",e[e.kPESPrivateData=6]="kPESPrivateData",e[e.kADTSAAC=15]="kADTSAAC",e[e.kID3=21]="kID3",e[e.kH264=27]="kH264",e[e.kH265=36]="kH265"}(h||(h={}));var S,T=function(){this.pid_stream_type={},this.common_pids={h264:void 0,adts_aac:void 0},this.pes_private_data_pids={},this.timed_id3_pids={}},E=function(){},w=function(){this.slices=[],this.total_length=0,this.expected_length=0,this.file_position=0};!function(e){e[e.kUnspecified=0]="kUnspecified",e[e.kSliceNonIDR=1]="kSliceNonIDR",e[e.kSliceDPA=2]="kSliceDPA",e[e.kSliceDPB=3]="kSliceDPB",e[e.kSliceDPC=4]="kSliceDPC",e[e.kSliceIDR=5]="kSliceIDR",e[e.kSliceSEI=6]="kSliceSEI",e[e.kSliceSPS=7]="kSliceSPS",e[e.kSlicePPS=8]="kSlicePPS",e[e.kSliceAUD=9]="kSliceAUD",e[e.kEndOfSequence=10]="kEndOfSequence",e[e.kEndOfStream=11]="kEndOfStream",e[e.kFiller=12]="kFiller",e[e.kSPSExt=13]="kSPSExt",e[e.kReserved0=14]="kReserved0"}(S||(S={}));var A,C,k=function(){},P=function(e){var t=e.data.byteLength;this.type=e.type,this.data=new Uint8Array(4+t),new DataView(this.data.buffer).setUint32(0,t),this.data.set(e.data,4)},I=function(){function e(e){this.TAG="H264AnnexBParser",this.current_startcode_offset_=0,this.eof_flag_=!1,this.data_=e,this.current_startcode_offset_=this.findNextStartCodeOffset(0),this.eof_flag_&&a.a.e(this.TAG,"Could not found H264 startcode until payload end!")}return e.prototype.findNextStartCodeOffset=function(e){for(var t=e,i=this.data_;;){if(t+3>=i.byteLength)return this.eof_flag_=!0,i.byteLength;var n=i[t+0]<<24|i[t+1]<<16|i[t+2]<<8|i[t+3],r=i[t+0]<<16|i[t+1]<<8|i[t+2];if(1===n||1===r)return t;t++}},e.prototype.readNextNaluPayload=function(){for(var e=this.data_,t=null;null==t&&!this.eof_flag_;){var i=this.current_startcode_offset_,n=31&e[i+=1==(e[i]<<24|e[i+1]<<16|e[i+2]<<8|e[i+3])?4:3],r=(128&e[i])>>>7,a=this.findNextStartCodeOffset(i);if(this.current_startcode_offset_=a,!(n>=S.kReserved0)&&0===r){var s=e.subarray(i,a);(t=new k).type=n,t.data=s}}return t},e}(),L=function(){function e(e,t,i){var n=8+e.byteLength+1+2+t.byteLength,r=!1;66!==e[3]&&77!==e[3]&&88!==e[3]&&(r=!0,n+=4);var a=this.data=new Uint8Array(n);a[0]=1,a[1]=e[1],a[2]=e[2],a[3]=e[3],a[4]=255,a[5]=225;var s=e.byteLength;a[6]=s>>>8,a[7]=255&s;var o=8;a.set(e,8),a[o+=s]=1;var u=t.byteLength;a[o+1]=u>>>8,a[o+2]=255&u,a.set(t,o+3),o+=3+u,r&&(a[o]=252|i.chroma_format_idc,a[o+1]=248|i.bit_depth_luma-8,a[o+2]=248|i.bit_depth_chroma-8,a[o+3]=0,o+=4)}return e.prototype.getData=function(){return this.data},e}();!function(e){e[e.kNull=0]="kNull",e[e.kAACMain=1]="kAACMain",e[e.kAAC_LC=2]="kAAC_LC",e[e.kAAC_SSR=3]="kAAC_SSR",e[e.kAAC_LTP=4]="kAAC_LTP",e[e.kAAC_SBR=5]="kAAC_SBR",e[e.kAAC_Scalable=6]="kAAC_Scalable",e[e.kLayer1=32]="kLayer1",e[e.kLayer2=33]="kLayer2",e[e.kLayer3=34]="kLayer3"}(A||(A={})),function(e){e[e.k96000Hz=0]="k96000Hz",e[e.k88200Hz=1]="k88200Hz",e[e.k64000Hz=2]="k64000Hz",e[e.k48000Hz=3]="k48000Hz",e[e.k44100Hz=4]="k44100Hz",e[e.k32000Hz=5]="k32000Hz",e[e.k24000Hz=6]="k24000Hz",e[e.k22050Hz=7]="k22050Hz",e[e.k16000Hz=8]="k16000Hz",e[e.k12000Hz=9]="k12000Hz",e[e.k11025Hz=10]="k11025Hz",e[e.k8000Hz=11]="k8000Hz",e[e.k7350Hz=12]="k7350Hz"}(C||(C={}));var x,R=[96e3,88200,64e3,48e3,44100,32e3,24e3,22050,16e3,12e3,11025,8e3,7350],D=function(){},O=function(){function e(e){this.TAG="AACADTSParser",this.data_=e,this.current_syncword_offset_=this.findNextSyncwordOffset(0),this.eof_flag_&&a.a.e(this.TAG,"Could not found ADTS syncword until payload end")}return e.prototype.findNextSyncwordOffset=function(e){for(var t=e,i=this.data_;;){if(t+7>=i.byteLength)return this.eof_flag_=!0,i.byteLength;if(4095==(i[t+0]<<8|i[t+1])>>>4)return t;t++}},e.prototype.readNextAACFrame=function(){for(var e=this.data_,t=null;null==t&&!this.eof_flag_;){var i=this.current_syncword_offset_,n=(8&e[i+1])>>>3,r=(6&e[i+1])>>>1,a=1&e[i+1],s=(192&e[i+2])>>>6,o=(60&e[i+2])>>>2,u=(1&e[i+2])<<2|(192&e[i+3])>>>6,l=(3&e[i+3])<<11|e[i+4]<<3|(224&e[i+5])>>>5;if(e[i+6],i+l>this.data_.byteLength){this.eof_flag_=!0,this.has_last_incomplete_data=!0;break}var h=1===a?7:9,d=l-h;i+=h;var c=this.findNextSyncwordOffset(i+d);if(this.current_syncword_offset_=c,(0===n||1===n)&&0===r){var f=e.subarray(i,i+d);(t=new D).audio_object_type=s+1,t.sampling_freq_index=o,t.sampling_frequency=R[o],t.channel_config=u,t.data=f}}return t},e.prototype.hasIncompleteData=function(){return this.has_last_incomplete_data},e.prototype.getIncompleteData=function(){return this.has_last_incomplete_data?this.data_.subarray(this.current_syncword_offset_):null},e}(),U=function(e){var t=null,i=e.audio_object_type,n=e.audio_object_type,r=e.sampling_freq_index,a=e.channel_config,s=0,o=navigator.userAgent.toLowerCase();-1!==o.indexOf("firefox")?r>=6?(n=5,t=new Array(4),s=r-3):(n=2,t=new Array(2),s=r):-1!==o.indexOf("android")?(n=2,t=new Array(2),s=r):(n=5,s=r,t=new Array(4),r>=6?s=r-3:1===a&&(n=2,t=new Array(2),s=r)),t[0]=n<<3,t[0]|=(15&r)>>>1,t[1]=(15&r)<<7,t[1]|=(15&a)<<3,5===n&&(t[1]|=(15&s)>>>1,t[2]=(1&s)<<7,t[2]|=8,t[3]=0),this.config=t,this.sampling_rate=R[r],this.channel_count=a,this.codec_mimetype="mp4a.40."+n,this.original_codec_mimetype="mp4a.40."+i},M=function(){},F=function(){},B=(x=function(e,t){return(x=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var i in t)t.hasOwnProperty(i)&&(e[i]=t[i])})(e,t)},function(e,t){function i(){this.constructor=e}x(e,t),e.prototype=null===t?Object.create(t):(i.prototype=t.prototype,new i)}),N=function(e){function t(t,i){var n=e.call(this)||this;return n.TAG="TSDemuxer",n.first_parse_=!0,n.media_info_=new o.a,n.timescale_=90,n.duration_=0,n.current_pmt_pid_=-1,n.program_pmt_map_={},n.pes_slice_queues_={},n.video_metadata_={sps:void 0,pps:void 0,sps_details:void 0},n.audio_metadata_={audio_object_type:void 0,sampling_freq_index:void 0,sampling_frequency:void 0,channel_config:void 0},n.aac_last_sample_pts_=void 0,n.aac_last_incomplete_data_=null,n.has_video_=!1,n.has_audio_=!1,n.video_init_segment_dispatched_=!1,n.audio_init_segment_dispatched_=!1,n.video_metadata_changed_=!1,n.audio_metadata_changed_=!1,n.video_track_={type:"video",id:1,sequenceNumber:0,samples:[],length:0},n.audio_track_={type:"audio",id:2,sequenceNumber:0,samples:[],length:0},n.ts_packet_size_=t.ts_packet_size,n.sync_offset_=t.sync_offset,n.config_=i,n}return B(t,e),t.prototype.destroy=function(){this.media_info_=null,this.pes_slice_queues_=null,this.video_metadata_=null,this.audio_metadata_=null,this.aac_last_incomplete_data_=null,this.video_track_=null,this.audio_track_=null,e.prototype.destroy.call(this)},t.probe=function(e){var t=new Uint8Array(e),i=-1,n=188;if(t.byteLength<=3*n)return a.a.e("TSDemuxer","Probe data "+t.byteLength+" bytes is too few for judging MPEG-TS stream format!"),{match:!1};for(;-1===i;){for(var r=Math.min(1e3,t.byteLength-3*n),s=0;s=4?(a.a.v("TSDemuxer","ts_packet_size = 192, m2ts mode"),i-=4):204===n&&a.a.v("TSDemuxer","ts_packet_size = 204, RS encoded MPEG2-TS stream"),{match:!0,consumed:0,ts_packet_size:n,sync_offset:i})},t.prototype.bindDataSource=function(e){return e.onDataArrival=this.parseChunks.bind(this),this},t.prototype.resetMediaInfo=function(){this.media_info_=new o.a},t.prototype.parseChunks=function(e,t){if(!(this.onError&&this.onMediaInfo&&this.onTrackMetadata&&this.onDataAvailable))throw new c.a("onError & onMediaInfo & onTrackMetadata & onDataAvailable callback must be specified");var i=0;for(this.first_parse_&&(this.first_parse_=!1,i=this.sync_offset_);i+this.ts_packet_size_<=e.byteLength;){var n=t+i;192===this.ts_packet_size_&&(i+=4);var r=new Uint8Array(e,i,188),s=r[0];if(71!==s){a.a.e(this.TAG,"sync_byte = "+s+", not 0x47");break}var o=(64&r[1])>>>6,u=(r[1],(31&r[1])<<8|r[2]),l=(48&r[3])>>>4,h=15&r[3],d={},f=4;if(2==l||3==l){var p=r[4];if(5+p===188){i+=188,204===this.ts_packet_size_&&(i+=16);continue}p>0&&(d=this.parseAdaptationField(e,i+4,1+p)),f=5+p}if(1==l||3==l)if(0===u||u===this.current_pmt_pid_){o&&(f+=1+r[f]);var m=188-f;0===u?this.parsePAT(e,i+f,m,{payload_unit_start_indicator:o,continuity_conunter:h}):this.parsePMT(e,i+f,m,{payload_unit_start_indicator:o,continuity_conunter:h})}else if(null!=this.pmt_&&null!=this.pmt_.pid_stream_type[u]){m=188-f;var _=this.pmt_.pid_stream_type[u];u!==this.pmt_.common_pids.h264&&u!==this.pmt_.common_pids.adts_aac&&!0!==this.pmt_.pes_private_data_pids[u]&&!0!==this.pmt_.timed_id3_pids[u]||this.handlePESSlice(e,i+f,m,{pid:u,stream_type:_,file_position:n,payload_unit_start_indicator:o,continuity_conunter:h,random_access_indicator:d.random_access_indicator})}i+=188,204===this.ts_packet_size_&&(i+=16)}return this.dispatchAudioVideoMediaSegment(),i},t.prototype.parseAdaptationField=function(e,t,i){var n=new Uint8Array(e,t,i),r=n[0];return r>0?r>183?(a.a.w(this.TAG,"Illegal adaptation_field_length: "+r),{}):{discontinuity_indicator:(128&n[1])>>>7,random_access_indicator:(64&n[1])>>>6,elementary_stream_priority_indicator:(32&n[1])>>>5}:{}},t.prototype.parsePAT=function(e,t,i,n){var r=new Uint8Array(e,t,i),s=r[0];if(0===s){var o=(15&r[1])<<8|r[2],u=(r[3],r[4],(62&r[5])>>>1),l=1&r[5],h=r[6],d=(r[7],null);if(1===l&&0===h)(d=new b).version_number=u;else if(null==(d=this.pat_))return;for(var c=o-5-4,f=-1,p=-1,m=8;m<8+c;m+=4){var _=r[m]<<8|r[m+1],g=(31&r[m+2])<<8|r[m+3];0===_?d.network_pid=g:(d.program_pmt_pid[_]=g,-1===f&&(f=_),-1===p&&(p=g))}1===l&&0===h&&(null==this.pat_&&a.a.v(this.TAG,"Parsed first PAT: "+JSON.stringify(d)),this.pat_=d,this.current_program_=f,this.current_pmt_pid_=p)}else a.a.e(this.TAG,"parsePAT: table_id "+s+" is not corresponded to PAT!")},t.prototype.parsePMT=function(e,t,i,n){var r=new Uint8Array(e,t,i),s=r[0];if(2===s){var o=(15&r[1])<<8|r[2],u=r[3]<<8|r[4],l=(62&r[5])>>>1,d=1&r[5],c=r[6],f=(r[7],null);if(1===d&&0===c)(f=new T).program_number=u,f.version_number=l,this.program_pmt_map_[u]=f;else if(null==(f=this.program_pmt_map_[u]))return;r[8],r[9];for(var p=(15&r[10])<<8|r[11],m=12+p,_=o-9-p-4,g=m;g0){var S=r.subarray(g+5,g+5+b);this.dispatchPESPrivateDataDescriptor(y,v,S)}}else v===h.kID3&&(f.timed_id3_pids[y]=!0);else f.common_pids.adts_aac=y;else f.common_pids.h264=y;g+=5+b}u===this.current_program_&&(null==this.pmt_&&a.a.v(this.TAG,"Parsed first PMT: "+JSON.stringify(f)),this.pmt_=f,f.common_pids.h264&&(this.has_video_=!0),f.common_pids.adts_aac&&(this.has_audio_=!0))}else a.a.e(this.TAG,"parsePMT: table_id "+s+" is not corresponded to PMT!")},t.prototype.handlePESSlice=function(e,t,i,n){var r=new Uint8Array(e,t,i),s=r[0]<<16|r[1]<<8|r[2],o=(r[3],r[4]<<8|r[5]);if(n.payload_unit_start_indicator){if(1!==s)return void a.a.e(this.TAG,"handlePESSlice: packet_start_code_prefix should be 1 but with value "+s);var u=this.pes_slice_queues_[n.pid];u&&(0===u.expected_length||u.expected_length===u.total_length?this.emitPESSlices(u,n):this.cleanPESSlices(u,n)),this.pes_slice_queues_[n.pid]=new w,this.pes_slice_queues_[n.pid].file_position=n.file_position,this.pes_slice_queues_[n.pid].random_access_indicator=n.random_access_indicator}if(null!=this.pes_slice_queues_[n.pid]){var l=this.pes_slice_queues_[n.pid];l.slices.push(r),n.payload_unit_start_indicator&&(l.expected_length=0===o?0:o+6),l.total_length+=r.byteLength,l.expected_length>0&&l.expected_length===l.total_length?this.emitPESSlices(l,n):l.expected_length>0&&l.expected_length>>6,o=t[8],u=void 0,l=void 0;2!==s&&3!==s||(u=536870912*(14&t[9])+4194304*(255&t[10])+16384*(254&t[11])+128*(255&t[12])+(254&t[13])/2,l=3===s?536870912*(14&t[14])+4194304*(255&t[15])+16384*(254&t[16])+128*(255&t[17])+(254&t[18])/2:u);var d=9+o,c=void 0;if(0!==r){if(r<3+o)return void a.a.v(this.TAG,"Malformed PES: PES_packet_length < 3 + PES_header_data_length");c=r-3-o}else c=t.byteLength-d;var f=t.subarray(d,d+c);switch(e.stream_type){case h.kMPEG1Audio:case h.kMPEG2Audio:break;case h.kPESPrivateData:this.parsePESPrivateDataPayload(f,u,l,e.pid,n);break;case h.kADTSAAC:this.parseAACPayload(f,u);break;case h.kID3:this.parseTimedID3MetadataPayload(f,u,l,e.pid,n);break;case h.kH264:this.parseH264Payload(f,u,l,e.file_position,e.random_access_indicator);break;case h.kH265:}}else 188!==n&&191!==n&&240!==n&&241!==n&&255!==n&&242!==n&&248!==n||e.stream_type!==h.kPESPrivateData||(d=6,c=void 0,c=0!==r?r:t.byteLength-d,f=t.subarray(d,d+c),this.parsePESPrivateDataPayload(f,void 0,void 0,e.pid,n));else a.a.e(this.TAG,"parsePES: packet_start_code_prefix should be 1 but with value "+i)},t.prototype.parseH264Payload=function(e,t,i,n,r){for(var s=new I(e),o=null,u=[],l=0,h=!1;null!=(o=s.readNextNaluPayload());){var d=new P(o);if(d.type===S.kSliceSPS){var c=_.parseSPS(o.data);this.video_init_segment_dispatched_?!0===this.detectVideoMetadataChange(d,c)&&(a.a.v(this.TAG,"H264: Critical h264 metadata has been changed, attempt to re-generate InitSegment"),this.video_metadata_changed_=!0,this.video_metadata_={sps:d,pps:void 0,sps_details:c}):(this.video_metadata_.sps=d,this.video_metadata_.sps_details=c)}else d.type===S.kSlicePPS?this.video_init_segment_dispatched_&&!this.video_metadata_changed_||(this.video_metadata_.pps=d,this.video_metadata_.sps&&this.video_metadata_.pps&&(this.video_metadata_changed_&&this.dispatchVideoMediaSegment(),this.dispatchVideoInitSegment())):(d.type===S.kSliceIDR||d.type===S.kSliceNonIDR&&1===r)&&(h=!0);this.video_init_segment_dispatched_&&(u.push(d),l+=d.data.byteLength)}var f=Math.floor(t/this.timescale_),p=Math.floor(i/this.timescale_);if(u.length){var m=this.video_track_,g={units:u,length:l,isKeyframe:h,dts:p,pts:f,cts:f-p,file_position:n};m.samples.push(g),m.length+=l}},t.prototype.detectVideoMetadataChange=function(e,t){if(t.codec_mimetype!==this.video_metadata_.sps_details.codec_mimetype)return a.a.v(this.TAG,"H264: Codec mimeType changed from "+this.video_metadata_.sps_details.codec_mimetype+" to "+t.codec_mimetype),!0;if(t.codec_size.width!==this.video_metadata_.sps_details.codec_size.width||t.codec_size.height!==this.video_metadata_.sps_details.codec_size.height){var i=this.video_metadata_.sps_details.codec_size,n=t.codec_size;return a.a.v(this.TAG,"H264: Coded Resolution changed from "+i.width+"x"+i.height+" to "+n.width+"x"+n.height),!0}return t.present_size.width!==this.video_metadata_.sps_details.present_size.width&&(a.a.v(this.TAG,"H264: Present resolution width changed from "+this.video_metadata_.sps_details.present_size.width+" to "+t.present_size.width),!0)},t.prototype.isInitSegmentDispatched=function(){return this.has_video_&&this.has_audio_?this.video_init_segment_dispatched_&&this.audio_init_segment_dispatched_:this.has_video_&&!this.has_audio_?this.video_init_segment_dispatched_:!(this.has_video_||!this.has_audio_)&&this.audio_init_segment_dispatched_},t.prototype.dispatchVideoInitSegment=function(){var e=this.video_metadata_.sps_details,t={type:"video"};t.id=this.video_track_.id,t.timescale=1e3,t.duration=this.duration_,t.codecWidth=e.codec_size.width,t.codecHeight=e.codec_size.height,t.presentWidth=e.present_size.width,t.presentHeight=e.present_size.height,t.profile=e.profile_string,t.level=e.level_string,t.bitDepth=e.bit_depth,t.chromaFormat=e.chroma_format,t.sarRatio=e.sar_ratio,t.frameRate=e.frame_rate;var i=t.frameRate.fps_den,n=t.frameRate.fps_num;t.refSampleDuration=i/n*1e3,t.codec=e.codec_mimetype;var r=this.video_metadata_.sps.data.subarray(4),s=this.video_metadata_.pps.data.subarray(4),o=new L(r,s,e);t.avcc=o.getData(),0==this.video_init_segment_dispatched_&&a.a.v(this.TAG,"Generated first AVCDecoderConfigurationRecord for mimeType: "+t.codec),this.onTrackMetadata("video",t),this.video_init_segment_dispatched_=!0,this.video_metadata_changed_=!1;var u=this.media_info_;u.hasVideo=!0,u.width=t.codecWidth,u.height=t.codecHeight,u.fps=t.frameRate.fps,u.profile=t.profile,u.level=t.level,u.refFrames=e.ref_frames,u.chromaFormat=e.chroma_format_string,u.sarNum=t.sarRatio.width,u.sarDen=t.sarRatio.height,u.videoCodec=t.codec,u.hasAudio&&u.audioCodec?u.mimeType='video/mp2t; codecs="'+u.videoCodec+","+u.audioCodec+'"':u.mimeType='video/mp2t; codecs="'+u.videoCodec+'"',u.isComplete()&&this.onMediaInfo(u)},t.prototype.dispatchVideoMediaSegment=function(){this.isInitSegmentDispatched()&&this.video_track_.length&&this.onDataAvailable(null,this.video_track_)},t.prototype.dispatchAudioMediaSegment=function(){this.isInitSegmentDispatched()&&this.audio_track_.length&&this.onDataAvailable(this.audio_track_,null)},t.prototype.dispatchAudioVideoMediaSegment=function(){this.isInitSegmentDispatched()&&(this.audio_track_.length||this.video_track_.length)&&this.onDataAvailable(this.audio_track_,this.video_track_)},t.prototype.parseAACPayload=function(e,t){if(!this.has_video_||this.video_init_segment_dispatched_){if(this.aac_last_incomplete_data_){var i=new Uint8Array(e.byteLength+this.aac_last_incomplete_data_.byteLength);i.set(this.aac_last_incomplete_data_,0),i.set(e,this.aac_last_incomplete_data_.byteLength),e=i}var n,r;if(null!=t)r=t/this.timescale_;else{if(null==this.aac_last_sample_pts_)return void a.a.w(this.TAG,"AAC: Unknown pts");n=1024/this.audio_metadata_.sampling_frequency*1e3,r=this.aac_last_sample_pts_+n}if(this.aac_last_incomplete_data_&&this.aac_last_sample_pts_){n=1024/this.audio_metadata_.sampling_frequency*1e3;var s=this.aac_last_sample_pts_+n;Math.abs(s-r)>1&&(a.a.w(this.TAG,"AAC: Detected pts overlapped, expected: "+s+"ms, PES pts: "+r+"ms"),r=s)}for(var o,u=new O(e),l=null,h=r;null!=(l=u.readNextAACFrame());){n=1024/l.sampling_frequency*1e3,0==this.audio_init_segment_dispatched_?(this.audio_metadata_.audio_object_type=l.audio_object_type,this.audio_metadata_.sampling_freq_index=l.sampling_freq_index,this.audio_metadata_.sampling_frequency=l.sampling_frequency,this.audio_metadata_.channel_config=l.channel_config,this.dispatchAudioInitSegment(l)):this.detectAudioMetadataChange(l)&&(this.dispatchAudioMediaSegment(),this.dispatchAudioInitSegment(l)),o=h;var d=Math.floor(h),c={unit:l.data,length:l.data.byteLength,pts:d,dts:d};this.audio_track_.samples.push(c),this.audio_track_.length+=l.data.byteLength,h+=n}u.hasIncompleteData()&&(this.aac_last_incomplete_data_=u.getIncompleteData()),o&&(this.aac_last_sample_pts_=o)}},t.prototype.detectAudioMetadataChange=function(e){return e.audio_object_type!==this.audio_metadata_.audio_object_type?(a.a.v(this.TAG,"AAC: AudioObjectType changed from "+this.audio_metadata_.audio_object_type+" to "+e.audio_object_type),!0):e.sampling_freq_index!==this.audio_metadata_.sampling_freq_index?(a.a.v(this.TAG,"AAC: SamplingFrequencyIndex changed from "+this.audio_metadata_.sampling_freq_index+" to "+e.sampling_freq_index),!0):e.channel_config!==this.audio_metadata_.channel_config&&(a.a.v(this.TAG,"AAC: Channel configuration changed from "+this.audio_metadata_.channel_config+" to "+e.channel_config),!0)},t.prototype.dispatchAudioInitSegment=function(e){var t=new U(e),i={type:"audio"};i.id=this.audio_track_.id,i.timescale=1e3,i.duration=this.duration_,i.audioSampleRate=t.sampling_rate,i.channelCount=t.channel_count,i.codec=t.codec_mimetype,i.originalCodec=t.original_codec_mimetype,i.config=t.config,i.refSampleDuration=1024/i.audioSampleRate*i.timescale,0==this.audio_init_segment_dispatched_&&a.a.v(this.TAG,"Generated first AudioSpecificConfig for mimeType: "+i.codec),this.onTrackMetadata("audio",i),this.audio_init_segment_dispatched_=!0,this.video_metadata_changed_=!1;var n=this.media_info_;n.hasAudio=!0,n.audioCodec=i.originalCodec,n.audioSampleRate=i.audioSampleRate,n.audioChannelCount=i.channelCount,n.hasVideo&&n.videoCodec?n.mimeType='video/mp2t; codecs="'+n.videoCodec+","+n.audioCodec+'"':n.mimeType='video/mp2t; codecs="'+n.audioCodec+'"',n.isComplete()&&this.onMediaInfo(n)},t.prototype.dispatchPESPrivateDataDescriptor=function(e,t,i){var n=new F;n.pid=e,n.stream_type=t,n.descriptor=i,this.onPESPrivateDataDescriptor&&this.onPESPrivateDataDescriptor(n)},t.prototype.parsePESPrivateDataPayload=function(e,t,i,n,r){var a=new M;if(a.pid=n,a.stream_id=r,a.len=e.byteLength,a.data=e,null!=t){var s=Math.floor(t/this.timescale_);a.pts=s}else a.nearest_pts=this.aac_last_sample_pts_;if(null!=i){var o=Math.floor(i/this.timescale_);a.dts=o}this.onPESPrivateData&&this.onPESPrivateData(a)},t.prototype.parseTimedID3MetadataPayload=function(e,t,i,n,r){var a=new M;if(a.pid=n,a.stream_id=r,a.len=e.byteLength,a.data=e,null!=t){var s=Math.floor(t/this.timescale_);a.pts=s}if(null!=i){var o=Math.floor(i/this.timescale_);a.dts=o}this.onTimedID3Metadata&&this.onTimedID3Metadata(a)},t}(y),j=function(){function e(){}return e.init=function(){for(var t in e.types={avc1:[],avcC:[],btrt:[],dinf:[],dref:[],esds:[],ftyp:[],hdlr:[],mdat:[],mdhd:[],mdia:[],mfhd:[],minf:[],moof:[],moov:[],mp4a:[],mvex:[],mvhd:[],sdtp:[],stbl:[],stco:[],stsc:[],stsd:[],stsz:[],stts:[],tfdt:[],tfhd:[],traf:[],trak:[],trun:[],trex:[],tkhd:[],vmhd:[],smhd:[],".mp3":[]},e.types)e.types.hasOwnProperty(t)&&(e.types[t]=[t.charCodeAt(0),t.charCodeAt(1),t.charCodeAt(2),t.charCodeAt(3)]);var i=e.constants={};i.FTYP=new Uint8Array([105,115,111,109,0,0,0,1,105,115,111,109,97,118,99,49]),i.STSD_PREFIX=new Uint8Array([0,0,0,0,0,0,0,1]),i.STTS=new Uint8Array([0,0,0,0,0,0,0,0]),i.STSC=i.STCO=i.STTS,i.STSZ=new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0]),i.HDLR_VIDEO=new Uint8Array([0,0,0,0,0,0,0,0,118,105,100,101,0,0,0,0,0,0,0,0,0,0,0,0,86,105,100,101,111,72,97,110,100,108,101,114,0]),i.HDLR_AUDIO=new Uint8Array([0,0,0,0,0,0,0,0,115,111,117,110,0,0,0,0,0,0,0,0,0,0,0,0,83,111,117,110,100,72,97,110,100,108,101,114,0]),i.DREF=new Uint8Array([0,0,0,0,0,0,0,1,0,0,0,12,117,114,108,32,0,0,0,1]),i.SMHD=new Uint8Array([0,0,0,0,0,0,0,0]),i.VMHD=new Uint8Array([0,0,0,1,0,0,0,0,0,0,0,0])},e.box=function(e){for(var t=8,i=null,n=Array.prototype.slice.call(arguments,1),r=n.length,a=0;a>>24&255,i[1]=t>>>16&255,i[2]=t>>>8&255,i[3]=255&t,i.set(e,4);var s=8;for(a=0;a>>24&255,t>>>16&255,t>>>8&255,255&t,i>>>24&255,i>>>16&255,i>>>8&255,255&i,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255]))},e.trak=function(t){return e.box(e.types.trak,e.tkhd(t),e.mdia(t))},e.tkhd=function(t){var i=t.id,n=t.duration,r=t.presentWidth,a=t.presentHeight;return e.box(e.types.tkhd,new Uint8Array([0,0,0,7,0,0,0,0,0,0,0,0,i>>>24&255,i>>>16&255,i>>>8&255,255&i,0,0,0,0,n>>>24&255,n>>>16&255,n>>>8&255,255&n,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,0,0,0,r>>>8&255,255&r,0,0,a>>>8&255,255&a,0,0]))},e.mdia=function(t){return e.box(e.types.mdia,e.mdhd(t),e.hdlr(t),e.minf(t))},e.mdhd=function(t){var i=t.timescale,n=t.duration;return e.box(e.types.mdhd,new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0,i>>>24&255,i>>>16&255,i>>>8&255,255&i,n>>>24&255,n>>>16&255,n>>>8&255,255&n,85,196,0,0]))},e.hdlr=function(t){var i;return i="audio"===t.type?e.constants.HDLR_AUDIO:e.constants.HDLR_VIDEO,e.box(e.types.hdlr,i)},e.minf=function(t){var i;return i="audio"===t.type?e.box(e.types.smhd,e.constants.SMHD):e.box(e.types.vmhd,e.constants.VMHD),e.box(e.types.minf,i,e.dinf(),e.stbl(t))},e.dinf=function(){return e.box(e.types.dinf,e.box(e.types.dref,e.constants.DREF))},e.stbl=function(t){return e.box(e.types.stbl,e.stsd(t),e.box(e.types.stts,e.constants.STTS),e.box(e.types.stsc,e.constants.STSC),e.box(e.types.stsz,e.constants.STSZ),e.box(e.types.stco,e.constants.STCO))},e.stsd=function(t){return"audio"===t.type?"mp3"===t.codec?e.box(e.types.stsd,e.constants.STSD_PREFIX,e.mp3(t)):e.box(e.types.stsd,e.constants.STSD_PREFIX,e.mp4a(t)):e.box(e.types.stsd,e.constants.STSD_PREFIX,e.avc1(t))},e.mp3=function(t){var i=t.channelCount,n=t.audioSampleRate,r=new Uint8Array([0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,i,0,16,0,0,0,0,n>>>8&255,255&n,0,0]);return e.box(e.types[".mp3"],r)},e.mp4a=function(t){var i=t.channelCount,n=t.audioSampleRate,r=new Uint8Array([0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,i,0,16,0,0,0,0,n>>>8&255,255&n,0,0]);return e.box(e.types.mp4a,r,e.esds(t))},e.esds=function(t){var i=t.config||[],n=i.length,r=new Uint8Array([0,0,0,0,3,23+n,0,1,0,4,15+n,64,21,0,0,0,0,0,0,0,0,0,0,0,5].concat([n]).concat(i).concat([6,1,2]));return e.box(e.types.esds,r)},e.avc1=function(t){var i=t.avcc,n=t.codecWidth,r=t.codecHeight,a=new Uint8Array([0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,n>>>8&255,255&n,r>>>8&255,255&r,0,72,0,0,0,72,0,0,0,0,0,0,0,1,10,120,113,113,47,102,108,118,46,106,115,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,24,255,255]);return e.box(e.types.avc1,a,e.box(e.types.avcC,i))},e.mvex=function(t){return e.box(e.types.mvex,e.trex(t))},e.trex=function(t){var i=t.id,n=new Uint8Array([0,0,0,0,i>>>24&255,i>>>16&255,i>>>8&255,255&i,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,1]);return e.box(e.types.trex,n)},e.moof=function(t,i){return e.box(e.types.moof,e.mfhd(t.sequenceNumber),e.traf(t,i))},e.mfhd=function(t){var i=new Uint8Array([0,0,0,0,t>>>24&255,t>>>16&255,t>>>8&255,255&t]);return e.box(e.types.mfhd,i)},e.traf=function(t,i){var n=t.id,r=e.box(e.types.tfhd,new Uint8Array([0,0,0,0,n>>>24&255,n>>>16&255,n>>>8&255,255&n])),a=e.box(e.types.tfdt,new Uint8Array([0,0,0,0,i>>>24&255,i>>>16&255,i>>>8&255,255&i])),s=e.sdtp(t),o=e.trun(t,s.byteLength+16+16+8+16+8+8);return e.box(e.types.traf,r,a,o,s)},e.sdtp=function(t){for(var i=t.samples||[],n=i.length,r=new Uint8Array(4+n),a=0;a>>24&255,r>>>16&255,r>>>8&255,255&r,i>>>24&255,i>>>16&255,i>>>8&255,255&i],0);for(var o=0;o>>24&255,u>>>16&255,u>>>8&255,255&u,l>>>24&255,l>>>16&255,l>>>8&255,255&l,h.isLeading<<2|h.dependsOn,h.isDependedOn<<6|h.hasRedundancy<<4|h.isNonSync,0,0,d>>>24&255,d>>>16&255,d>>>8&255,255&d],12+16*o)}return e.box(e.types.trun,s)},e.mdat=function(t){return e.box(e.types.mdat,t)},e}();j.init();var V=j,H=function(){function e(){}return e.getSilentFrame=function(e,t){if("mp4a.40.2"===e){if(1===t)return new Uint8Array([0,200,0,128,35,128]);if(2===t)return new Uint8Array([33,0,73,144,2,25,0,35,128]);if(3===t)return new Uint8Array([0,200,0,128,32,132,1,38,64,8,100,0,142]);if(4===t)return new Uint8Array([0,200,0,128,32,132,1,38,64,8,100,0,128,44,128,8,2,56]);if(5===t)return new Uint8Array([0,200,0,128,32,132,1,38,64,8,100,0,130,48,4,153,0,33,144,2,56]);if(6===t)return new Uint8Array([0,200,0,128,32,132,1,38,64,8,100,0,130,48,4,153,0,33,144,2,0,178,0,32,8,224])}else{if(1===t)return new Uint8Array([1,64,34,128,163,78,230,128,186,8,0,0,0,28,6,241,193,10,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,94]);if(2===t)return new Uint8Array([1,64,34,128,163,94,230,128,186,8,0,0,0,0,149,0,6,241,161,10,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,94]);if(3===t)return new Uint8Array([1,64,34,128,163,94,230,128,186,8,0,0,0,0,149,0,6,241,161,10,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,94])}return null},e}(),z=i(7),G=function(){function e(e){this.TAG="MP4Remuxer",this._config=e,this._isLive=!0===e.isLive,this._dtsBase=-1,this._dtsBaseInited=!1,this._audioDtsBase=1/0,this._videoDtsBase=1/0,this._audioNextDts=void 0,this._videoNextDts=void 0,this._audioStashedLastSample=null,this._videoStashedLastSample=null,this._audioMeta=null,this._videoMeta=null,this._audioSegmentInfoList=new z.c("audio"),this._videoSegmentInfoList=new z.c("video"),this._onInitSegment=null,this._onMediaSegment=null,this._forceFirstIDR=!(!s.a.chrome||!(s.a.version.major<50||50===s.a.version.major&&s.a.version.build<2661)),this._fillSilentAfterSeek=s.a.msedge||s.a.msie,this._mp3UseMpegAudio=!s.a.firefox,this._fillAudioTimestampGap=this._config.fixAudioTimestampGap}return e.prototype.destroy=function(){this._dtsBase=-1,this._dtsBaseInited=!1,this._audioMeta=null,this._videoMeta=null,this._audioSegmentInfoList.clear(),this._audioSegmentInfoList=null,this._videoSegmentInfoList.clear(),this._videoSegmentInfoList=null,this._onInitSegment=null,this._onMediaSegment=null},e.prototype.bindDataSource=function(e){return e.onDataAvailable=this.remux.bind(this),e.onTrackMetadata=this._onTrackMetadataReceived.bind(this),this},Object.defineProperty(e.prototype,"onInitSegment",{get:function(){return this._onInitSegment},set:function(e){this._onInitSegment=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onMediaSegment",{get:function(){return this._onMediaSegment},set:function(e){this._onMediaSegment=e},enumerable:!1,configurable:!0}),e.prototype.insertDiscontinuity=function(){this._audioNextDts=this._videoNextDts=void 0},e.prototype.seek=function(e){this._audioStashedLastSample=null,this._videoStashedLastSample=null,this._videoSegmentInfoList.clear(),this._audioSegmentInfoList.clear()},e.prototype.remux=function(e,t){if(!this._onMediaSegment)throw new c.a("MP4Remuxer: onMediaSegment callback must be specificed!");this._dtsBaseInited||this._calculateDtsBase(e,t),t&&this._remuxVideo(t),e&&this._remuxAudio(e)},e.prototype._onTrackMetadataReceived=function(e,t){var i=null,n="mp4",r=t.codec;if("audio"===e)this._audioMeta=t,"mp3"===t.codec&&this._mp3UseMpegAudio?(n="mpeg",r="",i=new Uint8Array):i=V.generateInitSegment(t);else{if("video"!==e)return;this._videoMeta=t,i=V.generateInitSegment(t)}if(!this._onInitSegment)throw new c.a("MP4Remuxer: onInitSegment callback must be specified!");this._onInitSegment(e,{type:e,data:i.buffer,codec:r,container:e+"/"+n,mediaDuration:t.duration})},e.prototype._calculateDtsBase=function(e,t){this._dtsBaseInited||(e&&e.samples&&e.samples.length&&(this._audioDtsBase=e.samples[0].dts),t&&t.samples&&t.samples.length&&(this._videoDtsBase=t.samples[0].dts),this._dtsBase=Math.min(this._audioDtsBase,this._videoDtsBase),this._dtsBaseInited=!0)},e.prototype.getTimestampBase=function(){if(this._dtsBaseInited)return this._dtsBase},e.prototype.flushStashedSamples=function(){var e=this._videoStashedLastSample,t=this._audioStashedLastSample,i={type:"video",id:1,sequenceNumber:0,samples:[],length:0};null!=e&&(i.samples.push(e),i.length=e.length);var n={type:"audio",id:2,sequenceNumber:0,samples:[],length:0};null!=t&&(n.samples.push(t),n.length=t.length),this._videoStashedLastSample=null,this._audioStashedLastSample=null,this._remuxVideo(i,!0),this._remuxAudio(n,!0)},e.prototype._remuxAudio=function(e,t){if(null!=this._audioMeta){var i,n=e,r=n.samples,o=void 0,u=-1,l=this._audioMeta.refSampleDuration,h="mp3"===this._audioMeta.codec&&this._mp3UseMpegAudio,d=this._dtsBaseInited&&void 0===this._audioNextDts,c=!1;if(r&&0!==r.length&&(1!==r.length||t)){var f=0,p=null,m=0;h?(f=0,m=n.length):(f=8,m=8+n.length);var _=null;if(r.length>1&&(m-=(_=r.pop()).length),null!=this._audioStashedLastSample){var g=this._audioStashedLastSample;this._audioStashedLastSample=null,r.unshift(g),m+=g.length}null!=_&&(this._audioStashedLastSample=_);var v=r[0].dts-this._dtsBase;if(this._audioNextDts)o=v-this._audioNextDts;else if(this._audioSegmentInfoList.isEmpty())o=0,this._fillSilentAfterSeek&&!this._videoSegmentInfoList.isEmpty()&&"mp3"!==this._audioMeta.originalCodec&&(c=!0);else{var y=this._audioSegmentInfoList.getLastSampleBefore(v);if(null!=y){var b=v-(y.originalDts+y.duration);b<=3&&(b=0),o=v-(y.dts+y.duration+b)}else o=0}if(c){var S=v-o,T=this._videoSegmentInfoList.getLastSegmentBefore(v);if(null!=T&&T.beginDts=3*l&&this._fillAudioTimestampGap&&!s.a.safari){I=!0;var D,O=Math.floor(o/l);a.a.w(this.TAG,"Large audio timestamp gap detected, may cause AV sync to drift. Silent frames will be generated to avoid unsync.\noriginalDts: "+P+" ms, curRefDts: "+R+" ms, dtsCorrection: "+Math.round(o)+" ms, generate: "+O+" frames"),E=Math.floor(R),x=Math.floor(R+l)-E,null==(D=H.getSilentFrame(this._audioMeta.originalCodec,this._audioMeta.channelCount))&&(a.a.w(this.TAG,"Unable to generate silent frame for "+this._audioMeta.originalCodec+" with "+this._audioMeta.channelCount+" channels, repeat last frame"),D=k),L=[];for(var U=0;U=1?A[A.length-1].duration:Math.floor(l),this._audioNextDts=E+x;-1===u&&(u=E),A.push({dts:E,pts:E,cts:0,unit:g.unit,size:g.unit.byteLength,duration:x,originalDts:P,flags:{isLeading:0,dependsOn:1,isDependedOn:0,hasRedundancy:0}}),I&&A.push.apply(A,L)}}if(0===A.length)return n.samples=[],void(n.length=0);for(h?p=new Uint8Array(m):((p=new Uint8Array(m))[0]=m>>>24&255,p[1]=m>>>16&255,p[2]=m>>>8&255,p[3]=255&m,p.set(V.types.mdat,4)),C=0;C1&&(d-=(c=a.pop()).length),null!=this._videoStashedLastSample){var f=this._videoStashedLastSample;this._videoStashedLastSample=null,a.unshift(f),d+=f.length}null!=c&&(this._videoStashedLastSample=c);var p=a[0].dts-this._dtsBase;if(this._videoNextDts)s=p-this._videoNextDts;else if(this._videoSegmentInfoList.isEmpty())s=0;else{var m=this._videoSegmentInfoList.getLastSampleBefore(p);if(null!=m){var _=p-(m.originalDts+m.duration);_<=3&&(_=0),s=p-(m.dts+m.duration+_)}else s=0}for(var g=new z.b,v=[],y=0;y=1?v[v.length-1].duration:Math.floor(this._videoMeta.refSampleDuration),S){var C=new z.d(T,w,A,f.dts,!0);C.fileposition=f.fileposition,g.appendSyncPoint(C)}v.push({dts:T,pts:w,cts:E,units:f.units,size:f.length,isKeyframe:S,duration:A,originalDts:b,flags:{isLeading:0,dependsOn:S?2:1,isDependedOn:S?1:0,hasRedundancy:0,isNonSync:S?0:1}})}for((h=new Uint8Array(d))[0]=d>>>24&255,h[1]=d>>>16&255,h[2]=d>>>8&255,h[3]=255&d,h.set(V.types.mdat,4),y=0;y0)this._demuxer.bindDataSource(this._ioctl),this._demuxer.timestampBase=this._mediaDataSource.segments[this._currentSegmentIndex].timestampBase,r=this._demuxer.parseChunks(e,t);else if((n=N.probe(e)).match){var s=this._demuxer=new N(n,this._config);this._remuxer||(this._remuxer=new G(this._config)),s.onError=this._onDemuxException.bind(this),s.onMediaInfo=this._onMediaInfo.bind(this),s.onMetaDataArrived=this._onMetaDataArrived.bind(this),s.onTimedID3Metadata=this._onTimedID3Metadata.bind(this),s.onPESPrivateDataDescriptor=this._onPESPrivateDataDescriptor.bind(this),s.onPESPrivateData=this._onPESPrivateData.bind(this),this._remuxer.bindDataSource(this._demuxer),this._demuxer.bindDataSource(this._ioctl),this._remuxer.onInitSegment=this._onRemuxerInitSegmentArrival.bind(this),this._remuxer.onMediaSegment=this._onRemuxerMediaSegmentArrival.bind(this),r=this._demuxer.parseChunks(e,t)}else if((n=v.probe(e)).match){this._demuxer=new v(n,this._config),this._remuxer||(this._remuxer=new G(this._config));var o=this._mediaDataSource;null==o.duration||isNaN(o.duration)||(this._demuxer.overridedDuration=o.duration),"boolean"==typeof o.hasAudio&&(this._demuxer.overridedHasAudio=o.hasAudio),"boolean"==typeof o.hasVideo&&(this._demuxer.overridedHasVideo=o.hasVideo),this._demuxer.timestampBase=o.segments[this._currentSegmentIndex].timestampBase,this._demuxer.onError=this._onDemuxException.bind(this),this._demuxer.onMediaInfo=this._onMediaInfo.bind(this),this._demuxer.onMetaDataArrived=this._onMetaDataArrived.bind(this),this._demuxer.onScriptDataArrived=this._onScriptDataArrived.bind(this),this._remuxer.bindDataSource(this._demuxer.bindDataSource(this._ioctl)),this._remuxer.onInitSegment=this._onRemuxerInitSegmentArrival.bind(this),this._remuxer.onMediaSegment=this._onRemuxerMediaSegmentArrival.bind(this),r=this._demuxer.parseChunks(e,t)}else n=null,a.a.e(this.TAG,"Non MPEG-TS/FLV, Unsupported media type!"),Promise.resolve().then((function(){i._internalAbort()})),this._emitter.emit(Y.a.DEMUX_ERROR,g.a.FORMAT_UNSUPPORTED,"Non MPEG-TS/FLV, Unsupported media type!"),r=0;return r},e.prototype._onMediaInfo=function(e){var t=this;null==this._mediaInfo&&(this._mediaInfo=Object.assign({},e),this._mediaInfo.keyframesIndex=null,this._mediaInfo.segments=[],this._mediaInfo.segmentCount=this._mediaDataSource.segments.length,Object.setPrototypeOf(this._mediaInfo,o.a.prototype));var i=Object.assign({},e);Object.setPrototypeOf(i,o.a.prototype),this._mediaInfo.segments[this._currentSegmentIndex]=i,this._reportSegmentMediaInfo(this._currentSegmentIndex),null!=this._pendingSeekTime&&Promise.resolve().then((function(){var e=t._pendingSeekTime;t._pendingSeekTime=null,t.seek(e)}))},e.prototype._onMetaDataArrived=function(e){this._emitter.emit(Y.a.METADATA_ARRIVED,e)},e.prototype._onScriptDataArrived=function(e){this._emitter.emit(Y.a.SCRIPTDATA_ARRIVED,e)},e.prototype._onTimedID3Metadata=function(e){var t=this._remuxer.getTimestampBase();null!=t&&(null!=e.pts&&(e.pts-=t),null!=e.dts&&(e.dts-=t),this._emitter.emit(Y.a.TIMED_ID3_METADATA_ARRIVED,e))},e.prototype._onPESPrivateDataDescriptor=function(e){this._emitter.emit(Y.a.PES_PRIVATE_DATA_DESCRIPTOR,e)},e.prototype._onPESPrivateData=function(e){var t=this._remuxer.getTimestampBase();null!=t&&(null!=e.pts&&(e.pts-=t),null!=e.nearest_pts&&(e.nearest_pts-=t),null!=e.dts&&(e.dts-=t),this._emitter.emit(Y.a.PES_PRIVATE_DATA_ARRIVED,e))},e.prototype._onIOSeeked=function(){this._remuxer.insertDiscontinuity()},e.prototype._onIOComplete=function(e){var t=e+1;t0&&i[0].originalDts===n&&(n=i[0].pts),this._emitter.emit(Y.a.RECOMMEND_SEEKPOINT,n)}},e.prototype._enableStatisticsReporter=function(){null==this._statisticsReporter&&(this._statisticsReporter=self.setInterval(this._reportStatisticsInfo.bind(this),this._config.statisticsInfoReportInterval))},e.prototype._disableStatisticsReporter=function(){this._statisticsReporter&&(self.clearInterval(this._statisticsReporter),this._statisticsReporter=null)},e.prototype._reportSegmentMediaInfo=function(e){var t=this._mediaInfo.segments[e],i=Object.assign({},t);i.duration=this._mediaInfo.duration,i.segmentCount=this._mediaInfo.segmentCount,delete i.segments,delete i.keyframesIndex,this._emitter.emit(Y.a.MEDIA_INFO,i)},e.prototype._reportStatisticsInfo=function(){var e={};e.url=this._ioctl.currentURL,e.hasRedirect=this._ioctl.hasRedirect,e.hasRedirect&&(e.redirectedURL=this._ioctl.currentRedirectedURL),e.speed=this._ioctl.currentSpeed,e.loaderType=this._ioctl.loaderType,e.currentSegmentIndex=this._currentSegmentIndex,e.totalSegmentCount=this._mediaDataSource.segments.length,this._emitter.emit(Y.a.STATISTICS_INFO,e)},e}();t.a=q},function(e,t,i){"use strict";var n,r=i(0),a=function(){function e(){this._firstCheckpoint=0,this._lastCheckpoint=0,this._intervalBytes=0,this._totalBytes=0,this._lastSecondBytes=0,self.performance&&self.performance.now?this._now=self.performance.now.bind(self.performance):this._now=Date.now}return e.prototype.reset=function(){this._firstCheckpoint=this._lastCheckpoint=0,this._totalBytes=this._intervalBytes=0,this._lastSecondBytes=0},e.prototype.addBytes=function(e){0===this._firstCheckpoint?(this._firstCheckpoint=this._now(),this._lastCheckpoint=this._firstCheckpoint,this._intervalBytes+=e,this._totalBytes+=e):this._now()-this._lastCheckpoint<1e3?(this._intervalBytes+=e,this._totalBytes+=e):(this._lastSecondBytes=this._intervalBytes,this._intervalBytes=e,this._totalBytes+=e,this._lastCheckpoint=this._now())},Object.defineProperty(e.prototype,"currentKBps",{get:function(){this.addBytes(0);var e=(this._now()-this._lastCheckpoint)/1e3;return 0==e&&(e=1),this._intervalBytes/e/1024},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"lastSecondKBps",{get:function(){return this.addBytes(0),0!==this._lastSecondBytes?this._lastSecondBytes/1024:this._now()-this._lastCheckpoint>=500?this.currentKBps:0},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"averageKBps",{get:function(){var e=(this._now()-this._firstCheckpoint)/1e3;return this._totalBytes/e/1024},enumerable:!1,configurable:!0}),e}(),s=i(2),o=i(4),u=i(3),l=(n=function(e,t){return(n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var i in t)t.hasOwnProperty(i)&&(e[i]=t[i])})(e,t)},function(e,t){function i(){this.constructor=e}n(e,t),e.prototype=null===t?Object.create(t):(i.prototype=t.prototype,new i)}),h=function(e){function t(t,i){var n=e.call(this,"fetch-stream-loader")||this;return n.TAG="FetchStreamLoader",n._seekHandler=t,n._config=i,n._needStash=!0,n._requestAbort=!1,n._abortController=null,n._contentLength=null,n._receivedLength=0,n}return l(t,e),t.isSupported=function(){try{var e=o.a.msedge&&o.a.version.minor>=15048,t=!o.a.msedge||e;return self.fetch&&self.ReadableStream&&t}catch(e){return!1}},t.prototype.destroy=function(){this.isWorking()&&this.abort(),e.prototype.destroy.call(this)},t.prototype.open=function(e,t){var i=this;this._dataSource=e,this._range=t;var n=e.url;this._config.reuseRedirectedURL&&null!=e.redirectedURL&&(n=e.redirectedURL);var r=this._seekHandler.getConfig(n,t),a=new self.Headers;if("object"==typeof r.headers){var o=r.headers;for(var l in o)o.hasOwnProperty(l)&&a.append(l,o[l])}var h={method:"GET",headers:a,mode:"cors",cache:"default",referrerPolicy:"no-referrer-when-downgrade"};if("object"==typeof this._config.headers)for(var l in this._config.headers)a.append(l,this._config.headers[l]);!1===e.cors&&(h.mode="same-origin"),e.withCredentials&&(h.credentials="include"),e.referrerPolicy&&(h.referrerPolicy=e.referrerPolicy),self.AbortController&&(this._abortController=new self.AbortController,h.signal=this._abortController.signal),this._status=s.c.kConnecting,self.fetch(r.url,h).then((function(e){if(i._requestAbort)return i._status=s.c.kIdle,void e.body.cancel();if(e.ok&&e.status>=200&&e.status<=299){if(e.url!==r.url&&i._onURLRedirect){var t=i._seekHandler.removeURLParameters(e.url);i._onURLRedirect(t)}var n=e.headers.get("Content-Length");return null!=n&&(i._contentLength=parseInt(n),0!==i._contentLength&&i._onContentLengthKnown&&i._onContentLengthKnown(i._contentLength)),i._pump.call(i,e.body.getReader())}if(i._status=s.c.kError,!i._onError)throw new u.d("FetchStreamLoader: Http code invalid, "+e.status+" "+e.statusText);i._onError(s.b.HTTP_STATUS_CODE_INVALID,{code:e.status,msg:e.statusText})})).catch((function(e){if(!i._abortController||!i._abortController.signal.aborted){if(i._status=s.c.kError,!i._onError)throw e;i._onError(s.b.EXCEPTION,{code:-1,msg:e.message})}}))},t.prototype.abort=function(){if(this._requestAbort=!0,(this._status!==s.c.kBuffering||!o.a.chrome)&&this._abortController)try{this._abortController.abort()}catch(e){}},t.prototype._pump=function(e){var t=this;return e.read().then((function(i){if(i.done)if(null!==t._contentLength&&t._receivedLength299)){if(this._status=s.c.kError,!this._onError)throw new u.d("MozChunkedLoader: Http code invalid, "+t.status+" "+t.statusText);this._onError(s.b.HTTP_STATUS_CODE_INVALID,{code:t.status,msg:t.statusText})}else this._status=s.c.kBuffering}},t.prototype._onProgress=function(e){if(this._status!==s.c.kError){null===this._contentLength&&null!==e.total&&0!==e.total&&(this._contentLength=e.total,this._onContentLengthKnown&&this._onContentLengthKnown(this._contentLength));var t=e.target.response,i=this._range.from+this._receivedLength;this._receivedLength+=t.byteLength,this._onDataArrival&&this._onDataArrival(t,i,this._receivedLength)}},t.prototype._onLoadEnd=function(e){!0!==this._requestAbort?this._status!==s.c.kError&&(this._status=s.c.kComplete,this._onComplete&&this._onComplete(this._range.from,this._range.from+this._receivedLength-1)):this._requestAbort=!1},t.prototype._onXhrError=function(e){this._status=s.c.kError;var t=0,i=null;if(this._contentLength&&e.loaded=this._contentLength&&(i=this._range.from+this._contentLength-1),this._currentRequestRange={from:t,to:i},this._internalOpen(this._dataSource,this._currentRequestRange)},t.prototype._internalOpen=function(e,t){this._lastTimeLoaded=0;var i=e.url;this._config.reuseRedirectedURL&&(null!=this._currentRedirectedURL?i=this._currentRedirectedURL:null!=e.redirectedURL&&(i=e.redirectedURL));var n=this._seekHandler.getConfig(i,t);this._currentRequestURL=n.url;var r=this._xhr=new XMLHttpRequest;if(r.open("GET",n.url,!0),r.responseType="arraybuffer",r.onreadystatechange=this._onReadyStateChange.bind(this),r.onprogress=this._onProgress.bind(this),r.onload=this._onLoad.bind(this),r.onerror=this._onXhrError.bind(this),e.withCredentials&&(r.withCredentials=!0),"object"==typeof n.headers){var a=n.headers;for(var s in a)a.hasOwnProperty(s)&&r.setRequestHeader(s,a[s])}if("object"==typeof this._config.headers)for(var s in a=this._config.headers)a.hasOwnProperty(s)&&r.setRequestHeader(s,a[s]);r.send()},t.prototype.abort=function(){this._requestAbort=!0,this._internalAbort(),this._status=s.c.kComplete},t.prototype._internalAbort=function(){this._xhr&&(this._xhr.onreadystatechange=null,this._xhr.onprogress=null,this._xhr.onload=null,this._xhr.onerror=null,this._xhr.abort(),this._xhr=null)},t.prototype._onReadyStateChange=function(e){var t=e.target;if(2===t.readyState){if(null!=t.responseURL){var i=this._seekHandler.removeURLParameters(t.responseURL);t.responseURL!==this._currentRequestURL&&i!==this._currentRedirectedURL&&(this._currentRedirectedURL=i,this._onURLRedirect&&this._onURLRedirect(i))}if(t.status>=200&&t.status<=299){if(this._waitForTotalLength)return;this._status=s.c.kBuffering}else{if(this._status=s.c.kError,!this._onError)throw new u.d("RangeLoader: Http code invalid, "+t.status+" "+t.statusText);this._onError(s.b.HTTP_STATUS_CODE_INVALID,{code:t.status,msg:t.statusText})}}},t.prototype._onProgress=function(e){if(this._status!==s.c.kError){if(null===this._contentLength){var t=!1;if(this._waitForTotalLength){this._waitForTotalLength=!1,this._totalLengthReceived=!0,t=!0;var i=e.total;this._internalAbort(),null!=i&0!==i&&(this._totalLength=i)}if(-1===this._range.to?this._contentLength=this._totalLength-this._range.from:this._contentLength=this._range.to-this._range.from+1,t)return void this._openSubRange();this._onContentLengthKnown&&this._onContentLengthKnown(this._contentLength)}var n=e.loaded-this._lastTimeLoaded;this._lastTimeLoaded=e.loaded,this._speedSampler.addBytes(n)}},t.prototype._normalizeSpeed=function(e){var t=this._chunkSizeKBList,i=t.length-1,n=0,r=0,a=i;if(e=t[n]&&e=3&&(t=this._speedSampler.currentKBps)),0!==t){var i=this._normalizeSpeed(t);this._currentSpeedNormalized!==i&&(this._currentSpeedNormalized=i,this._currentChunkSizeKB=i)}var n=e.target.response,r=this._range.from+this._receivedLength;this._receivedLength+=n.byteLength;var a=!1;null!=this._contentLength&&this._receivedLength0&&this._receivedLength0)for(var a=i.split("&"),s=0;s0;o[0]!==this._startName&&o[0]!==this._endName&&(u&&(r+="&"),r+=a[s])}return 0===r.length?t:t+"?"+r},e}(),y=function(){function e(e,t,i){this.TAG="IOController",this._config=t,this._extraData=i,this._stashInitialSize=65536,null!=t.stashInitialSize&&t.stashInitialSize>0&&(this._stashInitialSize=t.stashInitialSize),this._stashUsed=0,this._stashSize=this._stashInitialSize,this._bufferSize=3145728,this._stashBuffer=new ArrayBuffer(this._bufferSize),this._stashByteStart=0,this._enableStash=!0,!1===t.enableStashBuffer&&(this._enableStash=!1),this._loader=null,this._loaderClass=null,this._seekHandler=null,this._dataSource=e,this._isWebSocketURL=/wss?:\/\/(.+?)/.test(e.url),this._refTotalLength=e.filesize?e.filesize:null,this._totalLength=this._refTotalLength,this._fullRequestFlag=!1,this._currentRange=null,this._redirectedURL=null,this._speedNormalized=0,this._speedSampler=new a,this._speedNormalizeList=[32,64,96,128,192,256,384,512,768,1024,1536,2048,3072,4096],this._isEarlyEofReconnecting=!1,this._paused=!1,this._resumeFrom=0,this._onDataArrival=null,this._onSeeked=null,this._onError=null,this._onComplete=null,this._onRedirect=null,this._onRecoveredEarlyEof=null,this._selectSeekHandler(),this._selectLoader(),this._createLoader()}return e.prototype.destroy=function(){this._loader.isWorking()&&this._loader.abort(),this._loader.destroy(),this._loader=null,this._loaderClass=null,this._dataSource=null,this._stashBuffer=null,this._stashUsed=this._stashSize=this._bufferSize=this._stashByteStart=0,this._currentRange=null,this._speedSampler=null,this._isEarlyEofReconnecting=!1,this._onDataArrival=null,this._onSeeked=null,this._onError=null,this._onComplete=null,this._onRedirect=null,this._onRecoveredEarlyEof=null,this._extraData=null},e.prototype.isWorking=function(){return this._loader&&this._loader.isWorking()&&!this._paused},e.prototype.isPaused=function(){return this._paused},Object.defineProperty(e.prototype,"status",{get:function(){return this._loader.status},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"extraData",{get:function(){return this._extraData},set:function(e){this._extraData=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onDataArrival",{get:function(){return this._onDataArrival},set:function(e){this._onDataArrival=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onSeeked",{get:function(){return this._onSeeked},set:function(e){this._onSeeked=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onError",{get:function(){return this._onError},set:function(e){this._onError=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onComplete",{get:function(){return this._onComplete},set:function(e){this._onComplete=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onRedirect",{get:function(){return this._onRedirect},set:function(e){this._onRedirect=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onRecoveredEarlyEof",{get:function(){return this._onRecoveredEarlyEof},set:function(e){this._onRecoveredEarlyEof=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"currentURL",{get:function(){return this._dataSource.url},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"hasRedirect",{get:function(){return null!=this._redirectedURL||null!=this._dataSource.redirectedURL},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"currentRedirectedURL",{get:function(){return this._redirectedURL||this._dataSource.redirectedURL},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"currentSpeed",{get:function(){return this._loaderClass===p?this._loader.currentSpeed:this._speedSampler.lastSecondKBps},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"loaderType",{get:function(){return this._loader.type},enumerable:!1,configurable:!0}),e.prototype._selectSeekHandler=function(){var e=this._config;if("range"===e.seekType)this._seekHandler=new g(this._config.rangeLoadZeroStart);else if("param"===e.seekType){var t=e.seekParamStart||"bstart",i=e.seekParamEnd||"bend";this._seekHandler=new v(t,i)}else{if("custom"!==e.seekType)throw new u.b("Invalid seekType in config: "+e.seekType);if("function"!=typeof e.customSeekHandler)throw new u.b("Custom seekType specified in config but invalid customSeekHandler!");this._seekHandler=new e.customSeekHandler}},e.prototype._selectLoader=function(){if(null!=this._config.customLoader)this._loaderClass=this._config.customLoader;else if(this._isWebSocketURL)this._loaderClass=_;else if(h.isSupported())this._loaderClass=h;else if(c.isSupported())this._loaderClass=c;else{if(!p.isSupported())throw new u.d("Your browser doesn't support xhr with arraybuffer responseType!");this._loaderClass=p}},e.prototype._createLoader=function(){this._loader=new this._loaderClass(this._seekHandler,this._config),!1===this._loader.needStashBuffer&&(this._enableStash=!1),this._loader.onContentLengthKnown=this._onContentLengthKnown.bind(this),this._loader.onURLRedirect=this._onURLRedirect.bind(this),this._loader.onDataArrival=this._onLoaderChunkArrival.bind(this),this._loader.onComplete=this._onLoaderComplete.bind(this),this._loader.onError=this._onLoaderError.bind(this)},e.prototype.open=function(e){this._currentRange={from:0,to:-1},e&&(this._currentRange.from=e),this._speedSampler.reset(),e||(this._fullRequestFlag=!0),this._loader.open(this._dataSource,Object.assign({},this._currentRange))},e.prototype.abort=function(){this._loader.abort(),this._paused&&(this._paused=!1,this._resumeFrom=0)},e.prototype.pause=function(){this.isWorking()&&(this._loader.abort(),0!==this._stashUsed?(this._resumeFrom=this._stashByteStart,this._currentRange.to=this._stashByteStart-1):this._resumeFrom=this._currentRange.to+1,this._stashUsed=0,this._stashByteStart=0,this._paused=!0)},e.prototype.resume=function(){if(this._paused){this._paused=!1;var e=this._resumeFrom;this._resumeFrom=0,this._internalSeek(e,!0)}},e.prototype.seek=function(e){this._paused=!1,this._stashUsed=0,this._stashByteStart=0,this._internalSeek(e,!0)},e.prototype._internalSeek=function(e,t){this._loader.isWorking()&&this._loader.abort(),this._flushStashBuffer(t),this._loader.destroy(),this._loader=null;var i={from:e,to:-1};this._currentRange={from:i.from,to:-1},this._speedSampler.reset(),this._stashSize=this._stashInitialSize,this._createLoader(),this._loader.open(this._dataSource,i),this._onSeeked&&this._onSeeked()},e.prototype.updateUrl=function(e){if(!e||"string"!=typeof e||0===e.length)throw new u.b("Url must be a non-empty string!");this._dataSource.url=e},e.prototype._expandBuffer=function(e){for(var t=this._stashSize;t+10485760){var n=new Uint8Array(this._stashBuffer,0,this._stashUsed);new Uint8Array(i,0,t).set(n,0)}this._stashBuffer=i,this._bufferSize=t}},e.prototype._normalizeSpeed=function(e){var t=this._speedNormalizeList,i=t.length-1,n=0,r=0,a=i;if(e=t[n]&&e=512&&e<=1024?Math.floor(1.5*e):2*e)>8192&&(t=8192);var i=1024*t+1048576;this._bufferSize0){var a=this._stashBuffer.slice(0,this._stashUsed);(l=this._dispatchChunks(a,this._stashByteStart))0&&(h=new Uint8Array(a,l),o.set(h,0),this._stashUsed=h.byteLength,this._stashByteStart+=l):(this._stashUsed=0,this._stashByteStart+=l),this._stashUsed+e.byteLength>this._bufferSize&&(this._expandBuffer(this._stashUsed+e.byteLength),o=new Uint8Array(this._stashBuffer,0,this._bufferSize)),o.set(new Uint8Array(e),this._stashUsed),this._stashUsed+=e.byteLength}else(l=this._dispatchChunks(e,t))this._bufferSize&&(this._expandBuffer(s),o=new Uint8Array(this._stashBuffer,0,this._bufferSize)),o.set(new Uint8Array(e,l),0),this._stashUsed+=s,this._stashByteStart=t+l);else if(0===this._stashUsed){var s;(l=this._dispatchChunks(e,t))this._bufferSize&&this._expandBuffer(s),(o=new Uint8Array(this._stashBuffer,0,this._bufferSize)).set(new Uint8Array(e,l),0),this._stashUsed+=s,this._stashByteStart=t+l)}else{var o,l;if(this._stashUsed+e.byteLength>this._bufferSize&&this._expandBuffer(this._stashUsed+e.byteLength),(o=new Uint8Array(this._stashBuffer,0,this._bufferSize)).set(new Uint8Array(e),this._stashUsed),this._stashUsed+=e.byteLength,(l=this._dispatchChunks(this._stashBuffer.slice(0,this._stashUsed),this._stashByteStart))0){var h=new Uint8Array(this._stashBuffer,l);o.set(h,0)}this._stashUsed-=l,this._stashByteStart+=l}}},e.prototype._flushStashBuffer=function(e){if(this._stashUsed>0){var t=this._stashBuffer.slice(0,this._stashUsed),i=this._dispatchChunks(t,this._stashByteStart),n=t.byteLength-i;if(i0){var a=new Uint8Array(this._stashBuffer,0,this._bufferSize),s=new Uint8Array(t,i);a.set(s,0),this._stashUsed=s.byteLength,this._stashByteStart+=i}return 0}r.a.w(this.TAG,n+" bytes unconsumed data remain when flush buffer, dropped")}return this._stashUsed=0,this._stashByteStart=0,n}return 0},e.prototype._onLoaderComplete=function(e,t){this._flushStashBuffer(!0),this._onComplete&&this._onComplete(this._extraData)},e.prototype._onLoaderError=function(e,t){switch(r.a.e(this.TAG,"Loader error, code = "+t.code+", msg = "+t.msg),this._flushStashBuffer(!1),this._isEarlyEofReconnecting&&(this._isEarlyEofReconnecting=!1,e=s.b.UNRECOVERABLE_EARLY_EOF),e){case s.b.EARLY_EOF:if(!this._config.isLive&&this._totalLength){var i=this._currentRange.to+1;return void(i0}),!1)}e.exports=function(e,t){t=t||{};var r={main:i.m},o=t.all?{main:Object.keys(r.main)}:function(e,t){for(var i={main:[t]},n={main:[]},r={main:{}};s(i);)for(var o=Object.keys(i),u=0;u1)for(var i=1;i0&&(n+=";codecs="+i.codec);var r=!1;if(d.a.v(this.TAG,"Received Initialization Segment, mimeType: "+n),this._lastInitSegments[i.type]=i,n!==this._mimeTypes[i.type]){if(this._mimeTypes[i.type])d.a.v(this.TAG,"Notice: "+i.type+" mimeType changed, origin: "+this._mimeTypes[i.type]+", target: "+n);else{r=!0;try{var a=this._sourceBuffers[i.type]=this._mediaSource.addSourceBuffer(n);a.addEventListener("error",this.e.onSourceBufferError),a.addEventListener("updateend",this.e.onSourceBufferUpdateEnd)}catch(e){return d.a.e(this.TAG,e.message),void this._emitter.emit(S,{code:e.code,msg:e.message})}}this._mimeTypes[i.type]=n}t||this._pendingSegments[i.type].push(i),r||this._sourceBuffers[i.type]&&!this._sourceBuffers[i.type].updating&&this._doAppendSegments(),c.a.safari&&"audio/mpeg"===i.container&&i.mediaDuration>0&&(this._requireSetMediaDuration=!0,this._pendingMediaDuration=i.mediaDuration/1e3,this._updateMediaSourceDuration())},e.prototype.appendMediaSegment=function(e){var t=e;this._pendingSegments[t.type].push(t),this._config.autoCleanupSourceBuffer&&this._needCleanupSourceBuffer()&&this._doCleanupSourceBuffer();var i=this._sourceBuffers[t.type];!i||i.updating||this._hasPendingRemoveRanges()||this._doAppendSegments()},e.prototype.seek=function(e){for(var t in this._sourceBuffers)if(this._sourceBuffers[t]){var i=this._sourceBuffers[t];if("open"===this._mediaSource.readyState)try{i.abort()}catch(e){d.a.e(this.TAG,e.message)}this._idrList.clear();var n=this._pendingSegments[t];if(n.splice(0,n.length),"closed"!==this._mediaSource.readyState){for(var r=0;r=1&&e-n.start(0)>=this._config.autoCleanupMaxBackwardDuration)return!0}}return!1},e.prototype._doCleanupSourceBuffer=function(){var e=this._mediaElement.currentTime;for(var t in this._sourceBuffers){var i=this._sourceBuffers[t];if(i){for(var n=i.buffered,r=!1,a=0;a=this._config.autoCleanupMaxBackwardDuration){r=!0;var u=e-this._config.autoCleanupMinBackwardDuration;this._pendingRemoveRanges[t].push({start:s,end:u})}}else o0&&(isNaN(t)||i>t)&&(d.a.v(this.TAG,"Update MediaSource duration from "+t+" to "+i),this._mediaSource.duration=i),this._requireSetMediaDuration=!1,this._pendingMediaDuration=0}},e.prototype._doRemoveRanges=function(){for(var e in this._pendingRemoveRanges)if(this._sourceBuffers[e]&&!this._sourceBuffers[e].updating)for(var t=this._sourceBuffers[e],i=this._pendingRemoveRanges[e];i.length&&!t.updating;){var n=i.shift();t.remove(n.start,n.end)}},e.prototype._doAppendSegments=function(){var e=this._pendingSegments;for(var t in e)if(this._sourceBuffers[t]&&!this._sourceBuffers[t].updating&&e[t].length>0){var i=e[t].shift();if(i.timestampOffset){var n=this._sourceBuffers[t].timestampOffset,r=i.timestampOffset/1e3;Math.abs(n-r)>.1&&(d.a.v(this.TAG,"Update MPEG audio timestampOffset from "+n+" to "+r),this._sourceBuffers[t].timestampOffset=r),delete i.timestampOffset}if(!i.data||0===i.data.byteLength)continue;try{this._sourceBuffers[t].appendBuffer(i.data),this._isBufferFull=!1,"video"===t&&i.hasOwnProperty("info")&&this._idrList.appendArray(i.info.syncPoints)}catch(e){this._pendingSegments[t].unshift(i),22===e.code?(this._isBufferFull||this._emitter.emit(w),this._isBufferFull=!0):(d.a.e(this.TAG,e.message),this._emitter.emit(S,{code:e.code,msg:e.message}))}}},e.prototype._onSourceOpen=function(){if(d.a.v(this.TAG,"MediaSource onSourceOpen"),this._mediaSource.removeEventListener("sourceopen",this.e.onSourceOpen),this._pendingSourceBufferInit.length>0)for(var e=this._pendingSourceBufferInit;e.length;){var t=e.shift();this.appendInitSegment(t,!0)}this._hasPendingSegments()&&this._doAppendSegments(),this._emitter.emit(T)},e.prototype._onSourceEnded=function(){d.a.v(this.TAG,"MediaSource onSourceEnded")},e.prototype._onSourceClose=function(){d.a.v(this.TAG,"MediaSource onSourceClose"),this._mediaSource&&null!=this.e&&(this._mediaSource.removeEventListener("sourceopen",this.e.onSourceOpen),this._mediaSource.removeEventListener("sourceended",this.e.onSourceEnded),this._mediaSource.removeEventListener("sourceclose",this.e.onSourceClose))},e.prototype._hasPendingSegments=function(){var e=this._pendingSegments;return e.video.length>0||e.audio.length>0},e.prototype._hasPendingRemoveRanges=function(){var e=this._pendingRemoveRanges;return e.video.length>0||e.audio.length>0},e.prototype._onSourceBufferUpdateEnd=function(){this._requireSetMediaDuration?this._updateMediaSourceDuration():this._hasPendingRemoveRanges()?this._doRemoveRanges():this._hasPendingSegments()?this._doAppendSegments():this._hasPendingEos&&this.endOfStream(),this._emitter.emit(E)},e.prototype._onSourceBufferError=function(e){d.a.e(this.TAG,"SourceBuffer Error: "+e)},e}(),P=i(5),I={NETWORK_ERROR:"NetworkError",MEDIA_ERROR:"MediaError",OTHER_ERROR:"OtherError"},L={NETWORK_EXCEPTION:u.b.EXCEPTION,NETWORK_STATUS_CODE_INVALID:u.b.HTTP_STATUS_CODE_INVALID,NETWORK_TIMEOUT:u.b.CONNECTING_TIMEOUT,NETWORK_UNRECOVERABLE_EARLY_EOF:u.b.UNRECOVERABLE_EARLY_EOF,MEDIA_MSE_ERROR:"MediaMSEError",MEDIA_FORMAT_ERROR:P.a.FORMAT_ERROR,MEDIA_FORMAT_UNSUPPORTED:P.a.FORMAT_UNSUPPORTED,MEDIA_CODEC_UNSUPPORTED:P.a.CODEC_UNSUPPORTED},x=function(){function e(e,t){this.TAG="MSEPlayer",this._type="MSEPlayer",this._emitter=new h.a,this._config=s(),"object"==typeof t&&Object.assign(this._config,t);var i=e.type.toLowerCase();if("mse"!==i&&"mpegts"!==i&&"m2ts"!==i&&"flv"!==i)throw new C.b("MSEPlayer requires an mpegts/m2ts/flv MediaDataSource input!");!0===e.isLive&&(this._config.isLive=!0),this.e={onvLoadedMetadata:this._onvLoadedMetadata.bind(this),onvSeeking:this._onvSeeking.bind(this),onvCanPlay:this._onvCanPlay.bind(this),onvStalled:this._onvStalled.bind(this),onvProgress:this._onvProgress.bind(this)},self.performance&&self.performance.now?this._now=self.performance.now.bind(self.performance):this._now=Date.now,this._pendingSeekTime=null,this._requestSetTime=!1,this._seekpointRecord=null,this._progressChecker=null,this._mediaDataSource=e,this._mediaElement=null,this._msectl=null,this._transmuxer=null,this._mseSourceOpened=!1,this._hasPendingLoad=!1,this._receivedCanPlay=!1,this._mediaInfo=null,this._statisticsInfo=null;var n=c.a.chrome&&(c.a.version.major<50||50===c.a.version.major&&c.a.version.build<2661);this._alwaysSeekKeyframe=!!(n||c.a.msedge||c.a.msie),this._alwaysSeekKeyframe&&(this._config.accurateSeek=!1)}return e.prototype.destroy=function(){null!=this._progressChecker&&(window.clearInterval(this._progressChecker),this._progressChecker=null),this._transmuxer&&this.unload(),this._mediaElement&&this.detachMediaElement(),this.e=null,this._mediaDataSource=null,this._emitter.removeAllListeners(),this._emitter=null},e.prototype.on=function(e,t){var i=this;e===f.MEDIA_INFO?null!=this._mediaInfo&&Promise.resolve().then((function(){i._emitter.emit(f.MEDIA_INFO,i.mediaInfo)})):e===f.STATISTICS_INFO&&null!=this._statisticsInfo&&Promise.resolve().then((function(){i._emitter.emit(f.STATISTICS_INFO,i.statisticsInfo)})),this._emitter.addListener(e,t)},e.prototype.off=function(e,t){this._emitter.removeListener(e,t)},e.prototype.attachMediaElement=function(e){var t=this;if(this._mediaElement=e,e.addEventListener("loadedmetadata",this.e.onvLoadedMetadata),e.addEventListener("seeking",this.e.onvSeeking),e.addEventListener("canplay",this.e.onvCanPlay),e.addEventListener("stalled",this.e.onvStalled),e.addEventListener("progress",this.e.onvProgress),this._msectl=new k(this._config),this._msectl.on(E,this._onmseUpdateEnd.bind(this)),this._msectl.on(w,this._onmseBufferFull.bind(this)),this._msectl.on(T,(function(){t._mseSourceOpened=!0,t._hasPendingLoad&&(t._hasPendingLoad=!1,t.load())})),this._msectl.on(S,(function(e){t._emitter.emit(f.ERROR,I.MEDIA_ERROR,L.MEDIA_MSE_ERROR,e)})),this._msectl.attachMediaElement(e),null!=this._pendingSeekTime)try{e.currentTime=this._pendingSeekTime,this._pendingSeekTime=null}catch(e){}},e.prototype.detachMediaElement=function(){this._mediaElement&&(this._msectl.detachMediaElement(),this._mediaElement.removeEventListener("loadedmetadata",this.e.onvLoadedMetadata),this._mediaElement.removeEventListener("seeking",this.e.onvSeeking),this._mediaElement.removeEventListener("canplay",this.e.onvCanPlay),this._mediaElement.removeEventListener("stalled",this.e.onvStalled),this._mediaElement.removeEventListener("progress",this.e.onvProgress),this._mediaElement=null),this._msectl&&(this._msectl.destroy(),this._msectl=null)},e.prototype.load=function(){var e=this;if(!this._mediaElement)throw new C.a("HTMLMediaElement must be attached before load()!");if(this._transmuxer)throw new C.a("MSEPlayer.load() has been called, please call unload() first!");this._hasPendingLoad||(this._config.deferLoadAfterSourceOpen&&!1===this._mseSourceOpened?this._hasPendingLoad=!0:(this._mediaElement.readyState>0&&(this._requestSetTime=!0,this._mediaElement.currentTime=0),this._transmuxer=new b(this._mediaDataSource,this._config),this._transmuxer.on(v.a.INIT_SEGMENT,(function(t,i){e._msectl.appendInitSegment(i)})),this._transmuxer.on(v.a.MEDIA_SEGMENT,(function(t,i){if(e._msectl.appendMediaSegment(i),e._config.lazyLoad&&!e._config.isLive){var n=e._mediaElement.currentTime;i.info.endDts>=1e3*(n+e._config.lazyLoadMaxDuration)&&null==e._progressChecker&&(d.a.v(e.TAG,"Maximum buffering duration exceeded, suspend transmuxing task"),e._suspendTransmuxer())}})),this._transmuxer.on(v.a.LOADING_COMPLETE,(function(){e._msectl.endOfStream(),e._emitter.emit(f.LOADING_COMPLETE)})),this._transmuxer.on(v.a.RECOVERED_EARLY_EOF,(function(){e._emitter.emit(f.RECOVERED_EARLY_EOF)})),this._transmuxer.on(v.a.IO_ERROR,(function(t,i){e._emitter.emit(f.ERROR,I.NETWORK_ERROR,t,i)})),this._transmuxer.on(v.a.DEMUX_ERROR,(function(t,i){e._emitter.emit(f.ERROR,I.MEDIA_ERROR,t,{code:-1,msg:i})})),this._transmuxer.on(v.a.MEDIA_INFO,(function(t){e._mediaInfo=t,e._emitter.emit(f.MEDIA_INFO,Object.assign({},t))})),this._transmuxer.on(v.a.METADATA_ARRIVED,(function(t){e._emitter.emit(f.METADATA_ARRIVED,t)})),this._transmuxer.on(v.a.SCRIPTDATA_ARRIVED,(function(t){e._emitter.emit(f.SCRIPTDATA_ARRIVED,t)})),this._transmuxer.on(v.a.TIMED_ID3_METADATA_ARRIVED,(function(t){e._emitter.emit(f.TIMED_ID3_METADATA_ARRIVED,t)})),this._transmuxer.on(v.a.PES_PRIVATE_DATA_DESCRIPTOR,(function(t){e._emitter.emit(f.PES_PRIVATE_DATA_DESCRIPTOR,t)})),this._transmuxer.on(v.a.PES_PRIVATE_DATA_ARRIVED,(function(t){e._emitter.emit(f.PES_PRIVATE_DATA_ARRIVED,t)})),this._transmuxer.on(v.a.STATISTICS_INFO,(function(t){e._statisticsInfo=e._fillStatisticsInfo(t),e._emitter.emit(f.STATISTICS_INFO,Object.assign({},e._statisticsInfo))})),this._transmuxer.on(v.a.RECOMMEND_SEEKPOINT,(function(t){e._mediaElement&&!e._config.accurateSeek&&(e._requestSetTime=!0,e._mediaElement.currentTime=t/1e3)})),this._transmuxer.open()))},e.prototype.unload=function(){this._mediaElement&&this._mediaElement.pause(),this._msectl&&this._msectl.seek(0),this._transmuxer&&(this._transmuxer.close(),this._transmuxer.destroy(),this._transmuxer=null)},e.prototype.play=function(){return this._mediaElement.play()},e.prototype.pause=function(){this._mediaElement.pause()},Object.defineProperty(e.prototype,"type",{get:function(){return this._type},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"buffered",{get:function(){return this._mediaElement.buffered},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"duration",{get:function(){return this._mediaElement.duration},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"volume",{get:function(){return this._mediaElement.volume},set:function(e){this._mediaElement.volume=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"muted",{get:function(){return this._mediaElement.muted},set:function(e){this._mediaElement.muted=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"currentTime",{get:function(){return this._mediaElement?this._mediaElement.currentTime:0},set:function(e){this._mediaElement?this._internalSeek(e):this._pendingSeekTime=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"mediaInfo",{get:function(){return Object.assign({},this._mediaInfo)},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"statisticsInfo",{get:function(){return null==this._statisticsInfo&&(this._statisticsInfo={}),this._statisticsInfo=this._fillStatisticsInfo(this._statisticsInfo),Object.assign({},this._statisticsInfo)},enumerable:!1,configurable:!0}),e.prototype._fillStatisticsInfo=function(e){if(e.playerType=this._type,!(this._mediaElement instanceof HTMLVideoElement))return e;var t=!0,i=0,n=0;if(this._mediaElement.getVideoPlaybackQuality){var r=this._mediaElement.getVideoPlaybackQuality();i=r.totalVideoFrames,n=r.droppedVideoFrames}else null!=this._mediaElement.webkitDecodedFrameCount?(i=this._mediaElement.webkitDecodedFrameCount,n=this._mediaElement.webkitDroppedFrameCount):t=!1;return t&&(e.decodedFrames=i,e.droppedFrames=n),e},e.prototype._onmseUpdateEnd=function(){var e=this._mediaElement.buffered,t=this._mediaElement.currentTime;if(this._config.isLive&&this._config.liveBufferLatencyChasing&&e.length>0&&!this._mediaElement.paused){var i=e.end(e.length-1);if(i>this._config.liveBufferLatencyMaxLatency&&i-t>this._config.liveBufferLatencyMaxLatency){var n=i-this._config.liveBufferLatencyMinRemain;this.currentTime=n}}if(this._config.lazyLoad&&!this._config.isLive){for(var r=0,a=0;a=t+this._config.lazyLoadMaxDuration&&null==this._progressChecker&&(d.a.v(this.TAG,"Maximum buffering duration exceeded, suspend transmuxing task"),this._suspendTransmuxer())}},e.prototype._onmseBufferFull=function(){d.a.v(this.TAG,"MSE SourceBuffer is full, suspend transmuxing task"),null==this._progressChecker&&this._suspendTransmuxer()},e.prototype._suspendTransmuxer=function(){this._transmuxer&&(this._transmuxer.pause(),null==this._progressChecker&&(this._progressChecker=window.setInterval(this._checkProgressAndResume.bind(this),1e3)))},e.prototype._checkProgressAndResume=function(){for(var e=this._mediaElement.currentTime,t=this._mediaElement.buffered,i=!1,n=0;n=r&&e=a-this._config.lazyLoadRecoverDuration&&(i=!0);break}}i&&(window.clearInterval(this._progressChecker),this._progressChecker=null,i&&(d.a.v(this.TAG,"Continue loading from paused position"),this._transmuxer.resume()))},e.prototype._isTimepointBuffered=function(e){for(var t=this._mediaElement.buffered,i=0;i=n&&e0){var r=this._mediaElement.buffered.start(0);(r<1&&e0&&t.currentTime0){var n=i.start(0);if(n<1&&t0&&(this._mediaElement.currentTime=0),this._mediaElement.preload="auto",this._mediaElement.load(),this._statisticsReporter=window.setInterval(this._reportStatisticsInfo.bind(this),this._config.statisticsInfoReportInterval)},e.prototype.unload=function(){this._mediaElement&&(this._mediaElement.src="",this._mediaElement.removeAttribute("src")),null!=this._statisticsReporter&&(window.clearInterval(this._statisticsReporter),this._statisticsReporter=null)},e.prototype.play=function(){return this._mediaElement.play()},e.prototype.pause=function(){this._mediaElement.pause()},Object.defineProperty(e.prototype,"type",{get:function(){return this._type},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"buffered",{get:function(){return this._mediaElement.buffered},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"duration",{get:function(){return this._mediaElement.duration},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"volume",{get:function(){return this._mediaElement.volume},set:function(e){this._mediaElement.volume=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"muted",{get:function(){return this._mediaElement.muted},set:function(e){this._mediaElement.muted=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"currentTime",{get:function(){return this._mediaElement?this._mediaElement.currentTime:0},set:function(e){this._mediaElement?this._mediaElement.currentTime=e:this._pendingSeekTime=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"mediaInfo",{get:function(){var e={mimeType:(this._mediaElement instanceof HTMLAudioElement?"audio/":"video/")+this._mediaDataSource.type};return this._mediaElement&&(e.duration=Math.floor(1e3*this._mediaElement.duration),this._mediaElement instanceof HTMLVideoElement&&(e.width=this._mediaElement.videoWidth,e.height=this._mediaElement.videoHeight)),e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"statisticsInfo",{get:function(){var e={playerType:this._type,url:this._mediaDataSource.url};if(!(this._mediaElement instanceof HTMLVideoElement))return e;var t=!0,i=0,n=0;if(this._mediaElement.getVideoPlaybackQuality){var r=this._mediaElement.getVideoPlaybackQuality();i=r.totalVideoFrames,n=r.droppedVideoFrames}else null!=this._mediaElement.webkitDecodedFrameCount?(i=this._mediaElement.webkitDecodedFrameCount,n=this._mediaElement.webkitDroppedFrameCount):t=!1;return t&&(e.decodedFrames=i,e.droppedFrames=n),e},enumerable:!1,configurable:!0}),e.prototype._onvLoadedMetadata=function(e){null!=this._pendingSeekTime&&(this._mediaElement.currentTime=this._pendingSeekTime,this._pendingSeekTime=null),this._emitter.emit(f.MEDIA_INFO,this.mediaInfo)},e.prototype._reportStatisticsInfo=function(){this._emitter.emit(f.STATISTICS_INFO,this.statisticsInfo)},e}();n.a.install();var D={createPlayer:function(e,t){var i=e;if(null==i||"object"!=typeof i)throw new C.b("MediaDataSource must be an javascript object!");if(!i.hasOwnProperty("type"))throw new C.b("MediaDataSource must has type field to indicate video file type!");switch(i.type){case"mse":case"mpegts":case"m2ts":case"flv":return new x(i,t);default:return new R(i,t)}},isSupported:function(){return o.supportMSEH264Playback()},getFeatureList:function(){return o.getFeatureList()}};D.BaseLoader=u.a,D.LoaderStatus=u.c,D.LoaderErrors=u.b,D.Events=f,D.ErrorTypes=I,D.ErrorDetails=L,D.MSEPlayer=x,D.NativePlayer=R,D.LoggingControl=_.a,Object.defineProperty(D,"version",{enumerable:!0,get:function(){return"1.6.10"}}),t.default=D}])},"object"==typeof i&&"object"==typeof t?t.exports=r():"function"==typeof define&&define.amd?define([],r):"object"==typeof i?i.mpegts=r():n.mpegts=r()},{}],42:[function(e,t,i){var n=Math.pow(2,32);t.exports=function(e){var t=new DataView(e.buffer,e.byteOffset,e.byteLength),i={version:e[0],flags:new Uint8Array(e.subarray(1,4)),references:[],referenceId:t.getUint32(4),timescale:t.getUint32(8)},r=12;0===i.version?(i.earliestPresentationTime=t.getUint32(r),i.firstOffset=t.getUint32(r+4),r+=8):(i.earliestPresentationTime=t.getUint32(r)*n+t.getUint32(r+4),i.firstOffset=t.getUint32(r+8)*n+t.getUint32(r+12),r+=16),r+=2;var a=t.getUint16(r);for(r+=2;a>0;r+=12,a--)i.references.push({referenceType:(128&e[r])>>>7,referencedSize:2147483647&t.getUint32(r),subsegmentDuration:t.getUint32(r+4),startsWithSap:!!(128&e[r+8]),sapType:(112&e[r+8])>>>4,sapDeltaTime:268435455&t.getUint32(r+8)});return i}},{}],43:[function(e,t,i){var n,r,a,s,o,u,l;n=function(e){return 9e4*e},r=function(e,t){return e*t},a=function(e){return e/9e4},s=function(e,t){return e/t},o=function(e,t){return n(s(e,t))},u=function(e,t){return r(a(e),t)},l=function(e,t,i){return a(i?e:e-t)},t.exports={ONE_SECOND_IN_TS:9e4,secondsToVideoTs:n,secondsToAudioTs:r,videoTsToSeconds:a,audioTsToSeconds:s,audioTsToVideoTs:o,videoTsToAudioTs:u,metadataTsToSeconds:l}},{}],44:[function(e,t,i){var n,r,a=t.exports={};function s(){throw new Error("setTimeout has not been defined")}function o(){throw new Error("clearTimeout has not been defined")}function u(e){if(n===setTimeout)return setTimeout(e,0);if((n===s||!n)&&setTimeout)return n=setTimeout,setTimeout(e,0);try{return n(e,0)}catch(t){try{return n.call(null,e,0)}catch(t){return n.call(this,e,0)}}}!function(){try{n="function"==typeof setTimeout?setTimeout:s}catch(e){n=s}try{r="function"==typeof clearTimeout?clearTimeout:o}catch(e){r=o}}();var l,h=[],d=!1,c=-1;function f(){d&&l&&(d=!1,l.length?h=l.concat(h):c=-1,h.length&&p())}function p(){if(!d){var e=u(f);d=!0;for(var t=h.length;t;){for(l=h,h=[];++c1)for(var i=1;i * Copyright Brightcove, Inc. * Available under Apache License Version 2.0 * * * Includes vtt.js * Available under Apache License Version 2.0 * */ "use strict";var n=e("global/window"),r=e("global/document"),a=e("@babel/runtime/helpers/extends"),s=e("@babel/runtime/helpers/assertThisInitialized"),o=e("@babel/runtime/helpers/inheritsLoose"),u=e("safe-json-parse/tuple"),l=e("keycode"),h=e("@videojs/xhr"),d=e("videojs-vtt.js"),c=e("@babel/runtime/helpers/construct"),f=e("@babel/runtime/helpers/inherits"),p=e("@videojs/vhs-utils/cjs/resolve-url.js"),m=e("m3u8-parser"),_=e("@videojs/vhs-utils/cjs/codecs.js"),g=e("@videojs/vhs-utils/cjs/media-types.js"),v=e("mpd-parser"),y=e("mux.js/lib/tools/parse-sidx"),b=e("@videojs/vhs-utils/cjs/id3-helpers"),S=e("@videojs/vhs-utils/cjs/containers"),T=e("@videojs/vhs-utils/cjs/byte-helpers"),E=e("mux.js/lib/utils/clock");function w(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}for(var A,C=w(n),k=w(r),P=w(a),I=w(s),L=w(o),x=w(u),R=w(l),D=w(h),O=w(d),U=w(c),M=w(f),F=w(p),B=w(y),N={},j=function(e,t){return N[e]=N[e]||[],t&&(N[e]=N[e].concat(t)),N[e]},V=function(e,t){var i=j(e).indexOf(t);return!(i<=-1)&&(N[e]=N[e].slice(),N[e].splice(i,1),!0)},H={prefixed:!0},z=[["requestFullscreen","exitFullscreen","fullscreenElement","fullscreenEnabled","fullscreenchange","fullscreenerror","fullscreen"],["webkitRequestFullscreen","webkitExitFullscreen","webkitFullscreenElement","webkitFullscreenEnabled","webkitfullscreenchange","webkitfullscreenerror","-webkit-full-screen"],["mozRequestFullScreen","mozCancelFullScreen","mozFullScreenElement","mozFullScreenEnabled","mozfullscreenchange","mozfullscreenerror","-moz-full-screen"],["msRequestFullscreen","msExitFullscreen","msFullscreenElement","msFullscreenEnabled","MSFullscreenChange","MSFullscreenError","-ms-fullscreen"]],G=z[0],W=0;W0?o:0)}if(C.default.console){var u=C.default.console[i];u||"debug"!==i||(u=C.default.console.info||C.default.console.log),u&&a&&s.test(i)&&u[Array.isArray(r)?"apply":"call"](C.default.console,r)}}}(t,r),r.createLogger=function(i){return e(t+": "+i)},r.levels={all:"debug|log|warn|error",off:"",debug:"debug|log|warn|error",info:"log|warn|error",warn:"warn|error",error:"error",DEFAULT:n},r.level=function(e){if("string"==typeof e){if(!r.levels.hasOwnProperty(e))throw new Error('"'+e+'" in not a valid log level');n=e}return n},(r.history=function(){return q?[].concat(q):[]}).filter=function(e){return(q||[]).filter((function(t){return new RegExp(".*"+e+".*").test(t[0])}))},r.history.clear=function(){q&&(q.length=0)},r.history.disable=function(){null!==q&&(q.length=0,q=null)},r.history.enable=function(){null===q&&(q=[])},r.error=function(){for(var e=arguments.length,t=new Array(e),r=0;r1?t-1:0),n=1;n=0)throw new Error("class has illegal whitespace characters")}function ke(){return k.default===C.default.document}function Pe(e){return ee(e)&&1===e.nodeType}function Ie(){try{return C.default.parent!==C.default.self}catch(e){return!0}}function Le(e){return function(t,i){if(!Ae(t))return k.default[e](null);Ae(i)&&(i=k.default.querySelector(i));var n=Pe(i)?i:k.default;return n[e]&&n[e](t)}}function xe(e,t,i,n){void 0===e&&(e="div"),void 0===t&&(t={}),void 0===i&&(i={});var r=k.default.createElement(e);return Object.getOwnPropertyNames(t).forEach((function(e){var i=t[e];-1!==e.indexOf("aria-")||"role"===e||"type"===e?(K.warn("Setting attributes in the second argument of createEl()\nhas been deprecated. Use the third argument instead.\ncreateEl(type, properties, attributes). Attempting to set "+e+" to "+i+"."),r.setAttribute(e,i)):"textContent"===e?Re(r,i):r[e]===i&&"tabIndex"!==e||(r[e]=i)})),Object.getOwnPropertyNames(i).forEach((function(e){r.setAttribute(e,i[e])})),n&&$e(r,n),r}function Re(e,t){return void 0===e.textContent?e.innerText=t:e.textContent=t,e}function De(e,t){t.firstChild?t.insertBefore(e,t.firstChild):t.appendChild(e)}function Oe(e,t){return Ce(t),e.classList?e.classList.contains(t):(i=t,new RegExp("(^|\\s)"+i+"($|\\s)")).test(e.className);var i}function Ue(e,t){return e.classList?e.classList.add(t):Oe(e,t)||(e.className=(e.className+" "+t).trim()),e}function Me(e,t){return e?(e.classList?e.classList.remove(t):(Ce(t),e.className=e.className.split(/\s+/).filter((function(e){return e!==t})).join(" ")),e):(K.warn("removeClass was called with an element that doesn't exist"),null)}function Fe(e,t,i){var n=Oe(e,t);if("function"==typeof i&&(i=i(e,t)),"boolean"!=typeof i&&(i=!n),i!==n)return i?Ue(e,t):Me(e,t),e}function Be(e,t){Object.getOwnPropertyNames(t).forEach((function(i){var n=t[i];null==n||!1===n?e.removeAttribute(i):e.setAttribute(i,!0===n?"":n)}))}function Ne(e){var t={},i=",autoplay,controls,playsinline,loop,muted,default,defaultMuted,";if(e&&e.attributes&&e.attributes.length>0)for(var n=e.attributes,r=n.length-1;r>=0;r--){var a=n[r].name,s=n[r].value;"boolean"!=typeof e[a]&&-1===i.indexOf(","+a+",")||(s=null!==s),t[a]=s}return t}function je(e,t){return e.getAttribute(t)}function Ve(e,t,i){e.setAttribute(t,i)}function He(e,t){e.removeAttribute(t)}function ze(){k.default.body.focus(),k.default.onselectstart=function(){return!1}}function Ge(){k.default.onselectstart=function(){return!0}}function We(e){if(e&&e.getBoundingClientRect&&e.parentNode){var t=e.getBoundingClientRect(),i={};return["bottom","height","left","right","top","width"].forEach((function(e){void 0!==t[e]&&(i[e]=t[e])})),i.height||(i.height=parseFloat(ie(e,"height"))),i.width||(i.width=parseFloat(ie(e,"width"))),i}}function Ye(e){if(!e||e&&!e.offsetParent)return{left:0,top:0,width:0,height:0};for(var t=e.offsetWidth,i=e.offsetHeight,n=0,r=0;e.offsetParent&&e!==k.default[H.fullscreenElement];)n+=e.offsetLeft,r+=e.offsetTop,e=e.offsetParent;return{left:n,top:r,width:t,height:i}}function qe(e,t){var i={x:0,y:0};if(Te)for(var n=e;n&&"html"!==n.nodeName.toLowerCase();){var r=ie(n,"transform");if(/^matrix/.test(r)){var a=r.slice(7,-1).split(/,\s/).map(Number);i.x+=a[4],i.y+=a[5]}else if(/^matrix3d/.test(r)){var s=r.slice(9,-1).split(/,\s/).map(Number);i.x+=s[12],i.y+=s[13]}n=n.parentNode}var o={},u=Ye(t.target),l=Ye(e),h=l.width,d=l.height,c=t.offsetY-(l.top-u.top),f=t.offsetX-(l.left-u.left);return t.changedTouches&&(f=t.changedTouches[0].pageX-l.left,c=t.changedTouches[0].pageY+l.top,Te&&(f-=i.x,c-=i.y)),o.y=1-Math.max(0,Math.min(1,c/d)),o.x=Math.max(0,Math.min(1,f/h)),o}function Ke(e){return ee(e)&&3===e.nodeType}function Xe(e){for(;e.firstChild;)e.removeChild(e.firstChild);return e}function Qe(e){return"function"==typeof e&&(e=e()),(Array.isArray(e)?e:[e]).map((function(e){return"function"==typeof e&&(e=e()),Pe(e)||Ke(e)?e:"string"==typeof e&&/\S/.test(e)?k.default.createTextNode(e):void 0})).filter((function(e){return e}))}function $e(e,t){return Qe(t).forEach((function(t){return e.appendChild(t)})),e}function Je(e,t){return $e(Xe(e),t)}function Ze(e){return void 0===e.button&&void 0===e.buttons||(0===e.button&&void 0===e.buttons||("mouseup"===e.type&&0===e.button&&0===e.buttons||0===e.button&&1===e.buttons))}var et,tt=Le("querySelector"),it=Le("querySelectorAll"),nt=Object.freeze({__proto__:null,isReal:ke,isEl:Pe,isInFrame:Ie,createEl:xe,textContent:Re,prependTo:De,hasClass:Oe,addClass:Ue,removeClass:Me,toggleClass:Fe,setAttributes:Be,getAttributes:Ne,getAttribute:je,setAttribute:Ve,removeAttribute:He,blockTextSelection:ze,unblockTextSelection:Ge,getBoundingClientRect:We,findPosition:Ye,getPointerPosition:qe,isTextNode:Ke,emptyEl:Xe,normalizeContent:Qe,appendContent:$e,insertContent:Je,isSingleLeftClick:Ze,$:tt,$$:it}),rt=!1,at=function(){if(!1!==et.options.autoSetup){var e=Array.prototype.slice.call(k.default.getElementsByTagName("video")),t=Array.prototype.slice.call(k.default.getElementsByTagName("audio")),i=Array.prototype.slice.call(k.default.getElementsByTagName("video-js")),n=e.concat(t,i);if(n&&n.length>0)for(var r=0,a=n.length;r-1&&(r={passive:!0}),e.addEventListener(t,n.dispatcher,r)}else e.attachEvent&&e.attachEvent("on"+t,n.dispatcher)}function bt(e,t,i){if(pt.has(e)){var n=pt.get(e);if(n.handlers){if(Array.isArray(t))return _t(bt,e,t,i);var r=function(e,t){n.handlers[t]=[],mt(e,t)};if(void 0!==t){var a=n.handlers[t];if(a)if(i){if(i.guid)for(var s=0;s=t&&(e.apply(void 0,arguments),i=n)}},Pt=function(){};Pt.prototype.allowedEvents_={},Pt.prototype.on=function(e,t){var i=this.addEventListener;this.addEventListener=function(){},yt(this,e,t),this.addEventListener=i},Pt.prototype.addEventListener=Pt.prototype.on,Pt.prototype.off=function(e,t){bt(this,e,t)},Pt.prototype.removeEventListener=Pt.prototype.off,Pt.prototype.one=function(e,t){var i=this.addEventListener;this.addEventListener=function(){},Tt(this,e,t),this.addEventListener=i},Pt.prototype.any=function(e,t){var i=this.addEventListener;this.addEventListener=function(){},Et(this,e,t),this.addEventListener=i},Pt.prototype.trigger=function(e){var t=e.type||e;"string"==typeof e&&(e={type:t}),e=gt(e),this.allowedEvents_[t]&&this["on"+t]&&this["on"+t](e),St(this,e)},Pt.prototype.dispatchEvent=Pt.prototype.trigger,Pt.prototype.queueTrigger=function(e){var t=this;wt||(wt=new Map);var i=e.type||e,n=wt.get(this);n||(n=new Map,wt.set(this,n));var r=n.get(i);n.delete(i),C.default.clearTimeout(r);var a=C.default.setTimeout((function(){0===n.size&&(n=null,wt.delete(t)),t.trigger(e)}),0);n.set(i,a)};var It=function(e){return"function"==typeof e.name?e.name():"string"==typeof e.name?e.name:e.name_?e.name_:e.constructor&&e.constructor.name?e.constructor.name:typeof e},Lt=function(e){return e instanceof Pt||!!e.eventBusEl_&&["on","one","off","trigger"].every((function(t){return"function"==typeof e[t]}))},xt=function(e){return"string"==typeof e&&/\S/.test(e)||Array.isArray(e)&&!!e.length},Rt=function(e,t,i){if(!e||!e.nodeName&&!Lt(e))throw new Error("Invalid target for "+It(t)+"#"+i+"; must be a DOM node or evented object.")},Dt=function(e,t,i){if(!xt(e))throw new Error("Invalid event type for "+It(t)+"#"+i+"; must be a non-empty string or array.")},Ot=function(e,t,i){if("function"!=typeof e)throw new Error("Invalid listener for "+It(t)+"#"+i+"; must be a function.")},Ut=function(e,t,i){var n,r,a,s=t.length<3||t[0]===e||t[0]===e.eventBusEl_;return s?(n=e.eventBusEl_,t.length>=3&&t.shift(),r=t[0],a=t[1]):(n=t[0],r=t[1],a=t[2]),Rt(n,e,i),Dt(r,e,i),Ot(a,e,i),{isTargetingSelf:s,target:n,type:r,listener:a=Ct(e,a)}},Mt=function(e,t,i,n){Rt(e,e,t),e.nodeName?At[t](e,i,n):e[t](i,n)},Ft={on:function(){for(var e=this,t=arguments.length,i=new Array(t),n=0;n=0;e--)this.children_[e].dispose&&this.children_[e].dispose();this.children_=null,this.childIndex_=null,this.childNameIndex_=null,this.parentComponent_=null,this.el_&&(this.el_.parentNode&&this.el_.parentNode.removeChild(this.el_),this.el_=null),this.player_=null}},t.isDisposed=function(){return Boolean(this.isDisposed_)},t.player=function(){return this.player_},t.options=function(e){return e?(this.options_=zt(this.options_,e),this.options_):this.options_},t.el=function(){return this.el_},t.createEl=function(e,t,i){return xe(e,t,i)},t.localize=function(e,t,i){void 0===i&&(i=e);var n=this.player_.language&&this.player_.language(),r=this.player_.languages&&this.player_.languages(),a=r&&r[n],s=n&&n.split("-")[0],o=r&&r[s],u=i;return a&&a[e]?u=a[e]:o&&o[e]&&(u=o[e]),t&&(u=u.replace(/\{(\d+)\}/g,(function(e,i){var n=t[i-1],r=n;return void 0===n&&(r=e),r}))),u},t.handleLanguagechange=function(){},t.contentEl=function(){return this.contentEl_||this.el_},t.id=function(){return this.id_},t.name=function(){return this.name_},t.children=function(){return this.children_},t.getChildById=function(e){return this.childIndex_[e]},t.getChild=function(e){if(e)return this.childNameIndex_[e]},t.getDescendant=function(){for(var e=arguments.length,t=new Array(e),i=0;i=0;i--)if(this.children_[i]===e){t=!0,this.children_.splice(i,1);break}if(t){e.parentComponent_=null,this.childIndex_[e.id()]=null,this.childNameIndex_[Ht(e.name())]=null,this.childNameIndex_[Vt(e.name())]=null;var n=e.el();n&&n.parentNode===this.contentEl()&&this.contentEl().removeChild(e.el())}}},t.initChildren=function(){var t=this,i=this.options_.children;if(i){var n,r=this.options_,a=e.getComponent("Tech");(n=Array.isArray(i)?i:Object.keys(i)).concat(Object.keys(this.options_).filter((function(e){return!n.some((function(t){return"string"==typeof t?e===t:e===t.name}))}))).map((function(e){var n,r;return"string"==typeof e?r=i[n=e]||t.options_[n]||{}:(n=e.name,r=e),{name:n,opts:r}})).filter((function(t){var i=e.getComponent(t.opts.componentClass||Ht(t.name));return i&&!a.isTech(i)})).forEach((function(e){var i=e.name,n=e.opts;if(void 0!==r[i]&&(n=r[i]),!1!==n){!0===n&&(n={}),n.playerOptions=t.options_.playerOptions;var a=t.addChild(i,n);a&&(t[i]=a)}}))}},t.buildCSSClass=function(){return""},t.ready=function(e,t){if(void 0===t&&(t=!1),e)return this.isReady_?void(t?e.call(this):this.setTimeout(e,1)):(this.readyQueue_=this.readyQueue_||[],void this.readyQueue_.push(e))},t.triggerReady=function(){this.isReady_=!0,this.setTimeout((function(){var e=this.readyQueue_;this.readyQueue_=[],e&&e.length>0&&e.forEach((function(e){e.call(this)}),this),this.trigger("ready")}),1)},t.$=function(e,t){return tt(e,t||this.contentEl())},t.$$=function(e,t){return it(e,t||this.contentEl())},t.hasClass=function(e){return Oe(this.el_,e)},t.addClass=function(e){Ue(this.el_,e)},t.removeClass=function(e){Me(this.el_,e)},t.toggleClass=function(e,t){Fe(this.el_,e,t)},t.show=function(){this.removeClass("vjs-hidden")},t.hide=function(){this.addClass("vjs-hidden")},t.lockShowing=function(){this.addClass("vjs-lock-showing")},t.unlockShowing=function(){this.removeClass("vjs-lock-showing")},t.getAttribute=function(e){return je(this.el_,e)},t.setAttribute=function(e,t){Ve(this.el_,e,t)},t.removeAttribute=function(e){He(this.el_,e)},t.width=function(e,t){return this.dimension("width",e,t)},t.height=function(e,t){return this.dimension("height",e,t)},t.dimensions=function(e,t){this.width(e,!0),this.height(t)},t.dimension=function(e,t,i){if(void 0!==t)return null!==t&&t==t||(t=0),-1!==(""+t).indexOf("%")||-1!==(""+t).indexOf("px")?this.el_.style[e]=t:this.el_.style[e]="auto"===t?"":t+"px",void(i||this.trigger("componentresize"));if(!this.el_)return 0;var n=this.el_.style[e],r=n.indexOf("px");return-1!==r?parseInt(n.slice(0,r),10):parseInt(this.el_["offset"+Ht(e)],10)},t.currentDimension=function(e){var t=0;if("width"!==e&&"height"!==e)throw new Error("currentDimension only accepts width or height value");if(t=ie(this.el_,e),0===(t=parseFloat(t))||isNaN(t)){var i="offset"+Ht(e);t=this.el_[i]}return t},t.currentDimensions=function(){return{width:this.currentDimension("width"),height:this.currentDimension("height")}},t.currentWidth=function(){return this.currentDimension("width")},t.currentHeight=function(){return this.currentDimension("height")},t.focus=function(){this.el_.focus()},t.blur=function(){this.el_.blur()},t.handleKeyDown=function(e){this.player_&&(e.stopPropagation(),this.player_.handleKeyDown(e))},t.handleKeyPress=function(e){this.handleKeyDown(e)},t.emitTapEvents=function(){var e,t=0,i=null;this.on("touchstart",(function(n){1===n.touches.length&&(i={pageX:n.touches[0].pageX,pageY:n.touches[0].pageY},t=C.default.performance.now(),e=!0)})),this.on("touchmove",(function(t){if(t.touches.length>1)e=!1;else if(i){var n=t.touches[0].pageX-i.pageX,r=t.touches[0].pageY-i.pageY;Math.sqrt(n*n+r*r)>10&&(e=!1)}}));var n=function(){e=!1};this.on("touchleave",n),this.on("touchcancel",n),this.on("touchend",(function(n){(i=null,!0===e)&&(C.default.performance.now()-t<200&&(n.preventDefault(),this.trigger("tap")))}))},t.enableTouchActivity=function(){if(this.player()&&this.player().reportUserActivity){var e,t=Ct(this.player(),this.player().reportUserActivity);this.on("touchstart",(function(){t(),this.clearInterval(e),e=this.setInterval(t,250)}));var i=function(i){t(),this.clearInterval(e)};this.on("touchmove",t),this.on("touchend",i),this.on("touchcancel",i)}},t.setTimeout=function(e,t){var i,n=this;return e=Ct(this,e),this.clearTimersOnDispose_(),i=C.default.setTimeout((function(){n.setTimeoutIds_.has(i)&&n.setTimeoutIds_.delete(i),e()}),t),this.setTimeoutIds_.add(i),i},t.clearTimeout=function(e){return this.setTimeoutIds_.has(e)&&(this.setTimeoutIds_.delete(e),C.default.clearTimeout(e)),e},t.setInterval=function(e,t){e=Ct(this,e),this.clearTimersOnDispose_();var i=C.default.setInterval(e,t);return this.setIntervalIds_.add(i),i},t.clearInterval=function(e){return this.setIntervalIds_.has(e)&&(this.setIntervalIds_.delete(e),C.default.clearInterval(e)),e},t.requestAnimationFrame=function(e){var t,i=this;return this.supportsRaf_?(this.clearTimersOnDispose_(),e=Ct(this,e),t=C.default.requestAnimationFrame((function(){i.rafIds_.has(t)&&i.rafIds_.delete(t),e()})),this.rafIds_.add(t),t):this.setTimeout(e,1e3/60)},t.requestNamedAnimationFrame=function(e,t){var i=this;if(!this.namedRafs_.has(e)){this.clearTimersOnDispose_(),t=Ct(this,t);var n=this.requestAnimationFrame((function(){t(),i.namedRafs_.has(e)&&i.namedRafs_.delete(e)}));return this.namedRafs_.set(e,n),e}},t.cancelNamedAnimationFrame=function(e){this.namedRafs_.has(e)&&(this.cancelAnimationFrame(this.namedRafs_.get(e)),this.namedRafs_.delete(e))},t.cancelAnimationFrame=function(e){return this.supportsRaf_?(this.rafIds_.has(e)&&(this.rafIds_.delete(e),C.default.cancelAnimationFrame(e)),e):this.clearTimeout(e)},t.clearTimersOnDispose_=function(){var e=this;this.clearingTimersOnDispose_||(this.clearingTimersOnDispose_=!0,this.one("dispose",(function(){[["namedRafs_","cancelNamedAnimationFrame"],["rafIds_","cancelAnimationFrame"],["setTimeoutIds_","clearTimeout"],["setIntervalIds_","clearInterval"]].forEach((function(t){var i=t[0],n=t[1];e[i].forEach((function(t,i){return e[n](i)}))})),e.clearingTimersOnDispose_=!1})))},e.registerComponent=function(t,i){if("string"!=typeof t||!t)throw new Error('Illegal component name, "'+t+'"; must be a non-empty string.');var n,r=e.getComponent("Tech"),a=r&&r.isTech(i),s=e===i||e.prototype.isPrototypeOf(i.prototype);if(a||!s)throw n=a?"techs must be registered using Tech.registerTech()":"must be a Component subclass",new Error('Illegal component, "'+t+'"; '+n+".");t=Ht(t),e.components_||(e.components_={});var o=e.getComponent("Player");if("Player"===t&&o&&o.players){var u=o.players,l=Object.keys(u);if(u&&l.length>0&&l.map((function(e){return u[e]})).every(Boolean))throw new Error("Can not register Player component after player has been created.")}return e.components_[t]=i,e.components_[Vt(t)]=i,i},e.getComponent=function(t){if(t&&e.components_)return e.components_[t]},e}();function Xt(e,t,i,n){return function(e,t,i){if("number"!=typeof t||t<0||t>i)throw new Error("Failed to execute '"+e+"' on 'TimeRanges': The index provided ("+t+") is non-numeric or out of bounds (0-"+i+").")}(e,n,i.length-1),i[n][t]}function Qt(e){var t;return t=void 0===e||0===e.length?{length:0,start:function(){throw new Error("This TimeRanges object is empty")},end:function(){throw new Error("This TimeRanges object is empty")}}:{length:e.length,start:Xt.bind(null,"start",0,e),end:Xt.bind(null,"end",1,e)},C.default.Symbol&&C.default.Symbol.iterator&&(t[C.default.Symbol.iterator]=function(){return(e||[]).values()}),t}function $t(e,t){return Array.isArray(e)?Qt(e):void 0===e||void 0===t?Qt():Qt([[e,t]])}function Jt(e,t){var i,n,r=0;if(!t)return 0;e&&e.length||(e=$t(0,0));for(var a=0;at&&(n=t),r+=n-i;return r/t}function Zt(e){if(e instanceof Zt)return e;"number"==typeof e?this.code=e:"string"==typeof e?this.message=e:ee(e)&&("number"==typeof e.code&&(this.code=e.code),Z(this,e)),this.message||(this.message=Zt.defaultMessages[this.code]||"")}Kt.prototype.supportsRaf_="function"==typeof C.default.requestAnimationFrame&&"function"==typeof C.default.cancelAnimationFrame,Kt.registerComponent("Component",Kt),Zt.prototype.code=0,Zt.prototype.message="",Zt.prototype.status=null,Zt.errorTypes=["MEDIA_ERR_CUSTOM","MEDIA_ERR_ABORTED","MEDIA_ERR_NETWORK","MEDIA_ERR_DECODE","MEDIA_ERR_SRC_NOT_SUPPORTED","MEDIA_ERR_ENCRYPTED"],Zt.defaultMessages={1:"You aborted the media playback",2:"A network error caused the media download to fail part-way.",3:"The media playback was aborted due to a corruption problem or because the media used features your browser did not support.",4:"The media could not be loaded, either because the server or network failed or because the format is not supported.",5:"The media is encrypted and we do not have the keys to decrypt it."};for(var ei=0;ei=0;n--)if(t[n].enabled){li(t,t[n]);break}return(i=e.call(this,t)||this).changing_=!1,i}L.default(t,e);var i=t.prototype;return i.addTrack=function(t){var i=this;t.enabled&&li(this,t),e.prototype.addTrack.call(this,t),t.addEventListener&&(t.enabledChange_=function(){i.changing_||(i.changing_=!0,li(i,t),i.changing_=!1,i.trigger("change"))},t.addEventListener("enabledchange",t.enabledChange_))},i.removeTrack=function(t){e.prototype.removeTrack.call(this,t),t.removeEventListener&&t.enabledChange_&&(t.removeEventListener("enabledchange",t.enabledChange_),t.enabledChange_=null)},t}(oi),di=function(e,t){for(var i=0;i=0;n--)if(t[n].selected){di(t,t[n]);break}return(i=e.call(this,t)||this).changing_=!1,Object.defineProperty(I.default(i),"selectedIndex",{get:function(){for(var e=0;e0&&(C.default.console&&C.default.console.groupCollapsed&&C.default.console.groupCollapsed("Text Track parsing errors for "+t.src),n.forEach((function(e){return K.error(e)})),C.default.console&&C.default.console.groupEnd&&C.default.console.groupEnd()),i.flush()},ki=function(e,t){var i={uri:e},n=wi(e);n&&(i.cors=n);var r="use-credentials"===t.tech_.crossOrigin();r&&(i.withCredentials=r),D.default(i,Ct(this,(function(e,i,n){if(e)return K.error(e,i);t.loaded_=!0,"function"!=typeof C.default.WebVTT?t.tech_&&t.tech_.any(["vttjsloaded","vttjserror"],(function(e){if("vttjserror"!==e.type)return Ci(n,t);K.error("vttjs failed to load, stopping trying to process "+t.src)})):Ci(n,t)})))},Pi=function(e){function t(t){var i;if(void 0===t&&(t={}),!t.tech)throw new Error("A tech was not provided.");var n=zt(t,{kind:vi[t.kind]||"subtitles",language:t.language||t.srclang||""}),r=yi[n.mode]||"disabled",a=n.default;"metadata"!==n.kind&&"chapters"!==n.kind||(r="hidden"),(i=e.call(this,n)||this).tech_=n.tech,i.cues_=[],i.activeCues_=[],i.preload_=!1!==i.tech_.preloadTextTracks;var s=new mi(i.cues_),o=new mi(i.activeCues_),u=!1,l=Ct(I.default(i),(function(){this.tech_.isReady_&&!this.tech_.isDisposed()&&(this.activeCues=this.activeCues,u&&(this.trigger("cuechange"),u=!1))}));return i.tech_.one("dispose",(function(){i.tech_.off("timeupdate",l)})),"disabled"!==r&&i.tech_.on("timeupdate",l),Object.defineProperties(I.default(i),{default:{get:function(){return a},set:function(){}},mode:{get:function(){return r},set:function(e){yi[e]&&r!==e&&(r=e,this.preload_||"disabled"===r||0!==this.cues.length||ki(this.src,this),this.tech_.off("timeupdate",l),"disabled"!==r&&this.tech_.on("timeupdate",l),this.trigger("modechange"))}},cues:{get:function(){return this.loaded_?s:null},set:function(){}},activeCues:{get:function(){if(!this.loaded_)return null;if(0===this.cues.length)return o;for(var e=this.tech_.currentTime(),t=[],i=0,n=this.cues.length;i=e||r.startTime===r.endTime&&r.startTime<=e&&r.startTime+.5>=e)&&t.push(r)}if(u=!1,t.length!==this.activeCues_.length)u=!0;else for(var a=0;a0)return void this.trigger("vttjsloaded");var t=k.default.createElement("script");t.src=this.options_["vtt.js"]||"https://vjs.zencdn.net/vttjs/0.14.1/vtt.min.js",t.onload=function(){e.trigger("vttjsloaded")},t.onerror=function(){e.trigger("vttjserror")},this.on("dispose",(function(){t.onload=null,t.onerror=null})),C.default.WebVTT=!0,this.el().parentNode.appendChild(t)}else this.ready(this.addWebVttScript_)},i.emulateTextTracks=function(){var e=this,t=this.textTracks(),i=this.remoteTextTracks(),n=function(e){return t.addTrack(e.track)},r=function(e){return t.removeTrack(e.track)};i.on("addtrack",n),i.on("removetrack",r),this.addWebVttScript_();var a=function(){return e.trigger("texttrackchange")},s=function(){a();for(var e=0;e=0;r--){var a=e[r];a[t]&&a[t](n,i)}}(e,i,o,s),o}var Vi={buffered:1,currentTime:1,duration:1,muted:1,played:1,paused:1,seekable:1,volume:1,ended:1},Hi={setCurrentTime:1,setMuted:1,setVolume:1},zi={play:1,pause:1};function Gi(e){return function(t,i){return t===Bi?Bi:i[e]?i[e](t):t}}var Wi={opus:"video/ogg",ogv:"video/ogg",mp4:"video/mp4",mov:"video/mp4",m4v:"video/mp4",mkv:"video/x-matroska",m4a:"audio/mp4",mp3:"audio/mpeg",aac:"audio/aac",caf:"audio/x-caf",flac:"audio/flac",oga:"audio/ogg",wav:"audio/wav",m3u8:"application/x-mpegURL",jpg:"image/jpeg",jpeg:"image/jpeg",gif:"image/gif",png:"image/png",svg:"image/svg+xml",webp:"image/webp"},Yi=function(e){void 0===e&&(e="");var t=Ei(e);return Wi[t.toLowerCase()]||""};function qi(e){if(!e.type){var t=Yi(e.src);t&&(e.type=t)}return e}var Ki=function(e){function t(t,i,n){var r,a=zt({createEl:!1},i);if(r=e.call(this,t,a,n)||this,i.playerOptions.sources&&0!==i.playerOptions.sources.length)t.src(i.playerOptions.sources);else for(var s=0,o=i.playerOptions.techOrder;s0;!this.player_.tech(!0)||(_e||fe)&&t||this.player_.tech(!0).focus(),this.player_.paused()?ii(this.player_.play()):this.player_.pause()}},t}(Xi);Kt.registerComponent("PosterImage",Qi);var $i={monospace:"monospace",sansSerif:"sans-serif",serif:"serif",monospaceSansSerif:'"Andale Mono", "Lucida Console", monospace',monospaceSerif:'"Courier New", monospace',proportionalSansSerif:"sans-serif",proportionalSerif:"serif",casual:'"Comic Sans MS", Impact, fantasy',script:'"Monotype Corsiva", cursive',smallcaps:'"Andale Mono", "Lucida Console", monospace, sans-serif'};function Ji(e,t){var i;if(4===e.length)i=e[1]+e[1]+e[2]+e[2]+e[3]+e[3];else{if(7!==e.length)throw new Error("Invalid color code provided, "+e+"; must be formatted as e.g. #f0e or #f604e2.");i=e.slice(1)}return"rgba("+parseInt(i.slice(0,2),16)+","+parseInt(i.slice(2,4),16)+","+parseInt(i.slice(4,6),16)+","+t+")"}function Zi(e,t,i){try{e.style[t]=i}catch(e){return}}var en=function(e){function t(t,i,n){var r;r=e.call(this,t,i,n)||this;var a=function(e){return r.updateDisplay(e)};return t.on("loadstart",(function(e){return r.toggleDisplay(e)})),t.on("texttrackchange",a),t.on("loadedmetadata",(function(e){return r.preselectTrack(e)})),t.ready(Ct(I.default(r),(function(){if(t.tech_&&t.tech_.featuresNativeTextTracks)this.hide();else{t.on("fullscreenchange",a),t.on("playerresize",a),C.default.addEventListener("orientationchange",a),t.on("dispose",(function(){return C.default.removeEventListener("orientationchange",a)}));for(var e=this.options_.playerOptions.tracks||[],i=0;i0;return ii(t),void(!this.player_.tech(!0)||(_e||fe)&&i||this.player_.tech(!0).focus())}var n=this.player_.getChild("controlBar"),r=n&&n.getChild("playToggle");if(r){var a=function(){return r.focus()};ti(t)?t.then(a,(function(){})):this.setTimeout(a,1)}else this.player_.tech(!0).focus()},i.handleKeyDown=function(t){this.mouseused_=!1,e.prototype.handleKeyDown.call(this,t)},i.handleMouseDown=function(e){this.mouseused_=!0},t}(nn);rn.prototype.controlText_="Play Video",Kt.registerComponent("BigPlayButton",rn);var an=function(e){function t(t,i){var n;return(n=e.call(this,t,i)||this).controlText(i&&i.controlText||n.localize("Close")),n}L.default(t,e);var i=t.prototype;return i.buildCSSClass=function(){return"vjs-close-button "+e.prototype.buildCSSClass.call(this)},i.handleClick=function(e){this.trigger({type:"close",bubbles:!1})},i.handleKeyDown=function(t){R.default.isEventKey(t,"Esc")?(t.preventDefault(),t.stopPropagation(),this.trigger("click")):e.prototype.handleKeyDown.call(this,t)},t}(nn);Kt.registerComponent("CloseButton",an);var sn=function(e){function t(t,i){var n;return void 0===i&&(i={}),n=e.call(this,t,i)||this,i.replay=void 0===i.replay||i.replay,n.on(t,"play",(function(e){return n.handlePlay(e)})),n.on(t,"pause",(function(e){return n.handlePause(e)})),i.replay&&n.on(t,"ended",(function(e){return n.handleEnded(e)})),n}L.default(t,e);var i=t.prototype;return i.buildCSSClass=function(){return"vjs-play-control "+e.prototype.buildCSSClass.call(this)},i.handleClick=function(e){this.player_.paused()?ii(this.player_.play()):this.player_.pause()},i.handleSeeked=function(e){this.removeClass("vjs-ended"),this.player_.paused()?this.handlePause(e):this.handlePlay(e)},i.handlePlay=function(e){this.removeClass("vjs-ended"),this.removeClass("vjs-paused"),this.addClass("vjs-playing"),this.controlText("Pause")},i.handlePause=function(e){this.removeClass("vjs-playing"),this.addClass("vjs-paused"),this.controlText("Play")},i.handleEnded=function(e){var t=this;this.removeClass("vjs-playing"),this.addClass("vjs-ended"),this.controlText("Replay"),this.one(this.player_,"seeked",(function(e){return t.handleSeeked(e)}))},t}(nn);sn.prototype.controlText_="Play",Kt.registerComponent("PlayToggle",sn);var on=function(e,t){e=e<0?0:e;var i=Math.floor(e%60),n=Math.floor(e/60%60),r=Math.floor(e/3600),a=Math.floor(t/60%60),s=Math.floor(t/3600);return(isNaN(e)||e===1/0)&&(r=n=i="-"),(r=r>0||s>0?r+":":"")+(n=((r||a>=10)&&n<10?"0"+n:n)+":")+(i=i<10?"0"+i:i)},un=on;function ln(e,t){return void 0===t&&(t=e),un(e,t)}var hn=function(e){function t(t,i){var n;return(n=e.call(this,t,i)||this).on(t,["timeupdate","ended"],(function(e){return n.updateContent(e)})),n.updateTextNode_(),n}L.default(t,e);var i=t.prototype;return i.createEl=function(){var t=this.buildCSSClass(),i=e.prototype.createEl.call(this,"div",{className:t+" vjs-time-control vjs-control"}),n=xe("span",{className:"vjs-control-text",textContent:this.localize(this.labelText_)+" "},{role:"presentation"});return i.appendChild(n),this.contentEl_=xe("span",{className:t+"-display"},{"aria-live":"off",role:"presentation"}),i.appendChild(this.contentEl_),i},i.dispose=function(){this.contentEl_=null,this.textNode_=null,e.prototype.dispose.call(this)},i.updateTextNode_=function(e){var t=this;void 0===e&&(e=0),e=ln(e),this.formattedTime_!==e&&(this.formattedTime_=e,this.requestNamedAnimationFrame("TimeDisplay#updateTextNode_",(function(){if(t.contentEl_){var e=t.textNode_;e&&t.contentEl_.firstChild!==e&&(e=null,K.warn("TimeDisplay#updateTextnode_: Prevented replacement of text node element since it was no longer a child of this node. Appending a new node instead.")),t.textNode_=k.default.createTextNode(t.formattedTime_),t.textNode_&&(e?t.contentEl_.replaceChild(t.textNode_,e):t.contentEl_.appendChild(t.textNode_))}})))},i.updateContent=function(e){},t}(Kt);hn.prototype.labelText_="Time",hn.prototype.controlText_="Time",Kt.registerComponent("TimeDisplay",hn);var dn=function(e){function t(){return e.apply(this,arguments)||this}L.default(t,e);var i=t.prototype;return i.buildCSSClass=function(){return"vjs-current-time"},i.updateContent=function(e){var t;t=this.player_.ended()?this.player_.duration():this.player_.scrubbing()?this.player_.getCache().currentTime:this.player_.currentTime(),this.updateTextNode_(t)},t}(hn);dn.prototype.labelText_="Current Time",dn.prototype.controlText_="Current Time",Kt.registerComponent("CurrentTimeDisplay",dn);var cn=function(e){function t(t,i){var n,r=function(e){return n.updateContent(e)};return(n=e.call(this,t,i)||this).on(t,"durationchange",r),n.on(t,"loadstart",r),n.on(t,"loadedmetadata",r),n}L.default(t,e);var i=t.prototype;return i.buildCSSClass=function(){return"vjs-duration"},i.updateContent=function(e){var t=this.player_.duration();this.updateTextNode_(t)},t}(hn);cn.prototype.labelText_="Duration",cn.prototype.controlText_="Duration",Kt.registerComponent("DurationDisplay",cn);var fn=function(e){function t(){return e.apply(this,arguments)||this}return L.default(t,e),t.prototype.createEl=function(){var t=e.prototype.createEl.call(this,"div",{className:"vjs-time-control vjs-time-divider"},{"aria-hidden":!0}),i=e.prototype.createEl.call(this,"div"),n=e.prototype.createEl.call(this,"span",{textContent:"/"});return i.appendChild(n),t.appendChild(i),t},t}(Kt);Kt.registerComponent("TimeDivider",fn);var pn=function(e){function t(t,i){var n;return(n=e.call(this,t,i)||this).on(t,"durationchange",(function(e){return n.updateContent(e)})),n}L.default(t,e);var i=t.prototype;return i.buildCSSClass=function(){return"vjs-remaining-time"},i.createEl=function(){var t=e.prototype.createEl.call(this);return t.insertBefore(xe("span",{},{"aria-hidden":!0},"-"),this.contentEl_),t},i.updateContent=function(e){var t;"number"==typeof this.player_.duration()&&(t=this.player_.ended()?0:this.player_.remainingTimeDisplay?this.player_.remainingTimeDisplay():this.player_.remainingTime(),this.updateTextNode_(t))},t}(hn);pn.prototype.labelText_="Remaining Time",pn.prototype.controlText_="Remaining Time",Kt.registerComponent("RemainingTimeDisplay",pn);var mn=function(e){function t(t,i){var n;return(n=e.call(this,t,i)||this).updateShowing(),n.on(n.player(),"durationchange",(function(e){return n.updateShowing(e)})),n}L.default(t,e);var i=t.prototype;return i.createEl=function(){var t=e.prototype.createEl.call(this,"div",{className:"vjs-live-control vjs-control"});return this.contentEl_=xe("div",{className:"vjs-live-display"},{"aria-live":"off"}),this.contentEl_.appendChild(xe("span",{className:"vjs-control-text",textContent:this.localize("Stream Type")+" "})),this.contentEl_.appendChild(k.default.createTextNode(this.localize("LIVE"))),t.appendChild(this.contentEl_),t},i.dispose=function(){this.contentEl_=null,e.prototype.dispose.call(this)},i.updateShowing=function(e){this.player().duration()===1/0?this.show():this.hide()},t}(Kt);Kt.registerComponent("LiveDisplay",mn);var _n=function(e){function t(t,i){var n;return(n=e.call(this,t,i)||this).updateLiveEdgeStatus(),n.player_.liveTracker&&(n.updateLiveEdgeStatusHandler_=function(e){return n.updateLiveEdgeStatus(e)},n.on(n.player_.liveTracker,"liveedgechange",n.updateLiveEdgeStatusHandler_)),n}L.default(t,e);var i=t.prototype;return i.createEl=function(){var t=e.prototype.createEl.call(this,"button",{className:"vjs-seek-to-live-control vjs-control"});return this.textEl_=xe("span",{className:"vjs-seek-to-live-text",textContent:this.localize("LIVE")},{"aria-hidden":"true"}),t.appendChild(this.textEl_),t},i.updateLiveEdgeStatus=function(){!this.player_.liveTracker||this.player_.liveTracker.atLiveEdge()?(this.setAttribute("aria-disabled",!0),this.addClass("vjs-at-live-edge"),this.controlText("Seek to live, currently playing live")):(this.setAttribute("aria-disabled",!1),this.removeClass("vjs-at-live-edge"),this.controlText("Seek to live, currently behind live"))},i.handleClick=function(){this.player_.liveTracker.seekToLiveEdge()},i.dispose=function(){this.player_.liveTracker&&this.off(this.player_.liveTracker,"liveedgechange",this.updateLiveEdgeStatusHandler_),this.textEl_=null,e.prototype.dispose.call(this)},t}(nn);_n.prototype.controlText_="Seek to live, currently playing live",Kt.registerComponent("SeekToLive",_n);var gn=function(e,t,i){return e=Number(e),Math.min(i,Math.max(t,isNaN(e)?t:e))},vn=function(e){function t(t,i){var n;return(n=e.call(this,t,i)||this).handleMouseDown_=function(e){return n.handleMouseDown(e)},n.handleMouseUp_=function(e){return n.handleMouseUp(e)},n.handleKeyDown_=function(e){return n.handleKeyDown(e)},n.handleClick_=function(e){return n.handleClick(e)},n.handleMouseMove_=function(e){return n.handleMouseMove(e)},n.update_=function(e){return n.update(e)},n.bar=n.getChild(n.options_.barName),n.vertical(!!n.options_.vertical),n.enable(),n}L.default(t,e);var i=t.prototype;return i.enabled=function(){return this.enabled_},i.enable=function(){this.enabled()||(this.on("mousedown",this.handleMouseDown_),this.on("touchstart",this.handleMouseDown_),this.on("keydown",this.handleKeyDown_),this.on("click",this.handleClick_),this.on(this.player_,"controlsvisible",this.update),this.playerEvent&&this.on(this.player_,this.playerEvent,this.update),this.removeClass("disabled"),this.setAttribute("tabindex",0),this.enabled_=!0)},i.disable=function(){if(this.enabled()){var e=this.bar.el_.ownerDocument;this.off("mousedown",this.handleMouseDown_),this.off("touchstart",this.handleMouseDown_),this.off("keydown",this.handleKeyDown_),this.off("click",this.handleClick_),this.off(this.player_,"controlsvisible",this.update_),this.off(e,"mousemove",this.handleMouseMove_),this.off(e,"mouseup",this.handleMouseUp_),this.off(e,"touchmove",this.handleMouseMove_),this.off(e,"touchend",this.handleMouseUp_),this.removeAttribute("tabindex"),this.addClass("disabled"),this.playerEvent&&this.off(this.player_,this.playerEvent,this.update),this.enabled_=!1}},i.createEl=function(t,i,n){return void 0===i&&(i={}),void 0===n&&(n={}),i.className=i.className+" vjs-slider",i=Z({tabIndex:0},i),n=Z({role:"slider","aria-valuenow":0,"aria-valuemin":0,"aria-valuemax":100,tabIndex:0},n),e.prototype.createEl.call(this,t,i,n)},i.handleMouseDown=function(e){var t=this.bar.el_.ownerDocument;"mousedown"===e.type&&e.preventDefault(),"touchstart"!==e.type||pe||e.preventDefault(),ze(),this.addClass("vjs-sliding"),this.trigger("slideractive"),this.on(t,"mousemove",this.handleMouseMove_),this.on(t,"mouseup",this.handleMouseUp_),this.on(t,"touchmove",this.handleMouseMove_),this.on(t,"touchend",this.handleMouseUp_),this.handleMouseMove(e)},i.handleMouseMove=function(e){},i.handleMouseUp=function(){var e=this.bar.el_.ownerDocument;Ge(),this.removeClass("vjs-sliding"),this.trigger("sliderinactive"),this.off(e,"mousemove",this.handleMouseMove_),this.off(e,"mouseup",this.handleMouseUp_),this.off(e,"touchmove",this.handleMouseMove_),this.off(e,"touchend",this.handleMouseUp_),this.update()},i.update=function(){var e=this;if(this.el_&&this.bar){var t=this.getProgress();return t===this.progress_||(this.progress_=t,this.requestNamedAnimationFrame("Slider#update",(function(){var i=e.vertical()?"height":"width";e.bar.el().style[i]=(100*t).toFixed(2)+"%"}))),t}},i.getProgress=function(){return Number(gn(this.getPercent(),0,1).toFixed(4))},i.calculateDistance=function(e){var t=qe(this.el_,e);return this.vertical()?t.y:t.x},i.handleKeyDown=function(t){R.default.isEventKey(t,"Left")||R.default.isEventKey(t,"Down")?(t.preventDefault(),t.stopPropagation(),this.stepBack()):R.default.isEventKey(t,"Right")||R.default.isEventKey(t,"Up")?(t.preventDefault(),t.stopPropagation(),this.stepForward()):e.prototype.handleKeyDown.call(this,t)},i.handleClick=function(e){e.stopPropagation(),e.preventDefault()},i.vertical=function(e){if(void 0===e)return this.vertical_||!1;this.vertical_=!!e,this.vertical_?this.addClass("vjs-slider-vertical"):this.addClass("vjs-slider-horizontal")},t}(Kt);Kt.registerComponent("Slider",vn);var yn=function(e,t){return gn(e/t*100,0,100).toFixed(2)+"%"},bn=function(e){function t(t,i){var n;return(n=e.call(this,t,i)||this).partEls_=[],n.on(t,"progress",(function(e){return n.update(e)})),n}L.default(t,e);var i=t.prototype;return i.createEl=function(){var t=e.prototype.createEl.call(this,"div",{className:"vjs-load-progress"}),i=xe("span",{className:"vjs-control-text"}),n=xe("span",{textContent:this.localize("Loaded")}),r=k.default.createTextNode(": ");return this.percentageEl_=xe("span",{className:"vjs-control-text-loaded-percentage",textContent:"0%"}),t.appendChild(i),i.appendChild(n),i.appendChild(r),i.appendChild(this.percentageEl_),t},i.dispose=function(){this.partEls_=null,this.percentageEl_=null,e.prototype.dispose.call(this)},i.update=function(e){var t=this;this.requestNamedAnimationFrame("LoadProgressBar#update",(function(){var e=t.player_.liveTracker,i=t.player_.buffered(),n=e&&e.isLive()?e.seekableEnd():t.player_.duration(),r=t.player_.bufferedEnd(),a=t.partEls_,s=yn(r,n);t.percent_!==s&&(t.el_.style.width=s,Re(t.percentageEl_,s),t.percent_=s);for(var o=0;oi.length;d--)t.el_.removeChild(a[d-1]);a.length=i.length}))},t}(Kt);Kt.registerComponent("LoadProgressBar",bn);var Sn=function(e){function t(t,i){var n;return(n=e.call(this,t,i)||this).update=kt(Ct(I.default(n),n.update),30),n}L.default(t,e);var i=t.prototype;return i.createEl=function(){return e.prototype.createEl.call(this,"div",{className:"vjs-time-tooltip"},{"aria-hidden":"true"})},i.update=function(e,t,i){var n=Ye(this.el_),r=We(this.player_.el()),a=e.width*t;if(r&&n){var s=e.left-r.left+a,o=e.width-a+(r.right-e.right),u=n.width/2;sn.width&&(u=n.width),u=Math.round(u),this.el_.style.right="-"+u+"px",this.write(i)}},i.write=function(e){Re(this.el_,e)},i.updateTime=function(e,t,i,n){var r=this;this.requestNamedAnimationFrame("TimeTooltip#updateTime",(function(){var a,s=r.player_.duration();if(r.player_.liveTracker&&r.player_.liveTracker.isLive()){var o=r.player_.liveTracker.liveWindow(),u=o-t*o;a=(u<1?"":"-")+ln(u,o)}else a=ln(i,s);r.update(e,t,a),n&&n()}))},t}(Kt);Kt.registerComponent("TimeTooltip",Sn);var Tn=function(e){function t(t,i){var n;return(n=e.call(this,t,i)||this).update=kt(Ct(I.default(n),n.update),30),n}L.default(t,e);var i=t.prototype;return i.createEl=function(){return e.prototype.createEl.call(this,"div",{className:"vjs-play-progress vjs-slider-bar"},{"aria-hidden":"true"})},i.update=function(e,t){var i=this.getChild("timeTooltip");if(i){var n=this.player_.scrubbing()?this.player_.getCache().currentTime:this.player_.currentTime();i.updateTime(e,t,n)}},t}(Kt);Tn.prototype.options_={children:[]},Te||le||Tn.prototype.options_.children.push("timeTooltip"),Kt.registerComponent("PlayProgressBar",Tn);var En=function(e){function t(t,i){var n;return(n=e.call(this,t,i)||this).update=kt(Ct(I.default(n),n.update),30),n}L.default(t,e);var i=t.prototype;return i.createEl=function(){return e.prototype.createEl.call(this,"div",{className:"vjs-mouse-display"})},i.update=function(e,t){var i=this,n=t*this.player_.duration();this.getChild("timeTooltip").updateTime(e,t,n,(function(){i.el_.style.left=e.width*t+"px"}))},t}(Kt);En.prototype.options_={children:["timeTooltip"]},Kt.registerComponent("MouseTimeDisplay",En);var wn=function(e){function t(t,i){var n;return(n=e.call(this,t,i)||this).setEventHandlers_(),n}L.default(t,e);var i=t.prototype;return i.setEventHandlers_=function(){var e=this;this.update_=Ct(this,this.update),this.update=kt(this.update_,30),this.on(this.player_,["ended","durationchange","timeupdate"],this.update),this.player_.liveTracker&&this.on(this.player_.liveTracker,"liveedgechange",this.update),this.updateInterval=null,this.enableIntervalHandler_=function(t){return e.enableInterval_(t)},this.disableIntervalHandler_=function(t){return e.disableInterval_(t)},this.on(this.player_,["playing"],this.enableIntervalHandler_),this.on(this.player_,["ended","pause","waiting"],this.disableIntervalHandler_),"hidden"in k.default&&"visibilityState"in k.default&&this.on(k.default,"visibilitychange",this.toggleVisibility_)},i.toggleVisibility_=function(e){"hidden"===k.default.visibilityState?(this.cancelNamedAnimationFrame("SeekBar#update"),this.cancelNamedAnimationFrame("Slider#update"),this.disableInterval_(e)):(this.player_.ended()||this.player_.paused()||this.enableInterval_(),this.update())},i.enableInterval_=function(){this.updateInterval||(this.updateInterval=this.setInterval(this.update,30))},i.disableInterval_=function(e){this.player_.liveTracker&&this.player_.liveTracker.isLive()&&e&&"ended"!==e.type||this.updateInterval&&(this.clearInterval(this.updateInterval),this.updateInterval=null)},i.createEl=function(){return e.prototype.createEl.call(this,"div",{className:"vjs-progress-holder"},{"aria-label":this.localize("Progress Bar")})},i.update=function(t){var i=this;if("hidden"!==k.default.visibilityState){var n=e.prototype.update.call(this);return this.requestNamedAnimationFrame("SeekBar#update",(function(){var e=i.player_.ended()?i.player_.duration():i.getCurrentTime_(),t=i.player_.liveTracker,r=i.player_.duration();t&&t.isLive()&&(r=i.player_.liveTracker.liveCurrentTime()),i.percent_!==n&&(i.el_.setAttribute("aria-valuenow",(100*n).toFixed(2)),i.percent_=n),i.currentTime_===e&&i.duration_===r||(i.el_.setAttribute("aria-valuetext",i.localize("progress bar timing: currentTime={1} duration={2}",[ln(e,r),ln(r,r)],"{1} of {2}")),i.currentTime_=e,i.duration_=r),i.bar&&i.bar.update(We(i.el()),i.getProgress())})),n}},i.userSeek_=function(e){this.player_.liveTracker&&this.player_.liveTracker.isLive()&&this.player_.liveTracker.nextSeekedFromUser(),this.player_.currentTime(e)},i.getCurrentTime_=function(){return this.player_.scrubbing()?this.player_.getCache().currentTime:this.player_.currentTime()},i.getPercent=function(){var e,t=this.getCurrentTime_(),i=this.player_.liveTracker;return i&&i.isLive()?(e=(t-i.seekableStart())/i.liveWindow(),i.atLiveEdge()&&(e=1)):e=t/this.player_.duration(),e},i.handleMouseDown=function(t){Ze(t)&&(t.stopPropagation(),this.player_.scrubbing(!0),this.videoWasPlaying=!this.player_.paused(),this.player_.pause(),e.prototype.handleMouseDown.call(this,t))},i.handleMouseMove=function(e){if(Ze(e)){var t,i=this.calculateDistance(e),n=this.player_.liveTracker;if(n&&n.isLive()){if(i>=.99)return void n.seekToLiveEdge();var r=n.seekableStart(),a=n.liveCurrentTime();if((t=r+i*n.liveWindow())>=a&&(t=a),t<=r&&(t=r+.1),t===1/0)return}else(t=i*this.player_.duration())===this.player_.duration()&&(t-=.1);this.userSeek_(t)}},i.enable=function(){e.prototype.enable.call(this);var t=this.getChild("mouseTimeDisplay");t&&t.show()},i.disable=function(){e.prototype.disable.call(this);var t=this.getChild("mouseTimeDisplay");t&&t.hide()},i.handleMouseUp=function(t){e.prototype.handleMouseUp.call(this,t),t&&t.stopPropagation(),this.player_.scrubbing(!1),this.player_.trigger({type:"timeupdate",target:this,manuallyTriggered:!0}),this.videoWasPlaying?ii(this.player_.play()):this.update_()},i.stepForward=function(){this.userSeek_(this.player_.currentTime()+5)},i.stepBack=function(){this.userSeek_(this.player_.currentTime()-5)},i.handleAction=function(e){this.player_.paused()?this.player_.play():this.player_.pause()},i.handleKeyDown=function(t){var i=this.player_.liveTracker;if(R.default.isEventKey(t,"Space")||R.default.isEventKey(t,"Enter"))t.preventDefault(),t.stopPropagation(),this.handleAction(t);else if(R.default.isEventKey(t,"Home"))t.preventDefault(),t.stopPropagation(),this.userSeek_(0);else if(R.default.isEventKey(t,"End"))t.preventDefault(),t.stopPropagation(),i&&i.isLive()?this.userSeek_(i.liveCurrentTime()):this.userSeek_(this.player_.duration());else if(/^[0-9]$/.test(R.default(t))){t.preventDefault(),t.stopPropagation();var n=10*(R.default.codes[R.default(t)]-R.default.codes[0])/100;i&&i.isLive()?this.userSeek_(i.seekableStart()+i.liveWindow()*n):this.userSeek_(this.player_.duration()*n)}else R.default.isEventKey(t,"PgDn")?(t.preventDefault(),t.stopPropagation(),this.userSeek_(this.player_.currentTime()-60)):R.default.isEventKey(t,"PgUp")?(t.preventDefault(),t.stopPropagation(),this.userSeek_(this.player_.currentTime()+60)):e.prototype.handleKeyDown.call(this,t)},i.dispose=function(){this.disableInterval_(),this.off(this.player_,["ended","durationchange","timeupdate"],this.update),this.player_.liveTracker&&this.off(this.player_.liveTracker,"liveedgechange",this.update),this.off(this.player_,["playing"],this.enableIntervalHandler_),this.off(this.player_,["ended","pause","waiting"],this.disableIntervalHandler_),"hidden"in k.default&&"visibilityState"in k.default&&this.off(k.default,"visibilitychange",this.toggleVisibility_),e.prototype.dispose.call(this)},t}(vn);wn.prototype.options_={children:["loadProgressBar","playProgressBar"],barName:"playProgressBar"},Te||le||wn.prototype.options_.children.splice(1,0,"mouseTimeDisplay"),Kt.registerComponent("SeekBar",wn);var An=function(e){function t(t,i){var n;return(n=e.call(this,t,i)||this).handleMouseMove=kt(Ct(I.default(n),n.handleMouseMove),30),n.throttledHandleMouseSeek=kt(Ct(I.default(n),n.handleMouseSeek),30),n.handleMouseUpHandler_=function(e){return n.handleMouseUp(e)},n.handleMouseDownHandler_=function(e){return n.handleMouseDown(e)},n.enable(),n}L.default(t,e);var i=t.prototype;return i.createEl=function(){return e.prototype.createEl.call(this,"div",{className:"vjs-progress-control vjs-control"})},i.handleMouseMove=function(e){var t=this.getChild("seekBar");if(t){var i=t.getChild("playProgressBar"),n=t.getChild("mouseTimeDisplay");if(i||n){var r=t.el(),a=Ye(r),s=qe(r,e).x;s=gn(s,0,1),n&&n.update(a,s),i&&i.update(a,t.getProgress())}}},i.handleMouseSeek=function(e){var t=this.getChild("seekBar");t&&t.handleMouseMove(e)},i.enabled=function(){return this.enabled_},i.disable=function(){if(this.children().forEach((function(e){return e.disable&&e.disable()})),this.enabled()&&(this.off(["mousedown","touchstart"],this.handleMouseDownHandler_),this.off(this.el_,"mousemove",this.handleMouseMove),this.removeListenersAddedOnMousedownAndTouchstart(),this.addClass("disabled"),this.enabled_=!1,this.player_.scrubbing())){var e=this.getChild("seekBar");this.player_.scrubbing(!1),e.videoWasPlaying&&ii(this.player_.play())}},i.enable=function(){this.children().forEach((function(e){return e.enable&&e.enable()})),this.enabled()||(this.on(["mousedown","touchstart"],this.handleMouseDownHandler_),this.on(this.el_,"mousemove",this.handleMouseMove),this.removeClass("disabled"),this.enabled_=!0)},i.removeListenersAddedOnMousedownAndTouchstart=function(){var e=this.el_.ownerDocument;this.off(e,"mousemove",this.throttledHandleMouseSeek),this.off(e,"touchmove",this.throttledHandleMouseSeek),this.off(e,"mouseup",this.handleMouseUpHandler_),this.off(e,"touchend",this.handleMouseUpHandler_)},i.handleMouseDown=function(e){var t=this.el_.ownerDocument,i=this.getChild("seekBar");i&&i.handleMouseDown(e),this.on(t,"mousemove",this.throttledHandleMouseSeek),this.on(t,"touchmove",this.throttledHandleMouseSeek),this.on(t,"mouseup",this.handleMouseUpHandler_),this.on(t,"touchend",this.handleMouseUpHandler_)},i.handleMouseUp=function(e){var t=this.getChild("seekBar");t&&t.handleMouseUp(e),this.removeListenersAddedOnMousedownAndTouchstart()},t}(Kt);An.prototype.options_={children:["seekBar"]},Kt.registerComponent("ProgressControl",An);var Cn=function(e){function t(t,i){var n;return(n=e.call(this,t,i)||this).on(t,["enterpictureinpicture","leavepictureinpicture"],(function(e){return n.handlePictureInPictureChange(e)})),n.on(t,["disablepictureinpicturechanged","loadedmetadata"],(function(e){return n.handlePictureInPictureEnabledChange(e)})),n.disable(),n}L.default(t,e);var i=t.prototype;return i.buildCSSClass=function(){return"vjs-picture-in-picture-control "+e.prototype.buildCSSClass.call(this)},i.handlePictureInPictureEnabledChange=function(){k.default.pictureInPictureEnabled&&!1===this.player_.disablePictureInPicture()?this.enable():this.disable()},i.handlePictureInPictureChange=function(e){this.player_.isInPictureInPicture()?this.controlText("Exit Picture-in-Picture"):this.controlText("Picture-in-Picture"),this.handlePictureInPictureEnabledChange()},i.handleClick=function(e){this.player_.isInPictureInPicture()?this.player_.exitPictureInPicture():this.player_.requestPictureInPicture()},t}(nn);Cn.prototype.controlText_="Picture-in-Picture",Kt.registerComponent("PictureInPictureToggle",Cn);var kn=function(e){function t(t,i){var n;return(n=e.call(this,t,i)||this).on(t,"fullscreenchange",(function(e){return n.handleFullscreenChange(e)})),!1===k.default[t.fsApi_.fullscreenEnabled]&&n.disable(),n}L.default(t,e);var i=t.prototype;return i.buildCSSClass=function(){return"vjs-fullscreen-control "+e.prototype.buildCSSClass.call(this)},i.handleFullscreenChange=function(e){this.player_.isFullscreen()?this.controlText("Non-Fullscreen"):this.controlText("Fullscreen")},i.handleClick=function(e){this.player_.isFullscreen()?this.player_.exitFullscreen():this.player_.requestFullscreen()},t}(nn);kn.prototype.controlText_="Fullscreen",Kt.registerComponent("FullscreenToggle",kn);var Pn=function(e){function t(){return e.apply(this,arguments)||this}return L.default(t,e),t.prototype.createEl=function(){var t=e.prototype.createEl.call(this,"div",{className:"vjs-volume-level"});return t.appendChild(e.prototype.createEl.call(this,"span",{className:"vjs-control-text"})),t},t}(Kt);Kt.registerComponent("VolumeLevel",Pn);var In=function(e){function t(t,i){var n;return(n=e.call(this,t,i)||this).update=kt(Ct(I.default(n),n.update),30),n}L.default(t,e);var i=t.prototype;return i.createEl=function(){return e.prototype.createEl.call(this,"div",{className:"vjs-volume-tooltip"},{"aria-hidden":"true"})},i.update=function(e,t,i,n){if(!i){var r=We(this.el_),a=We(this.player_.el()),s=e.width*t;if(!a||!r)return;var o=e.left-a.left+s,u=e.width-s+(a.right-e.right),l=r.width/2;or.width&&(l=r.width),this.el_.style.right="-"+l+"px"}this.write(n+"%")},i.write=function(e){Re(this.el_,e)},i.updateVolume=function(e,t,i,n,r){var a=this;this.requestNamedAnimationFrame("VolumeLevelTooltip#updateVolume",(function(){a.update(e,t,i,n.toFixed(0)),r&&r()}))},t}(Kt);Kt.registerComponent("VolumeLevelTooltip",In);var Ln=function(e){function t(t,i){var n;return(n=e.call(this,t,i)||this).update=kt(Ct(I.default(n),n.update),30),n}L.default(t,e);var i=t.prototype;return i.createEl=function(){return e.prototype.createEl.call(this,"div",{className:"vjs-mouse-display"})},i.update=function(e,t,i){var n=this,r=100*t;this.getChild("volumeLevelTooltip").updateVolume(e,t,i,r,(function(){i?n.el_.style.bottom=e.height*t+"px":n.el_.style.left=e.width*t+"px"}))},t}(Kt);Ln.prototype.options_={children:["volumeLevelTooltip"]},Kt.registerComponent("MouseVolumeLevelDisplay",Ln);var xn=function(e){function t(t,i){var n;return(n=e.call(this,t,i)||this).on("slideractive",(function(e){return n.updateLastVolume_(e)})),n.on(t,"volumechange",(function(e){return n.updateARIAAttributes(e)})),t.ready((function(){return n.updateARIAAttributes()})),n}L.default(t,e);var i=t.prototype;return i.createEl=function(){return e.prototype.createEl.call(this,"div",{className:"vjs-volume-bar vjs-slider-bar"},{"aria-label":this.localize("Volume Level"),"aria-live":"polite"})},i.handleMouseDown=function(t){Ze(t)&&e.prototype.handleMouseDown.call(this,t)},i.handleMouseMove=function(e){var t=this.getChild("mouseVolumeLevelDisplay");if(t){var i=this.el(),n=We(i),r=this.vertical(),a=qe(i,e);a=r?a.y:a.x,a=gn(a,0,1),t.update(n,a,r)}Ze(e)&&(this.checkMuted(),this.player_.volume(this.calculateDistance(e)))},i.checkMuted=function(){this.player_.muted()&&this.player_.muted(!1)},i.getPercent=function(){return this.player_.muted()?0:this.player_.volume()},i.stepForward=function(){this.checkMuted(),this.player_.volume(this.player_.volume()+.1)},i.stepBack=function(){this.checkMuted(),this.player_.volume(this.player_.volume()-.1)},i.updateARIAAttributes=function(e){var t=this.player_.muted()?0:this.volumeAsPercentage_();this.el_.setAttribute("aria-valuenow",t),this.el_.setAttribute("aria-valuetext",t+"%")},i.volumeAsPercentage_=function(){return Math.round(100*this.player_.volume())},i.updateLastVolume_=function(){var e=this,t=this.player_.volume();this.one("sliderinactive",(function(){0===e.player_.volume()&&e.player_.lastVolume_(t)}))},t}(vn);xn.prototype.options_={children:["volumeLevel"],barName:"volumeLevel"},Te||le||xn.prototype.options_.children.splice(0,0,"mouseVolumeLevelDisplay"),xn.prototype.playerEvent="volumechange",Kt.registerComponent("VolumeBar",xn);var Rn=function(e){function t(t,i){var n;return void 0===i&&(i={}),i.vertical=i.vertical||!1,(void 0===i.volumeBar||te(i.volumeBar))&&(i.volumeBar=i.volumeBar||{},i.volumeBar.vertical=i.vertical),n=e.call(this,t,i)||this,function(e,t){t.tech_&&!t.tech_.featuresVolumeControl&&e.addClass("vjs-hidden"),e.on(t,"loadstart",(function(){t.tech_.featuresVolumeControl?e.removeClass("vjs-hidden"):e.addClass("vjs-hidden")}))}(I.default(n),t),n.throttledHandleMouseMove=kt(Ct(I.default(n),n.handleMouseMove),30),n.handleMouseUpHandler_=function(e){return n.handleMouseUp(e)},n.on("mousedown",(function(e){return n.handleMouseDown(e)})),n.on("touchstart",(function(e){return n.handleMouseDown(e)})),n.on("mousemove",(function(e){return n.handleMouseMove(e)})),n.on(n.volumeBar,["focus","slideractive"],(function(){n.volumeBar.addClass("vjs-slider-active"),n.addClass("vjs-slider-active"),n.trigger("slideractive")})),n.on(n.volumeBar,["blur","sliderinactive"],(function(){n.volumeBar.removeClass("vjs-slider-active"),n.removeClass("vjs-slider-active"),n.trigger("sliderinactive")})),n}L.default(t,e);var i=t.prototype;return i.createEl=function(){var t="vjs-volume-horizontal";return this.options_.vertical&&(t="vjs-volume-vertical"),e.prototype.createEl.call(this,"div",{className:"vjs-volume-control vjs-control "+t})},i.handleMouseDown=function(e){var t=this.el_.ownerDocument;this.on(t,"mousemove",this.throttledHandleMouseMove),this.on(t,"touchmove",this.throttledHandleMouseMove),this.on(t,"mouseup",this.handleMouseUpHandler_),this.on(t,"touchend",this.handleMouseUpHandler_)},i.handleMouseUp=function(e){var t=this.el_.ownerDocument;this.off(t,"mousemove",this.throttledHandleMouseMove),this.off(t,"touchmove",this.throttledHandleMouseMove),this.off(t,"mouseup",this.handleMouseUpHandler_),this.off(t,"touchend",this.handleMouseUpHandler_)},i.handleMouseMove=function(e){this.volumeBar.handleMouseMove(e)},t}(Kt);Rn.prototype.options_={children:["volumeBar"]},Kt.registerComponent("VolumeControl",Rn);var Dn=function(e){function t(t,i){var n;return n=e.call(this,t,i)||this,function(e,t){t.tech_&&!t.tech_.featuresMuteControl&&e.addClass("vjs-hidden"),e.on(t,"loadstart",(function(){t.tech_.featuresMuteControl?e.removeClass("vjs-hidden"):e.addClass("vjs-hidden")}))}(I.default(n),t),n.on(t,["loadstart","volumechange"],(function(e){return n.update(e)})),n}L.default(t,e);var i=t.prototype;return i.buildCSSClass=function(){return"vjs-mute-control "+e.prototype.buildCSSClass.call(this)},i.handleClick=function(e){var t=this.player_.volume(),i=this.player_.lastVolume_();if(0===t){var n=i<.1?.1:i;this.player_.volume(n),this.player_.muted(!1)}else this.player_.muted(!this.player_.muted())},i.update=function(e){this.updateIcon_(),this.updateControlText_()},i.updateIcon_=function(){var e=this.player_.volume(),t=3;Te&&this.player_.tech_&&this.player_.tech_.el_&&this.player_.muted(this.player_.tech_.el_.muted),0===e||this.player_.muted()?t=0:e<.33?t=1:e<.67&&(t=2);for(var i=0;i<4;i++)Me(this.el_,"vjs-vol-"+i);Ue(this.el_,"vjs-vol-"+t)},i.updateControlText_=function(){var e=this.player_.muted()||0===this.player_.volume()?"Unmute":"Mute";this.controlText()!==e&&this.controlText(e)},t}(nn);Dn.prototype.controlText_="Mute",Kt.registerComponent("MuteToggle",Dn);var On=function(e){function t(t,i){var n;return void 0===i&&(i={}),void 0!==i.inline?i.inline=i.inline:i.inline=!0,(void 0===i.volumeControl||te(i.volumeControl))&&(i.volumeControl=i.volumeControl||{},i.volumeControl.vertical=!i.inline),(n=e.call(this,t,i)||this).handleKeyPressHandler_=function(e){return n.handleKeyPress(e)},n.on(t,["loadstart"],(function(e){return n.volumePanelState_(e)})),n.on(n.muteToggle,"keyup",(function(e){return n.handleKeyPress(e)})),n.on(n.volumeControl,"keyup",(function(e){return n.handleVolumeControlKeyUp(e)})),n.on("keydown",(function(e){return n.handleKeyPress(e)})),n.on("mouseover",(function(e){return n.handleMouseOver(e)})),n.on("mouseout",(function(e){return n.handleMouseOut(e)})),n.on(n.volumeControl,["slideractive"],n.sliderActive_),n.on(n.volumeControl,["sliderinactive"],n.sliderInactive_),n}L.default(t,e);var i=t.prototype;return i.sliderActive_=function(){this.addClass("vjs-slider-active")},i.sliderInactive_=function(){this.removeClass("vjs-slider-active")},i.volumePanelState_=function(){this.volumeControl.hasClass("vjs-hidden")&&this.muteToggle.hasClass("vjs-hidden")&&this.addClass("vjs-hidden"),this.volumeControl.hasClass("vjs-hidden")&&!this.muteToggle.hasClass("vjs-hidden")&&this.addClass("vjs-mute-toggle-only")},i.createEl=function(){var t="vjs-volume-panel-horizontal";return this.options_.inline||(t="vjs-volume-panel-vertical"),e.prototype.createEl.call(this,"div",{className:"vjs-volume-panel vjs-control "+t})},i.dispose=function(){this.handleMouseOut(),e.prototype.dispose.call(this)},i.handleVolumeControlKeyUp=function(e){R.default.isEventKey(e,"Esc")&&this.muteToggle.focus()},i.handleMouseOver=function(e){this.addClass("vjs-hover"),yt(k.default,"keyup",this.handleKeyPressHandler_)},i.handleMouseOut=function(e){this.removeClass("vjs-hover"),bt(k.default,"keyup",this.handleKeyPressHandler_)},i.handleKeyPress=function(e){R.default.isEventKey(e,"Esc")&&this.handleMouseOut()},t}(Kt);On.prototype.options_={children:["muteToggle","volumeControl"]},Kt.registerComponent("VolumePanel",On);var Un=function(e){function t(t,i){var n;return n=e.call(this,t,i)||this,i&&(n.menuButton_=i.menuButton),n.focusedChild_=-1,n.on("keydown",(function(e){return n.handleKeyDown(e)})),n.boundHandleBlur_=function(e){return n.handleBlur(e)},n.boundHandleTapClick_=function(e){return n.handleTapClick(e)},n}L.default(t,e);var i=t.prototype;return i.addEventListenerForItem=function(e){e instanceof Kt&&(this.on(e,"blur",this.boundHandleBlur_),this.on(e,["tap","click"],this.boundHandleTapClick_))},i.removeEventListenerForItem=function(e){e instanceof Kt&&(this.off(e,"blur",this.boundHandleBlur_),this.off(e,["tap","click"],this.boundHandleTapClick_))},i.removeChild=function(t){"string"==typeof t&&(t=this.getChild(t)),this.removeEventListenerForItem(t),e.prototype.removeChild.call(this,t)},i.addItem=function(e){var t=this.addChild(e);t&&this.addEventListenerForItem(t)},i.createEl=function(){var t=this.options_.contentElType||"ul";this.contentEl_=xe(t,{className:"vjs-menu-content"}),this.contentEl_.setAttribute("role","menu");var i=e.prototype.createEl.call(this,"div",{append:this.contentEl_,className:"vjs-menu"});return i.appendChild(this.contentEl_),yt(i,"click",(function(e){e.preventDefault(),e.stopImmediatePropagation()})),i},i.dispose=function(){this.contentEl_=null,this.boundHandleBlur_=null,this.boundHandleTapClick_=null,e.prototype.dispose.call(this)},i.handleBlur=function(e){var t=e.relatedTarget||k.default.activeElement;if(!this.children().some((function(e){return e.el()===t}))){var i=this.menuButton_;i&&i.buttonPressed_&&t!==i.el().firstChild&&i.unpressButton()}},i.handleTapClick=function(e){if(this.menuButton_){this.menuButton_.unpressButton();var t=this.children();if(!Array.isArray(t))return;var i=t.filter((function(t){return t.el()===e.target}))[0];if(!i)return;"CaptionSettingsMenuItem"!==i.name()&&this.menuButton_.focus()}},i.handleKeyDown=function(e){R.default.isEventKey(e,"Left")||R.default.isEventKey(e,"Down")?(e.preventDefault(),e.stopPropagation(),this.stepForward()):(R.default.isEventKey(e,"Right")||R.default.isEventKey(e,"Up"))&&(e.preventDefault(),e.stopPropagation(),this.stepBack())},i.stepForward=function(){var e=0;void 0!==this.focusedChild_&&(e=this.focusedChild_+1),this.focus(e)},i.stepBack=function(){var e=0;void 0!==this.focusedChild_&&(e=this.focusedChild_-1),this.focus(e)},i.focus=function(e){void 0===e&&(e=0);var t=this.children().slice();t.length&&t[0].hasClass("vjs-menu-title")&&t.shift(),t.length>0&&(e<0?e=0:e>=t.length&&(e=t.length-1),this.focusedChild_=e,t[e].el_.focus())},t}(Kt);Kt.registerComponent("Menu",Un);var Mn=function(e){function t(t,i){var n;void 0===i&&(i={}),(n=e.call(this,t,i)||this).menuButton_=new nn(t,i),n.menuButton_.controlText(n.controlText_),n.menuButton_.el_.setAttribute("aria-haspopup","true");var r=nn.prototype.buildCSSClass();n.menuButton_.el_.className=n.buildCSSClass()+" "+r,n.menuButton_.removeClass("vjs-control"),n.addChild(n.menuButton_),n.update(),n.enabled_=!0;var a=function(e){return n.handleClick(e)};return n.handleMenuKeyUp_=function(e){return n.handleMenuKeyUp(e)},n.on(n.menuButton_,"tap",a),n.on(n.menuButton_,"click",a),n.on(n.menuButton_,"keydown",(function(e){return n.handleKeyDown(e)})),n.on(n.menuButton_,"mouseenter",(function(){n.addClass("vjs-hover"),n.menu.show(),yt(k.default,"keyup",n.handleMenuKeyUp_)})),n.on("mouseleave",(function(e){return n.handleMouseLeave(e)})),n.on("keydown",(function(e){return n.handleSubmenuKeyDown(e)})),n}L.default(t,e);var i=t.prototype;return i.update=function(){var e=this.createMenu();this.menu&&(this.menu.dispose(),this.removeChild(this.menu)),this.menu=e,this.addChild(e),this.buttonPressed_=!1,this.menuButton_.el_.setAttribute("aria-expanded","false"),this.items&&this.items.length<=this.hideThreshold_?this.hide():this.show()},i.createMenu=function(){var e=new Un(this.player_,{menuButton:this});if(this.hideThreshold_=0,this.options_.title){var t=xe("li",{className:"vjs-menu-title",textContent:Ht(this.options_.title),tabIndex:-1}),i=new Kt(this.player_,{el:t});e.addItem(i)}if(this.items=this.createItems(),this.items)for(var n=0;n-1&&"showing"===a.mode){i=!1;break}}i!==this.isSelected_&&this.selected(i)},i.handleSelectedLanguageChange=function(e){for(var t=this.player().textTracks(),i=!0,n=0,r=t.length;n-1&&"showing"===a.mode){i=!1;break}}i&&(this.player_.cache_.selectedLanguage={enabled:!1})},t}(jn);Kt.registerComponent("OffTextTrackMenuItem",Vn);var Hn=function(e){function t(t,i){return void 0===i&&(i={}),i.tracks=t.textTracks(),e.call(this,t,i)||this}return L.default(t,e),t.prototype.createItems=function(e,t){var i;void 0===e&&(e=[]),void 0===t&&(t=jn),this.label_&&(i=this.label_+" off"),e.push(new Vn(this.player_,{kinds:this.kinds_,kind:this.kind_,label:i})),this.hideThreshold_+=1;var n=this.player_.textTracks();Array.isArray(this.kinds_)||(this.kinds_=[this.kind_]);for(var r=0;r-1){var s=new t(this.player_,{track:a,kinds:this.kinds_,kind:this.kind_,selectable:!0,multiSelectable:!1});s.addClass("vjs-"+a.kind+"-menu-item"),e.push(s)}}return e},t}(Fn);Kt.registerComponent("TextTrackButton",Hn);var zn=function(e){function t(t,i){var n,r=i.track,a=i.cue,s=t.currentTime();return i.selectable=!0,i.multiSelectable=!1,i.label=a.text,i.selected=a.startTime<=s&&s=0;t--){var i=e[t];if(i.kind===this.kind_)return i}},i.getMenuCaption=function(){return this.track_&&this.track_.label?this.track_.label:this.localize(Ht(this.kind_))},i.createMenu=function(){return this.options_.title=this.getMenuCaption(),e.prototype.createMenu.call(this)},i.createItems=function(){var e=[];if(!this.track_)return e;var t=this.track_.cues;if(!t)return e;for(var i=0,n=t.length;i-1&&(n.label_="captions"),n.menuButton_.controlText(Ht(n.label_)),n}L.default(t,e);var i=t.prototype;return i.buildCSSClass=function(){return"vjs-subs-caps-button "+e.prototype.buildCSSClass.call(this)},i.buildWrapperCSSClass=function(){return"vjs-subs-caps-button "+e.prototype.buildWrapperCSSClass.call(this)},i.createItems=function(){var t=[];return this.player().tech_&&this.player().tech_.featuresNativeTextTracks||!this.player().getChild("textTrackSettings")||(t.push(new qn(this.player_,{kind:this.label_})),this.hideThreshold_+=1),t=e.prototype.createItems.call(this,t,Xn)},t}(Hn);Qn.prototype.kinds_=["captions","subtitles"],Qn.prototype.controlText_="Subtitles",Kt.registerComponent("SubsCapsButton",Qn);var $n=function(e){function t(t,i){var n,r=i.track,a=t.audioTracks();i.label=r.label||r.language||"Unknown",i.selected=r.enabled,(n=e.call(this,t,i)||this).track=r,n.addClass("vjs-"+r.kind+"-menu-item");var s=function(){for(var e=arguments.length,t=new Array(e),i=0;i=0;i--)t.push(new Zn(this.player(),{rate:e[i]+"x"}));return t},i.updateARIAAttributes=function(){this.el().setAttribute("aria-valuenow",this.player().playbackRate())},i.handleClick=function(e){for(var t=this.player().playbackRate(),i=this.playbackRates(),n=i[0],r=0;rt){n=i[r];break}this.player().playbackRate(n)},i.handlePlaybackRateschange=function(e){this.update()},i.playbackRates=function(){var e=this.player();return e.playbackRates&&e.playbackRates()||[]},i.playbackRateSupported=function(){return this.player().tech_&&this.player().tech_.featuresPlaybackRate&&this.playbackRates()&&this.playbackRates().length>0},i.updateVisibility=function(e){this.playbackRateSupported()?this.removeClass("vjs-hidden"):this.addClass("vjs-hidden")},i.updateLabel=function(e){this.playbackRateSupported()&&(this.labelEl_.textContent=this.player().playbackRate()+"x")},t}(Mn);er.prototype.controlText_="Playback Rate",Kt.registerComponent("PlaybackRateMenuButton",er);var tr=function(e){function t(){return e.apply(this,arguments)||this}L.default(t,e);var i=t.prototype;return i.buildCSSClass=function(){return"vjs-spacer "+e.prototype.buildCSSClass.call(this)},i.createEl=function(t,i,n){return void 0===t&&(t="div"),void 0===i&&(i={}),void 0===n&&(n={}),i.className||(i.className=this.buildCSSClass()),e.prototype.createEl.call(this,t,i,n)},t}(Kt);Kt.registerComponent("Spacer",tr);var ir=function(e){function t(){return e.apply(this,arguments)||this}L.default(t,e);var i=t.prototype;return i.buildCSSClass=function(){return"vjs-custom-control-spacer "+e.prototype.buildCSSClass.call(this)},i.createEl=function(){return e.prototype.createEl.call(this,"div",{className:this.buildCSSClass(),textContent:" "})},t}(tr);Kt.registerComponent("CustomControlSpacer",ir);var nr=function(e){function t(){return e.apply(this,arguments)||this}return L.default(t,e),t.prototype.createEl=function(){return e.prototype.createEl.call(this,"div",{className:"vjs-control-bar",dir:"ltr"})},t}(Kt);nr.prototype.options_={children:["playToggle","volumePanel","currentTimeDisplay","timeDivider","durationDisplay","progressControl","liveDisplay","seekToLive","remainingTimeDisplay","customControlSpacer","playbackRateMenuButton","chaptersButton","descriptionsButton","subsCapsButton","audioTrackButton","fullscreenToggle"]},"exitPictureInPicture"in k.default&&nr.prototype.options_.children.splice(nr.prototype.options_.children.length-1,0,"pictureInPictureToggle"),Kt.registerComponent("ControlBar",nr);var rr=function(e){function t(t,i){var n;return(n=e.call(this,t,i)||this).on(t,"error",(function(e){return n.open(e)})),n}L.default(t,e);var i=t.prototype;return i.buildCSSClass=function(){return"vjs-error-display "+e.prototype.buildCSSClass.call(this)},i.content=function(){var e=this.player().error();return e?this.localize(e.message):""},t}(si);rr.prototype.options_=P.default({},si.prototype.options_,{pauseOnOpen:!1,fillAlways:!0,temporary:!1,uncloseable:!0}),Kt.registerComponent("ErrorDisplay",rr);var ar=["#000","Black"],sr=["#00F","Blue"],or=["#0FF","Cyan"],ur=["#0F0","Green"],lr=["#F0F","Magenta"],hr=["#F00","Red"],dr=["#FFF","White"],cr=["#FF0","Yellow"],fr=["1","Opaque"],pr=["0.5","Semi-Transparent"],mr=["0","Transparent"],_r={backgroundColor:{selector:".vjs-bg-color > select",id:"captions-background-color-%s",label:"Color",options:[ar,dr,hr,ur,sr,cr,lr,or]},backgroundOpacity:{selector:".vjs-bg-opacity > select",id:"captions-background-opacity-%s",label:"Transparency",options:[fr,pr,mr]},color:{selector:".vjs-fg-color > select",id:"captions-foreground-color-%s",label:"Color",options:[dr,ar,hr,ur,sr,cr,lr,or]},edgeStyle:{selector:".vjs-edge-style > select",id:"%s",label:"Text Edge Style",options:[["none","None"],["raised","Raised"],["depressed","Depressed"],["uniform","Uniform"],["dropshadow","Dropshadow"]]},fontFamily:{selector:".vjs-font-family > select",id:"captions-font-family-%s",label:"Font Family",options:[["proportionalSansSerif","Proportional Sans-Serif"],["monospaceSansSerif","Monospace Sans-Serif"],["proportionalSerif","Proportional Serif"],["monospaceSerif","Monospace Serif"],["casual","Casual"],["script","Script"],["small-caps","Small Caps"]]},fontPercent:{selector:".vjs-font-percent > select",id:"captions-font-size-%s",label:"Font Size",options:[["0.50","50%"],["0.75","75%"],["1.00","100%"],["1.25","125%"],["1.50","150%"],["1.75","175%"],["2.00","200%"],["3.00","300%"],["4.00","400%"]],default:2,parser:function(e){return"1.00"===e?null:Number(e)}},textOpacity:{selector:".vjs-text-opacity > select",id:"captions-foreground-opacity-%s",label:"Transparency",options:[fr,pr]},windowColor:{selector:".vjs-window-color > select",id:"captions-window-color-%s",label:"Color"},windowOpacity:{selector:".vjs-window-opacity > select",id:"captions-window-opacity-%s",label:"Transparency",options:[mr,pr,fr]}};function gr(e,t){if(t&&(e=t(e)),e&&"none"!==e)return e}_r.windowColor.options=_r.backgroundColor.options;var vr=function(e){function t(t,i){var n;return i.temporary=!1,(n=e.call(this,t,i)||this).updateDisplay=n.updateDisplay.bind(I.default(n)),n.fill(),n.hasBeenOpened_=n.hasBeenFilled_=!0,n.endDialog=xe("p",{className:"vjs-control-text",textContent:n.localize("End of dialog window.")}),n.el().appendChild(n.endDialog),n.setDefaults(),void 0===i.persistTextTrackSettings&&(n.options_.persistTextTrackSettings=n.options_.playerOptions.persistTextTrackSettings),n.on(n.$(".vjs-done-button"),"click",(function(){n.saveSettings(),n.close()})),n.on(n.$(".vjs-default-button"),"click",(function(){n.setDefaults(),n.updateDisplay()})),J(_r,(function(e){n.on(n.$(e.selector),"change",n.updateDisplay)})),n.options_.persistTextTrackSettings&&n.restoreSettings(),n}L.default(t,e);var i=t.prototype;return i.dispose=function(){this.endDialog=null,e.prototype.dispose.call(this)},i.createElSelect_=function(e,t,i){var n=this;void 0===t&&(t=""),void 0===i&&(i="label");var r=_r[e],a=r.id.replace("%s",this.id_),s=[t,a].join(" ").trim();return["<"+i+' id="'+a+'" class="'+("label"===i?"vjs-label":"")+'">',this.localize(r.label),"",'").join("")},i.createElFgColor_=function(){var e="captions-text-legend-"+this.id_;return['
    ','',this.localize("Text"),"",this.createElSelect_("color",e),'',this.createElSelect_("textOpacity",e),"","
    "].join("")},i.createElBgColor_=function(){var e="captions-background-"+this.id_;return['
    ','',this.localize("Background"),"",this.createElSelect_("backgroundColor",e),'',this.createElSelect_("backgroundOpacity",e),"","
    "].join("")},i.createElWinColor_=function(){var e="captions-window-"+this.id_;return['
    ','',this.localize("Window"),"",this.createElSelect_("windowColor",e),'',this.createElSelect_("windowOpacity",e),"","
    "].join("")},i.createElColors_=function(){return xe("div",{className:"vjs-track-settings-colors",innerHTML:[this.createElFgColor_(),this.createElBgColor_(),this.createElWinColor_()].join("")})},i.createElFont_=function(){return xe("div",{className:"vjs-track-settings-font",innerHTML:['
    ',this.createElSelect_("fontPercent","","legend"),"
    ",'
    ',this.createElSelect_("edgeStyle","","legend"),"
    ",'
    ',this.createElSelect_("fontFamily","","legend"),"
    "].join("")})},i.createElControls_=function(){var e=this.localize("restore all settings to the default values");return xe("div",{className:"vjs-track-settings-controls",innerHTML:['",'"].join("")})},i.content=function(){return[this.createElColors_(),this.createElFont_(),this.createElControls_()]},i.label=function(){return this.localize("Caption Settings Dialog")},i.description=function(){return this.localize("Beginning of dialog window. Escape will cancel and close the window.")},i.buildCSSClass=function(){return e.prototype.buildCSSClass.call(this)+" vjs-text-track-settings"},i.getValues=function(){var e,t,i,n=this;return t=function(e,t,i){var r,a,s=(r=n.$(t.selector),a=t.parser,gr(r.options[r.options.selectedIndex].value,a));return void 0!==s&&(e[i]=s),e},void 0===(i={})&&(i=0),$(e=_r).reduce((function(i,n){return t(i,e[n],n)}),i)},i.setValues=function(e){var t=this;J(_r,(function(i,n){!function(e,t,i){if(t)for(var n=0;nthis.options_.liveTolerance;this.timeupdateSeen_&&n!==1/0||(a=!1),a!==this.behindLiveEdge_&&(this.behindLiveEdge_=a,this.trigger("liveedgechange"))}},i.handleDurationchange=function(){this.toggleTracking()},i.toggleTracking=function(){this.player_.duration()===1/0&&this.liveWindow()>=this.options_.trackingThreshold?(this.player_.options_.liveui&&this.player_.addClass("vjs-liveui"),this.startTracking()):(this.player_.removeClass("vjs-liveui"),this.stopTracking())},i.startTracking=function(){this.isTracking()||(this.timeupdateSeen_||(this.timeupdateSeen_=this.player_.hasStarted()),this.trackingInterval_=this.setInterval(this.trackLiveHandler_,30),this.trackLive_(),this.on(this.player_,["play","pause"],this.trackLiveHandler_),this.timeupdateSeen_?this.on(this.player_,"seeked",this.handleSeeked_):(this.one(this.player_,"play",this.handlePlay_),this.one(this.player_,"timeupdate",this.handleFirstTimeupdate_)))},i.handleFirstTimeupdate=function(){this.timeupdateSeen_=!0,this.on(this.player_,"seeked",this.handleSeeked_)},i.handleSeeked=function(){var e=Math.abs(this.liveCurrentTime()-this.player_.currentTime());this.seekedBehindLive_=this.nextSeekedFromUser_&&e>2,this.nextSeekedFromUser_=!1,this.trackLive_()},i.handlePlay=function(){this.one(this.player_,"timeupdate",this.seekToLiveEdge_)},i.reset_=function(){this.lastTime_=-1,this.pastSeekEnd_=0,this.lastSeekEnd_=-1,this.behindLiveEdge_=!0,this.timeupdateSeen_=!1,this.seekedBehindLive_=!1,this.nextSeekedFromUser_=!1,this.clearInterval(this.trackingInterval_),this.trackingInterval_=null,this.off(this.player_,["play","pause"],this.trackLiveHandler_),this.off(this.player_,"seeked",this.handleSeeked_),this.off(this.player_,"play",this.handlePlay_),this.off(this.player_,"timeupdate",this.handleFirstTimeupdate_),this.off(this.player_,"timeupdate",this.seekToLiveEdge_)},i.nextSeekedFromUser=function(){this.nextSeekedFromUser_=!0},i.stopTracking=function(){this.isTracking()&&(this.reset_(),this.trigger("liveedgechange"))},i.seekableEnd=function(){for(var e=this.player_.seekable(),t=[],i=e?e.length:0;i--;)t.push(e.end(i));return t.length?t.sort()[t.length-1]:1/0},i.seekableStart=function(){for(var e=this.player_.seekable(),t=[],i=e?e.length:0;i--;)t.push(e.start(i));return t.length?t.sort()[0]:0},i.liveWindow=function(){var e=this.liveCurrentTime();return e===1/0?0:e-this.seekableStart()},i.isLive=function(){return this.isTracking()},i.atLiveEdge=function(){return!this.behindLiveEdge()},i.liveCurrentTime=function(){return this.pastSeekEnd()+this.seekableEnd()},i.pastSeekEnd=function(){var e=this.seekableEnd();return-1!==this.lastSeekEnd_&&e!==this.lastSeekEnd_&&(this.pastSeekEnd_=0),this.lastSeekEnd_=e,this.pastSeekEnd_},i.behindLiveEdge=function(){return this.behindLiveEdge_},i.isTracking=function(){return"number"==typeof this.trackingInterval_},i.seekToLiveEdge=function(){this.seekedBehindLive_=!1,this.atLiveEdge()||(this.nextSeekedFromUser_=!1,this.player_.currentTime(this.liveCurrentTime()))},i.dispose=function(){this.off(k.default,"visibilitychange",this.handleVisibilityChange_),this.stopTracking(),e.prototype.dispose.call(this)},t}(Kt);Kt.registerComponent("LiveTracker",Sr);var Tr,Er=function(e){var t=e.el();if(t.hasAttribute("src"))return e.triggerSourceset(t.src),!0;var i=e.$$("source"),n=[],r="";if(!i.length)return!1;for(var a=0;a=2&&r.push("loadeddata"),e.readyState>=3&&r.push("canplay"),e.readyState>=4&&r.push("canplaythrough"),this.ready((function(){r.forEach((function(e){this.trigger(e)}),this)}))}},i.setScrubbing=function(e){this.isScrubbing_=e},i.scrubbing=function(){return this.isScrubbing_},i.setCurrentTime=function(e){try{this.isScrubbing_&&this.el_.fastSeek&&Ee?this.el_.fastSeek(e):this.el_.currentTime=e}catch(e){K(e,"Video is not ready. (Video.js)")}},i.duration=function(){var e=this;if(this.el_.duration===1/0&&le&&pe&&0===this.el_.currentTime){return this.on("timeupdate",(function t(){e.el_.currentTime>0&&(e.el_.duration===1/0&&e.trigger("durationchange"),e.off("timeupdate",t))})),NaN}return this.el_.duration||NaN},i.width=function(){return this.el_.offsetWidth},i.height=function(){return this.el_.offsetHeight},i.proxyWebkitFullscreen_=function(){var e=this;if("webkitDisplayingFullscreen"in this.el_){var t=function(){this.trigger("fullscreenchange",{isFullscreen:!1})},i=function(){"webkitPresentationMode"in this.el_&&"picture-in-picture"!==this.el_.webkitPresentationMode&&(this.one("webkitendfullscreen",t),this.trigger("fullscreenchange",{isFullscreen:!0,nativeIOSFullscreen:!0}))};this.on("webkitbeginfullscreen",i),this.on("dispose",(function(){e.off("webkitbeginfullscreen",i),e.off("webkitendfullscreen",t)}))}},i.supportsFullScreen=function(){if("function"==typeof this.el_.webkitEnterFullScreen){var e=C.default.navigator&&C.default.navigator.userAgent||"";if(/Android/.test(e)||!/Chrome|Mac OS X 10.5/.test(e))return!0}return!1},i.enterFullScreen=function(){var e=this.el_;if(e.paused&&e.networkState<=e.HAVE_METADATA)ii(this.el_.play()),this.setTimeout((function(){e.pause();try{e.webkitEnterFullScreen()}catch(e){this.trigger("fullscreenerror",e)}}),0);else try{e.webkitEnterFullScreen()}catch(e){this.trigger("fullscreenerror",e)}},i.exitFullScreen=function(){this.el_.webkitDisplayingFullscreen?this.el_.webkitExitFullScreen():this.trigger("fullscreenerror",new Error("The video is not fullscreen"))},i.requestPictureInPicture=function(){return this.el_.requestPictureInPicture()},i.src=function(e){if(void 0===e)return this.el_.src;this.setSrc(e)},i.reset=function(){t.resetMediaElement(this.el_)},i.currentSrc=function(){return this.currentSource_?this.currentSource_.src:this.el_.currentSrc},i.setControls=function(e){this.el_.controls=!!e},i.addTextTrack=function(t,i,n){return this.featuresNativeTextTracks?this.el_.addTextTrack(t,i,n):e.prototype.addTextTrack.call(this,t,i,n)},i.createRemoteTextTrack=function(t){if(!this.featuresNativeTextTracks)return e.prototype.createRemoteTextTrack.call(this,t);var i=k.default.createElement("track");return t.kind&&(i.kind=t.kind),t.label&&(i.label=t.label),(t.language||t.srclang)&&(i.srclang=t.language||t.srclang),t.default&&(i.default=t.default),t.id&&(i.id=t.id),t.src&&(i.src=t.src),i},i.addRemoteTextTrack=function(t,i){var n=e.prototype.addRemoteTextTrack.call(this,t,i);return this.featuresNativeTextTracks&&this.el().appendChild(n),n},i.removeRemoteTextTrack=function(t){if(e.prototype.removeRemoteTextTrack.call(this,t),this.featuresNativeTextTracks)for(var i=this.$$("track"),n=i.length;n--;)t!==i[n]&&t!==i[n].track||this.el().removeChild(i[n])},i.getVideoPlaybackQuality=function(){if("function"==typeof this.el().getVideoPlaybackQuality)return this.el().getVideoPlaybackQuality();var e={};return void 0!==this.el().webkitDroppedFrameCount&&void 0!==this.el().webkitDecodedFrameCount&&(e.droppedVideoFrames=this.el().webkitDroppedFrameCount,e.totalVideoFrames=this.el().webkitDecodedFrameCount),C.default.performance&&"function"==typeof C.default.performance.now?e.creationTime=C.default.performance.now():C.default.performance&&C.default.performance.timing&&"number"==typeof C.default.performance.timing.navigationStart&&(e.creationTime=C.default.Date.now()-C.default.performance.timing.navigationStart),e},t}(Ui);Ir(Lr,"TEST_VID",(function(){if(ke()){var e=k.default.createElement("video"),t=k.default.createElement("track");return t.kind="captions",t.srclang="en",t.label="English",e.appendChild(t),e}})),Lr.isSupported=function(){try{Lr.TEST_VID.volume=.5}catch(e){return!1}return!(!Lr.TEST_VID||!Lr.TEST_VID.canPlayType)},Lr.canPlayType=function(e){return Lr.TEST_VID.canPlayType(e)},Lr.canPlaySource=function(e,t){return Lr.canPlayType(e.type)},Lr.canControlVolume=function(){try{var e=Lr.TEST_VID.volume;return Lr.TEST_VID.volume=e/2+.1,e!==Lr.TEST_VID.volume}catch(e){return!1}},Lr.canMuteVolume=function(){try{var e=Lr.TEST_VID.muted;return Lr.TEST_VID.muted=!e,Lr.TEST_VID.muted?Ve(Lr.TEST_VID,"muted","muted"):He(Lr.TEST_VID,"muted"),e!==Lr.TEST_VID.muted}catch(e){return!1}},Lr.canControlPlaybackRate=function(){if(le&&pe&&me<58)return!1;try{var e=Lr.TEST_VID.playbackRate;return Lr.TEST_VID.playbackRate=e/2+.1,e!==Lr.TEST_VID.playbackRate}catch(e){return!1}},Lr.canOverrideAttributes=function(){try{var e=function(){};Object.defineProperty(k.default.createElement("video"),"src",{get:e,set:e}),Object.defineProperty(k.default.createElement("audio"),"src",{get:e,set:e}),Object.defineProperty(k.default.createElement("video"),"innerHTML",{get:e,set:e}),Object.defineProperty(k.default.createElement("audio"),"innerHTML",{get:e,set:e})}catch(e){return!1}return!0},Lr.supportsNativeTextTracks=function(){return Ee||Te&&pe},Lr.supportsNativeVideoTracks=function(){return!(!Lr.TEST_VID||!Lr.TEST_VID.videoTracks)},Lr.supportsNativeAudioTracks=function(){return!(!Lr.TEST_VID||!Lr.TEST_VID.audioTracks)},Lr.Events=["loadstart","suspend","abort","error","emptied","stalled","loadedmetadata","loadeddata","canplay","canplaythrough","playing","waiting","seeking","seeked","ended","durationchange","timeupdate","progress","play","pause","ratechange","resize","volumechange"],[["featuresVolumeControl","canControlVolume"],["featuresMuteControl","canMuteVolume"],["featuresPlaybackRate","canControlPlaybackRate"],["featuresSourceset","canOverrideAttributes"],["featuresNativeTextTracks","supportsNativeTextTracks"],["featuresNativeVideoTracks","supportsNativeVideoTracks"],["featuresNativeAudioTracks","supportsNativeAudioTracks"]].forEach((function(e){var t=e[0],i=e[1];Ir(Lr.prototype,t,(function(){return Lr[i]()}),!0)})),Lr.prototype.movingMediaElementInDOM=!Te,Lr.prototype.featuresFullscreenResize=!0,Lr.prototype.featuresProgressEvents=!0,Lr.prototype.featuresTimeupdateEvents=!0,Lr.patchCanPlayType=function(){he>=4&&!ce&&!pe&&(Tr=Lr.TEST_VID&&Lr.TEST_VID.constructor.prototype.canPlayType,Lr.TEST_VID.constructor.prototype.canPlayType=function(e){return e&&/^application\/(?:x-|vnd\.apple\.)mpegurl/i.test(e)?"maybe":Tr.call(this,e)})},Lr.unpatchCanPlayType=function(){var e=Lr.TEST_VID.constructor.prototype.canPlayType;return Tr&&(Lr.TEST_VID.constructor.prototype.canPlayType=Tr),e},Lr.patchCanPlayType(),Lr.disposeMediaElement=function(e){if(e){for(e.parentNode&&e.parentNode.removeChild(e);e.hasChildNodes();)e.removeChild(e.firstChild);e.removeAttribute("src"),"function"==typeof e.load&&function(){try{e.load()}catch(e){}}()}},Lr.resetMediaElement=function(e){if(e){for(var t=e.querySelectorAll("source"),i=t.length;i--;)e.removeChild(t[i]);e.removeAttribute("src"),"function"==typeof e.load&&function(){try{e.load()}catch(e){}}()}},["muted","defaultMuted","autoplay","controls","loop","playsinline"].forEach((function(e){Lr.prototype[e]=function(){return this.el_[e]||this.el_.hasAttribute(e)}})),["muted","defaultMuted","autoplay","loop","playsinline"].forEach((function(e){Lr.prototype["set"+Ht(e)]=function(t){this.el_[e]=t,t?this.el_.setAttribute(e,e):this.el_.removeAttribute(e)}})),["paused","currentTime","buffered","volume","poster","preload","error","seeking","seekable","ended","playbackRate","defaultPlaybackRate","disablePictureInPicture","played","networkState","readyState","videoWidth","videoHeight","crossOrigin"].forEach((function(e){Lr.prototype[e]=function(){return this.el_[e]}})),["volume","src","poster","preload","playbackRate","defaultPlaybackRate","disablePictureInPicture","crossOrigin"].forEach((function(e){Lr.prototype["set"+Ht(e)]=function(t){this.el_[e]=t}})),["pause","load","play"].forEach((function(e){Lr.prototype[e]=function(){return this.el_[e]()}})),Ui.withSourceHandlers(Lr),Lr.nativeSourceHandler={},Lr.nativeSourceHandler.canPlayType=function(e){try{return Lr.TEST_VID.canPlayType(e)}catch(e){return""}},Lr.nativeSourceHandler.canHandleSource=function(e,t){if(e.type)return Lr.nativeSourceHandler.canPlayType(e.type);if(e.src){var i=Ei(e.src);return Lr.nativeSourceHandler.canPlayType("video/"+i)}return""},Lr.nativeSourceHandler.handleSource=function(e,t,i){t.setSrc(e.src)},Lr.nativeSourceHandler.dispose=function(){},Lr.registerSourceHandler(Lr.nativeSourceHandler),Ui.registerTech("Html5",Lr);var xr=["progress","abort","suspend","emptied","stalled","loadedmetadata","loadeddata","timeupdate","resize","volumechange","texttrackchange"],Rr={canplay:"CanPlay",canplaythrough:"CanPlayThrough",playing:"Playing",seeked:"Seeked"},Dr=["tiny","xsmall","small","medium","large","xlarge","huge"],Or={};Dr.forEach((function(e){var t="x"===e.charAt(0)?"x-"+e.substring(1):e;Or[e]="vjs-layout-"+t}));var Ur={tiny:210,xsmall:320,small:425,medium:768,large:1440,xlarge:2560,huge:1/0},Mr=function(e){function t(i,n,r){var a;if(i.id=i.id||n.id||"vjs_video_"+ct(),(n=Z(t.getTagSettings(i),n)).initChildren=!1,n.createEl=!1,n.evented=!1,n.reportTouchActivity=!1,!n.language)if("function"==typeof i.closest){var s=i.closest("[lang]");s&&s.getAttribute&&(n.language=s.getAttribute("lang"))}else for(var o=i;o&&1===o.nodeType;){if(Ne(o).hasOwnProperty("lang")){n.language=o.getAttribute("lang");break}o=o.parentNode}if((a=e.call(this,null,n,r)||this).boundDocumentFullscreenChange_=function(e){return a.documentFullscreenChange_(e)},a.boundFullWindowOnEscKey_=function(e){return a.fullWindowOnEscKey(e)},a.boundUpdateStyleEl_=function(e){return a.updateStyleEl_(e)},a.boundApplyInitTime_=function(e){return a.applyInitTime_(e)},a.boundUpdateCurrentBreakpoint_=function(e){return a.updateCurrentBreakpoint_(e)},a.boundHandleTechClick_=function(e){return a.handleTechClick_(e)},a.boundHandleTechDoubleClick_=function(e){return a.handleTechDoubleClick_(e)},a.boundHandleTechTouchStart_=function(e){return a.handleTechTouchStart_(e)},a.boundHandleTechTouchMove_=function(e){return a.handleTechTouchMove_(e)},a.boundHandleTechTouchEnd_=function(e){return a.handleTechTouchEnd_(e)},a.boundHandleTechTap_=function(e){return a.handleTechTap_(e)},a.isFullscreen_=!1,a.log=X(a.id_),a.fsApi_=H,a.isPosterFromTech_=!1,a.queuedCallbacks_=[],a.isReady_=!1,a.hasStarted_=!1,a.userActive_=!1,a.debugEnabled_=!1,!a.options_||!a.options_.techOrder||!a.options_.techOrder.length)throw new Error("No techOrder specified. Did you overwrite videojs.options instead of just changing the properties you want to override?");if(a.tag=i,a.tagAttributes=i&&Ne(i),a.language(a.options_.language),n.languages){var u={};Object.getOwnPropertyNames(n.languages).forEach((function(e){u[e.toLowerCase()]=n.languages[e]})),a.languages_=u}else a.languages_=t.prototype.options_.languages;a.resetCache_(),a.poster_=n.poster||"",a.controls_=!!n.controls,i.controls=!1,i.removeAttribute("controls"),a.changingSrc_=!1,a.playCallbacks_=[],a.playTerminatedQueue_=[],i.hasAttribute("autoplay")?a.autoplay(!0):a.autoplay(a.options_.autoplay),n.plugins&&Object.keys(n.plugins).forEach((function(e){if("function"!=typeof a[e])throw new Error('plugin "'+e+'" does not exist')})),a.scrubbing_=!1,a.el_=a.createEl(),Bt(I.default(a),{eventBusKey:"el_"}),a.fsApi_.requestFullscreen&&(yt(k.default,a.fsApi_.fullscreenchange,a.boundDocumentFullscreenChange_),a.on(a.fsApi_.fullscreenchange,a.boundDocumentFullscreenChange_)),a.fluid_&&a.on(["playerreset","resize"],a.boundUpdateStyleEl_);var l=zt(a.options_);n.plugins&&Object.keys(n.plugins).forEach((function(e){a[e](n.plugins[e])})),n.debug&&a.debug(!0),a.options_.playerOptions=l,a.middleware_=[],a.playbackRates(n.playbackRates),a.initChildren(),a.isAudio("audio"===i.nodeName.toLowerCase()),a.controls()?a.addClass("vjs-controls-enabled"):a.addClass("vjs-controls-disabled"),a.el_.setAttribute("role","region"),a.isAudio()?a.el_.setAttribute("aria-label",a.localize("Audio Player")):a.el_.setAttribute("aria-label",a.localize("Video Player")),a.isAudio()&&a.addClass("vjs-audio"),a.flexNotSupported_()&&a.addClass("vjs-no-flex"),ye&&a.addClass("vjs-touch-enabled"),Te||a.addClass("vjs-workinghover"),t.players[a.id_]=I.default(a);var h="7.15.4".split(".")[0];return a.addClass("vjs-v"+h),a.userActive(!0),a.reportUserActivity(),a.one("play",(function(e){return a.listenForUserActivity_(e)})),a.on("stageclick",(function(e){return a.handleStageClick_(e)})),a.on("keydown",(function(e){return a.handleKeyDown(e)})),a.on("languagechange",(function(e){return a.handleLanguagechange(e)})),a.breakpoints(a.options_.breakpoints),a.responsive(a.options_.responsive),a}L.default(t,e);var i=t.prototype;return i.dispose=function(){var i=this;this.trigger("dispose"),this.off("dispose"),bt(k.default,this.fsApi_.fullscreenchange,this.boundDocumentFullscreenChange_),bt(k.default,"keydown",this.boundFullWindowOnEscKey_),this.styleEl_&&this.styleEl_.parentNode&&(this.styleEl_.parentNode.removeChild(this.styleEl_),this.styleEl_=null),t.players[this.id_]=null,this.tag&&this.tag.player&&(this.tag.player=null),this.el_&&this.el_.player&&(this.el_.player=null),this.tech_&&(this.tech_.dispose(),this.isPosterFromTech_=!1,this.poster_=""),this.playerElIngest_&&(this.playerElIngest_=null),this.tag&&(this.tag=null),Fi[this.id()]=null,Oi.names.forEach((function(e){var t=Oi[e],n=i[t.getterName]();n&&n.off&&n.off()})),e.prototype.dispose.call(this)},i.createEl=function(){var t,i=this.tag,n=this.playerElIngest_=i.parentNode&&i.parentNode.hasAttribute&&i.parentNode.hasAttribute("data-vjs-player"),r="video-js"===this.tag.tagName.toLowerCase();n?t=this.el_=i.parentNode:r||(t=this.el_=e.prototype.createEl.call(this,"div"));var a=Ne(i);if(r){for(t=this.el_=i,i=this.tag=k.default.createElement("video");t.children.length;)i.appendChild(t.firstChild);Oe(t,"video-js")||Ue(t,"video-js"),t.appendChild(i),n=this.playerElIngest_=t,Object.keys(t).forEach((function(e){try{i[e]=t[e]}catch(e){}}))}if(i.setAttribute("tabindex","-1"),a.tabindex="-1",(_e||pe&&ve)&&(i.setAttribute("role","application"),a.role="application"),i.removeAttribute("width"),i.removeAttribute("height"),"width"in a&&delete a.width,"height"in a&&delete a.height,Object.getOwnPropertyNames(a).forEach((function(e){r&&"class"===e||t.setAttribute(e,a[e]),r&&i.setAttribute(e,a[e])})),i.playerId=i.id,i.id+="_html5_api",i.className="vjs-tech",i.player=t.player=this,this.addClass("vjs-paused"),!0!==C.default.VIDEOJS_NO_DYNAMIC_STYLE){this.styleEl_=lt("vjs-styles-dimensions");var s=tt(".vjs-styles-defaults"),o=tt("head");o.insertBefore(this.styleEl_,s?s.nextSibling:o.firstChild)}this.fill_=!1,this.fluid_=!1,this.width(this.options_.width),this.height(this.options_.height),this.fill(this.options_.fill),this.fluid(this.options_.fluid),this.aspectRatio(this.options_.aspectRatio),this.crossOrigin(this.options_.crossOrigin||this.options_.crossorigin);for(var u=i.getElementsByTagName("a"),l=0;l0?this.videoWidth()+":"+this.videoHeight():"16:9").split(":"),r=n[1]/n[0];e=void 0!==this.width_?this.width_:void 0!==this.height_?this.height_/r:this.videoWidth()||300,t=void 0!==this.height_?this.height_:e*r,i=/^[^a-zA-Z]/.test(this.id())?"dimensions-"+this.id():this.id()+"-dimensions",this.addClass(i),ht(this.styleEl_,"\n ."+i+" {\n width: "+e+"px;\n height: "+t+"px;\n }\n\n ."+i+".vjs-fluid {\n padding-top: "+100*r+"%;\n }\n ")}else{var a="number"==typeof this.width_?this.width_:this.options_.width,s="number"==typeof this.height_?this.height_:this.options_.height,o=this.tech_&&this.tech_.el();o&&(a>=0&&(o.width=a),s>=0&&(o.height=s))}},i.loadTech_=function(e,t){var i=this;this.tech_&&this.unloadTech_();var n=Ht(e),r=e.charAt(0).toLowerCase()+e.slice(1);"Html5"!==n&&this.tag&&(Ui.getTech("Html5").disposeMediaElement(this.tag),this.tag.player=null,this.tag=null),this.techName_=n,this.isReady_=!1;var a=this.autoplay();("string"==typeof this.autoplay()||!0===this.autoplay()&&this.options_.normalizeAutoplay)&&(a=!1);var s={source:t,autoplay:a,nativeControlsForTouch:this.options_.nativeControlsForTouch,playerId:this.id(),techId:this.id()+"_"+r+"_api",playsinline:this.options_.playsinline,preload:this.options_.preload,loop:this.options_.loop,disablePictureInPicture:this.options_.disablePictureInPicture,muted:this.options_.muted,poster:this.poster(),language:this.language(),playerElIngest:this.playerElIngest_||!1,"vtt.js":this.options_["vtt.js"],canOverridePoster:!!this.options_.techCanOverridePoster,enableSourceset:this.options_.enableSourceset,Promise:this.options_.Promise};Oi.names.forEach((function(e){var t=Oi[e];s[t.getterName]=i[t.privateName]})),Z(s,this.options_[n]),Z(s,this.options_[r]),Z(s,this.options_[e.toLowerCase()]),this.tag&&(s.tag=this.tag),t&&t.src===this.cache_.src&&this.cache_.currentTime>0&&(s.startTime=this.cache_.currentTime);var o=Ui.getTech(e);if(!o)throw new Error("No Tech named '"+n+"' exists! '"+n+"' should be registered using videojs.registerTech()'");this.tech_=new o(s),this.tech_.ready(Ct(this,this.handleTechReady_),!0),ai(this.textTracksJson_||[],this.tech_),xr.forEach((function(e){i.on(i.tech_,e,(function(t){return i["handleTech"+Ht(e)+"_"](t)}))})),Object.keys(Rr).forEach((function(e){i.on(i.tech_,e,(function(t){0===i.tech_.playbackRate()&&i.tech_.seeking()?i.queuedCallbacks_.push({callback:i["handleTech"+Rr[e]+"_"].bind(i),event:t}):i["handleTech"+Rr[e]+"_"](t)}))})),this.on(this.tech_,"loadstart",(function(e){return i.handleTechLoadStart_(e)})),this.on(this.tech_,"sourceset",(function(e){return i.handleTechSourceset_(e)})),this.on(this.tech_,"waiting",(function(e){return i.handleTechWaiting_(e)})),this.on(this.tech_,"ended",(function(e){return i.handleTechEnded_(e)})),this.on(this.tech_,"seeking",(function(e){return i.handleTechSeeking_(e)})),this.on(this.tech_,"play",(function(e){return i.handleTechPlay_(e)})),this.on(this.tech_,"firstplay",(function(e){return i.handleTechFirstPlay_(e)})),this.on(this.tech_,"pause",(function(e){return i.handleTechPause_(e)})),this.on(this.tech_,"durationchange",(function(e){return i.handleTechDurationChange_(e)})),this.on(this.tech_,"fullscreenchange",(function(e,t){return i.handleTechFullscreenChange_(e,t)})),this.on(this.tech_,"fullscreenerror",(function(e,t){return i.handleTechFullscreenError_(e,t)})),this.on(this.tech_,"enterpictureinpicture",(function(e){return i.handleTechEnterPictureInPicture_(e)})),this.on(this.tech_,"leavepictureinpicture",(function(e){return i.handleTechLeavePictureInPicture_(e)})),this.on(this.tech_,"error",(function(e){return i.handleTechError_(e)})),this.on(this.tech_,"posterchange",(function(e){return i.handleTechPosterChange_(e)})),this.on(this.tech_,"textdata",(function(e){return i.handleTechTextData_(e)})),this.on(this.tech_,"ratechange",(function(e){return i.handleTechRateChange_(e)})),this.on(this.tech_,"loadedmetadata",this.boundUpdateStyleEl_),this.usingNativeControls(this.techGet_("controls")),this.controls()&&!this.usingNativeControls()&&this.addTechControlsListeners_(),this.tech_.el().parentNode===this.el()||"Html5"===n&&this.tag||De(this.tech_.el(),this.el()),this.tag&&(this.tag.player=null,this.tag=null)},i.unloadTech_=function(){var e=this;Oi.names.forEach((function(t){var i=Oi[t];e[i.privateName]=e[i.getterName]()})),this.textTracksJson_=ri(this.tech_),this.isReady_=!1,this.tech_.dispose(),this.tech_=!1,this.isPosterFromTech_&&(this.poster_="",this.trigger("posterchange")),this.isPosterFromTech_=!1},i.tech=function(e){return void 0===e&&K.warn("Using the tech directly can be dangerous. I hope you know what you're doing.\nSee https://github.com/videojs/video.js/issues/2617 for more info.\n"),this.tech_},i.addTechControlsListeners_=function(){this.removeTechControlsListeners_(),this.on(this.tech_,"click",this.boundHandleTechClick_),this.on(this.tech_,"dblclick",this.boundHandleTechDoubleClick_),this.on(this.tech_,"touchstart",this.boundHandleTechTouchStart_),this.on(this.tech_,"touchmove",this.boundHandleTechTouchMove_),this.on(this.tech_,"touchend",this.boundHandleTechTouchEnd_),this.on(this.tech_,"tap",this.boundHandleTechTap_)},i.removeTechControlsListeners_=function(){this.off(this.tech_,"tap",this.boundHandleTechTap_),this.off(this.tech_,"touchstart",this.boundHandleTechTouchStart_),this.off(this.tech_,"touchmove",this.boundHandleTechTouchMove_),this.off(this.tech_,"touchend",this.boundHandleTechTouchEnd_),this.off(this.tech_,"click",this.boundHandleTechClick_),this.off(this.tech_,"dblclick",this.boundHandleTechDoubleClick_)},i.handleTechReady_=function(){this.triggerReady(),this.cache_.volume&&this.techCall_("setVolume",this.cache_.volume),this.handleTechPosterChange_(),this.handleTechDurationChange_()},i.handleTechLoadStart_=function(){this.removeClass("vjs-ended"),this.removeClass("vjs-seeking"),this.error(null),this.handleTechDurationChange_(),this.paused()?(this.hasStarted(!1),this.trigger("loadstart")):(this.trigger("loadstart"),this.trigger("firstplay")),this.manualAutoplay_(!0===this.autoplay()&&this.options_.normalizeAutoplay?"play":this.autoplay())},i.manualAutoplay_=function(e){var t=this;if(this.tech_&&"string"==typeof e){var i,n=function(){var e=t.muted();t.muted(!0);var i=function(){t.muted(e)};t.playTerminatedQueue_.push(i);var n=t.play();if(ti(n))return n.catch((function(e){throw i(),new Error("Rejection at manualAutoplay. Restoring muted value. "+(e||""))}))};if("any"!==e||this.muted()?i="muted"!==e||this.muted()?this.play():n():ti(i=this.play())&&(i=i.catch(n)),ti(i))return i.then((function(){t.trigger({type:"autoplay-success",autoplay:e})})).catch((function(){t.trigger({type:"autoplay-failure",autoplay:e})}))}},i.updateSourceCaches_=function(e){void 0===e&&(e="");var t=e,i="";"string"!=typeof t&&(t=e.src,i=e.type),this.cache_.source=this.cache_.source||{},this.cache_.sources=this.cache_.sources||[],t&&!i&&(i=function(e,t){if(!t)return"";if(e.cache_.source.src===t&&e.cache_.source.type)return e.cache_.source.type;var i=e.cache_.sources.filter((function(e){return e.src===t}));if(i.length)return i[0].type;for(var n=e.$$("source"),r=0;r0&&0===this.cache_.lastPlaybackRate&&(this.queuedCallbacks_.forEach((function(e){return e.callback(e.event)})),this.queuedCallbacks_=[]),this.cache_.lastPlaybackRate=this.tech_.playbackRate(),this.trigger("ratechange")},i.handleTechWaiting_=function(){var e=this;this.addClass("vjs-waiting"),this.trigger("waiting");var t=this.currentTime();this.on("timeupdate",(function i(){t!==e.currentTime()&&(e.removeClass("vjs-waiting"),e.off("timeupdate",i))}))},i.handleTechCanPlay_=function(){this.removeClass("vjs-waiting"),this.trigger("canplay")},i.handleTechCanPlayThrough_=function(){this.removeClass("vjs-waiting"),this.trigger("canplaythrough")},i.handleTechPlaying_=function(){this.removeClass("vjs-waiting"),this.trigger("playing")},i.handleTechSeeking_=function(){this.addClass("vjs-seeking"),this.trigger("seeking")},i.handleTechSeeked_=function(){this.removeClass("vjs-seeking"),this.removeClass("vjs-ended"),this.trigger("seeked")},i.handleTechFirstPlay_=function(){this.options_.starttime&&(K.warn("Passing the `starttime` option to the player will be deprecated in 6.0"),this.currentTime(this.options_.starttime)),this.addClass("vjs-has-started"),this.trigger("firstplay")},i.handleTechPause_=function(){this.removeClass("vjs-playing"),this.addClass("vjs-paused"),this.trigger("pause")},i.handleTechEnded_=function(){this.addClass("vjs-ended"),this.removeClass("vjs-waiting"),this.options_.loop?(this.currentTime(0),this.play()):this.paused()||this.pause(),this.trigger("ended")},i.handleTechDurationChange_=function(){this.duration(this.techGet_("duration"))},i.handleTechClick_=function(e){this.controls_&&(this.paused()?ii(this.play()):this.pause())},i.handleTechDoubleClick_=function(e){this.controls_&&(Array.prototype.some.call(this.$$(".vjs-control-bar, .vjs-modal-dialog"),(function(t){return t.contains(e.target)}))||void 0!==this.options_&&void 0!==this.options_.userActions&&void 0!==this.options_.userActions.doubleClick&&!1===this.options_.userActions.doubleClick||(void 0!==this.options_&&void 0!==this.options_.userActions&&"function"==typeof this.options_.userActions.doubleClick?this.options_.userActions.doubleClick.call(this,e):this.isFullscreen()?this.exitFullscreen():this.requestFullscreen()))},i.handleTechTap_=function(){this.userActive(!this.userActive())},i.handleTechTouchStart_=function(){this.userWasActive=this.userActive()},i.handleTechTouchMove_=function(){this.userWasActive&&this.reportUserActivity()},i.handleTechTouchEnd_=function(e){e.cancelable&&e.preventDefault()},i.handleStageClick_=function(){this.reportUserActivity()},i.toggleFullscreenClass_=function(){this.isFullscreen()?this.addClass("vjs-fullscreen"):this.removeClass("vjs-fullscreen")},i.documentFullscreenChange_=function(e){var t=e.target.player;if(!t||t===this){var i=this.el(),n=k.default[this.fsApi_.fullscreenElement]===i;!n&&i.matches?n=i.matches(":"+this.fsApi_.fullscreen):!n&&i.msMatchesSelector&&(n=i.msMatchesSelector(":"+this.fsApi_.fullscreen)),this.isFullscreen(n)}},i.handleTechFullscreenChange_=function(e,t){t&&(t.nativeIOSFullscreen&&this.toggleClass("vjs-ios-native-fs"),this.isFullscreen(t.isFullscreen))},i.handleTechFullscreenError_=function(e,t){this.trigger("fullscreenerror",t)},i.togglePictureInPictureClass_=function(){this.isInPictureInPicture()?this.addClass("vjs-picture-in-picture"):this.removeClass("vjs-picture-in-picture")},i.handleTechEnterPictureInPicture_=function(e){this.isInPictureInPicture(!0)},i.handleTechLeavePictureInPicture_=function(e){this.isInPictureInPicture(!1)},i.handleTechError_=function(){var e=this.tech_.error();this.error(e)},i.handleTechTextData_=function(){var e=null;arguments.length>1&&(e=arguments[1]),this.trigger("textdata",e)},i.getCache=function(){return this.cache_},i.resetCache_=function(){this.cache_={currentTime:0,initTime:0,inactivityTimeout:this.options_.inactivityTimeout,duration:NaN,lastVolume:1,lastPlaybackRate:this.defaultPlaybackRate(),media:null,src:"",source:{},sources:[],playbackRates:[],volume:1}},i.techCall_=function(e,t){this.ready((function(){if(e in Hi)return function(e,t,i,n){return t[i](e.reduce(Gi(i),n))}(this.middleware_,this.tech_,e,t);if(e in zi)return ji(this.middleware_,this.tech_,e,t);try{this.tech_&&this.tech_[e](t)}catch(e){throw K(e),e}}),!0)},i.techGet_=function(e){if(this.tech_&&this.tech_.isReady_){if(e in Vi)return function(e,t,i){return e.reduceRight(Gi(i),t[i]())}(this.middleware_,this.tech_,e);if(e in zi)return ji(this.middleware_,this.tech_,e);try{return this.tech_[e]()}catch(t){if(void 0===this.tech_[e])throw K("Video.js: "+e+" method not defined for "+this.techName_+" playback technology.",t),t;if("TypeError"===t.name)throw K("Video.js: "+e+" unavailable on "+this.techName_+" playback technology element.",t),this.tech_.isReady_=!1,t;throw K(t),t}}},i.play=function(){var e=this,t=this.options_.Promise||C.default.Promise;return t?new t((function(t){e.play_(t)})):this.play_()},i.play_=function(e){var t=this;void 0===e&&(e=ii),this.playCallbacks_.push(e);var i=Boolean(!this.changingSrc_&&(this.src()||this.currentSrc()));if(this.waitToPlay_&&(this.off(["ready","loadstart"],this.waitToPlay_),this.waitToPlay_=null),!this.isReady_||!i)return this.waitToPlay_=function(e){t.play_()},this.one(["ready","loadstart"],this.waitToPlay_),void(i||!Ee&&!Te||this.load());var n=this.techGet_("play");null===n?this.runPlayTerminatedQueue_():this.runPlayCallbacks_(n)},i.runPlayTerminatedQueue_=function(){var e=this.playTerminatedQueue_.slice(0);this.playTerminatedQueue_=[],e.forEach((function(e){e()}))},i.runPlayCallbacks_=function(e){var t=this.playCallbacks_.slice(0);this.playCallbacks_=[],this.playTerminatedQueue_=[],t.forEach((function(t){t(e)}))},i.pause=function(){this.techCall_("pause")},i.paused=function(){return!1!==this.techGet_("paused")},i.played=function(){return this.techGet_("played")||$t(0,0)},i.scrubbing=function(e){if(void 0===e)return this.scrubbing_;this.scrubbing_=!!e,this.techCall_("setScrubbing",this.scrubbing_),e?this.addClass("vjs-scrubbing"):this.removeClass("vjs-scrubbing")},i.currentTime=function(e){return void 0!==e?(e<0&&(e=0),this.isReady_&&!this.changingSrc_&&this.tech_&&this.tech_.isReady_?(this.techCall_("setCurrentTime",e),void(this.cache_.initTime=0)):(this.cache_.initTime=e,this.off("canplay",this.boundApplyInitTime_),void this.one("canplay",this.boundApplyInitTime_))):(this.cache_.currentTime=this.techGet_("currentTime")||0,this.cache_.currentTime)},i.applyInitTime_=function(){this.currentTime(this.cache_.initTime)},i.duration=function(e){if(void 0===e)return void 0!==this.cache_.duration?this.cache_.duration:NaN;(e=parseFloat(e))<0&&(e=1/0),e!==this.cache_.duration&&(this.cache_.duration=e,e===1/0?this.addClass("vjs-live"):this.removeClass("vjs-live"),isNaN(e)||this.trigger("durationchange"))},i.remainingTime=function(){return this.duration()-this.currentTime()},i.remainingTimeDisplay=function(){return Math.floor(this.duration())-Math.floor(this.currentTime())},i.buffered=function(){var e=this.techGet_("buffered");return e&&e.length||(e=$t(0,0)),e},i.bufferedPercent=function(){return Jt(this.buffered(),this.duration())},i.bufferedEnd=function(){var e=this.buffered(),t=this.duration(),i=e.end(e.length-1);return i>t&&(i=t),i},i.volume=function(e){var t;return void 0!==e?(t=Math.max(0,Math.min(1,parseFloat(e))),this.cache_.volume=t,this.techCall_("setVolume",t),void(t>0&&this.lastVolume_(t))):(t=parseFloat(this.techGet_("volume")),isNaN(t)?1:t)},i.muted=function(e){if(void 0===e)return this.techGet_("muted")||!1;this.techCall_("setMuted",e)},i.defaultMuted=function(e){return void 0!==e?this.techCall_("setDefaultMuted",e):this.techGet_("defaultMuted")||!1},i.lastVolume_=function(e){if(void 0===e||0===e)return this.cache_.lastVolume;this.cache_.lastVolume=e},i.supportsFullScreen=function(){return this.techGet_("supportsFullScreen")||!1},i.isFullscreen=function(e){if(void 0!==e){var t=this.isFullscreen_;return this.isFullscreen_=Boolean(e),this.isFullscreen_!==t&&this.fsApi_.prefixed&&this.trigger("fullscreenchange"),void this.toggleFullscreenClass_()}return this.isFullscreen_},i.requestFullscreen=function(e){var t=this.options_.Promise||C.default.Promise;if(t){var i=this;return new t((function(t,n){function r(){i.off("fullscreenerror",s),i.off("fullscreenchange",a)}function a(){r(),t()}function s(e,t){r(),n(t)}i.one("fullscreenchange",a),i.one("fullscreenerror",s);var o=i.requestFullscreenHelper_(e);o&&(o.then(r,r),o.then(t,n))}))}return this.requestFullscreenHelper_()},i.requestFullscreenHelper_=function(e){var t,i=this;if(this.fsApi_.prefixed||(t=this.options_.fullscreen&&this.options_.fullscreen.options||{},void 0!==e&&(t=e)),this.fsApi_.requestFullscreen){var n=this.el_[this.fsApi_.requestFullscreen](t);return n&&n.then((function(){return i.isFullscreen(!0)}),(function(){return i.isFullscreen(!1)})),n}this.tech_.supportsFullScreen()&&!0==!this.options_.preferFullWindow?this.techCall_("enterFullScreen"):this.enterFullWindow()},i.exitFullscreen=function(){var e=this.options_.Promise||C.default.Promise;if(e){var t=this;return new e((function(e,i){function n(){t.off("fullscreenerror",a),t.off("fullscreenchange",r)}function r(){n(),e()}function a(e,t){n(),i(t)}t.one("fullscreenchange",r),t.one("fullscreenerror",a);var s=t.exitFullscreenHelper_();s&&(s.then(n,n),s.then(e,i))}))}return this.exitFullscreenHelper_()},i.exitFullscreenHelper_=function(){var e=this;if(this.fsApi_.requestFullscreen){var t=k.default[this.fsApi_.exitFullscreen]();return t&&ii(t.then((function(){return e.isFullscreen(!1)}))),t}this.tech_.supportsFullScreen()&&!0==!this.options_.preferFullWindow?this.techCall_("exitFullScreen"):this.exitFullWindow()},i.enterFullWindow=function(){this.isFullscreen(!0),this.isFullWindow=!0,this.docOrigOverflow=k.default.documentElement.style.overflow,yt(k.default,"keydown",this.boundFullWindowOnEscKey_),k.default.documentElement.style.overflow="hidden",Ue(k.default.body,"vjs-full-window"),this.trigger("enterFullWindow")},i.fullWindowOnEscKey=function(e){R.default.isEventKey(e,"Esc")&&!0===this.isFullscreen()&&(this.isFullWindow?this.exitFullWindow():this.exitFullscreen())},i.exitFullWindow=function(){this.isFullscreen(!1),this.isFullWindow=!1,bt(k.default,"keydown",this.boundFullWindowOnEscKey_),k.default.documentElement.style.overflow=this.docOrigOverflow,Me(k.default.body,"vjs-full-window"),this.trigger("exitFullWindow")},i.disablePictureInPicture=function(e){if(void 0===e)return this.techGet_("disablePictureInPicture");this.techCall_("setDisablePictureInPicture",e),this.options_.disablePictureInPicture=e,this.trigger("disablepictureinpicturechanged")},i.isInPictureInPicture=function(e){return void 0!==e?(this.isInPictureInPicture_=!!e,void this.togglePictureInPictureClass_()):!!this.isInPictureInPicture_},i.requestPictureInPicture=function(){if("pictureInPictureEnabled"in k.default&&!1===this.disablePictureInPicture())return this.techGet_("requestPictureInPicture")},i.exitPictureInPicture=function(){if("pictureInPictureEnabled"in k.default)return k.default.exitPictureInPicture()},i.handleKeyDown=function(e){var t=this.options_.userActions;if(t&&t.hotkeys){(function(e){var t=e.tagName.toLowerCase();if(e.isContentEditable)return!0;if("input"===t)return-1===["button","checkbox","hidden","radio","reset","submit"].indexOf(e.type);return-1!==["textarea"].indexOf(t)})(this.el_.ownerDocument.activeElement)||("function"==typeof t.hotkeys?t.hotkeys.call(this,e):this.handleHotkeys(e))}},i.handleHotkeys=function(e){var t=this.options_.userActions?this.options_.userActions.hotkeys:{},i=t.fullscreenKey,n=void 0===i?function(e){return R.default.isEventKey(e,"f")}:i,r=t.muteKey,a=void 0===r?function(e){return R.default.isEventKey(e,"m")}:r,s=t.playPauseKey,o=void 0===s?function(e){return R.default.isEventKey(e,"k")||R.default.isEventKey(e,"Space")}:s;if(n.call(this,e)){e.preventDefault(),e.stopPropagation();var u=Kt.getComponent("FullscreenToggle");!1!==k.default[this.fsApi_.fullscreenEnabled]&&u.prototype.handleClick.call(this,e)}else if(a.call(this,e)){e.preventDefault(),e.stopPropagation(),Kt.getComponent("MuteToggle").prototype.handleClick.call(this,e)}else if(o.call(this,e)){e.preventDefault(),e.stopPropagation(),Kt.getComponent("PlayToggle").prototype.handleClick.call(this,e)}},i.canPlayType=function(e){for(var t,i=0,n=this.options_.techOrder;i1?i.handleSrc_(n.slice(1)):(i.changingSrc_=!1,i.setTimeout((function(){this.error({code:4,message:this.localize(this.options_.notSupportedMessage)})}),0),void i.triggerReady());a=r,s=i.tech_,a.forEach((function(e){return e.setTech&&e.setTech(s)}))})),this.options_.retryOnError&&n.length>1){var r=function(){i.error(null),i.handleSrc_(n.slice(1),!0)},a=function(){i.off("error",r)};this.one("error",r),this.one("playing",a),this.resetRetryOnError_=function(){i.off("error",r),i.off("playing",a)}}}else this.setTimeout((function(){this.error({code:4,message:this.localize(this.options_.notSupportedMessage)})}),0)},i.src=function(e){return this.handleSrc_(e,!1)},i.src_=function(e){var t,i,n=this,r=this.selectSource([e]);return!r||(t=r.tech,i=this.techName_,Ht(t)!==Ht(i)?(this.changingSrc_=!0,this.loadTech_(r.tech,r.source),this.tech_.ready((function(){n.changingSrc_=!1})),!1):(this.ready((function(){this.tech_.constructor.prototype.hasOwnProperty("setSource")?this.techCall_("setSource",e):this.techCall_("src",e.src),this.changingSrc_=!1}),!0),!1))},i.load=function(){this.techCall_("load")},i.reset=function(){var e=this,t=this.options_.Promise||C.default.Promise;this.paused()||!t?this.doReset_():ii(this.play().then((function(){return e.doReset_()})))},i.doReset_=function(){this.tech_&&this.tech_.clearTracks("text"),this.resetCache_(),this.poster(""),this.loadTech_(this.options_.techOrder[0],null),this.techCall_("reset"),this.resetControlBarUI_(),Lt(this)&&this.trigger("playerreset")},i.resetControlBarUI_=function(){this.resetProgressBar_(),this.resetPlaybackRate_(),this.resetVolumeBar_()},i.resetProgressBar_=function(){this.currentTime(0);var e=this.controlBar,t=e.durationDisplay,i=e.remainingTimeDisplay;t&&t.updateContent(),i&&i.updateContent()},i.resetPlaybackRate_=function(){this.playbackRate(this.defaultPlaybackRate()),this.handleTechRateChange_()},i.resetVolumeBar_=function(){this.volume(1),this.trigger("volumechange")},i.currentSources=function(){var e=this.currentSource(),t=[];return 0!==Object.keys(e).length&&t.push(e),this.cache_.sources||t},i.currentSource=function(){return this.cache_.source||{}},i.currentSrc=function(){return this.currentSource()&&this.currentSource().src||""},i.currentType=function(){return this.currentSource()&&this.currentSource().type||""},i.preload=function(e){return void 0!==e?(this.techCall_("setPreload",e),void(this.options_.preload=e)):this.techGet_("preload")},i.autoplay=function(e){if(void 0===e)return this.options_.autoplay||!1;var t;"string"==typeof e&&/(any|play|muted)/.test(e)||!0===e&&this.options_.normalizeAutoplay?(this.options_.autoplay=e,this.manualAutoplay_("string"==typeof e?e:"play"),t=!1):this.options_.autoplay=!!e,t=void 0===t?this.options_.autoplay:t,this.tech_&&this.techCall_("setAutoplay",t)},i.playsinline=function(e){return void 0!==e?(this.techCall_("setPlaysinline",e),this.options_.playsinline=e,this):this.techGet_("playsinline")},i.loop=function(e){return void 0!==e?(this.techCall_("setLoop",e),void(this.options_.loop=e)):this.techGet_("loop")},i.poster=function(e){if(void 0===e)return this.poster_;e||(e=""),e!==this.poster_&&(this.poster_=e,this.techCall_("setPoster",e),this.isPosterFromTech_=!1,this.trigger("posterchange"))},i.handleTechPosterChange_=function(){if((!this.poster_||this.options_.techCanOverridePoster)&&this.tech_&&this.tech_.poster){var e=this.tech_.poster()||"";e!==this.poster_&&(this.poster_=e,this.isPosterFromTech_=!0,this.trigger("posterchange"))}},i.controls=function(e){if(void 0===e)return!!this.controls_;e=!!e,this.controls_!==e&&(this.controls_=e,this.usingNativeControls()&&this.techCall_("setControls",e),this.controls_?(this.removeClass("vjs-controls-disabled"),this.addClass("vjs-controls-enabled"),this.trigger("controlsenabled"),this.usingNativeControls()||this.addTechControlsListeners_()):(this.removeClass("vjs-controls-enabled"),this.addClass("vjs-controls-disabled"),this.trigger("controlsdisabled"),this.usingNativeControls()||this.removeTechControlsListeners_()))},i.usingNativeControls=function(e){if(void 0===e)return!!this.usingNativeControls_;e=!!e,this.usingNativeControls_!==e&&(this.usingNativeControls_=e,this.usingNativeControls_?(this.addClass("vjs-using-native-controls"),this.trigger("usingnativecontrols")):(this.removeClass("vjs-using-native-controls"),this.trigger("usingcustomcontrols")))},i.error=function(e){var t=this;if(void 0===e)return this.error_||null;if(j("beforeerror").forEach((function(i){var n=i(t,e);ee(n)&&!Array.isArray(n)||"string"==typeof n||"number"==typeof n||null===n?e=n:t.log.error("please return a value that MediaError expects in beforeerror hooks")})),this.options_.suppressNotSupportedError&&e&&4===e.code){var i=function(){this.error(e)};return this.options_.suppressNotSupportedError=!1,this.any(["click","touchstart"],i),void this.one("loadstart",(function(){this.off(["click","touchstart"],i)}))}if(null===e)return this.error_=e,this.removeClass("vjs-error"),void(this.errorDisplay&&this.errorDisplay.close());this.error_=new Zt(e),this.addClass("vjs-error"),K.error("(CODE:"+this.error_.code+" "+Zt.errorTypes[this.error_.code]+")",this.error_.message,this.error_),this.trigger("error"),j("error").forEach((function(e){return e(t,t.error_)}))},i.reportUserActivity=function(e){this.userActivity_=!0},i.userActive=function(e){if(void 0===e)return this.userActive_;if((e=!!e)!==this.userActive_){if(this.userActive_=e,this.userActive_)return this.userActivity_=!0,this.removeClass("vjs-user-inactive"),this.addClass("vjs-user-active"),void this.trigger("useractive");this.tech_&&this.tech_.one("mousemove",(function(e){e.stopPropagation(),e.preventDefault()})),this.userActivity_=!1,this.removeClass("vjs-user-active"),this.addClass("vjs-user-inactive"),this.trigger("userinactive")}},i.listenForUserActivity_=function(){var e,t,i,n=Ct(this,this.reportUserActivity),r=function(t){n(),this.clearInterval(e)};this.on("mousedown",(function(){n(),this.clearInterval(e),e=this.setInterval(n,250)})),this.on("mousemove",(function(e){e.screenX===t&&e.screenY===i||(t=e.screenX,i=e.screenY,n())})),this.on("mouseup",r),this.on("mouseleave",r);var a,s=this.getChild("controlBar");!s||Te||le||(s.on("mouseenter",(function(e){0!==this.player().options_.inactivityTimeout&&(this.player().cache_.inactivityTimeout=this.player().options_.inactivityTimeout),this.player().options_.inactivityTimeout=0})),s.on("mouseleave",(function(e){this.player().options_.inactivityTimeout=this.player().cache_.inactivityTimeout}))),this.on("keydown",n),this.on("keyup",n),this.setInterval((function(){if(this.userActivity_){this.userActivity_=!1,this.userActive(!0),this.clearTimeout(a);var e=this.options_.inactivityTimeout;e<=0||(a=this.setTimeout((function(){this.userActivity_||this.userActive(!1)}),e))}}),250)},i.playbackRate=function(e){if(void 0===e)return this.tech_&&this.tech_.featuresPlaybackRate?this.cache_.lastPlaybackRate||this.techGet_("playbackRate"):1;this.techCall_("setPlaybackRate",e)},i.defaultPlaybackRate=function(e){return void 0!==e?this.techCall_("setDefaultPlaybackRate",e):this.tech_&&this.tech_.featuresPlaybackRate?this.techGet_("defaultPlaybackRate"):1},i.isAudio=function(e){if(void 0===e)return!!this.isAudio_;this.isAudio_=!!e},i.addTextTrack=function(e,t,i){if(this.tech_)return this.tech_.addTextTrack(e,t,i)},i.addRemoteTextTrack=function(e,t){if(this.tech_)return this.tech_.addRemoteTextTrack(e,t)},i.removeRemoteTextTrack=function(e){void 0===e&&(e={});var t=e.track;if(t||(t=e),this.tech_)return this.tech_.removeRemoteTextTrack(t)},i.getVideoPlaybackQuality=function(){return this.techGet_("getVideoPlaybackQuality")},i.videoWidth=function(){return this.tech_&&this.tech_.videoWidth&&this.tech_.videoWidth()||0},i.videoHeight=function(){return this.tech_&&this.tech_.videoHeight&&this.tech_.videoHeight()||0},i.language=function(e){if(void 0===e)return this.language_;this.language_!==String(e).toLowerCase()&&(this.language_=String(e).toLowerCase(),Lt(this)&&this.trigger("languagechange"))},i.languages=function(){return zt(t.prototype.options_.languages,this.languages_)},i.toJSON=function(){var e=zt(this.options_),t=e.tracks;e.tracks=[];for(var i=0;i"):function(){}},Jr=function(e,t){var i,n=[];if(e&&e.length)for(i=0;i=t}))},ea=function(e,t){return Jr(e,(function(e){return e-1/30>=t}))},ta=function(e){var t=[];if(!e||!e.length)return"";for(var i=0;i "+e.end(i));return t.join(", ")},ia=function(e){for(var t=[],i=0;i0;return i&&t.serverControl&&t.serverControl.partHoldBack?t.serverControl.partHoldBack:i&&t.partTargetDuration?3*t.partTargetDuration:t.serverControl&&t.serverControl.holdBack?t.serverControl.holdBack:t.targetDuration?3*t.targetDuration:0},la=function(e,t,i){if(void 0===t&&(t=e.mediaSequence+e.segments.length),tr){var s=[r,n];n=s[0],r=s[1]}if(n<0){for(var o=n;oDate.now()},pa=function(e){return e.excludeUntil&&e.excludeUntil===1/0},ma=function(e){var t=fa(e);return!e.disabled&&!t},_a=function(e,t){return t.attributes&&t.attributes[e]},ga=function(e,t){if(1===e.playlists.length)return!0;var i=t.attributes.BANDWIDTH||Number.MAX_VALUE;return 0===e.playlists.filter((function(e){return!!ma(e)&&(e.attributes.BANDWIDTH||0)0)for(var c=l-1;c>=0;c--){var f=u[c];if(o+=f.duration,s){if(o<0)continue}else if(o+1/30<=0)continue;return{partIndex:f.partIndex,segmentIndex:f.segmentIndex,startTime:a-da({defaultDuration:t.targetDuration,durationList:u,startIndex:l,endIndex:c})}}return{partIndex:u[0]&&u[0].partIndex||null,segmentIndex:u[0]&&u[0].segmentIndex||0,startTime:i}}if(l<0){for(var p=l;p<0;p++)if((o-=t.targetDuration)<0)return{partIndex:u[0]&&u[0].partIndex||null,segmentIndex:u[0]&&u[0].segmentIndex||0,startTime:i};l=0}for(var m=l;m0)continue}else if(o-1/30>=0)continue;return{partIndex:_.partIndex,segmentIndex:_.segmentIndex,startTime:a+da({defaultDuration:t.targetDuration,durationList:u,startIndex:l,endIndex:m})}}return{segmentIndex:u[u.length-1].segmentIndex,partIndex:u[u.length-1].partIndex,startTime:i}},isEnabled:ma,isDisabled:function(e){return e.disabled},isBlacklisted:fa,isIncompatible:pa,playlistEnd:ca,isAes:function(e){for(var t=0;t-1&&s!==a.length-1&&i.push("_HLS_part="+s),(s>-1||a.length)&&r--}i.unshift("_HLS_msn="+r)}return t.serverControl&&t.serverControl.canSkipUntil&&i.unshift("_HLS_skip="+(t.serverControl.canSkipDateranges?"v2":"YES")),i.forEach((function(t,i){e+=""+(0===i?"?":"&")+t})),e}(i,t)),this.state="HAVE_CURRENT_METADATA",this.request=this.vhs_.xhr({uri:i,withCredentials:this.withCredentials},(function(t,i){if(e.request)return t?e.playlistRequestError(e.request,e.media(),"HAVE_METADATA"):void e.haveMetadata({playlistString:e.request.responseText,url:e.media().uri,id:e.media().id})}))}},i.playlistRequestError=function(e,t,i){var n=t.uri,r=t.id;this.request=null,i&&(this.state=i),this.error={playlist:this.master.playlists[r],status:e.status,message:"HLS playlist request error at URL: "+n+".",responseText:e.responseText,code:e.status>=500?4:2},this.trigger("error")},i.parseManifest_=function(e){var t=this,i=e.url;return function(e){var t=e.onwarn,i=e.oninfo,n=e.manifestString,r=e.customTagParsers,a=void 0===r?[]:r,s=e.customTagMappers,o=void 0===s?[]:s,u=e.experimentalLLHLS,l=new m.Parser;t&&l.on("warn",t),i&&l.on("info",i),a.forEach((function(e){return l.addParser(e)})),o.forEach((function(e){return l.addTagMapper(e)})),l.push(n),l.end();var h=l.manifest;if(u||(["preloadSegment","skip","serverControl","renditionReports","partInf","partTargetDuration"].forEach((function(e){h.hasOwnProperty(e)&&delete h[e]})),h.segments&&h.segments.forEach((function(e){["parts","preloadHints"].forEach((function(t){e.hasOwnProperty(t)&&delete e[t]}))}))),!h.targetDuration){var d=10;h.segments&&h.segments.length&&(d=h.segments.reduce((function(e,t){return Math.max(e,t.duration)}),0)),t&&t("manifest has no targetDuration defaulting to "+d),h.targetDuration=d}var c=sa(h);if(c.length&&!h.partTargetDuration){var f=c.reduce((function(e,t){return Math.max(e,t.duration)}),0);t&&(t("manifest has no partTargetDuration defaulting to "+f),Ta.error("LL-HLS manifest has parts but lacks required #EXT-X-PART-INF:PART-TARGET value. See https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis-09#section-4.4.3.7. Playback is not guaranteed.")),h.partTargetDuration=f}return h}({onwarn:function(e){var n=e.message;return t.logger_("m3u8-parser warn for "+i+": "+n)},oninfo:function(e){var n=e.message;return t.logger_("m3u8-parser info for "+i+": "+n)},manifestString:e.manifestString,customTagParsers:this.customTagParsers,customTagMappers:this.customTagMappers,experimentalLLHLS:this.experimentalLLHLS})},i.haveMetadata=function(e){var t=e.playlistString,i=e.playlistObject,n=e.url,r=e.id;this.request=null,this.state="HAVE_METADATA";var a=i||this.parseManifest_({url:n,manifestString:t});a.lastRequest=Date.now(),Aa({playlist:a,uri:n,id:r});var s=Da(this.master,a);this.targetDuration=a.partTargetDuration||a.targetDuration,s?(this.master=s,this.media_=this.master.playlists[r]):this.trigger("playlistunchanged"),this.updateMediaUpdateTimeout_(Oa(this.media(),!!s)),this.trigger("loadedplaylist")},i.dispose=function(){this.trigger("dispose"),this.stopRequest(),C.default.clearTimeout(this.mediaUpdateTimeout),C.default.clearTimeout(this.finalRenditionTimeout),this.off()},i.stopRequest=function(){if(this.request){var e=this.request;this.request=null,e.onreadystatechange=null,e.abort()}},i.media=function(e,t){var i=this;if(!e)return this.media_;if("HAVE_NOTHING"===this.state)throw new Error("Cannot switch media playlist from "+this.state);if("string"==typeof e){if(!this.master.playlists[e])throw new Error("Unknown playlist URI: "+e);e=this.master.playlists[e]}if(C.default.clearTimeout(this.finalRenditionTimeout),t){var n=(e.partTargetDuration||e.targetDuration)/2*1e3||5e3;this.finalRenditionTimeout=C.default.setTimeout(this.media.bind(this,e,!1),n)}else{var r=this.state,a=!this.media_||e.id!==this.media_.id,s=this.master.playlists[e.id];if(s&&s.endList||e.endList&&e.segments.length)return this.request&&(this.request.onreadystatechange=null,this.request.abort(),this.request=null),this.state="HAVE_METADATA",this.media_=e,void(a&&(this.trigger("mediachanging"),"HAVE_MASTER"===r?this.trigger("loadedmetadata"):this.trigger("mediachange")));if(this.updateMediaUpdateTimeout_(Oa(e,!0)),a){if(this.state="SWITCHING_MEDIA",this.request){if(e.resolvedUri===this.request.url)return;this.request.onreadystatechange=null,this.request.abort(),this.request=null}this.media_&&this.trigger("mediachanging"),this.request=this.vhs_.xhr({uri:e.resolvedUri,withCredentials:this.withCredentials},(function(t,n){if(i.request){if(e.lastRequest=Date.now(),e.resolvedUri=Qr(i.handleManifestRedirects,e.resolvedUri,n),t)return i.playlistRequestError(i.request,e,r);i.haveMetadata({playlistString:n.responseText,url:e.uri,id:e.id}),"HAVE_MASTER"===r?i.trigger("loadedmetadata"):i.trigger("mediachange")}}))}}},i.pause=function(){this.mediaUpdateTimeout&&(C.default.clearTimeout(this.mediaUpdateTimeout),this.mediaUpdateTimeout=null),this.stopRequest(),"HAVE_NOTHING"===this.state&&(this.started=!1),"SWITCHING_MEDIA"===this.state?this.media_?this.state="HAVE_METADATA":this.state="HAVE_MASTER":"HAVE_CURRENT_METADATA"===this.state&&(this.state="HAVE_METADATA")},i.load=function(e){var t=this;this.mediaUpdateTimeout&&(C.default.clearTimeout(this.mediaUpdateTimeout),this.mediaUpdateTimeout=null);var i=this.media();if(e){var n=i?(i.partTargetDuration||i.targetDuration)/2*1e3:5e3;this.mediaUpdateTimeout=C.default.setTimeout((function(){t.mediaUpdateTimeout=null,t.load()}),n)}else this.started?i&&!i.endList?this.trigger("mediaupdatetimeout"):this.trigger("loadedplaylist"):this.start()},i.updateMediaUpdateTimeout_=function(e){var t=this;this.mediaUpdateTimeout&&(C.default.clearTimeout(this.mediaUpdateTimeout),this.mediaUpdateTimeout=null),this.media()&&!this.media().endList&&(this.mediaUpdateTimeout=C.default.setTimeout((function(){t.mediaUpdateTimeout=null,t.trigger("mediaupdatetimeout"),t.updateMediaUpdateTimeout_(e)}),e))},i.start=function(){var e=this;if(this.started=!0,"object"==typeof this.src)return this.src.uri||(this.src.uri=C.default.location.href),this.src.resolvedUri=this.src.uri,void setTimeout((function(){e.setupInitialPlaylist(e.src)}),0);this.request=this.vhs_.xhr({uri:this.src,withCredentials:this.withCredentials},(function(t,i){if(e.request){if(e.request=null,t)return e.error={status:i.status,message:"HLS playlist request error at URL: "+e.src+".",responseText:i.responseText,code:2},"HAVE_NOTHING"===e.state&&(e.started=!1),e.trigger("error");e.src=Qr(e.handleManifestRedirects,e.src,i);var n=e.parseManifest_({manifestString:i.responseText,url:e.src});e.setupInitialPlaylist(n)}}))},i.srcUri=function(){return"string"==typeof this.src?this.src:this.src.uri},i.setupInitialPlaylist=function(e){if(this.state="HAVE_MASTER",e.playlists)return this.master=e,Ca(this.master,this.srcUri()),e.playlists.forEach((function(e){e.segments=xa(e),e.segments.forEach((function(t){La(t,e.resolvedUri)}))})),this.trigger("loadedplaylist"),void(this.request||this.media(this.master.playlists[0]));var t=this.srcUri()||C.default.location.href;this.master=function(e,t){var i=Ea(0,t),n={mediaGroups:{AUDIO:{},VIDEO:{},"CLOSED-CAPTIONS":{},SUBTITLES:{}},uri:C.default.location.href,resolvedUri:C.default.location.href,playlists:[{uri:t,id:i,resolvedUri:t,attributes:{}}]};return n.playlists[i]=n.playlists[0],n.playlists[t]=n.playlists[0],n}(0,t),this.haveMetadata({playlistObject:e,url:t,id:this.master.playlists[0].id}),this.trigger("loadedmetadata")},t}(Pa),Ma=Yr.xhr,Fa=Yr.mergeOptions,Ba=function(e,t,i,n){var r="arraybuffer"===e.responseType?e.response:e.responseText;!t&&r&&(e.responseTime=Date.now(),e.roundTripTime=e.responseTime-e.requestTime,e.bytesReceived=r.byteLength||r.length,e.bandwidth||(e.bandwidth=Math.floor(e.bytesReceived/e.roundTripTime*8*1e3))),i.headers&&(e.responseHeaders=i.headers),t&&"ETIMEDOUT"===t.code&&(e.timedout=!0),t||e.aborted||200===i.statusCode||206===i.statusCode||0===i.statusCode||(t=new Error("XHR Failed with a response of: "+(e&&(r||e.responseText)))),n(t,e)},Na=function(){var e=function e(t,i){t=Fa({timeout:45e3},t);var n=e.beforeRequest||Yr.Vhs.xhr.beforeRequest;if(n&&"function"==typeof n){var r=n(t);r&&(t=r)}var a=(!0===Yr.Vhs.xhr.original?Ma:Yr.Vhs.xhr)(t,(function(e,t){return Ba(a,e,t,i)})),s=a.abort;return a.abort=function(){return a.aborted=!0,s.apply(a,arguments)},a.uri=t.uri,a.requestTime=Date.now(),a};return e.original=!0,e},ja=function(e){var t,i,n={};return e.byterange&&(n.Range=(t=e.byterange,i=t.offset+t.length-1,"bytes="+t.offset+"-"+i)),n},Va=function(e,t){return e.start(t)+"-"+e.end(t)},Ha=function(e,t){var i=e.toString(16);return"00".substring(0,2-i.length)+i+(t%2?" ":"")},za=function(e){return e>=32&&e<126?String.fromCharCode(e):"."},Ga=function(e){var t={};return Object.keys(e).forEach((function(i){var n=e[i];ArrayBuffer.isView(n)?t[i]={bytes:n.buffer,byteOffset:n.byteOffset,byteLength:n.byteLength}:t[i]=n})),t},Wa=function(e){var t=e.byterange||{length:1/0,offset:0};return[t.length,t.offset,e.resolvedUri].join(",")},Ya=function(e){return e.resolvedUri},qa=function(e){for(var t=Array.prototype.slice.call(e),i="",n=0;nn){if(e>n+.25*a.duration)return null;i=a}return{segment:i,estimatedStart:i.videoTimingInfo?i.videoTimingInfo.transmuxedPresentationStart:n-i.duration,type:i.videoTimingInfo?"accurate":"estimate"}}(n,t);if(!a)return r({message:"valid programTime was not found"});if("estimate"===a.type)return r({message:"Accurate programTime could not be determined. Please seek to e.seekTime and try again",seekTime:a.estimatedStart});var s={mediaSeconds:n},o=function(e,t){if(!t.dateTimeObject)return null;var i=t.videoTimingInfo.transmuxerPrependedSeconds,n=e-(t.videoTimingInfo.transmuxedPresentationStart+i);return new Date(t.dateTimeObject.getTime()+1e3*n)}(n,a.segment);return o&&(s.programDateTime=o.toISOString()),r(null,s)},Qa=function e(t){var i=t.programTime,n=t.playlist,r=t.retryCount,a=void 0===r?2:r,s=t.seekTo,o=t.pauseAfterSeek,u=void 0===o||o,l=t.tech,h=t.callback;if(!h)throw new Error("seekToProgramTime: callback must be provided");if(void 0===i||!n||!s)return h({message:"seekToProgramTime: programTime, seekTo and playlist must be provided"});if(!n.endList&&!l.hasStarted_)return h({message:"player must be playing a live stream to start buffering"});if(!function(e){if(!e.segments||0===e.segments.length)return!1;for(var t=0;tnew Date(o.getTime()+1e3*u)?null:(i>o&&(n=s),{segment:n,estimatedStart:n.videoTimingInfo?n.videoTimingInfo.transmuxedPresentationStart:Sa.duration(t,t.mediaSequence+t.segments.indexOf(n)),type:n.videoTimingInfo?"accurate":"estimate"})}(i,n);if(!d)return h({message:i+" was not found in the stream"});var c=d.segment,f=function(e,t){var i,n;try{i=new Date(e),n=new Date(t)}catch(e){}var r=i.getTime();return(n.getTime()-r)/1e3}(c.dateTimeObject,i);if("estimate"===d.type)return 0===a?h({message:i+" is not buffered yet. Try again"}):(s(d.estimatedStart+f),void l.one("seeked",(function(){e({programTime:i,playlist:n,retryCount:a-1,seekTo:s,pauseAfterSeek:u,tech:l,callback:h})})));var p=c.start+f;l.one("seeked",(function(){return h(null,l.currentTime())})),u&&l.pause(),s(p)},$a=function(e,t){if(4===e.readyState)return t()},Ja=Yr.EventTarget,Za=Yr.mergeOptions,es=function(e,t){if(!Ra(e,t))return!1;if(e.sidx&&t.sidx&&(e.sidx.offset!==t.sidx.offset||e.sidx.length!==t.sidx.length))return!1;if(!e.sidx&&t.sidx||e.sidx&&!t.sidx)return!1;if(e.segments&&!t.segments||!e.segments&&t.segments)return!1;if(!e.segments&&!t.segments)return!0;for(var i=0;i=h+l)return s(t,{response:o.subarray(l,l+h),status:i.status,uri:i.uri});n.request=n.vhs_.xhr({uri:a,responseType:"arraybuffer",headers:ja({byterange:e.sidx.byterange})},s)}))}else this.mediaRequest_=C.default.setTimeout((function(){return i(!1)}),0)},i.dispose=function(){this.trigger("dispose"),this.stopRequest(),this.loadedPlaylists_={},C.default.clearTimeout(this.minimumUpdatePeriodTimeout_),C.default.clearTimeout(this.mediaRequest_),C.default.clearTimeout(this.mediaUpdateTimeout),this.mediaUpdateTimeout=null,this.mediaRequest_=null,this.minimumUpdatePeriodTimeout_=null,this.masterPlaylistLoader_.createMupOnMedia_&&(this.off("loadedmetadata",this.masterPlaylistLoader_.createMupOnMedia_),this.masterPlaylistLoader_.createMupOnMedia_=null),this.off()},i.hasPendingRequest=function(){return this.request||this.mediaRequest_},i.stopRequest=function(){if(this.request){var e=this.request;this.request=null,e.onreadystatechange=null,e.abort()}},i.media=function(e){var t=this;if(!e)return this.media_;if("HAVE_NOTHING"===this.state)throw new Error("Cannot switch media playlist from "+this.state);var i=this.state;if("string"==typeof e){if(!this.masterPlaylistLoader_.master.playlists[e])throw new Error("Unknown playlist URI: "+e);e=this.masterPlaylistLoader_.master.playlists[e]}var n=!this.media_||e.id!==this.media_.id;if(n&&this.loadedPlaylists_[e.id]&&this.loadedPlaylists_[e.id].endList)return this.state="HAVE_METADATA",this.media_=e,void(n&&(this.trigger("mediachanging"),this.trigger("mediachange")));n&&(this.media_&&this.trigger("mediachanging"),this.addSidxSegments_(e,i,(function(n){t.haveMetadata({startingState:i,playlist:e})})))},i.haveMetadata=function(e){var t=e.startingState,i=e.playlist;this.state="HAVE_METADATA",this.loadedPlaylists_[i.id]=i,this.mediaRequest_=null,this.refreshMedia_(i.id),"HAVE_MASTER"===t?this.trigger("loadedmetadata"):this.trigger("mediachange")},i.pause=function(){this.masterPlaylistLoader_.createMupOnMedia_&&(this.off("loadedmetadata",this.masterPlaylistLoader_.createMupOnMedia_),this.masterPlaylistLoader_.createMupOnMedia_=null),this.stopRequest(),C.default.clearTimeout(this.mediaUpdateTimeout),this.mediaUpdateTimeout=null,this.isMaster_&&(C.default.clearTimeout(this.masterPlaylistLoader_.minimumUpdatePeriodTimeout_),this.masterPlaylistLoader_.minimumUpdatePeriodTimeout_=null),"HAVE_NOTHING"===this.state&&(this.started=!1)},i.load=function(e){var t=this;C.default.clearTimeout(this.mediaUpdateTimeout),this.mediaUpdateTimeout=null;var i=this.media();if(e){var n=i?i.targetDuration/2*1e3:5e3;this.mediaUpdateTimeout=C.default.setTimeout((function(){return t.load()}),n)}else this.started?i&&!i.endList?(this.isMaster_&&!this.minimumUpdatePeriodTimeout_&&(this.trigger("minimumUpdatePeriod"),this.updateMinimumUpdatePeriodTimeout_()),this.trigger("mediaupdatetimeout")):this.trigger("loadedplaylist"):this.start()},i.start=function(){var e=this;this.started=!0,this.isMaster_?this.requestMaster_((function(t,i){e.haveMaster_(),e.hasPendingRequest()||e.media_||e.media(e.masterPlaylistLoader_.master.playlists[0])})):this.mediaRequest_=C.default.setTimeout((function(){return e.haveMaster_()}),0)},i.requestMaster_=function(e){var t=this;this.request=this.vhs_.xhr({uri:this.masterPlaylistLoader_.srcUrl,withCredentials:this.withCredentials},(function(i,n){if(!t.requestErrored_(i,n)){var r=n.responseText!==t.masterPlaylistLoader_.masterXml_;return t.masterPlaylistLoader_.masterXml_=n.responseText,n.responseHeaders&&n.responseHeaders.date?t.masterLoaded_=Date.parse(n.responseHeaders.date):t.masterLoaded_=Date.now(),t.masterPlaylistLoader_.srcUrl=Qr(t.handleManifestRedirects,t.masterPlaylistLoader_.srcUrl,n),r?(t.handleMaster_(),void t.syncClientServerClock_((function(){return e(n,r)}))):e(n,r)}"HAVE_NOTHING"===t.state&&(t.started=!1)}))},i.syncClientServerClock_=function(e){var t=this,i=v.parseUTCTiming(this.masterPlaylistLoader_.masterXml_);return null===i?(this.masterPlaylistLoader_.clientOffset_=this.masterLoaded_-Date.now(),e()):"DIRECT"===i.method?(this.masterPlaylistLoader_.clientOffset_=i.value-Date.now(),e()):void(this.request=this.vhs_.xhr({uri:Xr(this.masterPlaylistLoader_.srcUrl,i.value),method:i.method,withCredentials:this.withCredentials},(function(n,r){if(t.request){if(n)return t.masterPlaylistLoader_.clientOffset_=t.masterLoaded_-Date.now(),e();var a;a="HEAD"===i.method?r.responseHeaders&&r.responseHeaders.date?Date.parse(r.responseHeaders.date):t.masterLoaded_:Date.parse(r.responseText),t.masterPlaylistLoader_.clientOffset_=a-Date.now(),e()}})))},i.haveMaster_=function(){this.state="HAVE_MASTER",this.isMaster_?this.trigger("loadedplaylist"):this.media_||this.media(this.childPlaylist_)},i.handleMaster_=function(){this.mediaRequest_=null;var e,t,i,n,r,a,s=(e={masterXml:this.masterPlaylistLoader_.masterXml_,srcUrl:this.masterPlaylistLoader_.srcUrl,clientOffset:this.masterPlaylistLoader_.clientOffset_,sidxMapping:this.masterPlaylistLoader_.sidxMapping_},t=e.masterXml,i=e.srcUrl,n=e.clientOffset,r=e.sidxMapping,a=v.parse(t,{manifestUri:i,clientOffset:n,sidxMapping:r}),Ca(a,i),a),o=this.masterPlaylistLoader_.master;o&&(s=function(e,t,i){for(var n=!0,r=Za(e,{duration:t.duration,minimumUpdatePeriod:t.minimumUpdatePeriod}),a=0;a-1)},this.trigger=function(t){var i,n,r,a;if(i=e[t])if(2===arguments.length)for(r=i.length,n=0;n>>1,e.samplingfrequencyindex<<7|e.channelcount<<3,6,1,2]))},m=function(e){return t(T.hdlr,P[e])},p=function(e){var i=new Uint8Array([0,0,0,0,0,0,0,2,0,0,0,3,0,1,95,144,e.duration>>>24&255,e.duration>>>16&255,e.duration>>>8&255,255&e.duration,85,196,0,0]);return e.samplerate&&(i[12]=e.samplerate>>>24&255,i[13]=e.samplerate>>>16&255,i[14]=e.samplerate>>>8&255,i[15]=255&e.samplerate),t(T.mdhd,i)},f=function(e){return t(T.mdia,p(e),m(e.type),s(e))},a=function(e){return t(T.mfhd,new Uint8Array([0,0,0,0,(4278190080&e)>>24,(16711680&e)>>16,(65280&e)>>8,255&e]))},s=function(e){return t(T.minf,"video"===e.type?t(T.vmhd,I):t(T.smhd,L),i(),g(e))},o=function(e,i){for(var n=[],r=i.length;r--;)n[r]=y(i[r]);return t.apply(null,[T.moof,a(e)].concat(n))},u=function(e){for(var i=e.length,n=[];i--;)n[i]=d(e[i]);return t.apply(null,[T.moov,h(4294967295)].concat(n).concat(l(e)))},l=function(e){for(var i=e.length,n=[];i--;)n[i]=b(e[i]);return t.apply(null,[T.mvex].concat(n))},h=function(e){var i=new Uint8Array([0,0,0,0,0,0,0,1,0,0,0,2,0,1,95,144,(4278190080&e)>>24,(16711680&e)>>16,(65280&e)>>8,255&e,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255]);return t(T.mvhd,i)},_=function(e){var i,n,r=e.samples||[],a=new Uint8Array(4+r.length);for(n=0;n>>8),s.push(255&r[i].byteLength),s=s.concat(Array.prototype.slice.call(r[i]));for(i=0;i>>8),o.push(255&a[i].byteLength),o=o.concat(Array.prototype.slice.call(a[i]));if(n=[T.avc1,new Uint8Array([0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,(65280&e.width)>>8,255&e.width,(65280&e.height)>>8,255&e.height,0,72,0,0,0,72,0,0,0,0,0,0,0,1,19,118,105,100,101,111,106,115,45,99,111,110,116,114,105,98,45,104,108,115,0,0,0,0,0,0,0,0,0,0,0,0,0,24,17,17]),t(T.avcC,new Uint8Array([1,e.profileIdc,e.profileCompatibility,e.levelIdc,255].concat([r.length],s,[a.length],o))),t(T.btrt,new Uint8Array([0,28,156,128,0,45,198,192,0,45,198,192]))],e.sarRatio){var u=e.sarRatio[0],l=e.sarRatio[1];n.push(t(T.pasp,new Uint8Array([(4278190080&u)>>24,(16711680&u)>>16,(65280&u)>>8,255&u,(4278190080&l)>>24,(16711680&l)>>16,(65280&l)>>8,255&l])))}return t.apply(null,n)},F=function(e){return t(T.mp4a,new Uint8Array([0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,(65280&e.channelcount)>>8,255&e.channelcount,(65280&e.samplesize)>>8,255&e.samplesize,0,0,0,0,(65280&e.samplerate)>>8,255&e.samplerate,0,0]),n(e))},c=function(e){var i=new Uint8Array([0,0,0,7,0,0,0,0,0,0,0,0,(4278190080&e.id)>>24,(16711680&e.id)>>16,(65280&e.id)>>8,255&e.id,0,0,0,0,(4278190080&e.duration)>>24,(16711680&e.duration)>>16,(65280&e.duration)>>8,255&e.duration,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,0,0,0,(65280&e.width)>>8,255&e.width,0,0,(65280&e.height)>>8,255&e.height,0,0]);return t(T.tkhd,i)},y=function(e){var i,n,r,a,s,o;return i=t(T.tfhd,new Uint8Array([0,0,0,58,(4278190080&e.id)>>24,(16711680&e.id)>>16,(65280&e.id)>>8,255&e.id,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0])),s=Math.floor(e.baseMediaDecodeTime/(H+1)),o=Math.floor(e.baseMediaDecodeTime%(H+1)),n=t(T.tfdt,new Uint8Array([1,0,0,0,s>>>24&255,s>>>16&255,s>>>8&255,255&s,o>>>24&255,o>>>16&255,o>>>8&255,255&o])),92,"audio"===e.type?(r=S(e,92),t(T.traf,i,n,r)):(a=_(e),r=S(e,a.length+92),t(T.traf,i,n,r,a))},d=function(e){return e.duration=e.duration||4294967295,t(T.trak,c(e),f(e))},b=function(e){var i=new Uint8Array([0,0,0,0,(4278190080&e.id)>>24,(16711680&e.id)>>16,(65280&e.id)>>8,255&e.id,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,1]);return"video"!==e.type&&(i[i.length-1]=0),t(T.trex,i)},j=function(e,t){var i=0,n=0,r=0,a=0;return e.length&&(void 0!==e[0].duration&&(i=1),void 0!==e[0].size&&(n=2),void 0!==e[0].flags&&(r=4),void 0!==e[0].compositionTimeOffset&&(a=8)),[0,0,i|n|r|a,1,(4278190080&e.length)>>>24,(16711680&e.length)>>>16,(65280&e.length)>>>8,255&e.length,(4278190080&t)>>>24,(16711680&t)>>>16,(65280&t)>>>8,255&t]},N=function(e,i){var n,r,a,s,o,u;for(i+=20+16*(s=e.samples||[]).length,a=j(s,i),(r=new Uint8Array(a.length+16*s.length)).set(a),n=a.length,u=0;u>>24,r[n++]=(16711680&o.duration)>>>16,r[n++]=(65280&o.duration)>>>8,r[n++]=255&o.duration,r[n++]=(4278190080&o.size)>>>24,r[n++]=(16711680&o.size)>>>16,r[n++]=(65280&o.size)>>>8,r[n++]=255&o.size,r[n++]=o.flags.isLeading<<2|o.flags.dependsOn,r[n++]=o.flags.isDependedOn<<6|o.flags.hasRedundancy<<4|o.flags.paddingValue<<1|o.flags.isNonSyncSample,r[n++]=61440&o.flags.degradationPriority,r[n++]=15&o.flags.degradationPriority,r[n++]=(4278190080&o.compositionTimeOffset)>>>24,r[n++]=(16711680&o.compositionTimeOffset)>>>16,r[n++]=(65280&o.compositionTimeOffset)>>>8,r[n++]=255&o.compositionTimeOffset;return t(T.trun,r)},B=function(e,i){var n,r,a,s,o,u;for(i+=20+8*(s=e.samples||[]).length,a=j(s,i),(n=new Uint8Array(a.length+8*s.length)).set(a),r=a.length,u=0;u>>24,n[r++]=(16711680&o.duration)>>>16,n[r++]=(65280&o.duration)>>>8,n[r++]=255&o.duration,n[r++]=(4278190080&o.size)>>>24,n[r++]=(16711680&o.size)>>>16,n[r++]=(65280&o.size)>>>8,n[r++]=255&o.size;return t(T.trun,n)},S=function(e,t){return"audio"===e.type?B(e,t):N(e,t)};r=function(){return t(T.ftyp,E,w,E,A)};var z,G,W,Y,q,K,X,Q,$=function(e){return t(T.mdat,e)},J=o,Z=function(e){var t,i=r(),n=u(e);return(t=new Uint8Array(i.byteLength+n.byteLength)).set(i),t.set(n,i.byteLength),t},ee=function(e,t){var i={size:0,flags:{isLeading:0,dependsOn:1,isDependedOn:0,hasRedundancy:0,degradationPriority:0,isNonSyncSample:1}};return i.dataOffset=t,i.compositionTimeOffset=e.pts-e.dts,i.duration=e.duration,i.size=4*e.length,i.size+=e.byteLength,e.keyFrame&&(i.flags.dependsOn=2,i.flags.isNonSyncSample=0),i},te=function(e){var t,i,n=[],r=[];for(r.byteLength=0,r.nalCount=0,r.duration=0,n.byteLength=0,t=0;t1&&(t=e.shift(),e.byteLength-=t.byteLength,e.nalCount-=t.nalCount,e[0][0].dts=t.dts,e[0][0].pts=t.pts,e[0][0].duration+=t.duration),e},re=function(e,t){var i,n,r,a,s,o=t||0,u=[];for(i=0;ihe/2))){for((s=le()[e.samplerate])||(s=t[0].data),o=0;o=i?e:(t.minSegmentDts=1/0,e.filter((function(e){return e.dts>=i&&(t.minSegmentDts=Math.min(t.minSegmentDts,e.dts),t.minSegmentPts=t.minSegmentDts,!0)})))},ve=function(e){var t,i,n=[];for(t=0;t=this.virtualRowCount&&"function"==typeof this.beforeRowOverflow&&this.beforeRowOverflow(e),this.rows.length>0&&(this.rows.push(""),this.rowIdx++);this.rows.length>this.virtualRowCount;)this.rows.shift(),this.rowIdx--},Re.prototype.isEmpty=function(){return 0===this.rows.length||1===this.rows.length&&""===this.rows[0]},Re.prototype.addText=function(e){this.rows[this.rowIdx]+=e},Re.prototype.backspace=function(){if(!this.isEmpty()){var e=this.rows[this.rowIdx];this.rows[this.rowIdx]=e.substr(0,e.length-1)}};var De=function(e){this.serviceNum=e,this.text="",this.currentWindow=new Re(-1),this.windows=[]};De.prototype.init=function(e,t){this.startPts=e;for(var i=0;i<8;i++)this.windows[i]=new Re(i),"function"==typeof t&&(this.windows[i].beforeRowOverflow=t)},De.prototype.setCurrentWindow=function(e){this.currentWindow=this.windows[e]};var Oe=function e(){e.prototype.init.call(this);var t=this;this.current708Packet=null,this.services={},this.push=function(e){3===e.type?(t.new708Packet(),t.add708Bytes(e)):(null===t.current708Packet&&t.new708Packet(),t.add708Bytes(e))}};Oe.prototype=new V,Oe.prototype.new708Packet=function(){null!==this.current708Packet&&this.push708Packet(),this.current708Packet={data:[],ptsVals:[]}},Oe.prototype.add708Bytes=function(e){var t=e.ccData,i=t>>>8,n=255&t;this.current708Packet.ptsVals.push(e.pts),this.current708Packet.data.push(i),this.current708Packet.data.push(n)},Oe.prototype.push708Packet=function(){var e=this.current708Packet,t=e.data,i=null,n=null,r=0,a=t[r++];for(e.seq=a>>6,e.sizeCode=63&a;r>5)&&n>0&&(i=a=t[r++]),this.pushServiceBlock(i,r,n),n>0&&(r+=n-1)},Oe.prototype.pushServiceBlock=function(e,t,i){var n,r=t,a=this.current708Packet.data,s=this.services[e];for(s||(s=this.initService(e,r));r>5,a.rowLock=(16&n)>>4,a.columnLock=(8&n)>>3,a.priority=7&n,n=i[++e],a.relativePositioning=(128&n)>>7,a.anchorVertical=127&n,n=i[++e],a.anchorHorizontal=n,n=i[++e],a.anchorPoint=(240&n)>>4,a.rowCount=15&n,n=i[++e],a.columnCount=63&n,n=i[++e],a.windowStyle=(56&n)>>3,a.penStyle=7&n,a.virtualRowCount=a.rowCount+1,e},Oe.prototype.setWindowAttributes=function(e,t){var i=this.current708Packet.data,n=i[e],r=t.currentWindow.winAttr;return n=i[++e],r.fillOpacity=(192&n)>>6,r.fillRed=(48&n)>>4,r.fillGreen=(12&n)>>2,r.fillBlue=3&n,n=i[++e],r.borderType=(192&n)>>6,r.borderRed=(48&n)>>4,r.borderGreen=(12&n)>>2,r.borderBlue=3&n,n=i[++e],r.borderType+=(128&n)>>5,r.wordWrap=(64&n)>>6,r.printDirection=(48&n)>>4,r.scrollDirection=(12&n)>>2,r.justify=3&n,n=i[++e],r.effectSpeed=(240&n)>>4,r.effectDirection=(12&n)>>2,r.displayEffect=3&n,e},Oe.prototype.flushDisplayed=function(e,t){for(var i=[],n=0;n<8;n++)t.windows[n].visible&&!t.windows[n].isEmpty()&&i.push(t.windows[n].getText());t.endPts=e,t.text=i.join("\n\n"),this.pushCaption(t),t.startPts=e},Oe.prototype.pushCaption=function(e){""!==e.text&&(this.trigger("data",{startPts:e.startPts,endPts:e.endPts,text:e.text,stream:"cc708_"+e.serviceNum}),e.text="",e.startPts=e.endPts)},Oe.prototype.displayWindows=function(e,t){var i=this.current708Packet.data[++e],n=this.getPts(e);this.flushDisplayed(n,t);for(var r=0;r<8;r++)i&1<>4,r.offset=(12&n)>>2,r.penSize=3&n,n=i[++e],r.italics=(128&n)>>7,r.underline=(64&n)>>6,r.edgeType=(56&n)>>3,r.fontStyle=7&n,e},Oe.prototype.setPenColor=function(e,t){var i=this.current708Packet.data,n=i[e],r=t.currentWindow.penColor;return n=i[++e],r.fgOpacity=(192&n)>>6,r.fgRed=(48&n)>>4,r.fgGreen=(12&n)>>2,r.fgBlue=3&n,n=i[++e],r.bgOpacity=(192&n)>>6,r.bgRed=(48&n)>>4,r.bgGreen=(12&n)>>2,r.bgBlue=3&n,n=i[++e],r.edgeRed=(48&n)>>4,r.edgeGreen=(12&n)>>2,r.edgeBlue=3&n,e},Oe.prototype.setPenLocation=function(e,t){var i=this.current708Packet.data,n=i[e],r=t.currentWindow.penLoc;return t.currentWindow.pendingNewLine=!0,n=i[++e],r.row=15&n,n=i[++e],r.column=63&n,e},Oe.prototype.reset=function(e,t){var i=this.getPts(e);return this.flushDisplayed(i,t),this.initService(t.serviceNum,e)};var Ue={42:225,92:233,94:237,95:243,96:250,123:231,124:247,125:209,126:241,127:9608,304:174,305:176,306:189,307:191,308:8482,309:162,310:163,311:9834,312:224,313:160,314:232,315:226,316:234,317:238,318:244,319:251,544:193,545:201,546:211,547:218,548:220,549:252,550:8216,551:161,552:42,553:39,554:8212,555:169,556:8480,557:8226,558:8220,559:8221,560:192,561:194,562:199,563:200,564:202,565:203,566:235,567:206,568:207,569:239,570:212,571:217,572:249,573:219,574:171,575:187,800:195,801:227,802:205,803:204,804:236,805:210,806:242,807:213,808:245,809:123,810:125,811:92,812:94,813:95,814:124,815:126,816:196,817:228,818:214,819:246,820:223,821:165,822:164,823:9474,824:197,825:229,826:216,827:248,828:9484,829:9488,830:9492,831:9496},Me=function(e){return null===e?"":(e=Ue[e]||e,String.fromCharCode(e))},Fe=[4352,4384,4608,4640,5376,5408,5632,5664,5888,5920,4096,4864,4896,5120,5152],Be=function(){for(var e=[],t=15;t--;)e.push("");return e},Ne=function e(t,i){e.prototype.init.call(this),this.field_=t||0,this.dataChannel_=i||0,this.name_="CC"+(1+(this.field_<<1|this.dataChannel_)),this.setConstants(),this.reset(),this.push=function(e){var t,i,n,r,a;if((t=32639&e.ccData)!==this.lastControlCode_){if(4096==(61440&t)?this.lastControlCode_=t:t!==this.PADDING_&&(this.lastControlCode_=null),n=t>>>8,r=255&t,t!==this.PADDING_)if(t===this.RESUME_CAPTION_LOADING_)this.mode_="popOn";else if(t===this.END_OF_CAPTION_)this.mode_="popOn",this.clearFormatting(e.pts),this.flushDisplayed(e.pts),i=this.displayed_,this.displayed_=this.nonDisplayed_,this.nonDisplayed_=i,this.startPts_=e.pts;else if(t===this.ROLL_UP_2_ROWS_)this.rollUpRows_=2,this.setRollUp(e.pts);else if(t===this.ROLL_UP_3_ROWS_)this.rollUpRows_=3,this.setRollUp(e.pts);else if(t===this.ROLL_UP_4_ROWS_)this.rollUpRows_=4,this.setRollUp(e.pts);else if(t===this.CARRIAGE_RETURN_)this.clearFormatting(e.pts),this.flushDisplayed(e.pts),this.shiftRowsUp_(),this.startPts_=e.pts;else if(t===this.BACKSPACE_)"popOn"===this.mode_?this.nonDisplayed_[this.row_]=this.nonDisplayed_[this.row_].slice(0,-1):this.displayed_[this.row_]=this.displayed_[this.row_].slice(0,-1);else if(t===this.ERASE_DISPLAYED_MEMORY_)this.flushDisplayed(e.pts),this.displayed_=Be();else if(t===this.ERASE_NON_DISPLAYED_MEMORY_)this.nonDisplayed_=Be();else if(t===this.RESUME_DIRECT_CAPTIONING_)"paintOn"!==this.mode_&&(this.flushDisplayed(e.pts),this.displayed_=Be()),this.mode_="paintOn",this.startPts_=e.pts;else if(this.isSpecialCharacter(n,r))a=Me((n=(3&n)<<8)|r),this[this.mode_](e.pts,a),this.column_++;else if(this.isExtCharacter(n,r))"popOn"===this.mode_?this.nonDisplayed_[this.row_]=this.nonDisplayed_[this.row_].slice(0,-1):this.displayed_[this.row_]=this.displayed_[this.row_].slice(0,-1),a=Me((n=(3&n)<<8)|r),this[this.mode_](e.pts,a),this.column_++;else if(this.isMidRowCode(n,r))this.clearFormatting(e.pts),this[this.mode_](e.pts," "),this.column_++,14==(14&r)&&this.addFormatting(e.pts,["i"]),1==(1&r)&&this.addFormatting(e.pts,["u"]);else if(this.isOffsetControlCode(n,r))this.column_+=3&r;else if(this.isPAC(n,r)){var s=Fe.indexOf(7968&t);"rollUp"===this.mode_&&(s-this.rollUpRows_+1<0&&(s=this.rollUpRows_-1),this.setRollUp(e.pts,s)),s!==this.row_&&(this.clearFormatting(e.pts),this.row_=s),1&r&&-1===this.formatting_.indexOf("u")&&this.addFormatting(e.pts,["u"]),16==(16&t)&&(this.column_=4*((14&t)>>1)),this.isColorPAC(r)&&14==(14&r)&&this.addFormatting(e.pts,["i"])}else this.isNormalChar(n)&&(0===r&&(r=null),a=Me(n),a+=Me(r),this[this.mode_](e.pts,a),this.column_+=a.length)}else this.lastControlCode_=null}};Ne.prototype=new V,Ne.prototype.flushDisplayed=function(e){var t=this.displayed_.map((function(e,t){try{return e.trim()}catch(e){return this.trigger("log",{level:"warn",message:"Skipping a malformed 608 caption at index "+t+"."}),""}}),this).join("\n").replace(/^\n+|\n+$/g,"");t.length&&this.trigger("data",{startPts:this.startPts_,endPts:e,text:t,stream:this.name_})},Ne.prototype.reset=function(){this.mode_="popOn",this.topRow_=0,this.startPts_=0,this.displayed_=Be(),this.nonDisplayed_=Be(),this.lastControlCode_=null,this.column_=0,this.row_=14,this.rollUpRows_=2,this.formatting_=[]},Ne.prototype.setConstants=function(){0===this.dataChannel_?(this.BASE_=16,this.EXT_=17,this.CONTROL_=(20|this.field_)<<8,this.OFFSET_=23):1===this.dataChannel_&&(this.BASE_=24,this.EXT_=25,this.CONTROL_=(28|this.field_)<<8,this.OFFSET_=31),this.PADDING_=0,this.RESUME_CAPTION_LOADING_=32|this.CONTROL_,this.END_OF_CAPTION_=47|this.CONTROL_,this.ROLL_UP_2_ROWS_=37|this.CONTROL_,this.ROLL_UP_3_ROWS_=38|this.CONTROL_,this.ROLL_UP_4_ROWS_=39|this.CONTROL_,this.CARRIAGE_RETURN_=45|this.CONTROL_,this.RESUME_DIRECT_CAPTIONING_=41|this.CONTROL_,this.BACKSPACE_=33|this.CONTROL_,this.ERASE_DISPLAYED_MEMORY_=44|this.CONTROL_,this.ERASE_NON_DISPLAYED_MEMORY_=46|this.CONTROL_},Ne.prototype.isSpecialCharacter=function(e,t){return e===this.EXT_&&t>=48&&t<=63},Ne.prototype.isExtCharacter=function(e,t){return(e===this.EXT_+1||e===this.EXT_+2)&&t>=32&&t<=63},Ne.prototype.isMidRowCode=function(e,t){return e===this.EXT_&&t>=32&&t<=47},Ne.prototype.isOffsetControlCode=function(e,t){return e===this.OFFSET_&&t>=33&&t<=35},Ne.prototype.isPAC=function(e,t){return e>=this.BASE_&&e=64&&t<=127},Ne.prototype.isColorPAC=function(e){return e>=64&&e<=79||e>=96&&e<=127},Ne.prototype.isNormalChar=function(e){return e>=32&&e<=127},Ne.prototype.setRollUp=function(e,t){if("rollUp"!==this.mode_&&(this.row_=14,this.mode_="rollUp",this.flushDisplayed(e),this.nonDisplayed_=Be(),this.displayed_=Be()),void 0!==t&&t!==this.row_)for(var i=0;i"}),"");this[this.mode_](e,i)},Ne.prototype.clearFormatting=function(e){if(this.formatting_.length){var t=this.formatting_.reverse().reduce((function(e,t){return e+""}),"");this.formatting_=[],this[this.mode_](e,t)}},Ne.prototype.popOn=function(e,t){var i=this.nonDisplayed_[this.row_];i+=t,this.nonDisplayed_[this.row_]=i},Ne.prototype.rollUp=function(e,t){var i=this.displayed_[this.row_];i+=t,this.displayed_[this.row_]=i},Ne.prototype.shiftRowsUp_=function(){var e;for(e=0;et&&(i=-1);Math.abs(t-e)>4294967296;)e+=8589934592*i;return e},ze=function e(t){var i,n;e.prototype.init.call(this),this.type_=t||"shared",this.push=function(e){"shared"!==this.type_&&e.type!==this.type_||(void 0===n&&(n=e.dts),e.dts=He(e.dts,n),e.pts=He(e.pts,n),i=e.dts,this.trigger("data",e))},this.flush=function(){n=i,this.trigger("done")},this.endTimeline=function(){this.flush(),this.trigger("endedtimeline")},this.discontinuity=function(){n=void 0,i=void 0},this.reset=function(){this.discontinuity(),this.trigger("reset")}};ze.prototype=new V;var Ge,We=ze,Ye=He,qe=function(e,t,i){var n,r="";for(n=t;n>>2;h*=4,h+=3&l[7],o.timeStamp=h,void 0===t.pts&&void 0===t.dts&&(t.pts=o.timeStamp,t.dts=o.timeStamp),this.trigger("timestamp",o)}t.frames.push(o),i+=10,i+=s}while(i>>4>1&&(n+=t[n]+1),0===i.pid)i.type="pat",e(t.subarray(n),i),this.trigger("data",i);else if(i.pid===this.pmtPid)for(i.type="pmt",e(t.subarray(n),i),this.trigger("data",i);this.packetsWaitingForPmt.length;)this.processPes_.apply(this,this.packetsWaitingForPmt.shift());else void 0===this.programMapTable?this.packetsWaitingForPmt.push([t,n,i]):this.processPes_(t,n,i)},this.processPes_=function(e,t,i){i.pid===this.programMapTable.video?i.streamType=Ve.H264_STREAM_TYPE:i.pid===this.programMapTable.audio?i.streamType=Ve.ADTS_STREAM_TYPE:i.streamType=this.programMapTable["timed-metadata"][i.pid],i.type="pes",i.data=e.subarray(t),this.trigger("data",i)}}).prototype=new V,Je.STREAM_TYPES={h264:27,adts:15},(Ze=function(){var e,t=this,i=!1,n={data:[],size:0},r={data:[],size:0},a={data:[],size:0},s=function(e,i,n){var r,a,s=new Uint8Array(e.size),o={type:i},u=0,l=0;if(e.data.length&&!(e.size<9)){for(o.trackId=e.data[0].pid,u=0;u>>3,d.pts*=4,d.pts+=(6&h[13])>>>1,d.dts=d.pts,64&c&&(d.dts=(14&h[14])<<27|(255&h[15])<<20|(254&h[16])<<12|(255&h[17])<<5|(254&h[18])>>>3,d.dts*=4,d.dts+=(6&h[18])>>>1)),d.data=h.subarray(9+h[8])),r="video"===i||o.packetLength<=e.size,(n||r)&&(e.size=0,e.data.length=0),r&&t.trigger("data",o)}};Ze.prototype.init.call(this),this.push=function(o){({pat:function(){},pes:function(){var e,t;switch(o.streamType){case Ve.H264_STREAM_TYPE:e=n,t="video";break;case Ve.ADTS_STREAM_TYPE:e=r,t="audio";break;case Ve.METADATA_STREAM_TYPE:e=a,t="timed-metadata";break;default:return}o.payloadUnitStartIndicator&&s(e,t,!0),e.data.push(o),e.size+=o.data.byteLength},pmt:function(){var n={type:"metadata",tracks:[]};null!==(e=o.programMapTable).video&&n.tracks.push({timelineStartInfo:{baseMediaDecodeTime:0},id:+e.video,codec:"avc",type:"video"}),null!==e.audio&&n.tracks.push({timelineStartInfo:{baseMediaDecodeTime:0},id:+e.audio,codec:"adts",type:"audio"}),i=!0,t.trigger("data",n)}})[o.type]()},this.reset=function(){n.size=0,n.data.length=0,r.size=0,r.data.length=0,this.trigger("reset")},this.flushStreams_=function(){s(n,"video"),s(r,"audio"),s(a,"timed-metadata")},this.flush=function(){if(!i&&e){var n={type:"metadata",tracks:[]};null!==e.video&&n.tracks.push({timelineStartInfo:{baseMediaDecodeTime:0},id:+e.video,codec:"avc",type:"video"}),null!==e.audio&&n.tracks.push({timelineStartInfo:{baseMediaDecodeTime:0},id:+e.audio,codec:"adts",type:"audio"}),t.trigger("data",n)}i=!1,this.flushStreams_(),this.trigger("done")}}).prototype=new V;var it={PAT_PID:0,MP2T_PACKET_LENGTH:188,TransportPacketStream:$e,TransportParseStream:Je,ElementaryStream:Ze,TimestampRolloverStream:tt,CaptionStream:je.CaptionStream,Cea608Stream:je.Cea608Stream,Cea708Stream:je.Cea708Stream,MetadataStream:et};for(var nt in Ve)Ve.hasOwnProperty(nt)&&(it[nt]=Ve[nt]);var rt,at=it,st=he,ot=[96e3,88200,64e3,48e3,44100,32e3,24e3,22050,16e3,12e3,11025,8e3,7350];(rt=function(e){var t,i=0;rt.prototype.init.call(this),this.skipWarn_=function(e,t){this.trigger("log",{level:"warn",message:"adts skiping bytes "+e+" to "+t+" in frame "+i+" outside syncword"})},this.push=function(n){var r,a,s,o,u,l=0;if(e||(i=0),"audio"===n.type){var h;for(t&&t.length?(s=t,(t=new Uint8Array(s.byteLength+n.data.byteLength)).set(s),t.set(n.data,s.byteLength)):t=n.data;l+7>5,u=(o=1024*(1+(3&t[l+6])))*st/ot[(60&t[l+2])>>>2],t.byteLength-l>>6&3),channelcount:(1&t[l+2])<<2|(192&t[l+3])>>>6,samplerate:ot[(60&t[l+2])>>>2],samplingfrequencyindex:(60&t[l+2])>>>2,samplesize:16,data:t.subarray(l+7+a,l+r)}),i++,l+=r}else"number"!=typeof h&&(h=l),l++;"number"==typeof h&&(this.skipWarn_(h,l),h=null),t=t.subarray(l)}},this.flush=function(){i=0,this.trigger("done")},this.reset=function(){t=void 0,this.trigger("reset")},this.endTimeline=function(){t=void 0,this.trigger("endedtimeline")}}).prototype=new V;var ut,lt,ht,dt=rt,ct=function(e){var t=e.byteLength,i=0,n=0;this.length=function(){return 8*t},this.bitsAvailable=function(){return 8*t+n},this.loadWord=function(){var r=e.byteLength-t,a=new Uint8Array(4),s=Math.min(4,t);if(0===s)throw new Error("no bytes available");a.set(e.subarray(r,r+s)),i=new DataView(a.buffer).getUint32(0),n=8*s,t-=s},this.skipBits=function(e){var r;n>e?(i<<=e,n-=e):(e-=n,e-=8*(r=Math.floor(e/8)),t-=r,this.loadWord(),i<<=e,n-=e)},this.readBits=function(e){var r=Math.min(n,e),a=i>>>32-r;return(n-=r)>0?i<<=r:t>0&&this.loadWord(),(r=e-r)>0?a<>>e))return i<<=e,n-=e,e;return this.loadWord(),e+this.skipLeadingZeros()},this.skipUnsignedExpGolomb=function(){this.skipBits(1+this.skipLeadingZeros())},this.skipExpGolomb=function(){this.skipBits(1+this.skipLeadingZeros())},this.readUnsignedExpGolomb=function(){var e=this.skipLeadingZeros();return this.readBits(e+1)-1},this.readExpGolomb=function(){var e=this.readUnsignedExpGolomb();return 1&e?1+e>>>1:-1*(e>>>1)},this.readBoolean=function(){return 1===this.readBits(1)},this.readUnsignedByte=function(){return this.readBits(8)},this.loadWord()};(lt=function(){var e,t,i=0;lt.prototype.init.call(this),this.push=function(n){var r;t?((r=new Uint8Array(t.byteLength+n.data.byteLength)).set(t),r.set(n.data,t.byteLength),t=r):t=n.data;for(var a=t.byteLength;i3&&this.trigger("data",t.subarray(i+3)),t=null,i=0,this.trigger("done")},this.endTimeline=function(){this.flush(),this.trigger("endedtimeline")}}).prototype=new V,ht={100:!0,110:!0,122:!0,244:!0,44:!0,83:!0,86:!0,118:!0,128:!0,138:!0,139:!0,134:!0},(ut=function(){var e,t,i,n,r,a,s,o=new lt;ut.prototype.init.call(this),e=this,this.push=function(e){"video"===e.type&&(t=e.trackId,i=e.pts,n=e.dts,o.push(e))},o.on("data",(function(s){var o={trackId:t,pts:i,dts:n,data:s,nalUnitTypeCode:31&s[0]};switch(o.nalUnitTypeCode){case 5:o.nalUnitType="slice_layer_without_partitioning_rbsp_idr";break;case 6:o.nalUnitType="sei_rbsp",o.escapedRBSP=r(s.subarray(1));break;case 7:o.nalUnitType="seq_parameter_set_rbsp",o.escapedRBSP=r(s.subarray(1)),o.config=a(o.escapedRBSP);break;case 8:o.nalUnitType="pic_parameter_set_rbsp";break;case 9:o.nalUnitType="access_unit_delimiter_rbsp"}e.trigger("data",o)})),o.on("done",(function(){e.trigger("done")})),o.on("partialdone",(function(){e.trigger("partialdone")})),o.on("reset",(function(){e.trigger("reset")})),o.on("endedtimeline",(function(){e.trigger("endedtimeline")})),this.flush=function(){o.flush()},this.partialFlush=function(){o.partialFlush()},this.reset=function(){o.reset()},this.endTimeline=function(){o.endTimeline()},s=function(e,t){var i,n=8,r=8;for(i=0;i=0?i:0,(16&e[t+5])>>4?i+20:i+10},gt=function(e){return e[0]<<21|e[1]<<14|e[2]<<7|e[3]},vt={isLikelyAacData:function(e){var t=function e(t,i){return t.length-i<10||t[i]!=="I".charCodeAt(0)||t[i+1]!=="D".charCodeAt(0)||t[i+2]!=="3".charCodeAt(0)?i:e(t,i+=_t(t,i))}(e,0);return e.length>=t+2&&255==(255&e[t])&&240==(240&e[t+1])&&16==(22&e[t+1])},parseId3TagSize:_t,parseAdtsSize:function(e,t){var i=(224&e[t+5])>>5,n=e[t+4]<<3;return 6144&e[t+3]|n|i},parseType:function(e,t){return e[t]==="I".charCodeAt(0)&&e[t+1]==="D".charCodeAt(0)&&e[t+2]==="3".charCodeAt(0)?"timed-metadata":!0&e[t]&&240==(240&e[t+1])?"audio":null},parseSampleRate:function(e){for(var t=0;t+5>>2];t++}return null},parseAacTimestamp:function(e){var t,i,n;t=10,64&e[5]&&(t+=4,t+=gt(e.subarray(10,14)));do{if((i=gt(e.subarray(t+4,t+8)))<1)return null;if("PRIV"===String.fromCharCode(e[t],e[t+1],e[t+2],e[t+3])){n=e.subarray(t+10,t+i+10);for(var r=0;r>>2;return s*=4,s+=3&a[7]}break}}t+=10,t+=i}while(t=3;)if(e[u]!=="I".charCodeAt(0)||e[u+1]!=="D".charCodeAt(0)||e[u+2]!=="3".charCodeAt(0))if(255!=(255&e[u])||240!=(240&e[u+1]))u++;else{if(e.length-u<7)break;if(u+(o=vt.parseAdtsSize(e,u))>e.length)break;a={type:"audio",data:e.subarray(u,u+o),pts:t,dts:t},this.trigger("data",a),u+=o}else{if(e.length-u<10)break;if(u+(o=vt.parseId3TagSize(e,u))>e.length)break;r={type:"timed-metadata",data:e.subarray(u,u+o)},this.trigger("data",r),u+=o}n=e.length-u,e=n>0?e.subarray(u):new Uint8Array},this.reset=function(){e=new Uint8Array,this.trigger("reset")},this.endTimeline=function(){e=new Uint8Array,this.trigger("endedtimeline")}}).prototype=new V;var yt,bt,St,Tt,Et=ft,wt=["audioobjecttype","channelcount","samplerate","samplingfrequencyindex","samplesize"],At=["width","height","profileIdc","levelIdc","profileCompatibility","sarRatio"],Ct=pt.H264Stream,kt=vt.isLikelyAacData,Pt=he,It=function(e,t){var i;if(e.length!==t.length)return!1;for(i=0;i=-1e4&&i<=45e3&&(!n||o>i)&&(n=a,o=i));return n?n.gop:null},this.alignGopsAtStart_=function(e){var t,i,n,r,a,o,u,l;for(a=e.byteLength,o=e.nalCount,u=e.duration,t=i=0;tn.pts?t++:(i++,a-=r.byteLength,o-=r.nalCount,u-=r.duration);return 0===i?e:i===e.length?null:((l=e.slice(i)).byteLength=a,l.duration=u,l.nalCount=o,l.pts=l[0].pts,l.dts=l[0].dts,l)},this.alignGopsAtEnd_=function(e){var t,i,n,r,a,o,u;for(t=s.length-1,i=e.length-1,a=null,o=!1;t>=0&&i>=0;){if(n=s[t],r=e[i],n.pts===r.pts){o=!0;break}n.pts>r.pts?t--:(t===s.length-1&&(a=i),i--)}if(!o&&null===a)return null;if(0===(u=o?i:a))return e;var l=e.slice(u),h=l.reduce((function(e,t){return e.byteLength+=t.byteLength,e.duration+=t.duration,e.nalCount+=t.nalCount,e}),{byteLength:0,duration:0,nalCount:0});return l.byteLength=h.byteLength,l.duration=h.duration,l.nalCount=h.nalCount,l.pts=l[0].pts,l.dts=l[0].dts,l},this.alignGopsWith=function(e){s=e}}).prototype=new V,(Tt=function(e,t){this.numberOfTracks=0,this.metadataStream=t,void 0!==(e=e||{}).remux?this.remuxTracks=!!e.remux:this.remuxTracks=!0,"boolean"==typeof e.keepOriginalTimestamps?this.keepOriginalTimestamps=e.keepOriginalTimestamps:this.keepOriginalTimestamps=!1,this.pendingTracks=[],this.videoTrack=null,this.pendingBoxes=[],this.pendingCaptions=[],this.pendingMetadata=[],this.pendingBytes=0,this.emittedTracks=0,Tt.prototype.init.call(this),this.push=function(e){return e.text?this.pendingCaptions.push(e):e.frames?this.pendingMetadata.push(e):(this.pendingTracks.push(e.track),this.pendingBytes+=e.boxes.byteLength,"video"===e.track.type&&(this.videoTrack=e.track,this.pendingBoxes.push(e.boxes)),void("audio"===e.track.type&&(this.audioTrack=e.track,this.pendingBoxes.unshift(e.boxes))))}}).prototype=new V,Tt.prototype.flush=function(e){var t,i,n,r,a=0,s={captions:[],captionStreams:{},metadata:[],info:{}},o=0;if(this.pendingTracks.length=this.numberOfTracks&&(this.trigger("done"),this.emittedTracks=0))}if(this.videoTrack?(o=this.videoTrack.timelineStartInfo.pts,At.forEach((function(e){s.info[e]=this.videoTrack[e]}),this)):this.audioTrack&&(o=this.audioTrack.timelineStartInfo.pts,wt.forEach((function(e){s.info[e]=this.audioTrack[e]}),this)),this.videoTrack||this.audioTrack){for(1===this.pendingTracks.length?s.type=this.pendingTracks[0].type:s.type="combined",this.emittedTracks+=this.pendingTracks.length,n=Z(this.pendingTracks),s.initSegment=new Uint8Array(n.byteLength),s.initSegment.set(n),s.data=new Uint8Array(this.pendingBytes),r=0;r=this.numberOfTracks&&(this.trigger("done"),this.emittedTracks=0)},Tt.prototype.setRemux=function(e){this.remuxTracks=e},(St=function(e){var t,i,n=this,r=!0;St.prototype.init.call(this),e=e||{},this.baseMediaDecodeTime=e.baseMediaDecodeTime||0,this.transmuxPipeline_={},this.setupAacPipeline=function(){var r={};this.transmuxPipeline_=r,r.type="aac",r.metadataStream=new at.MetadataStream,r.aacStream=new Et,r.audioTimestampRolloverStream=new at.TimestampRolloverStream("audio"),r.timedMetadataTimestampRolloverStream=new at.TimestampRolloverStream("timed-metadata"),r.adtsStream=new dt,r.coalesceStream=new Tt(e,r.metadataStream),r.headOfPipeline=r.aacStream,r.aacStream.pipe(r.audioTimestampRolloverStream).pipe(r.adtsStream),r.aacStream.pipe(r.timedMetadataTimestampRolloverStream).pipe(r.metadataStream).pipe(r.coalesceStream),r.metadataStream.on("timestamp",(function(e){r.aacStream.setTimestamp(e.timeStamp)})),r.aacStream.on("data",(function(a){"timed-metadata"!==a.type&&"audio"!==a.type||r.audioSegmentStream||(i=i||{timelineStartInfo:{baseMediaDecodeTime:n.baseMediaDecodeTime},codec:"adts",type:"audio"},r.coalesceStream.numberOfTracks++,r.audioSegmentStream=new bt(i,e),r.audioSegmentStream.on("log",n.getLogTrigger_("audioSegmentStream")),r.audioSegmentStream.on("timingInfo",n.trigger.bind(n,"audioTimingInfo")),r.adtsStream.pipe(r.audioSegmentStream).pipe(r.coalesceStream),n.trigger("trackinfo",{hasAudio:!!i,hasVideo:!!t}))})),r.coalesceStream.on("data",this.trigger.bind(this,"data")),r.coalesceStream.on("done",this.trigger.bind(this,"done"))},this.setupTsPipeline=function(){var r={};this.transmuxPipeline_=r,r.type="ts",r.metadataStream=new at.MetadataStream,r.packetStream=new at.TransportPacketStream,r.parseStream=new at.TransportParseStream,r.elementaryStream=new at.ElementaryStream,r.timestampRolloverStream=new at.TimestampRolloverStream,r.adtsStream=new dt,r.h264Stream=new Ct,r.captionStream=new at.CaptionStream(e),r.coalesceStream=new Tt(e,r.metadataStream),r.headOfPipeline=r.packetStream,r.packetStream.pipe(r.parseStream).pipe(r.elementaryStream).pipe(r.timestampRolloverStream),r.timestampRolloverStream.pipe(r.h264Stream),r.timestampRolloverStream.pipe(r.adtsStream),r.timestampRolloverStream.pipe(r.metadataStream).pipe(r.coalesceStream),r.h264Stream.pipe(r.captionStream).pipe(r.coalesceStream),r.elementaryStream.on("data",(function(a){var s;if("metadata"===a.type){for(s=a.tracks.length;s--;)t||"video"!==a.tracks[s].type?i||"audio"!==a.tracks[s].type||((i=a.tracks[s]).timelineStartInfo.baseMediaDecodeTime=n.baseMediaDecodeTime):(t=a.tracks[s]).timelineStartInfo.baseMediaDecodeTime=n.baseMediaDecodeTime;t&&!r.videoSegmentStream&&(r.coalesceStream.numberOfTracks++,r.videoSegmentStream=new yt(t,e),r.videoSegmentStream.on("log",n.getLogTrigger_("videoSegmentStream")),r.videoSegmentStream.on("timelineStartInfo",(function(t){i&&!e.keepOriginalTimestamps&&(i.timelineStartInfo=t,r.audioSegmentStream.setEarliestDts(t.dts-n.baseMediaDecodeTime))})),r.videoSegmentStream.on("processedGopsInfo",n.trigger.bind(n,"gopInfo")),r.videoSegmentStream.on("segmentTimingInfo",n.trigger.bind(n,"videoSegmentTimingInfo")),r.videoSegmentStream.on("baseMediaDecodeTime",(function(e){i&&r.audioSegmentStream.setVideoBaseMediaDecodeTime(e)})),r.videoSegmentStream.on("timingInfo",n.trigger.bind(n,"videoTimingInfo")),r.h264Stream.pipe(r.videoSegmentStream).pipe(r.coalesceStream)),i&&!r.audioSegmentStream&&(r.coalesceStream.numberOfTracks++,r.audioSegmentStream=new bt(i,e),r.audioSegmentStream.on("log",n.getLogTrigger_("audioSegmentStream")),r.audioSegmentStream.on("timingInfo",n.trigger.bind(n,"audioTimingInfo")),r.audioSegmentStream.on("segmentTimingInfo",n.trigger.bind(n,"audioSegmentTimingInfo")),r.adtsStream.pipe(r.audioSegmentStream).pipe(r.coalesceStream)),n.trigger("trackinfo",{hasAudio:!!i,hasVideo:!!t})}})),r.coalesceStream.on("data",this.trigger.bind(this,"data")),r.coalesceStream.on("id3Frame",(function(e){e.dispatchType=r.metadataStream.dispatchType,n.trigger("id3Frame",e)})),r.coalesceStream.on("caption",this.trigger.bind(this,"caption")),r.coalesceStream.on("done",this.trigger.bind(this,"done"))},this.setBaseMediaDecodeTime=function(n){var r=this.transmuxPipeline_;e.keepOriginalTimestamps||(this.baseMediaDecodeTime=n),i&&(i.timelineStartInfo.dts=void 0,i.timelineStartInfo.pts=void 0,Se(i),r.audioTimestampRolloverStream&&r.audioTimestampRolloverStream.discontinuity()),t&&(r.videoSegmentStream&&(r.videoSegmentStream.gopCache_=[]),t.timelineStartInfo.dts=void 0,t.timelineStartInfo.pts=void 0,Se(t),r.captionStream.reset()),r.timestampRolloverStream&&r.timestampRolloverStream.discontinuity()},this.setAudioAppendStart=function(e){i&&this.transmuxPipeline_.audioSegmentStream.setAudioAppendStart(e)},this.setRemux=function(t){var i=this.transmuxPipeline_;e.remux=t,i&&i.coalesceStream&&i.coalesceStream.setRemux(t)},this.alignGopsWith=function(e){t&&this.transmuxPipeline_.videoSegmentStream&&this.transmuxPipeline_.videoSegmentStream.alignGopsWith(e)},this.getLogTrigger_=function(e){var t=this;return function(i){i.stream=e,t.trigger("log",i)}},this.push=function(e){if(r){var t=kt(e);if(t&&"aac"!==this.transmuxPipeline_.type?this.setupAacPipeline():t||"ts"===this.transmuxPipeline_.type||this.setupTsPipeline(),this.transmuxPipeline_)for(var i=Object.keys(this.transmuxPipeline_),n=0;n>>0},Mt=function(e){var t="";return t+=String.fromCharCode(e[0]),t+=String.fromCharCode(e[1]),t+=String.fromCharCode(e[2]),t+=String.fromCharCode(e[3])},Ft=Ut,Bt=function e(t,i){var n,r,a,s,o,u=[];if(!i.length)return null;for(n=0;n1?n+r:t.byteLength,a===i[0]&&(1===i.length?u.push(t.subarray(n+8,s)):(o=e(t.subarray(n+8,s),i.slice(1))).length&&(u=u.concat(o))),n=s;return u},Nt=Ut,jt=function(e){var t={version:e[0],flags:new Uint8Array(e.subarray(1,4)),baseMediaDecodeTime:Nt(e[4]<<24|e[5]<<16|e[6]<<8|e[7])};return 1===t.version&&(t.baseMediaDecodeTime*=Math.pow(2,32),t.baseMediaDecodeTime+=Nt(e[8]<<24|e[9]<<16|e[10]<<8|e[11])),t},Vt=function(e){return{isLeading:(12&e[0])>>>2,dependsOn:3&e[0],isDependedOn:(192&e[1])>>>6,hasRedundancy:(48&e[1])>>>4,paddingValue:(14&e[1])>>>1,isNonSyncSample:1&e[1],degradationPriority:e[2]<<8|e[3]}},Ht=function(e){var t,i={version:e[0],flags:new Uint8Array(e.subarray(1,4)),samples:[]},n=new DataView(e.buffer,e.byteOffset,e.byteLength),r=1&i.flags[2],a=4&i.flags[2],s=1&i.flags[1],o=2&i.flags[1],u=4&i.flags[1],l=8&i.flags[1],h=n.getUint32(4),d=8;for(r&&(i.dataOffset=n.getInt32(d),d+=4),a&&h&&(t={flags:Vt(e.subarray(d,d+4))},d+=4,s&&(t.duration=n.getUint32(d),d+=4),o&&(t.size=n.getUint32(d),d+=4),l&&(1===i.version?t.compositionTimeOffset=n.getInt32(d):t.compositionTimeOffset=n.getUint32(d),d+=4),i.samples.push(t),h--);h--;)t={},s&&(t.duration=n.getUint32(d),d+=4),o&&(t.size=n.getUint32(d),d+=4),u&&(t.flags=Vt(e.subarray(d,d+4)),d+=4),l&&(1===i.version?t.compositionTimeOffset=n.getInt32(d):t.compositionTimeOffset=n.getUint32(d),d+=4),i.samples.push(t);return i},zt=function(e){var t,i=new DataView(e.buffer,e.byteOffset,e.byteLength),n={version:e[0],flags:new Uint8Array(e.subarray(1,4)),trackId:i.getUint32(4)},r=1&n.flags[2],a=2&n.flags[2],s=8&n.flags[2],o=16&n.flags[2],u=32&n.flags[2],l=65536&n.flags[0],h=131072&n.flags[0];return t=8,r&&(t+=4,n.baseDataOffset=i.getUint32(12),t+=4),a&&(n.sampleDescriptionIndex=i.getUint32(t),t+=4),s&&(n.defaultSampleDuration=i.getUint32(t),t+=4),o&&(n.defaultSampleSize=i.getUint32(t),t+=4),u&&(n.defaultSampleFlags=i.getUint32(t)),l&&(n.durationIsEmpty=!0),!r&&h&&(n.baseDataOffsetIsMoof=!0),n},Gt=ke,Wt=je.CaptionStream,Yt=function(e,t){for(var i=e,n=0;n0?jt(l[0]).baseMediaDecodeTime:0,d=Bt(a,["trun"]);t===u&&d.length>0&&(i=function(e,t,i){var n,r,a,s,o=new DataView(e.buffer,e.byteOffset,e.byteLength),u={logs:[],seiNals:[]};for(r=0;r+40;){var u=t.shift();this.parse(u,a,s)}return(o=function(e,t,i){if(null===t)return null;var n=qt(e,t)[t]||{};return{seiNals:n.seiNals,logs:n.logs,timescale:i}}(e,i,n))&&o.logs&&(r.logs=r.logs.concat(o.logs)),null!==o&&o.seiNals?(this.pushNals(o.seiNals),this.flushStream(),r):r.logs.length?{logs:r.logs,captions:[],captionStreams:[]}:null},this.pushNals=function(t){if(!this.isInitialized()||!t||0===t.length)return null;t.forEach((function(t){e.push(t)}))},this.flushStream=function(){if(!this.isInitialized())return null;a?e.partialFlush():e.flush()},this.clearParsedCaptions=function(){r.captions=[],r.captionStreams={},r.logs=[]},this.resetCaptionStream=function(){if(!this.isInitialized())return null;e.reset()},this.clearAllCaptions=function(){this.clearParsedCaptions(),this.resetCaptionStream()},this.reset=function(){t=[],i=null,n=null,r?this.clearParsedCaptions():r={captions:[],captionStreams:{},logs:[]},this.resetCaptionStream()},this.reset()},Xt=Ut,Qt=function(e){return("00"+e.toString(16)).slice(-2)};xt=function(e,t){var i,n,r;return i=Bt(t,["moof","traf"]),n=[].concat.apply([],i.map((function(t){return Bt(t,["tfhd"]).map((function(i){var n,r,a;return n=Xt(i[4]<<24|i[5]<<16|i[6]<<8|i[7]),r=e[n]||9e4,(a="number"!=typeof(a=Bt(t,["tfdt"]).map((function(e){var t,i;return t=e[0],i=Xt(e[4]<<24|e[5]<<16|e[6]<<8|e[7]),1===t&&(i*=Math.pow(2,32),i+=Xt(e[8]<<24|e[9]<<16|e[10]<<8|e[11])),i}))[0])||isNaN(a)?1/0:a)/r}))}))),r=Math.min.apply(null,n),isFinite(r)?r:0},Rt=function(e){var t=Bt(e,["moov","trak"]),i=[];return t.forEach((function(e){var t,n,r={},a=Bt(e,["tkhd"])[0];a&&(n=(t=new DataView(a.buffer,a.byteOffset,a.byteLength)).getUint8(0),r.id=0===n?t.getUint32(12):t.getUint32(20));var s=Bt(e,["mdia","hdlr"])[0];if(s){var o=Mt(s.subarray(8,12));r.type="vide"===o?"video":"soun"===o?"audio":o}var u=Bt(e,["mdia","minf","stbl","stsd"])[0];if(u){var l=u.subarray(8);r.codec=Mt(l.subarray(4,8));var h,d=Bt(l,[r.codec])[0];d&&(/^[a-z]vc[1-9]$/i.test(r.codec)?(h=d.subarray(78),"avcC"===Mt(h.subarray(4,8))&&h.length>11?(r.codec+=".",r.codec+=Qt(h[9]),r.codec+=Qt(h[10]),r.codec+=Qt(h[11])):r.codec="avc1.4d400d"):/^mp4[a,v]$/i.test(r.codec)?(h=d.subarray(28),"esds"===Mt(h.subarray(4,8))&&h.length>20&&0!==h[19]?(r.codec+="."+Qt(h[19]),r.codec+="."+Qt(h[20]>>>2&63).replace(/^0/,"")):r.codec="mp4a.40.2"):r.codec=r.codec.toLowerCase())}var c=Bt(e,["mdia","mdhd"])[0];c&&(r.timescale=Dt(c)),i.push(r)})),i};var $t=xt,Jt=Rt,Zt=(Dt=function(e){var t=0===e[0]?12:20;return Xt(e[t]<<24|e[t+1]<<16|e[t+2]<<8|e[t+3])},function(e){var t=31&e[1];return t<<=8,t|=e[2]}),ei=function(e){return!!(64&e[1])},ti=function(e){var t=0;return(48&e[3])>>>4>1&&(t+=e[4]+1),t},ii=function(e){switch(e){case 5:return"slice_layer_without_partitioning_rbsp_idr";case 6:return"sei_rbsp";case 7:return"seq_parameter_set_rbsp";case 8:return"pic_parameter_set_rbsp";case 9:return"access_unit_delimiter_rbsp";default:return null}},ni={parseType:function(e,t){var i=Zt(e);return 0===i?"pat":i===t?"pmt":t?"pes":null},parsePat:function(e){var t=ei(e),i=4+ti(e);return t&&(i+=e[i]+1),(31&e[i+10])<<8|e[i+11]},parsePmt:function(e){var t={},i=ei(e),n=4+ti(e);if(i&&(n+=e[n]+1),1&e[n+5]){var r;r=3+((15&e[n+1])<<8|e[n+2])-4;for(var a=12+((15&e[n+10])<<8|e[n+11]);a=e.byteLength)return null;var i,n=null;return 192&(i=e[t+7])&&((n={}).pts=(14&e[t+9])<<27|(255&e[t+10])<<20|(254&e[t+11])<<12|(255&e[t+12])<<5|(254&e[t+13])>>>3,n.pts*=4,n.pts+=(6&e[t+13])>>>1,n.dts=n.pts,64&i&&(n.dts=(14&e[t+14])<<27|(255&e[t+15])<<20|(254&e[t+16])<<12|(255&e[t+17])<<5|(254&e[t+18])>>>3,n.dts*=4,n.dts+=(6&e[t+18])>>>1)),n},videoPacketContainsKeyFrame:function(e){for(var t=4+ti(e),i=e.subarray(t),n=0,r=0,a=!1;r3&&"slice_layer_without_partitioning_rbsp_idr"===ii(31&i[r+3])&&(a=!0),a}},ri=Ye,ai={};ai.ts=ni,ai.aac=vt;var si=he,oi=function(e,t,i){for(var n,r,a,s,o=0,u=188,l=!1;u<=e.byteLength;)if(71!==e[o]||71!==e[u]&&u!==e.byteLength)o++,u++;else{switch(n=e.subarray(o,u),ai.ts.parseType(n,t.pid)){case"pes":r=ai.ts.parsePesType(n,t.table),a=ai.ts.parsePayloadUnitStartIndicator(n),"audio"===r&&a&&(s=ai.ts.parsePesTime(n))&&(s.type="audio",i.audio.push(s),l=!0)}if(l)break;o+=188,u+=188}for(o=(u=e.byteLength)-188,l=!1;o>=0;)if(71!==e[o]||71!==e[u]&&u!==e.byteLength)o--,u--;else{switch(n=e.subarray(o,u),ai.ts.parseType(n,t.pid)){case"pes":r=ai.ts.parsePesType(n,t.table),a=ai.ts.parsePayloadUnitStartIndicator(n),"audio"===r&&a&&(s=ai.ts.parsePesTime(n))&&(s.type="audio",i.audio.push(s),l=!0)}if(l)break;o-=188,u-=188}},ui=function(e,t,i){for(var n,r,a,s,o,u,l,h=0,d=188,c=!1,f={data:[],size:0};d=0;)if(71!==e[h]||71!==e[d])h--,d--;else{switch(n=e.subarray(h,d),ai.ts.parseType(n,t.pid)){case"pes":r=ai.ts.parsePesType(n,t.table),a=ai.ts.parsePayloadUnitStartIndicator(n),"video"===r&&a&&(s=ai.ts.parsePesTime(n))&&(s.type="video",i.video.push(s),c=!0)}if(c)break;h-=188,d-=188}},li=function(e){var t={pid:null,table:null},i={};for(var n in function(e,t){for(var i,n=0,r=188;r=3;){switch(ai.aac.parseType(e,o)){case"timed-metadata":if(e.length-o<10){i=!0;break}if((s=ai.aac.parseId3TagSize(e,o))>e.length){i=!0;break}null===a&&(t=e.subarray(o,o+s),a=ai.aac.parseAacTimestamp(t)),o+=s;break;case"audio":if(e.length-o<7){i=!0;break}if((s=ai.aac.parseAdtsSize(e,o))>e.length){i=!0;break}null===r&&(t=e.subarray(o,o+s),r=ai.aac.parseSampleRate(t)),n++,o+=s;break;default:o++}if(i)return null}if(null===r||null===a)return null;var u=si/r;return{audio:[{type:"audio",dts:a,pts:a},{type:"audio",dts:a+1024*n*u,pts:a+1024*n*u}]}}(e):li(e))&&(i.audio||i.video)?(function(e,t){if(e.audio&&e.audio.length){var i=t;(void 0===i||isNaN(i))&&(i=e.audio[0].dts),e.audio.forEach((function(e){e.dts=ri(e.dts,i),e.pts=ri(e.pts,i),e.dtsTime=e.dts/si,e.ptsTime=e.pts/si}))}if(e.video&&e.video.length){var n=t;if((void 0===n||isNaN(n))&&(n=e.video[0].dts),e.video.forEach((function(e){e.dts=ri(e.dts,n),e.pts=ri(e.pts,n),e.dtsTime=e.dts/si,e.ptsTime=e.pts/si})),e.firstKeyFrame){var r=e.firstKeyFrame;r.dts=ri(r.dts,n),r.pts=ri(r.pts,n),r.dtsTime=r.dts/si,r.ptsTime=r.pts/si}}}(i,t),i):null},di=function(){function e(e,t){this.options=t||{},this.self=e,this.init()}var t=e.prototype;return t.init=function(){this.transmuxer&&this.transmuxer.dispose(),this.transmuxer=new Ot.Transmuxer(this.options),function(e,t){t.on("data",(function(t){var i=t.initSegment;t.initSegment={data:i.buffer,byteOffset:i.byteOffset,byteLength:i.byteLength};var n=t.data;t.data=n.buffer,e.postMessage({action:"data",segment:t,byteOffset:n.byteOffset,byteLength:n.byteLength},[t.data])})),t.on("done",(function(t){e.postMessage({action:"done"})})),t.on("gopInfo",(function(t){e.postMessage({action:"gopInfo",gopInfo:t})})),t.on("videoSegmentTimingInfo",(function(t){var i={start:{decode:ce(t.start.dts),presentation:ce(t.start.pts)},end:{decode:ce(t.end.dts),presentation:ce(t.end.pts)},baseMediaDecodeTime:ce(t.baseMediaDecodeTime)};t.prependedContentDuration&&(i.prependedContentDuration=ce(t.prependedContentDuration)),e.postMessage({action:"videoSegmentTimingInfo",videoSegmentTimingInfo:i})})),t.on("audioSegmentTimingInfo",(function(t){var i={start:{decode:ce(t.start.dts),presentation:ce(t.start.pts)},end:{decode:ce(t.end.dts),presentation:ce(t.end.pts)},baseMediaDecodeTime:ce(t.baseMediaDecodeTime)};t.prependedContentDuration&&(i.prependedContentDuration=ce(t.prependedContentDuration)),e.postMessage({action:"audioSegmentTimingInfo",audioSegmentTimingInfo:i})})),t.on("id3Frame",(function(t){e.postMessage({action:"id3Frame",id3Frame:t})})),t.on("caption",(function(t){e.postMessage({action:"caption",caption:t})})),t.on("trackinfo",(function(t){e.postMessage({action:"trackinfo",trackInfo:t})})),t.on("audioTimingInfo",(function(t){e.postMessage({action:"audioTimingInfo",audioTimingInfo:{start:ce(t.start),end:ce(t.end)}})})),t.on("videoTimingInfo",(function(t){e.postMessage({action:"videoTimingInfo",videoTimingInfo:{start:ce(t.start),end:ce(t.end)}})})),t.on("log",(function(t){e.postMessage({action:"log",log:t})}))}(this.self,this.transmuxer)},t.pushMp4Captions=function(e){this.captionParser||(this.captionParser=new Kt,this.captionParser.init());var t=new Uint8Array(e.data,e.byteOffset,e.byteLength),i=this.captionParser.parse(t,e.trackIds,e.timescales);this.self.postMessage({action:"mp4Captions",captions:i&&i.captions||[],logs:i&&i.logs||[],data:t.buffer},[t.buffer])},t.probeMp4StartTime=function(e){var t=e.timescales,i=e.data,n=$t(t,i);this.self.postMessage({action:"probeMp4StartTime",startTime:n,data:i},[i.buffer])},t.probeMp4Tracks=function(e){var t=e.data,i=Jt(t);this.self.postMessage({action:"probeMp4Tracks",tracks:i,data:t},[t.buffer])},t.probeTs=function(e){var t=e.data,i=e.baseStartTime,n="number"!=typeof i||isNaN(i)?void 0:i*he,r=hi(t,n),a=null;r&&((a={hasVideo:r.video&&2===r.video.length||!1,hasAudio:r.audio&&2===r.audio.length||!1}).hasVideo&&(a.videoStart=r.video[0].ptsTime),a.hasAudio&&(a.audioStart=r.audio[0].ptsTime)),this.self.postMessage({action:"probeTs",result:a,data:t},[t.buffer])},t.clearAllMp4Captions=function(){this.captionParser&&this.captionParser.clearAllCaptions()},t.clearParsedMp4Captions=function(){this.captionParser&&this.captionParser.clearParsedCaptions()},t.push=function(e){var t=new Uint8Array(e.data,e.byteOffset,e.byteLength);this.transmuxer.push(t)},t.reset=function(){this.transmuxer.reset()},t.setTimestampOffset=function(e){var t=e.timestampOffset||0;this.transmuxer.setBaseMediaDecodeTime(Math.round(de(t)))},t.setAudioAppendStart=function(e){this.transmuxer.setAudioAppendStart(Math.ceil(de(e.appendStart)))},t.setRemux=function(e){this.transmuxer.setRemux(e.remux)},t.flush=function(e){this.transmuxer.flush(),self.postMessage({action:"done",type:"transmuxed"})},t.endTimeline=function(){this.transmuxer.endTimeline(),self.postMessage({action:"endedtimeline",type:"transmuxed"})},t.alignGopsWith=function(e){this.transmuxer.alignGopsWith(e.gopsToAlignWith.slice())},e}();self.onmessage=function(e){"init"===e.data.action&&e.data.options?this.messageHandlers=new di(self,e.data.options):(this.messageHandlers||(this.messageHandlers=new di(self)),e.data&&e.data.action&&"init"!==e.data.action&&this.messageHandlers[e.data.action]&&this.messageHandlers[e.data.action](e.data))}})))),ls=function(e){var t=e.transmuxer,i=e.bytes,n=e.audioAppendStart,r=e.gopsToAlignWith,a=e.remux,s=e.onData,o=e.onTrackInfo,u=e.onAudioTimingInfo,l=e.onVideoTimingInfo,h=e.onVideoSegmentTimingInfo,d=e.onAudioSegmentTimingInfo,c=e.onId3,f=e.onCaptions,p=e.onDone,m=e.onEndedTimeline,_=e.onTransmuxerLog,g=e.isEndOfTimeline,v={buffer:[]},y=g;if(t.onmessage=function(i){t.currentTransmux===e&&("data"===i.data.action&&function(e,t,i){var n=e.data.segment,r=n.type,a=n.initSegment,s=n.captions,o=n.captionStreams,u=n.metadata,l=n.videoFrameDtsTime,h=n.videoFramePtsTime;t.buffer.push({captions:s,captionStreams:o,metadata:u});var d=e.data.segment.boxes||{data:e.data.segment.data},c={type:r,data:new Uint8Array(d.data,d.data.byteOffset,d.data.byteLength),initSegment:new Uint8Array(a.data,a.byteOffset,a.byteLength)};void 0!==l&&(c.videoFrameDtsTime=l),void 0!==h&&(c.videoFramePtsTime=h),i(c)}(i,v,s),"trackinfo"===i.data.action&&o(i.data.trackInfo),"gopInfo"===i.data.action&&function(e,t){t.gopInfo=e.data.gopInfo}(i,v),"audioTimingInfo"===i.data.action&&u(i.data.audioTimingInfo),"videoTimingInfo"===i.data.action&&l(i.data.videoTimingInfo),"videoSegmentTimingInfo"===i.data.action&&h(i.data.videoSegmentTimingInfo),"audioSegmentTimingInfo"===i.data.action&&d(i.data.audioSegmentTimingInfo),"id3Frame"===i.data.action&&c([i.data.id3Frame],i.data.id3Frame.dispatchType),"caption"===i.data.action&&f(i.data.caption),"endedtimeline"===i.data.action&&(y=!1,m()),"log"===i.data.action&&_(i.data.log),"transmuxed"===i.data.type&&(y||(t.onmessage=null,function(e){var t=e.transmuxedData,i=e.callback;t.buffer=[],i(t)}({transmuxedData:v,callback:p}),hs(t))))},n&&t.postMessage({action:"setAudioAppendStart",appendStart:n}),Array.isArray(r)&&t.postMessage({action:"alignGopsWith",gopsToAlignWith:r}),void 0!==a&&t.postMessage({action:"setRemux",remux:a}),i.byteLength){var b=i instanceof ArrayBuffer?i:i.buffer,S=i instanceof ArrayBuffer?0:i.byteOffset;t.postMessage({action:"push",data:b,byteOffset:S,byteLength:i.byteLength},[b])}g&&t.postMessage({action:"endTimeline"}),t.postMessage({action:"flush"})},hs=function(e){e.currentTransmux=null,e.transmuxQueue.length&&(e.currentTransmux=e.transmuxQueue.shift(),"function"==typeof e.currentTransmux?e.currentTransmux():ls(e.currentTransmux))},ds=function(e,t){e.postMessage({action:t}),hs(e)},cs=function(e,t){if(!t.currentTransmux)return t.currentTransmux=e,void ds(t,e);t.transmuxQueue.push(ds.bind(null,t,e))},fs=function(e){if(!e.transmuxer.currentTransmux)return e.transmuxer.currentTransmux=e,void ls(e);e.transmuxer.transmuxQueue.push(e)},ps=function(e){cs("reset",e)},ms=function(e){var t=new us;t.currentTransmux=null,t.transmuxQueue=[];var i=t.terminate;return t.terminate=function(){return t.currentTransmux=null,t.transmuxQueue.length=0,i.call(t)},t.postMessage({action:"init",options:e}),t},_s=function(e){var t=e.transmuxer,i=e.endAction||e.action,n=e.callback,r=P.default({},e,{endAction:null,transmuxer:null,callback:null});if(t.addEventListener("message",(function r(a){a.data.action===i&&(t.removeEventListener("message",r),a.data.data&&(a.data.data=new Uint8Array(a.data.data,e.byteOffset||0,e.byteLength||a.data.data.byteLength),e.data&&(e.data=a.data.data)),n(a.data))})),e.data){var a=e.data instanceof ArrayBuffer;r.byteOffset=a?0:e.data.byteOffset,r.byteLength=e.data.byteLength;var s=[a?e.data:e.data.buffer];t.postMessage(r,s)}else t.postMessage(r)},gs=2,vs=-101,ys=-102,bs=function(e){e.forEach((function(e){e.abort()}))},Ss=function(e,t){return t.timedout?{status:t.status,message:"HLS request timed-out at URL: "+t.uri,code:vs,xhr:t}:t.aborted?{status:t.status,message:"HLS request aborted at URL: "+t.uri,code:ys,xhr:t}:e?{status:t.status,message:"HLS request errored at URL: "+t.uri,code:gs,xhr:t}:"arraybuffer"===t.responseType&&0===t.response.byteLength?{status:t.status,message:"Empty HLS response at URL: "+t.uri,code:gs,xhr:t}:null},Ts=function(e,t,i){return function(n,r){var a=r.response,s=Ss(n,r);if(s)return i(s,e);if(16!==a.byteLength)return i({status:r.status,message:"Invalid HLS key at URL: "+r.uri,code:gs,xhr:r},e);for(var o=new DataView(a),u=new Uint32Array([o.getUint32(0),o.getUint32(4),o.getUint32(8),o.getUint32(12)]),l=0;l1)return xs("multiple "+e+" codecs found as attributes: "+t[e].join(", ")+". Setting playlist codecs to null so that we wait for mux.js to probe segments for real codecs."),void(t[e]=null);t[e]=t[e][0]})),t},Os=function(e){var t=0;return e.audio&&t++,e.video&&t++,t},Us=function(e,t){var i=t.attributes||{},n=Ds(function(e){var t=e.attributes||{};if(t.CODECS)return _.parseCodecs(t.CODECS)}(t)||[]);if(Rs(e,t)&&!n.audio&&!function(e,t){if(!Rs(e,t))return!0;var i=t.attributes||{},n=e.mediaGroups.AUDIO[i.AUDIO];for(var r in n)if(!n[r].uri&&!n[r].playlists)return!0;return!1}(e,t)){var r=Ds(_.codecsFromDefault(e,i.AUDIO)||[]);r.audio&&(n.audio=r.audio)}return n},Ms=$r("PlaylistSelector"),Fs=function(e){if(e&&e.playlist){var t=e.playlist;return JSON.stringify({id:t.id,bandwidth:e.bandwidth,width:e.width,height:e.height,codecs:t.attributes&&t.attributes.CODECS||""})}},Bs=function(e,t){if(!e)return"";var i=C.default.getComputedStyle(e);return i?i[t]:""},Ns=function(e,t){var i=e.slice();e.sort((function(e,n){var r=t(e,n);return 0===r?i.indexOf(e)-i.indexOf(n):r}))},js=function(e,t){var i,n;return e.attributes.BANDWIDTH&&(i=e.attributes.BANDWIDTH),i=i||C.default.Number.MAX_VALUE,t.attributes.BANDWIDTH&&(n=t.attributes.BANDWIDTH),i-(n=n||C.default.Number.MAX_VALUE)},Vs=function(e,t,i,n,r,a){if(e){var s={bandwidth:t,width:i,height:n,limitRenditionByPlayerDimensions:r},o=e.playlists;Sa.isAudioOnly(e)&&(o=a.getAudioTrackPlaylists_(),s.audioOnly=!0);var u=o.map((function(e){var t=e.attributes&&e.attributes.RESOLUTION&&e.attributes.RESOLUTION.width,i=e.attributes&&e.attributes.RESOLUTION&&e.attributes.RESOLUTION.height;return{bandwidth:e.attributes&&e.attributes.BANDWIDTH||C.default.Number.MAX_VALUE,width:t,height:i,playlist:e}}));Ns(u,(function(e,t){return e.bandwidth-t.bandwidth}));var l=(u=u.filter((function(e){return!Sa.isIncompatible(e.playlist)}))).filter((function(e){return Sa.isEnabled(e.playlist)}));l.length||(l=u.filter((function(e){return!Sa.isDisabled(e.playlist)})));var h=l.filter((function(e){return e.bandwidth*ns.BANDWIDTH_VARIANCEi||e.height>n}))).filter((function(e){return e.width===g[0].width&&e.height===g[0].height})),d=v[v.length-1],y=v.filter((function(e){return e.bandwidth===d.bandwidth}))[0]),a.experimentalLeastPixelDiffSelector){var T=m.map((function(e){return e.pixelDiff=Math.abs(e.width-i)+Math.abs(e.height-n),e}));Ns(T,(function(e,t){return e.pixelDiff===t.pixelDiff?t.bandwidth-e.bandwidth:e.pixelDiff-t.pixelDiff})),b=T[0]}var E=b||y||S||c||l[0]||u[0];if(E&&E.playlist){var w="sortedPlaylistReps";return b?w="leastPixelDiffRep":y?w="resolutionPlusOneRep":S?w="resolutionBestRep":c?w="bandwidthBestRep":l[0]&&(w="enabledPlaylistReps"),Ms("choosing "+Fs(E)+" using "+w+" with options",s),E.playlist}return Ms("could not choose a playlist with options",s),null}},Hs=function(){var e=this.useDevicePixelRatio&&C.default.devicePixelRatio||1;return Vs(this.playlists.master,this.systemBandwidth,parseInt(Bs(this.tech_.el(),"width"),10)*e,parseInt(Bs(this.tech_.el(),"height"),10)*e,this.limitRenditionByPlayerDimensions,this.masterPlaylistController_)},zs=function(e){var t=e.inbandTextTracks,i=e.metadataArray,n=e.timestampOffset,r=e.videoDuration;if(i){var a=C.default.WebKitDataCue||C.default.VTTCue,s=t.metadataTrack_;if(s&&(i.forEach((function(e){var t=e.cueTime+n;!("number"!=typeof t||C.default.isNaN(t)||t<0)&&t<1/0&&e.frames.forEach((function(e){var i=new a(t,t,e.value||e.url||e.data||"");i.frame=e,i.value=e,function(e){Object.defineProperties(e.frame,{id:{get:function(){return Yr.log.warn("cue.frame.id is deprecated. Use cue.value.key instead."),e.value.key}},value:{get:function(){return Yr.log.warn("cue.frame.value is deprecated. Use cue.value.data instead."),e.value.data}},privateData:{get:function(){return Yr.log.warn("cue.frame.privateData is deprecated. Use cue.value.data instead."),e.value.data}}})}(i),s.addCue(i)}))})),s.cues&&s.cues.length)){for(var o=s.cues,u=[],l=0;l=e&&r.endTime<=t&&i.removeCue(r)},Ws=function(e){return"number"==typeof e&&isFinite(e)},Ys=function(e){var t=e.startOfSegment,i=e.duration,n=e.segment,r=e.part,a=e.playlist,s=a.mediaSequence,o=a.id,u=a.segments,l=void 0===u?[]:u,h=e.mediaIndex,d=e.partIndex,c=e.timeline,f=l.length-1,p="mediaIndex/partIndex increment";e.getMediaInfoForTime?p="getMediaInfoForTime ("+e.getMediaInfoForTime+")":e.isSyncRequest&&(p="getSyncSegmentCandidate (isSyncRequest)");var m="number"==typeof d,_=e.segment.uri?"segment":"pre-segment",g=m?oa({preloadSegment:n})-1:0;return _+" ["+(s+h)+"/"+(s+f)+"]"+(m?" part ["+d+"/"+g+"]":"")+" segment start/end ["+n.start+" => "+n.end+"]"+(m?" part start/end ["+r.start+" => "+r.end+"]":"")+" startOfSegment ["+t+"] duration ["+i+"] timeline ["+c+"] selected by ["+p+"] playlist ["+o+"]"},qs=function(e){return e+"TimingInfo"},Ks=function(e){var t=e.timelineChangeController,i=e.currentTimeline,n=e.segmentTimeline,r=e.loaderType,a=e.audioDisabled;if(i===n)return!1;if("audio"===r){var s=t.lastTimelineChange({type:"main"});return!s||s.to!==n}if("main"===r&&a){var o=t.pendingTimelineChange({type:"audio"});return!o||o.to!==n}return!1},Xs=function(e){var t=e.segmentDuration,i=e.maxDuration;return!!t&&Math.round(t)>i+1/30},Qs=function(e,t){if("hls"!==t)return null;var i,n,r,a,s=(i=e.audioTimingInfo,n=e.videoTimingInfo,r=i&&"number"==typeof i.start&&"number"==typeof i.end?i.end-i.start:0,a=n&&"number"==typeof n.start&&"number"==typeof n.end?n.end-n.start:0,Math.max(r,a));if(!s)return null;var o=e.playlist.targetDuration,u=Xs({segmentDuration:s,maxDuration:2*o}),l=Xs({segmentDuration:s,maxDuration:o}),h="Segment with index "+e.mediaIndex+" from playlist "+e.playlist.id+" has a duration of "+s+" when the reported duration is "+e.duration+" and the target duration is "+o+". For HLS content, a duration in excess of the target duration may result in playback issues. See the HLS specification section on EXT-X-TARGETDURATION for more details: https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.3.1";return u||l?{severity:u?"warn":"info",message:h}:null},$s=function(e){function t(t,i){var n;if(n=e.call(this)||this,!t)throw new TypeError("Initialization settings are required");if("function"!=typeof t.currentTime)throw new TypeError("No currentTime getter specified");if(!t.mediaSource)throw new TypeError("No MediaSource specified");return n.bandwidth=t.bandwidth,n.throughput={rate:0,count:0},n.roundTrip=NaN,n.resetStats_(),n.mediaIndex=null,n.partIndex=null,n.hasPlayed_=t.hasPlayed,n.currentTime_=t.currentTime,n.seekable_=t.seekable,n.seeking_=t.seeking,n.duration_=t.duration,n.mediaSource_=t.mediaSource,n.vhs_=t.vhs,n.loaderType_=t.loaderType,n.currentMediaInfo_=void 0,n.startingMediaInfo_=void 0,n.segmentMetadataTrack_=t.segmentMetadataTrack,n.goalBufferLength_=t.goalBufferLength,n.sourceType_=t.sourceType,n.sourceUpdater_=t.sourceUpdater,n.inbandTextTracks_=t.inbandTextTracks,n.state_="INIT",n.timelineChangeController_=t.timelineChangeController,n.shouldSaveSegmentTimingInfo_=!0,n.parse708captions_=t.parse708captions,n.experimentalExactManifestTimings=t.experimentalExactManifestTimings,n.checkBufferTimeout_=null,n.error_=void 0,n.currentTimeline_=-1,n.pendingSegment_=null,n.xhrOptions_=null,n.pendingSegments_=[],n.audioDisabled_=!1,n.isPendingTimestampOffset_=!1,n.gopBuffer_=[],n.timeMapping_=0,n.safeAppend_=Yr.browser.IE_VERSION>=11,n.appendInitSegment_={audio:!0,video:!0},n.playlistOfLastInitSegment_={audio:null,video:null},n.callQueue_=[],n.loadQueue_=[],n.metadataQueue_={id3:[],caption:[]},n.waitingOnRemove_=!1,n.quotaExceededErrorRetryTimeout_=null,n.activeInitSegmentId_=null,n.initSegments_={},n.cacheEncryptionKeys_=t.cacheEncryptionKeys,n.keyCache_={},n.decrypter_=t.decrypter,n.syncController_=t.syncController,n.syncPoint_={segmentIndex:0,time:0},n.transmuxer_=n.createTransmuxer_(),n.triggerSyncInfoUpdate_=function(){return n.trigger("syncinfoupdate")},n.syncController_.on("syncinfoupdate",n.triggerSyncInfoUpdate_),n.mediaSource_.addEventListener("sourceopen",(function(){n.isEndOfStream_()||(n.ended_=!1)})),n.fetchAtBuffer_=!1,n.logger_=$r("SegmentLoader["+n.loaderType_+"]"),Object.defineProperty(I.default(n),"state",{get:function(){return this.state_},set:function(e){e!==this.state_&&(this.logger_(this.state_+" -> "+e),this.state_=e,this.trigger("statechange"))}}),n.sourceUpdater_.on("ready",(function(){n.hasEnoughInfoToAppend_()&&n.processCallQueue_()})),"main"===n.loaderType_&&n.timelineChangeController_.on("pendingtimelinechange",(function(){n.hasEnoughInfoToAppend_()&&n.processCallQueue_()})),"audio"===n.loaderType_&&n.timelineChangeController_.on("timelinechange",(function(){n.hasEnoughInfoToLoad_()&&n.processLoadQueue_(),n.hasEnoughInfoToAppend_()&&n.processCallQueue_()})),n}L.default(t,e);var i=t.prototype;return i.createTransmuxer_=function(){return ms({remux:!1,alignGopsAtEnd:this.safeAppend_,keepOriginalTimestamps:!0,parse708captions:this.parse708captions_})},i.resetStats_=function(){this.mediaBytesTransferred=0,this.mediaRequests=0,this.mediaRequestsAborted=0,this.mediaRequestsTimedout=0,this.mediaRequestsErrored=0,this.mediaTransferDuration=0,this.mediaSecondsLoaded=0,this.mediaAppends=0},i.dispose=function(){this.trigger("dispose"),this.state="DISPOSED",this.pause(),this.abort_(),this.transmuxer_&&this.transmuxer_.terminate(),this.resetStats_(),this.checkBufferTimeout_&&C.default.clearTimeout(this.checkBufferTimeout_),this.syncController_&&this.triggerSyncInfoUpdate_&&this.syncController_.off("syncinfoupdate",this.triggerSyncInfoUpdate_),this.off()},i.setAudio=function(e){this.audioDisabled_=!e,e?this.appendInitSegment_.audio=!0:this.sourceUpdater_.removeAudio(0,this.duration_())},i.abort=function(){"WAITING"===this.state?(this.abort_(),this.state="READY",this.paused()||this.monitorBuffer_()):this.pendingSegment_&&(this.pendingSegment_=null)},i.abort_=function(){this.pendingSegment_&&this.pendingSegment_.abortRequests&&this.pendingSegment_.abortRequests(),this.pendingSegment_=null,this.callQueue_=[],this.loadQueue_=[],this.metadataQueue_.id3=[],this.metadataQueue_.caption=[],this.timelineChangeController_.clearPendingTimelineChange(this.loaderType_),this.waitingOnRemove_=!1,C.default.clearTimeout(this.quotaExceededErrorRetryTimeout_),this.quotaExceededErrorRetryTimeout_=null},i.checkForAbort_=function(e){return"APPENDING"!==this.state||this.pendingSegment_?!this.pendingSegment_||this.pendingSegment_.requestId!==e:(this.state="READY",!0)},i.error=function(e){return void 0!==e&&(this.logger_("error occurred:",e),this.error_=e),this.pendingSegment_=null,this.error_},i.endOfStream=function(){this.ended_=!0,this.transmuxer_&&ps(this.transmuxer_),this.gopBuffer_.length=0,this.pause(),this.trigger("ended")},i.buffered_=function(){var e=this.getMediaInfo_();if(!this.sourceUpdater_||!e)return Yr.createTimeRanges();if("main"===this.loaderType_){var t=e.hasAudio,i=e.hasVideo,n=e.isMuxed;if(i&&t&&!this.audioDisabled_&&!n)return this.sourceUpdater_.buffered();if(i)return this.sourceUpdater_.videoBuffered()}return this.sourceUpdater_.audioBuffered()},i.initSegmentForMap=function(e,t){if(void 0===t&&(t=!1),!e)return null;var i=Wa(e),n=this.initSegments_[i];return t&&!n&&e.bytes&&(this.initSegments_[i]=n={resolvedUri:e.resolvedUri,byterange:e.byterange,bytes:e.bytes,tracks:e.tracks,timescales:e.timescales}),n||e},i.segmentKey=function(e,t){if(void 0===t&&(t=!1),!e)return null;var i=Ya(e),n=this.keyCache_[i];this.cacheEncryptionKeys_&&t&&!n&&e.bytes&&(this.keyCache_[i]=n={resolvedUri:e.resolvedUri,bytes:e.bytes});var r={resolvedUri:(n||e).resolvedUri};return n&&(r.bytes=n.bytes),r},i.couldBeginLoading_=function(){return this.playlist_&&!this.paused()},i.load=function(){if(this.monitorBuffer_(),this.playlist_)return"INIT"===this.state&&this.couldBeginLoading_()?this.init_():void(!this.couldBeginLoading_()||"READY"!==this.state&&"INIT"!==this.state||(this.state="READY"))},i.init_=function(){return this.state="READY",this.resetEverything(),this.monitorBuffer_()},i.playlist=function(e,t){if(void 0===t&&(t={}),e){var i=this.playlist_,n=this.pendingSegment_;this.playlist_=e,this.xhrOptions_=t,"INIT"===this.state&&(e.syncInfo={mediaSequence:e.mediaSequence,time:0},"main"===this.loaderType_&&this.syncController_.setDateTimeMappingForStart(e));var r=null;if(i&&(i.id?r=i.id:i.uri&&(r=i.uri)),this.logger_("playlist update ["+r+" => "+(e.id||e.uri)+"]"),this.trigger("syncinfoupdate"),"INIT"===this.state&&this.couldBeginLoading_())return this.init_();if(!i||i.uri!==e.uri)return null!==this.mediaIndex&&this.resyncLoader(),this.currentMediaInfo_=void 0,void this.trigger("playlistupdate");var a=e.mediaSequence-i.mediaSequence;if(this.logger_("live window shift ["+a+"]"),null!==this.mediaIndex)if(this.mediaIndex-=a,this.mediaIndex<0)this.mediaIndex=null,this.partIndex=null;else{var s=this.playlist_.segments[this.mediaIndex];if(this.partIndex&&(!s.parts||!s.parts.length||!s.parts[this.partIndex])){var o=this.mediaIndex;this.logger_("currently processing part (index "+this.partIndex+") no longer exists."),this.resetLoader(),this.mediaIndex=o}}n&&(n.mediaIndex-=a,n.mediaIndex<0?(n.mediaIndex=null,n.partIndex=null):(n.mediaIndex>=0&&(n.segment=e.segments[n.mediaIndex]),n.partIndex>=0&&n.segment.parts&&(n.part=n.segment.parts[n.partIndex]))),this.syncController_.saveExpiredSegmentInfo(i,e)}},i.pause=function(){this.checkBufferTimeout_&&(C.default.clearTimeout(this.checkBufferTimeout_),this.checkBufferTimeout_=null)},i.paused=function(){return null===this.checkBufferTimeout_},i.resetEverything=function(e){this.ended_=!1,this.appendInitSegment_={audio:!0,video:!0},this.resetLoader(),this.remove(0,1/0,e),this.transmuxer_&&this.transmuxer_.postMessage({action:"clearAllMp4Captions"})},i.resetLoader=function(){this.fetchAtBuffer_=!1,this.resyncLoader()},i.resyncLoader=function(){this.transmuxer_&&ps(this.transmuxer_),this.mediaIndex=null,this.partIndex=null,this.syncPoint_=null,this.isPendingTimestampOffset_=!1,this.callQueue_=[],this.loadQueue_=[],this.metadataQueue_.id3=[],this.metadataQueue_.caption=[],this.abort(),this.transmuxer_&&this.transmuxer_.postMessage({action:"clearParsedMp4Captions"})},i.remove=function(e,t,i,n){if(void 0===i&&(i=function(){}),void 0===n&&(n=!1),t===1/0&&(t=this.duration_()),t<=e)this.logger_("skipping remove because end ${end} is <= start ${start}");else if(this.sourceUpdater_&&this.getMediaInfo_()){var r=1,a=function(){0===--r&&i()};for(var s in!n&&this.audioDisabled_||(r++,this.sourceUpdater_.removeAudio(e,t,a)),(n||"main"===this.loaderType_)&&(this.gopBuffer_=function(e,t,i,n){for(var r=Math.ceil((t-n)*E.ONE_SECOND_IN_TS),a=Math.ceil((i-n)*E.ONE_SECOND_IN_TS),s=e.slice(),o=e.length;o--&&!(e[o].pts<=a););if(-1===o)return s;for(var u=o+1;u--&&!(e[u].pts<=r););return u=Math.max(u,0),s.splice(u,o-u+1),s}(this.gopBuffer_,e,t,this.timeMapping_),r++,this.sourceUpdater_.removeVideo(e,t,a)),this.inbandTextTracks_)Gs(e,t,this.inbandTextTracks_[s]);Gs(e,t,this.segmentMetadataTrack_),a()}else this.logger_("skipping remove because no source updater or starting media info")},i.monitorBuffer_=function(){this.checkBufferTimeout_&&C.default.clearTimeout(this.checkBufferTimeout_),this.checkBufferTimeout_=C.default.setTimeout(this.monitorBufferTick_.bind(this),1)},i.monitorBufferTick_=function(){"READY"===this.state&&this.fillBuffer_(),this.checkBufferTimeout_&&C.default.clearTimeout(this.checkBufferTimeout_),this.checkBufferTimeout_=C.default.setTimeout(this.monitorBufferTick_.bind(this),500)},i.fillBuffer_=function(){if(!this.sourceUpdater_.updating()){var e=this.chooseNextRequest_();e&&("number"==typeof e.timestampOffset&&(this.isPendingTimestampOffset_=!1,this.timelineChangeController_.pendingTimelineChange({type:this.loaderType_,from:this.currentTimeline_,to:e.timeline})),this.loadSegment_(e))}},i.isEndOfStream_=function(e,t,i){if(void 0===e&&(e=this.mediaIndex),void 0===t&&(t=this.playlist_),void 0===i&&(i=this.partIndex),!t||!this.mediaSource_)return!1;var n="number"==typeof e&&t.segments[e],r=e+1===t.segments.length,a=!n||!n.parts||i+1===n.parts.length;return t.endList&&"open"===this.mediaSource_.readyState&&r&&a},i.chooseNextRequest_=function(){var e=na(this.buffered_())||0,t=Math.max(0,e-this.currentTime_()),i=!this.hasPlayed_()&&t>=1,n=t>=this.goalBufferLength_(),r=this.playlist_.segments;if(!r.length||i||n)return null;this.syncPoint_=this.syncPoint_||this.syncController_.getSyncPoint(this.playlist_,this.duration_(),this.currentTimeline_,this.currentTime_());var a={partIndex:null,mediaIndex:null,startOfSegment:null,playlist:this.playlist_,isSyncRequest:Boolean(!this.syncPoint_)};if(a.isSyncRequest)a.mediaIndex=function(e,t,i){t=t||[];for(var n=[],r=0,a=0;ai))return a}return 0===n.length?0:n[n.length-1]}(this.currentTimeline_,r,e);else if(null!==this.mediaIndex){var s=r[this.mediaIndex],o="number"==typeof this.partIndex?this.partIndex:-1;a.startOfSegment=s.end?s.end:e,s.parts&&s.parts[o+1]?(a.mediaIndex=this.mediaIndex,a.partIndex=o+1):a.mediaIndex=this.mediaIndex+1}else{var u=Sa.getMediaInfoForTime({experimentalExactManifestTimings:this.experimentalExactManifestTimings,playlist:this.playlist_,currentTime:this.fetchAtBuffer_?e:this.currentTime_(),startingPartIndex:this.syncPoint_.partIndex,startingSegmentIndex:this.syncPoint_.segmentIndex,startTime:this.syncPoint_.time}),l=u.segmentIndex,h=u.startTime,d=u.partIndex;a.getMediaInfoForTime=this.fetchAtBuffer_?"bufferedEnd":"currentTime",a.mediaIndex=l,a.startOfSegment=h,a.partIndex=d}var c=r[a.mediaIndex],f=c&&"number"==typeof a.partIndex&&c.parts&&c.parts[a.partIndex];if(!c||"number"==typeof a.partIndex&&!f)return null;"number"!=typeof a.partIndex&&c.parts&&(a.partIndex=0);var p=this.mediaSource_&&"ended"===this.mediaSource_.readyState;return a.mediaIndex>=r.length-1&&p&&!this.seeking_()?null:this.generateSegmentInfo_(a)},i.generateSegmentInfo_=function(e){var t=e.playlist,i=e.mediaIndex,n=e.startOfSegment,r=e.isSyncRequest,a=e.partIndex,s=e.forceTimestampOffset,o=e.getMediaInfoForTime,u=t.segments[i],l="number"==typeof a&&u.parts[a],h={requestId:"segment-loader-"+Math.random(),uri:l&&l.resolvedUri||u.resolvedUri,mediaIndex:i,partIndex:l?a:null,isSyncRequest:r,startOfSegment:n,playlist:t,bytes:null,encryptedBytes:null,timestampOffset:null,timeline:u.timeline,duration:l&&l.duration||u.duration,segment:u,part:l,byteLength:0,transmuxer:this.transmuxer_,getMediaInfoForTime:o},d=void 0!==s?s:this.isPendingTimestampOffset_;h.timestampOffset=this.timestampOffsetForSegment_({segmentTimeline:u.timeline,currentTimeline:this.currentTimeline_,startOfSegment:n,buffered:this.buffered_(),overrideCheck:d});var c=na(this.sourceUpdater_.audioBuffered());return"number"==typeof c&&(h.audioAppendStart=c-this.sourceUpdater_.audioTimestampOffset()),this.sourceUpdater_.videoBuffered().length&&(h.gopsToAlignWith=function(e,t,i){if(null==t||!e.length)return[];var n,r=Math.ceil((t-i+3)*E.ONE_SECOND_IN_TS);for(n=0;nr);n++);return e.slice(n)}(this.gopBuffer_,this.currentTime_()-this.sourceUpdater_.videoTimestampOffset(),this.timeMapping_)),h},i.timestampOffsetForSegment_=function(e){return i=(t=e).segmentTimeline,n=t.currentTimeline,r=t.startOfSegment,a=t.buffered,t.overrideCheck||i!==n?i "+s+" for "+e),function(e,t,i){if(!e[i]){t.trigger({type:"usage",name:"vhs-608"}),t.trigger({type:"usage",name:"hls-608"});var n=i;/^cc708_/.test(i)&&(n="SERVICE"+i.split("_")[1]);var r=t.textTracks().getTrackById(n);if(r)e[i]=r;else{var a=i,s=i,o=!1,u=(t.options_.vhs&&t.options_.vhs.captionServices||{})[n];u&&(a=u.label,s=u.language,o=u.default),e[i]=t.addRemoteTextTrack({kind:"captions",id:n,default:o,label:a,language:s},!1).track}}}(u,i.vhs_.tech_,e),Gs(a,s,u[e]),function(e){var t=e.inbandTextTracks,i=e.captionArray,n=e.timestampOffset;if(i){var r=C.default.WebKitDataCue||C.default.VTTCue;i.forEach((function(e){var i=e.stream;t[i].addCue(new r(e.startTime+n,e.endTime+n,e.text))}))}}({captionArray:o,inbandTextTracks:u,timestampOffset:n})})),this.transmuxer_&&this.transmuxer_.postMessage({action:"clearParsedMp4Captions"})}else this.metadataQueue_.caption.push(this.handleCaptions_.bind(this,e,t));else this.logger_("SegmentLoader received no captions from a caption event")},i.handleId3_=function(e,t,i){if(this.earlyAbortWhenNeeded_(e.stats),!this.checkForAbort_(e.requestId))if(this.pendingSegment_.hasAppendedData_){var n=null===this.sourceUpdater_.videoTimestampOffset()?this.sourceUpdater_.audioTimestampOffset():this.sourceUpdater_.videoTimestampOffset();!function(e,t,i){e.metadataTrack_||(e.metadataTrack_=i.addRemoteTextTrack({kind:"metadata",label:"Timed Metadata"},!1).track,e.metadataTrack_.inBandMetadataTrackDispatchType=t)}(this.inbandTextTracks_,i,this.vhs_.tech_),zs({inbandTextTracks:this.inbandTextTracks_,metadataArray:t,timestampOffset:n,videoDuration:this.duration_()})}else this.metadataQueue_.id3.push(this.handleId3_.bind(this,e,t,i))},i.processMetadataQueue_=function(){this.metadataQueue_.id3.forEach((function(e){return e()})),this.metadataQueue_.caption.forEach((function(e){return e()})),this.metadataQueue_.id3=[],this.metadataQueue_.caption=[]},i.processCallQueue_=function(){var e=this.callQueue_;this.callQueue_=[],e.forEach((function(e){return e()}))},i.processLoadQueue_=function(){var e=this.loadQueue_;this.loadQueue_=[],e.forEach((function(e){return e()}))},i.hasEnoughInfoToLoad_=function(){if("audio"!==this.loaderType_)return!0;var e=this.pendingSegment_;return!!e&&(!this.getCurrentMediaInfo_()||!Ks({timelineChangeController:this.timelineChangeController_,currentTimeline:this.currentTimeline_,segmentTimeline:e.timeline,loaderType:this.loaderType_,audioDisabled:this.audioDisabled_}))},i.getCurrentMediaInfo_=function(e){return void 0===e&&(e=this.pendingSegment_),e&&e.trackInfo||this.currentMediaInfo_},i.getMediaInfo_=function(e){return void 0===e&&(e=this.pendingSegment_),this.getCurrentMediaInfo_(e)||this.startingMediaInfo_},i.hasEnoughInfoToAppend_=function(){if(!this.sourceUpdater_.ready())return!1;if(this.waitingOnRemove_||this.quotaExceededErrorRetryTimeout_)return!1;var e=this.pendingSegment_,t=this.getCurrentMediaInfo_();if(!e||!t)return!1;var i=t.hasAudio,n=t.hasVideo,r=t.isMuxed;return!(n&&!e.videoTimingInfo)&&(!(i&&!this.audioDisabled_&&!r&&!e.audioTimingInfo)&&!Ks({timelineChangeController:this.timelineChangeController_,currentTimeline:this.currentTimeline_,segmentTimeline:e.timeline,loaderType:this.loaderType_,audioDisabled:this.audioDisabled_}))},i.handleData_=function(e,t){if(this.earlyAbortWhenNeeded_(e.stats),!this.checkForAbort_(e.requestId))if(!this.callQueue_.length&&this.hasEnoughInfoToAppend_()){var i=this.pendingSegment_;if(this.setTimeMapping_(i.timeline),this.updateMediaSecondsLoaded_(i.segment),"closed"!==this.mediaSource_.readyState){if(e.map&&(e.map=this.initSegmentForMap(e.map,!0),i.segment.map=e.map),e.key&&this.segmentKey(e.key,!0),i.isFmp4=e.isFmp4,i.timingInfo=i.timingInfo||{},i.isFmp4)this.trigger("fmp4"),i.timingInfo.start=i[qs(t.type)].start;else{var n,r=this.getCurrentMediaInfo_(),a="main"===this.loaderType_&&r&&r.hasVideo;a&&(n=i.videoTimingInfo.start),i.timingInfo.start=this.trueSegmentStart_({currentStart:i.timingInfo.start,playlist:i.playlist,mediaIndex:i.mediaIndex,currentVideoTimestampOffset:this.sourceUpdater_.videoTimestampOffset(),useVideoTimingInfo:a,firstVideoFrameTimeForData:n,videoTimingInfo:i.videoTimingInfo,audioTimingInfo:i.audioTimingInfo})}if(this.updateAppendInitSegmentStatus(i,t.type),this.updateSourceBufferTimestampOffset_(i),i.isSyncRequest){this.updateTimingInfoEnd_(i),this.syncController_.saveSegmentTimingInfo({segmentInfo:i,shouldSaveTimelineMapping:"main"===this.loaderType_});var s=this.chooseNextRequest_();if(s.mediaIndex!==i.mediaIndex||s.partIndex!==i.partIndex)return void this.logger_("sync segment was incorrect, not appending");this.logger_("sync segment was correct, appending")}i.hasAppendedData_=!0,this.processMetadataQueue_(),this.appendData_(i,t)}}else this.callQueue_.push(this.handleData_.bind(this,e,t))},i.updateAppendInitSegmentStatus=function(e,t){"main"!==this.loaderType_||"number"!=typeof e.timestampOffset||e.changedTimestampOffset||(this.appendInitSegment_={audio:!0,video:!0}),this.playlistOfLastInitSegment_[t]!==e.playlist&&(this.appendInitSegment_[t]=!0)},i.getInitSegmentAndUpdateState_=function(e){var t=e.type,i=e.initSegment,n=e.map,r=e.playlist;if(n){var a=Wa(n);if(this.activeInitSegmentId_===a)return null;i=this.initSegmentForMap(n,!0).bytes,this.activeInitSegmentId_=a}return i&&this.appendInitSegment_[t]?(this.playlistOfLastInitSegment_[t]=r,this.appendInitSegment_[t]=!1,this.activeInitSegmentId_=null,i):null},i.handleQuotaExceededError_=function(e,t){var i=this,n=e.segmentInfo,r=e.type,a=e.bytes,s=this.sourceUpdater_.audioBuffered(),o=this.sourceUpdater_.videoBuffered();s.length>1&&this.logger_("On QUOTA_EXCEEDED_ERR, found gaps in the audio buffer: "+ia(s).join(", ")),o.length>1&&this.logger_("On QUOTA_EXCEEDED_ERR, found gaps in the video buffer: "+ia(o).join(", "));var u=s.length?s.start(0):0,l=s.length?s.end(s.length-1):0,h=o.length?o.start(0):0,d=o.length?o.end(o.length-1):0;if(l-u<=1&&d-h<=1)return this.logger_("On QUOTA_EXCEEDED_ERR, single segment too large to append to buffer, triggering an error. Appended byte length: "+a.byteLength+", audio buffer: "+ia(s).join(", ")+", video buffer: "+ia(o).join(", ")+", "),this.error({message:"Quota exceeded error with append of a single segment of content",excludeUntil:1/0}),void this.trigger("error");this.waitingOnRemove_=!0,this.callQueue_.push(this.appendToSourceBuffer_.bind(this,{segmentInfo:n,type:r,bytes:a}));var c=this.currentTime_()-1;this.logger_("On QUOTA_EXCEEDED_ERR, removing audio/video from 0 to "+c),this.remove(0,c,(function(){i.logger_("On QUOTA_EXCEEDED_ERR, retrying append in 1s"),i.waitingOnRemove_=!1,i.quotaExceededErrorRetryTimeout_=C.default.setTimeout((function(){i.logger_("On QUOTA_EXCEEDED_ERR, re-processing call queue"),i.quotaExceededErrorRetryTimeout_=null,i.processCallQueue_()}),1e3)}),!0)},i.handleAppendError_=function(e,t){var i=e.segmentInfo,n=e.type,r=e.bytes;t&&(22!==t.code?(this.logger_("Received non QUOTA_EXCEEDED_ERR on append",t),this.error(n+" append of "+r.length+"b failed for segment #"+i.mediaIndex+" in playlist "+i.playlist.id),this.trigger("appenderror")):this.handleQuotaExceededError_({segmentInfo:i,type:n,bytes:r}))},i.appendToSourceBuffer_=function(e){var t,i,n,r=e.segmentInfo,a=e.type,s=e.initSegment,o=e.data,u=e.bytes;if(!u){var l=[o],h=o.byteLength;s&&(l.unshift(s),h+=s.byteLength),n=0,(t={bytes:h,segments:l}).bytes&&(i=new Uint8Array(t.bytes),t.segments.forEach((function(e){i.set(e,n),n+=e.byteLength}))),u=i}this.sourceUpdater_.appendBuffer({segmentInfo:r,type:a,bytes:u},this.handleAppendError_.bind(this,{segmentInfo:r,type:a,bytes:u}))},i.handleSegmentTimingInfo_=function(e,t,i){if(this.pendingSegment_&&t===this.pendingSegment_.requestId){var n=this.pendingSegment_.segment,r=e+"TimingInfo";n[r]||(n[r]={}),n[r].transmuxerPrependedSeconds=i.prependedContentDuration||0,n[r].transmuxedPresentationStart=i.start.presentation,n[r].transmuxedDecodeStart=i.start.decode,n[r].transmuxedPresentationEnd=i.end.presentation,n[r].transmuxedDecodeEnd=i.end.decode,n[r].baseMediaDecodeTime=i.baseMediaDecodeTime}},i.appendData_=function(e,t){var i=t.type,n=t.data;if(n&&n.byteLength&&("audio"!==i||!this.audioDisabled_)){var r=this.getInitSegmentAndUpdateState_({type:i,initSegment:t.initSegment,playlist:e.playlist,map:e.isFmp4?e.segment.map:null});this.appendToSourceBuffer_({segmentInfo:e,type:i,initSegment:r,data:n})}},i.loadSegment_=function(e){var t=this;this.state="WAITING",this.pendingSegment_=e,this.trimBackBuffer_(e),"number"==typeof e.timestampOffset&&this.transmuxer_&&this.transmuxer_.postMessage({action:"clearAllMp4Captions"}),this.hasEnoughInfoToLoad_()?this.updateTransmuxerAndRequestSegment_(e):this.loadQueue_.push((function(){var i=P.default({},e,{forceTimestampOffset:!0});P.default(e,t.generateSegmentInfo_(i)),t.isPendingTimestampOffset_=!1,t.updateTransmuxerAndRequestSegment_(e)}))},i.updateTransmuxerAndRequestSegment_=function(e){var t=this;this.shouldUpdateTransmuxerTimestampOffset_(e.timestampOffset)&&(this.gopBuffer_.length=0,e.gopsToAlignWith=[],this.timeMapping_=0,this.transmuxer_.postMessage({action:"reset"}),this.transmuxer_.postMessage({action:"setTimestampOffset",timestampOffset:e.timestampOffset}));var i=this.createSimplifiedSegmentObj_(e),n=this.isEndOfStream_(e.mediaIndex,e.playlist,e.partIndex),r=null!==this.mediaIndex,a=e.timeline!==this.currentTimeline_&&e.timeline>0,s=n||r&&a;this.logger_("Requesting "+Ys(e)),i.map&&!i.map.bytes&&(this.logger_("going to request init segment."),this.appendInitSegment_={video:!0,audio:!0}),e.abortRequests=Ls({xhr:this.vhs_.xhr,xhrOptions:this.xhrOptions_,decryptionWorker:this.decrypter_,segment:i,abortFn:this.handleAbort_.bind(this,e),progressFn:this.handleProgress_.bind(this),trackInfoFn:this.handleTrackInfo_.bind(this),timingInfoFn:this.handleTimingInfo_.bind(this),videoSegmentTimingInfoFn:this.handleSegmentTimingInfo_.bind(this,"video",e.requestId),audioSegmentTimingInfoFn:this.handleSegmentTimingInfo_.bind(this,"audio",e.requestId),captionsFn:this.handleCaptions_.bind(this),isEndOfTimeline:s,endedTimelineFn:function(){t.logger_("received endedtimeline callback")},id3Fn:this.handleId3_.bind(this),dataFn:this.handleData_.bind(this),doneFn:this.segmentRequestFinished_.bind(this),onTransmuxerLog:function(i){var n=i.message,r=i.level,a=i.stream;t.logger_(Ys(e)+" logged from transmuxer stream "+a+" as a "+r+": "+n)}})},i.trimBackBuffer_=function(e){var t=function(e,t,i){var n=t-ns.BACK_BUFFER_LENGTH;e.length&&(n=Math.max(n,e.start(0)));var r=t-i;return Math.min(r,n)}(this.seekable_(),this.currentTime_(),this.playlist_.targetDuration||10);t>0&&this.remove(0,t)},i.createSimplifiedSegmentObj_=function(e){var t=e.segment,i=e.part,n={resolvedUri:i?i.resolvedUri:t.resolvedUri,byterange:i?i.byterange:t.byterange,requestId:e.requestId,transmuxer:e.transmuxer,audioAppendStart:e.audioAppendStart,gopsToAlignWith:e.gopsToAlignWith,part:e.part},r=e.playlist.segments[e.mediaIndex-1];if(r&&r.timeline===t.timeline&&(r.videoTimingInfo?n.baseStartTime=r.videoTimingInfo.transmuxedDecodeEnd:r.audioTimingInfo&&(n.baseStartTime=r.audioTimingInfo.transmuxedDecodeEnd)),t.key){var a=t.key.iv||new Uint32Array([0,0,0,e.mediaIndex+e.playlist.mediaSequence]);n.key=this.segmentKey(t.key),n.key.iv=a}return t.map&&(n.map=this.initSegmentForMap(t.map)),n},i.saveTransferStats_=function(e){this.mediaRequests+=1,e&&(this.mediaBytesTransferred+=e.bytesReceived,this.mediaTransferDuration+=e.roundTripTime)},i.saveBandwidthRelatedStats_=function(e,t){this.pendingSegment_.byteLength=t.bytesReceived,e<1/60?this.logger_("Ignoring segment's bandwidth because its duration of "+e+" is less than the min to record "+1/60):(this.bandwidth=t.bandwidth,this.roundTrip=t.roundTripTime)},i.handleTimeout_=function(){this.mediaRequestsTimedout+=1,this.bandwidth=1,this.roundTrip=NaN,this.trigger("bandwidthupdate")},i.segmentRequestFinished_=function(e,t,i){if(this.callQueue_.length)this.callQueue_.push(this.segmentRequestFinished_.bind(this,e,t,i));else if(this.saveTransferStats_(t.stats),this.pendingSegment_&&t.requestId===this.pendingSegment_.requestId){if(e){if(this.pendingSegment_=null,this.state="READY",e.code===ys)return;return this.pause(),e.code===vs?void this.handleTimeout_():(this.mediaRequestsErrored+=1,this.error(e),void this.trigger("error"))}var n=this.pendingSegment_;this.saveBandwidthRelatedStats_(n.duration,t.stats),n.endOfAllRequests=t.endOfAllRequests,i.gopInfo&&(this.gopBuffer_=function(e,t,i){if(!t.length)return e;if(i)return t.slice();for(var n=t[0].pts,r=0;r=n);r++);return e.slice(0,r).concat(t)}(this.gopBuffer_,i.gopInfo,this.safeAppend_)),this.state="APPENDING",this.trigger("appending"),this.waitForAppendsToComplete_(n)}},i.setTimeMapping_=function(e){var t=this.syncController_.mappingForTimeline(e);null!==t&&(this.timeMapping_=t)},i.updateMediaSecondsLoaded_=function(e){"number"==typeof e.start&&"number"==typeof e.end?this.mediaSecondsLoaded+=e.end-e.start:this.mediaSecondsLoaded+=e.duration},i.shouldUpdateTransmuxerTimestampOffset_=function(e){return null!==e&&("main"===this.loaderType_&&e!==this.sourceUpdater_.videoTimestampOffset()||!this.audioDisabled_&&e!==this.sourceUpdater_.audioTimestampOffset())},i.trueSegmentStart_=function(e){var t=e.currentStart,i=e.playlist,n=e.mediaIndex,r=e.firstVideoFrameTimeForData,a=e.currentVideoTimestampOffset,s=e.useVideoTimingInfo,o=e.videoTimingInfo,u=e.audioTimingInfo;if(void 0!==t)return t;if(!s)return u.start;var l=i.segments[n-1];return 0!==n&&l&&void 0!==l.start&&l.end===r+a?o.start:r},i.waitForAppendsToComplete_=function(e){var t=this.getCurrentMediaInfo_(e);if(!t)return this.error({message:"No starting media returned, likely due to an unsupported media format.",blacklistDuration:1/0}),void this.trigger("error");var i=t.hasAudio,n=t.hasVideo,r=t.isMuxed,a="main"===this.loaderType_&&n,s=!this.audioDisabled_&&i&&!r;if(e.waitingOnAppends=0,!e.hasAppendedData_)return e.timingInfo||"number"!=typeof e.timestampOffset||(this.isPendingTimestampOffset_=!0),e.timingInfo={start:0},e.waitingOnAppends++,this.isPendingTimestampOffset_||(this.updateSourceBufferTimestampOffset_(e),this.processMetadataQueue_()),void this.checkAppendsDone_(e);a&&e.waitingOnAppends++,s&&e.waitingOnAppends++,a&&this.sourceUpdater_.videoQueueCallback(this.checkAppendsDone_.bind(this,e)),s&&this.sourceUpdater_.audioQueueCallback(this.checkAppendsDone_.bind(this,e))},i.checkAppendsDone_=function(e){this.checkForAbort_(e.requestId)||(e.waitingOnAppends--,0===e.waitingOnAppends&&this.handleAppendsDone_())},i.checkForIllegalMediaSwitch=function(e){var t=function(e,t,i){return"main"===e&&t&&i?i.hasAudio||i.hasVideo?t.hasVideo&&!i.hasVideo?"Only audio found in segment when we expected video. We can't switch to audio only from a stream that had video. To get rid of this message, please add codec information to the manifest.":!t.hasVideo&&i.hasVideo?"Video found in segment when we expected only audio. We can't switch to a stream with video from an audio only stream. To get rid of this message, please add codec information to the manifest.":null:"Neither audio nor video found in segment.":null}(this.loaderType_,this.getCurrentMediaInfo_(),e);return!!t&&(this.error({message:t,blacklistDuration:1/0}),this.trigger("error"),!0)},i.updateSourceBufferTimestampOffset_=function(e){if(null!==e.timestampOffset&&"number"==typeof e.timingInfo.start&&!e.changedTimestampOffset&&"main"===this.loaderType_){var t=!1;e.timestampOffset-=e.timingInfo.start,e.changedTimestampOffset=!0,e.timestampOffset!==this.sourceUpdater_.videoTimestampOffset()&&(this.sourceUpdater_.videoTimestampOffset(e.timestampOffset),t=!0),e.timestampOffset!==this.sourceUpdater_.audioTimestampOffset()&&(this.sourceUpdater_.audioTimestampOffset(e.timestampOffset),t=!0),t&&this.trigger("timestampoffset")}},i.updateTimingInfoEnd_=function(e){e.timingInfo=e.timingInfo||{};var t=this.getMediaInfo_(),i="main"===this.loaderType_&&t&&t.hasVideo&&e.videoTimingInfo?e.videoTimingInfo:e.audioTimingInfo;i&&(e.timingInfo.end="number"==typeof i.end?i.end:i.start+e.duration)},i.handleAppendsDone_=function(){if(this.pendingSegment_&&this.trigger("appendsdone"),!this.pendingSegment_)return this.state="READY",void(this.paused()||this.monitorBuffer_());var e=this.pendingSegment_;this.updateTimingInfoEnd_(e),this.shouldSaveSegmentTimingInfo_&&this.syncController_.saveSegmentTimingInfo({segmentInfo:e,shouldSaveTimelineMapping:"main"===this.loaderType_});var t=Qs(e,this.sourceType_);if(t&&("warn"===t.severity?Yr.log.warn(t.message):this.logger_(t.message)),this.recordThroughput_(e),this.pendingSegment_=null,this.state="READY",!e.isSyncRequest||(this.trigger("syncinfoupdate"),e.hasAppendedData_)){this.logger_("Appended "+Ys(e)),this.addSegmentMetadataCue_(e),this.fetchAtBuffer_=!0,this.currentTimeline_!==e.timeline&&(this.timelineChangeController_.lastTimelineChange({type:this.loaderType_,from:this.currentTimeline_,to:e.timeline}),"main"!==this.loaderType_||this.audioDisabled_||this.timelineChangeController_.lastTimelineChange({type:"audio",from:this.currentTimeline_,to:e.timeline})),this.currentTimeline_=e.timeline,this.trigger("syncinfoupdate");var i=e.segment;if(i.end&&this.currentTime_()-i.end>3*e.playlist.targetDuration)this.resetEverything();else null!==this.mediaIndex&&this.trigger("bandwidthupdate"),this.trigger("progress"),this.mediaIndex=e.mediaIndex,this.partIndex=e.partIndex,this.isEndOfStream_(e.mediaIndex,e.playlist,e.partIndex)&&this.endOfStream(),this.trigger("appended"),e.hasAppendedData_&&this.mediaAppends++,this.paused()||this.monitorBuffer_()}else this.logger_("Throwing away un-appended sync request "+Ys(e))},i.recordThroughput_=function(e){if(e.duration<1/60)this.logger_("Ignoring segment's throughput because its duration of "+e.duration+" is less than the min to record "+1/60);else{var t=this.throughput.rate,i=Date.now()-e.endOfAllRequests+1,n=Math.floor(e.byteLength/i*8*1e3);this.throughput.rate+=(n-t)/++this.throughput.count}},i.addSegmentMetadataCue_=function(e){if(this.segmentMetadataTrack_){var t=e.segment,i=t.start,n=t.end;if(Ws(i)&&Ws(n)){Gs(i,n,this.segmentMetadataTrack_);var r=C.default.WebKitDataCue||C.default.VTTCue,a={custom:t.custom,dateTimeObject:t.dateTimeObject,dateTimeString:t.dateTimeString,bandwidth:e.playlist.attributes.BANDWIDTH,resolution:e.playlist.attributes.RESOLUTION,codecs:e.playlist.attributes.CODECS,byteLength:e.byteLength,uri:e.uri,timeline:e.timeline,playlist:e.playlist.id,start:i,end:n},s=new r(i,n,JSON.stringify(a));s.value=a,this.segmentMetadataTrack_.addCue(s)}}},t}(Yr.EventTarget);function Js(){}var Zs,eo=function(e){return"string"!=typeof e?e:e.replace(/./,(function(e){return e.toUpperCase()}))},to=["video","audio"],io=function(e,t){var i=t[e+"Buffer"];return i&&i.updating||t.queuePending[e]},no=function e(t,i){if(0!==i.queue.length){var n=0,r=i.queue[n];if("mediaSource"!==r.type){if("mediaSource"!==t&&i.ready()&&"closed"!==i.mediaSource.readyState&&!io(t,i)){if(r.type!==t){if(null===(n=function(e,t){for(var i=0;i=e.playlist.segments.length){e=null;break}e=this.generateSegmentInfo_({playlist:e.playlist,mediaIndex:e.mediaIndex+1,startOfSegment:e.startOfSegment+e.duration,isSyncRequest:e.isSyncRequest})}return e},i.stopForError=function(e){this.error(e),this.state="READY",this.pause(),this.trigger("error")},i.segmentRequestFinished_=function(e,t,i){var n=this;if(this.subtitlesTrack_){if(this.saveTransferStats_(t.stats),!this.pendingSegment_)return this.state="READY",void(this.mediaRequestsAborted+=1);if(e)return e.code===vs&&this.handleTimeout_(),e.code===ys?this.mediaRequestsAborted+=1:this.mediaRequestsErrored+=1,void this.stopForError(e);var r=this.pendingSegment_;this.saveBandwidthRelatedStats_(r.duration,t.stats),this.state="APPENDING",this.trigger("appending");var a=r.segment;if(a.map&&(a.map.bytes=t.map.bytes),r.bytes=t.bytes,"function"!=typeof C.default.WebVTT&&this.subtitlesTrack_&&this.subtitlesTrack_.tech_){var s,o=function(){n.subtitlesTrack_.tech_.off("vttjsloaded",s),n.stopForError({message:"Error loading vtt.js"})};return s=function(){n.subtitlesTrack_.tech_.off("vttjserror",o),n.segmentRequestFinished_(e,t,i)},this.state="WAITING_ON_VTTJS",this.subtitlesTrack_.tech_.one("vttjsloaded",s),void this.subtitlesTrack_.tech_.one("vttjserror",o)}a.requested=!0;try{this.parseVTTCues_(r)}catch(e){return void this.stopForError({message:e.message})}if(this.updateTimeMapping_(r,this.syncController_.timelines[r.timeline],this.playlist_),r.cues.length?r.timingInfo={start:r.cues[0].startTime,end:r.cues[r.cues.length-1].endTime}:r.timingInfo={start:r.startOfSegment,end:r.startOfSegment+r.duration},r.isSyncRequest)return this.trigger("syncinfoupdate"),this.pendingSegment_=null,void(this.state="READY");r.byteLength=r.bytes.byteLength,this.mediaSecondsLoaded+=a.duration,r.cues.forEach((function(e){n.subtitlesTrack_.addCue(n.featuresNativeTextTracks_?new C.default.VTTCue(e.startTime,e.endTime,e.text):e)})),function(e){var t=e.cues;if(t)for(var i=0;i1&&n.push(t[a]);n.length&&n.forEach((function(t){return e.removeCue(t)}))}}(this.subtitlesTrack_),this.handleAppendsDone_()}else this.state="READY"},i.handleData_=function(){},i.updateTimingInfoEnd_=function(){},i.parseVTTCues_=function(e){var t,i=!1;"function"==typeof C.default.TextDecoder?t=new C.default.TextDecoder("utf8"):(t=C.default.WebVTT.StringDecoder(),i=!0);var n=new C.default.WebVTT.Parser(C.default,C.default.vttjs,t);if(e.cues=[],e.timestampmap={MPEGTS:0,LOCAL:0},n.oncue=e.cues.push.bind(e.cues),n.ontimestampmap=function(t){e.timestampmap=t},n.onparsingerror=function(e){Yr.log.warn("Error encountered when parsing cues: "+e.message)},e.segment.map){var r=e.segment.map.bytes;i&&(r=bo(r)),n.parse(r)}var a=e.bytes;i&&(a=bo(a)),n.parse(a),n.flush()},i.updateTimeMapping_=function(e,t,i){var n=e.segment;if(t)if(e.cues.length){var r=e.timestampmap,a=r.MPEGTS/E.ONE_SECOND_IN_TS-r.LOCAL+t.mapping;if(e.cues.forEach((function(e){e.startTime+=a,e.endTime+=a})),!i.syncInfo){var s=e.cues[0].startTime,o=e.cues[e.cues.length-1].startTime;i.syncInfo={mediaSequence:i.mediaSequence+e.mediaIndex,time:Math.min(s,o-n.duration)}}}else n.empty=!0},t}($s),Eo=function(e,t){for(var i=e.cues,n=0;n=r.adStartTime&&t<=r.adEndTime)return r}return null},wo=[{name:"VOD",run:function(e,t,i,n,r){if(i!==1/0){return{time:0,segmentIndex:0,partIndex:null}}return null}},{name:"ProgramDateTime",run:function(e,t,i,n,r){if(!Object.keys(e.timelineToDatetimeMappings).length)return null;var a=null,s=null,o=aa(t);r=r||0;for(var u=0;u=c)&&(s=c,a={time:d,segmentIndex:l.segmentIndex,partIndex:l.partIndex})}}return a}},{name:"Discontinuity",run:function(e,t,i,n,r){var a=null;if(r=r||0,t.discontinuityStarts&&t.discontinuityStarts.length)for(var s=null,o=0;o=d)&&(s=d,a={time:h.time,segmentIndex:u,partIndex:null})}}return a}},{name:"Playlist",run:function(e,t,i,n,r){return t.syncInfo?{time:t.syncInfo.time,segmentIndex:t.syncInfo.mediaSequence-t.mediaSequence,partIndex:null}:null}}],Ao=function(e){function t(t){var i;return(i=e.call(this)||this).timelines=[],i.discontinuities=[],i.timelineToDatetimeMappings={},i.logger_=$r("SyncController"),i}L.default(t,e);var i=t.prototype;return i.getSyncPoint=function(e,t,i,n){var r=this.runStrategies_(e,t,i,n);return r.length?this.selectSyncPoint_(r,{key:"time",value:n}):null},i.getExpiredTime=function(e,t){if(!e||!e.segments)return null;var i=this.runStrategies_(e,t,e.discontinuitySequence,0);if(!i.length)return null;var n=this.selectSyncPoint_(i,{key:"segmentIndex",value:0});return n.segmentIndex>0&&(n.time*=-1),Math.abs(n.time+da({defaultDuration:e.targetDuration,durationList:e.segments,startIndex:n.segmentIndex,endIndex:0}))},i.runStrategies_=function(e,t,i,n){for(var r=[],a=0;a=0;i--){var n=e.segments[i];if(n&&void 0!==n.start){t.syncInfo={mediaSequence:e.mediaSequence+i,time:n.start},this.logger_("playlist refresh sync: [time:"+t.syncInfo.time+", mediaSequence: "+t.syncInfo.mediaSequence+"]"),this.trigger("syncinfoupdate");break}}},i.setDateTimeMappingForStart=function(e){if(this.timelineToDatetimeMappings={},e.segments&&e.segments.length&&e.segments[0].dateTimeObject){var t=e.segments[0],i=t.dateTimeObject.getTime()/1e3;this.timelineToDatetimeMappings[t.timeline]=-i}},i.saveSegmentTimingInfo=function(e){var t=e.segmentInfo,i=e.shouldSaveTimelineMapping,n=this.calculateSegmentTimeMapping_(t,t.timingInfo,i),r=t.segment;n&&(this.saveDiscontinuitySyncInfo_(t),t.playlist.syncInfo||(t.playlist.syncInfo={mediaSequence:t.playlist.mediaSequence+t.mediaIndex,time:r.start}));var a=r.dateTimeObject;r.discontinuity&&i&&a&&(this.timelineToDatetimeMappings[r.timeline]=-a.getTime()/1e3)},i.timestampOffsetForTimeline=function(e){return void 0===this.timelines[e]?null:this.timelines[e].time},i.mappingForTimeline=function(e){return void 0===this.timelines[e]?null:this.timelines[e].mapping},i.calculateSegmentTimeMapping_=function(e,t,i){var n,r,a=e.segment,s=e.part,o=this.timelines[e.timeline];if("number"==typeof e.timestampOffset)o={time:e.startOfSegment,mapping:e.startOfSegment-t.start},i&&(this.timelines[e.timeline]=o,this.trigger("timestampoffset"),this.logger_("time mapping for timeline "+e.timeline+": [time: "+o.time+"] [mapping: "+o.mapping+"]")),n=e.startOfSegment,r=t.end+o.mapping;else{if(!o)return!1;n=t.start+o.mapping,r=t.end+o.mapping}return s&&(s.start=n,s.end=r),(!a.start||no){var u=void 0;u=s<0?i.start-da({defaultDuration:t.targetDuration,durationList:t.segments,startIndex:e.mediaIndex,endIndex:r}):i.end+da({defaultDuration:t.targetDuration,durationList:t.segments,startIndex:e.mediaIndex+1,endIndex:r}),this.discontinuities[a]={time:u,accuracy:o}}}},i.dispose=function(){this.trigger("dispose"),this.off()},t}(Yr.EventTarget),Co=function(e){function t(){var t;return(t=e.call(this)||this).pendingTimelineChanges_={},t.lastTimelineChanges_={},t}L.default(t,e);var i=t.prototype;return i.clearPendingTimelineChange=function(e){this.pendingTimelineChanges_[e]=null,this.trigger("pendingtimelinechange")},i.pendingTimelineChange=function(e){var t=e.type,i=e.from,n=e.to;return"number"==typeof i&&"number"==typeof n&&(this.pendingTimelineChanges_[t]={type:t,from:i,to:n},this.trigger("pendingtimelinechange")),this.pendingTimelineChanges_[t]},i.lastTimelineChange=function(e){var t=e.type,i=e.from,n=e.to;return"number"==typeof i&&"number"==typeof n&&(this.lastTimelineChanges_[t]={type:t,from:i,to:n},delete this.pendingTimelineChanges_[t],this.trigger("timelinechange")),this.lastTimelineChanges_[t]},i.dispose=function(){this.trigger("dispose"),this.pendingTimelineChanges_={},this.lastTimelineChanges_={},this.off()},t}(Yr.EventTarget),ko=as(ss(os((function(){function e(e,t,i){return e(i={path:t,exports:{},require:function(e,t){return function(){throw new Error("Dynamic requires are not currently supported by @rollup/plugin-commonjs")}(null==t&&i.path)}},i.exports),i.exports}var t=e((function(e){function t(e,t){for(var i=0;i-1},t.trigger=function(e){var t=this.listeners[e];if(t)if(2===arguments.length)for(var i=t.length,n=0;n>7))^e]=e;for(t=i=0;!d[t];t^=n||1,i=p[i]||1)for(a=(a=i^i<<1^i<<2^i<<3^i<<4)>>8^255&a^99,d[t]=a,c[a]=t,o=16843009*f[r=f[n=f[t]]]^65537*r^257*n^16843008*t,s=257*f[a]^16843008*a,e=0;e<4;e++)l[e][t]=s=s<<24^s>>>8,h[e][a]=o=o<<24^o>>>8;for(e=0;e<5;e++)l[e]=l[e].slice(0),h[e]=h[e].slice(0);return u}()),this._tables=[[a[0][0].slice(),a[0][1].slice(),a[0][2].slice(),a[0][3].slice(),a[0][4].slice()],[a[1][0].slice(),a[1][1].slice(),a[1][2].slice(),a[1][3].slice(),a[1][4].slice()]];var r=this._tables[0][4],s=this._tables[1],o=e.length,u=1;if(4!==o&&6!==o&&8!==o)throw new Error("Invalid aes key size");var l=e.slice(0),h=[];for(this._key=[l,h],t=o;t<4*o+28;t++)n=l[t-1],(t%o==0||8===o&&t%o==4)&&(n=r[n>>>24]<<24^r[n>>16&255]<<16^r[n>>8&255]<<8^r[255&n],t%o==0&&(n=n<<8^n>>>24^u<<24,u=u<<1^283*(u>>7))),l[t]=l[t-o]^n;for(i=0;t;i++,t--)n=l[3&i?t:t-4],h[i]=t<=4||i<4?n:s[0][r[n>>>24]]^s[1][r[n>>16&255]]^s[2][r[n>>8&255]]^s[3][r[255&n]]}return e.prototype.decrypt=function(e,t,i,n,r,a){var s,o,u,l,h=this._key[1],d=e^h[0],c=n^h[1],f=i^h[2],p=t^h[3],m=h.length/4-2,_=4,g=this._tables[1],v=g[0],y=g[1],b=g[2],S=g[3],T=g[4];for(l=0;l>>24]^y[c>>16&255]^b[f>>8&255]^S[255&p]^h[_],o=v[c>>>24]^y[f>>16&255]^b[p>>8&255]^S[255&d]^h[_+1],u=v[f>>>24]^y[p>>16&255]^b[d>>8&255]^S[255&c]^h[_+2],p=v[p>>>24]^y[d>>16&255]^b[c>>8&255]^S[255&f]^h[_+3],_+=4,d=s,c=o,f=u;for(l=0;l<4;l++)r[(3&-l)+a]=T[d>>>24]<<24^T[c>>16&255]<<16^T[f>>8&255]<<8^T[255&p]^h[_++],s=d,d=c,c=f,f=p,p=s},e}(),o=function(e){function t(){var t;return(t=e.call(this,r)||this).jobs=[],t.delay=1,t.timeout_=null,t}n(t,e);var i=t.prototype;return i.processJob_=function(){this.jobs.shift()(),this.jobs.length?this.timeout_=setTimeout(this.processJob_.bind(this),this.delay):this.timeout_=null},i.push=function(e){this.jobs.push(e),this.timeout_||(this.timeout_=setTimeout(this.processJob_.bind(this),this.delay))},t}(r),u=function(e){return e<<24|(65280&e)<<8|(16711680&e)>>8|e>>>24},l=function(){function e(t,i,n,r){var a=e.STEP,s=new Int32Array(t.buffer),l=new Uint8Array(t.byteLength),h=0;for(this.asyncStream_=new o,this.asyncStream_.push(this.decryptChunk_(s.subarray(h,h+a),i,n,l)),h=a;h>2),m=new s(Array.prototype.slice.call(t)),_=new Uint8Array(e.byteLength),g=new Int32Array(_.buffer);for(n=i[0],r=i[1],a=i[2],o=i[3],f=0;f=0&&(t="main-desc"),t},Io=function(e,t){e.abort(),e.pause(),t&&t.activePlaylistLoader&&(t.activePlaylistLoader.pause(),t.activePlaylistLoader=null)},Lo=function(e,t){t.activePlaylistLoader=e,e.load()},xo={AUDIO:function(e,t){return function(){var i=t.segmentLoaders[e],n=t.mediaTypes[e],r=t.blacklistCurrentPlaylist;Io(i,n);var a=n.activeTrack(),s=n.activeGroup(),o=(s.filter((function(e){return e.default}))[0]||s[0]).id,u=n.tracks[o];if(a!==u){for(var l in Yr.log.warn("Problem encountered loading the alternate audio track.Switching back to default."),n.tracks)n.tracks[l].enabled=n.tracks[l]===u;n.onTrackChanged()}else r({message:"Problem encountered loading the default audio track."})}},SUBTITLES:function(e,t){return function(){var i=t.segmentLoaders[e],n=t.mediaTypes[e];Yr.log.warn("Problem encountered loading the subtitle track.Disabling subtitle track."),Io(i,n);var r=n.activeTrack();r&&(r.mode="disabled"),n.onTrackChanged()}}},Ro={AUDIO:function(e,t,i){if(t){var n=i.tech,r=i.requestOptions,a=i.segmentLoaders[e];t.on("loadedmetadata",(function(){var e=t.media();a.playlist(e,r),(!n.paused()||e.endList&&"none"!==n.preload())&&a.load()})),t.on("loadedplaylist",(function(){a.playlist(t.media(),r),n.paused()||a.load()})),t.on("error",xo[e](e,i))}},SUBTITLES:function(e,t,i){var n=i.tech,r=i.requestOptions,a=i.segmentLoaders[e],s=i.mediaTypes[e];t.on("loadedmetadata",(function(){var e=t.media();a.playlist(e,r),a.track(s.activeTrack()),(!n.paused()||e.endList&&"none"!==n.preload())&&a.load()})),t.on("loadedplaylist",(function(){a.playlist(t.media(),r),n.paused()||a.load()})),t.on("error",xo[e](e,i))}},Do={AUDIO:function(e,t){var i=t.vhs,n=t.sourceType,r=t.segmentLoaders[e],a=t.requestOptions,s=t.master.mediaGroups,o=t.mediaTypes[e],u=o.groups,l=o.tracks,h=o.logger_,d=t.masterPlaylistLoader,c=ba(d.master);for(var f in s[e]&&0!==Object.keys(s[e]).length||(s[e]={main:{default:{default:!0}}},c&&(s[e].main.default.playlists=d.master.playlists)),s[e])for(var p in u[f]||(u[f]=[]),s[e][f]){var m=s[e][f][p],_=void 0;if(c?(h("AUDIO group '"+f+"' label '"+p+"' is a master playlist"),m.isMasterPlaylist=!0,_=null):_="vhs-json"===n&&m.playlists?new Ua(m.playlists[0],i,a):m.resolvedUri?new Ua(m.resolvedUri,i,a):m.playlists&&"dash"===n?new is(m.playlists[0],i,a,d):null,m=Yr.mergeOptions({id:p,playlistLoader:_},m),Ro[e](e,m.playlistLoader,t),u[f].push(m),void 0===l[p]){var g=new Yr.AudioTrack({id:p,kind:Po(m),enabled:!1,language:m.language,default:m.default,label:p});l[p]=g}}r.on("error",xo[e](e,t))},SUBTITLES:function(e,t){var i=t.tech,n=t.vhs,r=t.sourceType,a=t.segmentLoaders[e],s=t.requestOptions,o=t.master.mediaGroups,u=t.mediaTypes[e],l=u.groups,h=u.tracks,d=t.masterPlaylistLoader;for(var c in o[e])for(var f in l[c]||(l[c]=[]),o[e][c])if(!o[e][c][f].forced){var p=o[e][c][f],m=void 0;if("hls"===r)m=new Ua(p.resolvedUri,n,s);else if("dash"===r){if(!p.playlists.filter((function(e){return e.excludeUntil!==1/0})).length)return;m=new is(p.playlists[0],n,s,d)}else"vhs-json"===r&&(m=new Ua(p.playlists?p.playlists[0]:p.resolvedUri,n,s));if(p=Yr.mergeOptions({id:f,playlistLoader:m},p),Ro[e](e,p.playlistLoader,t),l[c].push(p),void 0===h[f]){var _=i.addRemoteTextTrack({id:f,kind:"subtitles",default:p.default&&p.autoselect,language:p.language,label:f},!1).track;h[f]=_}}a.on("error",xo[e](e,t))},"CLOSED-CAPTIONS":function(e,t){var i=t.tech,n=t.master.mediaGroups,r=t.mediaTypes[e],a=r.groups,s=r.tracks;for(var o in n[e])for(var u in a[o]||(a[o]=[]),n[e][o]){var l=n[e][o][u];if(/^(?:CC|SERVICE)/.test(l.instreamId)){var h=i.options_.vhs&&i.options_.vhs.captionServices||{},d={label:u,language:l.language,instreamId:l.instreamId,default:l.default&&l.autoselect};if(h[d.instreamId]&&(d=Yr.mergeOptions(d,h[d.instreamId])),void 0===d.default&&delete d.default,a[o].push(Yr.mergeOptions({id:u},l)),void 0===s[u]){var c=i.addRemoteTextTrack({id:d.instreamId,kind:"captions",default:d.default,language:d.language,label:d.label},!1).track;s[u]=c}}}}},Oo=function e(t,i){for(var n=0;n1&&ba(t.master))for(var u=0;u "+a+" from "+t),this.tech_.trigger({type:"usage",name:"vhs-rendition-change-"+t})),this.masterPlaylistLoader_.media(e,i)},i.startABRTimer_=function(){var e=this;this.stopABRTimer_(),this.abrTimer_=C.default.setInterval((function(){return e.checkABR_()}),250)},i.stopABRTimer_=function(){this.tech_.scrubbing&&this.tech_.scrubbing()||(C.default.clearInterval(this.abrTimer_),this.abrTimer_=null)},i.getAudioTrackPlaylists_=function(){var e=this.master(),t=e&&e.playlists||[];if(!e||!e.mediaGroups||!e.mediaGroups.AUDIO)return t;var i,n=e.mediaGroups.AUDIO,r=Object.keys(n);if(Object.keys(this.mediaTypes_.AUDIO.groups).length)i=this.mediaTypes_.AUDIO.activeTrack();else{var a=n.main||r.length&&n[r[0]];for(var s in a)if(a[s].default){i={label:s};break}}if(!i)return t;var o=[];for(var u in n)if(n[u][i.label]){var l=n[u][i.label];if(l.playlists&&l.playlists.length)o.push.apply(o,l.playlists);else if(l.uri)o.push(l);else if(e.playlists.length)for(var h=0;h1&&(this.tech_.trigger({type:"usage",name:"vhs-alternate-audio"}),this.tech_.trigger({type:"usage",name:"hls-alternate-audio"})),this.useCueTags_&&(this.tech_.trigger({type:"usage",name:"vhs-playlist-cue-tags"}),this.tech_.trigger({type:"usage",name:"hls-playlist-cue-tags"}))},i.shouldSwitchToMedia_=function(e){var t=this.masterPlaylistLoader_.media(),i=this.tech_.buffered();return function(e){var t=e.currentPlaylist,i=e.nextPlaylist,n=e.forwardBuffer,r=e.bufferLowWaterLine,a=e.bufferHighWaterLine,s=e.duration,o=e.experimentalBufferBasedABR,u=e.log;if(!i)return Yr.log.warn("We received no playlist to switch to. Please check your stream."),!1;var l="allowing switch "+(t&&t.id||"null")+" -> "+i.id;if(!t)return u(l+" as current playlist is not set"),!0;if(i.id===t.id)return!1;if(!t.endList)return u(l+" as current playlist is live"),!0;var h=o?ns.EXPERIMENTAL_MAX_BUFFER_LOW_WATER_LINE:ns.MAX_BUFFER_LOW_WATER_LINE;if(sc)&&n>=r){var p=l+" as forwardBuffer >= bufferLowWaterLine ("+n+" >= "+r+")";return o&&(p+=" and next bandwidth > current bandwidth ("+d+" > "+c+")"),u(p),!0}return u("not "+l+" as no switching criteria met"),!1}({currentPlaylist:t,nextPlaylist:e,forwardBuffer:i.length?i.end(i.length-1)-this.tech_.currentTime():0,bufferLowWaterLine:this.bufferLowWaterLine(),bufferHighWaterLine:this.bufferHighWaterLine(),duration:this.duration(),experimentalBufferBasedABR:this.experimentalBufferBasedABR,log:this.logger_})},i.setupSegmentLoaderListeners_=function(){var e=this;this.experimentalBufferBasedABR||(this.mainSegmentLoader_.on("bandwidthupdate",(function(){var t=e.selectPlaylist();e.shouldSwitchToMedia_(t)&&e.switchMedia_(t,"bandwidthupdate"),e.tech_.trigger("bandwidthupdate")})),this.mainSegmentLoader_.on("progress",(function(){e.trigger("progress")}))),this.mainSegmentLoader_.on("error",(function(){e.blacklistCurrentPlaylist(e.mainSegmentLoader_.error())})),this.mainSegmentLoader_.on("appenderror",(function(){e.error=e.mainSegmentLoader_.error_,e.trigger("error")})),this.mainSegmentLoader_.on("syncinfoupdate",(function(){e.onSyncInfoUpdate_()})),this.mainSegmentLoader_.on("timestampoffset",(function(){e.tech_.trigger({type:"usage",name:"vhs-timestamp-offset"}),e.tech_.trigger({type:"usage",name:"hls-timestamp-offset"})})),this.audioSegmentLoader_.on("syncinfoupdate",(function(){e.onSyncInfoUpdate_()})),this.audioSegmentLoader_.on("appenderror",(function(){e.error=e.audioSegmentLoader_.error_,e.trigger("error")})),this.mainSegmentLoader_.on("ended",(function(){e.logger_("main segment loader ended"),e.onEndOfStream()})),this.mainSegmentLoader_.on("earlyabort",(function(t){e.experimentalBufferBasedABR||(e.delegateLoaders_("all",["abort"]),e.blacklistCurrentPlaylist({message:"Aborted early because there isn't enough bandwidth to complete the request without rebuffering."},120))}));var t=function(){if(!e.sourceUpdater_.hasCreatedSourceBuffers())return e.tryToCreateSourceBuffers_();var t=e.getCodecsOrExclude_();t&&e.sourceUpdater_.addOrChangeSourceBuffers(t)};this.mainSegmentLoader_.on("trackinfo",t),this.audioSegmentLoader_.on("trackinfo",t),this.mainSegmentLoader_.on("fmp4",(function(){e.triggeredFmp4Usage||(e.tech_.trigger({type:"usage",name:"vhs-fmp4"}),e.tech_.trigger({type:"usage",name:"hls-fmp4"}),e.triggeredFmp4Usage=!0)})),this.audioSegmentLoader_.on("fmp4",(function(){e.triggeredFmp4Usage||(e.tech_.trigger({type:"usage",name:"vhs-fmp4"}),e.tech_.trigger({type:"usage",name:"hls-fmp4"}),e.triggeredFmp4Usage=!0)})),this.audioSegmentLoader_.on("ended",(function(){e.logger_("audioSegmentLoader ended"),e.onEndOfStream()}))},i.mediaSecondsLoaded_=function(){return Math.max(this.audioSegmentLoader_.mediaSecondsLoaded+this.mainSegmentLoader_.mediaSecondsLoaded)},i.load=function(){this.mainSegmentLoader_.load(),this.mediaTypes_.AUDIO.activePlaylistLoader&&this.audioSegmentLoader_.load(),this.mediaTypes_.SUBTITLES.activePlaylistLoader&&this.subtitleSegmentLoader_.load()},i.smoothQualityChange_=function(e){void 0===e&&(e=this.selectPlaylist()),this.fastQualityChange_(e)},i.fastQualityChange_=function(e){var t=this;void 0===e&&(e=this.selectPlaylist()),e!==this.masterPlaylistLoader_.media()?(this.switchMedia_(e,"fast-quality"),this.mainSegmentLoader_.resetEverything((function(){Yr.browser.IE_VERSION||Yr.browser.IS_EDGE?t.tech_.setCurrentTime(t.tech_.currentTime()+.04):t.tech_.setCurrentTime(t.tech_.currentTime())}))):this.logger_("skipping fastQualityChange because new media is same as old")},i.play=function(){if(!this.setupFirstPlay()){this.tech_.ended()&&this.tech_.setCurrentTime(0),this.hasPlayed_&&this.load();var e=this.tech_.seekable();return this.tech_.duration()===1/0&&this.tech_.currentTime()this.maxPlaylistRetries?1/0:Date.now()+1e3*t,i.excludeUntil=n,e.reason&&(i.lastExcludeReason_=e.reason),this.tech_.trigger("blacklistplaylist"),this.tech_.trigger({type:"usage",name:"vhs-rendition-blacklisted"}),this.tech_.trigger({type:"usage",name:"hls-rendition-blacklisted"});var u=this.selectPlaylist();if(!u)return this.error="Playback cannot continue. No available working or supported playlists.",void this.trigger("error");var l=e.internal?this.logger_:Yr.log.warn,h=e.message?" "+e.message:"";l((e.internal?"Internal problem":"Problem")+" encountered with playlist "+i.id+"."+h+" Switching to playlist "+u.id+"."),u.attributes.AUDIO!==i.attributes.AUDIO&&this.delegateLoaders_("audio",["abort","pause"]),u.attributes.SUBTITLES!==i.attributes.SUBTITLES&&this.delegateLoaders_("subtitle",["abort","pause"]),this.delegateLoaders_("main",["abort","pause"]);var d=u.targetDuration/2*1e3||5e3,c="number"==typeof u.lastRequest&&Date.now()-u.lastRequest<=d;return this.switchMedia_(u,"exclude",s||c)},i.pauseLoading=function(){this.delegateLoaders_("all",["abort","pause"]),this.stopABRTimer_()},i.delegateLoaders_=function(e,t){var i=this,n=[],r="all"===e;(r||"main"===e)&&n.push(this.masterPlaylistLoader_);var a=[];(r||"audio"===e)&&a.push("AUDIO"),(r||"subtitle"===e)&&(a.push("CLOSED-CAPTIONS"),a.push("SUBTITLES")),a.forEach((function(e){var t=i.mediaTypes_[e]&&i.mediaTypes_[e].activePlaylistLoader;t&&n.push(t)})),["main","audio","subtitle"].forEach((function(t){var r=i[t+"SegmentLoader_"];!r||e!==t&&"all"!==e||n.push(r)})),n.forEach((function(e){return t.forEach((function(t){"function"==typeof e[t]&&e[t]()}))}))},i.setCurrentTime=function(e){var t=Zr(this.tech_.buffered(),e);return this.masterPlaylistLoader_&&this.masterPlaylistLoader_.media()&&this.masterPlaylistLoader_.media().segments?t&&t.length?e:(this.mainSegmentLoader_.resetEverything(),this.mainSegmentLoader_.abort(),this.mediaTypes_.AUDIO.activePlaylistLoader&&(this.audioSegmentLoader_.resetEverything(),this.audioSegmentLoader_.abort()),this.mediaTypes_.SUBTITLES.activePlaylistLoader&&(this.subtitleSegmentLoader_.resetEverything(),this.subtitleSegmentLoader_.abort()),void this.load()):0},i.duration=function(){if(!this.masterPlaylistLoader_)return 0;var e=this.masterPlaylistLoader_.media();return e?e.endList?this.mediaSource?this.mediaSource.duration:Zs.Playlist.duration(e):1/0:0},i.seekable=function(){return this.seekable_},i.onSyncInfoUpdate_=function(){var e;if(this.masterPlaylistLoader_){var t=this.masterPlaylistLoader_.media();if(t){var i=this.syncController_.getExpiredTime(t,this.duration());if(null!==i){var n=this.masterPlaylistLoader_.master,r=Zs.Playlist.seekable(t,i,Zs.Playlist.liveEdgeDelay(n,t));if(0!==r.length){if(this.mediaTypes_.AUDIO.activePlaylistLoader){if(t=this.mediaTypes_.AUDIO.activePlaylistLoader.media(),null===(i=this.syncController_.getExpiredTime(t,this.duration())))return;if(0===(e=Zs.Playlist.seekable(t,i,Zs.Playlist.liveEdgeDelay(n,t))).length)return}var a,s;this.seekable_&&this.seekable_.length&&(a=this.seekable_.end(0),s=this.seekable_.start(0)),e?e.start(0)>r.end(0)||r.start(0)>e.end(0)?this.seekable_=r:this.seekable_=Yr.createTimeRanges([[e.start(0)>r.start(0)?e.start(0):r.start(0),e.end(0)0&&(n=Math.max(n,i.end(i.length-1))),this.mediaSource.duration!==n&&this.sourceUpdater_.setDuration(n)}},i.dispose=function(){var e=this;this.trigger("dispose"),this.decrypter_.terminate(),this.masterPlaylistLoader_.dispose(),this.mainSegmentLoader_.dispose(),this.loadOnPlay_&&this.tech_.off("play",this.loadOnPlay_),["AUDIO","SUBTITLES"].forEach((function(t){var i=e.mediaTypes_[t].groups;for(var n in i)i[n].forEach((function(e){e.playlistLoader&&e.playlistLoader.dispose()}))})),this.audioSegmentLoader_.dispose(),this.subtitleSegmentLoader_.dispose(),this.sourceUpdater_.dispose(),this.timelineChangeController_.dispose(),this.stopABRTimer_(),this.updateDuration_&&this.mediaSource.removeEventListener("sourceopen",this.updateDuration_),this.mediaSource.removeEventListener("durationchange",this.handleDurationChange_),this.mediaSource.removeEventListener("sourceopen",this.handleSourceOpen_),this.mediaSource.removeEventListener("sourceended",this.handleSourceEnded_),this.off()},i.master=function(){return this.masterPlaylistLoader_.master},i.media=function(){return this.masterPlaylistLoader_.media()||this.initialMedia_},i.areMediaTypesKnown_=function(){var e=!!this.mediaTypes_.AUDIO.activePlaylistLoader,t=!!this.mainSegmentLoader_.getCurrentMediaInfo_(),i=!e||!!this.audioSegmentLoader_.getCurrentMediaInfo_();return!(!t||!i)},i.getCodecsOrExclude_=function(){var e=this,t={main:this.mainSegmentLoader_.getCurrentMediaInfo_()||{},audio:this.audioSegmentLoader_.getCurrentMediaInfo_()||{}};t.video=t.main;var i=Us(this.master(),this.media()),n={},r=!!this.mediaTypes_.AUDIO.activePlaylistLoader;if(t.main.hasVideo&&(n.video=i.video||t.main.videoCodec||_.DEFAULT_VIDEO_CODEC),t.main.isMuxed&&(n.video+=","+(i.audio||t.main.audioCodec||_.DEFAULT_AUDIO_CODEC)),(t.main.hasAudio&&!t.main.isMuxed||t.audio.hasAudio||r)&&(n.audio=i.audio||t.main.audioCodec||t.audio.audioCodec||_.DEFAULT_AUDIO_CODEC,t.audio.isFmp4=t.main.hasAudio&&!t.main.isMuxed?t.main.isFmp4:t.audio.isFmp4),n.audio||n.video){var a,s={};if(["video","audio"].forEach((function(e){if(n.hasOwnProperty(e)&&(r=t[e].isFmp4,o=n[e],!(r?_.browserSupportsCodec(o):_.muxerSupportsCodec(o)))){var i=t[e].isFmp4?"browser":"muxer";s[i]=s[i]||[],s[i].push(n[e]),"audio"===e&&(a=i)}var r,o})),r&&a&&this.media().attributes.AUDIO){var o=this.media().attributes.AUDIO;this.master().playlists.forEach((function(t){(t.attributes&&t.attributes.AUDIO)===o&&t!==e.media()&&(t.excludeUntil=1/0)})),this.logger_("excluding audio group "+o+" as "+a+' does not support codec(s): "'+n.audio+'"')}if(!Object.keys(s).length){if(this.sourceUpdater_.hasCreatedSourceBuffers()&&!this.sourceUpdater_.canChangeType()){var u=[];if(["video","audio"].forEach((function(t){var i=(_.parseCodecs(e.sourceUpdater_.codecs[t]||"")[0]||{}).type,r=(_.parseCodecs(n[t]||"")[0]||{}).type;i&&r&&i.toLowerCase()!==r.toLowerCase()&&u.push('"'+e.sourceUpdater_.codecs[t]+'" -> "'+n[t]+'"')})),u.length)return void this.blacklistCurrentPlaylist({playlist:this.media(),message:"Codec switching not supported: "+u.join(", ")+".",blacklistDuration:1/0,internal:!0})}return n}var l=Object.keys(s).reduce((function(e,t){return e&&(e+=", "),e+=t+' does not support codec(s): "'+s[t].join(",")+'"'}),"")+".";this.blacklistCurrentPlaylist({playlist:this.media(),internal:!0,message:l,blacklistDuration:1/0})}else this.blacklistCurrentPlaylist({playlist:this.media(),message:"Could not determine codecs for playlist.",blacklistDuration:1/0})},i.tryToCreateSourceBuffers_=function(){if("open"===this.mediaSource.readyState&&!this.sourceUpdater_.hasCreatedSourceBuffers()&&this.areMediaTypesKnown_()){var e=this.getCodecsOrExclude_();if(e){this.sourceUpdater_.createSourceBuffers(e);var t=[e.video,e.audio].filter(Boolean).join(",");this.excludeIncompatibleVariants_(t)}}},i.excludeUnsupportedVariants_=function(){var e=this,t=this.master().playlists,i=[];Object.keys(t).forEach((function(n){var r=t[n];if(-1===i.indexOf(r.id)){i.push(r.id);var a=Us(e.master,r),s=[];!a.audio||_.muxerSupportsCodec(a.audio)||_.browserSupportsCodec(a.audio)||s.push("audio codec "+a.audio),!a.video||_.muxerSupportsCodec(a.video)||_.browserSupportsCodec(a.video)||s.push("video codec "+a.video),a.text&&"stpp.ttml.im1t"===a.text&&s.push("text codec "+a.text),s.length&&(r.excludeUntil=1/0,e.logger_("excluding "+r.id+" for unsupported: "+s.join(", ")))}}))},i.excludeIncompatibleVariants_=function(e){var t=this,i=[],n=this.master().playlists,r=Ds(_.parseCodecs(e)),a=Os(r),s=r.video&&_.parseCodecs(r.video)[0]||null,o=r.audio&&_.parseCodecs(r.audio)[0]||null;Object.keys(n).forEach((function(e){var r=n[e];if(-1===i.indexOf(r.id)&&r.excludeUntil!==1/0){i.push(r.id);var u=[],l=Us(t.masterPlaylistLoader_.master,r),h=Os(l);if(l.audio||l.video){if(h!==a&&u.push('codec count "'+h+'" !== "'+a+'"'),!t.sourceUpdater_.canChangeType()){var d=l.video&&_.parseCodecs(l.video)[0]||null,c=l.audio&&_.parseCodecs(l.audio)[0]||null;d&&s&&d.type.toLowerCase()!==s.type.toLowerCase()&&u.push('video codec "'+d.type+'" !== "'+s.type+'"'),c&&o&&c.type.toLowerCase()!==o.type.toLowerCase()&&u.push('audio codec "'+c.type+'" !== "'+o.type+'"')}u.length&&(r.excludeUntil=1/0,t.logger_("blacklisting "+r.id+": "+u.join(" && ")))}}}))},i.updateAdCues_=function(e){var t=0,i=this.seekable();i.length&&(t=i.start(0)),function(e,t,i){if(void 0===i&&(i=0),e.segments)for(var n,r=i,a=0;a0&&this.logger_("resetting possible stalled download count for "+e+" loader"),this[e+"StalledDownloads_"]=0,this[e+"Buffered_"]=t.buffered_()},t.checkSegmentDownloads_=function(e){var t=this.masterPlaylistController_,i=t[e+"SegmentLoader_"],n=i.buffered_(),r=function(e,t){if(e===t)return!1;if(!e&&t||!t&&e)return!0;if(e.length!==t.length)return!0;for(var i=0;i=t.end(t.length-1)))return this.techWaiting_();this.consecutiveUpdates>=5&&e===this.lastRecordedTime?(this.consecutiveUpdates++,this.waiting_()):e===this.lastRecordedTime?this.consecutiveUpdates++:(this.consecutiveUpdates=0,this.lastRecordedTime=e)}},t.cancelTimer_=function(){this.consecutiveUpdates=0,this.timer_&&(this.logger_("cancelTimer_"),clearTimeout(this.timer_)),this.timer_=null},t.fixesBadSeeks_=function(){if(!this.tech_.seeking())return!1;var e,t=this.seekable(),i=this.tech_.currentTime();this.afterSeekableWindow_(t,i,this.media(),this.allowSeeksWithinUnsafeLiveWindow)&&(e=t.end(t.length-1));if(this.beforeSeekableWindow_(t,i)){var n=t.start(0);e=n+(n===t.end(0)?0:.1)}if(void 0!==e)return this.logger_("Trying to seek outside of seekable at time "+i+" with seekable range "+ta(t)+". Seeking to "+e+"."),this.tech_.setCurrentTime(e),!0;var r=this.tech_.buffered();return!!function(e){var t=e.buffered,i=e.targetDuration,n=e.currentTime;return!!t.length&&(!(t.end(0)-t.start(0)<2*i)&&(!(n>t.start(0))&&t.start(0)-n "+i.end(0)+"]. Attempting to resume playback by seeking to the current time."),this.tech_.trigger({type:"usage",name:"vhs-unknown-waiting"}),void this.tech_.trigger({type:"usage",name:"hls-unknown-waiting"})):void 0}},t.techWaiting_=function(){var e=this.seekable(),t=this.tech_.currentTime();if(this.tech_.seeking()&&this.fixesBadSeeks_())return!0;if(this.tech_.seeking()||null!==this.timer_)return!0;if(this.beforeSeekableWindow_(e,t)){var i=e.end(e.length-1);return this.logger_("Fell out of live window at time "+t+". Seeking to live point (seekable end) "+i),this.cancelTimer_(),this.tech_.setCurrentTime(i),this.tech_.trigger({type:"usage",name:"vhs-live-resync"}),this.tech_.trigger({type:"usage",name:"hls-live-resync"}),!0}var n=this.tech_.vhs.masterPlaylistController_.sourceUpdater_,r=this.tech_.buffered();if(this.videoUnderflow_({audioBuffered:n.audioBuffered(),videoBuffered:n.videoBuffered(),currentTime:t}))return this.cancelTimer_(),this.tech_.setCurrentTime(t),this.tech_.trigger({type:"usage",name:"vhs-video-underflow"}),this.tech_.trigger({type:"usage",name:"hls-video-underflow"}),!0;var a=ea(r,t);if(a.length>0){var s=a.start(0)-t;return this.logger_("Stopped at "+t+", setting timer for "+s+", seeking to "+a.start(0)),this.cancelTimer_(),this.timer_=setTimeout(this.skipTheGap_.bind(this),1e3*s,t),!0}return!1},t.afterSeekableWindow_=function(e,t,i,n){if(void 0===n&&(n=!1),!e.length)return!1;var r=e.end(e.length-1)+.1;return!i.endList&&n&&(r=e.end(e.length-1)+3*i.targetDuration),t>r},t.beforeSeekableWindow_=function(e,t){return!!(e.length&&e.start(0)>0&&t2)return{start:r,end:a}}return null},e}(),zo={errorInterval:30,getSource:function(e){return e(this.tech({IWillNotUseThisInPlugins:!0}).currentSource_||this.currentSource())}},Go=function(e){!function e(t,i){var n=0,r=0,a=Yr.mergeOptions(zo,i);t.ready((function(){t.trigger({type:"usage",name:"vhs-error-reload-initialized"}),t.trigger({type:"usage",name:"hls-error-reload-initialized"})}));var s=function(){r&&t.currentTime(r)},o=function(e){null!=e&&(r=t.duration()!==1/0&&t.currentTime()||0,t.one("loadedmetadata",s),t.src(e),t.trigger({type:"usage",name:"vhs-error-reload"}),t.trigger({type:"usage",name:"hls-error-reload"}),t.play())},u=function(){return Date.now()-n<1e3*a.errorInterval?(t.trigger({type:"usage",name:"vhs-error-reload-canceled"}),void t.trigger({type:"usage",name:"hls-error-reload-canceled"})):a.getSource&&"function"==typeof a.getSource?(n=Date.now(),a.getSource.call(t,o)):void Yr.log.error("ERROR: reloadSourceOnError - The option getSource must be a function!")},l=function e(){t.off("loadedmetadata",s),t.off("error",u),t.off("dispose",e)};t.on("error",u),t.on("dispose",l),t.reloadSourceOnError=function(i){l(),e(t,i)}}(this,e)},Wo={PlaylistLoader:Ua,Playlist:Sa,utils:Ka,STANDARD_PLAYLIST_SELECTOR:Hs,INITIAL_PLAYLIST_SELECTOR:function(){var e=this,t=this.playlists.master.playlists.filter(Sa.isEnabled);return Ns(t,(function(e,t){return js(e,t)})),t.filter((function(t){return!!Us(e.playlists.master,t).video}))[0]||null},lastBandwidthSelector:Hs,movingAverageBandwidthSelector:function(e){var t=-1,i=-1;if(e<0||e>1)throw new Error("Moving average bandwidth decay must be between 0 and 1.");return function(){var n=this.useDevicePixelRatio&&C.default.devicePixelRatio||1;return t<0&&(t=this.systemBandwidth,i=this.systemBandwidth),this.systemBandwidth>0&&this.systemBandwidth!==i&&(t=e*this.systemBandwidth+(1-e)*t,i=this.systemBandwidth),Vs(this.playlists.master,t,parseInt(Bs(this.tech_.el(),"width"),10)*n,parseInt(Bs(this.tech_.el(),"height"),10)*n,this.limitRenditionByPlayerDimensions,this.masterPlaylistController_)}},comparePlaylistBandwidth:js,comparePlaylistResolution:function(e,t){var i,n;return e.attributes.RESOLUTION&&e.attributes.RESOLUTION.width&&(i=e.attributes.RESOLUTION.width),i=i||C.default.Number.MAX_VALUE,t.attributes.RESOLUTION&&t.attributes.RESOLUTION.width&&(n=t.attributes.RESOLUTION.width),i===(n=n||C.default.Number.MAX_VALUE)&&e.attributes.BANDWIDTH&&t.attributes.BANDWIDTH?e.attributes.BANDWIDTH-t.attributes.BANDWIDTH:i-n},xhr:Na()};Object.keys(ns).forEach((function(e){Object.defineProperty(Wo,e,{get:function(){return Yr.log.warn("using Vhs."+e+" is UNSAFE be sure you know what you are doing"),ns[e]},set:function(t){Yr.log.warn("using Vhs."+e+" is UNSAFE be sure you know what you are doing"),"number"!=typeof t||t<0?Yr.log.warn("value of Vhs."+e+" must be greater than or equal to 0"):ns[e]=t}})}));var Yo=function(e,t){for(var i=t.media(),n=-1,r=0;r0?1/this.throughput:0,Math.floor(1/(t+e))},set:function(){Yr.log.error('The "systemBandwidth" property is read-only')}}}),this.options_.bandwidth&&(this.bandwidth=this.options_.bandwidth),this.options_.throughput&&(this.throughput=this.options_.throughput),Object.defineProperties(this.stats,{bandwidth:{get:function(){return i.bandwidth||0},enumerable:!0},mediaRequests:{get:function(){return i.masterPlaylistController_.mediaRequests_()||0},enumerable:!0},mediaRequestsAborted:{get:function(){return i.masterPlaylistController_.mediaRequestsAborted_()||0},enumerable:!0},mediaRequestsTimedout:{get:function(){return i.masterPlaylistController_.mediaRequestsTimedout_()||0},enumerable:!0},mediaRequestsErrored:{get:function(){return i.masterPlaylistController_.mediaRequestsErrored_()||0},enumerable:!0},mediaTransferDuration:{get:function(){return i.masterPlaylistController_.mediaTransferDuration_()||0},enumerable:!0},mediaBytesTransferred:{get:function(){return i.masterPlaylistController_.mediaBytesTransferred_()||0},enumerable:!0},mediaSecondsLoaded:{get:function(){return i.masterPlaylistController_.mediaSecondsLoaded_()||0},enumerable:!0},mediaAppends:{get:function(){return i.masterPlaylistController_.mediaAppends_()||0},enumerable:!0},mainAppendsToLoadedData:{get:function(){return i.masterPlaylistController_.mainAppendsToLoadedData_()||0},enumerable:!0},audioAppendsToLoadedData:{get:function(){return i.masterPlaylistController_.audioAppendsToLoadedData_()||0},enumerable:!0},appendsToLoadedData:{get:function(){return i.masterPlaylistController_.appendsToLoadedData_()||0},enumerable:!0},timeToLoadedData:{get:function(){return i.masterPlaylistController_.timeToLoadedData_()||0},enumerable:!0},buffered:{get:function(){return ia(i.tech_.buffered())},enumerable:!0},currentTime:{get:function(){return i.tech_.currentTime()},enumerable:!0},currentSource:{get:function(){return i.tech_.currentSource_},enumerable:!0},currentTech:{get:function(){return i.tech_.name_},enumerable:!0},duration:{get:function(){return i.tech_.duration()},enumerable:!0},master:{get:function(){return i.playlists.master},enumerable:!0},playerDimensions:{get:function(){return i.tech_.currentDimensions()},enumerable:!0},seekable:{get:function(){return ia(i.tech_.seekable())},enumerable:!0},timestamp:{get:function(){return Date.now()},enumerable:!0},videoPlaybackQuality:{get:function(){return i.tech_.getVideoPlaybackQuality()},enumerable:!0}}),this.tech_.one("canplay",this.masterPlaylistController_.setupFirstPlay.bind(this.masterPlaylistController_)),this.tech_.on("bandwidthupdate",(function(){i.options_.useBandwidthFromLocalStorage&&function(e){if(!C.default.localStorage)return!1;var t=Xo();t=t?Yr.mergeOptions(t,e):e;try{C.default.localStorage.setItem("videojs-vhs",JSON.stringify(t))}catch(e){return!1}}({bandwidth:i.bandwidth,throughput:Math.round(i.throughput)})})),this.masterPlaylistController_.on("selectedinitialmedia",(function(){var e;(e=i).representations=function(){var t=e.masterPlaylistController_.master(),i=ba(t)?e.masterPlaylistController_.getAudioTrackPlaylists_():t.playlists;return i?i.filter((function(e){return!pa(e)})).map((function(t,i){return new jo(e,t,t.id)})):[]}})),this.masterPlaylistController_.sourceUpdater_.on("createdsourcebuffers",(function(){i.setupEme_()})),this.on(this.masterPlaylistController_,"progress",(function(){this.tech_.trigger("progress")})),this.on(this.masterPlaylistController_,"firstplay",(function(){this.ignoreNextSeekingEvent_=!0})),this.setupQualityLevels_(),this.tech_.el()&&(this.mediaSourceUrl_=C.default.URL.createObjectURL(this.masterPlaylistController_.mediaSource),this.tech_.src(this.mediaSourceUrl_))}},i.setupEme_=function(){var e=this,t=this.masterPlaylistController_.mediaTypes_.AUDIO.activePlaylistLoader,i=Ko({player:this.player_,sourceKeySystems:this.source_.keySystems,media:this.playlists.media(),audioMedia:t&&t.media()});this.player_.tech_.on("keystatuschange",(function(t){"output-restricted"===t.status&&e.masterPlaylistController_.blacklistCurrentPlaylist({playlist:e.masterPlaylistController_.media(),message:"DRM keystatus changed to "+t.status+". Playlist will fail to play. Check for HDCP content.",blacklistDuration:1/0})})),11!==Yr.browser.IE_VERSION&&i?(this.logger_("waiting for EME key session creation"),qo({player:this.player_,sourceKeySystems:this.source_.keySystems,audioMedia:t&&t.media(),mainPlaylists:this.playlists.master.playlists}).then((function(){e.logger_("created EME key session"),e.masterPlaylistController_.sourceUpdater_.initializedEme()})).catch((function(t){e.logger_("error while creating EME key session",t),e.player_.error({message:"Failed to initialize media keys for EME",code:3})}))):this.masterPlaylistController_.sourceUpdater_.initializedEme()},i.setupQualityLevels_=function(){var e=this,t=Yr.players[this.tech_.options_.playerId];t&&t.qualityLevels&&!this.qualityLevels_&&(this.qualityLevels_=t.qualityLevels(),this.masterPlaylistController_.on("selectedinitialmedia",(function(){var t,i;t=e.qualityLevels_,(i=e).representations().forEach((function(e){t.addQualityLevel(e)})),Yo(t,i.playlists)})),this.playlists.on("mediachange",(function(){Yo(e.qualityLevels_,e.playlists)})))},t.version=function(){return{"@videojs/http-streaming":"2.10.2","mux.js":"5.13.0","mpd-parser":"0.19.0","m3u8-parser":"4.7.0","aes-decrypter":"3.1.2"}},i.version=function(){return this.constructor.version()},i.canChangeType=function(){return yo.canChangeType()},i.play=function(){this.masterPlaylistController_.play()},i.setCurrentTime=function(e){this.masterPlaylistController_.setCurrentTime(e)},i.duration=function(){return this.masterPlaylistController_.duration()},i.seekable=function(){return this.masterPlaylistController_.seekable()},i.dispose=function(){this.playbackWatcher_&&this.playbackWatcher_.dispose(),this.masterPlaylistController_&&this.masterPlaylistController_.dispose(),this.qualityLevels_&&this.qualityLevels_.dispose(),this.player_&&(delete this.player_.vhs,delete this.player_.dash,delete this.player_.hls),this.tech_&&this.tech_.vhs&&delete this.tech_.vhs,this.tech_&&delete this.tech_.hls,this.mediaSourceUrl_&&C.default.URL.revokeObjectURL&&(C.default.URL.revokeObjectURL(this.mediaSourceUrl_),this.mediaSourceUrl_=null),e.prototype.dispose.call(this)},i.convertToProgramTime=function(e,t){return Xa({playlist:this.masterPlaylistController_.media(),time:e,callback:t})},i.seekToProgramTime=function(e,t,i,n){return void 0===i&&(i=!0),void 0===n&&(n=2),Qa({programTime:e,playlist:this.masterPlaylistController_.media(),retryCount:n,pauseAfterSeek:i,seekTo:this.options_.seekTo,tech:this.options_.tech,callback:t})},t}(Yr.getComponent("Component")),$o={name:"videojs-http-streaming",VERSION:"2.10.2",canHandleSource:function(e,t){void 0===t&&(t={});var i=Yr.mergeOptions(Yr.options,t);return $o.canPlayType(e.type,i)},handleSource:function(e,t,i){void 0===i&&(i={});var n=Yr.mergeOptions(Yr.options,i);return t.vhs=new Qo(e,t,n),Yr.hasOwnProperty("hls")||Object.defineProperty(t,"hls",{get:function(){return Yr.log.warn("player.tech().hls is deprecated. Use player.tech().vhs instead."),t.vhs},configurable:!0}),t.vhs.xhr=Na(),t.vhs.src(e.src,e.type),t.vhs},canPlayType:function(e,t){void 0===t&&(t={});var i=Yr.mergeOptions(Yr.options,t).vhs.overrideNative,n=void 0===i?!Yr.browser.IS_ANY_SAFARI:i,r=g.simpleTypeFromSourceType(e);return r&&(!Wo.supportsTypeNatively(r)||n)?"maybe":""}};_.browserSupportsCodec("avc1.4d400d,mp4a.40.2")&&Yr.getTech("Html5").registerSourceHandler($o,0),Yr.VhsHandler=Qo,Object.defineProperty(Yr,"HlsHandler",{get:function(){return Yr.log.warn("videojs.HlsHandler is deprecated. Use videojs.VhsHandler instead."),Qo},configurable:!0}),Yr.VhsSourceHandler=$o,Object.defineProperty(Yr,"HlsSourceHandler",{get:function(){return Yr.log.warn("videojs.HlsSourceHandler is deprecated. Use videojs.VhsSourceHandler instead."),$o},configurable:!0}),Yr.Vhs=Wo,Object.defineProperty(Yr,"Hls",{get:function(){return Yr.log.warn("videojs.Hls is deprecated. Use videojs.Vhs instead."),Wo},configurable:!0}),Yr.use||(Yr.registerComponent("Hls",Wo),Yr.registerComponent("Vhs",Wo)),Yr.options.vhs=Yr.options.vhs||{},Yr.options.hls=Yr.options.hls||{},Yr.registerPlugin?Yr.registerPlugin("reloadSourceOnError",Go):Yr.plugin("reloadSourceOnError",Go),t.exports=Yr},{"@babel/runtime/helpers/assertThisInitialized":1,"@babel/runtime/helpers/construct":2,"@babel/runtime/helpers/extends":3,"@babel/runtime/helpers/inherits":4,"@babel/runtime/helpers/inheritsLoose":5,"@videojs/vhs-utils/cjs/byte-helpers":9,"@videojs/vhs-utils/cjs/codecs.js":11,"@videojs/vhs-utils/cjs/containers":12,"@videojs/vhs-utils/cjs/id3-helpers":15,"@videojs/vhs-utils/cjs/media-types.js":16,"@videojs/vhs-utils/cjs/resolve-url.js":20,"@videojs/xhr":23,"global/document":33,"global/window":34,keycode:37,"m3u8-parser":38,"mpd-parser":40,"mux.js/lib/tools/parse-sidx":42,"mux.js/lib/utils/clock":43,"safe-json-parse/tuple":45,"videojs-vtt.js":48}],48:[function(e,t,i){var n=e("global/window"),r=t.exports={WebVTT:e("./vtt.js"),VTTCue:e("./vttcue.js"),VTTRegion:e("./vttregion.js")};n.vttjs=r,n.WebVTT=r.WebVTT;var a=r.VTTCue,s=r.VTTRegion,o=n.VTTCue,u=n.VTTRegion;r.shim=function(){n.VTTCue=a,n.VTTRegion=s},r.restore=function(){n.VTTCue=o,n.VTTRegion=u},n.VTTCue||r.shim()},{"./vtt.js":49,"./vttcue.js":50,"./vttregion.js":51,"global/window":34}],49:[function(e,t,i){var n=e("global/document"),r=Object.create||function(){function e(){}return function(t){if(1!==arguments.length)throw new Error("Object.create shim only accepts one parameter.");return e.prototype=t,new e}}();function a(e,t){this.name="ParsingError",this.code=e.code,this.message=t||e.message}function s(e){function t(e,t,i,n){return 3600*(0|e)+60*(0|t)+(0|i)+(0|n)/1e3}var i=e.match(/^(\d+):(\d{1,2})(:\d{1,2})?\.(\d{3})/);return i?i[3]?t(i[1],i[2],i[3].replace(":",""),i[4]):i[1]>59?t(i[1],i[2],0,i[4]):t(0,i[1],i[2],i[4]):null}function o(){this.values=r(null)}function u(e,t,i,n){var r=n?e.split(n):[e];for(var a in r)if("string"==typeof r[a]){var s=r[a].split(i);if(2===s.length)t(s[0],s[1])}}function l(e,t,i){var n=e;function r(){var t=s(e);if(null===t)throw new a(a.Errors.BadTimeStamp,"Malformed timestamp: "+n);return e=e.replace(/^[^\sa-zA-Z-]+/,""),t}function l(){e=e.replace(/^\s+/,"")}if(l(),t.startTime=r(),l(),"--\x3e"!==e.substr(0,3))throw new a(a.Errors.BadTimeStamp,"Malformed time stamp (time stamps must be separated by '--\x3e'): "+n);e=e.substr(3),l(),t.endTime=r(),l(),function(e,t){var n=new o;u(e,(function(e,t){switch(e){case"region":for(var r=i.length-1;r>=0;r--)if(i[r].id===t){n.set(e,i[r].region);break}break;case"vertical":n.alt(e,t,["rl","lr"]);break;case"line":var a=t.split(","),s=a[0];n.integer(e,s),n.percent(e,s)&&n.set("snapToLines",!1),n.alt(e,s,["auto"]),2===a.length&&n.alt("lineAlign",a[1],["start","center","end"]);break;case"position":a=t.split(","),n.percent(e,a[0]),2===a.length&&n.alt("positionAlign",a[1],["start","center","end"]);break;case"size":n.percent(e,t);break;case"align":n.alt(e,t,["start","center","end","left","right"])}}),/:/,/\s/),t.region=n.get("region",null),t.vertical=n.get("vertical","");try{t.line=n.get("line","auto")}catch(e){}t.lineAlign=n.get("lineAlign","start"),t.snapToLines=n.get("snapToLines",!0),t.size=n.get("size",100);try{t.align=n.get("align","center")}catch(e){t.align=n.get("align","middle")}try{t.position=n.get("position","auto")}catch(e){t.position=n.get("position",{start:0,left:0,center:50,middle:50,end:100,right:100},t.align)}t.positionAlign=n.get("positionAlign",{start:"start",left:"start",center:"center",middle:"center",end:"end",right:"end"},t.align)}(e,t)}a.prototype=r(Error.prototype),a.prototype.constructor=a,a.Errors={BadSignature:{code:0,message:"Malformed WebVTT signature."},BadTimeStamp:{code:1,message:"Malformed time stamp."}},o.prototype={set:function(e,t){this.get(e)||""===t||(this.values[e]=t)},get:function(e,t,i){return i?this.has(e)?this.values[e]:t[i]:this.has(e)?this.values[e]:t},has:function(e){return e in this.values},alt:function(e,t,i){for(var n=0;n=0&&t<=100)&&(this.set(e,t),!0)}};var h=n.createElement&&n.createElement("textarea"),d={c:"span",i:"i",b:"b",u:"u",ruby:"ruby",rt:"rt",v:"span",lang:"span"},c={white:"rgba(255,255,255,1)",lime:"rgba(0,255,0,1)",cyan:"rgba(0,255,255,1)",red:"rgba(255,0,0,1)",yellow:"rgba(255,255,0,1)",magenta:"rgba(255,0,255,1)",blue:"rgba(0,0,255,1)",black:"rgba(0,0,0,1)"},f={v:"title",lang:"lang"},p={rt:"ruby"};function m(e,t){function i(){if(!t)return null;var e,i=t.match(/^([^<]*)(<[^>]*>?)?/);return e=i[1]?i[1]:i[2],t=t.substr(e.length),e}function n(e,t){return!p[t.localName]||p[t.localName]===e.localName}function r(t,i){var n=d[t];if(!n)return null;var r=e.document.createElement(n),a=f[t];return a&&i&&(r[a]=i.trim()),r}for(var a,o,u=e.document.createElement("div"),l=u,m=[];null!==(a=i());)if("<"!==a[0])l.appendChild(e.document.createTextNode((o=a,h.innerHTML=o,o=h.textContent,h.textContent="",o)));else{if("/"===a[1]){m.length&&m[m.length-1]===a.substr(2).replace(">","")&&(m.pop(),l=l.parentNode);continue}var _,g=s(a.substr(1,a.length-2));if(g){_=e.document.createProcessingInstruction("timestamp",g),l.appendChild(_);continue}var v=a.match(/^<([^.\s/0-9>]+)(\.[^\s\\>]+)?([^>\\]+)?(\\?)>?$/);if(!v)continue;if(!(_=r(v[1],v[3])))continue;if(!n(l,_))continue;if(v[2]){var y=v[2].split(".");y.forEach((function(e){var t=/^bg_/.test(e),i=t?e.slice(3):e;if(c.hasOwnProperty(i)){var n=t?"background-color":"color",r=c[i];_.style[n]=r}})),_.className=y.join(" ")}m.push(v[1]),l.appendChild(_),l=_}return u}var _=[[1470,1470],[1472,1472],[1475,1475],[1478,1478],[1488,1514],[1520,1524],[1544,1544],[1547,1547],[1549,1549],[1563,1563],[1566,1610],[1645,1647],[1649,1749],[1765,1766],[1774,1775],[1786,1805],[1807,1808],[1810,1839],[1869,1957],[1969,1969],[1984,2026],[2036,2037],[2042,2042],[2048,2069],[2074,2074],[2084,2084],[2088,2088],[2096,2110],[2112,2136],[2142,2142],[2208,2208],[2210,2220],[8207,8207],[64285,64285],[64287,64296],[64298,64310],[64312,64316],[64318,64318],[64320,64321],[64323,64324],[64326,64449],[64467,64829],[64848,64911],[64914,64967],[65008,65020],[65136,65140],[65142,65276],[67584,67589],[67592,67592],[67594,67637],[67639,67640],[67644,67644],[67647,67669],[67671,67679],[67840,67867],[67872,67897],[67903,67903],[67968,68023],[68030,68031],[68096,68096],[68112,68115],[68117,68119],[68121,68147],[68160,68167],[68176,68184],[68192,68223],[68352,68405],[68416,68437],[68440,68466],[68472,68479],[68608,68680],[126464,126467],[126469,126495],[126497,126498],[126500,126500],[126503,126503],[126505,126514],[126516,126519],[126521,126521],[126523,126523],[126530,126530],[126535,126535],[126537,126537],[126539,126539],[126541,126543],[126545,126546],[126548,126548],[126551,126551],[126553,126553],[126555,126555],[126557,126557],[126559,126559],[126561,126562],[126564,126564],[126567,126570],[126572,126578],[126580,126583],[126585,126588],[126590,126590],[126592,126601],[126603,126619],[126625,126627],[126629,126633],[126635,126651],[1114109,1114109]];function g(e){for(var t=0;t<_.length;t++){var i=_[t];if(e>=i[0]&&e<=i[1])return!0}return!1}function v(e){var t=[],i="";if(!e||!e.childNodes)return"ltr";function n(e,t){for(var i=t.childNodes.length-1;i>=0;i--)e.push(t.childNodes[i])}function r(e){if(!e||!e.length)return null;var t=e.pop(),i=t.textContent||t.innerText;if(i){var a=i.match(/^.*(\n|\r)/);return a?(e.length=0,a[0]):i}return"ruby"===t.tagName?r(e):t.childNodes?(n(e,t),r(e)):void 0}for(n(t,e);i=r(t);)for(var a=0;a=0&&e.line<=100))return e.line;if(!e.track||!e.track.textTrackList||!e.track.textTrackList.mediaElement)return-1;for(var t=e.track,i=t.textTrackList,n=0,r=0;rd&&(h=h<0?-1:1,h*=Math.ceil(d/l)*l),s<0&&(h+=""===a.vertical?i.height:i.width,o=o.reverse()),r.move(c,h)}else{var f=r.lineHeight/i.height*100;switch(a.lineAlign){case"center":s-=f/2;break;case"end":s-=f}switch(a.vertical){case"":t.applyStyles({top:t.formatStyle(s,"%")});break;case"rl":t.applyStyles({left:t.formatStyle(s,"%")});break;case"lr":t.applyStyles({right:t.formatStyle(s,"%")})}o=["+y","-x","+x","-y"],r=new S(t)}var p=function(e,t){for(var r,a=new S(e),s=1,o=0;ou&&(r=new S(e),s=u),e=new S(a)}return r||a}(r,o);t.move(p.toCSSCompatValues(i))}function E(){}y.prototype.applyStyles=function(e,t){for(var i in t=t||this.div,e)e.hasOwnProperty(i)&&(t.style[i]=e[i])},y.prototype.formatStyle=function(e,t){return 0===e?0:e+t},b.prototype=r(y.prototype),b.prototype.constructor=b,S.prototype.move=function(e,t){switch(t=void 0!==t?t:this.lineHeight,e){case"+x":this.left+=t,this.right+=t;break;case"-x":this.left-=t,this.right-=t;break;case"+y":this.top+=t,this.bottom+=t;break;case"-y":this.top-=t,this.bottom-=t}},S.prototype.overlaps=function(e){return this.lefte.left&&this.tope.top},S.prototype.overlapsAny=function(e){for(var t=0;t=e.top&&this.bottom<=e.bottom&&this.left>=e.left&&this.right<=e.right},S.prototype.overlapsOppositeAxis=function(e,t){switch(t){case"+x":return this.lefte.right;case"+y":return this.tope.bottom}},S.prototype.intersectPercentage=function(e){return Math.max(0,Math.min(this.right,e.right)-Math.max(this.left,e.left))*Math.max(0,Math.min(this.bottom,e.bottom)-Math.max(this.top,e.top))/(this.height*this.width)},S.prototype.toCSSCompatValues=function(e){return{top:this.top-e.top,bottom:e.bottom-this.bottom,left:this.left-e.left,right:e.right-this.right,height:this.height,width:this.width}},S.getSimpleBoxPosition=function(e){var t=e.div?e.div.offsetHeight:e.tagName?e.offsetHeight:0,i=e.div?e.div.offsetWidth:e.tagName?e.offsetWidth:0,n=e.div?e.div.offsetTop:e.tagName?e.offsetTop:0;return{left:(e=e.div?e.div.getBoundingClientRect():e.tagName?e.getBoundingClientRect():e).left,right:e.right,top:e.top||n,height:e.height||t,bottom:e.bottom||n+(e.height||t),width:e.width||i}},E.StringDecoder=function(){return{decode:function(e){if(!e)return"";if("string"!=typeof e)throw new Error("Error - expected string data.");return decodeURIComponent(encodeURIComponent(e))}}},E.convertCueToDOMTree=function(e,t){return e&&t?m(e,t):null};E.processCues=function(e,t,i){if(!e||!t||!i)return null;for(;i.firstChild;)i.removeChild(i.firstChild);var n=e.document.createElement("div");if(n.style.position="absolute",n.style.left="0",n.style.right="0",n.style.top="0",n.style.bottom="0",n.style.margin="1.5%",i.appendChild(n),function(e){for(var t=0;t100)throw new Error("Position must be between 0 and 100.");m=e,this.hasBeenReset=!0}},positionAlign:{enumerable:!0,get:function(){return _},set:function(e){var t=a(e);t&&(_=t,this.hasBeenReset=!0)}},size:{enumerable:!0,get:function(){return g},set:function(e){if(e<0||e>100)throw new Error("Size must be between 0 and 100.");g=e,this.hasBeenReset=!0}},align:{enumerable:!0,get:function(){return v},set:function(e){var t=a(e);if(!t)throw new SyntaxError("align: an invalid or illegal alignment string was specified.");v=t,this.hasBeenReset=!0}}}),this.displayState=void 0}s.prototype.getCueAsHTML=function(){return WebVTT.convertCueToDOMTree(window,this.text)},t.exports=s},{}],51:[function(e,t,i){var n={"":!0,up:!0};function r(e){return"number"==typeof e&&e>=0&&e<=100}t.exports=function(){var e=100,t=3,i=0,a=100,s=0,o=100,u="";Object.defineProperties(this,{width:{enumerable:!0,get:function(){return e},set:function(t){if(!r(t))throw new Error("Width must be between 0 and 100.");e=t}},lines:{enumerable:!0,get:function(){return t},set:function(e){if("number"!=typeof e)throw new TypeError("Lines must be set to a number.");t=e}},regionAnchorY:{enumerable:!0,get:function(){return a},set:function(e){if(!r(e))throw new Error("RegionAnchorX must be between 0 and 100.");a=e}},regionAnchorX:{enumerable:!0,get:function(){return i},set:function(e){if(!r(e))throw new Error("RegionAnchorY must be between 0 and 100.");i=e}},viewportAnchorY:{enumerable:!0,get:function(){return o},set:function(e){if(!r(e))throw new Error("ViewportAnchorY must be between 0 and 100.");o=e}},viewportAnchorX:{enumerable:!0,get:function(){return s},set:function(e){if(!r(e))throw new Error("ViewportAnchorX must be between 0 and 100.");s=e}},scroll:{enumerable:!0,get:function(){return u},set:function(e){var t=function(e){return"string"==typeof e&&(!!n[e.toLowerCase()]&&e.toLowerCase())}(e);!1===t||(u=t)}}})}},{}],52:[function(e,t,i){"use strict";t.exports={H265WEBJS_COMPILE_MULTI_THREAD_SHAREDBUFFER:0,DEFAULT_PLAYERE_LOAD_TIMEOUT:20,DEFAILT_WEBGL_PLAY_ID:"glplayer",PLAYER_IN_TYPE_MP4:"mp4",PLAYER_IN_TYPE_FLV:"flv",PLAYER_IN_TYPE_HTTPFLV:"httpflv",PLAYER_IN_TYPE_RAW_265:"raw265",PLAYER_IN_TYPE_TS:"ts",PLAYER_IN_TYPE_MPEGTS:"mpegts",PLAYER_IN_TYPE_M3U8:"hls",PLAYER_IN_TYPE_M3U8_VOD:"m3u8",PLAYER_IN_TYPE_M3U8_LIVE:"hls",APPEND_TYPE_STREAM:0,APPEND_TYPE_FRAME:1,APPEND_TYPE_SEQUENCE:2,DEFAULT_WIDTH:600,DEFAULT_HEIGHT:600,DEFAULT_FPS:30,DEFAULT_FRAME_DUR:40,DEFAULT_FIXED:!1,DEFAULT_SAMPLERATE:44100,DEFAULT_CHANNELS:2,DEFAULT_CONSU_SAMPLE_LEN:20,PLAYER_MODE_VOD:"vod",PLAYER_MODE_NOTIME_LIVE:"live",AUDIO_MODE_ONCE:"ONCE",AUDIO_MODE_SWAP:"SWAP",DEFAULT_STRING_LIVE:"LIVE",CODEC_H265:0,CODEC_H264:1,PLAYER_CORE_TYPE_DEFAULT:0,PLAYER_CORE_TYPE_CNATIVE:1,PLAYER_CNATIVE_VOD_RETRY_MAX:7,URI_PROTOCOL_WEBSOCKET:"ws",URI_PROTOCOL_WEBSOCKET_DESC:"websocket",URI_PROTOCOL_HTTP:"http",URI_PROTOCOL_HTTP_DESC:"http",FETCH_FIRST_MAX_TIMES:5,FETCH_HTTP_FLV_TIMEOUT_MS:7e3,V_CODEC_NAME_HEVC:265,V_CODEC_NAME_AVC:264,V_CODEC_NAME_UNKN:500,A_CODEC_NAME_AAC:112,A_CODEC_NAME_MP3:113,A_CODEC_NAME_UNKN:500,CACHE_NO_LOADCACHE:1001,CACHE_WITH_PLAY_SIGN:1002,CACHE_WITH_NOPLAY_SIGN:1003,V_CODEC_AVC_DEFAULT_FPS:25}},{}],53:[function(e,t,i){"use strict";var n=window.AudioContext||window.webkitAudioContext,r=e("../consts"),a=e("./av-common");t.exports=function(){var e={options:{sampleRate:r.DEFAULT_SAMPLERATE,appendType:r.APPEND_TYPE_FRAME,playMode:r.AUDIO_MODE_SWAP},sourceChannel:-1,audioCtx:new n({latencyHint:"interactive",sampleRate:r.DEFAULT_SAMPLERATE}),gainNode:null,sourceList:[],startStatus:!1,sampleQueue:[],nextBuffer:null,playTimestamp:0,playStartTime:0,durationMs:-1,isLIVE:!1,voice:1,onLoadCache:null,resetStartParam:function(){e.playTimestamp=0,e.playStartTime=0},setOnLoadCache:function(t){e.onLoadCache=t},setDurationMs:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:-1;e.durationMs=t},setVoice:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:1;e.voice=t,e.gainNode.gain.value=t},getAlignVPTS:function(){return e.playTimestamp+(a.GetMsTime()-e.playStartTime)/1e3},swapSource:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:-1,i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:-1;if(0==e.startStatus)return null;if(t<0||t>=e.sourceList.length)return null;if(i<0||i>=e.sourceList.length)return null;try{e.sourceChannel===t&&null!==e.sourceList[t]&&(e.sourceList[t].disconnect(e.gainNode),e.sourceList[t]=null)}catch(e){console.error("[DEFINE ERROR] audioPcmModule disconnect source Index:"+t+" error happened!",e)}e.sourceChannel=i;var n=e.decodeSample(i,t);-2==n&&e.isLIVE&&(e.getAlignVPTS()>=e.durationMs/1e3-.04?e.pause():null!==e.onLoadCache&&e.onLoadCache())},addSample:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null;return!(null==t||!t||null==t)&&(0==e.sampleQueue.length&&(e.seekPos=t.pts),e.sampleQueue.push(t),e.sampleQueue.length,!0)},runNextBuffer:function(){window.setInterval((function(){if(!(null!=e.nextBuffer||e.sampleQueue.length0&&void 0!==arguments[0]?arguments[0]:-1,i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:-1;if(t<0||t>=e.sourceList.length)return-1;if(null!=e.sourceList[t]&&null!=e.sourceList[t]&&e.sourceList[t]||(e.sourceList[t]=e.audioCtx.createBufferSource(),e.sourceList[t].onended=function(){e.swapSource(t,i)}),0==e.sampleQueue.length)return e.isLIVE?(e.sourceList[t].connect(e.gainNode),e.sourceList[t].start(),e.sourceList[t].onended=function(){e.swapSource(t,i)},e.sourceList[t].stop(),0):-2;if(e.sourceList[t].buffer)return e.swapSource(t,i),0;if(null==e.nextBuffer||e.nextBuffer.data.length<1)return e.sourceList[t].connect(e.gainNode),e.sourceList[t].start(),e.sourceList[t].startState=!0,e.sourceList[t].stop(),1;var n=e.nextBuffer.data;e.playTimestamp=e.nextBuffer.pts,e.playStartTime=a.GetMsTime(),e.nextBuffer.data,e.playTimestamp;try{var r=e.audioCtx.createBuffer(1,n.length,e.options.sampleRate);r.copyToChannel(n,0),null!==e.sourceList[t]&&(e.sourceList[t].buffer=r,e.sourceList[t].connect(e.gainNode),e.sourceList[t].start(),e.sourceList[t].startState=!0)}catch(t){return e.nextBuffer=null,-3}return e.nextBuffer=null,0},decodeWholeSamples:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:-1;if(e.sourceChannel=t,t<0||t>=e.sourceList.length)return-1;if(null!=e.sourceList[t]&&null!=e.sourceList[t]&&e.sourceList[t]||(e.sourceList[t]=e.audioCtx.createBufferSource(),e.sourceList[t].onended=function(){}),0==e.sampleQueue.length)return-2;for(var i=null,n=null,a=0;a0&&void 0!==arguments[0]?arguments[0]:-1;t.durationMs=e},setVoice:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:1;t.voice=e,t.gainNode.gain.value=e},getAlignVPTS:function(){return t.playTimestamp+(a.GetMsTime()-t.playStartTime)/1e3},swapSource:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:-1,i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:-1;if(0==t.startStatus)return null;if(e<0||e>=t.sourceList.length)return null;if(i<0||i>=t.sourceList.length)return null;try{t.sourceChannel===e&&null!==t.sourceList[e]&&(t.sourceList[e].disconnect(t.gainNode),t.sourceList[e]=null)}catch(t){console.error("[DEFINE ERROR] audioModule disconnect source Index:"+e+" error happened!",t)}t.sourceChannel=i;var n=t.decodeSample(i,e);-2==n&&t.isLIVE&&(t.getAlignVPTS()>=t.durationMs/1e3-.04?t.pause():null!==t.onLoadCache&&t.onLoadCache())},addSample:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null;return!(null==e||!e||null==e)&&(0==t.sampleQueue.length&&(t.seekPos=e.pts),t.sampleQueue.push(e),!0)},runNextBuffer:function(){window.setInterval((function(){if(!(null!=t.nextBuffer||t.sampleQueue.length0&&void 0!==arguments[0]?arguments[0]:-1,i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:-1;if(e<0||e>=t.sourceList.length)return-1;if(null!=t.sourceList[e]&&null!=t.sourceList[e]&&t.sourceList[e]||(t.sourceList[e]=t.audioCtx.createBufferSource(),t.sourceList[e].onended=function(){t.swapSource(e,i)}),0==t.sampleQueue.length)return t.isLIVE?(t.sourceList[e].connect(t.gainNode),t.sourceList[e].start(),t.sourceList[e].onended=function(){t.swapSource(e,i)},t.sourceList[e].stop(),0):-2;if(t.sourceList[e].buffer)return t.swapSource(e,i),0;if(null==t.nextBuffer||t.nextBuffer.data.length<1)return t.sourceList[e].connect(t.gainNode),t.sourceList[e].start(),t.sourceList[e].startState=!0,t.sourceList[e].stop(),1;var n=t.nextBuffer.data.buffer;t.playTimestamp=t.nextBuffer.pts,t.playStartTime=a.GetMsTime();try{t.audioCtx.decodeAudioData(n,(function(i){null!==t.sourceList[e]&&(t.sourceList[e].buffer=i,t.sourceList[e].connect(t.gainNode),t.sourceList[e].start(),t.sourceList[e].startState=!0)}),(function(e){}))}catch(e){return t.nextBuffer=null,-3}return t.nextBuffer=null,0},decodeWholeSamples:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:-1;if(t.sourceChannel=e,e<0||e>=t.sourceList.length)return-1;if(null!=t.sourceList[e]&&null!=t.sourceList[e]&&t.sourceList[e]||(t.sourceList[e]=t.audioCtx.createBufferSource(),t.sourceList[e].onended=function(){}),0==t.sampleQueue.length)return-2;for(var i=null,n=null,a=0;a=2){var s=i.length/2;a=new Float32Array(s);for(var o=0,u=0;uthis._push_start_idx))return-1;this.playStartTime<0&&(this.playStartTime=a.GetMsTime(),this.playTimestamp=a.GetMsTime()),this._swapStartPlay=!1;var e=this._push_start_idx+this._once_pop_len;e>this._pcm_array_buf.length&&(e=this._pcm_array_buf.length);var t=this._pcm_array_buf.slice(this._push_start_idx,e);this._push_start_idx+=t.length,this._now_seg_dur=1*t.length/this._sample_rate*1e3,t.length,this._sample_rate,this._now_seg_dur;var i=this._ctx.createBuffer(1,t.length,this._sample_rate);return t.length,new Date,i.copyToChannel(t,0),this._active_node=this._ctx.createBufferSource(),this._active_node.buffer=i,this._active_node.connect(this._gain),this.playStartTime=a.GetMsTime(),this._active_node.start(0),this.playTimestamp+=this._now_seg_dur,0}},{key:"getAlignVPTS",value:function(){return this.playTimestamp}},{key:"pause",value:function(){null!==this._playInterval&&(window.clearInterval(this._playInterval),this._playInterval=null)}},{key:"play",value:function(){var e=this;this._playInterval=window.setInterval((function(){e.readingLoopWithF32()}),10)}}])&&n(t.prototype,i),s&&n(t,s),e}();i.AudioPcmPlayer=s},{"../consts":52,"./av-common":56}],56:[function(e,t,i){"use strict";var n=e("../consts"),r=[{format:"mp4",value:"mp4",core:n.PLAYER_CORE_TYPE_CNATIVE},{format:"mov",value:"mp4",core:n.PLAYER_CORE_TYPE_CNATIVE},{format:"mkv",value:"mp4",core:n.PLAYER_CORE_TYPE_CNATIVE},{format:"flv",value:"flv",core:n.PLAYER_CORE_TYPE_CNATIVE},{format:"m3u8",value:"hls",core:n.PLAYER_CORE_TYPE_DEFAULT},{format:"m3u",value:"hls",core:n.PLAYER_CORE_TYPE_DEFAULT},{format:"ts",value:"ts",core:n.PLAYER_CORE_TYPE_DEFAULT},{format:"ps",value:"ts",core:n.PLAYER_CORE_TYPE_DEFAULT},{format:"mpegts",value:"ts",core:n.PLAYER_CORE_TYPE_DEFAULT},{format:"hevc",value:"raw265",core:n.PLAYER_CORE_TYPE_DEFAULT},{format:"h265",value:"raw265",core:n.PLAYER_CORE_TYPE_DEFAULT},{format:"265",value:"raw265",core:n.PLAYER_CORE_TYPE_DEFAULT}],a=[{format:n.URI_PROTOCOL_HTTP,value:n.URI_PROTOCOL_HTTP_DESC},{format:n.URI_PROTOCOL_WEBSOCKET,value:n.URI_PROTOCOL_WEBSOCKET_DESC}];t.exports={frameDataAlignCrop:function(e,t,i,n,r,a,s,o){if(0==e-n)return[a,s,o];for(var u=n*r,l=u/4,h=new Uint8Array(u),d=new Uint8Array(l),c=new Uint8Array(l),f=n,p=n/2,m=0;m=0)return i.value}return r[0].value},GetFormatPlayCore:function(e){if(null!=e)for(var t=0;t=0)return i.value}return a[0].value},GetMsTime:function(){return(new Date).getTime()},GetScriptPath:function(e){var t=e.toString(),i=t.match(/^\s*function\s*\(\s*\)\s*\{(([\s\S](?!\}$))*[\s\S])/),n=[i[1]];return window.URL.createObjectURL(new Blob(n,{type:"text/javascript"}))},BrowserJudge:function(){var e=window.document,t=window.navigator.userAgent.toLowerCase(),i=e.documentMode,n=window.chrome||!1,r={agent:t,isIE:/msie/.test(t),isGecko:t.indexOf("gecko")>0&&t.indexOf("like gecko")<0,isWebkit:t.indexOf("webkit")>0,isStrict:"CSS1Compat"===e.compatMode,supportSubTitle:function(){return"track"in e.createElement("track")},supportScope:function(){return"scoped"in e.createElement("style")},ieVersion:function(){try{return t.match(/msie ([\d.]+)/)[1]||0}catch(e){return i}},operaVersion:function(){try{if(window.opera)return t.match(/opera.([\d.]+)/)[1];if(t.indexOf("opr")>0)return t.match(/opr\/([\d.]+)/)[1]}catch(e){return 0}},versionFilter:function(){if(1===arguments.length&&"string"==typeof arguments[0]){var e=arguments[0],t=e.indexOf(".");if(t>0){var i=e.indexOf(".",t+1);if(-1!==i)return e.substr(0,i)}return e}return 1===arguments.length?arguments[0]:0}};try{r.type=r.isIE?"IE":window.opera||t.indexOf("opr")>0?"Opera":t.indexOf("chrome")>0?"Chrome":t.indexOf("safari")>0||window.openDatabase?"Safari":t.indexOf("firefox")>0?"Firefox":"unknow",r.version="IE"===r.type?r.ieVersion():"Firefox"===r.type?t.match(/firefox\/([\d.]+)/)[1]:"Chrome"===r.type?t.match(/chrome\/([\d.]+)/)[1]:"Opera"===r.type?r.operaVersion():"Safari"===r.type?t.match(/version\/([\d.]+)/)[1]:"0",r.shell=function(){if(t.indexOf("maxthon")>0)return r.version=t.match(/maxthon\/([\d.]+)/)[1]||r.version,"傲游浏览器";if(t.indexOf("qqbrowser")>0)return r.version=t.match(/qqbrowser\/([\d.]+)/)[1]||r.version,"QQ浏览器";if(t.indexOf("se 2.x")>0)return"搜狗浏览器";if(n&&"Opera"!==r.type){var e=window.external,i=window.clientInformation.languages;if(e&&"LiebaoGetVersion"in e)return"猎豹浏览器";if(t.indexOf("bidubrowser")>0)return r.version=t.match(/bidubrowser\/([\d.]+)/)[1]||t.match(/chrome\/([\d.]+)/)[1],"百度浏览器";if(r.supportSubTitle()&&void 0===i){var a=Object.keys(n.webstore).length;window;return a>1?"360极速浏览器":"360安全浏览器"}return"Chrome"}return r.type},r.name=r.shell(),r.version=r.versionFilter(r.version)}catch(e){}return[r.type,r.version]},ParseGetMediaURL:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"http";if("http"!==t&&"ws"!==t&&"wss"!==t&&(e.indexOf("ws")>=0||e.indexOf("wss")>=0)&&(t="ws"),"ws"===t||"wss"===t)return e;var i=e;if(e.indexOf(t)>=0)i=e;else if("/"===e[0])i="/"===e[1]?t+":"+e:window.location.origin+e;else if(":"===e[0])i=t+e;else{var n=window.location.href.split("/");i=window.location.href.replace(n[n.length-1],e)}return i},IsSupport265Mse:function(){return MediaSource.isTypeSupported('video/mp4;codecs=hvc1.1.1.L63.B0"')}}},{"../consts":52}],57:[function(e,t,i){"use strict";function n(e,t){for(var i=0;i0&&a.GetMsTime()-t.getPackageTimeMS>=o.FETCH_HTTP_FLV_TIMEOUT_MS&&(t.getPackageTimeMS=a.GetMsTime(),t.workerFetch.postMessage({cmd:"retry",data:null,msg:"retry"}))}),5));break;case"fetch-chunk":var n=i.data;t.download_length+=n.length,setTimeout((function(){var e=Module._malloc(n.length);Module.HEAP8.set(n,e),Module.cwrap("pushSniffG711FlvData","number",["number","number","number","number"])(t.corePtr,e,n.length,t.config.probeSize),Module._free(e),e=null}),0),t.totalLen+=n.length,n.length>0&&(t.getPackageTimeMS=a.GetMsTime()),t.pushPkg++;break;case"close":t.AVGetInterval&&clearInterval(t.AVGetInterval),t.AVGetInterval=null;case"fetch-fin":break;case"fetch-error":t.onError&&t.onError(i.data)}}},{key:"_checkDisplaySize",value:function(e,t,i){var n=t-e,r=this.config.width+Math.ceil(n/2),a=t/this.config.width>i/this.config.height,s=(r/t).toFixed(2),o=(this.config.height/i).toFixed(2),u=a?s:o,l=this.config.fixed,h=l?r:parseInt(t*u),d=l?this.config.height:parseInt(i*u);if(this.CanvasObj.offsetWidth!=h||this.CanvasObj.offsetHeight!=d){var c=parseInt((this.canvasBox.offsetHeight-d)/2),f=parseInt((this.canvasBox.offsetWidth-h)/2);c=c<0?0:c,f=f<0?0:f,this.CanvasObj.style.marginTop=c+"px",this.CanvasObj.style.marginLeft=f+"px",this.CanvasObj.style.width=h+"px",this.CanvasObj.style.height=d+"px"}return this.isCheckDisplay=!0,[h,d]}},{key:"_ptsFixed2",value:function(e){return Math.ceil(100*e)/100}},{key:"_reinitAudioModule",value:function(){void 0!==this.audioWAudio&&null!==this.audioWAudio&&(this.audioWAudio.stop(),this.audioWAudio=null),this.audioWAudio=s()}},{key:"_callbackProbe",value:function(e,t,i,n,r,a,s,u,l){for(var h=Module.HEAPU8.subarray(l,l+10),d=0;d100&&(c=o.DEFAULT_FPS,this.mediaInfo.noFPS=!0),this.vCodecID=u,this.config.fps=c,this.mediaInfo.fps=c,this.mediaInfo.size.width=t,this.mediaInfo.size.height=i,this.frameTime=Math.floor(1e3/(this.mediaInfo.fps+2)),this.CanvasObj.width==t&&this.CanvasObj.height==i||(this.CanvasObj.width=t,this.CanvasObj.height=i,this.isCheckDisplay)||this._checkDisplaySize(t,t,i),r>=0&&!1===this.mediaInfo.noFPS?(this.config.sampleRate=a,this.mediaInfo.sampleRate=a,!1===this.muted&&this._reinitAudioModule(this.mediaInfo.sampleRate)):this.mediaInfo.audioNone=!0,this.onProbeFinish&&this.onProbeFinish()}},{key:"_callbackYUV",value:function(e,t,i,n,r,a,s,o,u,l){var h=this,d=Module.HEAPU8.subarray(e,e+n*o),c=new Uint8Array(d),f=Module.HEAPU8.subarray(t,t+r*o/2),p=new Uint8Array(f),m=Module.HEAPU8.subarray(i,i+a*o/2),_={bufY:c,bufU:p,bufV:new Uint8Array(m),line_y:n,h:o,pts:u};this.YuvBuf.push(_),this.checkCacheState(),Module._free(d),d=null,Module._free(f),f=null,Module._free(m),m=null,!1===this.readyShowDone&&!0===this.playYUV()&&(this.readyShowDone=!0,this.onReadyShowDone&&this.onReadyShowDone(),this.audioWAudio||!0!==this.config.autoPlay||(this.play(),setTimeout((function(){h.isPlayingState()}),3e3)))}},{key:"_callbackNALU",value:function(e,t,i,n,r,a,s){if(!1===this.readyKeyFrame){if(i<=0)return;this.readyKeyFrame=!0}var o=Module.HEAPU8.subarray(e,e+t),u=new Uint8Array(o);this.NaluBuf.push({bufData:u,len:t,isKey:i,w:n,h:r,pts:1e3*a,dts:1e3*s}),Module._free(o),o=null}},{key:"_callbackPCM",value:function(e,t,i,n){var r=Module.HEAPU8.subarray(e,e+t),a=new Uint8Array(r).buffer,s=this._ptsFixed2(i),o=null,u=a.byteLength%4;if(0!==u){var l=new Uint8Array(a.byteLength+u);l.set(new Uint8Array(a),0),o=new Float32Array(l.buffer)}else o=new Float32Array(a);var h={pts:s,data:o};this.audioWAudio.addSample(h),this.checkCacheState()}},{key:"_decode",value:function(){var e=this;setTimeout((function(){null!==e.workerFetch&&(Module.cwrap("decodeG711Frame","number",["number"])(e.corePtr),e._decode())}),1)}},{key:"setScreen",value:function(){var e=arguments.length>0&&void 0!==arguments[0]&&arguments[0];this.showScreen=e}},{key:"checkCacheState",value:function(){var e=this.YuvBuf.length>=25&&(!0===this.mediaInfo.audioNone||this.audioWAudio&&this.audioWAudio.sampleQueue.length>=50);return!1===this.cache_status&&e&&(this.playInterval&&this.audioWAudio&&this.audioWAudio.play(),this.onLoadCacheFinshed&&this.onLoadCacheFinshed(),this.cache_status=!0),e}},{key:"setVoice",value:function(e){this.audioVoice=e,this.audioWAudio&&this.audioWAudio.setVoice(e)}},{key:"_removeBindFuncPtr",value:function(){null!==this._ptr_probeCallback&&Module.removeFunction(this._ptr_probeCallback),null!==this._ptr_frameCallback&&Module.removeFunction(this._ptr_frameCallback),null!==this._ptr_naluCallback&&Module.removeFunction(this._ptr_naluCallback),null!==this._ptr_sampleCallback&&Module.removeFunction(this._ptr_sampleCallback),null!==this._ptr_aacCallback&&Module.removeFunction(this._ptr_aacCallback),this._ptr_probeCallback=null,this._ptr_frameCallback=null,this._ptr_naluCallback=null,this._ptr_sampleCallback=null,this._ptr_aacCallback=null}},{key:"release",value:function(){return this.pause(),this.NaluBuf.length=0,this.YuvBuf.length=0,void 0!==this.workerFetch&&null!==this.workerFetch&&this.workerFetch.postMessage({cmd:"stop",data:"stop",msg:"stop"}),this.workerFetch=null,this.AVGetInterval&&clearInterval(this.AVGetInterval),this.AVGetInterval=null,this._removeBindFuncPtr(),void 0!==this.corePtr&&null!==this.corePtr&&Module.cwrap("releaseG711","number",["number"])(this.corePtr),this.playInterval&&clearInterval(this.playInterval),this.playInterval=null,this.audioWAudio&&this.audioWAudio.stop(),this.audioWAudio=null,void 0!==this.AVGLObj&&null!==this.AVGLObj&&(r.releaseContext(this.AVGLObj),this.AVGLObj=null),this.CanvasObj&&this.CanvasObj.remove(),this.CanvasObj=null,window.onclick=document.body.onclick=null,delete window.g_players[this.corePtr],0}},{key:"isPlayingState",value:function(){return null!==this.playInterval&&void 0!==this.playInterval}},{key:"pause",value:function(){this.audioWAudio&&this.audioWAudio.pause(),this.playInterval&&clearInterval(this.playInterval),this.playInterval=null,this.onPlayState&&this.onPlayState(this.isPlayingState())}},{key:"playYUV",value:function(){if(this.YuvBuf.length>0){var e=this.YuvBuf.shift();return e.pts,this.onRender&&this.onRender(e.line_y,e.h,e.bufY,e.bufU,e.bufV),r.renderFrame(this.AVGLObj,e.bufY,e.bufU,e.bufV,e.line_y,e.h),!0}return!1}},{key:"play",value:function(){var e=this;if(!1===this.checkCacheState())return this.onLoadCache&&this.onLoadCache(),setTimeout((function(){e.play()}),100),!1;var t=1*e.frameTime;if(void 0===this.playInterval||null===this.playInterval){var i=0,n=0,s=0;!1===this.mediaInfo.audioNone&&this.audioWAudio&&!1===this.mediaInfo.noFPS?(this.playInterval=setInterval((function(){if(n=a.GetMsTime(),e.cache_status){if(n-i>=e.frameTime-s){var o=e.YuvBuf.shift();if(null!=o&&null!==o){o.pts;var u=0;null!==e.audioWAudio&&void 0!==e.audioWAudio?(u=1e3*(o.pts-e.audioWAudio.getAlignVPTS()),s=u<0&&-1*u<=t||u>0&&u<=t||0===u||u>0&&u>t?a.GetMsTime()-n+1:e.frameTime):s=a.GetMsTime()-n+1,e.showScreen&&e.onRender&&e.onRender(o.line_y,o.h,o.bufY,o.bufU,o.bufV),o.pts,r.renderFrame(e.AVGLObj,o.bufY,o.bufU,o.bufV,o.line_y,o.h)}e.YuvBuf.length<=0&&(e.cache_status=!1,e.onLoadCache&&e.onLoadCache(),e.audioWAudio&&e.audioWAudio.pause()),i=n}}else s=e.frameTime}),1),this.audioWAudio&&this.audioWAudio.play()):this.playInterval=setInterval((function(){var t=e.YuvBuf.shift();null!=t&&null!==t&&(t.pts,e.showScreen&&e.onRender&&e.onRender(t.line_y,t.h,t.bufY,t.bufU,t.bufV),r.renderFrame(e.AVGLObj,t.bufY,t.bufU,t.bufV,t.line_y,t.h)),e.YuvBuf.length<=0&&(e.cache_status=!1)}),e.frameTime)}this.onPlayState&&this.onPlayState(this.isPlayingState())}},{key:"start",value:function(e){var t=this;this.workerFetch=new Worker(a.GetScriptPath((function(){var e=null,t=new AbortController,i=t.signal,n=(self,function(e){var t=!1;t||(t=!0,fetch(e,{signal:i}).then((function(e){return function e(t){return t.read().then((function(i){if(!i.done){var n=i.value;return self.postMessage({cmd:"fetch-chunk",data:n,msg:"fetch-chunk"}),e(t)}self.postMessage({cmd:"fetch-fin",data:null,msg:"fetch-fin"})}))}(e.body.getReader())})).catch((function(e){if(!e.toString().includes("user aborted")){var t=" httplive request error:"+e+" start to retry";console.error(t),self.postMessage({cmd:"fetch-error",data:t,msg:"fetch-error"})}})))});self.onmessage=function(r){var a=r.data;switch(void 0===a.cmd||null===a.cmd?"":a.cmd){case"start":e=a.data,n(e),self.postMessage({cmd:"startok",data:"WORKER STARTED",msg:"startok"});break;case"stop":t.abort(),self.close(),self.postMessage({cmd:"close",data:"close",msg:"close"});break;case"retry":t.abort(),t=null,i=null,t=new AbortController,i=t.signal,setTimeout((function(){n(e)}),3e3)}}}))),this.workerFetch.onmessage=function(e){t._workerFetch_onmessage(e,t)},this.workerFetch,this._ptr_probeCallback=Module.addFunction(this._callbackProbe.bind(this)),this._ptr_yuvCallback=Module.addFunction(this._callbackYUV.bind(this)),this._ptr_sampleCallback=Module.addFunction(this._callbackPCM.bind(this)),Module.cwrap("initializeSniffG711Module","number",["number","number","number","number","number","number"])(this.corePtr,this._ptr_probeCallback,this._ptr_yuvCallback,this._ptr_sampleCallback,0,1),this.AVGLObj=r.setupCanvas(this.CanvasObj,{preserveDrawingBuffer:!1}),this.workerFetch.postMessage({cmd:"start",data:e,msg:"start"}),0===o.H265WEBJS_COMPILE_MULTI_THREAD_SHAREDBUFFER&&this._decode()}}])&&n(t.prototype,i),u&&n(t,u),e}());i.CHttpG711Core=u},{"../consts":52,"../demuxer/buffer":66,"../demuxer/bufferFrame":67,"../render-engine/webgl-420p":81,"../version":84,"./audio-core":54,"./audio-core-pcm":53,"./audio-native-core":55,"./av-common":56,"./cache":61,"./cacheYuv":62}],58:[function(e,t,i){"use strict";function n(e,t){for(var i=0;it.config.probeSize?(Module.cwrap("getSniffHttpFlvPkg","number",["number"])(t.corePtr),t.pushPkg-=1):t.getPackageTimeMS>0&&a.GetMsTime()-t.getPackageTimeMS>=o.FETCH_HTTP_FLV_TIMEOUT_MS&&(t.getPackageTimeMS=a.GetMsTime(),t.workerFetch.postMessage({cmd:"retry",data:null,msg:"retry"}))}),5));break;case"fetch-chunk":var n=i.data;t.download_length+=n.length,setTimeout((function(){var e=Module._malloc(n.length);Module.HEAP8.set(n,e),Module.cwrap("pushSniffHttpFlvData","number",["number","number","number","number"])(t.corePtr,e,n.length,t.config.probeSize),Module._free(e),e=null}),0),t.totalLen+=n.length,n.length>0&&(t.getPackageTimeMS=a.GetMsTime()),t.pushPkg++;break;case"close":t.AVGetInterval&&clearInterval(t.AVGetInterval),t.AVGetInterval=null;break;case"fetch-fin":break;case"fetch-error":t.onError&&t.onError(i.data)}}},{key:"_checkDisplaySize",value:function(e,t,i){var n=t-e,r=this.config.width+Math.ceil(n/2),a=t/this.config.width>i/this.config.height,s=(r/t).toFixed(2),o=(this.config.height/i).toFixed(2),u=a?s:o,l=this.config.fixed,h=l?r:parseInt(t*u),d=l?this.config.height:parseInt(i*u);if(this.CanvasObj.offsetWidth!=h||this.CanvasObj.offsetHeight!=d){var c=parseInt((this.canvasBox.offsetHeight-d)/2),f=parseInt((this.canvasBox.offsetWidth-h)/2);c=c<0?0:c,f=f<0?0:f,this.CanvasObj.style.marginTop=c+"px",this.CanvasObj.style.marginLeft=f+"px",this.CanvasObj.style.width=h+"px",this.CanvasObj.style.height=d+"px"}return this.isCheckDisplay=!0,[h,d]}},{key:"_ptsFixed2",value:function(e){return Math.ceil(100*e)/100}},{key:"_reinitAudioModule",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:44100;this.config.ignoreAudio>0||(void 0!==this.audioWAudio&&null!==this.audioWAudio&&(this.audioWAudio.stop(),this.audioWAudio=null),this.audioWAudio=s({sampleRate:e,appendType:o.APPEND_TYPE_FRAME}),this.audioWAudio.isLIVE=!0)}},{key:"_callbackProbe",value:function(e,t,i,n,r,a,s,u,l){var h=arguments.length>9&&void 0!==arguments[9]?arguments[9]:0;if(1!==h){for(var d=Module.HEAPU8.subarray(l,l+10),c=0;c100&&(f=o.DEFAULT_FPS,this.mediaInfo.noFPS=!0),this.vCodecID=u,this.config.fps=f,this.mediaInfo.fps=f,this.mediaInfo.size.width=t,this.mediaInfo.size.height=i,this.frameTime=Math.floor(1e3/(this.mediaInfo.fps+5)),this.chaseFrame=0,this.CanvasObj.width==t&&this.CanvasObj.height==i||(this.CanvasObj.width=t,this.CanvasObj.height=i,this.isCheckDisplay)||this._checkDisplaySize(t,t,i),r>=0&&!1===this.mediaInfo.noFPS?(this.config.sampleRate=a,this.mediaInfo.sampleRate=a,this.config.ignoreAudio<1&&!1===this.muted&&this._reinitAudioModule(this.mediaInfo.sampleRate)):this.mediaInfo.audioNone=!0,this.onProbeFinish&&this.onProbeFinish()}else this.onProbeFinish&&this.onProbeFinish(h)}},{key:"_callbackYUV",value:function(e,t,i,n,r,a,s,o,u,l){var h=this,d=Module.HEAPU8.subarray(e,e+n*o),c=new Uint8Array(d),f=Module.HEAPU8.subarray(t,t+r*o/2),p=new Uint8Array(f),m=Module.HEAPU8.subarray(i,i+a*o/2),_={bufY:c,bufU:p,bufV:new Uint8Array(m),line_y:n,h:o,pts:u};this.YuvBuf.push(_),this.YuvBuf.length,this.checkCacheState(),Module._free(d),d=null,Module._free(f),f=null,Module._free(m),m=null,!1===this.readyShowDone&&!0===this.playYUV()&&(this.readyShowDone=!0,this.onReadyShowDone&&this.onReadyShowDone(),this.audioWAudio||!0!==this.config.autoPlay||(this.play(),setTimeout((function(){h.isPlayingState()}),3e3)))}},{key:"_callbackNALU",value:function(e,t,i,n,r,a,s){if(!1===this.readyKeyFrame){if(i<=0)return;this.readyKeyFrame=!0}var o=Module.HEAPU8.subarray(e,e+t),u=new Uint8Array(o);this.NaluBuf.push({bufData:u,len:t,isKey:i,w:n,h:r,pts:1e3*a,dts:1e3*s}),Module._free(o),o=null}},{key:"_callbackPCM",value:function(e){this.config.ignoreAudio}},{key:"_callbackAAC",value:function(e,t,i,n){if(!(this.config.ignoreAudio>0)){var r=this._ptsFixed2(n);if(this.audioWAudio&&!1===this.muted){var a=Module.HEAPU8.subarray(e,e+t),s={pts:r,data:new Uint8Array(a)};this.audioWAudio.addSample(s),this.checkCacheState()}}}},{key:"_decode",value:function(){var e=this;setTimeout((function(){if(null!==e.workerFetch){var t=e.NaluBuf.shift();if(null!=t){var i=Module._malloc(t.bufData.length);Module.HEAP8.set(t.bufData,i),Module.cwrap("decodeHttpFlvVideoFrame","number",["number","number","number","number","number"])(e.corePtr,i,t.bufData.length,t.pts,t.dts,0),Module._free(i),i=null}e._decode()}}),1)}},{key:"setScreen",value:function(){var e=arguments.length>0&&void 0!==arguments[0]&&arguments[0];this.showScreen=e}},{key:"checkCacheState",value:function(){this.YuvBuf.length,this.config.ignoreAudio>0||!0===this.mediaInfo.audioNone||this.audioWAudio&&this.audioWAudio.sampleQueue.length;var e=this.YuvBuf.length>=25&&(!0===this.muted||this.config.ignoreAudio>0||!0===this.mediaInfo.audioNone||this.audioWAudio&&this.audioWAudio.sampleQueue.length>=50);return!1===this.cache_status&&e&&(this.playInterval&&this.audioWAudio&&this.audioWAudio.play(),this.onLoadCacheFinshed&&this.onLoadCacheFinshed(),this.cache_status=!0),e}},{key:"setVoice",value:function(e){this.config.ignoreAudio<1&&(this.audioVoice=e,this.audioWAudio&&this.audioWAudio.setVoice(e))}},{key:"_removeBindFuncPtr",value:function(){null!==this._ptr_probeCallback&&Module.removeFunction(this._ptr_probeCallback),null!==this._ptr_frameCallback&&Module.removeFunction(this._ptr_frameCallback),null!==this._ptr_naluCallback&&Module.removeFunction(this._ptr_naluCallback),null!==this._ptr_sampleCallback&&Module.removeFunction(this._ptr_sampleCallback),null!==this._ptr_aacCallback&&Module.removeFunction(this._ptr_aacCallback),this._ptr_probeCallback=null,this._ptr_frameCallback=null,this._ptr_naluCallback=null,this._ptr_sampleCallback=null,this._ptr_aacCallback=null}},{key:"release",value:function(){return this.pause(),this.NaluBuf.length=0,this.YuvBuf.length=0,void 0!==this.workerFetch&&null!==this.workerFetch&&this.workerFetch.postMessage({cmd:"stop",data:"stop",msg:"stop"}),this.workerFetch=null,this.AVGetInterval&&clearInterval(this.AVGetInterval),this.AVGetInterval=null,this._removeBindFuncPtr(),void 0!==this.corePtr&&null!==this.corePtr&&Module.cwrap("releaseHttpFLV","number",["number"])(this.corePtr),this.playInterval&&clearInterval(this.playInterval),this.playInterval=null,this.audioWAudio&&this.audioWAudio.stop(),this.audioWAudio=null,void 0!==this.AVGLObj&&null!==this.AVGLObj&&(r.releaseContext(this.AVGLObj),this.AVGLObj=null),this.CanvasObj&&this.CanvasObj.remove(),this.CanvasObj=null,window.onclick=document.body.onclick=null,delete window.g_players[this.corePtr],0}},{key:"isPlayingState",value:function(){return null!==this.playInterval&&void 0!==this.playInterval}},{key:"pause",value:function(){this.config.ignoreAudio,this.audioWAudio,this.config.ignoreAudio<1&&this.audioWAudio&&this.audioWAudio.pause(),this.playInterval&&clearInterval(this.playInterval),this.playInterval=null,this.chaseFrame=0,this.onPlayState&&this.onPlayState(this.isPlayingState())}},{key:"playYUV",value:function(){if(this.YuvBuf.length>0){var e=this.YuvBuf.shift();return this.onRender&&this.onRender(e.line_y,e.h,e.bufY,e.bufU,e.bufV),r.renderFrame(this.AVGLObj,e.bufY,e.bufU,e.bufV,e.line_y,e.h),!0}return!1}},{key:"play",value:function(){var e=this,t=this;if(this.chaseFrame=0,!1===this.checkCacheState())return this.onLoadCache&&this.onLoadCache(),setTimeout((function(){e.play()}),100),!1;var i=1*t.frameTime;if(void 0===this.playInterval||null===this.playInterval){var n=0,s=0,o=0;if(this.config.ignoreAudio<1&&!1===this.mediaInfo.audioNone&&null!=this.audioWAudio&&!1===this.mediaInfo.noFPS)this.config.ignoreAudio,this.mediaInfo.audioNone,this.audioWAudio,this.mediaInfo.noFPS,this.playInterval=setInterval((function(){if(s=a.GetMsTime(),t.cache_status){if(s-n>=t.frameTime-o){var e=t.YuvBuf.shift();if(e.pts,t.YuvBuf.length,null!=e&&null!==e){var u=0;null!==t.audioWAudio&&void 0!==t.audioWAudio?(u=1e3*(e.pts-t.audioWAudio.getAlignVPTS()),o=u<0&&-1*u<=i||u>0&&u<=i||0===u||u>0&&u>i?a.GetMsTime()-s+1:t.frameTime):o=a.GetMsTime()-s+1,t.showScreen&&t.onRender&&t.onRender(e.line_y,e.h,e.bufY,e.bufU,e.bufV),e.pts,r.renderFrame(t.AVGLObj,e.bufY,e.bufU,e.bufV,e.line_y,e.h)}(t.YuvBuf.length<=0||t.audioWAudio&&t.audioWAudio.sampleQueue.length<=0)&&(t.cache_status=!1,t.onLoadCache&&t.onLoadCache(),t.audioWAudio&&t.audioWAudio.pause()),n=s}}else o=t.frameTime}),1),this.audioWAudio&&this.audioWAudio.play();else{var u=-1;this.playInterval=setInterval((function(){if(s=a.GetMsTime(),t.cache_status){t.YuvBuf.length,t.frameTime,t.frameTime,t.chaseFrame;var e=-1;if(u>0&&(e=s-n,t.frameTime,t.chaseFrame<=0&&o>0&&(t.chaseFrame=Math.floor(o/t.frameTime),t.chaseFrame)),u<=0||e>=t.frameTime||t.chaseFrame>0){u=1;var i=t.YuvBuf.shift();i.pts,t.YuvBuf.length,null!=i&&null!==i&&(t.showScreen&&t.onRender&&t.onRender(i.line_y,i.h,i.bufY,i.bufU,i.bufV),i.pts,r.renderFrame(t.AVGLObj,i.bufY,i.bufU,i.bufV,i.line_y,i.h),o=a.GetMsTime()-s+1),t.YuvBuf.length<=0&&(t.cache_status=!1,t.onLoadCache&&t.onLoadCache()),n=s,t.chaseFrame>0&&(t.chaseFrame--,0===t.chaseFrame&&(o=t.frameTime))}}else o=t.frameTime,u=-1,t.chaseFrame=0,n=0,s=0,o=0}),1)}}this.onPlayState&&this.onPlayState(this.isPlayingState())}},{key:"start",value:function(e){var t=this;this.workerFetch=new Worker(a.GetScriptPath((function(){var e=null,t=new AbortController,i=t.signal,n=(self,function(e){var t=!1;t||(t=!0,fetch(e,{signal:i}).then((function(e){return function e(t){return t.read().then((function(i){if(!i.done){var n=i.value;return self.postMessage({cmd:"fetch-chunk",data:n,msg:"fetch-chunk"}),e(t)}self.postMessage({cmd:"fetch-fin",data:null,msg:"fetch-fin"})}))}(e.body.getReader())})).catch((function(e){if(!e.toString().includes("user aborted")){var t=" httplive request error:"+e+" start to retry";console.error(t),self.postMessage({cmd:"fetch-error",data:t,msg:"fetch-error"})}})))});self.onmessage=function(r){var a=r.data;switch(void 0===a.cmd||null===a.cmd?"":a.cmd){case"start":e=a.data,n(e),self.postMessage({cmd:"startok",data:"WORKER STARTED",msg:"startok"});break;case"stop":t.abort(),self.close(),self.postMessage({cmd:"close",data:"close",msg:"close"});break;case"retry":t.abort(),t=null,i=null,t=new AbortController,i=t.signal,setTimeout((function(){n(e)}),3e3)}}}))),this.workerFetch.onmessage=function(e){t._workerFetch_onmessage(e,t)},this.workerFetch,this._ptr_probeCallback=Module.addFunction(this._callbackProbe.bind(this)),this._ptr_yuvCallback=Module.addFunction(this._callbackYUV.bind(this)),this._ptr_naluCallback=Module.addFunction(this._callbackNALU.bind(this)),this._ptr_sampleCallback=Module.addFunction(this._callbackPCM.bind(this)),this._ptr_aacCallback=Module.addFunction(this._callbackAAC.bind(this)),Module.cwrap("initializeSniffHttpFlvModule","number",["number","number","number","number","number","number","number"])(this.corePtr,this._ptr_probeCallback,this._ptr_yuvCallback,this._ptr_naluCallback,this._ptr_sampleCallback,this._ptr_aacCallback,this.config.ignoreAudio),this.AVGLObj=r.setupCanvas(this.CanvasObj,{preserveDrawingBuffer:!1}),this.workerFetch.postMessage({cmd:"start",data:e,msg:"start"}),this._decode()}}])&&n(t.prototype,i),u&&n(t,u),e}());i.CHttpLiveCore=u},{"../consts":52,"../demuxer/buffer":66,"../demuxer/bufferFrame":67,"../render-engine/webgl-420p":81,"../version":84,"./audio-core":54,"./audio-native-core":55,"./av-common":56,"./cache":61,"./cacheYuv":62}],59:[function(e,t,i){"use strict";function n(e,t){for(var i=0;i0&&void 0!==arguments[0]&&arguments[0];this.showScreen=e}},{key:"getCachePTS",value:function(){return 1!==this.config.ignoreAudio&&this.audioWAudio?Math.max(this.vCachePTS,this.aCachePTS):this.vCachePTS}},{key:"getMaxPTS",value:function(){return Math.max(this.vCachePTS,this.aCachePTS)}},{key:"isPlayingState",value:function(){return this.isPlaying}},{key:"_clearDecInterval",value:function(){this.decVFrameInterval&&window.clearInterval(this.decVFrameInterval),this.decVFrameInterval=null}},{key:"_checkPlayFinished",value:function(){return!(this.config.playMode!==h.PLAYER_MODE_VOD||!(!0===this.bufRecvStat&&(this.playPTS>=this.bufLastVDTS||this.audioWAudio&&this.playPTS>=this.bufLastADTS)||this.duration-this.playPTS0&&n-i>=t.frameTime-r){var e=t._videoQueue.shift();e.pts,o.renderFrame(t.yuv,e.data_y,e.data_u,e.data_v,e.line1,e.height),(r=u.GetMsTime()-n)>=t.frameTime&&(r=t.frameTime),i=n}}),2):this.playFrameInterval=window.setInterval((function(){if(n=u.GetMsTime(),e._videoQueue.length>0&&n-i>=e.frameTime-r){var t=e._videoQueue.shift(),s=0;if(e.isNewSeek||null===e.audioWAudio||void 0===e.audioWAudio||(s=1e3*(t.pts-e.audioWAudio.getAlignVPTS()),e.playPTS=Math.max(e.audioWAudio.getAlignVPTS(),e.playPTS)),i=n,e.playPTS=Math.max(t.pts,e.playPTS),e.isNewSeek&&e.seekTarget-e.frameDur>t.pts)return void(r=e.frameTime);if(e.isNewSeek&&(e.audioWAudio&&e.audioWAudio.setVoice(e.audioVoice),e.audioWAudio&&e.audioWAudio.play(),r=0,e.isNewSeek=!1,e.seekTarget=0),e.showScreen&&e.onRender&&e.onRender(t.line1,t.height,t.data_y,t.data_u,t.data_v),o.renderFrame(e.yuv,t.data_y,t.data_u,t.data_v,t.line1,t.height),e.onPlayingTime&&e.onPlayingTime(t.pts),!e.isNewSeek&&e.audioWAudio&&(s<0&&-1*s<=a||s>=0)){if(e.config.playMode===h.PLAYER_MODE_VOD)if(t.pts>=e.duration)e.onLoadCacheFinshed&&e.onLoadCacheFinshed(),e.onPlayingFinish&&e.onPlayingFinish(),e._clearDecInterval(),e.pause();else if(e._checkPlayFinished())return;r=u.GetMsTime()-n}else!e.isNewSeek&&e.audioWAudio&&(r=e.frameTime)}e._checkPlayFinished()}),1)}this.isNewSeek||this.audioWAudio&&this.audioWAudio.play()}},{key:"pause",value:function(){this.isPlaying=!1,this._pause(),this.isCacheV===h.CACHE_WITH_PLAY_SIGN&&(this.isCacheV=h.CACHE_WITH_NOPLAY_SIGN)}},{key:"_pause",value:function(){this.playFrameInterval&&window.clearInterval(this.playFrameInterval),this.playFrameInterval=null,this.audioWAudio&&this.audioWAudio.pause()}},{key:"seek",value:function(e){var t=this,i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};this.openFrameCall=!1,this.pause(),this._clearDecInterval(),null!==this.avFeedVideoInterval&&(window.clearInterval(this.avFeedVideoInterval),this.avFeedVideoInterval=null),null!==this.avFeedAudioInterval&&(window.clearInterval(this.avFeedAudioInterval),this.avFeedAudioInterval=null),this.yuvMaxTime=0,this.playVPipe.length=0,this._videoQueue.length=0,this.audioWAudio&&this.audioWAudio.stop(),e&&e(),this.isNewSeek=!0,this.avSeekVState=!0,this.seekTarget=i.seekTime,null!==this.audioWAudio&&void 0!==this.audioWAudio&&(this.audioWAudio.setVoice(0),this.audioWAudio.resetStartParam(),this.audioWAudio.stop()),this._avFeedData(i.seekTime),setTimeout((function(){t.yuvMaxTime=0,t._videoQueue.length=0,t.openFrameCall=!0,t.frameCallTag+=1,t._decVFrameIntervalFunc()}),1e3)}},{key:"setVoice",value:function(e){this.audioVoice=e,this.audioWAudio&&this.audioWAudio.setVoice(e)}},{key:"cacheIsFull",value:function(){return this._videoQueue.length>=this._VIDEO_CACHE_LEN}},{key:"_checkDisplaySize",value:function(e,t,i){var n=t-e,r=this.config.width+Math.ceil(n/2),a=t/this.config.width>i/this.config.height,s=(r/t).toFixed(2),o=(this.config.height/i).toFixed(2),u=a?s:o,l=this.config.fixed,h=l?r:parseInt(t*u),d=l?this.config.height:parseInt(i*u);if(this.canvas.offsetWidth!=h||this.canvas.offsetHeight!=d){var c=parseInt((this.canvasBox.offsetHeight-d)/2),f=parseInt((this.canvasBox.offsetWidth-h)/2);c=c<0?0:c,f=f<0?0:f,this.canvas.style.marginTop=c+"px",this.canvas.style.marginLeft=f+"px",this.canvas.style.width=h+"px",this.canvas.style.height=d+"px"}return this.isCheckDisplay=!0,[h,d]}},{key:"_createYUVCanvas",value:function(){this.canvasBox=document.querySelector("#"+this.config.playerId),this.canvasBox.style.overflow="hidden",this.canvas=document.createElement("canvas"),this.canvas.style.width=this.canvasBox.clientWidth+"px",this.canvas.style.height=this.canvasBox.clientHeight+"px",this.canvas.style.top="0px",this.canvas.style.left="0px",this.canvasBox.appendChild(this.canvas),this.yuv=o.setupCanvas(this.canvas,{preserveDrawingBuffer:!1})}},{key:"_avRecvPackets",value:function(){var e=this;this.bufObject.cleanPipeline(),null!==this.avRecvInterval&&(window.clearInterval(this.avRecvInterval),this.avRecvInterval=null),!0===this.config.checkProbe?this.avRecvInterval=window.setInterval((function(){Module.cwrap("getSniffStreamPkg","number",["number"])(e.corePtr),e._avCheckRecvFinish()}),5):this.avRecvInterval=window.setInterval((function(){Module.cwrap("getSniffStreamPkgNoCheckProbe","number",["number"])(e.corePtr),e._avCheckRecvFinish()}),5),this._avFeedData(0,!1)}},{key:"_avCheckRecvFinish",value:function(){this.config.playMode===h.PLAYER_MODE_VOD&&this.duration-this.getMaxPTS()=t._VIDEO_CACHE_LEN&&(t.onSeekFinish&&t.onSeekFinish(),t.onPlayingTime&&t.onPlayingTime(e),t.play(),window.clearInterval(i),i=null)}),10);return!0}},{key:"_afterAvFeedSeekToStartWithUnFinBuffer",value:function(e){var t=this,i=this,n=window.setInterval((function(){t._videoQueue.length,i._videoQueue.length>=i._VIDEO_CACHE_LEN&&(i.onSeekFinish&&i.onSeekFinish(),i.onPlayingTime&&i.onPlayingTime(e),!1===i.reFull?i.play():i.reFull=!1,window.clearInterval(n),n=null)}),10);return!0}},{key:"_avFeedData",value:function(e){var t=this;if(this.playVPipe.length=0,this.audioWAudio&&this.audioWAudio.cleanQueue(),e<=0&&!1===this.bufOK){var i=0;if(t.avFeedVideoInterval=window.setInterval((function(){var n=t.bufObject.videoBuffer.length;if(n-1>i||t.duration>0&&t.duration-t.getMaxPTS()0){for(var s=0;s0&&t.playVPipe[t.playVPipe.length-1].pts>=t.bufLastVDTS&&(window.clearInterval(t.avFeedVideoInterval),t.avFeedVideoInterval=null,t.playVPipe[t.playVPipe.length-1].pts,t.bufLastVDTS,t.bufObject.videoBuffer,t.playVPipe)}else t.config.playMode===h.PLAYER_MODE_VOD&&t.playVPipe.length>0&&t.playVPipe[t.playVPipe.length-1].pts>=t.duration&&(window.clearInterval(t.avFeedVideoInterval),t.avFeedVideoInterval=null,t.playVPipe[t.playVPipe.length-1].pts,t.duration,t.bufObject.videoBuffer,t.playVPipe);t.avSeekVState&&(t.getMaxPTS(),t.duration,t.config.playMode===h.PLAYER_MODE_VOD&&(t._afterAvFeedSeekToStartWithFinishedBuffer(e),t.avSeekVState=!1))}),5),void 0!==t.audioWAudio&&null!==t.audioWAudio&&t.config.ignoreAudio<1){var n=0;t.avFeedAudioInterval=window.setInterval((function(){var e=t.bufObject.audioBuffer.length;if(e-1>n||t.duration-t.getMaxPTS()0&&t.audioWAudio.sampleQueue[t.audioWAudio.sampleQueue.length-1].pts>=t.bufLastADTS&&(window.clearInterval(t.avFeedAudioInterval),t.avFeedAudioInterval=null,t.audioWAudio.sampleQueue[t.audioWAudio.sampleQueue.length-1].pts,t.bufObject.audioBuffer)}else t.config.playMode===h.PLAYER_MODE_VOD&&t.audioWAudio.sampleQueue.length>0&&t.audioWAudio.sampleQueue[t.audioWAudio.sampleQueue.length-1].pts>=t.duration&&(window.clearInterval(t.avFeedAudioInterval),t.avFeedAudioInterval=null,t.audioWAudio.sampleQueue[t.audioWAudio.sampleQueue.length-1].pts,t.bufObject.audioBuffer)}),5)}}else{var r=this.bufObject.seekIDR(e),s=parseInt(r,10);this.playPTS=0;var o=s;if(this.avFeedVideoInterval=window.setInterval((function(){var i=t.bufObject.videoBuffer.length;if(i-1>o||t.duration-t.getMaxPTS()0){for(var r=0;r0&&t.playVPipe[t.playVPipe.length-1].pts>=t.bufLastVDTS&&(window.clearInterval(t.avFeedVideoInterval),t.avFeedVideoInterval=null)}else t.config.playMode===h.PLAYER_MODE_VOD&&t.playVPipe.length>0&&t.playVPipe[t.playVPipe.length-1].pts>=t.duration&&(window.clearInterval(t.avFeedVideoInterval),t.avFeedVideoInterval=null);t.avSeekVState&&(t.getMaxPTS(),t.duration,t.config.playMode===h.PLAYER_MODE_VOD&&(t._afterAvFeedSeekToStartWithUnFinBuffer(e),t.avSeekVState=!1))}),5),this.audioWAudio&&this.config.ignoreAudio<1){var u=parseInt(e,10);this.avFeedAudioInterval=window.setInterval((function(){var e=t.bufObject.audioBuffer.length;if(e-1>u||t.duration-t.getMaxPTS()0&&t.audioWAudio.sampleQueue[t.audioWAudio.sampleQueue.length-1].pts>=t.bufLastADTS&&(window.clearInterval(t.avFeedAudioInterval),t.avFeedAudioInterval=null)}else t.config.playMode===h.PLAYER_MODE_VOD&&t.audioWAudio.sampleQueue.length>0&&t.audioWAudio.sampleQueue[t.audioWAudio.sampleQueue.length-1].pts>=t.duration&&(window.clearInterval(t.avFeedAudioInterval),t.avFeedAudioInterval=null)}),5)}}}},{key:"_probeFinCallback",value:function(e,t,i,n,r,a,s,o,u){var d=this;this._createYUVCanvas(),h.V_CODEC_NAME_HEVC,this.config.fps=1*n,this.frameTime=1e3/this.config.fps,this.width=t,this.height=i,this.frameDur=1/this.config.fps,this.duration=e-this.frameDur,this.vCodecID=o,this.config.sampleRate=a,this.channels=s,this.audioIdx=r,this.duration<0&&(this.config.playMode=h.PLAYER_MODE_NOTIME_LIVE,this.frameTime,this.frameDur);for(var c=Module.HEAPU8.subarray(u,u+10),f=0;f=0&&this.config.ignoreAudio<1?this.audioNone=!1:this.audioNone=!0,h.V_CODEC_NAME_HEVC===this.vCodecID&&(!1===this.audioNone&&(void 0!==this.audioWAudio&&null!==this.audioWAudio&&(this.audioWAudio.stop(),this.audioWAudio=null),this.audioWAudio=l({sampleRate:a,appendType:h.APPEND_TYPE_FRAME}),this.audioWAudio.setDurationMs(1e3*e),this.onLoadCache&&this.audioWAudio.setOnLoadCache((function(){if(d.retryAuSampleNo,d.retryAuSampleNo<=5){d.pause(),d.onLoadCache&&d.onLoadCache();var e=window.setInterval((function(){return d.retryAuSampleNo,d.audioWAudio.sampleQueue.length,d.audioWAudio.sampleQueue.length>2?(d.onLoadCacheFinshed&&d.onLoadCacheFinshed(),d.play(),d.retryAuSampleNo=0,window.clearInterval(e),void(e=null)):(d.retryAuSampleNo+=1,d.retryAuSampleNo>5?(d.play(),d.onLoadCacheFinshed&&d.onLoadCacheFinshed(),window.clearInterval(e),void(e=null)):void 0)}),1e3)}}))),this._avRecvPackets(),this._decVFrameIntervalFunc()),this.onProbeFinish&&this.onProbeFinish()}},{key:"_ptsFixed2",value:function(e){return Math.ceil(100*e)/100}},{key:"_naluCallback",value:function(e,t,i,n,r,a,s,o){var u=this._ptsFixed2(a);o>0&&(u=a);var l=Module.HEAPU8.subarray(e,e+t),h=new Uint8Array(l);this.bufObject.appendFrameWithDts(u,s,h,!0,i),this.bufLastVDTS=Math.max(s,this.bufLastVDTS),this.vCachePTS=Math.max(u,this.vCachePTS),this.onCacheProcess&&this.onCacheProcess(this.getCachePTS())}},{key:"_samplesCallback",value:function(e,t,i,n){}},{key:"_aacFrameCallback",value:function(e,t,i,n){var r=this._ptsFixed2(n);if(this.audioWAudio){var a=Module.HEAPU8.subarray(e,e+t),s=new Uint8Array(a);this.bufObject.appendFrame(r,s,!1,!0),this.bufLastADTS=Math.max(r,this.bufLastADTS),this.aCachePTS=Math.max(r,this.aCachePTS),this.onCacheProcess&&this.onCacheProcess(this.getCachePTS())}}},{key:"_setLoadCache",value:function(){if(null===this.avFeedVideoInterval&&null===this.avFeedAudioInterval&&this.playVPipe.length<=0)return 1;if(this.isCacheV===h.CACHE_NO_LOADCACHE){var e=this.isPlaying;this.pause(),this.onLoadCache&&this.onLoadCache(),this.isCacheV=e?h.CACHE_WITH_PLAY_SIGN:h.CACHE_WITH_NOPLAY_SIGN}return 0}},{key:"_setLoadCacheFinished",value:function(){this.isCacheV!==h.CACHE_NO_LOADCACHE&&(this.isCacheV,this.onLoadCacheFinshed&&this.onLoadCacheFinshed(),this.isCacheV===h.CACHE_WITH_PLAY_SIGN&&this.play(),this.isCacheV=h.CACHE_NO_LOADCACHE)}},{key:"_createDecVframeInterval",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:10,t=this;null!==this.decVFrameInterval&&(window.clearInterval(this.decVFrameInterval),this.decVFrameInterval=null);var i=0;this.loopMs=e,this.decVFrameInterval=window.setInterval((function(){if(t._videoQueue.length<1?t._setLoadCache():t._videoQueue.length>=t._VIDEO_CACHE_LEN&&t._setLoadCacheFinished(),t._videoQueue.length0){100===t.loopMs&&t._createDecVframeInterval(10);var e=t.playVPipe.shift(),n=e.data,r=Module._malloc(n.length);Module.HEAP8.set(n,r);var a=parseInt(1e3*e.pts,10),s=parseInt(1e3*e.dts,10);t.yuvMaxTime=Math.max(e.pts,t.yuvMaxTime);var o=Module.cwrap("decodeVideoFrame","number",["number","number","number","number","number"])(t.corePtr,r,n.length,a,s,t.frameCallTag);o>0&&(i=o),Module._free(r),r=null}}else i=Module.cwrap("naluLListLength","number",["number"])(t.corePtr)}),e)}},{key:"_decVFrameIntervalFunc",value:function(){null==this.decVFrameInterval&&this._createDecVframeInterval(10)}},{key:"_frameCallback",value:function(e,t,i,n,r,a,s,o,u,l){if(this._videoQueue.length,!1===this.openFrameCall)return-1;if(l!==this.frameCallTag)return-2;if(u>this.yuvMaxTime+this.frameDur)return-3;if(this.isNewSeek&&this.seekTarget-u>3*this.frameDur)return-4;var h=this._videoQueue.length;if(this.canvas.width==n&&this.canvas.height==o||(this.canvas.width=n,this.canvas.height=o,this.isCheckDisplay)||this._checkDisplaySize(s,n,o),this.playPTS>u)return-5;var d=Module.HEAPU8.subarray(e,e+n*o),f=Module.HEAPU8.subarray(t,t+r*o/2),p=Module.HEAPU8.subarray(i,i+a*o/2),m=new Uint8Array(d),_=new Uint8Array(f),g=new Uint8Array(p),v=new c(m,_,g,n,r,a,s,o,u);if(h<=0||u>this._videoQueue[h-1].pts)this._videoQueue.push(v);else if(uthis._videoQueue[y].pts&&y+1this.yuvMaxTime+this.frameDur||this.isNewSeek&&this.seekTarget-u>3*this.frameDur)){var p=this._videoQueue.length;if(this.canvas.width==n&&this.canvas.height==o||(this.canvas.width=n,this.canvas.height=o,this.isCheckDisplay)||this._checkDisplaySize(s,n,o),!(this.playPTS>u)){var m=new c(h,d,f,n,r,a,s,o,u);if(p<=0||u>this._videoQueue[p-1].pts)this._videoQueue.push(m);else if(uthis._videoQueue[_].pts&&_+10){var e=this._videoQueue.shift();return e.pts,this.onRender&&this.onRender(e.line1,e.height,e.data_y,e.data_u,e.data_v),o.renderFrame(this.yuv,e.data_y,e.data_u,e.data_v,e.line1,e.height),!0}return!1}},{key:"setProbeSize",value:function(e){this.probeSize=e}},{key:"pushBuffer",value:function(e){if(void 0===this.corePtr||null===this.corePtr)return-1;var t=Module._malloc(e.length);Module.HEAP8.set(e,t);var i=Module.cwrap("pushSniffStreamData","number",["number","number","number","number"])(this.corePtr,t,e.length,this.probeSize);return i}}])&&n(t.prototype,i),f&&n(t,f),e}();i.CNativeCore=f},{"../consts":52,"../demuxer/buffer":66,"../demuxer/bufferFrame":67,"../render-engine/webgl-420p":81,"../version":84,"./audio-core":54,"./audio-native-core":55,"./av-common":56,"./cache":61,"./cacheYuv":62}],60:[function(e,t,i){"use strict";function n(e,t){for(var i=0;i0&&(t.getPackageTimeMS=a.GetMsTime()),t.pushPkg++,void 0!==t.AVGetInterval&&null!==t.AVGetInterval||(t.AVGetInterval=window.setInterval((function(){Module.cwrap("getBufferLengthApi","number",["number"])(t.corePtr)>t.config.probeSize&&(Module.cwrap("getSniffHttpFlvPkg","number",["number"])(t.corePtr),t.pushPkg-=1)}),5));break;case"close":t.AVGetInterval&&clearInterval(t.AVGetInterval),t.AVGetInterval=null;case"fetch-fin":break;case"fetch-error":t.onError&&t.onError(i.data)}}},{key:"_checkDisplaySize",value:function(e,t,i){var n=t-e,r=this.config.width+Math.ceil(n/2),a=t/this.config.width>i/this.config.height,s=(r/t).toFixed(2),o=(this.config.height/i).toFixed(2),u=a?s:o,l=this.config.fixed,h=l?r:parseInt(t*u),d=l?this.config.height:parseInt(i*u);if(this.CanvasObj.offsetWidth!=h||this.CanvasObj.offsetHeight!=d){var c=parseInt((this.canvasBox.offsetHeight-d)/2),f=parseInt((this.canvasBox.offsetWidth-h)/2);c=c<0?0:c,f=f<0?0:f,this.CanvasObj.style.marginTop=c+"px",this.CanvasObj.style.marginLeft=f+"px",this.CanvasObj.style.width=h+"px",this.CanvasObj.style.height=d+"px"}return this.isCheckDisplay=!0,[h,d]}},{key:"_ptsFixed2",value:function(e){return Math.ceil(100*e)/100}},{key:"_callbackProbe",value:function(e,t,i,n,r,a,u,l,h){for(var d=Module.HEAPU8.subarray(h,h+10),c=0;c100&&(f=o.DEFAULT_FPS,this.mediaInfo.noFPS=!0),this.vCodecID=l,this.config.fps=f,this.mediaInfo.fps=f,this.mediaInfo.size.width=t,this.mediaInfo.size.height=i,this.frameTime=Math.floor(1e3/(this.mediaInfo.fps+2)),this.CanvasObj.width==t&&this.CanvasObj.height==i||(this.CanvasObj.width=t,this.CanvasObj.height=i,this.isCheckDisplay)||this._checkDisplaySize(t,t,i),r>=0&&!1===this.mediaInfo.noFPS&&this.config.ignoreAudio<1?(void 0!==this.audioWAudio&&null!==this.audioWAudio&&(this.audioWAudio.stop(),this.audioWAudio=null),this.config.sampleRate=a,this.mediaInfo.sampleRate=a,this.audioWAudio=s({sampleRate:this.mediaInfo.sampleRate,appendType:o.APPEND_TYPE_FRAME}),this.audioWAudio.isLIVE=!0):this.mediaInfo.audioNone=!0,this.onProbeFinish&&this.onProbeFinish()}},{key:"_callbackYUV",value:function(e,t,i,n,r,a,s,o,u){var l=Module.HEAPU8.subarray(e,e+n*o),h=new Uint8Array(l),d=Module.HEAPU8.subarray(t,t+r*o/2),c=new Uint8Array(d),f=Module.HEAPU8.subarray(i,i+a*o/2),p={bufY:h,bufU:c,bufV:new Uint8Array(f),line_y:n,h:o,pts:u};this.YuvBuf.push(p),this.checkCacheState(),Module._free(l),l=null,Module._free(d),d=null,Module._free(f),f=null,!1===this.readyShowDone&&!0===this.playYUV()&&(this.readyShowDone=!0,this.onReadyShowDone&&this.onReadyShowDone(),this.audioWAudio||this.play())}},{key:"_callbackNALU",value:function(e,t,i,n,r,a,s){if(!1===this.readyKeyFrame){if(i<=0)return;this.readyKeyFrame=!0}var o=Module.HEAPU8.subarray(e,e+t),u=new Uint8Array(o);this.NaluBuf.push({bufData:u,len:t,isKey:i,w:n,h:r,pts:1e3*a,dts:1e3*s}),Module._free(o),o=null}},{key:"_callbackPCM",value:function(e){}},{key:"_callbackAAC",value:function(e,t,i,n){var r=this._ptsFixed2(n);if(this.audioWAudio){var a=Module.HEAPU8.subarray(e,e+t),s={pts:r,data:new Uint8Array(a)};this.audioWAudio.addSample(s),this.checkCacheState()}}},{key:"_decode",value:function(){var e=this;setTimeout((function(){if(null!==e.workerFetch){var t=e.NaluBuf.shift();if(null!=t){var i=Module._malloc(t.bufData.length);Module.HEAP8.set(t.bufData,i),Module.cwrap("decodeHttpFlvVideoFrame","number",["number","number","number","number","number"])(e.corePtr,i,t.bufData.length,t.pts,t.dts,0),Module._free(i),i=null}e._decode()}}),1)}},{key:"setScreen",value:function(){var e=arguments.length>0&&void 0!==arguments[0]&&arguments[0];this.showScreen=e}},{key:"checkCacheState",value:function(){var e=this.YuvBuf.length>=25&&(!0===this.mediaInfo.audioNone||this.audioWAudio&&this.audioWAudio.sampleQueue.length>=50);return!1===this.cache_status&&e&&(this.playInterval&&this.audioWAudio&&this.audioWAudio.play(),this.onLoadCacheFinshed&&this.onLoadCacheFinshed(),this.cache_status=!0),e}},{key:"setVoice",value:function(e){this.audioVoice=e,this.audioWAudio&&this.audioWAudio.setVoice(e)}},{key:"_removeBindFuncPtr",value:function(){null!==this._ptr_probeCallback&&Module.removeFunction(this._ptr_probeCallback),null!==this._ptr_frameCallback&&Module.removeFunction(this._ptr_frameCallback),null!==this._ptr_naluCallback&&Module.removeFunction(this._ptr_naluCallback),null!==this._ptr_sampleCallback&&Module.removeFunction(this._ptr_sampleCallback),null!==this._ptr_aacCallback&&Module.removeFunction(this._ptr_aacCallback),this._ptr_probeCallback=null,this._ptr_frameCallback=null,this._ptr_naluCallback=null,this._ptr_sampleCallback=null,this._ptr_aacCallback=null}},{key:"release",value:function(){return this.pause(),this.NaluBuf.length=0,this.YuvBuf.length=0,void 0!==this.workerFetch&&null!==this.workerFetch&&this.workerFetch.postMessage({cmd:"stop",data:"stop",msg:"stop"}),this.workerFetch=null,this.AVGetInterval&&clearInterval(this.AVGetInterval),this.AVGetInterval=null,this._removeBindFuncPtr(),void 0!==this.corePtr&&null!==this.corePtr&&Module.cwrap("releaseHttpFLV","number",["number"])(this.corePtr),this.playInterval&&clearInterval(this.playInterval),this.playInterval=null,this.audioWAudio&&this.audioWAudio.stop(),this.audioWAudio=null,void 0!==this.AVGLObj&&null!==this.AVGLObj&&(r.releaseContext(this.AVGLObj),this.AVGLObj=null),this.CanvasObj&&this.CanvasObj.remove(),this.CanvasObj=null,window.onclick=document.body.onclick=null,0}},{key:"isPlayingState",value:function(){return null!==this.playInterval&&void 0!==this.playInterval}},{key:"pause",value:function(){this.audioWAudio&&this.audioWAudio.pause(),this.playInterval&&clearInterval(this.playInterval),this.playInterval=null}},{key:"playYUV",value:function(){if(this.YuvBuf.length>0){var e=this.YuvBuf.shift();return this.onRender&&this.onRender(e.line_y,e.h,e.bufY,e.bufU,e.bufV),r.renderFrame(this.AVGLObj,e.bufY,e.bufU,e.bufV,e.line_y,e.h),!0}return!1}},{key:"play",value:function(){var e=this,t=this;if(!1===this.checkCacheState())return this.onLoadCache&&this.onLoadCache(),setTimeout((function(){e.play()}),100),!1;if(void 0===this.playInterval||null===this.playInterval){var i=0,n=0,s=0;!1===this.mediaInfo.audioNone&&this.audioWAudio&&!1===this.mediaInfo.noFPS?(this.playInterval=setInterval((function(){if(n=a.GetMsTime(),t.cache_status){if(n-i>=t.frameTime-s){var e=t.YuvBuf.shift();if(null!=e&&null!==e){var o=0;null!==t.audioWAudio&&void 0!==t.audioWAudio&&(o=1e3*(e.pts-t.audioWAudio.getAlignVPTS())),s=t.audioWAudio?o<0&&-1*o<=t.frameTime||o>=0?a.GetMsTime()-n+1:t.frameTime:a.GetMsTime()-n+1,t.showScreen&&t.onRender&&t.onRender(e.line_y,e.h,e.bufY,e.bufU,e.bufV),e.pts,r.renderFrame(t.AVGLObj,e.bufY,e.bufU,e.bufV,e.line_y,e.h)}(t.YuvBuf.length<=0||t.audioWAudio&&t.audioWAudio.sampleQueue.length<=0)&&(t.cache_status=!1,t.onLoadCache&&t.onLoadCache(),t.audioWAudio&&t.audioWAudio.pause()),i=n}}else s=t.frameTime}),1),this.audioWAudio&&this.audioWAudio.play()):this.playInterval=setInterval((function(){var e=t.YuvBuf.shift();null!=e&&null!==e&&(t.showScreen&&t.onRender&&t.onRender(e.line_y,e.h,e.bufY,e.bufU,e.bufV),r.renderFrame(t.AVGLObj,e.bufY,e.bufU,e.bufV,e.line_y,e.h)),t.YuvBuf.length<=0&&(t.cache_status=!1)}),t.frameTime)}}},{key:"start",value:function(e){var t=this;this.workerFetch=new Worker(a.GetScriptPath((function(){var e=null;self,self.onmessage=function(t){var i=t.data;switch(void 0===i.cmd||null===i.cmd?"":i.cmd){case"start":var n=i.data;(e=new WebSocket(n)).binaryType="arraybuffer",e.onopen=function(t){e.send("Hello WebSockets!")},e.onmessage=function(e){if(e.data instanceof ArrayBuffer){var t=e.data;t.byteLength>0&&postMessage({cmd:"fetch-chunk",data:new Uint8Array(t),msg:"fetch-chunk"})}},e.onclose=function(e){};break;case"stop":e&&e.close(),self.close(),self.postMessage({cmd:"close",data:"close",msg:"close"})}}}))),this.workerFetch.onmessage=function(e){t._workerFetch_onmessage(e,t)},this.workerFetch,this._ptr_probeCallback=Module.addFunction(this._callbackProbe.bind(this)),this._ptr_yuvCallback=Module.addFunction(this._callbackYUV.bind(this)),this._ptr_naluCallback=Module.addFunction(this._callbackNALU.bind(this)),this._ptr_sampleCallback=Module.addFunction(this._callbackPCM.bind(this)),this._ptr_aacCallback=Module.addFunction(this._callbackAAC.bind(this)),Module.cwrap("initializeSniffHttpFlvModule","number",["number","number","number","number","number","number"])(this.corePtr,this._ptr_probeCallback,this._ptr_yuvCallback,this._ptr_naluCallback,this._ptr_sampleCallback,this._ptr_aacCallback),this.AVGLObj=r.setupCanvas(this.CanvasObj,{preserveDrawingBuffer:!1}),this.workerFetch.postMessage({cmd:"start",data:e,msg:"start"}),this._decode()}}])&&n(t.prototype,i),u&&n(t,u),e}());i.CWsLiveCore=u},{"../consts":52,"../demuxer/buffer":66,"../demuxer/bufferFrame":67,"../render-engine/webgl-420p":81,"../version":84,"./audio-core":54,"./audio-native-core":55,"./av-common":56,"./cache":61,"./cacheYuv":62}],61:[function(e,t,i){(function(i){"use strict";e("./cacheYuv");i.CACHE_APPEND_STATUS_CODE={FAILED:-1,OVERFLOW:-2,OK:0,NOT_FULL:1,FULL:2,NULL:3},t.exports=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:60,t={limit:e,yuvCache:[],appendCacheByCacheYuv:function(e){e.pts;return t.yuvCache.length>=t.limit?CACHE_APPEND_STATUS_CODE.OVERFLOW:(t.yuvCache.push(e),t.yuvCache.length>=t.limit?CACHE_APPEND_STATUS_CODE.FULL:CACHE_APPEND_STATUS_CODE.NOT_FULL)},getState:function(){return t.yuvCache.length<=0?CACHE_APPEND_STATUS_CODE.NULL:t.yuvCache.length>=t.limit?CACHE_APPEND_STATUS_CODE.FULL:CACHE_APPEND_STATUS_CODE.NOT_FULL},cleanPipeline:function(){t.yuvCache.length=0},vYuv:function(){return t.yuvCache.length<=0?null:t.yuvCache.shift()}};return t}}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./cacheYuv":62}],62:[function(e,t,i){"use strict";function n(e,t){for(var i=0;i>1;return r.indexOf(t)},GET_NALU_TYPE:function(e){var t=(126&e)>>1;if(t>=1&&t<=9)return n.DEFINE_P_FRAME;if(t>=16&&t<=21)return n.DEFINE_KEY_FRAME;var i=r.indexOf(t);return i>=0?r[i]:n.DEFINE_OTHERS_FRAME},PACK_NALU:function(e){var t=e.nalu,i=e.vlc.vlc;null==t.vps&&(t.vps=new Uint8Array);var n=new Uint8Array(t.vps.length+t.sps.length+t.pps.length+t.sei.length+i.length);return n.set(t.vps,0),n.set(t.sps,t.vps.length),n.set(t.pps,t.vps.length+t.sps.length),n.set(t.sei,t.vps.length+t.sps.length+t.pps.length),n.set(i,t.vps.length+t.sps.length+t.pps.length+t.sei.length),n}}},{"./hevc-header":63}],65:[function(e,t,i){"use strict";function n(e){return function(e){if(Array.isArray(e)){for(var t=0,i=new Array(e.length);t0&&void 0!==arguments[0]&&arguments[0];null!=t&&(t.showScreen=e)},setSize:function(e,i){t.config.width=e||l.DEFAULT_WIDTH,t.config.height=i||l.DEFAULT_HEIGHT},setFrameRate:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:25;t.config.fps=e,t.config.frameDurMs=1e3/e},setDurationMs:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:-1;t.durationMs=e,0==t.config.audioNone&&t.audio.setDurationMs(e)},setPlayingCall:function(e){t.onPlayingTime=e},setVoice:function(e){t.realVolume=e,0==t.config.audioNone&&t.audio.setVoice(t.realVolume)},isPlayingState:function(){return t.isPlaying||t.isCaching===l.CACHE_WITH_PLAY_SIGN},appendAACFrame:function(e){t.audio.addSample(e),t.aCachePTS=Math.max(e.pts,t.aCachePTS)},appendHevcFrame:function(e){var i;t.config.appendHevcType==l.APPEND_TYPE_STREAM?t.stream=new Uint8Array((i=n(t.stream)).concat.apply(i,n(e))):t.config.appendHevcType==l.APPEND_TYPE_FRAME&&(t.frameList.push(e),t.vCachePTS=Math.max(e.pts,t.vCachePTS))},getCachePTS:function(){return Math.max(t.vCachePTS,t.aCachePTS)},endAudio:function(){0==t.config.audioNone&&t.audio.stop()},cleanSample:function(){0==t.config.audioNone&&t.audio.cleanQueue()},cleanVideoQueue:function(){t.config.appendHevcType==l.APPEND_TYPE_STREAM?t.stream=new Uint8Array:t.config.appendHevcType==l.APPEND_TYPE_FRAME&&(t.frameList=[],t.frameList.length=0)},cleanCacheYUV:function(){t.cacheYuvBuf.cleanPipeline()},pause:function(){t.loop&&window.clearInterval(t.loop),t.loop=null,0==t.config.audioNone&&t.audio.pause(),t.isPlaying=!1,t.isCaching===l.CACHE_WITH_PLAY_SIGN&&(t.isCaching=l.CACHE_WITH_NOPLAY_SIGN)},checkFinished:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:l.PLAYER_MODE_VOD;return e==l.PLAYER_MODE_VOD&&t.cacheYuvBuf.yuvCache.length<=0&&(t.videoPTS.toFixed(1)>=(t.durationMs-t.config.frameDurMs)/1e3||t.noCacheFrame>=10)&&(null!=t.onPlayingFinish&&(l.PLAYER_MODE_VOD,t.frameList.length,t.cacheYuvBuf.yuvCache.length,t.videoPTS.toFixed(1),t.durationMs,t.config.frameDurMs,t.noCacheFrame,t.onPlayingFinish()),!0)},clearAllCache:function(){t.nowPacket=null,t.vCachePTS=0,t.aCachePTS=0,t.cleanSample(),t.cleanVideoQueue(),t.cleanCacheYUV()},seek:function(e){var i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=t.isPlaying;t.pause(),t.stopCacheThread(),t.clearAllCache(),e&&e(),t.isNewSeek=!0,t.flushDecoder=1,t.videoPTS=parseInt(i.seekTime);var r={seekPos:i.seekTime||-1,mode:i.mode||l.PLAYER_MODE_VOD,accurateSeek:i.accurateSeek||!0,seekEvent:i.seekEvent||!0,realPlay:n};t.cacheThread(),t.play(r)},getNalu1Packet:function(){var e=!(arguments.length>0&&void 0!==arguments[0])||arguments[0],i=null,n=-1;if(t.config.appendHevcType==l.APPEND_TYPE_STREAM)i=t.nextNalu();else{if(t.config.appendHevcType!=l.APPEND_TYPE_FRAME)return null;var r=t.frameList.shift();if(!r)return null;i=r.data,n=r.pts,e&&(t.videoPTS=n)}return{nalBuf:i,pts:n}},decodeNalu1Frame:function(e,i){var n=Module._malloc(e.length);Module.HEAP8.set(e,n);var r=parseInt(1e3*i);Module.cwrap("decodeCodecContext","number",["number","number","number","number","number"])(t.vcodecerPtr,n,e.length,r,t.flushDecoder);return t.flushDecoder=0,Module._free(n),n=null,!1},cacheThread:function(){t.cacheLoop=window.setInterval((function(){if(t.cacheYuvBuf.getState()!=CACHE_APPEND_STATUS_CODE.FULL){var e=t.getNalu1Packet(!1);if(null!=e){var i=e.nalBuf,n=e.pts;t.decodeNalu1Frame(i,n,!0)}}}),10)},stopCacheThread:function(){null!==t.cacheLoop&&(window.clearInterval(t.cacheLoop),t.cacheLoop=null)},loadCache:function(){if(!(t.frameList.length<=3)){var e=t.isPlaying;if(t.cacheYuvBuf.yuvCache.length<=3){t.pause(),null!=t.onLoadCache&&t.onLoadCache(),t.isCaching=e?l.CACHE_WITH_PLAY_SIGN:l.CACHE_WITH_NOPLAY_SIGN;var i=t.frameList.length>30?30:t.frameList.length;null===t.cacheInterval&&(t.cacheInterval=window.setInterval((function(){t.cacheYuvBuf.yuvCache.length>=i&&(null!=t.onLoadCacheFinshed&&t.onLoadCacheFinshed(),window.clearInterval(t.cacheInterval),t.cacheInterval=null,t.isCaching===l.CACHE_WITH_PLAY_SIGN&&t.play(t.playParams),t.isCaching=l.CACHE_NO_LOADCACHE)}),40))}}},playFunc:function(){var e=!1;if(t.playParams.seekEvent||r.GetMsTime()-t.calcuteStartTime>=t.frameTime-t.preCostTime){e=!0;var i=!0;if(t.calcuteStartTime=r.GetMsTime(),t.config.audioNone)t.playFrameYUV(i,t.playParams.accurateSeek);else{t.fix_poc_err_skip>0&&(t.fix_poc_err_skip--,i=!1);var n=t.videoPTS-t.audio.getAlignVPTS();if(n>0)return void(t.playParams.seekEvent&&!t.config.audioNone&&t.audio.setVoice(0));if(i){if(!(i=-1*n<=1*t.frameTimeSec)){for(var a=parseInt(n/t.frameTimeSec),s=0;s=i&&(t.playFrameYUV(!0,t.playParams.accurateSeek),i+=1)}),1)}else t.videoPTS>=t.playParams.seekPos&&!t.isNewSeek||0===t.playParams.seekPos||0===t.playParams.seekPos?(t.frameTime=1e3/t.config.fps,t.frameTimeSec=t.frameTime/1e3,0==t.config.audioNone&&t.audio.play(),t.realVolume=t.config.audioNone?0:t.audio.voice,t.playParams.seekEvent&&(t.fix_poc_err_skip=10),t.loop=window.setInterval((function(){var e=r.GetMsTime();t.playFunc(),t.preCostTime=r.GetMsTime()-e}),1)):(t.loop=window.setInterval((function(){t.playFrameYUV(!1,t.playParams.accurateSeek),t.checkFinished(t.playParams.mode)?(window.clearInterval(t.loop),t.loop=null):t.videoPTS>=t.playParams.seekPos&&(window.clearInterval(t.loop),t.loop=null,t.play(t.playParams))}),1),t.isNewSeek=!1)},stop:function(){t.release(),Module.cwrap("initializeDecoder","number",["number"])(t.vcodecerPtr),t.stream=new Uint8Array},release:function(){return void 0!==t.yuv&&null!==t.yuv&&(u.releaseContext(t.yuv),t.yuv=null),t.endAudio(),t.cacheLoop&&window.clearInterval(t.cacheLoop),t.cacheLoop=null,t.loop&&window.clearInterval(t.loop),t.loop=null,t.pause(),null!==t.videoCallback&&Module.removeFunction(t.videoCallback),t.videoCallback=null,Module.cwrap("release","number",["number"])(t.vcodecerPtr),t.stream=null,t.frameList.length=0,t.durationMs=-1,t.videoPTS=0,t.isPlaying=!1,t.canvas.remove(),t.canvas=null,window.onclick=document.body.onclick=null,!0},nextNalu:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:1;if(t.stream.length<=4)return!1;for(var i=-1,n=0;n=t.stream.length){if(-1==i)return!1;var r=t.stream.subarray(i);return t.stream=new Uint8Array,r}var a="0 0 1"==t.stream.slice(0,3).join(" "),s="0 0 0 1"==t.stream.slice(0,4).join(" ");if(a||s){if(-1==i)i=n;else{if(e<=1){var o=t.stream.subarray(i,n);return t.stream=t.stream.subarray(n),o}e-=1}n+=3}}return!1},decodeSendPacket:function(e){var i=Module._malloc(e.length);Module.HEAP8.set(e,i);var n=Module.cwrap("decodeSendPacket","number",["number","number","number"])(t.vcodecerPtr,i,e.length);return Module._free(i),n},decodeRecvFrame:function(){return Module.cwrap("decodeRecv","number",["number"])(t.vcodecerPtr)},playYUV:function(){return t.playFrameYUV(!0,!0)},playFrameYUV:function(){var e=arguments.length>0&&void 0!==arguments[0]&&arguments[0],i=arguments.length>1&&void 0!==arguments[1]&&arguments[1],n=t.cacheYuvBuf.vYuv();if(null==n)return t.noCacheFrame+=1,e&&!t.playParams.seekEvent&&t.loadCache(),!1;t.noCacheFrame=0;var r=n.pts;return t.videoPTS=r,(!e&&i||e)&&e&&(t.onRender(n.width,n.height,n.imageBufferY,n.imageBufferB,n.imageBufferR),t.drawImage(n.width,n.height,n.imageBufferY,n.imageBufferB,n.imageBufferR)),e&&!t.playParams.seekEvent&&t.isPlaying&&t.loadCache(),!0},drawImage:function(e,i,n,r,a){if(t.canvas.width===e&&t.canvas.height==i||(t.canvas.width=e,t.canvas.height=i),t.showScreen&&null!=t.onRender&&t.onRender(e,i,n,r,a),!t.isCheckDisplay)t.checkDisplaySize(e,i);var s=e*i,o=e/2*(i/2),l=new Uint8Array(s+2*o);l.set(n,0),l.set(r,s),l.set(a,s+o),u.renderFrame(t.yuv,n,r,a,e,i)},debugYUV:function(e){t.debugYUVSwitch=!0,t.debugID=e},checkDisplaySize:function(e,i){var n=e/t.config.width>i/t.config.height,r=(t.config.width/e).toFixed(2),a=(t.config.height/i).toFixed(2),s=n?r:a,o=t.config.fixed,u=o?t.config.width:parseInt(e*s),l=o?t.config.height:parseInt(i*s);if(t.canvas.offsetWidth!=u||t.canvas.offsetHeight!=l){var h=parseInt((t.canvasBox.offsetHeight-l)/2),d=parseInt((t.canvasBox.offsetWidth-u)/2);t.canvas.style.marginTop=h+"px",t.canvas.style.marginLeft=d+"px",t.canvas.style.width=u+"px",t.canvas.style.height=l+"px"}return t.isCheckDisplay=!0,[u,l]},makeWasm:function(){if(null!=t.config.token){t.vcodecerPtr=Module.cwrap("registerPlayer","number",["string","string"])(t.config.token,h.PLAYER_VERSION),t.videoCallback=Module.addFunction((function(e,i,n,r,a,s,u,l,h){var d=Module.HEAPU8.subarray(e,e+r*l),c=Module.HEAPU8.subarray(i,i+a*l/2),f=Module.HEAPU8.subarray(n,n+s*l/2),p=new Uint8Array(d),m=new Uint8Array(c),_=new Uint8Array(f),g=1*h/1e3,v=new o.CacheYuvStruct(g,r,l,p,m,_);Module._free(d),d=null,Module._free(c),c=null,Module._free(f),f=null,t.cacheYuvBuf.appendCacheByCacheYuv(v)})),Module.cwrap("setCodecType","number",["number","number","number"])(t.vcodecerPtr,t.config.videoCodec,t.videoCallback);Module.cwrap("initializeDecoder","number",["number"])(t.vcodecerPtr)}},makeIt:function(){var e=document.querySelector("div#"+t.config.playerId),i=document.createElement("canvas");i.style.width=e.clientWidth+"px",i.style.height=e.clientHeight+"px",i.style.top="0px",i.style.left="0px",e.appendChild(i),t.canvasBox=e,t.canvas=i,t.yuv=u.setupCanvas(i,{preserveDrawingBuffer:!1}),0==t.config.audioNone&&(t.audio=a({sampleRate:t.config.sampleRate,appendType:t.config.appendHevcType})),t.isPlayLoadingFinish=1}};return t.makeWasm(),t.makeIt(),t.cacheThread(),t}},{"../consts":52,"../render-engine/webgl-420p":81,"../version":84,"./audio-core":54,"./av-common":56,"./cache":61,"./cacheYuv":62}],66:[function(e,t,i){"use strict";var n=e("./bufferFrame");t.exports=function(){var e={videoBuffer:[],audioBuffer:[],idrIdxBuffer:[],appendFrame:function(t,i){var r=!(arguments.length>2&&void 0!==arguments[2])||arguments[2],a=arguments.length>3&&void 0!==arguments[3]&&arguments[3],s=new n.BufferFrame(t,a,i,r),o=parseInt(t);return r?(e.videoBuffer.length-1>=o?e.videoBuffer[o].push(s):e.videoBuffer.push([s]),a&&!e.idrIdxBuffer.includes(t)&&e.idrIdxBuffer.push(t)):e.audioBuffer.length-1>=o&&null!=e.audioBuffer[o]&&null!=e.audioBuffer[o]?e.audioBuffer[o]&&e.audioBuffer[o].push(s):e.audioBuffer.push([s]),!0},appendFrameWithDts:function(t,i,r){var a=!(arguments.length>3&&void 0!==arguments[3])||arguments[3],s=arguments.length>4&&void 0!==arguments[4]&&arguments[4],o=n.ConstructWithDts(t,i,s,r,a),u=parseInt(i);return a?(e.videoBuffer.length-1>=u?e.videoBuffer[u].push(o):e.videoBuffer.push([o]),s&&!e.idrIdxBuffer.includes(i)&&e.idrIdxBuffer.push(i)):e.audioBuffer.length-1>=u&&null!=e.audioBuffer[u]&&null!=e.audioBuffer[u]?e.audioBuffer[u]&&e.audioBuffer[u].push(o):e.audioBuffer.push([o]),e.videoBuffer,e.idrIdxBuffer,!0},appendFrameByBufferFrame:function(t){var i=t.pts,n=parseInt(i);return t.video?(e.videoBuffer.length-1>=n?e.videoBuffer[n].push(t):e.videoBuffer.push([t]),isKey&&!e.idrIdxBuffer.includes(i)&&e.idrIdxBuffer.push(i)):e.audioBuffer.length-1>=n?e.audioBuffer[n].push(t):e.audioBuffer.push([t]),!0},cleanPipeline:function(){e.videoBuffer.length=0,e.audioBuffer.length=0},vFrame:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:-1;if(!(t<0||t>e.videoBuffer.length-1))return e.videoBuffer[t]},aFrame:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:-1;if(!(t<0||t>e.audioBuffer.length-1))return e.audioBuffer[t]},seekIDR:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:-1;if(e.idrIdxBuffer,e.videoBuffer,t<0)return null;if(e.idrIdxBuffer.includes(t))return t;for(var i=0;it||0===i&&e.idrIdxBuffer[i]>=t){for(var n=1;n>=0;n--){var r=i-n;if(r>=0)return e.idrIdxBuffer[r],e.idrIdxBuffer[r]}return e.idrIdxBuffer[i],j,e.idrIdxBuffer[i]}}};return e}},{"./bufferFrame":67}],67:[function(e,t,i){"use strict";function n(e,t){for(var i=0;i0&&s.length>r&&!s.warned){s.warned=!0;var o=new Error("Possible EventEmitter memory leak detected. "+s.length+" "+String(t)+" listeners added. Use emitter.setMaxListeners() to increase limit");o.name="MaxListenersExceededWarning",o.emitter=e,o.type=t,o.count=s.length,console&&console.warn}return e}function d(){if(!this.fired)return this.target.removeListener(this.type,this.wrapFn),this.fired=!0,0===arguments.length?this.listener.call(this.target):this.listener.apply(this.target,arguments)}function c(e,t,i){var n={fired:!1,wrapFn:void 0,target:e,type:t,listener:i},r=d.bind(n);return r.listener=i,n.wrapFn=r,r}function f(e,t,i){var n=e._events;if(void 0===n)return[];var r=n[t];return void 0===r?[]:"function"==typeof r?i?[r.listener||r]:[r]:i?function(e){for(var t=new Array(e.length),i=0;i0&&(s=t[0]),s instanceof Error)throw s;var o=new Error("Unhandled error."+(s?" ("+s.message+")":""));throw o.context=s,o}var u=a[e];if(void 0===u)return!1;if("function"==typeof u)r(u,this,t);else{var l=u.length,h=m(u,l);for(i=0;i=0;a--)if(i[a]===t||i[a].listener===t){s=i[a].listener,r=a;break}if(r<0)return this;0===r?i.shift():function(e,t){for(;t+1=0;n--)this.removeListener(e,t[n]);return this},s.prototype.listeners=function(e){return f(this,e,!0)},s.prototype.rawListeners=function(e){return f(this,e,!1)},s.listenerCount=function(e,t){return"function"==typeof e.listenerCount?e.listenerCount(t):p.call(e,t)},s.prototype.listenerCount=p,s.prototype.eventNames=function(){return this._eventsCount>0?t(this._events):[]}},"./node_modules/webworkify-webpack/index.js": /*!**************************************************!*\ !*** ./node_modules/webworkify-webpack/index.js ***! \**************************************************/ function(e,t,i){function n(e){var t={};function i(n){if(t[n])return t[n].exports;var r=t[n]={i:n,l:!1,exports:{}};return e[n].call(r.exports,r,r.exports,i),r.l=!0,r.exports}i.m=e,i.c=t,i.i=function(e){return e},i.d=function(e,t,n){i.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:n})},i.r=function(e){Object.defineProperty(e,"__esModule",{value:!0})},i.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="/",i.oe=function(e){throw console.error(e),e};var n=i(i.s=ENTRY_MODULE);return n.default||n}function r(e){return(e+"").replace(/[.?*+^$[\]\\(){}|-]/g,"\\$&")}function a(e,t,n){var a={};a[n]=[];var s=t.toString(),o=s.match(/^function\s?\w*\(\w+,\s*\w+,\s*(\w+)\)/);if(!o)return a;for(var u,l=o[1],h=new RegExp("(\\\\n|\\W)"+r(l)+"\\(\\s*(/\\*.*?\\*/)?\\s*.*?([\\.|\\-|\\+|\\w|/|@]+).*?\\)","g");u=h.exec(s);)"dll-reference"!==u[3]&&a[n].push(u[3]);for(h=new RegExp("\\("+r(l)+'\\("(dll-reference\\s([\\.|\\-|\\+|\\w|/|@]+))"\\)\\)\\(\\s*(/\\*.*?\\*/)?\\s*.*?([\\.|\\-|\\+|\\w|/|@]+).*?\\)',"g");u=h.exec(s);)e[u[2]]||(a[n].push(u[1]),e[u[2]]=i(u[1]).m),a[u[2]]=a[u[2]]||[],a[u[2]].push(u[4]);for(var d,c=Object.keys(a),f=0;f0}),!1)}e.exports=function(e,t){t=t||{};var r={main:i.m},o=t.all?{main:Object.keys(r.main)}:function(e,t){for(var i={main:[t]},n={main:[]},r={main:{}};s(i);)for(var o=Object.keys(i),u=0;u=e[r]&&t0&&e[0].originalDts=t[r].dts&&et[n].lastSample.originalDts&&e=t[n].lastSample.originalDts&&(n===t.length-1||n0&&(r=this._searchNearestSegmentBefore(i.originalBeginDts)+1),this._lastAppendLocation=r,this._list.splice(r,0,i)},e.prototype.getLastSegmentBefore=function(e){var t=this._searchNearestSegmentBefore(e);return t>=0?this._list[t]:null},e.prototype.getLastSampleBefore=function(e){var t=this.getLastSegmentBefore(e);return null!=t?t.lastSample:null},e.prototype.getLastSyncPointBefore=function(e){for(var t=this._searchNearestSegmentBefore(e),i=this._list[t].syncPoints;0===i.length&&t>0;)t--,i=this._list[t].syncPoints;return i.length>0?i[i.length-1]:null},e}()},"./src/core/mse-controller.js": /*!************************************!*\ !*** ./src/core/mse-controller.js ***! \************************************/ function(e,t,i){i.r(t);var n=i( /*! events */ "./node_modules/events/events.js"),r=i.n(n),a=i( /*! ../utils/logger.js */ "./src/utils/logger.js"),s=i( /*! ../utils/browser.js */ "./src/utils/browser.js"),o=i( /*! ./mse-events.js */ "./src/core/mse-events.js"),u=i( /*! ./media-segment-info.js */ "./src/core/media-segment-info.js"),l=i( /*! ../utils/exception.js */ "./src/utils/exception.js"),h=function(){function e(e){this.TAG="MSEController",this._config=e,this._emitter=new(r()),this._config.isLive&&null==this._config.autoCleanupSourceBuffer&&(this._config.autoCleanupSourceBuffer=!0),this.e={onSourceOpen:this._onSourceOpen.bind(this),onSourceEnded:this._onSourceEnded.bind(this),onSourceClose:this._onSourceClose.bind(this),onSourceBufferError:this._onSourceBufferError.bind(this),onSourceBufferUpdateEnd:this._onSourceBufferUpdateEnd.bind(this)},this._mediaSource=null,this._mediaSourceObjectURL=null,this._mediaElement=null,this._isBufferFull=!1,this._hasPendingEos=!1,this._requireSetMediaDuration=!1,this._pendingMediaDuration=0,this._pendingSourceBufferInit=[],this._mimeTypes={video:null,audio:null},this._sourceBuffers={video:null,audio:null},this._lastInitSegments={video:null,audio:null},this._pendingSegments={video:[],audio:[]},this._pendingRemoveRanges={video:[],audio:[]},this._idrList=new u.IDRSampleList}return e.prototype.destroy=function(){(this._mediaElement||this._mediaSource)&&this.detachMediaElement(),this.e=null,this._emitter.removeAllListeners(),this._emitter=null},e.prototype.on=function(e,t){this._emitter.addListener(e,t)},e.prototype.off=function(e,t){this._emitter.removeListener(e,t)},e.prototype.attachMediaElement=function(e){if(this._mediaSource)throw new l.IllegalStateException("MediaSource has been attached to an HTMLMediaElement!");var t=this._mediaSource=new window.MediaSource;t.addEventListener("sourceopen",this.e.onSourceOpen),t.addEventListener("sourceended",this.e.onSourceEnded),t.addEventListener("sourceclose",this.e.onSourceClose),this._mediaElement=e,this._mediaSourceObjectURL=window.URL.createObjectURL(this._mediaSource),e.src=this._mediaSourceObjectURL},e.prototype.detachMediaElement=function(){if(this._mediaSource){var e=this._mediaSource;for(var t in this._sourceBuffers){var i=this._pendingSegments[t];i.splice(0,i.length),this._pendingSegments[t]=null,this._pendingRemoveRanges[t]=null,this._lastInitSegments[t]=null;var n=this._sourceBuffers[t];if(n){if("closed"!==e.readyState){try{e.removeSourceBuffer(n)}catch(e){a.default.e(this.TAG,e.message)}n.removeEventListener("error",this.e.onSourceBufferError),n.removeEventListener("updateend",this.e.onSourceBufferUpdateEnd)}this._mimeTypes[t]=null,this._sourceBuffers[t]=null}}if("open"===e.readyState)try{e.endOfStream()}catch(e){a.default.e(this.TAG,e.message)}e.removeEventListener("sourceopen",this.e.onSourceOpen),e.removeEventListener("sourceended",this.e.onSourceEnded),e.removeEventListener("sourceclose",this.e.onSourceClose),this._pendingSourceBufferInit=[],this._isBufferFull=!1,this._idrList.clear(),this._mediaSource=null}this._mediaElement&&(this._mediaElement.src="",this._mediaElement.removeAttribute("src"),this._mediaElement=null),this._mediaSourceObjectURL&&(window.URL.revokeObjectURL(this._mediaSourceObjectURL),this._mediaSourceObjectURL=null)},e.prototype.appendInitSegment=function(e,t){if(!this._mediaSource||"open"!==this._mediaSource.readyState)return this._pendingSourceBufferInit.push(e),void this._pendingSegments[e.type].push(e);var i=e,n=""+i.container;i.codec&&i.codec.length>0&&(n+=";codecs="+i.codec);var r=!1;if(a.default.v(this.TAG,"Received Initialization Segment, mimeType: "+n),this._lastInitSegments[i.type]=i,n!==this._mimeTypes[i.type]){if(this._mimeTypes[i.type])a.default.v(this.TAG,"Notice: "+i.type+" mimeType changed, origin: "+this._mimeTypes[i.type]+", target: "+n);else{r=!0;try{var u=this._sourceBuffers[i.type]=this._mediaSource.addSourceBuffer(n);u.addEventListener("error",this.e.onSourceBufferError),u.addEventListener("updateend",this.e.onSourceBufferUpdateEnd)}catch(e){return a.default.e(this.TAG,e.message),void this._emitter.emit(o.default.ERROR,{code:e.code,msg:e.message})}}this._mimeTypes[i.type]=n}t||this._pendingSegments[i.type].push(i),r||this._sourceBuffers[i.type]&&!this._sourceBuffers[i.type].updating&&this._doAppendSegments(),s.default.safari&&"audio/mpeg"===i.container&&i.mediaDuration>0&&(this._requireSetMediaDuration=!0,this._pendingMediaDuration=i.mediaDuration/1e3,this._updateMediaSourceDuration())},e.prototype.appendMediaSegment=function(e){var t=e;this._pendingSegments[t.type].push(t),this._config.autoCleanupSourceBuffer&&this._needCleanupSourceBuffer()&&this._doCleanupSourceBuffer();var i=this._sourceBuffers[t.type];!i||i.updating||this._hasPendingRemoveRanges()||this._doAppendSegments()},e.prototype.seek=function(e){for(var t in this._sourceBuffers)if(this._sourceBuffers[t]){var i=this._sourceBuffers[t];if("open"===this._mediaSource.readyState)try{i.abort()}catch(e){a.default.e(this.TAG,e.message)}this._idrList.clear();var n=this._pendingSegments[t];if(n.splice(0,n.length),"closed"!==this._mediaSource.readyState){for(var r=0;r=1&&e-n.start(0)>=this._config.autoCleanupMaxBackwardDuration)return!0}}return!1},e.prototype._doCleanupSourceBuffer=function(){var e=this._mediaElement.currentTime;for(var t in this._sourceBuffers){var i=this._sourceBuffers[t];if(i){for(var n=i.buffered,r=!1,a=0;a=this._config.autoCleanupMaxBackwardDuration){r=!0;var u=e-this._config.autoCleanupMinBackwardDuration;this._pendingRemoveRanges[t].push({start:s,end:u})}}else o0&&(isNaN(t)||i>t)&&(a.default.v(this.TAG,"Update MediaSource duration from "+t+" to "+i),this._mediaSource.duration=i),this._requireSetMediaDuration=!1,this._pendingMediaDuration=0}},e.prototype._doRemoveRanges=function(){for(var e in this._pendingRemoveRanges)if(this._sourceBuffers[e]&&!this._sourceBuffers[e].updating)for(var t=this._sourceBuffers[e],i=this._pendingRemoveRanges[e];i.length&&!t.updating;){var n=i.shift();t.remove(n.start,n.end)}},e.prototype._doAppendSegments=function(){var e=this._pendingSegments;for(var t in e)if(this._sourceBuffers[t]&&!this._sourceBuffers[t].updating&&e[t].length>0){var i=e[t].shift();if(i.timestampOffset){var n=this._sourceBuffers[t].timestampOffset,r=i.timestampOffset/1e3;Math.abs(n-r)>.1&&(a.default.v(this.TAG,"Update MPEG audio timestampOffset from "+n+" to "+r),this._sourceBuffers[t].timestampOffset=r),delete i.timestampOffset}if(!i.data||0===i.data.byteLength)continue;try{this._sourceBuffers[t].appendBuffer(i.data),this._isBufferFull=!1,"video"===t&&i.hasOwnProperty("info")&&this._idrList.appendArray(i.info.syncPoints)}catch(e){this._pendingSegments[t].unshift(i),22===e.code?(this._isBufferFull||this._emitter.emit(o.default.BUFFER_FULL),this._isBufferFull=!0):(a.default.e(this.TAG,t,e.message),this._emitter.emit(o.default.ERROR,{code:e.code,msg:e.message}))}}},e.prototype._onSourceOpen=function(){if(a.default.v(this.TAG,"MediaSource onSourceOpen"),this._mediaSource.removeEventListener("sourceopen",this.e.onSourceOpen),this._pendingSourceBufferInit.length>0)for(var e=this._pendingSourceBufferInit;e.length;){var t=e.shift();this.appendInitSegment(t,!0)}this._hasPendingSegments()&&this._doAppendSegments(),this._emitter.emit(o.default.SOURCE_OPEN)},e.prototype._onSourceEnded=function(){a.default.v(this.TAG,"MediaSource onSourceEnded")},e.prototype._onSourceClose=function(){a.default.v(this.TAG,"MediaSource onSourceClose"),this._mediaSource&&null!=this.e&&(this._mediaSource.removeEventListener("sourceopen",this.e.onSourceOpen),this._mediaSource.removeEventListener("sourceended",this.e.onSourceEnded),this._mediaSource.removeEventListener("sourceclose",this.e.onSourceClose))},e.prototype._hasPendingSegments=function(){var e=this._pendingSegments;return(e.video&&e.video.length)>0||e.audio&&e.audio.length>0},e.prototype._hasPendingRemoveRanges=function(){var e=this._pendingRemoveRanges;return(e.video&&e.video.length)>0||e.audio&&e.audio.length>0},e.prototype._onSourceBufferUpdateEnd=function(){this._requireSetMediaDuration?this._updateMediaSourceDuration():this._hasPendingRemoveRanges()?this._doRemoveRanges():this._hasPendingSegments()?this._doAppendSegments():this._hasPendingEos&&this.endOfStream(),this._emitter.emit(o.default.UPDATE_END)},e.prototype._onSourceBufferError=function(e){a.default.e(this.TAG,"SourceBuffer Error: "+e)},e}();t.default=h},"./src/core/mse-events.js": /*!********************************!*\ !*** ./src/core/mse-events.js ***! \********************************/ function(e,t,i){i.r(t),t.default={ERROR:"error",SOURCE_OPEN:"source_open",UPDATE_END:"update_end",BUFFER_FULL:"buffer_full"}},"./src/core/transmuxer.js": /*!********************************!*\ !*** ./src/core/transmuxer.js ***! \********************************/ function(e,t,i){i.r(t);var n=i( /*! events */ "./node_modules/events/events.js"),r=i.n(n),a=i( /*! webworkify-webpack */ "./node_modules/webworkify-webpack/index.js"),s=i.n(a),o=i( /*! ../utils/logger.js */ "./src/utils/logger.js"),u=i( /*! ../utils/logging-control.js */ "./src/utils/logging-control.js"),l=i( /*! ./transmuxing-controller.js */ "./src/core/transmuxing-controller.js"),h=i( /*! ./transmuxing-events.js */ "./src/core/transmuxing-events.js"),d=i( /*! ./media-info.js */ "./src/core/media-info.js"),c=function(){function e(e,t){if(this.TAG="Transmuxer",this._emitter=new(r()),t.enableWorker&&"undefined"!=typeof Worker)try{this._worker=s()( /*! ./transmuxing-worker */ "./src/core/transmuxing-worker.js"),this._workerDestroying=!1,this._worker.addEventListener("message",this._onWorkerMessage.bind(this)),this._worker.postMessage({cmd:"init",param:[e,t]}),this.e={onLoggingConfigChanged:this._onLoggingConfigChanged.bind(this)},u.default.registerListener(this.e.onLoggingConfigChanged),this._worker.postMessage({cmd:"logging_config",param:u.default.getConfig()})}catch(i){o.default.e(this.TAG,"Error while initialize transmuxing worker, fallback to inline transmuxing"),this._worker=null,this._controller=new l.default(e,t)}else this._controller=new l.default(e,t);if(this._controller){var i=this._controller;i.on(h.default.IO_ERROR,this._onIOError.bind(this)),i.on(h.default.DEMUX_ERROR,this._onDemuxError.bind(this)),i.on(h.default.INIT_SEGMENT,this._onInitSegment.bind(this)),i.on(h.default.MEDIA_SEGMENT,this._onMediaSegment.bind(this)),i.on(h.default.LOADING_COMPLETE,this._onLoadingComplete.bind(this)),i.on(h.default.RECOVERED_EARLY_EOF,this._onRecoveredEarlyEof.bind(this)),i.on(h.default.MEDIA_INFO,this._onMediaInfo.bind(this)),i.on(h.default.METADATA_ARRIVED,this._onMetaDataArrived.bind(this)),i.on(h.default.SCRIPTDATA_ARRIVED,this._onScriptDataArrived.bind(this)),i.on(h.default.STATISTICS_INFO,this._onStatisticsInfo.bind(this)),i.on(h.default.RECOMMEND_SEEKPOINT,this._onRecommendSeekpoint.bind(this))}}return e.prototype.destroy=function(){this._worker?this._workerDestroying||(this._workerDestroying=!0,this._worker.postMessage({cmd:"destroy"}),u.default.removeListener(this.e.onLoggingConfigChanged),this.e=null):(this._controller.destroy(),this._controller=null),this._emitter.removeAllListeners(),this._emitter=null},e.prototype.on=function(e,t){this._emitter.addListener(e,t)},e.prototype.off=function(e,t){this._emitter.removeListener(e,t)},e.prototype.hasWorker=function(){return null!=this._worker},e.prototype.open=function(){this._worker?this._worker.postMessage({cmd:"start"}):this._controller.start()},e.prototype.close=function(){this._worker?this._worker.postMessage({cmd:"stop"}):this._controller.stop()},e.prototype.seek=function(e){this._worker?this._worker.postMessage({cmd:"seek",param:e}):this._controller.seek(e)},e.prototype.pause=function(){this._worker?this._worker.postMessage({cmd:"pause"}):this._controller.pause()},e.prototype.resume=function(){this._worker?this._worker.postMessage({cmd:"resume"}):this._controller.resume()},e.prototype._onInitSegment=function(e,t){var i=this;Promise.resolve().then((function(){i._emitter.emit(h.default.INIT_SEGMENT,e,t)}))},e.prototype._onMediaSegment=function(e,t){var i=this;Promise.resolve().then((function(){i._emitter.emit(h.default.MEDIA_SEGMENT,e,t)}))},e.prototype._onLoadingComplete=function(){var e=this;Promise.resolve().then((function(){e._emitter.emit(h.default.LOADING_COMPLETE)}))},e.prototype._onRecoveredEarlyEof=function(){var e=this;Promise.resolve().then((function(){e._emitter.emit(h.default.RECOVERED_EARLY_EOF)}))},e.prototype._onMediaInfo=function(e){var t=this;Promise.resolve().then((function(){t._emitter.emit(h.default.MEDIA_INFO,e)}))},e.prototype._onMetaDataArrived=function(e){var t=this;Promise.resolve().then((function(){t._emitter.emit(h.default.METADATA_ARRIVED,e)}))},e.prototype._onScriptDataArrived=function(e){var t=this;Promise.resolve().then((function(){t._emitter.emit(h.default.SCRIPTDATA_ARRIVED,e)}))},e.prototype._onStatisticsInfo=function(e){var t=this;Promise.resolve().then((function(){t._emitter.emit(h.default.STATISTICS_INFO,e)}))},e.prototype._onIOError=function(e,t){var i=this;Promise.resolve().then((function(){i._emitter.emit(h.default.IO_ERROR,e,t)}))},e.prototype._onDemuxError=function(e,t){var i=this;Promise.resolve().then((function(){i._emitter.emit(h.default.DEMUX_ERROR,e,t)}))},e.prototype._onRecommendSeekpoint=function(e){var t=this;Promise.resolve().then((function(){t._emitter.emit(h.default.RECOMMEND_SEEKPOINT,e)}))},e.prototype._onLoggingConfigChanged=function(e){this._worker&&this._worker.postMessage({cmd:"logging_config",param:e})},e.prototype._onWorkerMessage=function(e){var t=e.data,i=t.data;if("destroyed"===t.msg||this._workerDestroying)return this._workerDestroying=!1,this._worker.terminate(),void(this._worker=null);switch(t.msg){case h.default.INIT_SEGMENT:case h.default.MEDIA_SEGMENT:this._emitter.emit(t.msg,i.type,i.data);break;case h.default.LOADING_COMPLETE:case h.default.RECOVERED_EARLY_EOF:this._emitter.emit(t.msg);break;case h.default.MEDIA_INFO:Object.setPrototypeOf(i,d.default.prototype),this._emitter.emit(t.msg,i);break;case h.default.METADATA_ARRIVED:case h.default.SCRIPTDATA_ARRIVED:case h.default.STATISTICS_INFO:this._emitter.emit(t.msg,i);break;case h.default.IO_ERROR:case h.default.DEMUX_ERROR:this._emitter.emit(t.msg,i.type,i.info);break;case h.default.RECOMMEND_SEEKPOINT:this._emitter.emit(t.msg,i);break;case"logcat_callback":o.default.emitter.emit("log",i.type,i.logcat)}},e}();t.default=c},"./src/core/transmuxing-controller.js": /*!********************************************!*\ !*** ./src/core/transmuxing-controller.js ***! \********************************************/ function(e,t,i){i.r(t);var n=i( /*! events */ "./node_modules/events/events.js"),r=i.n(n),a=i( /*! ../utils/logger.js */ "./src/utils/logger.js"),s=i( /*! ../utils/browser.js */ "./src/utils/browser.js"),o=i( /*! ./media-info.js */ "./src/core/media-info.js"),u=i( /*! ../demux/flv-demuxer.js */ "./src/demux/flv-demuxer.js"),l=i( /*! ../remux/mp4-remuxer.js */ "./src/remux/mp4-remuxer.js"),h=i( /*! ../demux/demux-errors.js */ "./src/demux/demux-errors.js"),d=i( /*! ../io/io-controller.js */ "./src/io/io-controller.js"),c=i( /*! ./transmuxing-events.js */ "./src/core/transmuxing-events.js"),f=function(){function e(e,t){this.TAG="TransmuxingController",this._emitter=new(r()),this._config=t,e.segments||(e.segments=[{duration:e.duration,filesize:e.filesize,url:e.url}]),"boolean"!=typeof e.cors&&(e.cors=!0),"boolean"!=typeof e.withCredentials&&(e.withCredentials=!1),this._mediaDataSource=e,this._currentSegmentIndex=0;var i=0;this._mediaDataSource.segments.forEach((function(n){n.timestampBase=i,i+=n.duration,n.cors=e.cors,n.withCredentials=e.withCredentials,t.referrerPolicy&&(n.referrerPolicy=t.referrerPolicy)})),isNaN(i)||this._mediaDataSource.duration===i||(this._mediaDataSource.duration=i),this._mediaInfo=null,this._demuxer=null,this._remuxer=null,this._ioctl=null,this._pendingSeekTime=null,this._pendingResolveSeekPoint=null,this._statisticsReporter=null}return e.prototype.destroy=function(){this._mediaInfo=null,this._mediaDataSource=null,this._statisticsReporter&&this._disableStatisticsReporter(),this._ioctl&&(this._ioctl.destroy(),this._ioctl=null),this._demuxer&&(this._demuxer.destroy(),this._demuxer=null),this._remuxer&&(this._remuxer.destroy(),this._remuxer=null),this._emitter.removeAllListeners(),this._emitter=null},e.prototype.on=function(e,t){this._emitter.addListener(e,t)},e.prototype.off=function(e,t){this._emitter.removeListener(e,t)},e.prototype.start=function(){this._loadSegment(0),this._enableStatisticsReporter()},e.prototype._loadSegment=function(e,t){this._currentSegmentIndex=e;var i=this._mediaDataSource.segments[e],n=this._ioctl=new d.default(i,this._config,e);n.onError=this._onIOException.bind(this),n.onSeeked=this._onIOSeeked.bind(this),n.onComplete=this._onIOComplete.bind(this),n.onRedirect=this._onIORedirect.bind(this),n.onRecoveredEarlyEof=this._onIORecoveredEarlyEof.bind(this),t?this._demuxer.bindDataSource(this._ioctl):n.onDataArrival=this._onInitChunkArrival.bind(this),n.open(t)},e.prototype.stop=function(){this._internalAbort(),this._disableStatisticsReporter()},e.prototype._internalAbort=function(){this._ioctl&&(this._ioctl.destroy(),this._ioctl=null)},e.prototype.pause=function(){this._ioctl&&this._ioctl.isWorking()&&(this._ioctl.pause(),this._disableStatisticsReporter())},e.prototype.resume=function(){this._ioctl&&this._ioctl.isPaused()&&(this._ioctl.resume(),this._enableStatisticsReporter())},e.prototype.seek=function(e){if(null!=this._mediaInfo&&this._mediaInfo.isSeekable()){var t=this._searchSegmentIndexContains(e);if(t===this._currentSegmentIndex){var i=this._mediaInfo.segments[t];if(null==i)this._pendingSeekTime=e;else{var n=i.getNearestKeyframe(e);this._remuxer.seek(n.milliseconds),this._ioctl.seek(n.fileposition),this._pendingResolveSeekPoint=n.milliseconds}}else{var r=this._mediaInfo.segments[t];null==r?(this._pendingSeekTime=e,this._internalAbort(),this._remuxer.seek(),this._remuxer.insertDiscontinuity(),this._loadSegment(t)):(n=r.getNearestKeyframe(e),this._internalAbort(),this._remuxer.seek(e),this._remuxer.insertDiscontinuity(),this._demuxer.resetMediaInfo(),this._demuxer.timestampBase=this._mediaDataSource.segments[t].timestampBase,this._loadSegment(t,n.fileposition),this._pendingResolveSeekPoint=n.milliseconds,this._reportSegmentMediaInfo(t))}this._enableStatisticsReporter()}},e.prototype._searchSegmentIndexContains=function(e){for(var t=this._mediaDataSource.segments,i=t.length-1,n=0;n0)this._demuxer.bindDataSource(this._ioctl),this._demuxer.timestampBase=this._mediaDataSource.segments[this._currentSegmentIndex].timestampBase,r=this._demuxer.parseChunks(e,t);else if((n=u.default.probe(e)).match){this._demuxer=new u.default(n,this._config),this._remuxer||(this._remuxer=new l.default(this._config));var s=this._mediaDataSource;null==s.duration||isNaN(s.duration)||(this._demuxer.overridedDuration=s.duration),"boolean"==typeof s.hasAudio&&(this._demuxer.overridedHasAudio=s.hasAudio),"boolean"==typeof s.hasVideo&&(this._demuxer.overridedHasVideo=s.hasVideo),this._demuxer.timestampBase=s.segments[this._currentSegmentIndex].timestampBase,this._demuxer.onError=this._onDemuxException.bind(this),this._demuxer.onMediaInfo=this._onMediaInfo.bind(this),this._demuxer.onMetaDataArrived=this._onMetaDataArrived.bind(this),this._demuxer.onScriptDataArrived=this._onScriptDataArrived.bind(this),this._remuxer.bindDataSource(this._demuxer.bindDataSource(this._ioctl)),this._remuxer.onInitSegment=this._onRemuxerInitSegmentArrival.bind(this),this._remuxer.onMediaSegment=this._onRemuxerMediaSegmentArrival.bind(this),r=this._demuxer.parseChunks(e,t)}else n=null,a.default.e(this.TAG,"Non-FLV, Unsupported media type!"),Promise.resolve().then((function(){i._internalAbort()})),this._emitter.emit(c.default.DEMUX_ERROR,h.default.FORMAT_UNSUPPORTED,"Non-FLV, Unsupported media type"),r=0;return r},e.prototype._onMediaInfo=function(e){var t=this;null==this._mediaInfo&&(this._mediaInfo=Object.assign({},e),this._mediaInfo.keyframesIndex=null,this._mediaInfo.segments=[],this._mediaInfo.segmentCount=this._mediaDataSource.segments.length,Object.setPrototypeOf(this._mediaInfo,o.default.prototype));var i=Object.assign({},e);Object.setPrototypeOf(i,o.default.prototype),this._mediaInfo.segments[this._currentSegmentIndex]=i,this._reportSegmentMediaInfo(this._currentSegmentIndex),null!=this._pendingSeekTime&&Promise.resolve().then((function(){var e=t._pendingSeekTime;t._pendingSeekTime=null,t.seek(e)}))},e.prototype._onMetaDataArrived=function(e){this._emitter.emit(c.default.METADATA_ARRIVED,e)},e.prototype._onScriptDataArrived=function(e){this._emitter.emit(c.default.SCRIPTDATA_ARRIVED,e)},e.prototype._onIOSeeked=function(){this._remuxer.insertDiscontinuity()},e.prototype._onIOComplete=function(e){var t=e+1;t0&&i[0].originalDts===n&&(n=i[0].pts),this._emitter.emit(c.default.RECOMMEND_SEEKPOINT,n)}},e.prototype._enableStatisticsReporter=function(){null==this._statisticsReporter&&(this._statisticsReporter=self.setInterval(this._reportStatisticsInfo.bind(this),this._config.statisticsInfoReportInterval))},e.prototype._disableStatisticsReporter=function(){this._statisticsReporter&&(self.clearInterval(this._statisticsReporter),this._statisticsReporter=null)},e.prototype._reportSegmentMediaInfo=function(e){var t=this._mediaInfo.segments[e],i=Object.assign({},t);i.duration=this._mediaInfo.duration,i.segmentCount=this._mediaInfo.segmentCount,delete i.segments,delete i.keyframesIndex,this._emitter.emit(c.default.MEDIA_INFO,i)},e.prototype._reportStatisticsInfo=function(){var e={};e.url=this._ioctl.currentURL,e.hasRedirect=this._ioctl.hasRedirect,e.hasRedirect&&(e.redirectedURL=this._ioctl.currentRedirectedURL),e.speed=this._ioctl.currentSpeed,e.loaderType=this._ioctl.loaderType,e.currentSegmentIndex=this._currentSegmentIndex,e.totalSegmentCount=this._mediaDataSource.segments.length,this._emitter.emit(c.default.STATISTICS_INFO,e)},e}();t.default=f},"./src/core/transmuxing-events.js": /*!****************************************!*\ !*** ./src/core/transmuxing-events.js ***! \****************************************/ function(e,t,i){i.r(t),t.default={IO_ERROR:"io_error",DEMUX_ERROR:"demux_error",INIT_SEGMENT:"init_segment",MEDIA_SEGMENT:"media_segment",LOADING_COMPLETE:"loading_complete",RECOVERED_EARLY_EOF:"recovered_early_eof",MEDIA_INFO:"media_info",METADATA_ARRIVED:"metadata_arrived",SCRIPTDATA_ARRIVED:"scriptdata_arrived",STATISTICS_INFO:"statistics_info",RECOMMEND_SEEKPOINT:"recommend_seekpoint"}},"./src/core/transmuxing-worker.js": /*!****************************************!*\ !*** ./src/core/transmuxing-worker.js ***! \****************************************/ function(e,t,i){i.r(t);var n=i( /*! ../utils/logging-control.js */ "./src/utils/logging-control.js"),r=i( /*! ../utils/polyfill.js */ "./src/utils/polyfill.js"),a=i( /*! ./transmuxing-controller.js */ "./src/core/transmuxing-controller.js"),s=i( /*! ./transmuxing-events.js */ "./src/core/transmuxing-events.js");t.default=function(e){var t=null,i=function(t,i){e.postMessage({msg:"logcat_callback",data:{type:t,logcat:i}})}.bind(this);function o(t,i){var n={msg:s.default.INIT_SEGMENT,data:{type:t,data:i}};e.postMessage(n,[i.data])}function u(t,i){var n={msg:s.default.MEDIA_SEGMENT,data:{type:t,data:i}};e.postMessage(n,[i.data])}function l(){var t={msg:s.default.LOADING_COMPLETE};e.postMessage(t)}function h(){var t={msg:s.default.RECOVERED_EARLY_EOF};e.postMessage(t)}function d(t){var i={msg:s.default.MEDIA_INFO,data:t};e.postMessage(i)}function c(t){var i={msg:s.default.METADATA_ARRIVED,data:t};e.postMessage(i)}function f(t){var i={msg:s.default.SCRIPTDATA_ARRIVED,data:t};e.postMessage(i)}function p(t){var i={msg:s.default.STATISTICS_INFO,data:t};e.postMessage(i)}function m(t,i){e.postMessage({msg:s.default.IO_ERROR,data:{type:t,info:i}})}function _(t,i){e.postMessage({msg:s.default.DEMUX_ERROR,data:{type:t,info:i}})}function g(t){e.postMessage({msg:s.default.RECOMMEND_SEEKPOINT,data:t})}r.default.install(),e.addEventListener("message",(function(r){switch(r.data.cmd){case"init":(t=new a.default(r.data.param[0],r.data.param[1])).on(s.default.IO_ERROR,m.bind(this)),t.on(s.default.DEMUX_ERROR,_.bind(this)),t.on(s.default.INIT_SEGMENT,o.bind(this)),t.on(s.default.MEDIA_SEGMENT,u.bind(this)),t.on(s.default.LOADING_COMPLETE,l.bind(this)),t.on(s.default.RECOVERED_EARLY_EOF,h.bind(this)),t.on(s.default.MEDIA_INFO,d.bind(this)),t.on(s.default.METADATA_ARRIVED,c.bind(this)),t.on(s.default.SCRIPTDATA_ARRIVED,f.bind(this)),t.on(s.default.STATISTICS_INFO,p.bind(this)),t.on(s.default.RECOMMEND_SEEKPOINT,g.bind(this));break;case"destroy":t&&(t.destroy(),t=null),e.postMessage({msg:"destroyed"});break;case"start":t.start();break;case"stop":t.stop();break;case"seek":t.seek(r.data.param);break;case"pause":t.pause();break;case"resume":t.resume();break;case"logging_config":var v=r.data.param;n.default.applyConfig(v),!0===v.enableCallback?n.default.addLogListener(i):n.default.removeLogListener(i)}}))}},"./src/demux/amf-parser.js": /*!*********************************!*\ !*** ./src/demux/amf-parser.js ***! \*********************************/ function(e,t,i){i.r(t);var n,r=i( /*! ../utils/logger.js */ "./src/utils/logger.js"),a=i( /*! ../utils/utf8-conv.js */ "./src/utils/utf8-conv.js"),s=i( /*! ../utils/exception.js */ "./src/utils/exception.js"),o=(n=new ArrayBuffer(2),new DataView(n).setInt16(0,256,!0),256===new Int16Array(n)[0]),u=function(){function e(){}return e.parseScriptData=function(t,i,n){var a={};try{var s=e.parseValue(t,i,n),o=e.parseValue(t,i+s.size,n-s.size);a[s.data]=o.data}catch(e){r.default.e("AMF",e.toString())}return a},e.parseObject=function(t,i,n){if(n<3)throw new s.IllegalStateException("Data not enough when parse ScriptDataObject");var r=e.parseString(t,i,n),a=e.parseValue(t,i+r.size,n-r.size),o=a.objectEnd;return{data:{name:r.data,value:a.data},size:r.size+a.size,objectEnd:o}},e.parseVariable=function(t,i,n){return e.parseObject(t,i,n)},e.parseString=function(e,t,i){if(i<2)throw new s.IllegalStateException("Data not enough when parse String");var n=new DataView(e,t,i).getUint16(0,!o);return{data:n>0?(0,a.default)(new Uint8Array(e,t+2,n)):"",size:2+n}},e.parseLongString=function(e,t,i){if(i<4)throw new s.IllegalStateException("Data not enough when parse LongString");var n=new DataView(e,t,i).getUint32(0,!o);return{data:n>0?(0,a.default)(new Uint8Array(e,t+4,n)):"",size:4+n}},e.parseDate=function(e,t,i){if(i<10)throw new s.IllegalStateException("Data size invalid when parse Date");var n=new DataView(e,t,i),r=n.getFloat64(0,!o),a=n.getInt16(8,!o);return{data:new Date(r+=60*a*1e3),size:10}},e.parseValue=function(t,i,n){if(n<1)throw new s.IllegalStateException("Data not enough when parse Value");var a,u=new DataView(t,i,n),l=1,h=u.getUint8(0),d=!1;try{switch(h){case 0:a=u.getFloat64(1,!o),l+=8;break;case 1:a=!!u.getUint8(1),l+=1;break;case 2:var c=e.parseString(t,i+1,n-1);a=c.data,l+=c.size;break;case 3:a={};var f=0;for(9==(16777215&u.getUint32(n-4,!o))&&(f=3);l32)throw new n.InvalidArgumentException("ExpGolomb: readBits() bits exceeded max 32bits!");if(e<=this._current_word_bits_left){var t=this._current_word>>>32-e;return this._current_word<<=e,this._current_word_bits_left-=e,t}var i=this._current_word_bits_left?this._current_word:0;i>>>=32-this._current_word_bits_left;var r=e-this._current_word_bits_left;this._fillCurrentWord();var a=Math.min(r,this._current_word_bits_left),s=this._current_word>>>32-a;return this._current_word<<=a,this._current_word_bits_left-=a,i=i<>>e))return this._current_word<<=e,this._current_word_bits_left-=e,e;return this._fillCurrentWord(),e+this._skipLeadingZero()},e.prototype.readUEG=function(){var e=this._skipLeadingZero();return this.readBits(e+1)-1},e.prototype.readSEG=function(){var e=this.readUEG();return 1&e?e+1>>>1:-1*(e>>>1)},e}();t.default=r},"./src/demux/flv-demuxer.js": /*!**********************************!*\ !*** ./src/demux/flv-demuxer.js ***! \**********************************/ function(e,t,i){i.r(t);var r=i( /*! ../utils/logger.js */ "./src/utils/logger.js"),a=i( /*! ./amf-parser.js */ "./src/demux/amf-parser.js"),s=i( /*! ./sps-parser.js */ "./src/demux/sps-parser.js"),o=i( /*! ./hevc-sps-parser.js */ "./src/demux/hevc-sps-parser.js"),u=i( /*! ./demux-errors.js */ "./src/demux/demux-errors.js"),l=i( /*! ../core/media-info.js */ "./src/core/media-info.js"),h=i( /*! ../utils/exception.js */ "./src/utils/exception.js"),d=function(){function e(e,t){var i;this.TAG="FLVDemuxer",this._config=t,this._onError=null,this._onMediaInfo=null,this._onMetaDataArrived=null,this._onScriptDataArrived=null,this._onTrackMetadata=null,this._onDataAvailable=null,this._dataOffset=e.dataOffset,this._firstParse=!0,this._dispatch=!1,this._hasAudio=e.hasAudioTrack,this._hasVideo=e.hasVideoTrack,this._hasAudioFlagOverrided=!1,this._hasVideoFlagOverrided=!1,this._audioInitialMetadataDispatched=!1,this._videoInitialMetadataDispatched=!1,this._mediaInfo=new l.default,this._mediaInfo.hasAudio=this._hasAudio,this._mediaInfo.hasVideo=this._hasVideo,this._metadata=null,this._audioMetadata=null,this._videoMetadata=null,this._naluLengthSize=4,this._timestampBase=0,this._timescale=1e3,this._duration=0,this._durationOverrided=!1,this._referenceFrameRate={fixed:!0,fps:23.976,fps_num:23976,fps_den:1e3},this._flvSoundRateTable=[5500,11025,22050,44100,48e3],this._mpegSamplingRates=[96e3,88200,64e3,48e3,44100,32e3,24e3,22050,16e3,12e3,11025,8e3,7350],this._mpegAudioV10SampleRateTable=[44100,48e3,32e3,0],this._mpegAudioV20SampleRateTable=[22050,24e3,16e3,0],this._mpegAudioV25SampleRateTable=[11025,12e3,8e3,0],this._mpegAudioL1BitRateTable=[0,32,64,96,128,160,192,224,256,288,320,352,384,416,448,-1],this._mpegAudioL2BitRateTable=[0,32,48,56,64,80,96,112,128,160,192,224,256,320,384,-1],this._mpegAudioL3BitRateTable=[0,32,40,48,56,64,80,96,112,128,160,192,224,256,320,-1],this._videoTrack={type:"video",id:1,sequenceNumber:0,samples:[],length:0},this._audioTrack={type:"audio",id:2,sequenceNumber:0,samples:[],length:0},this._littleEndian=(i=new ArrayBuffer(2),new DataView(i).setInt16(0,256,!0),256===new Int16Array(i)[0])}return e.prototype.destroy=function(){this._mediaInfo=null,this._metadata=null,this._audioMetadata=null,this._videoMetadata=null,this._videoTrack=null,this._audioTrack=null,this._onError=null,this._onMediaInfo=null,this._onMetaDataArrived=null,this._onScriptDataArrived=null,this._onTrackMetadata=null,this._onDataAvailable=null},e.probe=function(e){var t=new Uint8Array(e),i={match:!1};if(70!==t[0]||76!==t[1]||86!==t[2]||1!==t[3])return i;var n,r,a=(4&t[4])>>>2!=0,s=0!=(1&t[4]),o=(n=t)[r=5]<<24|n[r+1]<<16|n[r+2]<<8|n[r+3];return o<9?i:{match:!0,consumed:o,dataOffset:o,hasAudioTrack:a,hasVideoTrack:s}},e.prototype.bindDataSource=function(e){return e.onDataArrival=this.parseChunks.bind(this),this},Object.defineProperty(e.prototype,"onTrackMetadata",{get:function(){return this._onTrackMetadata},set:function(e){this._onTrackMetadata=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onMediaInfo",{get:function(){return this._onMediaInfo},set:function(e){this._onMediaInfo=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onMetaDataArrived",{get:function(){return this._onMetaDataArrived},set:function(e){this._onMetaDataArrived=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onScriptDataArrived",{get:function(){return this._onScriptDataArrived},set:function(e){this._onScriptDataArrived=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onError",{get:function(){return this._onError},set:function(e){this._onError=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onDataAvailable",{get:function(){return this._onDataAvailable},set:function(e){this._onDataAvailable=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"timestampBase",{get:function(){return this._timestampBase},set:function(e){this._timestampBase=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"overridedDuration",{get:function(){return this._duration},set:function(e){this._durationOverrided=!0,this._duration=e,this._mediaInfo.duration=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"overridedHasAudio",{set:function(e){this._hasAudioFlagOverrided=!0,this._hasAudio=e,this._mediaInfo.hasAudio=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"overridedHasVideo",{set:function(e){this._hasVideoFlagOverrided=!0,this._hasVideo=e,this._mediaInfo.hasVideo=e},enumerable:!1,configurable:!0}),e.prototype.resetMediaInfo=function(){this._mediaInfo=new l.default},e.prototype._isInitialMetadataDispatched=function(){return this._hasAudio&&this._hasVideo?this._audioInitialMetadataDispatched&&this._videoInitialMetadataDispatched:this._hasAudio&&!this._hasVideo?this._audioInitialMetadataDispatched:!(this._hasAudio||!this._hasVideo)&&this._videoInitialMetadataDispatched},e.prototype.parseChunks=function(t,i){if(!(this._onError&&this._onMediaInfo&&this._onTrackMetadata&&this._onDataAvailable))throw new h.IllegalStateException("Flv: onError & onMediaInfo & onTrackMetadata & onDataAvailable callback must be specified");var n=0,a=this._littleEndian;if(0===i){if(!(t.byteLength>13))return 0;n=e.probe(t).dataOffset}for(this._firstParse&&(this._firstParse=!1,i+n!==this._dataOffset&&r.default.w(this.TAG,"First time parsing but chunk byteStart invalid!"),0!==(s=new DataView(t,n)).getUint32(0,!a)&&r.default.w(this.TAG,"PrevTagSize0 !== 0 !!!"),n+=4);nt.byteLength)break;var o=s.getUint8(0),u=16777215&s.getUint32(0,!a);if(n+11+u+4>t.byteLength)break;if(8===o||9===o||18===o){var l=s.getUint8(4),d=s.getUint8(5),c=s.getUint8(6)|d<<8|l<<16|s.getUint8(7)<<24;0!=(16777215&s.getUint32(7,!a))&&r.default.w(this.TAG,"Meet tag which has StreamID != 0!");var f=n+11;switch(o){case 8:this._parseAudioData(t,f,u,c);break;case 9:this._parseVideoData(t,f,u,c,i+n);break;case 18:this._parseScriptData(t,f,u)}var p=s.getUint32(11+u,!a);p!==11+u&&r.default.w(this.TAG,"Invalid PrevTagSize "+p),n+=11+u+4}else r.default.w(this.TAG,"Unsupported tag type "+o+", skipped"),n+=11+u+4}return this._isInitialMetadataDispatched()&&this._dispatch&&(this._audioTrack.length||this._videoTrack.length)&&this._onDataAvailable(this._audioTrack,this._videoTrack),n},e.prototype._parseScriptData=function(e,t,i){var s=a.default.parseScriptData(e,t,i);if(s.hasOwnProperty("onMetaData")){if(null==s.onMetaData||"object"!==n(s.onMetaData))return void r.default.w(this.TAG,"Invalid onMetaData structure!");this._metadata&&r.default.w(this.TAG,"Found another onMetaData tag!"),this._metadata=s;var o=this._metadata.onMetaData;if(this._onMetaDataArrived&&this._onMetaDataArrived(Object.assign({},o)),"boolean"==typeof o.hasAudio&&!1===this._hasAudioFlagOverrided&&(this._hasAudio=o.hasAudio,this._mediaInfo.hasAudio=this._hasAudio),"boolean"==typeof o.hasVideo&&!1===this._hasVideoFlagOverrided&&(this._hasVideo=o.hasVideo,this._mediaInfo.hasVideo=this._hasVideo),"number"==typeof o.audiodatarate&&(this._mediaInfo.audioDataRate=o.audiodatarate),"number"==typeof o.videodatarate&&(this._mediaInfo.videoDataRate=o.videodatarate),"number"==typeof o.width&&(this._mediaInfo.width=o.width),"number"==typeof o.height&&(this._mediaInfo.height=o.height),"number"==typeof o.duration){if(!this._durationOverrided){var u=Math.floor(o.duration*this._timescale);this._duration=u,this._mediaInfo.duration=u}}else this._mediaInfo.duration=0;if("number"==typeof o.framerate){var l=Math.floor(1e3*o.framerate);if(l>0){var h=l/1e3;this._referenceFrameRate.fixed=!0,this._referenceFrameRate.fps=h,this._referenceFrameRate.fps_num=l,this._referenceFrameRate.fps_den=1e3,this._mediaInfo.fps=h}}if("object"===n(o.keyframes)){this._mediaInfo.hasKeyframesIndex=!0;var d=o.keyframes;this._mediaInfo.keyframesIndex=this._parseKeyframesIndex(d),o.keyframes=null}else this._mediaInfo.hasKeyframesIndex=!1;this._dispatch=!1,this._mediaInfo.metadata=o,r.default.v(this.TAG,"Parsed onMetaData"),this._mediaInfo.isComplete()&&this._onMediaInfo(this._mediaInfo)}Object.keys(s).length>0&&this._onScriptDataArrived&&this._onScriptDataArrived(Object.assign({},s))},e.prototype._parseKeyframesIndex=function(e){for(var t=[],i=[],n=1;n>>4;if(2===s||10===s){var o=0,l=(12&a)>>>2;if(l>=0&&l<=4){o=this._flvSoundRateTable[l];var h=1&a,d=this._audioMetadata,c=this._audioTrack;if(d||(!1===this._hasAudio&&!1===this._hasAudioFlagOverrided&&(this._hasAudio=!0,this._mediaInfo.hasAudio=!0),(d=this._audioMetadata={}).type="audio",d.id=c.id,d.timescale=this._timescale,d.duration=this._duration,d.audioSampleRate=o,d.channelCount=0===h?1:2),10===s){var f=this._parseAACAudioData(e,t+1,i-1);if(null==f)return;if(0===f.packetType){d.config&&r.default.w(this.TAG,"Found another AudioSpecificConfig!");var p=f.data;d.audioSampleRate=p.samplingRate,d.channelCount=p.channelCount,d.codec=p.codec,d.originalCodec=p.originalCodec,d.config=p.config,d.refSampleDuration=1024/d.audioSampleRate*d.timescale,r.default.v(this.TAG,"Parsed AudioSpecificConfig"),this._isInitialMetadataDispatched()?this._dispatch&&(this._audioTrack.length||this._videoTrack.length)&&this._onDataAvailable(this._audioTrack,this._videoTrack):this._audioInitialMetadataDispatched=!0,this._dispatch=!1,this._onTrackMetadata("audio",d),(g=this._mediaInfo).audioCodec=d.originalCodec,g.audioSampleRate=d.audioSampleRate,g.audioChannelCount=d.channelCount,g.hasVideo?null!=g.videoCodec&&(g.mimeType='video/x-flv; codecs="'+g.videoCodec+","+g.audioCodec+'"'):g.mimeType='video/x-flv; codecs="'+g.audioCodec+'"',g.isComplete()&&this._onMediaInfo(g)}else if(1===f.packetType){var m=this._timestampBase+n,_={unit:f.data,length:f.data.byteLength,dts:m,pts:m};c.samples.push(_),c.length+=f.data.length}else r.default.e(this.TAG,"Flv: Unsupported AAC data type "+f.packetType)}else if(2===s){if(!d.codec){var g;if(null==(p=this._parseMP3AudioData(e,t+1,i-1,!0)))return;d.audioSampleRate=p.samplingRate,d.channelCount=p.channelCount,d.codec=p.codec,d.originalCodec=p.originalCodec,d.refSampleDuration=1152/d.audioSampleRate*d.timescale,r.default.v(this.TAG,"Parsed MPEG Audio Frame Header"),this._audioInitialMetadataDispatched=!0,this._onTrackMetadata("audio",d),(g=this._mediaInfo).audioCodec=d.codec,g.audioSampleRate=d.audioSampleRate,g.audioChannelCount=d.channelCount,g.audioDataRate=p.bitRate,g.hasVideo?null!=g.videoCodec&&(g.mimeType='video/x-flv; codecs="'+g.videoCodec+","+g.audioCodec+'"'):g.mimeType='video/x-flv; codecs="'+g.audioCodec+'"',g.isComplete()&&this._onMediaInfo(g)}var v=this._parseMP3AudioData(e,t+1,i-1,!1);if(null==v)return;m=this._timestampBase+n;var y={unit:v,length:v.byteLength,dts:m,pts:m};c.samples.push(y),c.length+=v.length}}else this._onError(u.default.FORMAT_ERROR,"Flv: Invalid audio sample rate idx: "+l)}else this._onError(u.default.CODEC_UNSUPPORTED,"Flv: Unsupported audio codec idx: "+s)}},e.prototype._parseAACAudioData=function(e,t,i){if(!(i<=1)){var n={},a=new Uint8Array(e,t,i);return n.packetType=a[0],0===a[0]?n.data=this._parseAACAudioSpecificConfig(e,t+1,i-1):n.data=a.subarray(1),n}r.default.w(this.TAG,"Flv: Invalid AAC packet, missing AACPacketType or/and Data!")},e.prototype._parseAACAudioSpecificConfig=function(e,t,i){var n,r,a=new Uint8Array(e,t,i),s=null,o=0,l=null;if(o=n=a[0]>>>3,(r=(7&a[0])<<1|a[1]>>>7)<0||r>=this._mpegSamplingRates.length)this._onError(u.default.FORMAT_ERROR,"Flv: AAC invalid sampling frequency index!");else{var h=this._mpegSamplingRates[r],d=(120&a[1])>>>3;if(!(d<0||d>=8)){5===o&&(l=(7&a[1])<<1|a[2]>>>7,a[2]);var c=self.navigator.userAgent.toLowerCase();return-1!==c.indexOf("firefox")?r>=6?(o=5,s=new Array(4),l=r-3):(o=2,s=new Array(2),l=r):-1!==c.indexOf("android")?(o=2,s=new Array(2),l=r):(o=5,l=r,s=new Array(4),r>=6?l=r-3:1===d&&(o=2,s=new Array(2),l=r)),s[0]=o<<3,s[0]|=(15&r)>>>1,s[1]=(15&r)<<7,s[1]|=(15&d)<<3,5===o&&(s[1]|=(15&l)>>>1,s[2]=(1&l)<<7,s[2]|=8,s[3]=0),{config:s,samplingRate:h,channelCount:d,codec:"mp4a.40."+o,originalCodec:"mp4a.40."+n}}this._onError(u.default.FORMAT_ERROR,"Flv: AAC invalid channel configuration")}},e.prototype._parseMP3AudioData=function(e,t,i,n){if(!(i<4)){this._littleEndian;var a=new Uint8Array(e,t,i),s=null;if(n){if(255!==a[0])return;var o=a[1]>>>3&3,u=(6&a[1])>>1,l=(240&a[2])>>>4,h=(12&a[2])>>>2,d=3!=(a[3]>>>6&3)?2:1,c=0,f=0;switch(o){case 0:c=this._mpegAudioV25SampleRateTable[h];break;case 2:c=this._mpegAudioV20SampleRateTable[h];break;case 3:c=this._mpegAudioV10SampleRateTable[h]}switch(u){case 1:l>>4,l=15&s;7===l||12===l?7===l?this._parseAVCVideoPacket(e,t+1,i-1,n,a,o):12===l&&this._parseHVCVideoPacket(e,t+1,i-1,n,a,o):this._onError(u.default.CODEC_UNSUPPORTED,"Flv: Unsupported codec in video frame: "+l)}},e.prototype._parseAVCVideoPacket=function(e,t,i,n,a,s){if(i<4)r.default.w(this.TAG,"Flv: Invalid AVC packet, missing AVCPacketType or/and CompositionTime");else{var o=this._littleEndian,l=new DataView(e,t,i),h=l.getUint8(0),d=(16777215&l.getUint32(0,!o))<<8>>8;if(0===h)this._parseAVCDecoderConfigurationRecord(e,t+4,i-4);else if(1===h)this._parseAVCVideoData(e,t+4,i-4,n,a,s,d);else if(2!==h)return void this._onError(u.default.FORMAT_ERROR,"Flv: Invalid video packet type "+h)}},e.prototype._parseAVCDecoderConfigurationRecord=function(e,t,i){if(i<7)r.default.w(this.TAG,"Flv: Invalid AVCDecoderConfigurationRecord, lack of data!");else{var n=this._videoMetadata,a=this._videoTrack,o=this._littleEndian,l=new DataView(e,t,i);n?void 0!==n.avcc&&r.default.w(this.TAG,"Found another AVCDecoderConfigurationRecord!"):(!1===this._hasVideo&&!1===this._hasVideoFlagOverrided&&(this._hasVideo=!0,this._mediaInfo.hasVideo=!0),(n=this._videoMetadata={}).type="video",n.id=a.id,n.timescale=this._timescale,n.duration=this._duration);var h=l.getUint8(0),d=l.getUint8(1);if(l.getUint8(2),l.getUint8(3),1===h&&0!==d)if(this._naluLengthSize=1+(3&l.getUint8(4)),3===this._naluLengthSize||4===this._naluLengthSize){var c=31&l.getUint8(5);if(0!==c){c>1&&r.default.w(this.TAG,"Flv: Strange AVCDecoderConfigurationRecord: SPS Count = "+c);for(var f=6,p=0;p1&&r.default.w(this.TAG,"Flv: Strange AVCDecoderConfigurationRecord: PPS Count = "+A),f++,p=0;p=i){r.default.w(this.TAG,"Malformed Nalu near timestamp "+p+", offset = "+c+", dataSize = "+i);break}var _=l.getUint32(c,!u);if(3===f&&(_>>>=8),_>i-f)return void r.default.w(this.TAG,"Malformed Nalus near timestamp "+p+", NaluSize > DataSize!");var g=31&l.getUint8(c+f);5===g&&(m=!0);var v=new Uint8Array(e,t+c,f+_),y={type:g,data:v};h.push(y),d+=v.byteLength,c+=f+_}if(h.length){var b=this._videoTrack,S={units:h,length:d,isKeyframe:m,dts:p,cts:o,pts:p+o};m&&(S.fileposition=a),b.samples.push(S),b.length+=d}},e.prototype._parseHVCVideoPacket=function(e,t,i,n,a,s){if(i<4)r.default.w(this.TAG,"Flv: Invalid HVC packet, missing HVCPacketType or/and CompositionTime");else{var o=this._littleEndian,l=new DataView(e,t,i),h=l.getUint8(0),d=(16777215&l.getUint32(0,!o))<<8>>8;if(0===h)this._parseHVCDecoderConfigurationRecord(e,t+4,i-4);else if(1===h)this._parseHVCVideoData(e,t+4,i-4,n,a,s,d);else if(2!==h)return void this._onError(u.default.FORMAT_ERROR,"Flv: Invalid video packet type "+h)}},e.prototype._parseHVCDecoderConfigurationRecord=function(e,t,i){if(i<23)r.default.w(this.TAG,"Flv: Invalid HVCDecoderConfigurationRecord, lack of data!");else{var n=this._videoMetadata,a=this._videoTrack,s=this._littleEndian,l=new DataView(e,t,i);if(n?void 0!==n.avcc&&r.default.w(this.TAG,"Found another HVCDecoderConfigurationRecord!"):(!1===this._hasVideo&&!1===this._hasVideoFlagOverrided&&(this._hasVideo=!0,this._mediaInfo.hasVideo=!0),(n=this._videoMetadata={}).type="video",n.id=a.id,n.timescale=this._timescale,n.duration=this._duration),1===l.getUint8(0))if(this._naluLengthSize=1+(3&l.getUint8(21)),3===this._naluLengthSize||4===this._naluLengthSize){for(var h,d,c,f=l.getUint8(22),p=23,m=[],_=0;_1&&r.default.w(this.TAG,"Flv: Strange HVCDecoderConfigurationRecord: VPS Count = "+h),0!==d)if(d>1&&r.default.w(this.TAG,"Flv: Strange HVCDecoderConfigurationRecord: SPS Count = "+d),0!==c){c>1&&r.default.w(this.TAG,"Flv: Strange HVCDecoderConfigurationRecord: PPS Count = "+d);var T=m[0],E=o.default.parseSPS(T);n.codecWidth=E.codec_size.width,n.codecHeight=E.codec_size.height,n.presentWidth=E.present_size.width,n.presentHeight=E.present_size.height,n.profile=E.profile_string,n.level=E.level_string,n.profile_idc=E.profile_idc,n.level_idc=E.level_idc,n.bitDepth=E.bit_depth,n.chromaFormat=E.chroma_format,n.sarRatio=E.sar_ratio,n.frameRate=E.frame_rate,!1!==E.frame_rate.fixed&&0!==E.frame_rate.fps_num&&0!==E.frame_rate.fps_den||(n.frameRate=this._referenceFrameRate);var w=n.frameRate.fps_den,A=n.frameRate.fps_num;n.refSampleDuration=n.timescale*(w/A);var C="hvc1."+n.profile_idc+".1.L"+n.level_idc+".B0";n.codec=C;var k=this._mediaInfo;k.width=n.codecWidth,k.height=n.codecHeight,k.fps=n.frameRate.fps,k.profile=n.profile,k.level=n.level,k.refFrames=E.ref_frames,k.chromaFormat=E.chroma_format_string,k.sarNum=n.sarRatio.width,k.sarDen=n.sarRatio.height,k.videoCodec=C,k.hasAudio?null!=k.audioCodec&&(k.mimeType='video/x-flv; codecs="'+k.videoCodec+","+k.audioCodec+'"'):k.mimeType='video/x-flv; codecs="'+k.videoCodec+'"',k.isComplete()&&this._onMediaInfo(k),n.avcc=new Uint8Array(i),n.avcc.set(new Uint8Array(e,t,i),0),r.default.v(this.TAG,"Parsed HVCDecoderConfigurationRecord"),this._isInitialMetadataDispatched()?this._dispatch&&(this._audioTrack.length||this._videoTrack.length)&&this._onDataAvailable(this._audioTrack,this._videoTrack):this._videoInitialMetadataDispatched=!0,this._dispatch=!1,this._onTrackMetadata("video",n)}else this._onError(u.default.FORMAT_ERROR,"Flv: Invalid HVCDecoderConfigurationRecord: No PPS");else this._onError(u.default.FORMAT_ERROR,"Flv: Invalid HVCDecoderConfigurationRecord: No SPS");else this._onError(u.default.FORMAT_ERROR,"Flv: Invalid HVCDecoderConfigurationRecord: No VPS")}else this._onError(u.default.FORMAT_ERROR,"Flv: Strange NaluLengthSizeMinusOne: "+(this._naluLengthSize-1));else this._onError(u.default.FORMAT_ERROR,"Flv: Invalid HVCDecoderConfigurationRecord")}},e.prototype._parseHVCVideoData=function(e,t,i,n,a,s,o){for(var u=this._littleEndian,l=new DataView(e,t,i),h=[],d=0,c=0,f=this._naluLengthSize,p=this._timestampBase+n,m=1===s;c=i){r.default.w(this.TAG,"Malformed Nalu near timestamp "+p+", offset = "+c+", dataSize = "+i);break}var _=l.getUint32(c,!u);if(3===f&&(_>>>=8),_>i-f)return void r.default.w(this.TAG,"Malformed Nalus near timestamp "+p+", NaluSize > DataSize!");var g=l.getUint8(c+f)>>1&63;g>=16&&g<=23&&(m=!0);var v=new Uint8Array(e,t+c,f+_),y={type:g,data:v};h.push(y),d+=v.byteLength,c+=f+_}if(h.length){var b=this._videoTrack,S={units:h,length:d,isKeyframe:m,dts:p,cts:o,pts:p+o};m&&(S.fileposition=a),b.samples.push(S),b.length+=d}},e}();t.default=d},"./src/demux/hevc-sps-parser.js": /*!**************************************!*\ !*** ./src/demux/hevc-sps-parser.js ***! \**************************************/ function(e,t,i){i.r(t);var n=i( /*! ./exp-golomb.js */ "./src/demux/exp-golomb.js"),r=i( /*! ./sps-parser.js */ "./src/demux/sps-parser.js"),a=function(){function e(){}return e.parseSPS=function(t){var i=r.default._ebsp2rbsp(t),a=new n.default(i),s={};a.readBits(16),a.readBits(4);var o=a.readBits(3);a.readBits(1),e._hvcc_parse_ptl(a,s,o),a.readUEG();var u=0,l=a.readUEG();3==l&&(u=a.readBits(1)),s.sar_width=s.sar_height=1,s.conf_win_left_offset=s.conf_win_right_offset=s.conf_win_top_offset=s.conf_win_bottom_offset=0,s.def_disp_win_left_offset=s.def_disp_win_right_offset=s.def_disp_win_top_offset=s.def_disp_win_bottom_offset=0;var h=a.readUEG(),d=a.readUEG();a.readBits(1)&&(s.conf_win_left_offset=a.readUEG(),s.conf_win_right_offset=a.readUEG(),s.conf_win_top_offset=a.readUEG(),s.conf_win_bottom_offset=a.readUEG(),1===s.default_display_window_flag&&(s.conf_win_left_offset,s.def_disp_win_left_offset,s.conf_win_right_offset,s.def_disp_win_right_offset,s.conf_win_top_offset,s.def_disp_win_top_offset,s.conf_win_bottom_offset,s.def_disp_win_bottom_offset));var c=a.readUEG()+8;a.readUEG();for(var f=a.readUEG(),p=a.readBits(1)?0:o;p<=o;p++)e._skip_sub_layer_ordering_info(a);a.readUEG(),a.readUEG(),a.readUEG(),a.readUEG(),a.readUEG(),a.readUEG(),a.readBits(1)&&a.readBits(1)&&e._skip_scaling_list_data(a),a.readBits(1),a.readBits(1),a.readBits(1)&&(a.readBits(4),a.readBits(4),a.readUEG(),a.readUEG(),a.readBits(1));var m=[],_=a.readUEG();for(p=0;p<_;p++){var g=e._parse_rps(a,p,_,m);if(g<0)return g}if(a.readBits(1)){var v=a.readUEG();for(p=0;p32){for(var b=y/32,S=y%32,T=0;T0)for(u=i;u<8;u++)e.readBits(2);for(u=0;u=i)return-1;e.readBits(1),e.readUEG(),n[t]=0;for(var r=0;r<=n[t-1];r++){var a=0,s=e.readBits(1);s||(a=e.readBits(1)),(s||a)&&n[t]++}}else{var o=e.readUEG(),u=e.readUEG();for(n[t]=o+u,r=0;r1&&e.readSEG();for(var r=0;r0&&(t.fps=t.fps_num/t.fps_den);var i=0;e.readBits(1)&&(i=e.readUEG())>=0&&(t.fps/=i+1)},e._skip_hrd_parameters=function(t,i,n){var r=0,a=0;if(i&&(r=t.readBits(1),a=t.readBits(1),r||a)){var s=t.readBits(1);s&&t.readBits(19),t.readByte(),s&&t.readBits(4),t.readBits(15)}for(var o=0;o<=n;o++){var u=0,l=0,h=0,d=t.readBits(1);hvcc.fps_fixed=d,d||(h=t.readBits(1)),h?t.readUEG():l=t.readBits(1),l||(u=t.readUEG(t)),r&&e._skip_sub_layer_hrd_parameters(t,u,0),a&&e._skip_sub_layer_hrd_parameters(t,u,0)}},e.getProfileString=function(e){switch(e){case 1:return"Main";case 2:return"Main10";case 3:return"MainSP";case 4:return"Rext";case 9:return"SCC";default:return"Unknown"}},e.getLevelString=function(e){return(e/30).toFixed(1)},e.getChromaFormatString=function(e){switch(e){case 0:return"4:0:0";case 1:return"4:2:0";case 2:return"4:2:2";case 3:return"4:4:4";default:return"Unknown"}},e}();t.default=a},"./src/demux/sps-parser.js": /*!*********************************!*\ !*** ./src/demux/sps-parser.js ***! \*********************************/ function(e,t,i){i.r(t);var n=i( /*! ./exp-golomb.js */ "./src/demux/exp-golomb.js"),r=function(){function e(){}return e._ebsp2rbsp=function(e){for(var t=e,i=t.byteLength,n=new Uint8Array(i),r=0,a=0;a=2&&3===t[a]&&0===t[a-1]&&0===t[a-2]||(n[r]=t[a],r++);return new Uint8Array(n.buffer,0,r)},e.parseSPS=function(t){var i=e._ebsp2rbsp(t),r=new n.default(i);r.readByte();var a=r.readByte();r.readByte();var s=r.readByte();r.readUEG();var o=e.getProfileString(a),u=e.getLevelString(s),l=1,h=420,d=8;if((100===a||110===a||122===a||244===a||44===a||83===a||86===a||118===a||128===a||138===a||144===a)&&(3===(l=r.readUEG())&&r.readBits(1),l<=3&&(h=[0,420,422,444][l]),d=r.readUEG()+8,r.readUEG(),r.readBits(1),r.readBool()))for(var c=3!==l?8:12,f=0;f0&&L<16?(w=[1,12,10,16,40,24,20,32,80,18,15,64,160,4,3,2][L-1],A=[1,11,11,11,33,11,11,11,33,11,11,33,99,3,2,1][L-1]):255===L&&(w=r.readByte()<<8|r.readByte(),A=r.readByte()<<8|r.readByte())}if(r.readBool()&&r.readBool(),r.readBool()&&(r.readBits(4),r.readBool()&&r.readBits(24)),r.readBool()&&(r.readUEG(),r.readUEG()),r.readBool()){var x=r.readBits(32),R=r.readBits(32);k=r.readBool(),C=(P=R)/(I=2*x)}}var D=1;1===w&&1===A||(D=w/A);var O=0,U=0;0===l?(O=1,U=2-y):(O=3===l?1:2,U=(1===l?2:1)*(2-y));var M=16*(g+1),F=16*(v+1)*(2-y);M-=(b+S)*O,F-=(T+E)*U;var B=Math.ceil(M*D);return r.destroy(),r=null,{profile_string:o,level_string:u,bit_depth:d,ref_frames:_,chroma_format:h,chroma_format_string:e.getChromaFormatString(h),frame_rate:{fixed:k,fps:C,fps_den:I,fps_num:P},sar_ratio:{width:w,height:A},codec_size:{width:M,height:F},present_size:{width:B,height:F}}},e._skipScalingList=function(e,t){for(var i=8,n=8,r=0;r=15048,t=!a.default.msedge||e;return self.fetch&&self.ReadableStream&&t}catch(e){return!1}},t.prototype.destroy=function(){this.isWorking()&&this.abort(),e.prototype.destroy.call(this)},t.prototype.open=function(e,t){var i=this;this._dataSource=e,this._range=t;var r=e.url;this._config.reuseRedirectedURL&&null!=e.redirectedURL&&(r=e.redirectedURL);var a=this._seekHandler.getConfig(r,t),u=new self.Headers;if("object"===n(a.headers)){var l=a.headers;for(var h in l)l.hasOwnProperty(h)&&u.append(h,l[h])}var d={method:"GET",headers:u,mode:"cors",cache:"default",referrerPolicy:"no-referrer-when-downgrade"};if("object"===n(this._config.headers))for(var h in this._config.headers)u.append(h,this._config.headers[h]);!1===e.cors&&(d.mode="same-origin"),e.withCredentials&&(d.credentials="include"),e.referrerPolicy&&(d.referrerPolicy=e.referrerPolicy),self.AbortController&&(this._abortController=new self.AbortController,d.signal=this._abortController.signal),this._status=s.LoaderStatus.kConnecting,self.fetch(a.url,d).then((function(e){if(i._requestAbort)return i._status=s.LoaderStatus.kIdle,void e.body.cancel();if(e.ok&&e.status>=200&&e.status<=299){if(e.url!==a.url&&i._onURLRedirect){var t=i._seekHandler.removeURLParameters(e.url);i._onURLRedirect(t)}var n=e.headers.get("Content-Length");return null!=n&&(i._contentLength=parseInt(n),0!==i._contentLength&&i._onContentLengthKnown&&i._onContentLengthKnown(i._contentLength)),i._pump.call(i,e.body.getReader())}if(i._status=s.LoaderStatus.kError,!i._onError)throw new o.RuntimeException("FetchStreamLoader: Http code invalid, "+e.status+" "+e.statusText);i._onError(s.LoaderErrors.HTTP_STATUS_CODE_INVALID,{code:e.status,msg:e.statusText})})).catch((function(e){if(!i._abortController||!i._abortController.signal.aborted){if(i._status=s.LoaderStatus.kError,!i._onError)throw e;i._onError(s.LoaderErrors.EXCEPTION,{code:-1,msg:e.message})}}))},t.prototype.abort=function(){if(this._requestAbort=!0,(this._status!==s.LoaderStatus.kBuffering||!a.default.chrome)&&this._abortController)try{this._abortController.abort()}catch(e){}},t.prototype._pump=function(e){var t=this;return e.read().then((function(i){if(i.done)if(null!==t._contentLength&&t._receivedLength0&&(this._stashInitialSize=t.stashInitialSize),this._stashUsed=0,this._stashSize=this._stashInitialSize,this._bufferSize=3145728,this._stashBuffer=new ArrayBuffer(this._bufferSize),this._stashByteStart=0,this._enableStash=!0,!1===t.enableStashBuffer&&(this._enableStash=!1),this._loader=null,this._loaderClass=null,this._seekHandler=null,this._dataSource=e,this._isWebSocketURL=/wss?:\/\/(.+?)/.test(e.url),this._refTotalLength=e.filesize?e.filesize:null,this._totalLength=this._refTotalLength,this._fullRequestFlag=!1,this._currentRange=null,this._redirectedURL=null,this._speedNormalized=0,this._speedSampler=new r.default,this._speedNormalizeList=[64,128,256,384,512,768,1024,1536,2048,3072,4096],this._isEarlyEofReconnecting=!1,this._paused=!1,this._resumeFrom=0,this._onDataArrival=null,this._onSeeked=null,this._onError=null,this._onComplete=null,this._onRedirect=null,this._onRecoveredEarlyEof=null,this._selectSeekHandler(),this._selectLoader(),this._createLoader()}return e.prototype.destroy=function(){this._loader.isWorking()&&this._loader.abort(),this._loader.destroy(),this._loader=null,this._loaderClass=null,this._dataSource=null,this._stashBuffer=null,this._stashUsed=this._stashSize=this._bufferSize=this._stashByteStart=0,this._currentRange=null,this._speedSampler=null,this._isEarlyEofReconnecting=!1,this._onDataArrival=null,this._onSeeked=null,this._onError=null,this._onComplete=null,this._onRedirect=null,this._onRecoveredEarlyEof=null,this._extraData=null},e.prototype.isWorking=function(){return this._loader&&this._loader.isWorking()&&!this._paused},e.prototype.isPaused=function(){return this._paused},Object.defineProperty(e.prototype,"status",{get:function(){return this._loader.status},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"extraData",{get:function(){return this._extraData},set:function(e){this._extraData=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onDataArrival",{get:function(){return this._onDataArrival},set:function(e){this._onDataArrival=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onSeeked",{get:function(){return this._onSeeked},set:function(e){this._onSeeked=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onError",{get:function(){return this._onError},set:function(e){this._onError=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onComplete",{get:function(){return this._onComplete},set:function(e){this._onComplete=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onRedirect",{get:function(){return this._onRedirect},set:function(e){this._onRedirect=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onRecoveredEarlyEof",{get:function(){return this._onRecoveredEarlyEof},set:function(e){this._onRecoveredEarlyEof=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"currentURL",{get:function(){return this._dataSource.url},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"hasRedirect",{get:function(){return null!=this._redirectedURL||null!=this._dataSource.redirectedURL},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"currentRedirectedURL",{get:function(){return this._redirectedURL||this._dataSource.redirectedURL},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"currentSpeed",{get:function(){return this._loaderClass===u.default?this._loader.currentSpeed:this._speedSampler.lastSecondKBps},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"loaderType",{get:function(){return this._loader.type},enumerable:!1,configurable:!0}),e.prototype._selectSeekHandler=function(){var e=this._config;if("range"===e.seekType)this._seekHandler=new h.default(this._config.rangeLoadZeroStart);else if("param"===e.seekType){var t=e.seekParamStart||"bstart",i=e.seekParamEnd||"bend";this._seekHandler=new d.default(t,i)}else{if("custom"!==e.seekType)throw new c.InvalidArgumentException("Invalid seekType in config: "+e.seekType);if("function"!=typeof e.customSeekHandler)throw new c.InvalidArgumentException("Custom seekType specified in config but invalid customSeekHandler!");this._seekHandler=new e.customSeekHandler}},e.prototype._selectLoader=function(){if(null!=this._config.customLoader)this._loaderClass=this._config.customLoader;else if(this._isWebSocketURL)this._loaderClass=l.default;else if(s.default.isSupported())this._loaderClass=s.default;else if(o.default.isSupported())this._loaderClass=o.default;else{if(!u.default.isSupported())throw new c.RuntimeException("Your browser doesn't support xhr with arraybuffer responseType!");this._loaderClass=u.default}},e.prototype._createLoader=function(){this._loader=new this._loaderClass(this._seekHandler,this._config),!1===this._loader.needStashBuffer&&(this._enableStash=!1),this._loader.onContentLengthKnown=this._onContentLengthKnown.bind(this),this._loader.onURLRedirect=this._onURLRedirect.bind(this),this._loader.onDataArrival=this._onLoaderChunkArrival.bind(this),this._loader.onComplete=this._onLoaderComplete.bind(this),this._loader.onError=this._onLoaderError.bind(this)},e.prototype.open=function(e){this._currentRange={from:0,to:-1},e&&(this._currentRange.from=e),this._speedSampler.reset(),e||(this._fullRequestFlag=!0),this._loader.open(this._dataSource,Object.assign({},this._currentRange))},e.prototype.abort=function(){this._loader.abort(),this._paused&&(this._paused=!1,this._resumeFrom=0)},e.prototype.pause=function(){this.isWorking()&&(this._loader.abort(),0!==this._stashUsed?(this._resumeFrom=this._stashByteStart,this._currentRange.to=this._stashByteStart-1):this._resumeFrom=this._currentRange.to+1,this._stashUsed=0,this._stashByteStart=0,this._paused=!0)},e.prototype.resume=function(){if(this._paused){this._paused=!1;var e=this._resumeFrom;this._resumeFrom=0,this._internalSeek(e,!0)}},e.prototype.seek=function(e){this._paused=!1,this._stashUsed=0,this._stashByteStart=0,this._internalSeek(e,!0)},e.prototype._internalSeek=function(e,t){this._loader.isWorking()&&this._loader.abort(),this._flushStashBuffer(t),this._loader.destroy(),this._loader=null;var i={from:e,to:-1};this._currentRange={from:i.from,to:-1},this._speedSampler.reset(),this._stashSize=this._stashInitialSize,this._createLoader(),this._loader.open(this._dataSource,i),this._onSeeked&&this._onSeeked()},e.prototype.updateUrl=function(e){if(!e||"string"!=typeof e||0===e.length)throw new c.InvalidArgumentException("Url must be a non-empty string!");this._dataSource.url=e},e.prototype._expandBuffer=function(e){for(var t=this._stashSize;t+10485760){var n=new Uint8Array(this._stashBuffer,0,this._stashUsed);new Uint8Array(i,0,t).set(n,0)}this._stashBuffer=i,this._bufferSize=t}},e.prototype._normalizeSpeed=function(e){var t=this._speedNormalizeList,i=t.length-1,n=0,r=0,a=i;if(e=t[n]&&e=512&&e<=1024?Math.floor(1.5*e):2*e)>8192&&(t=8192);var i=1024*t+1048576;this._bufferSize0){var a=this._stashBuffer.slice(0,this._stashUsed);(u=this._dispatchChunks(a,this._stashByteStart))0&&(l=new Uint8Array(a,u),o.set(l,0),this._stashUsed=l.byteLength,this._stashByteStart+=u):(this._stashUsed=0,this._stashByteStart+=u),this._stashUsed+e.byteLength>this._bufferSize&&(this._expandBuffer(this._stashUsed+e.byteLength),o=new Uint8Array(this._stashBuffer,0,this._bufferSize)),o.set(new Uint8Array(e),this._stashUsed),this._stashUsed+=e.byteLength}else(u=this._dispatchChunks(e,t))this._bufferSize&&(this._expandBuffer(s),o=new Uint8Array(this._stashBuffer,0,this._bufferSize)),o.set(new Uint8Array(e,u),0),this._stashUsed+=s,this._stashByteStart=t+u);else if(0===this._stashUsed){var s;(u=this._dispatchChunks(e,t))this._bufferSize&&this._expandBuffer(s),(o=new Uint8Array(this._stashBuffer,0,this._bufferSize)).set(new Uint8Array(e,u),0),this._stashUsed+=s,this._stashByteStart=t+u)}else{var o,u;if(this._stashUsed+e.byteLength>this._bufferSize&&this._expandBuffer(this._stashUsed+e.byteLength),(o=new Uint8Array(this._stashBuffer,0,this._bufferSize)).set(new Uint8Array(e),this._stashUsed),this._stashUsed+=e.byteLength,(u=this._dispatchChunks(this._stashBuffer.slice(0,this._stashUsed),this._stashByteStart))0){var l=new Uint8Array(this._stashBuffer,u);o.set(l,0)}this._stashUsed-=u,this._stashByteStart+=u}}},e.prototype._flushStashBuffer=function(e){if(this._stashUsed>0){var t=this._stashBuffer.slice(0,this._stashUsed),i=this._dispatchChunks(t,this._stashByteStart),r=t.byteLength-i;if(i0){var a=new Uint8Array(this._stashBuffer,0,this._bufferSize),s=new Uint8Array(t,i);a.set(s,0),this._stashUsed=s.byteLength,this._stashByteStart+=i}return 0}n.default.w(this.TAG,r+" bytes unconsumed data remain when flush buffer, dropped")}return this._stashUsed=0,this._stashByteStart=0,r}return 0},e.prototype._onLoaderComplete=function(e,t){this._flushStashBuffer(!0),this._onComplete&&this._onComplete(this._extraData)},e.prototype._onLoaderError=function(e,t){switch(n.default.e(this.TAG,"Loader error, code = "+t.code+", msg = "+t.msg),this._flushStashBuffer(!1),this._isEarlyEofReconnecting&&(this._isEarlyEofReconnecting=!1,e=a.LoaderErrors.UNRECOVERABLE_EARLY_EOF),e){case a.LoaderErrors.EARLY_EOF:if(!this._config.isLive&&this._totalLength){var i=this._currentRange.to+1;return void(i0)for(var a=i.split("&"),s=0;s0;o[0]!==this._startName&&o[0]!==this._endName&&(u&&(r+="&"),r+=a[s])}return 0===r.length?t:t+"?"+r},e}();t.default=n},"./src/io/range-seek-handler.js": /*!**************************************!*\ !*** ./src/io/range-seek-handler.js ***! \**************************************/ function(e,t,i){i.r(t);var n=function(){function e(e){this._zeroStart=e||!1}return e.prototype.getConfig=function(e,t){var i={};if(0!==t.from||-1!==t.to){var n=void 0;n=-1!==t.to?"bytes="+t.from.toString()+"-"+t.to.toString():"bytes="+t.from.toString()+"-",i.Range=n}else this._zeroStart&&(i.Range="bytes=0-");return{url:e,headers:i}},e.prototype.removeURLParameters=function(e){return e},e}();t.default=n},"./src/io/speed-sampler.js": /*!*********************************!*\ !*** ./src/io/speed-sampler.js ***! \*********************************/ function(e,t,i){i.r(t);var n=function(){function e(){this._firstCheckpoint=0,this._lastCheckpoint=0,this._intervalBytes=0,this._totalBytes=0,this._lastSecondBytes=0,self.performance&&self.performance.now?this._now=self.performance.now.bind(self.performance):this._now=Date.now}return e.prototype.reset=function(){this._firstCheckpoint=this._lastCheckpoint=0,this._totalBytes=this._intervalBytes=0,this._lastSecondBytes=0},e.prototype.addBytes=function(e){0===this._firstCheckpoint?(this._firstCheckpoint=this._now(),this._lastCheckpoint=this._firstCheckpoint,this._intervalBytes+=e,this._totalBytes+=e):this._now()-this._lastCheckpoint<1e3?(this._intervalBytes+=e,this._totalBytes+=e):(this._lastSecondBytes=this._intervalBytes,this._intervalBytes=e,this._totalBytes+=e,this._lastCheckpoint=this._now())},Object.defineProperty(e.prototype,"currentKBps",{get:function(){this.addBytes(0);var e=(this._now()-this._lastCheckpoint)/1e3;return 0==e&&(e=1),this._intervalBytes/e/1024},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"lastSecondKBps",{get:function(){return this.addBytes(0),0!==this._lastSecondBytes?this._lastSecondBytes/1024:this._now()-this._lastCheckpoint>=500?this.currentKBps:0},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"averageKBps",{get:function(){var e=(this._now()-this._firstCheckpoint)/1e3;return this._totalBytes/e/1024},enumerable:!1,configurable:!0}),e}();t.default=n},"./src/io/websocket-loader.js": /*!************************************!*\ !*** ./src/io/websocket-loader.js ***! \************************************/ function(e,t,i){i.r(t);var n,r=i( /*! ./loader.js */ "./src/io/loader.js"),a=i( /*! ../utils/exception.js */ "./src/utils/exception.js"),s=(n=function(e,t){return(n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var i in t)Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i])})(e,t)},function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function i(){this.constructor=e}n(e,t),e.prototype=null===t?Object.create(t):(i.prototype=t.prototype,new i)}),o=function(e){function t(){var t=e.call(this,"websocket-loader")||this;return t.TAG="WebSocketLoader",t._needStash=!0,t._ws=null,t._requestAbort=!1,t._receivedLength=0,t}return s(t,e),t.isSupported=function(){try{return void 0!==self.WebSocket}catch(e){return!1}},t.prototype.destroy=function(){this._ws&&this.abort(),e.prototype.destroy.call(this)},t.prototype.open=function(e){try{var t=this._ws=new self.WebSocket(e.url);t.binaryType="arraybuffer",t.onopen=this._onWebSocketOpen.bind(this),t.onclose=this._onWebSocketClose.bind(this),t.onmessage=this._onWebSocketMessage.bind(this),t.onerror=this._onWebSocketError.bind(this),this._status=r.LoaderStatus.kConnecting}catch(e){this._status=r.LoaderStatus.kError;var i={code:e.code,msg:e.message};if(!this._onError)throw new a.RuntimeException(i.msg);this._onError(r.LoaderErrors.EXCEPTION,i)}},t.prototype.abort=function(){var e=this._ws;!e||0!==e.readyState&&1!==e.readyState||(this._requestAbort=!0,e.close()),this._ws=null,this._status=r.LoaderStatus.kComplete},t.prototype._onWebSocketOpen=function(e){this._status=r.LoaderStatus.kBuffering},t.prototype._onWebSocketClose=function(e){!0!==this._requestAbort?(this._status=r.LoaderStatus.kComplete,this._onComplete&&this._onComplete(0,this._receivedLength-1)):this._requestAbort=!1},t.prototype._onWebSocketMessage=function(e){var t=this;if(e.data instanceof ArrayBuffer)this._dispatchArrayBuffer(e.data);else if(e.data instanceof Blob){var i=new FileReader;i.onload=function(){t._dispatchArrayBuffer(i.result)},i.readAsArrayBuffer(e.data)}else{this._status=r.LoaderStatus.kError;var n={code:-1,msg:"Unsupported WebSocket message type: "+e.data.constructor.name};if(!this._onError)throw new a.RuntimeException(n.msg);this._onError(r.LoaderErrors.EXCEPTION,n)}},t.prototype._dispatchArrayBuffer=function(e){var t=e,i=this._receivedLength;this._receivedLength+=t.byteLength,this._onDataArrival&&this._onDataArrival(t,i,this._receivedLength)},t.prototype._onWebSocketError=function(e){this._status=r.LoaderStatus.kError;var t={code:e.code,msg:e.message};if(!this._onError)throw new a.RuntimeException(t.msg);this._onError(r.LoaderErrors.EXCEPTION,t)},t}(r.BaseLoader);t.default=o},"./src/io/xhr-moz-chunked-loader.js": /*!******************************************!*\ !*** ./src/io/xhr-moz-chunked-loader.js ***! \******************************************/ function(e,t,i){i.r(t);var r,a=i( /*! ../utils/logger.js */ "./src/utils/logger.js"),s=i( /*! ./loader.js */ "./src/io/loader.js"),o=i( /*! ../utils/exception.js */ "./src/utils/exception.js"),u=(r=function(e,t){return(r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var i in t)Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i])})(e,t)},function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function i(){this.constructor=e}r(e,t),e.prototype=null===t?Object.create(t):(i.prototype=t.prototype,new i)}),l=function(e){function t(t,i){var n=e.call(this,"xhr-moz-chunked-loader")||this;return n.TAG="MozChunkedLoader",n._seekHandler=t,n._config=i,n._needStash=!0,n._xhr=null,n._requestAbort=!1,n._contentLength=null,n._receivedLength=0,n}return u(t,e),t.isSupported=function(){try{var e=new XMLHttpRequest;return e.open("GET","https://example.com",!0),e.responseType="moz-chunked-arraybuffer","moz-chunked-arraybuffer"===e.responseType}catch(e){return a.default.w("MozChunkedLoader",e.message),!1}},t.prototype.destroy=function(){this.isWorking()&&this.abort(),this._xhr&&(this._xhr.onreadystatechange=null,this._xhr.onprogress=null,this._xhr.onloadend=null,this._xhr.onerror=null,this._xhr=null),e.prototype.destroy.call(this)},t.prototype.open=function(e,t){this._dataSource=e,this._range=t;var i=e.url;this._config.reuseRedirectedURL&&null!=e.redirectedURL&&(i=e.redirectedURL);var r=this._seekHandler.getConfig(i,t);this._requestURL=r.url;var a=this._xhr=new XMLHttpRequest;if(a.open("GET",r.url,!0),a.responseType="moz-chunked-arraybuffer",a.onreadystatechange=this._onReadyStateChange.bind(this),a.onprogress=this._onProgress.bind(this),a.onloadend=this._onLoadEnd.bind(this),a.onerror=this._onXhrError.bind(this),e.withCredentials&&(a.withCredentials=!0),"object"===n(r.headers)){var o=r.headers;for(var u in o)o.hasOwnProperty(u)&&a.setRequestHeader(u,o[u])}if("object"===n(this._config.headers))for(var u in o=this._config.headers)o.hasOwnProperty(u)&&a.setRequestHeader(u,o[u]);this._status=s.LoaderStatus.kConnecting,a.send()},t.prototype.abort=function(){this._requestAbort=!0,this._xhr&&this._xhr.abort(),this._status=s.LoaderStatus.kComplete},t.prototype._onReadyStateChange=function(e){var t=e.target;if(2===t.readyState){if(null!=t.responseURL&&t.responseURL!==this._requestURL&&this._onURLRedirect){var i=this._seekHandler.removeURLParameters(t.responseURL);this._onURLRedirect(i)}if(0!==t.status&&(t.status<200||t.status>299)){if(this._status=s.LoaderStatus.kError,!this._onError)throw new o.RuntimeException("MozChunkedLoader: Http code invalid, "+t.status+" "+t.statusText);this._onError(s.LoaderErrors.HTTP_STATUS_CODE_INVALID,{code:t.status,msg:t.statusText})}else this._status=s.LoaderStatus.kBuffering}},t.prototype._onProgress=function(e){if(this._status!==s.LoaderStatus.kError){null===this._contentLength&&null!==e.total&&0!==e.total&&(this._contentLength=e.total,this._onContentLengthKnown&&this._onContentLengthKnown(this._contentLength));var t=e.target.response,i=this._range.from+this._receivedLength;this._receivedLength+=t.byteLength,this._onDataArrival&&this._onDataArrival(t,i,this._receivedLength)}},t.prototype._onLoadEnd=function(e){!0!==this._requestAbort?this._status!==s.LoaderStatus.kError&&(this._status=s.LoaderStatus.kComplete,this._onComplete&&this._onComplete(this._range.from,this._range.from+this._receivedLength-1)):this._requestAbort=!1},t.prototype._onXhrError=function(e){this._status=s.LoaderStatus.kError;var t=0,i=null;if(this._contentLength&&e.loaded=this._contentLength&&(i=this._range.from+this._contentLength-1),this._currentRequestRange={from:t,to:i},this._internalOpen(this._dataSource,this._currentRequestRange)},t.prototype._internalOpen=function(e,t){this._lastTimeLoaded=0;var i=e.url;this._config.reuseRedirectedURL&&(null!=this._currentRedirectedURL?i=this._currentRedirectedURL:null!=e.redirectedURL&&(i=e.redirectedURL));var r=this._seekHandler.getConfig(i,t);this._currentRequestURL=r.url;var a=this._xhr=new XMLHttpRequest;if(a.open("GET",r.url,!0),a.responseType="arraybuffer",a.onreadystatechange=this._onReadyStateChange.bind(this),a.onprogress=this._onProgress.bind(this),a.onload=this._onLoad.bind(this),a.onerror=this._onXhrError.bind(this),e.withCredentials&&(a.withCredentials=!0),"object"===n(r.headers)){var s=r.headers;for(var o in s)s.hasOwnProperty(o)&&a.setRequestHeader(o,s[o])}if("object"===n(this._config.headers))for(var o in s=this._config.headers)s.hasOwnProperty(o)&&a.setRequestHeader(o,s[o]);a.send()},t.prototype.abort=function(){this._requestAbort=!0,this._internalAbort(),this._status=o.LoaderStatus.kComplete},t.prototype._internalAbort=function(){this._xhr&&(this._xhr.onreadystatechange=null,this._xhr.onprogress=null,this._xhr.onload=null,this._xhr.onerror=null,this._xhr.abort(),this._xhr=null)},t.prototype._onReadyStateChange=function(e){var t=e.target;if(2===t.readyState){if(null!=t.responseURL){var i=this._seekHandler.removeURLParameters(t.responseURL);t.responseURL!==this._currentRequestURL&&i!==this._currentRedirectedURL&&(this._currentRedirectedURL=i,this._onURLRedirect&&this._onURLRedirect(i))}if(t.status>=200&&t.status<=299){if(this._waitForTotalLength)return;this._status=o.LoaderStatus.kBuffering}else{if(this._status=o.LoaderStatus.kError,!this._onError)throw new u.RuntimeException("RangeLoader: Http code invalid, "+t.status+" "+t.statusText);this._onError(o.LoaderErrors.HTTP_STATUS_CODE_INVALID,{code:t.status,msg:t.statusText})}}},t.prototype._onProgress=function(e){if(this._status!==o.LoaderStatus.kError){if(null===this._contentLength){var t=!1;if(this._waitForTotalLength){this._waitForTotalLength=!1,this._totalLengthReceived=!0,t=!0;var i=e.total;this._internalAbort(),null!=i&0!==i&&(this._totalLength=i)}if(-1===this._range.to?this._contentLength=this._totalLength-this._range.from:this._contentLength=this._range.to-this._range.from+1,t)return void this._openSubRange();this._onContentLengthKnown&&this._onContentLengthKnown(this._contentLength)}var n=e.loaded-this._lastTimeLoaded;this._lastTimeLoaded=e.loaded,this._speedSampler.addBytes(n)}},t.prototype._normalizeSpeed=function(e){var t=this._chunkSizeKBList,i=t.length-1,n=0,r=0,a=i;if(e=t[n]&&e=3&&(t=this._speedSampler.currentKBps)),0!==t){var i=this._normalizeSpeed(t);this._currentSpeedNormalized!==i&&(this._currentSpeedNormalized=i,this._currentChunkSizeKB=i)}var n=e.target.response,r=this._range.from+this._receivedLength;this._receivedLength+=n.byteLength;var a=!1;null!=this._contentLength&&this._receivedLength0&&this._receivedLength0&&(this._requestSetTime=!0,this._mediaElement.currentTime=0),this._transmuxer=new l.default(this._mediaDataSource,this._config),this._transmuxer.on(h.default.INIT_SEGMENT,(function(t,i){e._msectl.appendInitSegment(i)})),this._transmuxer.on(h.default.MEDIA_SEGMENT,(function(t,i){if(e._msectl.appendMediaSegment(i),e._config.lazyLoad&&!e._config.isLive){var n=e._mediaElement.currentTime;i.info.endDts>=1e3*(n+e._config.lazyLoadMaxDuration)&&null==e._progressChecker&&(s.default.v(e.TAG,"Maximum buffering duration exceeded, suspend transmuxing task"),e._suspendTransmuxer())}})),this._transmuxer.on(h.default.LOADING_COMPLETE,(function(){e._msectl.endOfStream(),e._emitter.emit(u.default.LOADING_COMPLETE)})),this._transmuxer.on(h.default.RECOVERED_EARLY_EOF,(function(){e._emitter.emit(u.default.RECOVERED_EARLY_EOF)})),this._transmuxer.on(h.default.IO_ERROR,(function(t,i){e._emitter.emit(u.default.ERROR,f.ErrorTypes.NETWORK_ERROR,t,i)})),this._transmuxer.on(h.default.DEMUX_ERROR,(function(t,i){e._emitter.emit(u.default.ERROR,f.ErrorTypes.MEDIA_ERROR,t,{code:-1,msg:i})})),this._transmuxer.on(h.default.MEDIA_INFO,(function(t){e._mediaInfo=t,e._emitter.emit(u.default.MEDIA_INFO,Object.assign({},t))})),this._transmuxer.on(h.default.METADATA_ARRIVED,(function(t){e._emitter.emit(u.default.METADATA_ARRIVED,t)})),this._transmuxer.on(h.default.SCRIPTDATA_ARRIVED,(function(t){e._emitter.emit(u.default.SCRIPTDATA_ARRIVED,t)})),this._transmuxer.on(h.default.STATISTICS_INFO,(function(t){e._statisticsInfo=e._fillStatisticsInfo(t),e._emitter.emit(u.default.STATISTICS_INFO,Object.assign({},e._statisticsInfo))})),this._transmuxer.on(h.default.RECOMMEND_SEEKPOINT,(function(t){e._mediaElement&&!e._config.accurateSeek&&(e._requestSetTime=!0,e._mediaElement.currentTime=t/1e3)})),this._transmuxer.open()))},e.prototype.unload=function(){this._mediaElement&&this._mediaElement.pause(),this._msectl&&this._msectl.seek(0),this._transmuxer&&(this._transmuxer.close(),this._transmuxer.destroy(),this._transmuxer=null)},e.prototype.play=function(){return this._mediaElement.play()},e.prototype.pause=function(){this._mediaElement.pause()},Object.defineProperty(e.prototype,"type",{get:function(){return this._type},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"buffered",{get:function(){return this._mediaElement.buffered},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"duration",{get:function(){return this._mediaElement.duration},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"volume",{get:function(){return this._mediaElement.volume},set:function(e){this._mediaElement.volume=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"muted",{get:function(){return this._mediaElement.muted},set:function(e){this._mediaElement.muted=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"currentTime",{get:function(){return this._mediaElement?this._mediaElement.currentTime:0},set:function(e){this._mediaElement?this._internalSeek(e):this._pendingSeekTime=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"mediaInfo",{get:function(){return Object.assign({},this._mediaInfo)},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"statisticsInfo",{get:function(){return null==this._statisticsInfo&&(this._statisticsInfo={}),this._statisticsInfo=this._fillStatisticsInfo(this._statisticsInfo),Object.assign({},this._statisticsInfo)},enumerable:!1,configurable:!0}),e.prototype._fillStatisticsInfo=function(e){if(e.playerType=this._type,!(this._mediaElement instanceof HTMLVideoElement))return e;var t=!0,i=0,n=0;if(this._mediaElement.getVideoPlaybackQuality){var r=this._mediaElement.getVideoPlaybackQuality();i=r.totalVideoFrames,n=r.droppedVideoFrames}else null!=this._mediaElement.webkitDecodedFrameCount?(i=this._mediaElement.webkitDecodedFrameCount,n=this._mediaElement.webkitDroppedFrameCount):t=!1;return t&&(e.decodedFrames=i,e.droppedFrames=n),e},e.prototype._onmseUpdateEnd=function(){if(this._config.lazyLoad&&!this._config.isLive){for(var e=this._mediaElement.buffered,t=this._mediaElement.currentTime,i=0,n=0;n=t+this._config.lazyLoadMaxDuration&&null==this._progressChecker&&(s.default.v(this.TAG,"Maximum buffering duration exceeded, suspend transmuxing task"),this._suspendTransmuxer())}},e.prototype._onmseBufferFull=function(){s.default.v(this.TAG,"MSE SourceBuffer is full, suspend transmuxing task"),null==this._progressChecker&&this._suspendTransmuxer()},e.prototype._suspendTransmuxer=function(){this._transmuxer&&(this._transmuxer.pause(),null==this._progressChecker&&(this._progressChecker=window.setInterval(this._checkProgressAndResume.bind(this),1e3)))},e.prototype._checkProgressAndResume=function(){for(var e=this._mediaElement.currentTime,t=this._mediaElement.buffered,i=!1,n=0;n=r&&e=a-this._config.lazyLoadRecoverDuration&&(i=!0);break}}i&&(window.clearInterval(this._progressChecker),this._progressChecker=null,i&&(s.default.v(this.TAG,"Continue loading from paused position"),this._transmuxer.resume()))},e.prototype._isTimepointBuffered=function(e){for(var t=this._mediaElement.buffered,i=0;i=n&&e0){var r=this._mediaElement.buffered.start(0);(r<1&&e0&&t.currentTime0){var n=i.start(0);if(n<1&&t0&&(this._mediaElement.currentTime=0),this._mediaElement.preload="auto",this._mediaElement.load(),this._statisticsReporter=window.setInterval(this._reportStatisticsInfo.bind(this),this._config.statisticsInfoReportInterval)},e.prototype.unload=function(){this._mediaElement&&(this._mediaElement.src="",this._mediaElement.removeAttribute("src")),null!=this._statisticsReporter&&(window.clearInterval(this._statisticsReporter),this._statisticsReporter=null)},e.prototype.play=function(){return this._mediaElement.play()},e.prototype.pause=function(){this._mediaElement.pause()},Object.defineProperty(e.prototype,"type",{get:function(){return this._type},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"buffered",{get:function(){return this._mediaElement.buffered},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"duration",{get:function(){return this._mediaElement.duration},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"volume",{get:function(){return this._mediaElement.volume},set:function(e){this._mediaElement.volume=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"muted",{get:function(){return this._mediaElement.muted},set:function(e){this._mediaElement.muted=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"currentTime",{get:function(){return this._mediaElement?this._mediaElement.currentTime:0},set:function(e){this._mediaElement?this._mediaElement.currentTime=e:this._pendingSeekTime=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"mediaInfo",{get:function(){var e={mimeType:(this._mediaElement instanceof HTMLAudioElement?"audio/":"video/")+this._mediaDataSource.type};return this._mediaElement&&(e.duration=Math.floor(1e3*this._mediaElement.duration),this._mediaElement instanceof HTMLVideoElement&&(e.width=this._mediaElement.videoWidth,e.height=this._mediaElement.videoHeight)),e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"statisticsInfo",{get:function(){var e={playerType:this._type,url:this._mediaDataSource.url};if(!(this._mediaElement instanceof HTMLVideoElement))return e;var t=!0,i=0,n=0;if(this._mediaElement.getVideoPlaybackQuality){var r=this._mediaElement.getVideoPlaybackQuality();i=r.totalVideoFrames,n=r.droppedVideoFrames}else null!=this._mediaElement.webkitDecodedFrameCount?(i=this._mediaElement.webkitDecodedFrameCount,n=this._mediaElement.webkitDroppedFrameCount):t=!1;return t&&(e.decodedFrames=i,e.droppedFrames=n),e},enumerable:!1,configurable:!0}),e.prototype._onvLoadedMetadata=function(e){null!=this._pendingSeekTime&&(this._mediaElement.currentTime=this._pendingSeekTime,this._pendingSeekTime=null),this._emitter.emit(s.default.MEDIA_INFO,this.mediaInfo)},e.prototype._reportStatisticsInfo=function(){this._emitter.emit(s.default.STATISTICS_INFO,this.statisticsInfo)},e}();t.default=l},"./src/player/player-errors.js": /*!*************************************!*\ !*** ./src/player/player-errors.js ***! \*************************************/ function(e,t,i){i.r(t),i.d(t,{ErrorTypes:function(){return a},ErrorDetails:function(){return s}});var n=i( /*! ../io/loader.js */ "./src/io/loader.js"),r=i( /*! ../demux/demux-errors.js */ "./src/demux/demux-errors.js"),a={NETWORK_ERROR:"NetworkError",MEDIA_ERROR:"MediaError",OTHER_ERROR:"OtherError"},s={NETWORK_EXCEPTION:n.LoaderErrors.EXCEPTION,NETWORK_STATUS_CODE_INVALID:n.LoaderErrors.HTTP_STATUS_CODE_INVALID,NETWORK_TIMEOUT:n.LoaderErrors.CONNECTING_TIMEOUT,NETWORK_UNRECOVERABLE_EARLY_EOF:n.LoaderErrors.UNRECOVERABLE_EARLY_EOF,MEDIA_MSE_ERROR:"MediaMSEError",MEDIA_FORMAT_ERROR:r.default.FORMAT_ERROR,MEDIA_FORMAT_UNSUPPORTED:r.default.FORMAT_UNSUPPORTED,MEDIA_CODEC_UNSUPPORTED:r.default.CODEC_UNSUPPORTED}},"./src/player/player-events.js": /*!*************************************!*\ !*** ./src/player/player-events.js ***! \*************************************/ function(e,t,i){i.r(t),t.default={ERROR:"error",LOADING_COMPLETE:"loading_complete",RECOVERED_EARLY_EOF:"recovered_early_eof",MEDIA_INFO:"media_info",METADATA_ARRIVED:"metadata_arrived",SCRIPTDATA_ARRIVED:"scriptdata_arrived",STATISTICS_INFO:"statistics_info"}},"./src/remux/aac-silent.js": /*!*********************************!*\ !*** ./src/remux/aac-silent.js ***! \*********************************/ function(e,t,i){i.r(t);var n=function(){function e(){}return e.getSilentFrame=function(e,t){if("mp4a.40.2"===e){if(1===t)return new Uint8Array([0,200,0,128,35,128]);if(2===t)return new Uint8Array([33,0,73,144,2,25,0,35,128]);if(3===t)return new Uint8Array([0,200,0,128,32,132,1,38,64,8,100,0,142]);if(4===t)return new Uint8Array([0,200,0,128,32,132,1,38,64,8,100,0,128,44,128,8,2,56]);if(5===t)return new Uint8Array([0,200,0,128,32,132,1,38,64,8,100,0,130,48,4,153,0,33,144,2,56]);if(6===t)return new Uint8Array([0,200,0,128,32,132,1,38,64,8,100,0,130,48,4,153,0,33,144,2,0,178,0,32,8,224])}else{if(1===t)return new Uint8Array([1,64,34,128,163,78,230,128,186,8,0,0,0,28,6,241,193,10,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,94]);if(2===t)return new Uint8Array([1,64,34,128,163,94,230,128,186,8,0,0,0,0,149,0,6,241,161,10,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,94]);if(3===t)return new Uint8Array([1,64,34,128,163,94,230,128,186,8,0,0,0,0,149,0,6,241,161,10,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,94])}return null},e}();t.default=n},"./src/remux/mp4-generator.js": /*!************************************!*\ !*** ./src/remux/mp4-generator.js ***! \************************************/ function(e,t,i){i.r(t);var n=function(){function e(){}return e.init=function(){for(var t in e.types={hvc1:[],hvcC:[],avc1:[],avcC:[],btrt:[],dinf:[],dref:[],esds:[],ftyp:[],hdlr:[],mdat:[],mdhd:[],mdia:[],mfhd:[],minf:[],moof:[],moov:[],mp4a:[],mvex:[],mvhd:[],sdtp:[],stbl:[],stco:[],stsc:[],stsd:[],stsz:[],stts:[],tfdt:[],tfhd:[],traf:[],trak:[],trun:[],trex:[],tkhd:[],vmhd:[],smhd:[],pasp:[],".mp3":[]},e.types)e.types.hasOwnProperty(t)&&(e.types[t]=[t.charCodeAt(0),t.charCodeAt(1),t.charCodeAt(2),t.charCodeAt(3)]);var i=e.constants={};i.FTYP=new Uint8Array([105,115,111,109,0,0,0,1,105,115,111,109,97,118,99,49]),i.STSD_PREFIX=new Uint8Array([0,0,0,0,0,0,0,1]),i.STTS=new Uint8Array([0,0,0,0,0,0,0,0]),i.STSC=i.STCO=i.STTS,i.STSZ=new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0]),i.HDLR_VIDEO=new Uint8Array([0,0,0,0,0,0,0,0,118,105,100,101,0,0,0,0,0,0,0,0,0,0,0,0,86,105,100,101,111,72,97,110,100,108,101,114,0]),i.HDLR_AUDIO=new Uint8Array([0,0,0,0,0,0,0,0,115,111,117,110,0,0,0,0,0,0,0,0,0,0,0,0,83,111,117,110,100,72,97,110,100,108,101,114,0]),i.DREF=new Uint8Array([0,0,0,0,0,0,0,1,0,0,0,12,117,114,108,32,0,0,0,1]),i.SMHD=new Uint8Array([0,0,0,0,0,0,0,0]),i.VMHD=new Uint8Array([0,0,0,1,0,0,0,0,0,0,0,0])},e.box=function(e){for(var t=8,i=null,n=Array.prototype.slice.call(arguments,1),r=n.length,a=0;a>>24&255,i[1]=t>>>16&255,i[2]=t>>>8&255,i[3]=255&t,i.set(e,4);var s=8;for(a=0;a>>24&255,t>>>16&255,t>>>8&255,255&t,i>>>24&255,i>>>16&255,i>>>8&255,255&i,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255]))},e.trak=function(t){return e.box(e.types.trak,e.tkhd(t),e.mdia(t))},e.tkhd=function(t){var i=t.id,n=t.duration,r=t.presentWidth,a=t.presentHeight;return e.box(e.types.tkhd,new Uint8Array([0,0,0,7,0,0,0,0,0,0,0,0,i>>>24&255,i>>>16&255,i>>>8&255,255&i,0,0,0,0,n>>>24&255,n>>>16&255,n>>>8&255,255&n,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,0,0,0,r>>>8&255,255&r,0,0,a>>>8&255,255&a,0,0]))},e.mdia=function(t){return e.box(e.types.mdia,e.mdhd(t),e.hdlr(t),e.minf(t))},e.mdhd=function(t){var i=t.timescale,n=t.duration;return e.box(e.types.mdhd,new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0,i>>>24&255,i>>>16&255,i>>>8&255,255&i,n>>>24&255,n>>>16&255,n>>>8&255,255&n,85,196,0,0]))},e.hdlr=function(t){var i=null;return i="audio"===t.type?e.constants.HDLR_AUDIO:e.constants.HDLR_VIDEO,e.box(e.types.hdlr,i)},e.minf=function(t){var i=null;return i="audio"===t.type?e.box(e.types.smhd,e.constants.SMHD):e.box(e.types.vmhd,e.constants.VMHD),e.box(e.types.minf,i,e.dinf(),e.stbl(t))},e.dinf=function(){return e.box(e.types.dinf,e.box(e.types.dref,e.constants.DREF))},e.stbl=function(t){return e.box(e.types.stbl,e.stsd(t),e.box(e.types.stts,e.constants.STTS),e.box(e.types.stsc,e.constants.STSC),e.box(e.types.stsz,e.constants.STSZ),e.box(e.types.stco,e.constants.STCO))},e.stsd=function(t){return"audio"===t.type?"mp3"===t.codec?e.box(e.types.stsd,e.constants.STSD_PREFIX,e.mp3(t)):e.box(e.types.stsd,e.constants.STSD_PREFIX,e.mp4a(t)):e.box(e.types.stsd,e.constants.STSD_PREFIX,e.avc1(t))},e.mp3=function(t){var i=t.channelCount,n=t.audioSampleRate,r=new Uint8Array([0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,i,0,16,0,0,0,0,n>>>8&255,255&n,0,0]);return e.box(e.types[".mp3"],r)},e.mp4a=function(t){var i=t.channelCount,n=t.audioSampleRate,r=new Uint8Array([0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,i,0,16,0,0,0,0,n>>>8&255,255&n,0,0]);return e.box(e.types.mp4a,r,e.esds(t))},e.esds=function(t){var i=t.config||[],n=i.length,r=new Uint8Array([0,0,0,0,3,23+n,0,1,0,4,15+n,64,21,0,0,0,0,0,0,0,0,0,0,0,5].concat([n]).concat(i).concat([6,1,2]));return e.box(e.types.esds,r)},e.avc1=function(t){var i=t.avcc,n=t.codecWidth,r=t.codecHeight,a=new Uint8Array([0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,n>>>8&255,255&n,r>>>8&255,255&r,0,72,0,0,0,72,0,0,0,0,0,0,0,1,10,120,113,113,47,102,108,118,46,106,115,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,24,255,255]);return t.codec.indexOf("avc1")>=0?e.box(e.types.avc1,a,e.box(e.types.avcC,i)):e.box(e.types.hvc1,a,e.box(e.types.hvcC,i))},e.mvex=function(t){return e.box(e.types.mvex,e.trex(t))},e.trex=function(t){var i=t.id,n=new Uint8Array([0,0,0,0,i>>>24&255,i>>>16&255,i>>>8&255,255&i,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,1]);return e.box(e.types.trex,n)},e.moof=function(t,i){return e.box(e.types.moof,e.mfhd(t.sequenceNumber),e.traf(t,i))},e.mfhd=function(t){var i=new Uint8Array([0,0,0,0,t>>>24&255,t>>>16&255,t>>>8&255,255&t]);return e.box(e.types.mfhd,i)},e.traf=function(t,i){var n=t.id,r=e.box(e.types.tfhd,new Uint8Array([0,0,0,0,n>>>24&255,n>>>16&255,n>>>8&255,255&n])),a=e.box(e.types.tfdt,new Uint8Array([0,0,0,0,i>>>24&255,i>>>16&255,i>>>8&255,255&i])),s=e.sdtp(t),o=e.trun(t,s.byteLength+16+16+8+16+8+8);return e.box(e.types.traf,r,a,o,s)},e.sdtp=function(t){for(var i=t.samples||[],n=i.length,r=new Uint8Array(4+n),a=0;a>>24&255,r>>>16&255,r>>>8&255,255&r,i>>>24&255,i>>>16&255,i>>>8&255,255&i],0);for(var o=0;o>>24&255,u>>>16&255,u>>>8&255,255&u,l>>>24&255,l>>>16&255,l>>>8&255,255&l,h.isLeading<<2|h.dependsOn,h.isDependedOn<<6|h.hasRedundancy<<4|h.isNonSync,0,0,d>>>24&255,d>>>16&255,d>>>8&255,255&d],12+16*o)}return e.box(e.types.trun,s)},e.mdat=function(t){return e.box(e.types.mdat,t)},e}();n.init(),t.default=n},"./src/remux/mp4-remuxer.js": /*!**********************************!*\ !*** ./src/remux/mp4-remuxer.js ***! \**********************************/ function(e,t,i){i.r(t);var n=i( /*! ../utils/logger.js */ "./src/utils/logger.js"),r=i( /*! ./mp4-generator.js */ "./src/remux/mp4-generator.js"),a=i( /*! ./aac-silent.js */ "./src/remux/aac-silent.js"),s=i( /*! ../utils/browser.js */ "./src/utils/browser.js"),o=i( /*! ../core/media-segment-info.js */ "./src/core/media-segment-info.js"),u=i( /*! ../utils/exception.js */ "./src/utils/exception.js"),l=function(){function e(e){this.TAG="MP4Remuxer",this._config=e,this._isLive=!0===e.isLive,this._dtsBase=-1,this._dtsBaseInited=!1,this._audioDtsBase=1/0,this._videoDtsBase=1/0,this._audioNextDts=void 0,this._videoNextDts=void 0,this._audioStashedLastSample=null,this._videoStashedLastSample=null,this._audioMeta=null,this._videoMeta=null,this._audioSegmentInfoList=new o.MediaSegmentInfoList("audio"),this._videoSegmentInfoList=new o.MediaSegmentInfoList("video"),this._onInitSegment=null,this._onMediaSegment=null,this._forceFirstIDR=!(!s.default.chrome||!(s.default.version.major<50||50===s.default.version.major&&s.default.version.build<2661)),this._fillSilentAfterSeek=s.default.msedge||s.default.msie,this._mp3UseMpegAudio=!s.default.firefox,this._fillAudioTimestampGap=this._config.fixAudioTimestampGap}return e.prototype.destroy=function(){this._dtsBase=-1,this._dtsBaseInited=!1,this._audioMeta=null,this._videoMeta=null,this._audioSegmentInfoList.clear(),this._audioSegmentInfoList=null,this._videoSegmentInfoList.clear(),this._videoSegmentInfoList=null,this._onInitSegment=null,this._onMediaSegment=null},e.prototype.bindDataSource=function(e){return e.onDataAvailable=this.remux.bind(this),e.onTrackMetadata=this._onTrackMetadataReceived.bind(this),this},Object.defineProperty(e.prototype,"onInitSegment",{get:function(){return this._onInitSegment},set:function(e){this._onInitSegment=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onMediaSegment",{get:function(){return this._onMediaSegment},set:function(e){this._onMediaSegment=e},enumerable:!1,configurable:!0}),e.prototype.insertDiscontinuity=function(){this._audioNextDts=this._videoNextDts=void 0},e.prototype.seek=function(e){this._audioStashedLastSample=null,this._videoStashedLastSample=null,this._videoSegmentInfoList.clear(),this._audioSegmentInfoList.clear()},e.prototype.remux=function(e,t){if(!this._onMediaSegment)throw new u.IllegalStateException("MP4Remuxer: onMediaSegment callback must be specificed!");this._dtsBaseInited||this._calculateDtsBase(e,t),this._remuxVideo(t),this._remuxAudio(e)},e.prototype._onTrackMetadataReceived=function(e,t){var i=null,n="mp4",a=t.codec;if("audio"===e)this._audioMeta=t,"mp3"===t.codec&&this._mp3UseMpegAudio?(n="mpeg",a="",i=new Uint8Array):i=r.default.generateInitSegment(t);else{if("video"!==e)return;this._videoMeta=t,i=r.default.generateInitSegment(t)}if(!this._onInitSegment)throw new u.IllegalStateException("MP4Remuxer: onInitSegment callback must be specified!");this._onInitSegment(e,{type:e,data:i.buffer,codec:a,container:e+"/"+n,mediaDuration:t.duration})},e.prototype._calculateDtsBase=function(e,t){this._dtsBaseInited||(e.samples&&e.samples.length&&(this._audioDtsBase=e.samples[0].dts),t.samples&&t.samples.length&&(this._videoDtsBase=t.samples[0].dts),this._dtsBase=Math.min(this._audioDtsBase,this._videoDtsBase),this._dtsBaseInited=!0)},e.prototype.flushStashedSamples=function(){var e=this._videoStashedLastSample,t=this._audioStashedLastSample,i={type:"video",id:1,sequenceNumber:0,samples:[],length:0};null!=e&&(i.samples.push(e),i.length=e.length);var n={type:"audio",id:2,sequenceNumber:0,samples:[],length:0};null!=t&&(n.samples.push(t),n.length=t.length),this._videoStashedLastSample=null,this._audioStashedLastSample=null,this._remuxVideo(i,!0),this._remuxAudio(n,!0)},e.prototype._remuxAudio=function(e,t){if(null!=this._audioMeta){var i,u=e,l=u.samples,h=void 0,d=-1,c=this._audioMeta.refSampleDuration,f="mp3"===this._audioMeta.codec&&this._mp3UseMpegAudio,p=this._dtsBaseInited&&void 0===this._audioNextDts,m=!1;if(l&&0!==l.length&&(1!==l.length||t)){var _=0,g=null,v=0;f?(_=0,v=u.length):(_=8,v=8+u.length);var y=null;if(l.length>1&&(v-=(y=l.pop()).length),null!=this._audioStashedLastSample){var b=this._audioStashedLastSample;this._audioStashedLastSample=null,l.unshift(b),v+=b.length}null!=y&&(this._audioStashedLastSample=y);var S=l[0].dts-this._dtsBase;if(this._audioNextDts)h=S-this._audioNextDts;else if(this._audioSegmentInfoList.isEmpty())h=0,this._fillSilentAfterSeek&&!this._videoSegmentInfoList.isEmpty()&&"mp3"!==this._audioMeta.originalCodec&&(m=!0);else{var T=this._audioSegmentInfoList.getLastSampleBefore(S);if(null!=T){var E=S-(T.originalDts+T.duration);E<=3&&(E=0),h=S-(T.dts+T.duration+E)}else h=0}if(m){var w=S-h,A=this._videoSegmentInfoList.getLastSegmentBefore(S);if(null!=A&&A.beginDts=3*c&&this._fillAudioTimestampGap&&!s.default.safari){R=!0;var M,F=Math.floor(h/c);n.default.w(this.TAG,"Large audio timestamp gap detected, may cause AV sync to drift. Silent frames will be generated to avoid unsync.\noriginalDts: "+x+" ms, curRefDts: "+U+" ms, dtsCorrection: "+Math.round(h)+" ms, generate: "+F+" frames"),C=Math.floor(U),O=Math.floor(U+c)-C,null==(M=a.default.getSilentFrame(this._audioMeta.originalCodec,this._audioMeta.channelCount))&&(n.default.w(this.TAG,"Unable to generate silent frame for "+this._audioMeta.originalCodec+" with "+this._audioMeta.channelCount+" channels, repeat last frame"),M=L),D=[];for(var B=0;B=1?P[P.length-1].duration:Math.floor(c),this._audioNextDts=C+O;-1===d&&(d=C),P.push({dts:C,pts:C,cts:0,unit:b.unit,size:b.unit.byteLength,duration:O,originalDts:x,flags:{isLeading:0,dependsOn:1,isDependedOn:0,hasRedundancy:0}}),R&&P.push.apply(P,D)}}if(0===P.length)return u.samples=[],void(u.length=0);for(f?g=new Uint8Array(v):((g=new Uint8Array(v))[0]=v>>>24&255,g[1]=v>>>16&255,g[2]=v>>>8&255,g[3]=255&v,g.set(r.default.types.mdat,4)),I=0;I1&&(f-=(p=s.pop()).length),null!=this._videoStashedLastSample){var m=this._videoStashedLastSample;this._videoStashedLastSample=null,s.unshift(m),f+=m.length}null!=p&&(this._videoStashedLastSample=p);var _=s[0].dts-this._dtsBase;if(this._videoNextDts)u=_-this._videoNextDts;else if(this._videoSegmentInfoList.isEmpty())u=0;else{var g=this._videoSegmentInfoList.getLastSampleBefore(_);if(null!=g){var v=_-(g.originalDts+g.duration);v<=3&&(v=0),u=_-(g.dts+g.duration+v)}else u=0}for(var y=new o.MediaSegmentInfo,b=[],S=0;S=1?b[b.length-1].duration:Math.floor(this._videoMeta.refSampleDuration),E){var P=new o.SampleInfo(w,C,k,m.dts,!0);P.fileposition=m.fileposition,y.appendSyncPoint(P)}b.push({dts:w,pts:C,cts:A,units:m.units,size:m.length,isKeyframe:E,duration:k,originalDts:T,flags:{isLeading:0,dependsOn:E?2:1,isDependedOn:E?1:0,hasRedundancy:0,isNonSync:E?0:1}})}for((c=new Uint8Array(f))[0]=f>>>24&255,c[1]=f>>>16&255,c[2]=f>>>8&255,c[3]=255&f,c.set(r.default.types.mdat,4),S=0;S=0&&/(rv)(?::| )([\w.]+)/.exec(e)||e.indexOf("compatible")<0&&/(firefox)[ \/]([\w.]+)/.exec(e)||[],i=/(ipad)/.exec(e)||/(ipod)/.exec(e)||/(windows phone)/.exec(e)||/(iphone)/.exec(e)||/(kindle)/.exec(e)||/(android)/.exec(e)||/(windows)/.exec(e)||/(mac)/.exec(e)||/(linux)/.exec(e)||/(cros)/.exec(e)||[],r={browser:t[5]||t[3]||t[1]||"",version:t[2]||t[4]||"0",majorVersion:t[4]||t[2]||"0",platform:i[0]||""},a={};if(r.browser){a[r.browser]=!0;var s=r.majorVersion.split(".");a.version={major:parseInt(r.majorVersion,10),string:r.version},s.length>1&&(a.version.minor=parseInt(s[1],10)),s.length>2&&(a.version.build=parseInt(s[2],10))}for(var o in r.platform&&(a[r.platform]=!0),(a.chrome||a.opr||a.safari)&&(a.webkit=!0),(a.rv||a.iemobile)&&(a.rv&&delete a.rv,r.browser="msie",a.msie=!0),a.edge&&(delete a.edge,r.browser="msedge",a.msedge=!0),a.opr&&(r.browser="opera",a.opera=!0),a.safari&&a.android&&(r.browser="android",a.android=!0),a.name=r.browser,a.platform=r.platform,n)n.hasOwnProperty(o)&&delete n[o];Object.assign(n,a)}(),t.default=n},"./src/utils/exception.js": /*!********************************!*\ !*** ./src/utils/exception.js ***! \********************************/ function(e,t,i){i.r(t),i.d(t,{RuntimeException:function(){return a},IllegalStateException:function(){return s},InvalidArgumentException:function(){return o},NotImplementedException:function(){return u}});var n,r=(n=function(e,t){return(n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var i in t)Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i])})(e,t)},function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function i(){this.constructor=e}n(e,t),e.prototype=null===t?Object.create(t):(i.prototype=t.prototype,new i)}),a=function(){function e(e){this._message=e}return Object.defineProperty(e.prototype,"name",{get:function(){return"RuntimeException"},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"message",{get:function(){return this._message},enumerable:!1,configurable:!0}),e.prototype.toString=function(){return this.name+": "+this.message},e}(),s=function(e){function t(t){return e.call(this,t)||this}return r(t,e),Object.defineProperty(t.prototype,"name",{get:function(){return"IllegalStateException"},enumerable:!1,configurable:!0}),t}(a),o=function(e){function t(t){return e.call(this,t)||this}return r(t,e),Object.defineProperty(t.prototype,"name",{get:function(){return"InvalidArgumentException"},enumerable:!1,configurable:!0}),t}(a),u=function(e){function t(t){return e.call(this,t)||this}return r(t,e),Object.defineProperty(t.prototype,"name",{get:function(){return"NotImplementedException"},enumerable:!1,configurable:!0}),t}(a)},"./src/utils/logger.js": /*!*****************************!*\ !*** ./src/utils/logger.js ***! \*****************************/ function(e,t,i){i.r(t);var n=i( /*! events */ "./node_modules/events/events.js"),r=i.n(n),a=function(){function e(){}return e.e=function(t,i){t&&!e.FORCE_GLOBAL_TAG||(t=e.GLOBAL_TAG);var n="["+t+"] > "+i;e.ENABLE_CALLBACK&&e.emitter.emit("log","error",n),e.ENABLE_ERROR&&(console.error?console.error(n):console.warn)},e.i=function(t,i){t&&!e.FORCE_GLOBAL_TAG||(t=e.GLOBAL_TAG);var n="["+t+"] > "+i;e.ENABLE_CALLBACK&&e.emitter.emit("log","info",n),e.ENABLE_INFO&&console.info&&console.info(n)},e.w=function(t,i){t&&!e.FORCE_GLOBAL_TAG||(t=e.GLOBAL_TAG);var n="["+t+"] > "+i;e.ENABLE_CALLBACK&&e.emitter.emit("log","warn",n),e.ENABLE_WARN&&console.warn},e.d=function(t,i){t&&!e.FORCE_GLOBAL_TAG||(t=e.GLOBAL_TAG);var n="["+t+"] > "+i;e.ENABLE_CALLBACK&&e.emitter.emit("log","debug",n),e.ENABLE_DEBUG&&console.debug&&console.debug(n)},e.v=function(t,i){t&&!e.FORCE_GLOBAL_TAG||(t=e.GLOBAL_TAG);var n="["+t+"] > "+i;e.ENABLE_CALLBACK&&e.emitter.emit("log","verbose",n),e.ENABLE_VERBOSE},e}();a.GLOBAL_TAG="flv.js",a.FORCE_GLOBAL_TAG=!1,a.ENABLE_ERROR=!0,a.ENABLE_INFO=!0,a.ENABLE_WARN=!0,a.ENABLE_DEBUG=!0,a.ENABLE_VERBOSE=!0,a.ENABLE_CALLBACK=!1,a.emitter=new(r()),t.default=a},"./src/utils/logging-control.js": /*!**************************************!*\ !*** ./src/utils/logging-control.js ***! \**************************************/ function(e,t,i){i.r(t);var n=i( /*! events */ "./node_modules/events/events.js"),r=i.n(n),a=i( /*! ./logger.js */ "./src/utils/logger.js"),s=function(){function e(){}return Object.defineProperty(e,"forceGlobalTag",{get:function(){return a.default.FORCE_GLOBAL_TAG},set:function(t){a.default.FORCE_GLOBAL_TAG=t,e._notifyChange()},enumerable:!1,configurable:!0}),Object.defineProperty(e,"globalTag",{get:function(){return a.default.GLOBAL_TAG},set:function(t){a.default.GLOBAL_TAG=t,e._notifyChange()},enumerable:!1,configurable:!0}),Object.defineProperty(e,"enableAll",{get:function(){return a.default.ENABLE_VERBOSE&&a.default.ENABLE_DEBUG&&a.default.ENABLE_INFO&&a.default.ENABLE_WARN&&a.default.ENABLE_ERROR},set:function(t){a.default.ENABLE_VERBOSE=t,a.default.ENABLE_DEBUG=t,a.default.ENABLE_INFO=t,a.default.ENABLE_WARN=t,a.default.ENABLE_ERROR=t,e._notifyChange()},enumerable:!1,configurable:!0}),Object.defineProperty(e,"enableDebug",{get:function(){return a.default.ENABLE_DEBUG},set:function(t){a.default.ENABLE_DEBUG=t,e._notifyChange()},enumerable:!1,configurable:!0}),Object.defineProperty(e,"enableVerbose",{get:function(){return a.default.ENABLE_VERBOSE},set:function(t){a.default.ENABLE_VERBOSE=t,e._notifyChange()},enumerable:!1,configurable:!0}),Object.defineProperty(e,"enableInfo",{get:function(){return a.default.ENABLE_INFO},set:function(t){a.default.ENABLE_INFO=t,e._notifyChange()},enumerable:!1,configurable:!0}),Object.defineProperty(e,"enableWarn",{get:function(){return a.default.ENABLE_WARN},set:function(t){a.default.ENABLE_WARN=t,e._notifyChange()},enumerable:!1,configurable:!0}),Object.defineProperty(e,"enableError",{get:function(){return a.default.ENABLE_ERROR},set:function(t){a.default.ENABLE_ERROR=t,e._notifyChange()},enumerable:!1,configurable:!0}),e.getConfig=function(){return{globalTag:a.default.GLOBAL_TAG,forceGlobalTag:a.default.FORCE_GLOBAL_TAG,enableVerbose:a.default.ENABLE_VERBOSE,enableDebug:a.default.ENABLE_DEBUG,enableInfo:a.default.ENABLE_INFO,enableWarn:a.default.ENABLE_WARN,enableError:a.default.ENABLE_ERROR,enableCallback:a.default.ENABLE_CALLBACK}},e.applyConfig=function(e){a.default.GLOBAL_TAG=e.globalTag,a.default.FORCE_GLOBAL_TAG=e.forceGlobalTag,a.default.ENABLE_VERBOSE=e.enableVerbose,a.default.ENABLE_DEBUG=e.enableDebug,a.default.ENABLE_INFO=e.enableInfo,a.default.ENABLE_WARN=e.enableWarn,a.default.ENABLE_ERROR=e.enableError,a.default.ENABLE_CALLBACK=e.enableCallback},e._notifyChange=function(){var t=e.emitter;if(t.listenerCount("change")>0){var i=e.getConfig();t.emit("change",i)}},e.registerListener=function(t){e.emitter.addListener("change",t)},e.removeListener=function(t){e.emitter.removeListener("change",t)},e.addLogListener=function(t){a.default.emitter.addListener("log",t),a.default.emitter.listenerCount("log")>0&&(a.default.ENABLE_CALLBACK=!0,e._notifyChange())},e.removeLogListener=function(t){a.default.emitter.removeListener("log",t),0===a.default.emitter.listenerCount("log")&&(a.default.ENABLE_CALLBACK=!1,e._notifyChange())},e}();s.emitter=new(r()),t.default=s},"./src/utils/polyfill.js": /*!*******************************!*\ !*** ./src/utils/polyfill.js ***! \*******************************/ function(e,t,i){i.r(t);var n=function(){function e(){}return e.install=function(){Object.setPrototypeOf=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e},Object.assign=Object.assign||function(e){if(null==e)throw new TypeError("Cannot convert undefined or null to object");for(var t=Object(e),i=1;i=128){t.push(String.fromCharCode(65535&s)),r+=2;continue}}else if(i[r]<240){if(n(i,r,2)&&(s=(15&i[r])<<12|(63&i[r+1])<<6|63&i[r+2])>=2048&&55296!=(63488&s)){t.push(String.fromCharCode(65535&s)),r+=3;continue}}else if(i[r]<248){var s;if(n(i,r,3)&&(s=(7&i[r])<<18|(63&i[r+1])<<12|(63&i[r+2])<<6|63&i[r+3])>65536&&s<1114112){s-=65536,t.push(String.fromCharCode(s>>>10|55296)),t.push(String.fromCharCode(1023&s|56320)),r+=4;continue}}t.push(String.fromCharCode(65533)),++r}return t.join("")}}},i={};function r(e){var n=i[e];if(void 0!==n)return n.exports;var a=i[e]={exports:{}};return t[e].call(a.exports,a,a.exports,r),a.exports}return r.m=t,r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,{a:t}),t},r.d=function(e,t){for(var i in t)r.o(t,i)&&!r.o(e,i)&&Object.defineProperty(e,i,{enumerable:!0,get:t[i]})},r.g=function(){if("object"===("undefined"==typeof globalThis?"undefined":n(globalThis)))return globalThis;try{return this||new Function("return this")()}catch(e){if("object"===("undefined"==typeof window?"undefined":n(window)))return window}}(),r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r("./src/index.js")}()},"object"===(void 0===i?"undefined":n(i))&&"object"===(void 0===t?"undefined":n(t))?t.exports=a():"function"==typeof define&&define.amd?define([],a):"object"===(void 0===i?"undefined":n(i))?i.flvjshevc=a():r.flvjshevc=a()}).call(this,e("_process"))},{_process:44}],69:[function(e,t,i){"use strict";function n(e,t){for(var i=0;i0&&i.extensionInfo.vHeight>0&&(i.size.width=i.extensionInfo.vWidth,i.size.height=i.extensionInfo.vHeight)),i.mediaInfo.duration,null!=i.onDemuxed&&i.onDemuxed(i.onReadyOBJ);for(var e=!1;void 0!==i.mpegTsObj&&null!==i.mpegTsObj;){var n=i.mpegTsObj.readPacket();if(n.size<=0)break;var r=n.dtime>0?n.dtime:n.ptime;if(!(r<0)){if(0==n.type){r<=i.vPreFramePTS&&(e=!0);var a=u.PACK_NALU(n.layer),o=1==n.keyframe,l=1==e?r+i.vStartTime:r,h=new s.BufferFrame(l,o,a,!0);i.bufObject.appendFrame(h.pts,h.data,!0,h.isKey),i.vPreFramePTS=l,null!=i.onSamples&&i.onSamples(i.onReadyOBJ,h)}else if(r<=i.aPreFramePTS&&(e=!0),"aac"==i.mediaInfo.aCodec)for(var d=n.data,c=0;c=3?(i._onTsReady(e),window.clearInterval(i.timerTsWasm),i.timerTsWasm=null):(i.mpegTsWasmRetryLoadTimes+=1,i.mpegTsObj.initDemuxer())}),3e3)}},{key:"_onTsReady",value:function(e){var t=this;t.hls.fetchM3u8(e),t.mpegTsWasmState=!0,t.timerFeed=window.setInterval((function(){if(t.tsList.length>0&&0==t.lockWait.state)try{var e=t.tsList.shift();if(null!=e){var i=e.streamURI,n=e.streamDur;t.lockWait.state=!0,t.lockWait.lockMember.dur=n,t.mpegTsObj.isLive=t.hls.isLive(),t.mpegTsObj.demuxURL(i)}else console.error("_onTsReady need wait ")}catch(e){console.error("onTsReady ERROR:",e),t.lockWait.state=!1}}),50)}},{key:"release",value:function(){this.hls&&this.hls.release(),this.hls=null,this.timerFeed&&window.clearInterval(this.timerFeed),this.timerFeed=null,this.timerTsWasm&&window.clearInterval(this.timerTsWasm),this.timerTsWasm=null}},{key:"bindReady",value:function(e){this.onReadyOBJ=e}},{key:"popBuffer",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:1,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:-1;return t<0?null:1===e?t+1>this.bufObject.videoBuffer.length?null:this.bufObject.vFrame(t):2===e?t+1>this.bufObject.audioBuffer.length?null:this.bufObject.aFrame(t):void 0}},{key:"getVLen",value:function(){return this.bufObject.videoBuffer.length}},{key:"getALen",value:function(){return this.bufObject.audioBuffer.length}},{key:"getLastIdx",value:function(){return this.bufObject.videoBuffer.length-1}},{key:"getALastIdx",value:function(){return this.bufObject.audioBuffer.length-1}},{key:"getACodec",value:function(){return this.aCodec}},{key:"getVCodec",value:function(){return this.vCodec}},{key:"getDurationMs",value:function(){return this.durationMs}},{key:"getFPS",value:function(){return this.fps}},{key:"getSampleRate",value:function(){return this.sampleRate}},{key:"getSampleChannel",value:function(){return this.aChannel}},{key:"getSize",value:function(){return this.size}},{key:"seek",value:function(e){if(e>=0){var t=this.bufObject.seekIDR(e);this.seekPos=t}}}])&&n(t.prototype,i),h&&n(t,h),e}();i.M3u8=h},{"../consts":52,"../decoder/hevc-imp":64,"./buffer":66,"./bufferFrame":67,"./m3u8base":70,"./mpegts/mpeg.js":74}],70:[function(e,t,i){"use strict";function n(e,t){for(var i=0;i ",t),setTimeout((function(){i.fetchM3u8(e)}),500)}))}},{key:"_uriParse",value:function(e){this._preURI="";var t=e.split("://"),i=null,n=null;if(t.length<1)return!1;t.length>1?(i=t[0],n=t[1].split("/"),this._preURI=i+"://"):n=t[0].split("/");for(var r=0;rp&&(o=p);var m=n[l+=1],_=null;if(m.indexOf("http")>=0)_=m;else{if("/"===m[0]){var g=this._preURI.split("//"),v=g[g.length-1].split("/");this._preURI=g[0]+"//"+v[0]}_=this._preURI+m}this._slices.indexOf(_)<0&&(this._slices.push(_),this._slices[this._slices.length-1],null!=this.onTransportStream&&this.onTransportStream(_,p))}}}if(this._slices.length>s.hlsSliceLimit&&this._type==r.PLAYER_IN_TYPE_M3U8_LIVE&&(this._slices=this._slices.slice(-1*s.hlsSliceLimit)),null!=this.onFinished){var y={type:this._type,duration:-1};this.onFinished(y)}return o}},{key:"_readTag",value:function(e){var t=s.tagParse.exec(e);return null!==t?{key:t[1],value:t[3]}:null}}])&&n(t.prototype,i),o&&n(t,o),e}();i.M3u8Base=o},{"../consts":52}],71:[function(e,t,i){"use strict";var n=e("mp4box"),r=e("../decoder/hevc-header"),a=e("../decoder/hevc-imp"),s=e("./buffer"),o=e("../consts"),u={96e3:0,88200:1,64e3:2,48e3:3,44100:4,32e3:5,24e3:6,22050:7,16e3:8,12e3:9,11025:10,8e3:11,7350:12,Reserved:13,"frequency is written explictly":15},l=function(e){for(var t=[],i=0;i1&&void 0!==arguments[1]&&arguments[1],i=null;return t?((i=e)[0]=r.DEFINE_STARTCODE[0],i[1]=r.DEFINE_STARTCODE[1],i[2]=r.DEFINE_STARTCODE[2],i[3]=r.DEFINE_STARTCODE[3]):((i=new Uint8Array(r.DEFINE_STARTCODE.length+e.length)).set(r.DEFINE_STARTCODE,0),i.set(e,r.DEFINE_STARTCODE.length)),i},h.prototype.setAACAdts=function(e){var t=null,i=this.aacProfile,n=u[this.sampleRate],r=new Uint8Array(7),a=r.length+e.length;return r[0]=255,r[1]=241,r[2]=(i-1<<6)+(n<<2)+0,r[3]=128+(a>>11),r[4]=(2047&a)>>3,r[5]=31+((7&a)<<5),r[6]=252,(t=new Uint8Array(a)).set(r,0),t.set(e,r.length),t},h.prototype.demux=function(){var e=this;e.seekPos=-1,e.mp4boxfile=n.createFile(),e.movieInfo=null,e.videoCodec=null,e.durationMs=-1,e.fps=-1,e.sampleRate=-1,e.aacProfile=2,e.size={width:-1,height:-1},e.bufObject=s(),e.audioNone=!1,e.naluHeader={vps:null,sps:null,pps:null,sei:null},e.mp4boxfile.onError=function(e){},this.mp4boxfile.onReady=function(t){for(var i in e.movieInfo=t,t.tracks)"VideoHandler"!==t.tracks[i].name&&"video"!==t.tracks[i].type||(t.tracks[i].codec,t.tracks[i].codec.indexOf("hev")>=0||t.tracks[i].codec.indexOf("hvc")>=0?e.videoCodec=o.CODEC_H265:t.tracks[i].codec.indexOf("avc")>=0&&(e.videoCodec=o.CODEC_H264));var n=-1;if(n=t.videoTracks[0].samples_duration/t.videoTracks[0].timescale,e.durationMs=1e3*n,e.fps=t.videoTracks[0].nb_samples/n,e.seekDiffTime=1/e.fps,e.size.width=t.videoTracks[0].track_width,e.size.height=t.videoTracks[0].track_height,t.audioTracks.length>0){e.sampleRate=t.audioTracks[0].audio.sample_rate;var r=t.audioTracks[0].codec.split(".");e.aacProfile=r[r.length-1]}else e.audioNone=!0;null!=e.onMp4BoxReady&&e.onMp4BoxReady(e.videoCodec),e.videoCodec===o.CODEC_H265?(e.initializeAllSourceBuffers(),e.mp4boxfile.start()):(e.videoCodec,o.CODEC_H264)},e.mp4boxfile.onSamples=function(t,i,n){var s=window.setInterval((function(){for(var i=0;i3?e.naluHeader.sei=e.setStartCode(_[3][0].data,!1):e.naluHeader.sei=new Uint8Array,e.naluHeader}else e.videoCodec==o.CODEC_H264&&(e.naluHeader.vps=new Uint8Array,e.naluHeader.sps=e.setStartCode(f.SPS[0].nalu,!1),e.naluHeader.pps=e.setStartCode(f.PPS[0].nalu,!1),e.naluHeader.sei=new Uint8Array);h[4].toString(16),e.naluHeader.vps[4].toString(16),l(e.naluHeader.vps),l(h);var g=e.setStartCode(h.subarray(0,e.naluHeader.vps.length),!0);if(l(g),h[4]===e.naluHeader.vps[4]){var v=e.naluHeader.vps.length+4,y=e.naluHeader.vps.length+e.naluHeader.sps.length+4,b=e.naluHeader.vps.length+e.naluHeader.sps.length+e.naluHeader.pps.length+4;if(e.naluHeader.sei.length<=0&&e.naluHeader.sps.length>0&&h[v]===e.naluHeader.sps[4]&&e.naluHeader.pps.length>0&&h[y]===e.naluHeader.pps[4]&&78===h[b]){h[e.naluHeader.vps.length+4],e.naluHeader.sps[4],h[e.naluHeader.vps.length+e.naluHeader.sps.length+4],e.naluHeader.pps[4],h[e.naluHeader.vps.length+e.naluHeader.sps.length+e.naluHeader.pps.length+4];for(var S=0,T=0;T4&&h[4]===e.naluHeader.sei[4]){var E=h.subarray(0,10),w=new Uint8Array(e.naluHeader.vps.length+E.length);w.set(E,0),w.set(e.naluHeader.vps,E.length),w[3]=1,e.naluHeader.vps=null,e.naluHeader.vps=new Uint8Array(w),w=null,E=null,(h=h.subarray(10))[4],e.naluHeader.vps[4],e.naluHeader.vps}else if(0===e.naluHeader.sei.length&&78===h[4]){h=e.setStartCode(h,!0);for(var A=0,C=0;C1&&void 0!==arguments[1]?arguments[1]:0;return e.fileStart=t,this.mp4boxfile.appendBuffer(e)},h.prototype.finishBuffer=function(){this.mp4boxfile.flush()},h.prototype.play=function(){},h.prototype.getVideoCoder=function(){return this.videoCodec},h.prototype.getDurationMs=function(){return this.durationMs},h.prototype.getFPS=function(){return this.fps},h.prototype.getSampleRate=function(){return this.sampleRate},h.prototype.getSize=function(){return this.size},h.prototype.seek=function(e){if(e>=0){var t=this.bufObject.seekIDR(e);this.seekPos=t}},h.prototype.popBuffer=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:1,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:-1;return t<0?null:1==e?this.bufObject.vFrame(t):2==e?this.bufObject.aFrame(t):void 0},h.prototype.addBuffer=function(e){var t=e.id;this.mp4boxfile.setExtractionOptions(t)},h.prototype.initializeAllSourceBuffers=function(){if(this.movieInfo){for(var e=this.movieInfo,t=0;t>5)}},{key:"sliceAACFrames",value:function(e,t){for(var i=[],n=e,r=0;r>4==15){var a=this._getPktLen(t[r+3],t[r+4],t[r+5]);if(a<=0)continue;var s=t.subarray(r,r+a),o=new Uint8Array(a);o.set(s,0),i.push({ptime:n,data:o}),n+=this.frameDurSec,r+=a}else r+=1;return i}}])&&n(t.prototype,i),r&&n(t,r),e}();i.AACDecoder=r},{}],74:[function(e,t,i){(function(t){"use strict";function n(e,t){for(var i=0;i ",e),n=null})).catch((function(i){console.error("demuxerTsInit ERROR fetch ERROR ==> ",i),t._releaseOffset(),t.onDemuxedFailed&&t.onDemuxedFailed(i,e)}))}},{key:"_releaseOffset",value:function(){void 0!==this.offsetDemux&&null!==this.offsetDemux&&(Module._free(this.offsetDemux),this.offsetDemux=null)}},{key:"_demuxCore",value:function(e){if(this._releaseOffset(),this._refreshDemuxer(),!(e.length<=0)){this.offsetDemux=Module._malloc(e.length),Module.HEAP8.set(e,this.offsetDemux);var t=Module.cwrap("demuxBox","number",["number","number","number"])(this.offsetDemux,e.length,this.isLive);Module._free(this.offsetDemux),this.offsetDemux=null,t>=0&&(this._setMediaInfo(),this._setExtensionInfo(),null!=this.onDemuxed&&this.onDemuxed())}}},{key:"_setMediaInfo",value:function(){var e=Module.cwrap("getMediaInfo","number",[])(),t=Module.HEAPU32[e/4],i=Module.HEAPU32[e/4+1],n=Module.HEAPF64[e/8+1],s=Module.HEAPF64[e/8+1+1],o=Module.HEAPF64[e/8+1+1+1],u=Module.HEAPF64[e/8+1+1+1+1],l=Module.HEAPU32[e/4+2+2+2+2+2];this.mediaAttr.vFps=n,this.mediaAttr.vGop=l,this.mediaAttr.vDuration=s,this.mediaAttr.aDuration=o,this.mediaAttr.duration=u;var h=Module.cwrap("getAudioCodecID","number",[])();h>=0?(this.mediaAttr.aCodec=a.CODEC_OFFSET_TABLE[h],this.mediaAttr.sampleRate=t>0?t:a.DEFAULT_SAMPLERATE,this.mediaAttr.sampleChannel=i>=0?i:a.DEFAULT_CHANNEL):(this.mediaAttr.sampleRate=0,this.mediaAttr.sampleChannel=0,this.mediaAttr.audioNone=!0);var d=Module.cwrap("getVideoCodecID","number",[])();d>=0&&(this.mediaAttr.vCodec=a.CODEC_OFFSET_TABLE[d]),null==this.aacDec?this.aacDec=new r.AACDecoder(this.mediaAttr):this.aacDec.updateConfig(this.mediaAttr)}},{key:"_setExtensionInfo",value:function(){var e=Module.cwrap("getExtensionInfo","number",[])(),t=Module.HEAPU32[e/4],i=Module.HEAPU32[e/4+1];this.extensionInfo.vWidth=t,this.extensionInfo.vHeight=i}},{key:"readMediaInfo",value:function(){return this.mediaAttr}},{key:"readExtensionInfo",value:function(){return this.extensionInfo}},{key:"readAudioNone",value:function(){return this.mediaAttr.audioNone}},{key:"_readLayer",value:function(){null===this.naluLayer?this.naluLayer={vps:null,sps:null,pps:null,sei:null}:(this.naluLayer.vps=null,this.naluLayer.sps=null,this.naluLayer.pps=null,this.naluLayer.sei=null),null===this.vlcLayer?this.vlcLayer={vlc:null}:this.vlcLayer.vlc=null;var e=Module.cwrap("getSPSLen","number",[])(),t=Module.cwrap("getSPS","number",[])();if(!(e<0)){var i=Module.HEAPU8.subarray(t,t+e);this.naluLayer.sps=new Uint8Array(e),this.naluLayer.sps.set(i,0);var n=Module.cwrap("getPPSLen","number",[])(),r=Module.cwrap("getPPS","number",[])(),s=Module.HEAPU8.subarray(r,r+n);this.naluLayer.pps=new Uint8Array(n),this.naluLayer.pps.set(s,0);var o=Module.cwrap("getSEILen","number",[])(),u=Module.cwrap("getSEI","number",[])(),l=Module.HEAPU8.subarray(u,u+o);this.naluLayer.sei=new Uint8Array(o),this.naluLayer.sei.set(l,0);var h=Module.cwrap("getVLCLen","number",[])(),d=Module.cwrap("getVLC","number",[])(),c=Module.HEAPU8.subarray(d,d+h);if(this.vlcLayer.vlc=new Uint8Array(h),this.vlcLayer.vlc.set(c,0),this.mediaAttr.vCodec==a.DEF_HEVC||this.mediaAttr.vCodec==a.DEF_H265){var f=Module.cwrap("getVPSLen","number",[])(),p=Module.cwrap("getVPS","number",[])(),m=Module.HEAPU8.subarray(p,p+f);this.naluLayer.vps=new Uint8Array(f),this.naluLayer.vps.set(m,0),Module._free(m),m=null}else this.mediaAttr.vCodec==a.DEF_AVC||(this.mediaAttr.vCodec,a.DEF_H264);return Module._free(i),i=null,Module._free(s),s=null,Module._free(l),l=null,Module._free(c),c=null,{nalu:this.naluLayer,vlc:this.vlcLayer}}}},{key:"isHEVC",value:function(){return this.mediaAttr.vCodec==a.DEF_HEVC||this.mediaAttr.vCodec==a.DEF_H265}},{key:"readPacket",value:function(){var e=Module.cwrap("getPacket","number",[])(),t=Module.HEAPU32[e/4],i=Module.HEAPU32[e/4+1],n=Module.HEAPF64[e/8+1],r=Module.HEAPF64[e/8+1+1],s=Module.HEAPU32[e/4+1+1+2+2],o=Module.HEAPU32[e/4+1+1+2+2+1],u=Module.HEAPU8.subarray(o,o+i),l=this._readLayer(),h={type:t,size:i,ptime:n,dtime:r,keyframe:s,src:u,data:1==t&&this.mediaAttr.aCodec==a.DEF_AAC?this.aacDec.sliceAACFrames(n,u):u,layer:l};return Module._free(u),u=null,h}},{key:"_refreshDemuxer",value:function(){this.releaseTsDemuxer(),this._initDemuxer()}},{key:"_initDemuxer",value:function(){Module.cwrap("initTsMissile","number",[])(),Module.cwrap("initializeDemuxer","number",[])()}},{key:"releaseTsDemuxer",value:function(){Module.cwrap("exitTsMissile","number",[])()}}])&&n(i.prototype,s),o&&n(i,o),e}();i.MPEG_JS=s}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./consts":72,"./decoder/aac":73}],75:[function(e,t,i){"use strict";function n(e,t){for(var i=0;i0&&e.extensionInfo.vHeight>0&&(e.size.width=e.extensionInfo.vWidth,e.size.height=e.extensionInfo.vHeight);for(var t=null;!((t=e.mpegTsObj.readPacket()).size<=0);){var i=t.dtime;if(0==t.type){var n=s.PACK_NALU(t.layer),r=1==t.keyframe;e.bufObject.appendFrame(i,n,!0,r)}else if("aac"==e.mediaInfo.aCodec)for(var a=t.data,o=0;o0&&void 0!==arguments[0]?arguments[0]:1,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:-1;return t<0?null:1==e?this.bufObject.vFrame(t):2==e?this.bufObject.aFrame(t):void 0}},{key:"isHEVC",value:function(){return this.mpegTsObj.isHEVC()}},{key:"getACodec",value:function(){return this.aCodec}},{key:"getVCodec",value:function(){return this.vCodec}},{key:"getAudioNone",value:function(){return this.mpegTsObj.mediaAttr.audioNone}},{key:"getDurationMs",value:function(){return this.durationMs}},{key:"getFPS",value:function(){return this.fps}},{key:"getSampleRate",value:function(){return this.sampleRate}},{key:"getSize",value:function(){return this.size}},{key:"seek",value:function(e){if(e>=0){var t=this.bufObject.seekIDR(e);this.seekPos=t}}}])&&n(t.prototype,i),o&&n(t,o),e}();i.MpegTs=o},{"../decoder/hevc-imp":64,"./buffer":66,"./mpegts/mpeg.js":74}],76:[function(e,t,i){(function(t){"use strict";function n(e){return(n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function r(e,t){for(var i=0;i0&&(i=!0),this.configFormat.type===v.PLAYER_IN_TYPE_RAW_265&&(i=!0,this.playMode=v.PLAYER_MODE_NOTIME_LIVE),this.playParam={durationMs:0,fps:0,sampleRate:0,size:{width:0,height:0},audioNone:i,videoCodec:v.CODEC_H265},y.UI.createPlayerRender(this.configFormat.playerId,this.configFormat.playerW,this.configFormat.playerH),!1===this._isSupportWASM())return this._makeMP4Player(!1),0;if(!1===this.configFormat.extInfo.hevc)return Module.cwrap("AVPlayerInit","number",["string","string"])(this.configFormat.token,"0.0.0"),this._makeMP4Player(!0),0;var n=window.setInterval((function(){t.STATICE_MEM_playerIndexPtr===e.playerIndex&&(t.STATICE_MEM_playerIndexPtr,e.playerIndex,window.WebAssembly?(t.STATIC_MEM_wasmDecoderState,1==t.STATIC_MEM_wasmDecoderState&&(e._makeMP4Player(),t.STATICE_MEM_playerIndexPtr+=1,window.clearInterval(n),n=null)):(/iPhone|iPad/.test(window.navigator.userAgent),t.STATICE_MEM_playerIndexPtr+=1,window.clearInterval(n),n=null))}),500)}},{key:"release",value:function(){return void 0!==this.player&&null!==this.player&&(this.player,this.playParam.videoCodec===v.CODEC_H265&&this.player?(this.configFormat.type==v.PLAYER_IN_TYPE_M3U8&&void 0!==this.hlsObj&&null!==this.hlsObj&&this.hlsObj.release(),this.player.release()):this.player.release(),void 0!==this.snapshotCanvasContext&&null!==this.snapshotCanvasContext&&(b.releaseContext(this.snapshotCanvasContext),this.snapshotCanvasContext=null,void 0!==this.snapshotYuvLastFrame&&null!==this.snapshotYuvLastFrame&&(this.snapshotYuvLastFrame.luma=null,this.snapshotYuvLastFrame.chromaB=null,this.snapshotYuvLastFrame.chromaR=null,this.snapshotYuvLastFrame.width=0,this.snapshotYuvLastFrame.height=0)),void 0!==this.workerFetch&&null!==this.workerFetch&&(this.workerFetch.postMessage({cmd:"stop",params:"",type:this.mediaExtProtocol}),this.workerFetch.onmessage=null),void 0!==this.workerParse&&null!==this.workerParse&&(this.workerParse.postMessage({cmd:"stop",params:""}),this.workerParse.onmessage=null),this.workerFetch=null,this.workerParse=null,this.configFormat.extInfo.readyShow=!0,window.onclick=document.body.onclick=null,window.g_players={},!0)}},{key:"debugYUV",value:function(e){this.player.debugYUV(e)}},{key:"setPlaybackRate",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:1;return!(this.playParam.videoCodec===v.CODEC_H265||e<=0||void 0===this.player||null===this.player)&&this.player.setPlaybackRate(e)}},{key:"getPlaybackRate",value:function(){return void 0!==this.player&&null!==this.player&&(this.playParam.videoCodec===v.CODEC_H265?1:this.player.getPlaybackRate())}},{key:"setRenderScreen",value:function(){var e=arguments.length>0&&void 0!==arguments[0]&&arguments[0];return void 0!==this.player&&null!==this.player&&(this.player.setScreen(e),!0)}},{key:"play",value:function(){if(void 0===this.player||null===this.player)return!1;if(this.playParam.videoCodec===v.CODEC_H265){var e={seekPos:this._getSeekTarget(),mode:this.playMode,accurateSeek:this.configFormat.accurateSeek,seekEvent:!1,realPlay:!0};this.player.play(e)}else this.player.play();return!0}},{key:"pause",value:function(){return void 0!==this.player&&null!==this.player&&(this.player.pause(),!0)}},{key:"isPlaying",value:function(){return void 0!==this.player&&null!==this.player&&this.player.isPlayingState()}},{key:"setVoice",value:function(e){return!(e<0||void 0===this.player||null===this.player||(this.volume=e,this.player&&this.player.setVoice(e),0))}},{key:"getVolume",value:function(){return this.volume}},{key:"mediaInfo",value:function(){var e={meta:this.playParam,videoType:this.playMode};return e.meta.isHEVC=0===this.playParam.videoCodec,e}},{key:"snapshot",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null;return null===e||void 0!==this.playParam&&null!==this.playParam&&(0===this.playParam.videoCodec?(this.player.setScreen(!0),e.width=this.snapshotYuvLastFrame.width,e.height=this.snapshotYuvLastFrame.height,this.snapshotYuvLastFrame,void 0!==this.snapshotCanvasContext&&null!==this.snapshotCanvasContext||(this.snapshotCanvasContext=b.setupCanvas(e,{preserveDrawingBuffer:!1})),b.renderFrame(this.snapshotCanvasContext,this.snapshotYuvLastFrame.luma,this.snapshotYuvLastFrame.chromaB,this.snapshotYuvLastFrame.chromaR,this.snapshotYuvLastFrame.width,this.snapshotYuvLastFrame.height)):(e.width=this.playParam.size.width,e.height=this.playParam.size.height,e.getContext("2d").drawImage(this.player.videoTag,0,0,e.width,e.height))),null}},{key:"_seekHLS",value:function(e,t,i){if(void 0===this.player||null===this.player)return!1;setTimeout((function(){t.player.getCachePTS(),t.player.getCachePTS()>e?i():t._seekHLS(e,t,i)}),100)}},{key:"seek",value:function(e){if(void 0===this.player||null===this.player)return!1;var t=this;this.seekTarget=e,this.onSeekStart&&this.onSeekStart(e),this.timerFeed&&(window.clearInterval(this.timerFeed),this.timerFeed=null);var i=this._getSeekTarget();return this.playParam.videoCodec===v.CODEC_H264?(this.player.seek(e),this.onSeekFinish&&this.onSeekFinish()):this.configFormat.extInfo.core===v.PLAYER_CORE_TYPE_CNATIVE?(this.pause(),this._seekHLS(e,this,(function(){t.player.seek((function(){}),{seekTime:i,mode:t.playMode,accurateSeek:t.configFormat.accurateSeek})}))):this._seekHLS(e,this,(function(){t.player.seek((function(){t.configFormat.type==v.PLAYER_IN_TYPE_MP4?t.mp4Obj.seek(e):t.configFormat.type==v.PLAYER_IN_TYPE_TS||t.configFormat.type==v.PLAYER_IN_TYPE_MPEGTS?t.mpegTsObj.seek(e):t.configFormat.type==v.PLAYER_IN_TYPE_M3U8&&(t.hlsObj.onSamples=null,t.hlsObj.seek(e));var i,n=(i=0,i=t.configFormat.accurateSeek?e:t._getBoxBufSeekIDR(),parseInt(i)),r=parseInt(t._getBoxBufSeekIDR())||0;t._avFeedMP4Data(r,n)}),{seekTime:i,mode:t.playMode,accurateSeek:t.configFormat.accurateSeek})})),!0}},{key:"fullScreen",value:function(){if(this.autoScreenClose=!0,this.player.vCodecID,this.player,this.player.vCodecID===v.V_CODEC_NAME_HEVC){var e=document.querySelector("#"+this.configFormat.playerId),t=e.getElementsByTagName("canvas")[0];e.style.width=this.screenW+"px",e.style.height=this.screenH+"px";var i=this._checkScreenDisplaySize(this.screenW,this.screenH,this.playParam.size.width,this.playParam.size.height);t.style.marginTop=i[0]+"px",t.style.marginLeft=i[1]+"px",t.style.width=i[2]+"px",t.style.height=i[3]+"px",this._requestFullScreen(e)}else this._requestFullScreen(this.player.videoTag)}},{key:"closeFullScreen",value:function(){var e=arguments.length>0&&void 0!==arguments[0]&&arguments[0];if(!1===e&&(this.autoScreenClose=!1,this._exitFull()),this.player.vCodecID===v.V_CODEC_NAME_HEVC){var t=document.querySelector("#"+this.configFormat.playerId),i=t.getElementsByTagName("canvas")[0];t.style.width=this.configFormat.playerW+"px",t.style.height=this.configFormat.playerH+"px";var n=this._checkScreenDisplaySize(this.configFormat.playerW,this.configFormat.playerH,this.playParam.size.width,this.playParam.size.height);i.style.marginTop=n[0]+"px",i.style.marginLeft=n[1]+"px",i.style.width=n[2]+"px",i.style.height=n[3]+"px"}}},{key:"playNextFrame",value:function(){return this.pause(),void 0!==this.playParam&&null!==this.playParam&&(0===this.playParam.videoCodec?this.player.playYUV():this.player.nativeNextFrame(),!0)}},{key:"resize",value:function(e,t){if(void 0!==this.player&&null!==this.player){if(!(e&&t&&this.playParam.size.width&&this.playParam.size.height))return!1;var i=this.playParam.size.width,n=this.playParam.size.height,r=0===this.playParam.videoCodec,a=document.querySelector("#"+this.configFormat.playerId);if(a.style.width=e+"px",a.style.height=t+"px",!0===r){var s=a.getElementsByTagName("canvas")[0],o=function(e,t){var r=i/e>n/t,a=(e/i).toFixed(2),s=(t/n).toFixed(2),o=r?a:s,u=parseInt(i*o,10),l=parseInt(n*o,10);return[parseInt((t-l)/2,10),parseInt((e-u)/2,10),u,l]}(e,t);s.style.marginTop=o[0]+"px",s.style.marginLeft=o[1]+"px",s.style.width=o[2]+"px",s.style.height=o[3]+"px"}else{var u=a.getElementsByTagName("video")[0];u.style.width=e+"px",u.style.height=t+"px"}return!0}return!1}},{key:"_checkScreenDisplaySize",value:function(e,t,i,n){var r=i/e>n/t,a=(e/i).toFixed(2),s=(t/n).toFixed(2),o=r?a:s,u=this.fixed?e:parseInt(i*o),l=this.fixed?t:parseInt(n*o);return[parseInt((t-l)/2),parseInt((e-u)/2),u,l]}},{key:"_isFullScreen",value:function(){var e=document.fullscreenElement||document.mozFullscreenElement||document.webkitFullscreenElement;return document.fullscreenEnabled||document.mozFullscreenEnabled||document.webkitFullscreenEnabled,null!=e}},{key:"_requestFullScreen",value:function(e){e.requestFullscreen?e.requestFullscreen():e.mozRequestFullScreen?e.mozRequestFullScreen():e.msRequestFullscreen?e.msRequestFullscreen():e.webkitRequestFullscreen&&e.webkitRequestFullScreen()}},{key:"_exitFull",value:function(){document.exitFullscreen?document.exitFullscreen():document.webkitExitFullscreen?document.webkitExitFullscreen():document.mozCancelFullScreen?document.mozCancelFullScreen():document.msExitFullscreen&&document.msExitFullscreen()}},{key:"_durationText",value:function(e){if(e<0)return"Play";var t=Math.round(e);return Math.floor(t/3600)+":"+Math.floor(t%3600/60)+":"+Math.floor(t%60)}},{key:"_getSeekTarget",value:function(){return this.configFormat.accurateSeek?this.seekTarget:this._getBoxBufSeekIDR()}},{key:"_getBoxBufSeekIDR",value:function(){return this.configFormat.type==v.PLAYER_IN_TYPE_MP4?this.mp4Obj.seekPos:this.configFormat.type==v.PLAYER_IN_TYPE_TS||this.configFormat.type==v.PLAYER_IN_TYPE_MPEGTS?this.mpegTsObj.seekPos:this.configFormat.type==v.PLAYER_IN_TYPE_M3U8?this.hlsObj.seekPos:void 0}},{key:"_playControl",value:function(){this.isPlaying()?this.pause():this.play()}},{key:"_avFeedMP4Data",value:function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0,i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;if(void 0===this.player||null===this.player)return!1;var r=parseInt(this.playParam.durationMs/1e3);this.player.clearAllCache(),this.timerFeed=window.setInterval((function(){var a=null,s=null,o=!0,u=!0;if(e.configFormat.type==v.PLAYER_IN_TYPE_MP4?(a=e.mp4Obj.popBuffer(1,t),s=e.mp4Obj.audioNone?null:e.mp4Obj.popBuffer(2,i)):e.configFormat.type==v.PLAYER_IN_TYPE_TS||e.configFormat.type==v.PLAYER_IN_TYPE_MPEGTS?(a=e.mpegTsObj.popBuffer(1,t),s=e.mpegTsObj.getAudioNone()?null:e.mpegTsObj.popBuffer(2,i)):e.configFormat.type==v.PLAYER_IN_TYPE_M3U8&&(a=e.hlsObj.popBuffer(1,t),s=e.hlsObj.audioNone?null:e.hlsObj.popBuffer(2,i),t=e.hlsObj.getLastIdx()&&(o=!1),i=e.hlsObj.getALastIdx()&&(u=!1)),!0===o&&null!=a)for(var l=0;lr)return window.clearInterval(e.timerFeed),e.timerFeed=null,e.player.vCachePTS,e.player.aCachePTS,void(null!=n&&n())}),5)}},{key:"_isSupportWASM",value:function(){window.document;var e=window.navigator,t=e.userAgent.toLowerCase(),i="ipad"==t.match(/ipad/i),r="iphone os"==t.match(/iphone os/i),a="iPad"==t.match(/iPad/i),s="iPhone os"==t.match(/iPhone os/i),o="midp"==t.match(/midp/i),u="rv:1.2.3.4"==t.match(/rv:1.2.3.4/i),l="ucweb"==t.match(/ucweb/i),h="android"==t.match(/android/i),d="Android"==t.match(/Android/i),c="windows ce"==t.match(/windows ce/i),f="windows mobile"==t.match(/windows mobile/i);if(i||r||a||s||o||u||l||h||d||c||f)return!1;var m=function(){try{if("object"===("undefined"==typeof WebAssembly?"undefined":n(WebAssembly))&&"function"==typeof WebAssembly.instantiate){var e=new WebAssembly.Module(Uint8Array.of(0,97,115,109,1,0,0,0));if(e instanceof WebAssembly.Module)return new WebAssembly.Instance(e)instanceof WebAssembly.Instance}}catch(e){}return!1}();if(!1===m)return!1;if(!0===m){var _=p.BrowserJudge(),g=_[0],v=_[1];if("Chrome"===g&&v<85)return!1;if(g.indexOf("360")>=0)return!1;if(/Safari/.test(e.userAgent)&&!/Chrome/.test(e.userAgent)&&v>13)return!1}return!0}},{key:"_makeMP4Player",value:function(){var e=arguments.length>0&&void 0!==arguments[0]&&arguments[0],t=this;if(this._isSupportWASM(),!1===this._isSupportWASM()||!0===e){if(this.configFormat.type==v.PLAYER_IN_TYPE_MP4)t.mediaExtFormat===v.PLAYER_IN_TYPE_FLV?this._flvJsPlayer(this.playParam.durationMs,t.playParam.audioNone):this._makeNativePlayer();else if(this.configFormat.type==v.PLAYER_IN_TYPE_TS||this.configFormat.type==v.PLAYER_IN_TYPE_MPEGTS)this._mpegTsNv3rdPlayer(-1,!1);else if(this.configFormat.type==v.PLAYER_IN_TYPE_M3U8)this._videoJsPlayer();else if(this.configFormat.type===v.PLAYER_IN_TYPE_RAW_265)return-1;return 1}return this.mediaExtProtocol===v.URI_PROTOCOL_WEBSOCKET_DESC?(this.configFormat.type,this.configFormat.type===v.PLAYER_IN_TYPE_RAW_265?this._raw265Entry():this._cWsFLVDecoderEntry(),0):(null!=this.configFormat.extInfo.core&&null!==this.configFormat.extInfo.core&&this.configFormat.extInfo.core===v.PLAYER_CORE_TYPE_CNATIVE?this._cDemuxDecoderEntry():this.configFormat.type==v.PLAYER_IN_TYPE_MP4?this.configFormat.extInfo.moovStartFlag?this._mp4EntryVodStream():this._mp4Entry():this.configFormat.type==v.PLAYER_IN_TYPE_TS||this.configFormat.type==v.PLAYER_IN_TYPE_MPEGTS?this._mpegTsEntry():this.configFormat.type==v.PLAYER_IN_TYPE_M3U8?this._m3u8Entry():this.configFormat.type===v.PLAYER_IN_TYPE_RAW_265&&this._raw265Entry(),0)}},{key:"_makeMP4PlayerViewEvent",value:function(e,t,i,n){var r=this,s=arguments.length>4&&void 0!==arguments[4]&&arguments[4],o=arguments.length>5&&void 0!==arguments[5]?arguments[5]:null,u=this;if(this.playParam.durationMs=e,this.playParam.fps=t,this.playParam.sampleRate=i,this.playParam.size=n,this.playParam.audioNone=s,this.playParam.videoCodec=o||v.CODEC_H265,this.playParam,(this.configFormat.type==v.PLAYER_IN_TYPE_M3U8&&this.hlsConf.hlsType==v.PLAYER_IN_TYPE_M3U8_LIVE||this.configFormat.type==v.PLAYER_IN_TYPE_RAW_265)&&(this.playMode=v.PLAYER_MODE_NOTIME_LIVE),u.configFormat.extInfo.autoCrop){var l=document.querySelector("#"+this.configFormat.playerId),h=n.width/n.height,d=this.configFormat.playerW/this.configFormat.playerH;h>d?l.style.height=this.configFormat.playerW/h+"px":h0&&void 0!==arguments[0]?arguments[0]:0,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:0,n=arguments.length>3?arguments[3]:void 0,r=arguments.length>4&&void 0!==arguments[4]?arguments[4]:0,a=arguments.length>5?arguments[5]:void 0,o=this;this.playParam.durationMs=e,this.playParam.fps=t,this.playParam.sampleRate=i,this.playParam.size=n,this.playParam.audioNone=r,this.playParam.videoCodec=a||v.CODEC_H264,this.configFormat.type==v.PLAYER_IN_TYPE_M3U8&&this.hlsConf.hlsType==v.PLAYER_IN_TYPE_M3U8_LIVE&&(this.playMode=v.PLAYER_MODE_NOTIME_LIVE),this.player=new s.Mp4Player({width:this.configFormat.playerW,height:this.configFormat.playerH,sampleRate:i,fps:t,appendHevcType:v.APPEND_TYPE_FRAME,fixed:!1,playerId:this.configFormat.playerId,audioNone:r,token:this.configFormat.token,videoCodec:a,autoPlay:this.configFormat.extInfo.autoPlay});var u=0,l=window.setInterval((function(){u++,void 0!==o.player&&null!==o.player||(window.clearInterval(l),l=null),u>v.DEFAULT_PLAYERE_LOAD_TIMEOUT&&(o.player.release(),o.player=null,o._cDemuxDecoderEntry(0,!0),window.clearInterval(l),l=null)}),1e3);this.player.makeIt(this.videoURL),this.player.onPlayingTime=function(t){o._durationText(t),o._durationText(e/1e3),null!=o.onPlayTime&&o.onPlayTime(t)},this.player.onPlayingFinish=function(){null!=o.onPlayFinish&&o.onPlayFinish()},this.player.onLoadFinish=function(){window.clearInterval(l),l=null,o.playParam.durationMs=1e3*o.player.duration,o.playParam.size=o.player.getSize(),o.onLoadFinish&&o.onLoadFinish(),o.onReadyShowDone&&o.onReadyShowDone()},this.player.onPlayState=function(e){o.onPlayState&&o.onPlayState(e)},this.player.onCacheProcess=function(e){o.onCacheProcess&&o.onCacheProcess(e)}}},{key:"_initMp4BoxObject",value:function(){var e=this;this.timerFeed=null,this.mp4Obj=new m,this.mp4Obj.onMp4BoxReady=function(t){var i=e.mp4Obj.getFPS(),n=T(i,e.mp4Obj.getDurationMs()),r=e.mp4Obj.getSampleRate(),a=e.mp4Obj.getSize(),s=e.mp4Obj.getVideoCoder();t===v.CODEC_H265?(e._makeMP4PlayerViewEvent(n,i,r,a,e.mp4Obj.audioNone,s),parseInt(n/1e3),e._avFeedMP4Data(0,0)):e._makeNativePlayer(n,i,r,a,e.mp4Obj.audioNone,s)}}},{key:"_mp4Entry",value:function(){var e=this,t=this;fetch(this.videoURL).then((function(e){return e.arrayBuffer()})).then((function(i){t._initMp4BoxObject(),e.mp4Obj.demux(),e.mp4Obj.appendBufferData(i,0),e.mp4Obj.finishBuffer(),e.mp4Obj.seek(-1)}))}},{key:"_mp4EntryVodStream",value:function(){var e=this,t=this;this.timerFeed=null,this.mp4Obj=new m,this._initMp4BoxObject(),this.mp4Obj.demux();var i=0,n=!1,r=window.setInterval((function(){n||(n=!0,fetch(e.videoURL).then((function(e){return function e(n){return n.read().then((function(a){if(a.done)return t.mp4Obj.finishBuffer(),t.mp4Obj.seek(-1),void window.clearInterval(r);var s=a.value;return t.mp4Obj.appendBufferData(s.buffer,i),i+=s.byteLength,e(n)}))}(e.body.getReader())})).catch((function(e){})))}),1)}},{key:"_cDemuxDecoderEntry",value:function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0,i=arguments.length>1&&void 0!==arguments[1]&&arguments[1];this.configFormat.type;var n=this,r=!1,a=new AbortController,s=a.signal,u={width:this.configFormat.playerW,height:this.configFormat.playerH,playerId:this.configFormat.playerId,token:this.configFormat.token,readyShow:this.configFormat.extInfo.readyShow,checkProbe:this.configFormat.extInfo.checkProbe,ignoreAudio:this.configFormat.extInfo.ignoreAudio,playMode:this.playMode,autoPlay:this.configFormat.extInfo.autoPlay,defaultFps:this.configFormat.extInfo.rawFps,cacheLength:this.configFormat.extInfo.cacheLength};this.player=new o.CNativeCore(u),window.g_players[this.player.corePtr]=this.player,this.player.onReadyShowDone=function(){n.configFormat.extInfo.readyShow=!1,n.onReadyShowDone&&n.onReadyShowDone()},this.player.onRelease=function(){a.abort()},this.player.onProbeFinish=function(){r=!0,n.player.config,n.player.audioNone,n.playParam.fps=n.player.config.fps,n.playParam.durationMs=T(n.playParam.fps,1e3*n.player.duration),n.player.duration<0&&(n.playMode=v.PLAYER_MODE_NOTIME_LIVE,n.playParam.durationMs=-1),n.playParam.sampleRate=n.player.config.sampleRate,n.playParam.size={width:n.player.width,height:n.player.height},n.playParam.audioNone=n.player.audioNone,n.player.vCodecID===v.V_CODEC_NAME_HEVC?(n.playParam.videoCodec=v.CODEC_H265,n.playParam.audioIdx<0&&(n.playParam.audioNone=!0),!0!==p.IsSupport265Mse()||!1!==i||n.mediaExtFormat!==v.PLAYER_IN_TYPE_MP4&&n.mediaExtFormat!==v.PLAYER_IN_TYPE_FLV?n.onLoadFinish&&n.onLoadFinish():(a.abort(),n.player.release(),n.mediaExtFormat,v.PLAYER_IN_TYPE_MP4,n.player=null,n.mediaExtFormat===v.PLAYER_IN_TYPE_MP4?n._makeNativePlayer(n.playParam.durationMs,n.playParam.fps,n.playParam.sampleRate,n.playParam.size,!1,n.playParam.videoCodec):n.mediaExtFormat===v.PLAYER_IN_TYPE_FLV&&n._flvJsPlayer(n.playParam.durationMs,n.playParam.audioNone))):(n.playParam.videoCodec=v.CODEC_H264,a.abort(),n.player.release(),n.player=null,n.mediaExtFormat===v.PLAYER_IN_TYPE_MP4?n._makeNativePlayer(n.playParam.durationMs,n.playParam.fps,n.playParam.sampleRate,n.playParam.size,!1,n.playParam.videoCodec):n.mediaExtFormat===v.PLAYER_IN_TYPE_FLV?n._flvJsPlayer(n.playParam.durationMs,n.playParam.audioNone):n.onLoadFinish&&n.onLoadFinish())},this.player.onPlayingTime=function(e){n._durationText(e),n._durationText(n.player.duration),null!=n.onPlayTime&&n.onPlayTime(e)},this.player.onPlayingFinish=function(){n.pause(),null!=n.onPlayTime&&n.onPlayTime(0),n.onPlayFinish&&n.onPlayFinish(),n.player.reFull=!0,n.seek(0)},this.player.onCacheProcess=function(t){e.onCacheProcess&&e.onCacheProcess(t)},this.player.onLoadCache=function(){null!=e.onLoadCache&&e.onLoadCache()},this.player.onLoadCacheFinshed=function(){null!=e.onLoadCacheFinshed&&e.onLoadCacheFinshed()},this.player.onRender=function(e,t,i,r,a){n.snapshotYuvLastFrame.luma=null,n.snapshotYuvLastFrame.chromaB=null,n.snapshotYuvLastFrame.chromaR=null,n.snapshotYuvLastFrame.width=e,n.snapshotYuvLastFrame.height=t,n.snapshotYuvLastFrame.luma=new Uint8Array(i),n.snapshotYuvLastFrame.chromaB=new Uint8Array(r),n.snapshotYuvLastFrame.chromaR=new Uint8Array(a),null!=n.onRender&&n.onRender(e,t,i,r,a)},this.player.onSeekFinish=function(){null!=e.onSeekFinish&&e.onSeekFinish()};var l=!1,h=0,d=function e(i){setTimeout((function(){if(!1===l){if(a.abort(),a=null,s=null,i>=v.FETCH_FIRST_MAX_TIMES)return;a=new AbortController,s=a.signal,e(i+1)}}),v.FETCH_HTTP_FLV_TIMEOUT_MS),fetch(n.videoURL,{signal:s}).then((function(e){if(e.headers.get("Content-Length"),!e.ok)return console.error("error cdemuxdecoder prepare request media failed with http code:",e.status),!1;if(l=!0,e.headers.has("Content-Length"))h=e.headers.get("Content-Length"),n.configFormat.extInfo.coreProbePart<=0?n.player&&n.player.setProbeSize(n.configFormat.extInfo.probeSize):n.player&&n.player.setProbeSize(h*n.configFormat.extInfo.coreProbePart);else{if(n.mediaExtFormat===v.PLAYER_IN_TYPE_FLV)return a.abort(),n.player.release(),n.player=null,n._cLiveFLVDecoderEntry(u),!0;n.player&&n.player.setProbeSize(40960)}return e.headers.get("Content-Length"),n.configFormat.type,n.mediaExtFormat,function e(i){return i.read().then((function(a){if(a.done)return!0===r||(n.player.release(),n.player=null,t0&&void 0!==arguments[0]?arguments[0]:0;if(1===t)return i.player.release(),i.player=null,void i._cLiveG711DecoderEntry(e);if(i.playParam.fps=i.player.mediaInfo.fps,i.playParam.durationMs=-1,i.playMode=v.PLAYER_MODE_NOTIME_LIVE,i.playParam.sampleRate=i.player.mediaInfo.sampleRate,i.playParam.size={width:i.player.mediaInfo.width,height:i.player.mediaInfo.height},i.playParam.audioNone=i.player.mediaInfo.audioNone,i.player.mediaInfo,i.player.vCodecID===v.V_CODEC_NAME_HEVC)i.playParam.videoCodec=v.CODEC_H265,i.playParam.audioIdx<0&&(i.playParam.audioNone=!0),!0===p.IsSupport265Mse()&&i.mediaExtFormat===v.PLAYER_IN_TYPE_FLV?(i.player.release(),i.player=null,i.mediaExtFormat===v.PLAYER_IN_TYPE_FLV&&i._flvJsPlayer(i.playParam.durationMs,i.playParam.audioNone)):i.onLoadFinish&&i.onLoadFinish();else if(i.playParam.videoCodec=v.CODEC_H264,i.player.release(),i.player=null,i.mediaExtFormat===v.PLAYER_IN_TYPE_FLV)i._flvJsPlayer(i.playParam.durationMs,i.playParam.audioNone);else{if(i.mediaExtFormat!==v.PLAYER_IN_TYPE_TS&&i.mediaExtFormat!==v.PLAYER_IN_TYPE_MPEGTS)return-1;i._mpegTsNv3rdPlayer(i.playParam.durationMs,i.playParam.audioNone)}},this.player.onError=function(e){i.onError&&i.onError(e)},this.player.onReadyShowDone=function(){i.configFormat.extInfo.readyShow=!1,i.onReadyShowDone&&i.onReadyShowDone()},this.player.onLoadCache=function(){null!=t.onLoadCache&&t.onLoadCache()},this.player.onLoadCacheFinshed=function(){null!=t.onLoadCacheFinshed&&t.onLoadCacheFinshed()},this.player.onRender=function(e,t,n,r,a){i.snapshotYuvLastFrame.luma=null,i.snapshotYuvLastFrame.chromaB=null,i.snapshotYuvLastFrame.chromaR=null,i.snapshotYuvLastFrame.width=e,i.snapshotYuvLastFrame.height=t,i.snapshotYuvLastFrame.luma=new Uint8Array(n),i.snapshotYuvLastFrame.chromaB=new Uint8Array(r),i.snapshotYuvLastFrame.chromaR=new Uint8Array(a),null!=i.onRender&&i.onRender(e,t,n,r,a)},this.player.onPlayState=function(e){i.onPlayState&&i.onPlayState(e)},this.player.start(this.videoURL)}},{key:"_cWsFLVDecoderEntry",value:function(){var e=this,t=this,i={width:this.configFormat.playerW,height:this.configFormat.playerH,playerId:this.configFormat.playerId,token:this.configFormat.token,readyShow:this.configFormat.extInfo.readyShow,checkProbe:this.configFormat.extInfo.checkProbe,ignoreAudio:this.configFormat.extInfo.ignoreAudio,playMode:this.playMode,autoPlay:this.configFormat.extInfo.autoPlay};i.probeSize=this.configFormat.extInfo.probeSize,this.player=new h.CWsLiveCore(i),i.probeSize,window.g_players[this.player.corePtr]=this.player,this.player.onProbeFinish=function(){t.playParam.fps=t.player.mediaInfo.fps,t.playParam.durationMs=-1,t.playMode=v.PLAYER_MODE_NOTIME_LIVE,t.playParam.sampleRate=t.player.mediaInfo.sampleRate,t.playParam.size={width:t.player.mediaInfo.width,height:t.player.mediaInfo.height},t.playParam.audioNone=t.player.mediaInfo.audioNone,t.player.mediaInfo,t.player.vCodecID===v.V_CODEC_NAME_HEVC?(t.playParam.audioIdx<0&&(t.playParam.audioNone=!0),t.playParam.videoCodec=v.CODEC_H265,!0===p.IsSupport265Mse()&&t.mediaExtFormat===v.PLAYER_IN_TYPE_FLV?(t.player.release(),t.player=null,t._flvJsPlayer(t.playParam.durationMs,t.playParam.audioNone)):t.onLoadFinish&&t.onLoadFinish()):(t.playParam.videoCodec=v.CODEC_H264,t.player.release(),t.player=null,t._flvJsPlayer(t.playParam.durationMs,t.playParam.audioNone))},this.player.onError=function(e){t.onError&&t.onError(e)},this.player.onReadyShowDone=function(){t.configFormat.extInfo.readyShow=!1,t.onReadyShowDone&&t.onReadyShowDone()},this.player.onLoadCache=function(){null!=e.onLoadCache&&e.onLoadCache()},this.player.onLoadCacheFinshed=function(){null!=e.onLoadCacheFinshed&&e.onLoadCacheFinshed()},this.player.onRender=function(e,i,n,r,a){t.snapshotYuvLastFrame.luma=null,t.snapshotYuvLastFrame.chromaB=null,t.snapshotYuvLastFrame.chromaR=null,t.snapshotYuvLastFrame.width=e,t.snapshotYuvLastFrame.height=i,t.snapshotYuvLastFrame.luma=new Uint8Array(n),t.snapshotYuvLastFrame.chromaB=new Uint8Array(r),t.snapshotYuvLastFrame.chromaR=new Uint8Array(a),null!=t.onRender&&t.onRender(e,i,n,r,a)},this.player.start(this.videoURL)}},{key:"_mpegTsEntry",value:function(){var e=this,t=(Module.cwrap("AVPlayerInit","number",["string","string"])(this.configFormat.token,"0.0.0"),new AbortController),i=t.signal;this.timerFeed=null,this.mpegTsObj=new _.MpegTs,this.mpegTsObj.bindReady(e),this.mpegTsObj.onDemuxed=this._mpegTsEntryReady.bind(this),this.mpegTsObj.onReady=function(){var n=null;fetch(e.videoURL,{signal:i}).then((function(r){if(r.headers.has("Content-Length"))return function t(i){return i.read().then((function(r){if(!r.done){var a=r.value;if(null===n)n=a;else{var s=a,o=n.length+s.length,u=new Uint8Array(o);u.set(n),u.set(s,n.length),n=new Uint8Array(u),s=null,u=null}return t(i)}e.mpegTsObj.demux(n)}))}(r.body.getReader());t.abort(),i=null,t=null;var a={width:e.configFormat.playerW,height:e.configFormat.playerH,playerId:e.configFormat.playerId,token:e.configFormat.token,readyShow:e.configFormat.extInfo.readyShow,checkProbe:e.configFormat.extInfo.checkProbe,ignoreAudio:e.configFormat.extInfo.ignoreAudio,playMode:e.playMode,autoPlay:e.configFormat.extInfo.autoPlay};e._cLiveFLVDecoderEntry(a)})).catch((function(e){if(!e.toString().includes("user aborted")){var t=" mpegts request error:"+e;console.error(t)}}))},this.mpegTsObj.initMPEG()}},{key:"_mpegTsEntryReady",value:function(e){var t=e,i=(t.mpegTsObj.getVCodec(),t.mpegTsObj.getACodec()),n=t.mpegTsObj.getDurationMs(),r=t.mpegTsObj.getFPS(),a=t.mpegTsObj.getSampleRate(),s=t.mpegTsObj.getSize(),o=this.mpegTsObj.isHEVC();if(!o)return this.mpegTsObj.releaseTsDemuxer(),this.mpegTsObj=null,this.playParam.durationMs=n,this.playParam.fps=r,this.playParam.sampleRate=a,this.playParam.size=s,this.playParam.audioNone=""==i,this.playParam.videoCodec=o?0:1,this.playParam,void this._mpegTsNv3rdPlayer(this.playParam.durationMs,this.playParam.audioNone);t._makeMP4PlayerViewEvent(n,r,a,s,""==i),parseInt(n/1e3),t._avFeedMP4Data(0,0)}},{key:"_m3u8Entry",value:function(){var e=this,t=this;if(!1===this._isSupportWASM())return this._videoJsPlayer();Module.cwrap("AVPlayerInit","number",["string","string"])(this.configFormat.token,"0.0.0");var i=!1,n=0;this.hlsObj=new g.M3u8,this.hlsObj.bindReady(t),this.hlsObj.onFinished=function(e,r){0==i&&(n=t.hlsObj.getDurationMs(),t.hlsConf.hlsType=r.type,i=!0)},this.hlsObj.onCacheProcess=function(t){e.playMode!==v.PLAYER_MODE_NOTIME_LIVE&&e.onCacheProcess&&e.onCacheProcess(t)},this.hlsObj.onDemuxed=function(e){if(null==t.player){var i=t.hlsObj.isHevcParam,r=(t.hlsObj.getVCodec(),t.hlsObj.getACodec()),a=t.hlsObj.getFPS(),s=t.hlsObj.getSampleRate(),o=t.hlsObj.getSize(),u=!1;if(u=t.hlsObj.getSampleChannel()<=0||""===r,!i)return t.hlsObj.release(),t.hlsObj.mpegTsObj&&t.hlsObj.mpegTsObj.releaseTsDemuxer(),t.hlsObj=null,t.playParam.durationMs=n,t.playParam.fps=a,t.playParam.sampleRate=s,t.playParam.size=o,t.playParam.audioNone=""==r,t.playParam.videoCodec=i?0:1,t.playParam,void t._videoJsPlayer(n);t._makeMP4PlayerViewEvent(n,a,s,o,u)}},this.hlsObj.onSamples=this._hlsOnSamples.bind(this),this.hlsObj.demux(this.videoURL)}},{key:"_hlsOnSamples",value:function(e,t){1==t.video?this.player.appendHevcFrame(t):!1===this.hlsObj.audioNone&&this.player.appendAACFrame(t)}},{key:"_videoJsPlayer",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:-1,t=this,i={probeDurationMS:e,width:this.configFormat.playerW,height:this.configFormat.playerH,playerId:this.configFormat.playerId,ignoreAudio:this.configFormat.extInfo.ignoreAudio,autoPlay:this.configFormat.extInfo.autoPlay,playMode:this.playMode};this.player=new d.NvVideojsCore(i),this.player.onMakeItReady=function(){t.onMakeItReady&&t.onMakeItReady()},this.player.onLoadFinish=function(){t.playParam.size=t.player.getSize(),t.playParam.videoCodec=1,t.player.duration===1/0||t.player.duration<0?(t.playParam.durationMs=-1,t.playMode=v.PLAYER_MODE_NOTIME_LIVE):(t.playParam.durationMs=1e3*t.player.duration,t.playMode=v.PLAYER_MODE_VOD),t.playParam,t.player.duration,t.player.getSize(),t.onLoadFinish&&t.onLoadFinish()},this.player.onReadyShowDone=function(){t.onReadyShowDone&&t.onReadyShowDone()},this.player.onPlayingFinish=function(){t.pause(),t.seek(0),null!=t.onPlayFinish&&t.onPlayFinish()},this.player.onPlayingTime=function(e){t._durationText(e),t._durationText(t.player.duration),null!=t.onPlayTime&&t.onPlayTime(e)},this.player.onSeekFinish=function(){t.onSeekFinish&&t.onSeekFinish()},this.player.onPlayState=function(e){t.onPlayState&&t.onPlayState(e)},this.player.onCacheProcess=function(e){t.onCacheProcess&&t.onCacheProcess(e)},this.player.makeIt(this.videoURL)}},{key:"_flvJsPlayer",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:-1,t=arguments.length>1&&void 0!==arguments[1]&&arguments[1],i=this,n={width:this.configFormat.playerW,height:this.configFormat.playerH,playerId:this.configFormat.playerId,ignoreAudio:this.configFormat.extInfo.ignoreAudio,duration:e,autoPlay:this.configFormat.extInfo.autoPlay,audioNone:t};this.player=new c.NvFlvjsCore(n),this.player.onLoadFinish=function(){i.playParam.size=i.player.getSize(),!i.player.duration||NaN===i.player.duration||i.player.duration===1/0||i.player.duration<0?(i.playParam.durationMs=-1,i.playMode=v.PLAYER_MODE_NOTIME_LIVE):(i.playParam.durationMs=1e3*i.player.duration,i.playMode=v.PLAYER_MODE_VOD),i.onLoadFinish&&i.onLoadFinish()},this.player.onReadyShowDone=function(){i.onReadyShowDone&&i.onReadyShowDone()},this.player.onPlayingTime=function(e){i._durationText(e),i._durationText(i.player.duration),null!=i.onPlayTime&&i.onPlayTime(e)},this.player.onPlayingFinish=function(){i.pause(),i.seek(0),null!=i.onPlayFinish&&i.onPlayFinish()},this.player.onPlayState=function(e){i.onPlayState&&i.onPlayState(e)},this.player.onCacheProcess=function(e){i.onCacheProcess&&i.onCacheProcess(e)},this.player.makeIt(this.videoURL)}},{key:"_mpegTsNv3rdPlayer",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:-1,t=arguments.length>1&&void 0!==arguments[1]&&arguments[1],i=this,n={width:this.configFormat.playerW,height:this.configFormat.playerH,playerId:this.configFormat.playerId,ignoreAudio:this.configFormat.extInfo.ignoreAudio,duration:e,autoPlay:this.configFormat.extInfo.autoPlay,audioNone:t};this.player=new f.NvMpegTsCore(n),this.player.onLoadFinish=function(){i.playParam.size=i.player.getSize(),!i.player.duration||NaN===i.player.duration||i.player.duration===1/0||i.player.duration<0?(i.playParam.durationMs=-1,i.playMode=v.PLAYER_MODE_NOTIME_LIVE):(i.playParam.durationMs=1e3*i.player.duration,i.playMode=v.PLAYER_MODE_VOD),i.onLoadFinish&&i.onLoadFinish()},this.player.onReadyShowDone=function(){i.onReadyShowDone&&i.onReadyShowDone()},this.player.onPlayingTime=function(e){i._durationText(e),i._durationText(i.player.duration),null!=i.onPlayTime&&i.onPlayTime(e)},this.player.onPlayingFinish=function(){i.pause(),i.seek(0),null!=i.onPlayFinish&&i.onPlayFinish()},this.player.onPlayState=function(e){i.onPlayState&&i.onPlayState(e)},this.player.onCacheProcess=function(e){i.onCacheProcess&&i.onCacheProcess(e)},this.player.makeIt(this.videoURL)}},{key:"_raw265Entry",value:function(){var e=this;this.videoURL;var t=function t(){setTimeout((function(){e.workerParse.postMessage({cmd:"get-nalu",data:null,msg:"get-nalu"}),e.workerParse.parseEmpty,e.workerFetch.onMsgFetchFinished,!0===e.workerFetch.onMsgFetchFinished&&!0===e.workerParse.frameListEmpty&&!1===e.workerParse.streamEmpty&&e.workerParse.postMessage({cmd:"last-nalu",data:null,msg:"last-nalu"}),!0===e.workerParse.parseEmpty&&(e.workerParse.stopNaluInterval=!0),!0!==e.workerParse.stopNaluInterval&&t()}),1e3)};this._makeMP4PlayerViewEvent(-1,this.configFormat.extInfo.rawFps,-1,{width:this.configFormat.playerW,height:this.configFormat.playerH},!0,v.CODEC_H265),this.timerFeed&&(window.clearInterval(this.timerFeed),this.timerFeed=null),e.workerFetch=new Worker(p.GetScriptPath((function(){var e=new AbortController,t=e.signal,i=null;onmessage=function(n){var r=n.data;switch(void 0===r.cmd||null===r.cmd?"":r.cmd){case"start":var a=r.url;"http"===r.type?fetch(a,{signal:t}).then((function(e){return function e(t){return t.read().then((function(i){if(!i.done){var n=i.value;return postMessage({cmd:"fetch-chunk",data:n,msg:"fetch-chunk"}),e(t)}postMessage({cmd:"fetch-fin",data:null,msg:"fetch-fin"})}))}(e.body.getReader())})).catch((function(e){})):"websocket"===r.type&&function(e){(i=new WebSocket(e)).binaryType="arraybuffer",i.onopen=function(e){i.send("Hello WebSockets!")},i.onmessage=function(e){if(e.data instanceof ArrayBuffer){var t=e.data;t.byteLength>0&&postMessage({cmd:"fetch-chunk",data:new Uint8Array(t),msg:"fetch-chunk"})}},i.onclose=function(e){postMessage({cmd:"fetch-fin",data:null,msg:"fetch-fin"})}}(a),postMessage({cmd:"default",data:"WORKER STARTED",msg:"default"});break;case"stop":"http"===r.type?e.abort():"websocket"===r.type&&i&&i.close(),close()}}}))),e.workerFetch.onMsgFetchFinished=!1,e.workerFetch.onmessage=function(i){var n=i.data;switch(void 0===n.cmd||null===n.cmd?"":n.cmd){case"fetch-chunk":var r=n.data;e.workerParse.postMessage({cmd:"append-chunk",data:r,msg:"append-chunk"});break;case"fetch-fin":e.workerFetch.onMsgFetchFinished=!0,t()}},e.workerParse=new Worker(p.GetScriptPath((function(){var e,t=((e=new Object).frameList=[],e.stream=null,e.frameListEmpty=function(){return e.frameList.length<=0},e.streamEmpty=function(){return null===e.stream||e.stream.length<=0},e.checkEmpty=function(){return!0===e.streamEmpty()&&!0===e.frameListEmpty()||(e.stream,e.frameList,!1)},e.pushFrameRet=function(t){return!(!t||null==t||null==t||(e.frameList&&null!=e.frameList&&null!=e.frameList||(e.frameList=[]),e.frameList.push(t),0))},e.nextFrame=function(){return!e.frameList&&null==e.frameList||null==e.frameList&&e.frameList.length<1?null:e.frameList.shift()},e.clearFrameRet=function(){e.frameList=null},e.setStreamRet=function(t){e.stream=t},e.getStreamRet=function(){return e.stream},e.appendStreamRet=function(t){if(!t||void 0===t||null==t)return!1;if(!e.stream||void 0===e.stream||null==e.stream)return e.stream=t,!0;var i=e.stream.length,n=t.length,r=new Uint8Array(i+n);r.set(e.stream,0),r.set(t,i),e.stream=r;for(var a=0;a<9999;a++){var s=e.nextNalu();if(!1===s||null==s)break;e.frameList.push(s)}return!0},e.subBuf=function(t,i){var n=new Uint8Array(e.stream.subarray(t,i+1));return e.stream=new Uint8Array(e.stream.subarray(i+1)),n},e.lastNalu=function(){var t=e.subBuf(0,e.stream.length);e.frameList.push(t)},e.nextNalu=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:1;if(null==e.stream||e.stream.length<=4)return!1;for(var i=-1,n=0;n=e.stream.length)return!1;if(0==e.stream[n]&&0==e.stream[n+1]&&1==e.stream[n+2]||0==e.stream[n]&&0==e.stream[n+1]&&0==e.stream[n+2]&&1==e.stream[n+3]){var r=n;if(n+=3,-1==i)i=r;else{if(t<=1)return e.subBuf(i,r-1);t-=1}}}return!1},e.nextNalu2=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:1;if(null==e.stream||e.stream.length<=4)return!1;for(var i=-1,n=0;n=e.stream.length)return-1!=i&&e.subBuf(i,e.stream.length-1);var r="0 0 1"==e.stream.slice(n,n+3).join(" "),a="0 0 0 1"==e.stream.slice(n,n+4).join(" ");if(r||a){var s=n;if(n+=3,-1==i)i=s;else{if(t<=1)return e.subBuf(i,s-1);t-=1}}}return!1},e);onmessage=function(e){var i=e.data;switch(void 0===i.cmd||null===i.cmd?"":i.cmd){case"append-chunk":var n=i.data;t.appendStreamRet(n);var r=t.nextFrame();postMessage({cmd:"return-nalu",data:r,msg:"return-nalu",parseEmpty:t.checkEmpty(),streamEmpty:t.streamEmpty(),frameListEmpty:t.frameListEmpty()});break;case"get-nalu":var a=t.nextFrame();postMessage({cmd:"return-nalu",data:a,msg:"return-nalu",parseEmpty:t.checkEmpty(),streamEmpty:t.streamEmpty(),frameListEmpty:t.frameListEmpty()});break;case"last-nalu":var s=t.lastNalu();postMessage({cmd:"return-nalu",data:s,msg:"return-nalu",parseEmpty:t.checkEmpty(),streamEmpty:t.streamEmpty(),frameListEmpty:t.frameListEmpty()});break;case"stop":postMessage("parse - WORKER STOPPED: "+i),close()}}}))),e.workerParse.stopNaluInterval=!1,e.workerParse.parseEmpty=!1,e.workerParse.streamEmpty=!1,e.workerParse.frameListEmpty=!1,e.workerParse.onmessage=function(t){var i=t.data;switch(void 0===i.cmd||null===i.cmd?"":i.cmd){case"return-nalu":var n=i.data,r=i.parseEmpty,a=i.streamEmpty,s=i.frameListEmpty;e.workerParse.parseEmpty=r,e.workerParse.streamEmpty=a,e.workerParse.frameListEmpty=s,!1===n||null==n?!0===e.workerFetch.onMsgFetchFinished&&!0===r&&(e.workerParse.stopNaluInterval=!0):(e.append265NaluFrame(n),e.workerParse.postMessage({cmd:"get-nalu",data:null,msg:"get-nalu"}))}},p.ParseGetMediaURL(this.videoURL),this.workerFetch.postMessage({cmd:"start",url:p.ParseGetMediaURL(this.videoURL),type:this.mediaExtProtocol,msg:"start"}),function t(){setTimeout((function(){e.configFormat.extInfo.readyShow&&(e.player.cacheYuvBuf.getState()!=CACHE_APPEND_STATUS_CODE.NULL?(e.player.playFrameYUV(!0,!0),e.configFormat.extInfo.readyShow=!1,e.onReadyShowDone&&e.onReadyShowDone()):t())}),1e3)}()}},{key:"append265NaluFrame",value:function(e){var t={data:e,pts:this.rawModePts};this.player.appendHevcFrame(t),this.configFormat.extInfo.readyShow&&this.player.cacheYuvBuf.getState()!=CACHE_APPEND_STATUS_CODE.NULL&&(this.player.playFrameYUV(!0,!0),this.configFormat.extInfo.readyShow=!1,this.onReadyShowDone&&this.onReadyShowDone()),this.rawModePts+=1/this.configFormat.extInfo.rawFps}}])&&r(i.prototype,E),w&&r(i,w),e}();i.H265webjs=E,t.new265webjs=function(e,t){return new E(e,t)}}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./consts":52,"./decoder/av-common":56,"./decoder/c-http-g711-core":57,"./decoder/c-httplive-core":58,"./decoder/c-native-core":59,"./decoder/c-wslive-core":60,"./decoder/cache":61,"./decoder/player-core":65,"./demuxer/m3u8":69,"./demuxer/mp4":71,"./demuxer/mpegts/mpeg.js":74,"./demuxer/ts":75,"./native/mp4-player":77,"./native/nv-flvjs-core":78,"./native/nv-mpegts-core":79,"./native/nv-videojs-core":80,"./render-engine/webgl-420p":81,"./utils/static-mem":82,"./utils/ui/ui":83}],77:[function(e,t,i){"use strict";function n(e,t){for(var i=0;i=t.duration-.04)return t.onCacheProcess&&t.onCacheProcess(t.duration),void window.clearInterval(t.bufferInterval);t.onCacheProcess&&t.onCacheProcess(e)}),200)},this.videoTag.src=e,this.videoTag.style.width="100%",this.videoTag.style.height="100%",i.appendChild(this.videoTag)}},{key:"setPlaybackRate",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:1;return!(e<=0||null==this.videoTag||null===this.videoTag||(this.videoTag.playbackRate=e,0))}},{key:"getPlaybackRate",value:function(){return null==this.videoTag||null===this.videoTag?0:this.videoTag.playbackRate}},{key:"getSize",value:function(){return{width:this.videoTag.videoWidth>0?this.videoTag.videoWidth:this.configFormat.width,height:this.videoTag.videoHeight>0?this.videoTag.videoHeight:this.configFormat.height}}},{key:"play",value:function(){this.videoTag.play()}},{key:"seek",value:function(e){this.videoTag.currentTime=e}},{key:"pause",value:function(){this.videoTag.pause()}},{key:"setVoice",value:function(e){this.videoTag.volume=e}},{key:"isPlayingState",value:function(){return!this.videoTag.paused}},{key:"release",value:function(){this.videoTag&&this.videoTag.remove(),this.videoTag=null,this.onLoadFinish=null,this.onPlayingTime=null,this.onPlayingFinish=null,this.onPlayState=null,null!==this.bufferInterval&&(window.clearInterval(this.bufferInterval),this.bufferInterval=null),window.onclick=document.body.onclick=null}},{key:"nativeNextFrame",value:function(){void 0!==this.videoTag&&null!==this.videoTag&&(this.videoTag.currentTime+=1/this.configFormat.fps)}}])&&n(t.prototype,i),a&&n(t,a),e}();i.Mp4Player=a},{"../consts":52}],78:[function(e,t,i){"use strict";function n(e,t){for(var i=0;i0&&s.GetMsTime()-t.lastDecodedFrameTime>1e4)return window.clearInterval(t.checkPicBlockInterval),t.checkPicBlockInterval=null,void t._reBuildFlvjs(e)}),1e3)}},{key:"_checkLoadState",value:function(e){var t=this;this.checkStartIntervalCount=0,this.checkStartInterval=window.setInterval((function(){return t.lastDecodedFrame,t.isInitDecodeFrames,t.checkStartIntervalCount,!1!==t.isInitDecodeFrames?(t.checkStartIntervalCount=0,window.clearInterval(t.checkStartInterval),void(t.checkStartInterval=null)):(t.checkStartIntervalCount+=1,t.checkStartIntervalCount>20?(window.clearInterval(t.checkStartInterval),t.checkStartIntervalCount=0,t.checkStartInterval=null,void(!1===t.isInitDecodeFrames&&t._reBuildFlvjs(e))):void 0)}),500)}},{key:"makeIt",value:function(e){var t=this;if(a.isSupported()){var i=document.querySelector("#"+this.configFormat.playerId);this.videoTag=document.createElement("video"),this.videoTag.id=this.myPlayerID,this.videoTag.style.width=this.configFormat.width+"px",this.videoTag.style.height=this.configFormat.height+"px",i.appendChild(this.videoTag),!0===this.configFormat.autoPlay&&(this.videoTag.muted="muted",this.videoTag.autoplay="autoplay",window.onclick=document.body.onclick=function(e){t.videoTag.muted=!1,t.isPlayingState(),window.onclick=document.body.onclick=null}),this.videoTag.onplay=function(){var e=t.isPlayingState();t.onPlayState&&t.onPlayState(e)},this.videoTag.onpause=function(){var e=t.isPlayingState();t.onPlayState&&t.onPlayState(e)};var n={hasVideo:!0,hasAudio:!(!0===this.configFormat.audioNone),type:"flv",url:e,isLive:this.configFormat.duration<=0,withCredentials:!1};this.myPlayer=a.createPlayer(n),this.myPlayer.attachMediaElement(this.videoTag),this.myPlayer.on(a.Events.MEDIA_INFO,(function(e){t.videoTag.videoWidth,!1===t.isInitDecodeFrames&&(t.isInitDecodeFrames=!0,t.width=Math.max(t.videoTag.videoWidth,e.width),t.height=Math.max(t.videoTag.videoHeight,e.height),t.duration=t.videoTag.duration,t.duration,t.onLoadFinish&&t.onLoadFinish(),t.onReadyShowDone&&t.onReadyShowDone(),t._loopBufferState(),t.isPlayingState(),t.videoTag.ontimeupdate=function(){t.onPlayingTime&&t.onPlayingTime(t.videoTag.currentTime)},t.duration!==1/0&&t.duration>0&&(t.videoTag.onended=function(){t.onPlayingFinish&&t.onPlayingFinish()}))})),this.myPlayer.on(a.Events.STATISTICS_INFO,(function(e){t.videoTag.videoWidth,t.videoTag.videoHeight,t.videoTag.duration,!1===t.isInitDecodeFrames&&t.videoTag.videoWidth>0&&t.videoTag.videoHeight>0&&(t.isInitDecodeFrames=!0,t.width=t.videoTag.videoWidth,t.height=t.videoTag.videoHeight,t.duration=t.videoTag.duration,t.duration,t.onLoadFinish&&t.onLoadFinish(),t.onReadyShowDone&&t.onReadyShowDone(),t._loopBufferState(),t.isPlayingState(),t.videoTag.ontimeupdate=function(){t.onPlayingTime&&t.onPlayingTime(t.videoTag.currentTime)},t.duration!==1/0&&(t.videoTag.onended=function(){t.onPlayingFinish&&t.onPlayingFinish()})),t.lastDecodedFrame=e.decodedFrames,t.lastDecodedFrameTime=s.GetMsTime()})),this.myPlayer.on(a.Events.SCRIPTDATA_ARRIVED,(function(e){})),this.myPlayer.on(a.Events.METADATA_ARRIVED,(function(e){!1===t.isInitDecodeFrames&&e.width&&e.width>0&&(t.isInitDecodeFrames=!0,t.duration=e.duration,t.width=e.width,t.height=e.height,t.duration,t.onLoadFinish&&t.onLoadFinish(),t.onReadyShowDone&&t.onReadyShowDone(),t._loopBufferState(),t.isPlayingState(),t.videoTag.ontimeupdate=function(){t.onPlayingTime&&t.onPlayingTime(t.videoTag.currentTime)},t.duration!==1/0&&(t.videoTag.onended=function(){t.onPlayingFinish&&t.onPlayingFinish()}))})),this.myPlayer.on(a.Events.ERROR,(function(i,n,r){t.myPlayer&&t._reBuildFlvjs(e)})),this.myPlayer.load(),this._checkLoadState(e),this._checkPicBlock(e)}else console.error("FLV is AVC/H.264, But your brower do not support mse!")}},{key:"setPlaybackRate",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:1;return!(e<=0||null==this.videoTag||null===this.videoTag||(this.videoTag.playbackRate=e,0))}},{key:"getPlaybackRate",value:function(){return null==this.videoTag||null===this.videoTag?0:this.videoTag.playbackRate}},{key:"getSize",value:function(){return{width:this.videoTag.videoWidth>0?this.videoTag.videoWidth:this.width,height:this.videoTag.videoHeight>0?this.videoTag.videoHeight:this.height}}},{key:"play",value:function(){this.myPlayer.play()}},{key:"seek",value:function(e){this.myPlayer.currentTime=e}},{key:"pause",value:function(){this.myPlayer.pause()}},{key:"setVoice",value:function(e){this.myPlayer.volume=e}},{key:"isPlayingState",value:function(){return!this.videoTag.paused}},{key:"_loopBufferState",value:function(){var e=this;e.duration<=0&&(e.duration=e.videoTag.duration),null!==e.bufferInterval&&(window.clearInterval(e.bufferInterval),e.bufferInterval=null),e.bufferInterval=window.setInterval((function(){if(!e.duration||e.duration<0)window.clearInterval(e.bufferInterval);else{var t=e.videoTag.buffered.end(0);if(t>=e.duration-.04)return e.onCacheProcess&&e.onCacheProcess(e.duration),void window.clearInterval(e.bufferInterval);e.onCacheProcess&&e.onCacheProcess(t)}}),200)}},{key:"_releaseFlvjs",value:function(){this.myPlayer,this.myPlayer.pause(),this.myPlayer.unload(),this.myPlayer.detachMediaElement(),this.myPlayer.destroy(),this.myPlayer=null,this.videoTag.remove(),this.videoTag=null,null!==this.checkStartInterval&&(this.checkStartIntervalCount=0,window.clearInterval(this.checkStartInterval),this.checkStartInterval=null),null!==this.checkPicBlockInterval&&(window.clearInterval(this.checkPicBlockInterval),this.checkPicBlockInterval=null),this.isInitDecodeFrames=!1,this.lastDecodedFrame=0,this.lastDecodedFrameTime=-1}},{key:"release",value:function(){null!==this.checkStartInterval&&(this.checkStartIntervalCount=0,window.clearInterval(this.checkStartInterval),this.checkStartInterval=null),null!==this.checkPicBlockInterval&&(window.clearInterval(this.checkPicBlockInterval),this.checkPicBlockInterval=null),null!==this.bufferInterval&&(window.clearInterval(this.bufferInterval),this.bufferInterval=null),this._releaseFlvjs(),this.myPlayerID=null,this.videoContaner=null,this.onLoadFinish=null,this.onPlayingTime=null,this.onPlayingFinish=null,this.onReadyShowDone=null,this.onPlayState=null,window.onclick=document.body.onclick=null}}])&&n(t.prototype,i),o&&n(t,o),e}();i.NvFlvjsCore=o},{"../consts":52,"../decoder/av-common":56,"../demuxer/flv-hevc/flv-hevc.js":68,"../version":84}],79:[function(e,t,i){"use strict";function n(e,t){for(var i=0;i0&&s.GetMsTime()-t.lastDecodedFrameTime>1e4)return window.clearInterval(t.checkPicBlockInterval),t.checkPicBlockInterval=null,void t._reBuildMpegTsjs(e)}),1e3)}},{key:"_checkLoadState",value:function(e){var t=this;this.checkStartIntervalCount=0,this.checkStartInterval=window.setInterval((function(){return t.lastDecodedFrame,t.isInitDecodeFrames,t.checkStartIntervalCount,!1!==t.isInitDecodeFrames?(t.checkStartIntervalCount=0,window.clearInterval(t.checkStartInterval),void(t.checkStartInterval=null)):(t.checkStartIntervalCount+=1,t.checkStartIntervalCount>20?(window.clearInterval(t.checkStartInterval),t.checkStartIntervalCount=0,t.checkStartInterval=null,void(!1===t.isInitDecodeFrames&&t._reBuildMpegTsjs(e))):void 0)}),500)}},{key:"makeIt",value:function(e){var t=this;if(a.isSupported()){var i=document.querySelector("#"+this.configFormat.playerId);this.videoTag=document.createElement("video"),this.videoTag.id=this.myPlayerID,this.videoTag.style.width=this.configFormat.width+"px",this.videoTag.style.height=this.configFormat.height+"px",i.appendChild(this.videoTag),!0===this.configFormat.autoPlay&&(this.videoTag.muted="muted",this.videoTag.autoplay="autoplay",window.onclick=document.body.onclick=function(e){t.videoTag.muted=!1,t.isPlayingState(),window.onclick=document.body.onclick=null}),this.videoTag.onplay=function(){var e=t.isPlayingState();t.onPlayState&&t.onPlayState(e)},this.videoTag.onpause=function(){var e=t.isPlayingState();t.onPlayState&&t.onPlayState(e)};var n={hasVideo:!0,hasAudio:!(!0===this.configFormat.audioNone),type:"mse",url:e,isLive:this.configFormat.duration<=0,withCredentials:!1};this.myPlayer=a.createPlayer(n),this.myPlayer.attachMediaElement(this.videoTag),this.myPlayer.on(a.Events.MEDIA_INFO,(function(e){t.videoTag.videoWidth,!1===t.isInitDecodeFrames&&(t.isInitDecodeFrames=!0,t.width=Math.max(t.videoTag.videoWidth,e.width),t.height=Math.max(t.videoTag.videoHeight,e.height),t.videoTag.duration&&e.duration?t.videoTag.duration?t.duration=t.videoTag.duration:e.duration&&(t.duration=e.duration):t.duration=t.configFormat.duration/1e3,t.duration,t.onLoadFinish&&t.onLoadFinish(),t.onReadyShowDone&&t.onReadyShowDone(),t._loopBufferState(),t.isPlayingState(),t.videoTag.ontimeupdate=function(){t.onPlayingTime&&t.onPlayingTime(t.videoTag.currentTime)},t.duration!==1/0&&t.duration>0&&(t.videoTag.onended=function(){t.onPlayingFinish&&t.onPlayingFinish()}))})),this.myPlayer.on(a.Events.SCRIPTDATA_ARRIVED,(function(e){})),this.myPlayer.on(a.Events.ERROR,(function(i,n,r){t.myPlayer&&t._reBuildMpegTsjs(e)})),this.myPlayer.load(),this._checkLoadState(e),this._checkPicBlock(e)}else console.error("FLV is AVC/H.264, But your brower do not support mse!")}},{key:"setPlaybackRate",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:1;return!(e<=0||null==this.videoTag||null===this.videoTag||(this.videoTag.playbackRate=e,0))}},{key:"getPlaybackRate",value:function(){return null==this.videoTag||null===this.videoTag?0:this.videoTag.playbackRate}},{key:"getSize",value:function(){return{width:this.videoTag.videoWidth>0?this.videoTag.videoWidth:this.width,height:this.videoTag.videoHeight>0?this.videoTag.videoHeight:this.height}}},{key:"play",value:function(){this.videoTag,this.videoTag.play()}},{key:"seek",value:function(e){this.videoTag.currentTime=e}},{key:"pause",value:function(){this.videoTag.pause()}},{key:"setVoice",value:function(e){this.videoTag.volume=e}},{key:"isPlayingState",value:function(){return!this.videoTag.paused}},{key:"_loopBufferState",value:function(){var e=this;e.duration<=0&&e.videoTag.duration&&(e.duration=e.videoTag.duration),null!==e.bufferInterval&&(window.clearInterval(e.bufferInterval),e.bufferInterval=null),e.bufferInterval=window.setInterval((function(){if(e.configFormat.duration<=0)window.clearInterval(e.bufferInterval);else{var t=e.videoTag.buffered.end(0);if(t>=e.duration-.04)return e.onCacheProcess&&e.onCacheProcess(e.duration),void window.clearInterval(e.bufferInterval);e.onCacheProcess&&e.onCacheProcess(t)}}),200)}},{key:"_releaseMpegTsjs",value:function(){this.myPlayer,this.myPlayer.pause(),this.myPlayer.unload(),this.myPlayer.detachMediaElement(),this.myPlayer.destroy(),this.myPlayer=null,this.videoTag.remove(),this.videoTag=null,null!==this.checkStartInterval&&(this.checkStartIntervalCount=0,window.clearInterval(this.checkStartInterval),this.checkStartInterval=null),null!==this.checkPicBlockInterval&&(window.clearInterval(this.checkPicBlockInterval),this.checkPicBlockInterval=null),this.isInitDecodeFrames=!1,this.lastDecodedFrame=0,this.lastDecodedFrameTime=-1}},{key:"release",value:function(){null!==this.checkStartInterval&&(this.checkStartIntervalCount=0,window.clearInterval(this.checkStartInterval),this.checkStartInterval=null),null!==this.checkPicBlockInterval&&(window.clearInterval(this.checkPicBlockInterval),this.checkPicBlockInterval=null),null!==this.bufferInterval&&(window.clearInterval(this.bufferInterval),this.bufferInterval=null),this._releaseMpegTsjs(),this.myPlayerID=null,this.videoContaner=null,this.onLoadFinish=null,this.onPlayingTime=null,this.onPlayingFinish=null,this.onReadyShowDone=null,this.onPlayState=null,window.onclick=document.body.onclick=null}}])&&n(t.prototype,i),o&&n(t,o),e}();i.NvMpegTsCore=o},{"../consts":52,"../decoder/av-common":56,"../version":84,"mpegts.js":41}],80:[function(e,t,i){"use strict";function n(e,t){for(var i=0;i0&&void 0!==arguments[0]?arguments[0]:1;return!(e<=0||null==this.videoTag||null===this.videoTag||(this.videoTag.playbackRate=e,0))}},{key:"getPlaybackRate",value:function(){return null==this.videoTag||null===this.videoTag?0:this.videoTag.playbackRate}},{key:"getSize",value:function(){return this.myPlayer.videoWidth()<=0?{width:this.videoTag.videoWidth,height:this.videoTag.videoHeight}:{width:this.myPlayer.videoWidth(),height:this.myPlayer.videoHeight()}}},{key:"play",value:function(){void 0===this.videoTag||null===this.videoTag?this.myPlayer.play():this.videoTag.play()}},{key:"seek",value:function(e){void 0===this.videoTag||null===this.videoTag?this.myPlayer.currentTime=e:this.videoTag.currentTime=e}},{key:"pause",value:function(){void 0===this.videoTag||null===this.videoTag?this.myPlayer.pause():this.videoTag.pause()}},{key:"setVoice",value:function(e){void 0===this.videoTag||null===this.videoTag?this.myPlayer.volume=e:this.videoTag.volume=e}},{key:"isPlayingState",value:function(){return!this.myPlayer.paused()}},{key:"_loopBufferState",value:function(){var e=this;e.duration<=0&&(e.duration=e.videoTag.duration),null!==e.bufferInterval&&(window.clearInterval(e.bufferInterval),e.bufferInterval=null),e.configFormat.probeDurationMS,e.configFormat.probeDurationMS<=0||e.duration<=0||(e.bufferInterval=window.setInterval((function(){var t=e.videoTag.buffered.end(0);if(t>=e.duration-.04)return e.onCacheProcess&&e.onCacheProcess(e.duration),void window.clearInterval(e.bufferInterval);e.onCacheProcess&&e.onCacheProcess(t)}),200))}},{key:"release",value:function(){this.loadSuccess=!1,void 0!==this.bootInterval&&null!==this.bootInterval&&(window.clearInterval(this.bootInterval),this.bootInterval=null),this.myPlayer.dispose(),this.myPlayerID=null,this.myPlayer=null,this.videoContaner=null,this.videoTag=null,this.onLoadFinish=null,this.onPlayingTime=null,this.onPlayingFinish=null,this.onSeekFinish=null,this.onReadyShowDone=null,this.onPlayState=null,null!==this.bufferInterval&&(window.clearInterval(this.bufferInterval),this.bufferInterval=null),window.onclick=document.body.onclick=null}}])&&n(t.prototype,i),s&&n(t,s),e}();i.NvVideojsCore=s},{"../consts":52,"../version":84,"video.js":47}],81:[function(e,t,i){"use strict";e("../decoder/av-common");function n(e){this.gl=e,this.texture=e.createTexture(),e.bindTexture(e.TEXTURE_2D,this.texture),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,e.LINEAR),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.LINEAR),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE)}n.prototype.bind=function(e,t,i){var n=this.gl;n.activeTexture([n.TEXTURE0,n.TEXTURE1,n.TEXTURE2][e]),n.bindTexture(n.TEXTURE_2D,this.texture),n.uniform1i(n.getUniformLocation(t,i),e)},n.prototype.fill=function(e,t,i){var n=this.gl;n.bindTexture(n.TEXTURE_2D,this.texture),n.texImage2D(n.TEXTURE_2D,0,n.LUMINANCE,e,t,0,n.LUMINANCE,n.UNSIGNED_BYTE,i)},t.exports={renderFrame:function(e,t,i,n,r,a){e.viewport(0,0,e.canvas.width,e.canvas.height),e.clearColor(0,0,0,0),e.clear(e.COLOR_BUFFER_BIT),e.y.fill(r,a,t),e.u.fill(r>>1,a>>1,i),e.v.fill(r>>1,a>>1,n),e.drawArrays(e.TRIANGLE_STRIP,0,4)},setupCanvas:function(e,t){var i=e.getContext("webgl")||e.getContext("experimental-webgl");if(!i)return i;var r=i.createProgram(),a=["attribute highp vec4 aVertexPosition;","attribute vec2 aTextureCoord;","varying highp vec2 vTextureCoord;","void main(void) {"," gl_Position = aVertexPosition;"," vTextureCoord = aTextureCoord;","}"].join("\n"),s=i.createShader(i.VERTEX_SHADER);i.shaderSource(s,a),i.compileShader(s);var o=["precision highp float;","varying lowp vec2 vTextureCoord;","uniform sampler2D YTexture;","uniform sampler2D UTexture;","uniform sampler2D VTexture;","const mat4 YUV2RGB = mat4","("," 1.1643828125, 0, 1.59602734375, -.87078515625,"," 1.1643828125, -.39176171875, -.81296875, .52959375,"," 1.1643828125, 2.017234375, 0, -1.081390625,"," 0, 0, 0, 1",");","void main(void) {"," gl_FragColor = vec4( texture2D(YTexture, vTextureCoord).x, texture2D(UTexture, vTextureCoord).x, texture2D(VTexture, vTextureCoord).x, 1) * YUV2RGB;","}"].join("\n"),u=i.createShader(i.FRAGMENT_SHADER);i.shaderSource(u,o),i.compileShader(u),i.attachShader(r,s),i.attachShader(r,u),i.linkProgram(r),i.useProgram(r),i.getProgramParameter(r,i.LINK_STATUS);var l=i.getAttribLocation(r,"aVertexPosition");i.enableVertexAttribArray(l);var h=i.getAttribLocation(r,"aTextureCoord");i.enableVertexAttribArray(h);var d=i.createBuffer();i.bindBuffer(i.ARRAY_BUFFER,d),i.bufferData(i.ARRAY_BUFFER,new Float32Array([1,1,0,-1,1,0,1,-1,0,-1,-1,0]),i.STATIC_DRAW),i.vertexAttribPointer(l,3,i.FLOAT,!1,0,0);var c=i.createBuffer();return i.bindBuffer(i.ARRAY_BUFFER,c),i.bufferData(i.ARRAY_BUFFER,new Float32Array([1,0,0,0,1,1,0,1]),i.STATIC_DRAW),i.vertexAttribPointer(h,2,i.FLOAT,!1,0,0),i.y=new n(i),i.u=new n(i),i.v=new n(i),i.y.bind(0,r,"YTexture"),i.u.bind(1,r,"UTexture"),i.v.bind(2,r,"VTexture"),i},releaseContext:function(e){e.deleteTexture(e.y.texture),e.deleteTexture(e.u.texture),e.deleteTexture(e.v.texture)}}},{"../decoder/av-common":56}],82:[function(e,t,i){(function(e){"use strict";e.STATIC_MEM_wasmDecoderState=-1,e.STATICE_MEM_playerCount=-1,e.STATICE_MEM_playerIndexPtr=0}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],83:[function(e,t,i){"use strict";function n(e,t){for(var i=0;i New265WebJs declare global { interface Window { new265webjs: new265webJsFn } } export default class H265webjsModule { static createPlayer: (url: string, config: Web265JsConfig) => New265WebJs static clear(): void } ================================================ FILE: web/public/static/js/h265web/index.js ================================================ /********************************************************* * LICENSE: LICENSE-Free_CN.MD * * Author: Numberwolf - ChangYanlong * QQ: 531365872 * QQ Group:925466059 * Wechat: numberwolf11 * Discord: numberwolf#8694 * E-Mail: porschegt23@foxmail.com * Github: https://github.com/numberwolf/h265web.js * * 作者: 小老虎(Numberwolf)(常炎隆) * QQ: 531365872 * QQ群: 531365872 * 微信: numberwolf11 * Discord: numberwolf#8694 * 邮箱: porschegt23@foxmail.com * 博客: https://www.jianshu.com/u/9c09c1e00fd1 * Github: https://github.com/numberwolf/h265web.js * **********************************************************/ require('./h265webjs-v20221106'); export default class h265webjs { static createPlayer(videoURL, config) { return window.new265webjs(videoURL, config); } static clear() { global.STATICE_MEM_playerCount = -1; global.STATICE_MEM_playerIndexPtr = 0; } } ================================================ FILE: web/public/static/js/h265web/missile.js ================================================ var ENVIRONMENT_IS_PTHREAD = true; var Module = typeof Module !== "undefined" ? Module : {}; var moduleOverrides = {}; var key; for (key in Module) { if (Module.hasOwnProperty(key)) { moduleOverrides[key] = Module[key] } } var arguments_ = []; var thisProgram = "./this.program"; var quit_ = function(status, toThrow) { throw toThrow }; var ENVIRONMENT_IS_WEB = false; var ENVIRONMENT_IS_WORKER = false; var ENVIRONMENT_IS_NODE = false; var ENVIRONMENT_HAS_NODE = false; var ENVIRONMENT_IS_SHELL = false; ENVIRONMENT_IS_WEB = typeof window === "object"; ENVIRONMENT_IS_WORKER = typeof importScripts === "function"; ENVIRONMENT_HAS_NODE = typeof process === "object" && typeof process.versions === "object" && typeof process.versions.node === "string"; ENVIRONMENT_IS_NODE = ENVIRONMENT_HAS_NODE && !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_WORKER; ENVIRONMENT_IS_SHELL = !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_NODE && !ENVIRONMENT_IS_WORKER; if (Module["ENVIRONMENT"]) { throw new Error("Module.ENVIRONMENT has been deprecated. To force the environment, use the ENVIRONMENT compile-time option (for example, -s ENVIRONMENT=web or -s ENVIRONMENT=node)") } var scriptDirectory = ""; function locateFile(path) { if (Module["locateFile"]) { return Module["locateFile"](path, scriptDirectory) } return scriptDirectory + path } var read_, readAsync, readBinary, setWindowTitle; if (ENVIRONMENT_IS_NODE) { scriptDirectory = __dirname + "/"; var nodeFS; var nodePath; read_ = function shell_read(filename, binary) { var ret; if (!nodeFS) nodeFS = require("fs"); if (!nodePath) nodePath = require("path"); filename = nodePath["normalize"](filename); ret = nodeFS["readFileSync"](filename); return binary ? ret : ret.toString() }; readBinary = function readBinary(filename) { var ret = read_(filename, true); if (!ret.buffer) { ret = new Uint8Array(ret) } assert(ret.buffer); return ret }; if (process["argv"].length > 1) { thisProgram = process["argv"][1].replace(/\\/g, "/") } arguments_ = process["argv"].slice(2); if (typeof module !== "undefined") { module["exports"] = Module } process["on"]("uncaughtException", function(ex) { if (!(ex instanceof ExitStatus)) { throw ex } }); process["on"]("unhandledRejection", abort); quit_ = function(status) { process["exit"](status) }; Module["inspect"] = function() { return "[Emscripten Module object]" } } else if (ENVIRONMENT_IS_SHELL) { if (typeof read != "undefined") { read_ = function shell_read(f) { return read(f) } } readBinary = function readBinary(f) { var data; if (typeof readbuffer === "function") { return new Uint8Array(readbuffer(f)) } data = read(f, "binary"); assert(typeof data === "object"); return data }; if (typeof scriptArgs != "undefined") { arguments_ = scriptArgs } else if (typeof arguments != "undefined") { arguments_ = arguments } if (typeof quit === "function") { quit_ = function(status) { quit(status) } } if (typeof print !== "undefined") { if (typeof console === "undefined") console = {}; console.log = print; console.warn = console.error = typeof printErr !== "undefined" ? printErr : print } } else if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) { if (ENVIRONMENT_IS_WORKER) { scriptDirectory = self.location.href } else if (document.currentScript) { scriptDirectory = document.currentScript.src } if (scriptDirectory.indexOf("blob:") !== 0) { scriptDirectory = scriptDirectory.substr(0, scriptDirectory.lastIndexOf("/") + 1) } else { scriptDirectory = "" } read_ = function shell_read(url) { var xhr = new XMLHttpRequest; xhr.open("GET", url, false); xhr.send(null); return xhr.responseText }; if (ENVIRONMENT_IS_WORKER) { readBinary = function readBinary(url) { var xhr = new XMLHttpRequest; xhr.open("GET", url, false); xhr.responseType = "arraybuffer"; xhr.send(null); return new Uint8Array(xhr.response) } } readAsync = function readAsync(url, onload, onerror) { var xhr = new XMLHttpRequest; xhr.open("GET", url, true); xhr.responseType = "arraybuffer"; xhr.onload = function xhr_onload() { if (xhr.status == 200 || xhr.status == 0 && xhr.response) { onload(xhr.response); return } onerror() }; xhr.onerror = onerror; xhr.send(null) }; setWindowTitle = function(title) { document.title = title } } else { throw new Error("environment detection error") } var out = Module["print"] || console.log.bind(console); var err = Module["printErr"] || console.warn.bind(console); for (key in moduleOverrides) { if (moduleOverrides.hasOwnProperty(key)) { Module[key] = moduleOverrides[key] } } moduleOverrides = null; if (Module["arguments"]) arguments_ = Module["arguments"]; if (!Object.getOwnPropertyDescriptor(Module, "arguments")) Object.defineProperty(Module, "arguments", { configurable: true, get: function() { abort("Module.arguments has been replaced with plain arguments_") } }); if (Module["thisProgram"]) thisProgram = Module["thisProgram"]; if (!Object.getOwnPropertyDescriptor(Module, "thisProgram")) Object.defineProperty(Module, "thisProgram", { configurable: true, get: function() { abort("Module.thisProgram has been replaced with plain thisProgram") } }); if (Module["quit"]) quit_ = Module["quit"]; if (!Object.getOwnPropertyDescriptor(Module, "quit")) Object.defineProperty(Module, "quit", { configurable: true, get: function() { abort("Module.quit has been replaced with plain quit_") } }); assert(typeof Module["memoryInitializerPrefixURL"] === "undefined", "Module.memoryInitializerPrefixURL option was removed, use Module.locateFile instead"); assert(typeof Module["pthreadMainPrefixURL"] === "undefined", "Module.pthreadMainPrefixURL option was removed, use Module.locateFile instead"); assert(typeof Module["cdInitializerPrefixURL"] === "undefined", "Module.cdInitializerPrefixURL option was removed, use Module.locateFile instead"); assert(typeof Module["filePackagePrefixURL"] === "undefined", "Module.filePackagePrefixURL option was removed, use Module.locateFile instead"); assert(typeof Module["read"] === "undefined", "Module.read option was removed (modify read_ in JS)"); assert(typeof Module["readAsync"] === "undefined", "Module.readAsync option was removed (modify readAsync in JS)"); assert(typeof Module["readBinary"] === "undefined", "Module.readBinary option was removed (modify readBinary in JS)"); assert(typeof Module["setWindowTitle"] === "undefined", "Module.setWindowTitle option was removed (modify setWindowTitle in JS)"); if (!Object.getOwnPropertyDescriptor(Module, "read")) Object.defineProperty(Module, "read", { configurable: true, get: function() { abort("Module.read has been replaced with plain read_") } }); if (!Object.getOwnPropertyDescriptor(Module, "readAsync")) Object.defineProperty(Module, "readAsync", { configurable: true, get: function() { abort("Module.readAsync has been replaced with plain readAsync") } }); if (!Object.getOwnPropertyDescriptor(Module, "readBinary")) Object.defineProperty(Module, "readBinary", { configurable: true, get: function() { abort("Module.readBinary has been replaced with plain readBinary") } }); stackSave = stackRestore = stackAlloc = function() { abort("cannot use the stack before compiled code is ready to run, and has provided stack access") }; function dynamicAlloc(size) { assert(DYNAMICTOP_PTR); var ret = HEAP32[DYNAMICTOP_PTR >> 2]; var end = ret + size + 15 & -16; if (end > _emscripten_get_heap_size()) { abort("failure to dynamicAlloc - memory growth etc. is not supported there, call malloc/sbrk directly") } HEAP32[DYNAMICTOP_PTR >> 2] = end; return ret } function getNativeTypeSize(type) { switch (type) { case "i1": case "i8": return 1; case "i16": return 2; case "i32": return 4; case "i64": return 8; case "float": return 4; case "double": return 8; default: { if (type[type.length - 1] === "*") { return 4 } else if (type[0] === "i") { var bits = parseInt(type.substr(1)); assert(bits % 8 === 0, "getNativeTypeSize invalid bits " + bits + ", type " + type); return bits / 8 } else { return 0 } } } } function warnOnce(text) { if (!warnOnce.shown) warnOnce.shown = {}; if (!warnOnce.shown[text]) { warnOnce.shown[text] = 1; err(text) } } var asm2wasmImports = { "f64-rem": function(x, y) { return x % y }, "debugger": function() { debugger } }; var jsCallStartIndex = 1; var functionPointers = new Array(35); function addFunction(func, sig) { assert(typeof func !== "undefined"); var base = 0; for (var i = base; i < base + 35; i++) { if (!functionPointers[i]) { functionPointers[i] = func; return jsCallStartIndex + i } } throw "Finished up all reserved function pointers. Use a higher value for RESERVED_FUNCTION_POINTERS." } function removeFunction(index) { functionPointers[index - jsCallStartIndex] = null } var tempRet0 = 0; var getTempRet0 = function() { return tempRet0 }; var wasmBinary; if (Module["wasmBinary"]) wasmBinary = Module["wasmBinary"]; if (!Object.getOwnPropertyDescriptor(Module, "wasmBinary")) Object.defineProperty(Module, "wasmBinary", { configurable: true, get: function() { abort("Module.wasmBinary has been replaced with plain wasmBinary") } }); var noExitRuntime; if (Module["noExitRuntime"]) noExitRuntime = Module["noExitRuntime"]; if (!Object.getOwnPropertyDescriptor(Module, "noExitRuntime")) Object.defineProperty(Module, "noExitRuntime", { configurable: true, get: function() { abort("Module.noExitRuntime has been replaced with plain noExitRuntime") } }); if (typeof WebAssembly !== "object") { abort("No WebAssembly support found. Build with -s WASM=0 to target JavaScript instead.") } function setValue(ptr, value, type, noSafe) { type = type || "i8"; if (type.charAt(type.length - 1) === "*") type = "i32"; switch (type) { case "i1": HEAP8[ptr >> 0] = value; break; case "i8": HEAP8[ptr >> 0] = value; break; case "i16": HEAP16[ptr >> 1] = value; break; case "i32": HEAP32[ptr >> 2] = value; break; case "i64": tempI64 = [value >>> 0, (tempDouble = value, +Math_abs(tempDouble) >= 1 ? tempDouble > 0 ? (Math_min(+Math_floor(tempDouble / 4294967296), 4294967295) | 0) >>> 0 : ~~+Math_ceil((tempDouble - +(~~tempDouble >>> 0)) / 4294967296) >>> 0 : 0)], HEAP32[ptr >> 2] = tempI64[0], HEAP32[ptr + 4 >> 2] = tempI64[1]; break; case "float": HEAPF32[ptr >> 2] = value; break; case "double": HEAPF64[ptr >> 3] = value; break; default: abort("invalid type for setValue: " + type) } } var wasmMemory; var wasmTable = new WebAssembly.Table({ "initial": 4928, "element": "anyfunc" }); var ABORT = false; var EXITSTATUS = 0; function assert(condition, text) { if (!condition) { abort("Assertion failed: " + text) } } function getCFunc(ident) { var func = Module["_" + ident]; assert(func, "Cannot call unknown function " + ident + ", make sure it is exported"); return func } function ccall(ident, returnType, argTypes, args, opts) { var toC = { "string": function(str) { var ret = 0; if (str !== null && str !== undefined && str !== 0) { var len = (str.length << 2) + 1; ret = stackAlloc(len); stringToUTF8(str, ret, len) } return ret }, "array": function(arr) { var ret = stackAlloc(arr.length); writeArrayToMemory(arr, ret); return ret } }; function convertReturnValue(ret) { if (returnType === "string") return UTF8ToString(ret); if (returnType === "boolean") return Boolean(ret); return ret } var func = getCFunc(ident); var cArgs = []; var stack = 0; assert(returnType !== "array", 'Return type should not be "array".'); if (args) { for (var i = 0; i < args.length; i++) { var converter = toC[argTypes[i]]; if (converter) { if (stack === 0) stack = stackSave(); cArgs[i] = converter(args[i]) } else { cArgs[i] = args[i] } } } var ret = func.apply(null, cArgs); ret = convertReturnValue(ret); if (stack !== 0) stackRestore(stack); return ret } function cwrap(ident, returnType, argTypes, opts) { return function() { return ccall(ident, returnType, argTypes, arguments, opts) } } var ALLOC_NORMAL = 0; var ALLOC_NONE = 3; function allocate(slab, types, allocator, ptr) { var zeroinit, size; if (typeof slab === "number") { zeroinit = true; size = slab } else { zeroinit = false; size = slab.length } var singleType = typeof types === "string" ? types : null; var ret; if (allocator == ALLOC_NONE) { ret = ptr } else { ret = [_malloc, stackAlloc, dynamicAlloc][allocator](Math.max(size, singleType ? 1 : types.length)) } if (zeroinit) { var stop; ptr = ret; assert((ret & 3) == 0); stop = ret + (size & ~3); for (; ptr < stop; ptr += 4) { HEAP32[ptr >> 2] = 0 } stop = ret + size; while (ptr < stop) { HEAP8[ptr++ >> 0] = 0 } return ret } if (singleType === "i8") { if (slab.subarray || slab.slice) { HEAPU8.set(slab, ret) } else { HEAPU8.set(new Uint8Array(slab), ret) } return ret } var i = 0, type, typeSize, previousType; while (i < size) { var curr = slab[i]; type = singleType || types[i]; if (type === 0) { i++; continue } assert(type, "Must know what type to store in allocate!"); if (type == "i64") type = "i32"; setValue(ret + i, curr, type); if (previousType !== type) { typeSize = getNativeTypeSize(type); previousType = type } i += typeSize } return ret } function getMemory(size) { if (!runtimeInitialized) return dynamicAlloc(size); return _malloc(size) } var UTF8Decoder = typeof TextDecoder !== "undefined" ? new TextDecoder("utf8") : undefined; function UTF8ArrayToString(u8Array, idx, maxBytesToRead) { var endIdx = idx + maxBytesToRead; var endPtr = idx; while (u8Array[endPtr] && !(endPtr >= endIdx)) ++endPtr; if (endPtr - idx > 16 && u8Array.subarray && UTF8Decoder) { return UTF8Decoder.decode(u8Array.subarray(idx, endPtr)) } else { var str = ""; while (idx < endPtr) { var u0 = u8Array[idx++]; if (!(u0 & 128)) { str += String.fromCharCode(u0); continue } var u1 = u8Array[idx++] & 63; if ((u0 & 224) == 192) { str += String.fromCharCode((u0 & 31) << 6 | u1); continue } var u2 = u8Array[idx++] & 63; if ((u0 & 240) == 224) { u0 = (u0 & 15) << 12 | u1 << 6 | u2 } else { if ((u0 & 248) != 240) warnOnce("Invalid UTF-8 leading byte 0x" + u0.toString(16) + " encountered when deserializing a UTF-8 string on the asm.js/wasm heap to a JS string!"); u0 = (u0 & 7) << 18 | u1 << 12 | u2 << 6 | u8Array[idx++] & 63 } if (u0 < 65536) { str += String.fromCharCode(u0) } else { var ch = u0 - 65536; str += String.fromCharCode(55296 | ch >> 10, 56320 | ch & 1023) } } } return str } function UTF8ToString(ptr, maxBytesToRead) { return ptr ? UTF8ArrayToString(HEAPU8, ptr, maxBytesToRead) : "" } function stringToUTF8Array(str, outU8Array, outIdx, maxBytesToWrite) { if (!(maxBytesToWrite > 0)) return 0; var startIdx = outIdx; var endIdx = outIdx + maxBytesToWrite - 1; for (var i = 0; i < str.length; ++i) { var u = str.charCodeAt(i); if (u >= 55296 && u <= 57343) { var u1 = str.charCodeAt(++i); u = 65536 + ((u & 1023) << 10) | u1 & 1023 } if (u <= 127) { if (outIdx >= endIdx) break; outU8Array[outIdx++] = u } else if (u <= 2047) { if (outIdx + 1 >= endIdx) break; outU8Array[outIdx++] = 192 | u >> 6; outU8Array[outIdx++] = 128 | u & 63 } else if (u <= 65535) { if (outIdx + 2 >= endIdx) break; outU8Array[outIdx++] = 224 | u >> 12; outU8Array[outIdx++] = 128 | u >> 6 & 63; outU8Array[outIdx++] = 128 | u & 63 } else { if (outIdx + 3 >= endIdx) break; if (u >= 2097152) warnOnce("Invalid Unicode code point 0x" + u.toString(16) + " encountered when serializing a JS string to an UTF-8 string on the asm.js/wasm heap! (Valid unicode code points should be in range 0-0x1FFFFF)."); outU8Array[outIdx++] = 240 | u >> 18; outU8Array[outIdx++] = 128 | u >> 12 & 63; outU8Array[outIdx++] = 128 | u >> 6 & 63; outU8Array[outIdx++] = 128 | u & 63 } } outU8Array[outIdx] = 0; return outIdx - startIdx } function stringToUTF8(str, outPtr, maxBytesToWrite) { assert(typeof maxBytesToWrite == "number", "stringToUTF8(str, outPtr, maxBytesToWrite) is missing the third parameter that specifies the length of the output buffer!"); return stringToUTF8Array(str, HEAPU8, outPtr, maxBytesToWrite) } function lengthBytesUTF8(str) { var len = 0; for (var i = 0; i < str.length; ++i) { var u = str.charCodeAt(i); if (u >= 55296 && u <= 57343) u = 65536 + ((u & 1023) << 10) | str.charCodeAt(++i) & 1023; if (u <= 127) ++len; else if (u <= 2047) len += 2; else if (u <= 65535) len += 3; else len += 4 } return len } var UTF16Decoder = typeof TextDecoder !== "undefined" ? new TextDecoder("utf-16le") : undefined; function allocateUTF8(str) { var size = lengthBytesUTF8(str) + 1; var ret = _malloc(size); if (ret) stringToUTF8Array(str, HEAP8, ret, size); return ret } function allocateUTF8OnStack(str) { var size = lengthBytesUTF8(str) + 1; var ret = stackAlloc(size); stringToUTF8Array(str, HEAP8, ret, size); return ret } function writeArrayToMemory(array, buffer) { assert(array.length >= 0, "writeArrayToMemory array must have a length (should be an array or typed array)"); HEAP8.set(array, buffer) } function writeAsciiToMemory(str, buffer, dontAddNull) { for (var i = 0; i < str.length; ++i) { assert(str.charCodeAt(i) === str.charCodeAt(i) & 255); HEAP8[buffer++ >> 0] = str.charCodeAt(i) } if (!dontAddNull) HEAP8[buffer >> 0] = 0 } var PAGE_SIZE = 16384; var WASM_PAGE_SIZE = 65536; var buffer, HEAP8, HEAPU8, HEAP16, HEAPU16, HEAP32, HEAPU32, HEAPF32, HEAPF64; function updateGlobalBufferAndViews(buf) { buffer = buf; Module["HEAP8"] = HEAP8 = new Int8Array(buf); Module["HEAP16"] = HEAP16 = new Int16Array(buf); Module["HEAP32"] = HEAP32 = new Int32Array(buf); Module["HEAPU8"] = HEAPU8 = new Uint8Array(buf); Module["HEAPU16"] = HEAPU16 = new Uint16Array(buf); Module["HEAPU32"] = HEAPU32 = new Uint32Array(buf); Module["HEAPF32"] = HEAPF32 = new Float32Array(buf); Module["HEAPF64"] = HEAPF64 = new Float64Array(buf) } var STACK_BASE = 1398224, STACK_MAX = 6641104, DYNAMIC_BASE = 6641104, DYNAMICTOP_PTR = 1398e3; assert(STACK_BASE % 16 === 0, "stack must start aligned"); assert(DYNAMIC_BASE % 16 === 0, "heap must start aligned"); var TOTAL_STACK = 5242880; if (Module["TOTAL_STACK"]) assert(TOTAL_STACK === Module["TOTAL_STACK"], "the stack size can no longer be determined at runtime"); var INITIAL_TOTAL_MEMORY = Module["TOTAL_MEMORY"] || 2147483648; if (!Object.getOwnPropertyDescriptor(Module, "TOTAL_MEMORY")) Object.defineProperty(Module, "TOTAL_MEMORY", { configurable: true, get: function() { abort("Module.TOTAL_MEMORY has been replaced with plain INITIAL_TOTAL_MEMORY") } }); assert(INITIAL_TOTAL_MEMORY >= TOTAL_STACK, "TOTAL_MEMORY should be larger than TOTAL_STACK, was " + INITIAL_TOTAL_MEMORY + "! (TOTAL_STACK=" + TOTAL_STACK + ")"); assert(typeof Int32Array !== "undefined" && typeof Float64Array !== "undefined" && Int32Array.prototype.subarray !== undefined && Int32Array.prototype.set !== undefined, "JS engine does not provide full typed array support"); if (Module["wasmMemory"]) { wasmMemory = Module["wasmMemory"] } else { wasmMemory = new WebAssembly.Memory({ "initial": INITIAL_TOTAL_MEMORY / WASM_PAGE_SIZE, "maximum": INITIAL_TOTAL_MEMORY / WASM_PAGE_SIZE }) } if (wasmMemory) { buffer = wasmMemory.buffer } INITIAL_TOTAL_MEMORY = buffer.byteLength; assert(INITIAL_TOTAL_MEMORY % WASM_PAGE_SIZE === 0); updateGlobalBufferAndViews(buffer); HEAP32[DYNAMICTOP_PTR >> 2] = DYNAMIC_BASE; function writeStackCookie() { assert((STACK_MAX & 3) == 0); HEAPU32[(STACK_MAX >> 2) - 1] = 34821223; HEAPU32[(STACK_MAX >> 2) - 2] = 2310721022; HEAP32[0] = 1668509029 } function checkStackCookie() { var cookie1 = HEAPU32[(STACK_MAX >> 2) - 1]; var cookie2 = HEAPU32[(STACK_MAX >> 2) - 2]; if (cookie1 != 34821223 || cookie2 != 2310721022) { abort("Stack overflow! Stack cookie has been overwritten, expected hex dwords 0x89BACDFE and 0x02135467, but received 0x" + cookie2.toString(16) + " " + cookie1.toString(16)) } if (HEAP32[0] !== 1668509029) abort("Runtime error: The application has corrupted its heap memory area (address zero)!") } function abortStackOverflow(allocSize) { abort("Stack overflow! Attempted to allocate " + allocSize + " bytes on the stack, but stack has only " + (STACK_MAX - stackSave() + allocSize) + " bytes available!") }(function() { var h16 = new Int16Array(1); var h8 = new Int8Array(h16.buffer); h16[0] = 25459; if (h8[0] !== 115 || h8[1] !== 99) throw "Runtime error: expected the system to be little-endian!" })(); function abortFnPtrError(ptr, sig) { var possibleSig = ""; for (var x in debug_tables) { var tbl = debug_tables[x]; if (tbl[ptr]) { possibleSig += 'as sig "' + x + '" pointing to function ' + tbl[ptr] + ", " } } abort("Invalid function pointer " + ptr + " called with signature '" + sig + "'. Perhaps this is an invalid value (e.g. caused by calling a virtual method on a NULL pointer)? Or calling a function with an incorrect type, which will fail? (it is worth building your source files with -Werror (warnings are errors), as warnings can indicate undefined behavior which can cause this). This pointer might make sense in another type signature: " + possibleSig) } function callRuntimeCallbacks(callbacks) { while (callbacks.length > 0) { var callback = callbacks.shift(); if (typeof callback == "function") { callback(); continue } var func = callback.func; if (typeof func === "number") { if (callback.arg === undefined) { Module["dynCall_v"](func) } else { Module["dynCall_vi"](func, callback.arg) } } else { func(callback.arg === undefined ? null : callback.arg) } } } var __ATPRERUN__ = []; var __ATINIT__ = []; var __ATMAIN__ = []; var __ATPOSTRUN__ = []; var runtimeInitialized = false; var runtimeExited = false; function preRun() { if (Module["preRun"]) { if (typeof Module["preRun"] == "function") Module["preRun"] = [Module["preRun"]]; while (Module["preRun"].length) { addOnPreRun(Module["preRun"].shift()) } } callRuntimeCallbacks(__ATPRERUN__) } function initRuntime() { checkStackCookie(); assert(!runtimeInitialized); runtimeInitialized = true; if (!Module["noFSInit"] && !FS.init.initialized) FS.init(); TTY.init(); callRuntimeCallbacks(__ATINIT__) } function preMain() { checkStackCookie(); FS.ignorePermissions = false; callRuntimeCallbacks(__ATMAIN__) } function exitRuntime() { checkStackCookie(); runtimeExited = true } function postRun() { checkStackCookie(); if (Module["postRun"]) { if (typeof Module["postRun"] == "function") Module["postRun"] = [Module["postRun"]]; while (Module["postRun"].length) { addOnPostRun(Module["postRun"].shift()) } } callRuntimeCallbacks(__ATPOSTRUN__) } function addOnPreRun(cb) { __ATPRERUN__.unshift(cb) } function addOnPostRun(cb) { __ATPOSTRUN__.unshift(cb) } assert(Math.imul, "This browser does not support Math.imul(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill"); assert(Math.fround, "This browser does not support Math.fround(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill"); assert(Math.clz32, "This browser does not support Math.clz32(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill"); assert(Math.trunc, "This browser does not support Math.trunc(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill"); var Math_abs = Math.abs; var Math_ceil = Math.ceil; var Math_floor = Math.floor; var Math_min = Math.min; var Math_trunc = Math.trunc; var runDependencies = 0; var runDependencyWatcher = null; var dependenciesFulfilled = null; var runDependencyTracking = {}; function getUniqueRunDependency(id) { var orig = id; while (1) { if (!runDependencyTracking[id]) return id; id = orig + Math.random() } return id } function addRunDependency(id) { runDependencies++; if (Module["monitorRunDependencies"]) { Module["monitorRunDependencies"](runDependencies) } if (id) { assert(!runDependencyTracking[id]); runDependencyTracking[id] = 1; if (runDependencyWatcher === null && typeof setInterval !== "undefined") { runDependencyWatcher = setInterval(function() { if (ABORT) { clearInterval(runDependencyWatcher); runDependencyWatcher = null; return } var shown = false; for (var dep in runDependencyTracking) { if (!shown) { shown = true; err("still waiting on run dependencies:") } err("dependency: " + dep) } if (shown) { err("(end of list)") } }, 1e4) } } else { err("warning: run dependency added without ID") } } function removeRunDependency(id) { runDependencies--; if (Module["monitorRunDependencies"]) { Module["monitorRunDependencies"](runDependencies) } if (id) { assert(runDependencyTracking[id]); delete runDependencyTracking[id] } else { err("warning: run dependency removed without ID") } if (runDependencies == 0) { if (runDependencyWatcher !== null) { clearInterval(runDependencyWatcher); runDependencyWatcher = null } if (dependenciesFulfilled) { var callback = dependenciesFulfilled; dependenciesFulfilled = null; callback() } } } Module["preloadedImages"] = {}; Module["preloadedAudios"] = {}; function abort(what) { if (Module["onAbort"]) { Module["onAbort"](what) } what += ""; out(what); err(what); ABORT = true; EXITSTATUS = 1; var extra = ""; var output = "abort(" + what + ") at " + stackTrace() + extra; throw output } var dataURIPrefix = "data:application/octet-stream;base64,"; function isDataURI(filename) { return String.prototype.startsWith ? filename.startsWith(dataURIPrefix) : filename.indexOf(dataURIPrefix) === 0 } var wasmBinaryFile = "missile-v20221120.wasm"; if (!isDataURI(wasmBinaryFile)) { wasmBinaryFile = locateFile(wasmBinaryFile) } function getBinary() { try { if (wasmBinary) { return new Uint8Array(wasmBinary) } if (readBinary) { return readBinary(wasmBinaryFile) } else { throw "both async and sync fetching of the wasm failed" } } catch (err) { abort(err) } } function getBinaryPromise() { if (!wasmBinary && (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) && typeof fetch === "function") { return fetch(wasmBinaryFile, { credentials: "same-origin" }).then(function(response) { if (!response["ok"]) { throw "failed to load wasm binary file at '" + wasmBinaryFile + "'" } return response["arrayBuffer"]() }).catch(function() { return getBinary() }) } return new Promise(function(resolve, reject) { resolve(getBinary()) }) } function createWasm() { var info = { "env": asmLibraryArg, "wasi_unstable": asmLibraryArg, "global": { "NaN": NaN, Infinity: Infinity }, "global.Math": Math, "asm2wasm": asm2wasmImports }; function receiveInstance(instance, module) { var exports = instance.exports; Module["asm"] = exports; removeRunDependency("wasm-instantiate") } addRunDependency("wasm-instantiate"); var trueModule = Module; function receiveInstantiatedSource(output) { assert(Module === trueModule, "the Module object should not be replaced during async compilation - perhaps the order of HTML elements is wrong?"); trueModule = null; receiveInstance(output["instance"]) } function instantiateArrayBuffer(receiver) { return getBinaryPromise().then(function(binary) { return WebAssembly.instantiate(binary, info) }).then(receiver, function(reason) { err("failed to asynchronously prepare wasm: " + reason); abort(reason) }) } function instantiateAsync() { if (!wasmBinary && typeof WebAssembly.instantiateStreaming === "function" && !isDataURI(wasmBinaryFile) && typeof fetch === "function") { fetch(wasmBinaryFile, { credentials: "same-origin" }).then(function(response) { var result = WebAssembly.instantiateStreaming(response, info); return result.then(receiveInstantiatedSource, function(reason) { err("wasm streaming compile failed: " + reason); err("falling back to ArrayBuffer instantiation"); instantiateArrayBuffer(receiveInstantiatedSource) }) }) } else { return instantiateArrayBuffer(receiveInstantiatedSource) } } if (Module["instantiateWasm"]) { try { var exports = Module["instantiateWasm"](info, receiveInstance); return exports } catch (e) { err("Module.instantiateWasm callback failed with error: " + e); return false } } instantiateAsync(); return {} } Module["asm"] = createWasm; var tempDouble; var tempI64; var ASM_CONSTS = [function() { if (typeof window != "undefined") { window.dispatchEvent(new CustomEvent("wasmLoaded")) } else {} }]; function _emscripten_asm_const_i(code) { return ASM_CONSTS[code]() } __ATINIT__.push({ func: function() { ___emscripten_environ_constructor() } }); var tempDoublePtr = 1398208; assert(tempDoublePtr % 8 == 0); function demangle(func) { warnOnce("warning: build with -s DEMANGLE_SUPPORT=1 to link in libcxxabi demangling"); return func } function demangleAll(text) { var regex = /\b__Z[\w\d_]+/g; return text.replace(regex, function(x) { var y = demangle(x); return x === y ? x : y + " [" + x + "]" }) } function jsStackTrace() { var err = new Error; if (!err.stack) { try { throw new Error(0) } catch (e) { err = e } if (!err.stack) { return "(no stack trace available)" } } return err.stack.toString() } function stackTrace() { var js = jsStackTrace(); if (Module["extraStackTrace"]) js += "\n" + Module["extraStackTrace"](); return demangleAll(js) } var ENV = {}; function ___buildEnvironment(environ) { var MAX_ENV_VALUES = 64; var TOTAL_ENV_SIZE = 1024; var poolPtr; var envPtr; if (!___buildEnvironment.called) { ___buildEnvironment.called = true; ENV["USER"] = "web_user"; ENV["LOGNAME"] = "web_user"; ENV["PATH"] = "/"; ENV["PWD"] = "/"; ENV["HOME"] = "/home/web_user"; ENV["LANG"] = (typeof navigator === "object" && navigator.languages && navigator.languages[0] || "C").replace("-", "_") + ".UTF-8"; ENV["_"] = thisProgram; poolPtr = getMemory(TOTAL_ENV_SIZE); envPtr = getMemory(MAX_ENV_VALUES * 4); HEAP32[envPtr >> 2] = poolPtr; HEAP32[environ >> 2] = envPtr } else { envPtr = HEAP32[environ >> 2]; poolPtr = HEAP32[envPtr >> 2] } var strings = []; var totalSize = 0; for (var key in ENV) { if (typeof ENV[key] === "string") { var line = key + "=" + ENV[key]; strings.push(line); totalSize += line.length } } if (totalSize > TOTAL_ENV_SIZE) { throw new Error("Environment size exceeded TOTAL_ENV_SIZE!") } var ptrSize = 4; for (var i = 0; i < strings.length; i++) { var line = strings[i]; writeAsciiToMemory(line, poolPtr); HEAP32[envPtr + i * ptrSize >> 2] = poolPtr; poolPtr += line.length + 1 } HEAP32[envPtr + strings.length * ptrSize >> 2] = 0 } function ___lock() {} function ___setErrNo(value) { if (Module["___errno_location"]) HEAP32[Module["___errno_location"]() >> 2] = value; else err("failed to set errno from JS"); return value } var PATH = { splitPath: function(filename) { var splitPathRe = /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/; return splitPathRe.exec(filename).slice(1) }, normalizeArray: function(parts, allowAboveRoot) { var up = 0; for (var i = parts.length - 1; i >= 0; i--) { var last = parts[i]; if (last === ".") { parts.splice(i, 1) } else if (last === "..") { parts.splice(i, 1); up++ } else if (up) { parts.splice(i, 1); up-- } } if (allowAboveRoot) { for (; up; up--) { parts.unshift("..") } } return parts }, normalize: function(path) { var isAbsolute = path.charAt(0) === "/", trailingSlash = path.substr(-1) === "/"; path = PATH.normalizeArray(path.split("/").filter(function(p) { return !!p }), !isAbsolute).join("/"); if (!path && !isAbsolute) { path = "." } if (path && trailingSlash) { path += "/" } return (isAbsolute ? "/" : "") + path }, dirname: function(path) { var result = PATH.splitPath(path), root = result[0], dir = result[1]; if (!root && !dir) { return "." } if (dir) { dir = dir.substr(0, dir.length - 1) } return root + dir }, basename: function(path) { if (path === "/") return "/"; var lastSlash = path.lastIndexOf("/"); if (lastSlash === -1) return path; return path.substr(lastSlash + 1) }, extname: function(path) { return PATH.splitPath(path)[3] }, join: function() { var paths = Array.prototype.slice.call(arguments, 0); return PATH.normalize(paths.join("/")) }, join2: function(l, r) { return PATH.normalize(l + "/" + r) } }; var PATH_FS = { resolve: function() { var resolvedPath = "", resolvedAbsolute = false; for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) { var path = i >= 0 ? arguments[i] : FS.cwd(); if (typeof path !== "string") { throw new TypeError("Arguments to path.resolve must be strings") } else if (!path) { return "" } resolvedPath = path + "/" + resolvedPath; resolvedAbsolute = path.charAt(0) === "/" } resolvedPath = PATH.normalizeArray(resolvedPath.split("/").filter(function(p) { return !!p }), !resolvedAbsolute).join("/"); return (resolvedAbsolute ? "/" : "") + resolvedPath || "." }, relative: function(from, to) { from = PATH_FS.resolve(from).substr(1); to = PATH_FS.resolve(to).substr(1); function trim(arr) { var start = 0; for (; start < arr.length; start++) { if (arr[start] !== "") break } var end = arr.length - 1; for (; end >= 0; end--) { if (arr[end] !== "") break } if (start > end) return []; return arr.slice(start, end - start + 1) } var fromParts = trim(from.split("/")); var toParts = trim(to.split("/")); var length = Math.min(fromParts.length, toParts.length); var samePartsLength = length; for (var i = 0; i < length; i++) { if (fromParts[i] !== toParts[i]) { samePartsLength = i; break } } var outputParts = []; for (var i = samePartsLength; i < fromParts.length; i++) { outputParts.push("..") } outputParts = outputParts.concat(toParts.slice(samePartsLength)); return outputParts.join("/") } }; var TTY = { ttys: [], init: function() {}, shutdown: function() {}, register: function(dev, ops) { TTY.ttys[dev] = { input: [], output: [], ops: ops }; FS.registerDevice(dev, TTY.stream_ops) }, stream_ops: { open: function(stream) { var tty = TTY.ttys[stream.node.rdev]; if (!tty) { throw new FS.ErrnoError(43) } stream.tty = tty; stream.seekable = false }, close: function(stream) { stream.tty.ops.flush(stream.tty) }, flush: function(stream) { stream.tty.ops.flush(stream.tty) }, read: function(stream, buffer, offset, length, pos) { if (!stream.tty || !stream.tty.ops.get_char) { throw new FS.ErrnoError(60) } var bytesRead = 0; for (var i = 0; i < length; i++) { var result; try { result = stream.tty.ops.get_char(stream.tty) } catch (e) { throw new FS.ErrnoError(29) } if (result === undefined && bytesRead === 0) { throw new FS.ErrnoError(6) } if (result === null || result === undefined) break; bytesRead++; buffer[offset + i] = result } if (bytesRead) { stream.node.timestamp = Date.now() } return bytesRead }, write: function(stream, buffer, offset, length, pos) { if (!stream.tty || !stream.tty.ops.put_char) { throw new FS.ErrnoError(60) } try { for (var i = 0; i < length; i++) { stream.tty.ops.put_char(stream.tty, buffer[offset + i]) } } catch (e) { throw new FS.ErrnoError(29) } if (length) { stream.node.timestamp = Date.now() } return i } }, default_tty_ops: { get_char: function(tty) { if (!tty.input.length) { var result = null; if (ENVIRONMENT_IS_NODE) { var BUFSIZE = 256; var buf = Buffer.alloc ? Buffer.alloc(BUFSIZE) : new Buffer(BUFSIZE); var bytesRead = 0; try { bytesRead = fs.readSync(process.stdin.fd, buf, 0, BUFSIZE, null) } catch (e) { if (e.toString().indexOf("EOF") != -1) bytesRead = 0; else throw e } if (bytesRead > 0) { result = buf.slice(0, bytesRead).toString("utf-8") } else { result = null } } else if (typeof window != "undefined" && typeof window.prompt == "function") { result = window.prompt("Input: "); if (result !== null) { result += "\n" } } else if (typeof readline == "function") { result = readline(); if (result !== null) { result += "\n" } } if (!result) { return null } tty.input = intArrayFromString(result, true) } return tty.input.shift() }, put_char: function(tty, val) { if (val === null || val === 10) { out(UTF8ArrayToString(tty.output, 0)); tty.output = [] } else { if (val != 0) tty.output.push(val) } }, flush: function(tty) { if (tty.output && tty.output.length > 0) { out(UTF8ArrayToString(tty.output, 0)); tty.output = [] } } }, default_tty1_ops: { put_char: function(tty, val) { if (val === null || val === 10) { err(UTF8ArrayToString(tty.output, 0)); tty.output = [] } else { if (val != 0) tty.output.push(val) } }, flush: function(tty) { if (tty.output && tty.output.length > 0) { err(UTF8ArrayToString(tty.output, 0)); tty.output = [] } } } }; var MEMFS = { ops_table: null, mount: function(mount) { return MEMFS.createNode(null, "/", 16384 | 511, 0) }, createNode: function(parent, name, mode, dev) { if (FS.isBlkdev(mode) || FS.isFIFO(mode)) { throw new FS.ErrnoError(63) } if (!MEMFS.ops_table) { MEMFS.ops_table = { dir: { node: { getattr: MEMFS.node_ops.getattr, setattr: MEMFS.node_ops.setattr, lookup: MEMFS.node_ops.lookup, mknod: MEMFS.node_ops.mknod, rename: MEMFS.node_ops.rename, unlink: MEMFS.node_ops.unlink, rmdir: MEMFS.node_ops.rmdir, readdir: MEMFS.node_ops.readdir, symlink: MEMFS.node_ops.symlink }, stream: { llseek: MEMFS.stream_ops.llseek } }, file: { node: { getattr: MEMFS.node_ops.getattr, setattr: MEMFS.node_ops.setattr }, stream: { llseek: MEMFS.stream_ops.llseek, read: MEMFS.stream_ops.read, write: MEMFS.stream_ops.write, allocate: MEMFS.stream_ops.allocate, mmap: MEMFS.stream_ops.mmap, msync: MEMFS.stream_ops.msync } }, link: { node: { getattr: MEMFS.node_ops.getattr, setattr: MEMFS.node_ops.setattr, readlink: MEMFS.node_ops.readlink }, stream: {} }, chrdev: { node: { getattr: MEMFS.node_ops.getattr, setattr: MEMFS.node_ops.setattr }, stream: FS.chrdev_stream_ops } } } var node = FS.createNode(parent, name, mode, dev); if (FS.isDir(node.mode)) { node.node_ops = MEMFS.ops_table.dir.node; node.stream_ops = MEMFS.ops_table.dir.stream; node.contents = {} } else if (FS.isFile(node.mode)) { node.node_ops = MEMFS.ops_table.file.node; node.stream_ops = MEMFS.ops_table.file.stream; node.usedBytes = 0; node.contents = null } else if (FS.isLink(node.mode)) { node.node_ops = MEMFS.ops_table.link.node; node.stream_ops = MEMFS.ops_table.link.stream } else if (FS.isChrdev(node.mode)) { node.node_ops = MEMFS.ops_table.chrdev.node; node.stream_ops = MEMFS.ops_table.chrdev.stream } node.timestamp = Date.now(); if (parent) { parent.contents[name] = node } return node }, getFileDataAsRegularArray: function(node) { if (node.contents && node.contents.subarray) { var arr = []; for (var i = 0; i < node.usedBytes; ++i) arr.push(node.contents[i]); return arr } return node.contents }, getFileDataAsTypedArray: function(node) { if (!node.contents) return new Uint8Array; if (node.contents.subarray) return node.contents.subarray(0, node.usedBytes); return new Uint8Array(node.contents) }, expandFileStorage: function(node, newCapacity) { var prevCapacity = node.contents ? node.contents.length : 0; if (prevCapacity >= newCapacity) return; var CAPACITY_DOUBLING_MAX = 1024 * 1024; newCapacity = Math.max(newCapacity, prevCapacity * (prevCapacity < CAPACITY_DOUBLING_MAX ? 2 : 1.125) | 0); if (prevCapacity != 0) newCapacity = Math.max(newCapacity, 256); var oldContents = node.contents; node.contents = new Uint8Array(newCapacity); if (node.usedBytes > 0) node.contents.set(oldContents.subarray(0, node.usedBytes), 0); return }, resizeFileStorage: function(node, newSize) { if (node.usedBytes == newSize) return; if (newSize == 0) { node.contents = null; node.usedBytes = 0; return } if (!node.contents || node.contents.subarray) { var oldContents = node.contents; node.contents = new Uint8Array(new ArrayBuffer(newSize)); if (oldContents) { node.contents.set(oldContents.subarray(0, Math.min(newSize, node.usedBytes))) } node.usedBytes = newSize; return } if (!node.contents) node.contents = []; if (node.contents.length > newSize) node.contents.length = newSize; else while (node.contents.length < newSize) node.contents.push(0); node.usedBytes = newSize }, node_ops: { getattr: function(node) { var attr = {}; attr.dev = FS.isChrdev(node.mode) ? node.id : 1; attr.ino = node.id; attr.mode = node.mode; attr.nlink = 1; attr.uid = 0; attr.gid = 0; attr.rdev = node.rdev; if (FS.isDir(node.mode)) { attr.size = 4096 } else if (FS.isFile(node.mode)) { attr.size = node.usedBytes } else if (FS.isLink(node.mode)) { attr.size = node.link.length } else { attr.size = 0 } attr.atime = new Date(node.timestamp); attr.mtime = new Date(node.timestamp); attr.ctime = new Date(node.timestamp); attr.blksize = 4096; attr.blocks = Math.ceil(attr.size / attr.blksize); return attr }, setattr: function(node, attr) { if (attr.mode !== undefined) { node.mode = attr.mode } if (attr.timestamp !== undefined) { node.timestamp = attr.timestamp } if (attr.size !== undefined) { MEMFS.resizeFileStorage(node, attr.size) } }, lookup: function(parent, name) { throw FS.genericErrors[44] }, mknod: function(parent, name, mode, dev) { return MEMFS.createNode(parent, name, mode, dev) }, rename: function(old_node, new_dir, new_name) { if (FS.isDir(old_node.mode)) { var new_node; try { new_node = FS.lookupNode(new_dir, new_name) } catch (e) {} if (new_node) { for (var i in new_node.contents) { throw new FS.ErrnoError(55) } } } delete old_node.parent.contents[old_node.name]; old_node.name = new_name; new_dir.contents[new_name] = old_node; old_node.parent = new_dir }, unlink: function(parent, name) { delete parent.contents[name] }, rmdir: function(parent, name) { var node = FS.lookupNode(parent, name); for (var i in node.contents) { throw new FS.ErrnoError(55) } delete parent.contents[name] }, readdir: function(node) { var entries = [".", ".."]; for (var key in node.contents) { if (!node.contents.hasOwnProperty(key)) { continue } entries.push(key) } return entries }, symlink: function(parent, newname, oldpath) { var node = MEMFS.createNode(parent, newname, 511 | 40960, 0); node.link = oldpath; return node }, readlink: function(node) { if (!FS.isLink(node.mode)) { throw new FS.ErrnoError(28) } return node.link } }, stream_ops: { read: function(stream, buffer, offset, length, position) { var contents = stream.node.contents; if (position >= stream.node.usedBytes) return 0; var size = Math.min(stream.node.usedBytes - position, length); assert(size >= 0); if (size > 8 && contents.subarray) { buffer.set(contents.subarray(position, position + size), offset) } else { for (var i = 0; i < size; i++) buffer[offset + i] = contents[position + i] } return size }, write: function(stream, buffer, offset, length, position, canOwn) { if (!length) return 0; var node = stream.node; node.timestamp = Date.now(); if (buffer.subarray && (!node.contents || node.contents.subarray)) { if (canOwn) { assert(position === 0, "canOwn must imply no weird position inside the file"); node.contents = buffer.subarray(offset, offset + length); node.usedBytes = length; return length } else if (node.usedBytes === 0 && position === 0) { node.contents = new Uint8Array(buffer.subarray(offset, offset + length)); node.usedBytes = length; return length } else if (position + length <= node.usedBytes) { node.contents.set(buffer.subarray(offset, offset + length), position); return length } } MEMFS.expandFileStorage(node, position + length); if (node.contents.subarray && buffer.subarray) node.contents.set(buffer.subarray(offset, offset + length), position); else { for (var i = 0; i < length; i++) { node.contents[position + i] = buffer[offset + i] } } node.usedBytes = Math.max(node.usedBytes, position + length); return length }, llseek: function(stream, offset, whence) { var position = offset; if (whence === 1) { position += stream.position } else if (whence === 2) { if (FS.isFile(stream.node.mode)) { position += stream.node.usedBytes } } if (position < 0) { throw new FS.ErrnoError(28) } return position }, allocate: function(stream, offset, length) { MEMFS.expandFileStorage(stream.node, offset + length); stream.node.usedBytes = Math.max(stream.node.usedBytes, offset + length) }, mmap: function(stream, buffer, offset, length, position, prot, flags) { if (!FS.isFile(stream.node.mode)) { throw new FS.ErrnoError(43) } var ptr; var allocated; var contents = stream.node.contents; if (!(flags & 2) && (contents.buffer === buffer || contents.buffer === buffer.buffer)) { allocated = false; ptr = contents.byteOffset } else { if (position > 0 || position + length < stream.node.usedBytes) { if (contents.subarray) { contents = contents.subarray(position, position + length) } else { contents = Array.prototype.slice.call(contents, position, position + length) } } allocated = true; var fromHeap = buffer.buffer == HEAP8.buffer; ptr = _malloc(length); if (!ptr) { throw new FS.ErrnoError(48) }(fromHeap ? HEAP8 : buffer).set(contents, ptr) } return { ptr: ptr, allocated: allocated } }, msync: function(stream, buffer, offset, length, mmapFlags) { if (!FS.isFile(stream.node.mode)) { throw new FS.ErrnoError(43) } if (mmapFlags & 2) { return 0 } var bytesWritten = MEMFS.stream_ops.write(stream, buffer, 0, length, offset, false); return 0 } } }; var IDBFS = { dbs: {}, indexedDB: function() { if (typeof indexedDB !== "undefined") return indexedDB; var ret = null; if (typeof window === "object") ret = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB; assert(ret, "IDBFS used, but indexedDB not supported"); return ret }, DB_VERSION: 21, DB_STORE_NAME: "FILE_DATA", mount: function(mount) { return MEMFS.mount.apply(null, arguments) }, syncfs: function(mount, populate, callback) { IDBFS.getLocalSet(mount, function(err, local) { if (err) return callback(err); IDBFS.getRemoteSet(mount, function(err, remote) { if (err) return callback(err); var src = populate ? remote : local; var dst = populate ? local : remote; IDBFS.reconcile(src, dst, callback) }) }) }, getDB: function(name, callback) { var db = IDBFS.dbs[name]; if (db) { return callback(null, db) } var req; try { req = IDBFS.indexedDB().open(name, IDBFS.DB_VERSION) } catch (e) { return callback(e) } if (!req) { return callback("Unable to connect to IndexedDB") } req.onupgradeneeded = function(e) { var db = e.target.result; var transaction = e.target.transaction; var fileStore; if (db.objectStoreNames.contains(IDBFS.DB_STORE_NAME)) { fileStore = transaction.objectStore(IDBFS.DB_STORE_NAME) } else { fileStore = db.createObjectStore(IDBFS.DB_STORE_NAME) } if (!fileStore.indexNames.contains("timestamp")) { fileStore.createIndex("timestamp", "timestamp", { unique: false }) } }; req.onsuccess = function() { db = req.result; IDBFS.dbs[name] = db; callback(null, db) }; req.onerror = function(e) { callback(this.error); e.preventDefault() } }, getLocalSet: function(mount, callback) { var entries = {}; function isRealDir(p) { return p !== "." && p !== ".." } function toAbsolute(root) { return function(p) { return PATH.join2(root, p) } } var check = FS.readdir(mount.mountpoint).filter(isRealDir).map(toAbsolute(mount.mountpoint)); while (check.length) { var path = check.pop(); var stat; try { stat = FS.stat(path) } catch (e) { return callback(e) } if (FS.isDir(stat.mode)) { check.push.apply(check, FS.readdir(path).filter(isRealDir).map(toAbsolute(path))) } entries[path] = { timestamp: stat.mtime } } return callback(null, { type: "local", entries: entries }) }, getRemoteSet: function(mount, callback) { var entries = {}; IDBFS.getDB(mount.mountpoint, function(err, db) { if (err) return callback(err); try { var transaction = db.transaction([IDBFS.DB_STORE_NAME], "readonly"); transaction.onerror = function(e) { callback(this.error); e.preventDefault() }; var store = transaction.objectStore(IDBFS.DB_STORE_NAME); var index = store.index("timestamp"); index.openKeyCursor().onsuccess = function(event) { var cursor = event.target.result; if (!cursor) { return callback(null, { type: "remote", db: db, entries: entries }) } entries[cursor.primaryKey] = { timestamp: cursor.key }; cursor.continue() } } catch (e) { return callback(e) } }) }, loadLocalEntry: function(path, callback) { var stat, node; try { var lookup = FS.lookupPath(path); node = lookup.node; stat = FS.stat(path) } catch (e) { return callback(e) } if (FS.isDir(stat.mode)) { return callback(null, { timestamp: stat.mtime, mode: stat.mode }) } else if (FS.isFile(stat.mode)) { node.contents = MEMFS.getFileDataAsTypedArray(node); return callback(null, { timestamp: stat.mtime, mode: stat.mode, contents: node.contents }) } else { return callback(new Error("node type not supported")) } }, storeLocalEntry: function(path, entry, callback) { try { if (FS.isDir(entry.mode)) { FS.mkdir(path, entry.mode) } else if (FS.isFile(entry.mode)) { FS.writeFile(path, entry.contents, { canOwn: true }) } else { return callback(new Error("node type not supported")) } FS.chmod(path, entry.mode); FS.utime(path, entry.timestamp, entry.timestamp) } catch (e) { return callback(e) } callback(null) }, removeLocalEntry: function(path, callback) { try { var lookup = FS.lookupPath(path); var stat = FS.stat(path); if (FS.isDir(stat.mode)) { FS.rmdir(path) } else if (FS.isFile(stat.mode)) { FS.unlink(path) } } catch (e) { return callback(e) } callback(null) }, loadRemoteEntry: function(store, path, callback) { var req = store.get(path); req.onsuccess = function(event) { callback(null, event.target.result) }; req.onerror = function(e) { callback(this.error); e.preventDefault() } }, storeRemoteEntry: function(store, path, entry, callback) { var req = store.put(entry, path); req.onsuccess = function() { callback(null) }; req.onerror = function(e) { callback(this.error); e.preventDefault() } }, removeRemoteEntry: function(store, path, callback) { var req = store.delete(path); req.onsuccess = function() { callback(null) }; req.onerror = function(e) { callback(this.error); e.preventDefault() } }, reconcile: function(src, dst, callback) { var total = 0; var create = []; Object.keys(src.entries).forEach(function(key) { var e = src.entries[key]; var e2 = dst.entries[key]; if (!e2 || e.timestamp > e2.timestamp) { create.push(key); total++ } }); var remove = []; Object.keys(dst.entries).forEach(function(key) { var e = dst.entries[key]; var e2 = src.entries[key]; if (!e2) { remove.push(key); total++ } }); if (!total) { return callback(null) } var errored = false; var db = src.type === "remote" ? src.db : dst.db; var transaction = db.transaction([IDBFS.DB_STORE_NAME], "readwrite"); var store = transaction.objectStore(IDBFS.DB_STORE_NAME); function done(err) { if (err && !errored) { errored = true; return callback(err) } } transaction.onerror = function(e) { done(this.error); e.preventDefault() }; transaction.oncomplete = function(e) { if (!errored) { callback(null) } }; create.sort().forEach(function(path) { if (dst.type === "local") { IDBFS.loadRemoteEntry(store, path, function(err, entry) { if (err) return done(err); IDBFS.storeLocalEntry(path, entry, done) }) } else { IDBFS.loadLocalEntry(path, function(err, entry) { if (err) return done(err); IDBFS.storeRemoteEntry(store, path, entry, done) }) } }); remove.sort().reverse().forEach(function(path) { if (dst.type === "local") { IDBFS.removeLocalEntry(path, done) } else { IDBFS.removeRemoteEntry(store, path, done) } }) } }; var ERRNO_CODES = { EPERM: 63, ENOENT: 44, ESRCH: 71, EINTR: 27, EIO: 29, ENXIO: 60, E2BIG: 1, ENOEXEC: 45, EBADF: 8, ECHILD: 12, EAGAIN: 6, EWOULDBLOCK: 6, ENOMEM: 48, EACCES: 2, EFAULT: 21, ENOTBLK: 105, EBUSY: 10, EEXIST: 20, EXDEV: 75, ENODEV: 43, ENOTDIR: 54, EISDIR: 31, EINVAL: 28, ENFILE: 41, EMFILE: 33, ENOTTY: 59, ETXTBSY: 74, EFBIG: 22, ENOSPC: 51, ESPIPE: 70, EROFS: 69, EMLINK: 34, EPIPE: 64, EDOM: 18, ERANGE: 68, ENOMSG: 49, EIDRM: 24, ECHRNG: 106, EL2NSYNC: 156, EL3HLT: 107, EL3RST: 108, ELNRNG: 109, EUNATCH: 110, ENOCSI: 111, EL2HLT: 112, EDEADLK: 16, ENOLCK: 46, EBADE: 113, EBADR: 114, EXFULL: 115, ENOANO: 104, EBADRQC: 103, EBADSLT: 102, EDEADLOCK: 16, EBFONT: 101, ENOSTR: 100, ENODATA: 116, ETIME: 117, ENOSR: 118, ENONET: 119, ENOPKG: 120, EREMOTE: 121, ENOLINK: 47, EADV: 122, ESRMNT: 123, ECOMM: 124, EPROTO: 65, EMULTIHOP: 36, EDOTDOT: 125, EBADMSG: 9, ENOTUNIQ: 126, EBADFD: 127, EREMCHG: 128, ELIBACC: 129, ELIBBAD: 130, ELIBSCN: 131, ELIBMAX: 132, ELIBEXEC: 133, ENOSYS: 52, ENOTEMPTY: 55, ENAMETOOLONG: 37, ELOOP: 32, EOPNOTSUPP: 138, EPFNOSUPPORT: 139, ECONNRESET: 15, ENOBUFS: 42, EAFNOSUPPORT: 5, EPROTOTYPE: 67, ENOTSOCK: 57, ENOPROTOOPT: 50, ESHUTDOWN: 140, ECONNREFUSED: 14, EADDRINUSE: 3, ECONNABORTED: 13, ENETUNREACH: 40, ENETDOWN: 38, ETIMEDOUT: 73, EHOSTDOWN: 142, EHOSTUNREACH: 23, EINPROGRESS: 26, EALREADY: 7, EDESTADDRREQ: 17, EMSGSIZE: 35, EPROTONOSUPPORT: 66, ESOCKTNOSUPPORT: 137, EADDRNOTAVAIL: 4, ENETRESET: 39, EISCONN: 30, ENOTCONN: 53, ETOOMANYREFS: 141, EUSERS: 136, EDQUOT: 19, ESTALE: 72, ENOTSUP: 138, ENOMEDIUM: 148, EILSEQ: 25, EOVERFLOW: 61, ECANCELED: 11, ENOTRECOVERABLE: 56, EOWNERDEAD: 62, ESTRPIPE: 135 }; var NODEFS = { isWindows: false, staticInit: function() { NODEFS.isWindows = !!process.platform.match(/^win/); var flags = process["binding"]("constants"); if (flags["fs"]) { flags = flags["fs"] } NODEFS.flagsForNodeMap = { 1024: flags["O_APPEND"], 64: flags["O_CREAT"], 128: flags["O_EXCL"], 0: flags["O_RDONLY"], 2: flags["O_RDWR"], 4096: flags["O_SYNC"], 512: flags["O_TRUNC"], 1: flags["O_WRONLY"] } }, bufferFrom: function(arrayBuffer) { return Buffer["alloc"] ? Buffer.from(arrayBuffer) : new Buffer(arrayBuffer) }, convertNodeCode: function(e) { var code = e.code; assert(code in ERRNO_CODES); return ERRNO_CODES[code] }, mount: function(mount) { assert(ENVIRONMENT_HAS_NODE); return NODEFS.createNode(null, "/", NODEFS.getMode(mount.opts.root), 0) }, createNode: function(parent, name, mode, dev) { if (!FS.isDir(mode) && !FS.isFile(mode) && !FS.isLink(mode)) { throw new FS.ErrnoError(28) } var node = FS.createNode(parent, name, mode); node.node_ops = NODEFS.node_ops; node.stream_ops = NODEFS.stream_ops; return node }, getMode: function(path) { var stat; try { stat = fs.lstatSync(path); if (NODEFS.isWindows) { stat.mode = stat.mode | (stat.mode & 292) >> 2 } } catch (e) { if (!e.code) throw e; throw new FS.ErrnoError(NODEFS.convertNodeCode(e)) } return stat.mode }, realPath: function(node) { var parts = []; while (node.parent !== node) { parts.push(node.name); node = node.parent } parts.push(node.mount.opts.root); parts.reverse(); return PATH.join.apply(null, parts) }, flagsForNode: function(flags) { flags &= ~2097152; flags &= ~2048; flags &= ~32768; flags &= ~524288; var newFlags = 0; for (var k in NODEFS.flagsForNodeMap) { if (flags & k) { newFlags |= NODEFS.flagsForNodeMap[k]; flags ^= k } } if (!flags) { return newFlags } else { throw new FS.ErrnoError(28) } }, node_ops: { getattr: function(node) { var path = NODEFS.realPath(node); var stat; try { stat = fs.lstatSync(path) } catch (e) { if (!e.code) throw e; throw new FS.ErrnoError(NODEFS.convertNodeCode(e)) } if (NODEFS.isWindows && !stat.blksize) { stat.blksize = 4096 } if (NODEFS.isWindows && !stat.blocks) { stat.blocks = (stat.size + stat.blksize - 1) / stat.blksize | 0 } return { dev: stat.dev, ino: stat.ino, mode: stat.mode, nlink: stat.nlink, uid: stat.uid, gid: stat.gid, rdev: stat.rdev, size: stat.size, atime: stat.atime, mtime: stat.mtime, ctime: stat.ctime, blksize: stat.blksize, blocks: stat.blocks } }, setattr: function(node, attr) { var path = NODEFS.realPath(node); try { if (attr.mode !== undefined) { fs.chmodSync(path, attr.mode); node.mode = attr.mode } if (attr.timestamp !== undefined) { var date = new Date(attr.timestamp); fs.utimesSync(path, date, date) } if (attr.size !== undefined) { fs.truncateSync(path, attr.size) } } catch (e) { if (!e.code) throw e; throw new FS.ErrnoError(NODEFS.convertNodeCode(e)) } }, lookup: function(parent, name) { var path = PATH.join2(NODEFS.realPath(parent), name); var mode = NODEFS.getMode(path); return NODEFS.createNode(parent, name, mode) }, mknod: function(parent, name, mode, dev) { var node = NODEFS.createNode(parent, name, mode, dev); var path = NODEFS.realPath(node); try { if (FS.isDir(node.mode)) { fs.mkdirSync(path, node.mode) } else { fs.writeFileSync(path, "", { mode: node.mode }) } } catch (e) { if (!e.code) throw e; throw new FS.ErrnoError(NODEFS.convertNodeCode(e)) } return node }, rename: function(oldNode, newDir, newName) { var oldPath = NODEFS.realPath(oldNode); var newPath = PATH.join2(NODEFS.realPath(newDir), newName); try { fs.renameSync(oldPath, newPath) } catch (e) { if (!e.code) throw e; throw new FS.ErrnoError(NODEFS.convertNodeCode(e)) } }, unlink: function(parent, name) { var path = PATH.join2(NODEFS.realPath(parent), name); try { fs.unlinkSync(path) } catch (e) { if (!e.code) throw e; throw new FS.ErrnoError(NODEFS.convertNodeCode(e)) } }, rmdir: function(parent, name) { var path = PATH.join2(NODEFS.realPath(parent), name); try { fs.rmdirSync(path) } catch (e) { if (!e.code) throw e; throw new FS.ErrnoError(NODEFS.convertNodeCode(e)) } }, readdir: function(node) { var path = NODEFS.realPath(node); try { return fs.readdirSync(path) } catch (e) { if (!e.code) throw e; throw new FS.ErrnoError(NODEFS.convertNodeCode(e)) } }, symlink: function(parent, newName, oldPath) { var newPath = PATH.join2(NODEFS.realPath(parent), newName); try { fs.symlinkSync(oldPath, newPath) } catch (e) { if (!e.code) throw e; throw new FS.ErrnoError(NODEFS.convertNodeCode(e)) } }, readlink: function(node) { var path = NODEFS.realPath(node); try { path = fs.readlinkSync(path); path = NODEJS_PATH.relative(NODEJS_PATH.resolve(node.mount.opts.root), path); return path } catch (e) { if (!e.code) throw e; throw new FS.ErrnoError(NODEFS.convertNodeCode(e)) } } }, stream_ops: { open: function(stream) { var path = NODEFS.realPath(stream.node); try { if (FS.isFile(stream.node.mode)) { stream.nfd = fs.openSync(path, NODEFS.flagsForNode(stream.flags)) } } catch (e) { if (!e.code) throw e; throw new FS.ErrnoError(NODEFS.convertNodeCode(e)) } }, close: function(stream) { try { if (FS.isFile(stream.node.mode) && stream.nfd) { fs.closeSync(stream.nfd) } } catch (e) { if (!e.code) throw e; throw new FS.ErrnoError(NODEFS.convertNodeCode(e)) } }, read: function(stream, buffer, offset, length, position) { if (length === 0) return 0; try { return fs.readSync(stream.nfd, NODEFS.bufferFrom(buffer.buffer), offset, length, position) } catch (e) { throw new FS.ErrnoError(NODEFS.convertNodeCode(e)) } }, write: function(stream, buffer, offset, length, position) { try { return fs.writeSync(stream.nfd, NODEFS.bufferFrom(buffer.buffer), offset, length, position) } catch (e) { throw new FS.ErrnoError(NODEFS.convertNodeCode(e)) } }, llseek: function(stream, offset, whence) { var position = offset; if (whence === 1) { position += stream.position } else if (whence === 2) { if (FS.isFile(stream.node.mode)) { try { var stat = fs.fstatSync(stream.nfd); position += stat.size } catch (e) { throw new FS.ErrnoError(NODEFS.convertNodeCode(e)) } } } if (position < 0) { throw new FS.ErrnoError(28) } return position } } }; var WORKERFS = { DIR_MODE: 16895, FILE_MODE: 33279, reader: null, mount: function(mount) { assert(ENVIRONMENT_IS_WORKER); if (!WORKERFS.reader) WORKERFS.reader = new FileReaderSync; var root = WORKERFS.createNode(null, "/", WORKERFS.DIR_MODE, 0); var createdParents = {}; function ensureParent(path) { var parts = path.split("/"); var parent = root; for (var i = 0; i < parts.length - 1; i++) { var curr = parts.slice(0, i + 1).join("/"); if (!createdParents[curr]) { createdParents[curr] = WORKERFS.createNode(parent, parts[i], WORKERFS.DIR_MODE, 0) } parent = createdParents[curr] } return parent } function base(path) { var parts = path.split("/"); return parts[parts.length - 1] } Array.prototype.forEach.call(mount.opts["files"] || [], function(file) { WORKERFS.createNode(ensureParent(file.name), base(file.name), WORKERFS.FILE_MODE, 0, file, file.lastModifiedDate) }); (mount.opts["blobs"] || []).forEach(function(obj) { WORKERFS.createNode(ensureParent(obj["name"]), base(obj["name"]), WORKERFS.FILE_MODE, 0, obj["data"]) }); (mount.opts["packages"] || []).forEach(function(pack) { pack["metadata"].files.forEach(function(file) { var name = file.filename.substr(1); WORKERFS.createNode(ensureParent(name), base(name), WORKERFS.FILE_MODE, 0, pack["blob"].slice(file.start, file.end)) }) }); return root }, createNode: function(parent, name, mode, dev, contents, mtime) { var node = FS.createNode(parent, name, mode); node.mode = mode; node.node_ops = WORKERFS.node_ops; node.stream_ops = WORKERFS.stream_ops; node.timestamp = (mtime || new Date).getTime(); assert(WORKERFS.FILE_MODE !== WORKERFS.DIR_MODE); if (mode === WORKERFS.FILE_MODE) { node.size = contents.size; node.contents = contents } else { node.size = 4096; node.contents = {} } if (parent) { parent.contents[name] = node } return node }, node_ops: { getattr: function(node) { return { dev: 1, ino: undefined, mode: node.mode, nlink: 1, uid: 0, gid: 0, rdev: undefined, size: node.size, atime: new Date(node.timestamp), mtime: new Date(node.timestamp), ctime: new Date(node.timestamp), blksize: 4096, blocks: Math.ceil(node.size / 4096) } }, setattr: function(node, attr) { if (attr.mode !== undefined) { node.mode = attr.mode } if (attr.timestamp !== undefined) { node.timestamp = attr.timestamp } }, lookup: function(parent, name) { throw new FS.ErrnoError(44) }, mknod: function(parent, name, mode, dev) { throw new FS.ErrnoError(63) }, rename: function(oldNode, newDir, newName) { throw new FS.ErrnoError(63) }, unlink: function(parent, name) { throw new FS.ErrnoError(63) }, rmdir: function(parent, name) { throw new FS.ErrnoError(63) }, readdir: function(node) { var entries = [".", ".."]; for (var key in node.contents) { if (!node.contents.hasOwnProperty(key)) { continue } entries.push(key) } return entries }, symlink: function(parent, newName, oldPath) { throw new FS.ErrnoError(63) }, readlink: function(node) { throw new FS.ErrnoError(63) } }, stream_ops: { read: function(stream, buffer, offset, length, position) { if (position >= stream.node.size) return 0; var chunk = stream.node.contents.slice(position, position + length); var ab = WORKERFS.reader.readAsArrayBuffer(chunk); buffer.set(new Uint8Array(ab), offset); return chunk.size }, write: function(stream, buffer, offset, length, position) { throw new FS.ErrnoError(29) }, llseek: function(stream, offset, whence) { var position = offset; if (whence === 1) { position += stream.position } else if (whence === 2) { if (FS.isFile(stream.node.mode)) { position += stream.node.size } } if (position < 0) { throw new FS.ErrnoError(28) } return position } } }; var ERRNO_MESSAGES = { 0: "Success", 1: "Arg list too long", 2: "Permission denied", 3: "Address already in use", 4: "Address not available", 5: "Address family not supported by protocol family", 6: "No more processes", 7: "Socket already connected", 8: "Bad file number", 9: "Trying to read unreadable message", 10: "Mount device busy", 11: "Operation canceled", 12: "No children", 13: "Connection aborted", 14: "Connection refused", 15: "Connection reset by peer", 16: "File locking deadlock error", 17: "Destination address required", 18: "Math arg out of domain of func", 19: "Quota exceeded", 20: "File exists", 21: "Bad address", 22: "File too large", 23: "Host is unreachable", 24: "Identifier removed", 25: "Illegal byte sequence", 26: "Connection already in progress", 27: "Interrupted system call", 28: "Invalid argument", 29: "I/O error", 30: "Socket is already connected", 31: "Is a directory", 32: "Too many symbolic links", 33: "Too many open files", 34: "Too many links", 35: "Message too long", 36: "Multihop attempted", 37: "File or path name too long", 38: "Network interface is not configured", 39: "Connection reset by network", 40: "Network is unreachable", 41: "Too many open files in system", 42: "No buffer space available", 43: "No such device", 44: "No such file or directory", 45: "Exec format error", 46: "No record locks available", 47: "The link has been severed", 48: "Not enough core", 49: "No message of desired type", 50: "Protocol not available", 51: "No space left on device", 52: "Function not implemented", 53: "Socket is not connected", 54: "Not a directory", 55: "Directory not empty", 56: "State not recoverable", 57: "Socket operation on non-socket", 59: "Not a typewriter", 60: "No such device or address", 61: "Value too large for defined data type", 62: "Previous owner died", 63: "Not super-user", 64: "Broken pipe", 65: "Protocol error", 66: "Unknown protocol", 67: "Protocol wrong type for socket", 68: "Math result not representable", 69: "Read only file system", 70: "Illegal seek", 71: "No such process", 72: "Stale file handle", 73: "Connection timed out", 74: "Text file busy", 75: "Cross-device link", 100: "Device not a stream", 101: "Bad font file fmt", 102: "Invalid slot", 103: "Invalid request code", 104: "No anode", 105: "Block device required", 106: "Channel number out of range", 107: "Level 3 halted", 108: "Level 3 reset", 109: "Link number out of range", 110: "Protocol driver not attached", 111: "No CSI structure available", 112: "Level 2 halted", 113: "Invalid exchange", 114: "Invalid request descriptor", 115: "Exchange full", 116: "No data (for no delay io)", 117: "Timer expired", 118: "Out of streams resources", 119: "Machine is not on the network", 120: "Package not installed", 121: "The object is remote", 122: "Advertise error", 123: "Srmount error", 124: "Communication error on send", 125: "Cross mount point (not really error)", 126: "Given log. name not unique", 127: "f.d. invalid for this operation", 128: "Remote address changed", 129: "Can access a needed shared lib", 130: "Accessing a corrupted shared lib", 131: ".lib section in a.out corrupted", 132: "Attempting to link in too many libs", 133: "Attempting to exec a shared library", 135: "Streams pipe error", 136: "Too many users", 137: "Socket type not supported", 138: "Not supported", 139: "Protocol family not supported", 140: "Can't send after socket shutdown", 141: "Too many references", 142: "Host is down", 148: "No medium (in tape drive)", 156: "Level 2 not synchronized" }; var FS = { root: null, mounts: [], devices: {}, streams: [], nextInode: 1, nameTable: null, currentPath: "/", initialized: false, ignorePermissions: true, trackingDelegate: {}, tracking: { openFlags: { READ: 1, WRITE: 2 } }, ErrnoError: null, genericErrors: {}, filesystems: null, syncFSRequests: 0, handleFSError: function(e) { if (!(e instanceof FS.ErrnoError)) throw e + " : " + stackTrace(); return ___setErrNo(e.errno) }, lookupPath: function(path, opts) { path = PATH_FS.resolve(FS.cwd(), path); opts = opts || {}; if (!path) return { path: "", node: null }; var defaults = { follow_mount: true, recurse_count: 0 }; for (var key in defaults) { if (opts[key] === undefined) { opts[key] = defaults[key] } } if (opts.recurse_count > 8) { throw new FS.ErrnoError(32) } var parts = PATH.normalizeArray(path.split("/").filter(function(p) { return !!p }), false); var current = FS.root; var current_path = "/"; for (var i = 0; i < parts.length; i++) { var islast = i === parts.length - 1; if (islast && opts.parent) { break } current = FS.lookupNode(current, parts[i]); current_path = PATH.join2(current_path, parts[i]); if (FS.isMountpoint(current)) { if (!islast || islast && opts.follow_mount) { current = current.mounted.root } } if (!islast || opts.follow) { var count = 0; while (FS.isLink(current.mode)) { var link = FS.readlink(current_path); current_path = PATH_FS.resolve(PATH.dirname(current_path), link); var lookup = FS.lookupPath(current_path, { recurse_count: opts.recurse_count }); current = lookup.node; if (count++ > 40) { throw new FS.ErrnoError(32) } } } } return { path: current_path, node: current } }, getPath: function(node) { var path; while (true) { if (FS.isRoot(node)) { var mount = node.mount.mountpoint; if (!path) return mount; return mount[mount.length - 1] !== "/" ? mount + "/" + path : mount + path } path = path ? node.name + "/" + path : node.name; node = node.parent } }, hashName: function(parentid, name) { var hash = 0; for (var i = 0; i < name.length; i++) { hash = (hash << 5) - hash + name.charCodeAt(i) | 0 } return (parentid + hash >>> 0) % FS.nameTable.length }, hashAddNode: function(node) { var hash = FS.hashName(node.parent.id, node.name); node.name_next = FS.nameTable[hash]; FS.nameTable[hash] = node }, hashRemoveNode: function(node) { var hash = FS.hashName(node.parent.id, node.name); if (FS.nameTable[hash] === node) { FS.nameTable[hash] = node.name_next } else { var current = FS.nameTable[hash]; while (current) { if (current.name_next === node) { current.name_next = node.name_next; break } current = current.name_next } } }, lookupNode: function(parent, name) { var err = FS.mayLookup(parent); if (err) { throw new FS.ErrnoError(err, parent) } var hash = FS.hashName(parent.id, name); for (var node = FS.nameTable[hash]; node; node = node.name_next) { var nodeName = node.name; if (node.parent.id === parent.id && nodeName === name) { return node } } return FS.lookup(parent, name) }, createNode: function(parent, name, mode, rdev) { if (!FS.FSNode) { FS.FSNode = function(parent, name, mode, rdev) { if (!parent) { parent = this } this.parent = parent; this.mount = parent.mount; this.mounted = null; this.id = FS.nextInode++; this.name = name; this.mode = mode; this.node_ops = {}; this.stream_ops = {}; this.rdev = rdev }; FS.FSNode.prototype = {}; var readMode = 292 | 73; var writeMode = 146; Object.defineProperties(FS.FSNode.prototype, { read: { get: function() { return (this.mode & readMode) === readMode }, set: function(val) { val ? this.mode |= readMode : this.mode &= ~readMode } }, write: { get: function() { return (this.mode & writeMode) === writeMode }, set: function(val) { val ? this.mode |= writeMode : this.mode &= ~writeMode } }, isFolder: { get: function() { return FS.isDir(this.mode) } }, isDevice: { get: function() { return FS.isChrdev(this.mode) } } }) } var node = new FS.FSNode(parent, name, mode, rdev); FS.hashAddNode(node); return node }, destroyNode: function(node) { FS.hashRemoveNode(node) }, isRoot: function(node) { return node === node.parent }, isMountpoint: function(node) { return !!node.mounted }, isFile: function(mode) { return (mode & 61440) === 32768 }, isDir: function(mode) { return (mode & 61440) === 16384 }, isLink: function(mode) { return (mode & 61440) === 40960 }, isChrdev: function(mode) { return (mode & 61440) === 8192 }, isBlkdev: function(mode) { return (mode & 61440) === 24576 }, isFIFO: function(mode) { return (mode & 61440) === 4096 }, isSocket: function(mode) { return (mode & 49152) === 49152 }, flagModes: { "r": 0, "rs": 1052672, "r+": 2, "w": 577, "wx": 705, "xw": 705, "w+": 578, "wx+": 706, "xw+": 706, "a": 1089, "ax": 1217, "xa": 1217, "a+": 1090, "ax+": 1218, "xa+": 1218 }, modeStringToFlags: function(str) { var flags = FS.flagModes[str]; if (typeof flags === "undefined") { throw new Error("Unknown file open mode: " + str) } return flags }, flagsToPermissionString: function(flag) { var perms = ["r", "w", "rw"][flag & 3]; if (flag & 512) { perms += "w" } return perms }, nodePermissions: function(node, perms) { if (FS.ignorePermissions) { return 0 } if (perms.indexOf("r") !== -1 && !(node.mode & 292)) { return 2 } else if (perms.indexOf("w") !== -1 && !(node.mode & 146)) { return 2 } else if (perms.indexOf("x") !== -1 && !(node.mode & 73)) { return 2 } return 0 }, mayLookup: function(dir) { var err = FS.nodePermissions(dir, "x"); if (err) return err; if (!dir.node_ops.lookup) return 2; return 0 }, mayCreate: function(dir, name) { try { var node = FS.lookupNode(dir, name); return 20 } catch (e) {} return FS.nodePermissions(dir, "wx") }, mayDelete: function(dir, name, isdir) { var node; try { node = FS.lookupNode(dir, name) } catch (e) { return e.errno } var err = FS.nodePermissions(dir, "wx"); if (err) { return err } if (isdir) { if (!FS.isDir(node.mode)) { return 54 } if (FS.isRoot(node) || FS.getPath(node) === FS.cwd()) { return 10 } } else { if (FS.isDir(node.mode)) { return 31 } } return 0 }, mayOpen: function(node, flags) { if (!node) { return 44 } if (FS.isLink(node.mode)) { return 32 } else if (FS.isDir(node.mode)) { if (FS.flagsToPermissionString(flags) !== "r" || flags & 512) { return 31 } } return FS.nodePermissions(node, FS.flagsToPermissionString(flags)) }, MAX_OPEN_FDS: 4096, nextfd: function(fd_start, fd_end) { fd_start = fd_start || 0; fd_end = fd_end || FS.MAX_OPEN_FDS; for (var fd = fd_start; fd <= fd_end; fd++) { if (!FS.streams[fd]) { return fd } } throw new FS.ErrnoError(33) }, getStream: function(fd) { return FS.streams[fd] }, createStream: function(stream, fd_start, fd_end) { if (!FS.FSStream) { FS.FSStream = function() {}; FS.FSStream.prototype = {}; Object.defineProperties(FS.FSStream.prototype, { object: { get: function() { return this.node }, set: function(val) { this.node = val } }, isRead: { get: function() { return (this.flags & 2097155) !== 1 } }, isWrite: { get: function() { return (this.flags & 2097155) !== 0 } }, isAppend: { get: function() { return this.flags & 1024 } } }) } var newStream = new FS.FSStream; for (var p in stream) { newStream[p] = stream[p] } stream = newStream; var fd = FS.nextfd(fd_start, fd_end); stream.fd = fd; FS.streams[fd] = stream; return stream }, closeStream: function(fd) { FS.streams[fd] = null }, chrdev_stream_ops: { open: function(stream) { var device = FS.getDevice(stream.node.rdev); stream.stream_ops = device.stream_ops; if (stream.stream_ops.open) { stream.stream_ops.open(stream) } }, llseek: function() { throw new FS.ErrnoError(70) } }, major: function(dev) { return dev >> 8 }, minor: function(dev) { return dev & 255 }, makedev: function(ma, mi) { return ma << 8 | mi }, registerDevice: function(dev, ops) { FS.devices[dev] = { stream_ops: ops } }, getDevice: function(dev) { return FS.devices[dev] }, getMounts: function(mount) { var mounts = []; var check = [mount]; while (check.length) { var m = check.pop(); mounts.push(m); check.push.apply(check, m.mounts) } return mounts }, syncfs: function(populate, callback) { if (typeof populate === "function") { callback = populate; populate = false } FS.syncFSRequests++; if (FS.syncFSRequests > 1) { console.log("warning: " + FS.syncFSRequests + " FS.syncfs operations in flight at once, probably just doing extra work") } var mounts = FS.getMounts(FS.root.mount); var completed = 0; function doCallback(err) { assert(FS.syncFSRequests > 0); FS.syncFSRequests--; return callback(err) } function done(err) { if (err) { if (!done.errored) { done.errored = true; return doCallback(err) } return } if (++completed >= mounts.length) { doCallback(null) } } mounts.forEach(function(mount) { if (!mount.type.syncfs) { return done(null) } mount.type.syncfs(mount, populate, done) }) }, mount: function(type, opts, mountpoint) { var root = mountpoint === "/"; var pseudo = !mountpoint; var node; if (root && FS.root) { throw new FS.ErrnoError(10) } else if (!root && !pseudo) { var lookup = FS.lookupPath(mountpoint, { follow_mount: false }); mountpoint = lookup.path; node = lookup.node; if (FS.isMountpoint(node)) { throw new FS.ErrnoError(10) } if (!FS.isDir(node.mode)) { throw new FS.ErrnoError(54) } } var mount = { type: type, opts: opts, mountpoint: mountpoint, mounts: [] }; var mountRoot = type.mount(mount); mountRoot.mount = mount; mount.root = mountRoot; if (root) { FS.root = mountRoot } else if (node) { node.mounted = mount; if (node.mount) { node.mount.mounts.push(mount) } } return mountRoot }, unmount: function(mountpoint) { var lookup = FS.lookupPath(mountpoint, { follow_mount: false }); if (!FS.isMountpoint(lookup.node)) { throw new FS.ErrnoError(28) } var node = lookup.node; var mount = node.mounted; var mounts = FS.getMounts(mount); Object.keys(FS.nameTable).forEach(function(hash) { var current = FS.nameTable[hash]; while (current) { var next = current.name_next; if (mounts.indexOf(current.mount) !== -1) { FS.destroyNode(current) } current = next } }); node.mounted = null; var idx = node.mount.mounts.indexOf(mount); assert(idx !== -1); node.mount.mounts.splice(idx, 1) }, lookup: function(parent, name) { return parent.node_ops.lookup(parent, name) }, mknod: function(path, mode, dev) { var lookup = FS.lookupPath(path, { parent: true }); var parent = lookup.node; var name = PATH.basename(path); if (!name || name === "." || name === "..") { throw new FS.ErrnoError(28) } var err = FS.mayCreate(parent, name); if (err) { throw new FS.ErrnoError(err) } if (!parent.node_ops.mknod) { throw new FS.ErrnoError(63) } return parent.node_ops.mknod(parent, name, mode, dev) }, create: function(path, mode) { mode = mode !== undefined ? mode : 438; mode &= 4095; mode |= 32768; return FS.mknod(path, mode, 0) }, mkdir: function(path, mode) { mode = mode !== undefined ? mode : 511; mode &= 511 | 512; mode |= 16384; return FS.mknod(path, mode, 0) }, mkdirTree: function(path, mode) { var dirs = path.split("/"); var d = ""; for (var i = 0; i < dirs.length; ++i) { if (!dirs[i]) continue; d += "/" + dirs[i]; try { FS.mkdir(d, mode) } catch (e) { if (e.errno != 20) throw e } } }, mkdev: function(path, mode, dev) { if (typeof dev === "undefined") { dev = mode; mode = 438 } mode |= 8192; return FS.mknod(path, mode, dev) }, symlink: function(oldpath, newpath) { if (!PATH_FS.resolve(oldpath)) { throw new FS.ErrnoError(44) } var lookup = FS.lookupPath(newpath, { parent: true }); var parent = lookup.node; if (!parent) { throw new FS.ErrnoError(44) } var newname = PATH.basename(newpath); var err = FS.mayCreate(parent, newname); if (err) { throw new FS.ErrnoError(err) } if (!parent.node_ops.symlink) { throw new FS.ErrnoError(63) } return parent.node_ops.symlink(parent, newname, oldpath) }, rename: function(old_path, new_path) { var old_dirname = PATH.dirname(old_path); var new_dirname = PATH.dirname(new_path); var old_name = PATH.basename(old_path); var new_name = PATH.basename(new_path); var lookup, old_dir, new_dir; try { lookup = FS.lookupPath(old_path, { parent: true }); old_dir = lookup.node; lookup = FS.lookupPath(new_path, { parent: true }); new_dir = lookup.node } catch (e) { throw new FS.ErrnoError(10) } if (!old_dir || !new_dir) throw new FS.ErrnoError(44); if (old_dir.mount !== new_dir.mount) { throw new FS.ErrnoError(75) } var old_node = FS.lookupNode(old_dir, old_name); var relative = PATH_FS.relative(old_path, new_dirname); if (relative.charAt(0) !== ".") { throw new FS.ErrnoError(28) } relative = PATH_FS.relative(new_path, old_dirname); if (relative.charAt(0) !== ".") { throw new FS.ErrnoError(55) } var new_node; try { new_node = FS.lookupNode(new_dir, new_name) } catch (e) {} if (old_node === new_node) { return } var isdir = FS.isDir(old_node.mode); var err = FS.mayDelete(old_dir, old_name, isdir); if (err) { throw new FS.ErrnoError(err) } err = new_node ? FS.mayDelete(new_dir, new_name, isdir) : FS.mayCreate(new_dir, new_name); if (err) { throw new FS.ErrnoError(err) } if (!old_dir.node_ops.rename) { throw new FS.ErrnoError(63) } if (FS.isMountpoint(old_node) || new_node && FS.isMountpoint(new_node)) { throw new FS.ErrnoError(10) } if (new_dir !== old_dir) { err = FS.nodePermissions(old_dir, "w"); if (err) { throw new FS.ErrnoError(err) } } try { if (FS.trackingDelegate["willMovePath"]) { FS.trackingDelegate["willMovePath"](old_path, new_path) } } catch (e) { console.log("FS.trackingDelegate['willMovePath']('" + old_path + "', '" + new_path + "') threw an exception: " + e.message) } FS.hashRemoveNode(old_node); try { old_dir.node_ops.rename(old_node, new_dir, new_name) } catch (e) { throw e } finally { FS.hashAddNode(old_node) } try { if (FS.trackingDelegate["onMovePath"]) FS.trackingDelegate["onMovePath"](old_path, new_path) } catch (e) { console.log("FS.trackingDelegate['onMovePath']('" + old_path + "', '" + new_path + "') threw an exception: " + e.message) } }, rmdir: function(path) { var lookup = FS.lookupPath(path, { parent: true }); var parent = lookup.node; var name = PATH.basename(path); var node = FS.lookupNode(parent, name); var err = FS.mayDelete(parent, name, true); if (err) { throw new FS.ErrnoError(err) } if (!parent.node_ops.rmdir) { throw new FS.ErrnoError(63) } if (FS.isMountpoint(node)) { throw new FS.ErrnoError(10) } try { if (FS.trackingDelegate["willDeletePath"]) { FS.trackingDelegate["willDeletePath"](path) } } catch (e) { console.log("FS.trackingDelegate['willDeletePath']('" + path + "') threw an exception: " + e.message) } parent.node_ops.rmdir(parent, name); FS.destroyNode(node); try { if (FS.trackingDelegate["onDeletePath"]) FS.trackingDelegate["onDeletePath"](path) } catch (e) { console.log("FS.trackingDelegate['onDeletePath']('" + path + "') threw an exception: " + e.message) } }, readdir: function(path) { var lookup = FS.lookupPath(path, { follow: true }); var node = lookup.node; if (!node.node_ops.readdir) { throw new FS.ErrnoError(54) } return node.node_ops.readdir(node) }, unlink: function(path) { var lookup = FS.lookupPath(path, { parent: true }); var parent = lookup.node; var name = PATH.basename(path); var node = FS.lookupNode(parent, name); var err = FS.mayDelete(parent, name, false); if (err) { throw new FS.ErrnoError(err) } if (!parent.node_ops.unlink) { throw new FS.ErrnoError(63) } if (FS.isMountpoint(node)) { throw new FS.ErrnoError(10) } try { if (FS.trackingDelegate["willDeletePath"]) { FS.trackingDelegate["willDeletePath"](path) } } catch (e) { console.log("FS.trackingDelegate['willDeletePath']('" + path + "') threw an exception: " + e.message) } parent.node_ops.unlink(parent, name); FS.destroyNode(node); try { if (FS.trackingDelegate["onDeletePath"]) FS.trackingDelegate["onDeletePath"](path) } catch (e) { console.log("FS.trackingDelegate['onDeletePath']('" + path + "') threw an exception: " + e.message) } }, readlink: function(path) { var lookup = FS.lookupPath(path); var link = lookup.node; if (!link) { throw new FS.ErrnoError(44) } if (!link.node_ops.readlink) { throw new FS.ErrnoError(28) } return PATH_FS.resolve(FS.getPath(link.parent), link.node_ops.readlink(link)) }, stat: function(path, dontFollow) { var lookup = FS.lookupPath(path, { follow: !dontFollow }); var node = lookup.node; if (!node) { throw new FS.ErrnoError(44) } if (!node.node_ops.getattr) { throw new FS.ErrnoError(63) } return node.node_ops.getattr(node) }, lstat: function(path) { return FS.stat(path, true) }, chmod: function(path, mode, dontFollow) { var node; if (typeof path === "string") { var lookup = FS.lookupPath(path, { follow: !dontFollow }); node = lookup.node } else { node = path } if (!node.node_ops.setattr) { throw new FS.ErrnoError(63) } node.node_ops.setattr(node, { mode: mode & 4095 | node.mode & ~4095, timestamp: Date.now() }) }, lchmod: function(path, mode) { FS.chmod(path, mode, true) }, fchmod: function(fd, mode) { var stream = FS.getStream(fd); if (!stream) { throw new FS.ErrnoError(8) } FS.chmod(stream.node, mode) }, chown: function(path, uid, gid, dontFollow) { var node; if (typeof path === "string") { var lookup = FS.lookupPath(path, { follow: !dontFollow }); node = lookup.node } else { node = path } if (!node.node_ops.setattr) { throw new FS.ErrnoError(63) } node.node_ops.setattr(node, { timestamp: Date.now() }) }, lchown: function(path, uid, gid) { FS.chown(path, uid, gid, true) }, fchown: function(fd, uid, gid) { var stream = FS.getStream(fd); if (!stream) { throw new FS.ErrnoError(8) } FS.chown(stream.node, uid, gid) }, truncate: function(path, len) { if (len < 0) { throw new FS.ErrnoError(28) } var node; if (typeof path === "string") { var lookup = FS.lookupPath(path, { follow: true }); node = lookup.node } else { node = path } if (!node.node_ops.setattr) { throw new FS.ErrnoError(63) } if (FS.isDir(node.mode)) { throw new FS.ErrnoError(31) } if (!FS.isFile(node.mode)) { throw new FS.ErrnoError(28) } var err = FS.nodePermissions(node, "w"); if (err) { throw new FS.ErrnoError(err) } node.node_ops.setattr(node, { size: len, timestamp: Date.now() }) }, ftruncate: function(fd, len) { var stream = FS.getStream(fd); if (!stream) { throw new FS.ErrnoError(8) } if ((stream.flags & 2097155) === 0) { throw new FS.ErrnoError(28) } FS.truncate(stream.node, len) }, utime: function(path, atime, mtime) { var lookup = FS.lookupPath(path, { follow: true }); var node = lookup.node; node.node_ops.setattr(node, { timestamp: Math.max(atime, mtime) }) }, open: function(path, flags, mode, fd_start, fd_end) { if (path === "") { throw new FS.ErrnoError(44) } flags = typeof flags === "string" ? FS.modeStringToFlags(flags) : flags; mode = typeof mode === "undefined" ? 438 : mode; if (flags & 64) { mode = mode & 4095 | 32768 } else { mode = 0 } var node; if (typeof path === "object") { node = path } else { path = PATH.normalize(path); try { var lookup = FS.lookupPath(path, { follow: !(flags & 131072) }); node = lookup.node } catch (e) {} } var created = false; if (flags & 64) { if (node) { if (flags & 128) { throw new FS.ErrnoError(20) } } else { node = FS.mknod(path, mode, 0); created = true } } if (!node) { throw new FS.ErrnoError(44) } if (FS.isChrdev(node.mode)) { flags &= ~512 } if (flags & 65536 && !FS.isDir(node.mode)) { throw new FS.ErrnoError(54) } if (!created) { var err = FS.mayOpen(node, flags); if (err) { throw new FS.ErrnoError(err) } } if (flags & 512) { FS.truncate(node, 0) } flags &= ~(128 | 512); var stream = FS.createStream({ node: node, path: FS.getPath(node), flags: flags, seekable: true, position: 0, stream_ops: node.stream_ops, ungotten: [], error: false }, fd_start, fd_end); if (stream.stream_ops.open) { stream.stream_ops.open(stream) } if (Module["logReadFiles"] && !(flags & 1)) { if (!FS.readFiles) FS.readFiles = {}; if (!(path in FS.readFiles)) { FS.readFiles[path] = 1; console.log("FS.trackingDelegate error on read file: " + path) } } try { if (FS.trackingDelegate["onOpenFile"]) { var trackingFlags = 0; if ((flags & 2097155) !== 1) { trackingFlags |= FS.tracking.openFlags.READ } if ((flags & 2097155) !== 0) { trackingFlags |= FS.tracking.openFlags.WRITE } FS.trackingDelegate["onOpenFile"](path, trackingFlags) } } catch (e) { console.log("FS.trackingDelegate['onOpenFile']('" + path + "', flags) threw an exception: " + e.message) } return stream }, close: function(stream) { if (FS.isClosed(stream)) { throw new FS.ErrnoError(8) } if (stream.getdents) stream.getdents = null; try { if (stream.stream_ops.close) { stream.stream_ops.close(stream) } } catch (e) { throw e } finally { FS.closeStream(stream.fd) } stream.fd = null }, isClosed: function(stream) { return stream.fd === null }, llseek: function(stream, offset, whence) { if (FS.isClosed(stream)) { throw new FS.ErrnoError(8) } if (!stream.seekable || !stream.stream_ops.llseek) { throw new FS.ErrnoError(70) } if (whence != 0 && whence != 1 && whence != 2) { throw new FS.ErrnoError(28) } stream.position = stream.stream_ops.llseek(stream, offset, whence); stream.ungotten = []; return stream.position }, read: function(stream, buffer, offset, length, position) { if (length < 0 || position < 0) { throw new FS.ErrnoError(28) } if (FS.isClosed(stream)) { throw new FS.ErrnoError(8) } if ((stream.flags & 2097155) === 1) { throw new FS.ErrnoError(8) } if (FS.isDir(stream.node.mode)) { throw new FS.ErrnoError(31) } if (!stream.stream_ops.read) { throw new FS.ErrnoError(28) } var seeking = typeof position !== "undefined"; if (!seeking) { position = stream.position } else if (!stream.seekable) { throw new FS.ErrnoError(70) } var bytesRead = stream.stream_ops.read(stream, buffer, offset, length, position); if (!seeking) stream.position += bytesRead; return bytesRead }, write: function(stream, buffer, offset, length, position, canOwn) { if (length < 0 || position < 0) { throw new FS.ErrnoError(28) } if (FS.isClosed(stream)) { throw new FS.ErrnoError(8) } if ((stream.flags & 2097155) === 0) { throw new FS.ErrnoError(8) } if (FS.isDir(stream.node.mode)) { throw new FS.ErrnoError(31) } if (!stream.stream_ops.write) { throw new FS.ErrnoError(28) } if (stream.flags & 1024) { FS.llseek(stream, 0, 2) } var seeking = typeof position !== "undefined"; if (!seeking) { position = stream.position } else if (!stream.seekable) { throw new FS.ErrnoError(70) } var bytesWritten = stream.stream_ops.write(stream, buffer, offset, length, position, canOwn); if (!seeking) stream.position += bytesWritten; try { if (stream.path && FS.trackingDelegate["onWriteToFile"]) FS.trackingDelegate["onWriteToFile"](stream.path) } catch (e) { console.log("FS.trackingDelegate['onWriteToFile']('" + stream.path + "') threw an exception: " + e.message) } return bytesWritten }, allocate: function(stream, offset, length) { if (FS.isClosed(stream)) { throw new FS.ErrnoError(8) } if (offset < 0 || length <= 0) { throw new FS.ErrnoError(28) } if ((stream.flags & 2097155) === 0) { throw new FS.ErrnoError(8) } if (!FS.isFile(stream.node.mode) && !FS.isDir(stream.node.mode)) { throw new FS.ErrnoError(43) } if (!stream.stream_ops.allocate) { throw new FS.ErrnoError(138) } stream.stream_ops.allocate(stream, offset, length) }, mmap: function(stream, buffer, offset, length, position, prot, flags) { if ((prot & 2) !== 0 && (flags & 2) === 0 && (stream.flags & 2097155) !== 2) { throw new FS.ErrnoError(2) } if ((stream.flags & 2097155) === 1) { throw new FS.ErrnoError(2) } if (!stream.stream_ops.mmap) { throw new FS.ErrnoError(43) } return stream.stream_ops.mmap(stream, buffer, offset, length, position, prot, flags) }, msync: function(stream, buffer, offset, length, mmapFlags) { if (!stream || !stream.stream_ops.msync) { return 0 } return stream.stream_ops.msync(stream, buffer, offset, length, mmapFlags) }, munmap: function(stream) { return 0 }, ioctl: function(stream, cmd, arg) { if (!stream.stream_ops.ioctl) { throw new FS.ErrnoError(59) } return stream.stream_ops.ioctl(stream, cmd, arg) }, readFile: function(path, opts) { opts = opts || {}; opts.flags = opts.flags || "r"; opts.encoding = opts.encoding || "binary"; if (opts.encoding !== "utf8" && opts.encoding !== "binary") { throw new Error('Invalid encoding type "' + opts.encoding + '"') } var ret; var stream = FS.open(path, opts.flags); var stat = FS.stat(path); var length = stat.size; var buf = new Uint8Array(length); FS.read(stream, buf, 0, length, 0); if (opts.encoding === "utf8") { ret = UTF8ArrayToString(buf, 0) } else if (opts.encoding === "binary") { ret = buf } FS.close(stream); return ret }, writeFile: function(path, data, opts) { opts = opts || {}; opts.flags = opts.flags || "w"; var stream = FS.open(path, opts.flags, opts.mode); if (typeof data === "string") { var buf = new Uint8Array(lengthBytesUTF8(data) + 1); var actualNumBytes = stringToUTF8Array(data, buf, 0, buf.length); FS.write(stream, buf, 0, actualNumBytes, undefined, opts.canOwn) } else if (ArrayBuffer.isView(data)) { FS.write(stream, data, 0, data.byteLength, undefined, opts.canOwn) } else { throw new Error("Unsupported data type") } FS.close(stream) }, cwd: function() { return FS.currentPath }, chdir: function(path) { var lookup = FS.lookupPath(path, { follow: true }); if (lookup.node === null) { throw new FS.ErrnoError(44) } if (!FS.isDir(lookup.node.mode)) { throw new FS.ErrnoError(54) } var err = FS.nodePermissions(lookup.node, "x"); if (err) { throw new FS.ErrnoError(err) } FS.currentPath = lookup.path }, createDefaultDirectories: function() { FS.mkdir("/tmp"); FS.mkdir("/home"); FS.mkdir("/home/web_user") }, createDefaultDevices: function() { FS.mkdir("/dev"); FS.registerDevice(FS.makedev(1, 3), { read: function() { return 0 }, write: function(stream, buffer, offset, length, pos) { return length } }); FS.mkdev("/dev/null", FS.makedev(1, 3)); TTY.register(FS.makedev(5, 0), TTY.default_tty_ops); TTY.register(FS.makedev(6, 0), TTY.default_tty1_ops); FS.mkdev("/dev/tty", FS.makedev(5, 0)); FS.mkdev("/dev/tty1", FS.makedev(6, 0)); var random_device; if (typeof crypto === "object" && typeof crypto["getRandomValues"] === "function") { var randomBuffer = new Uint8Array(1); random_device = function() { crypto.getRandomValues(randomBuffer); return randomBuffer[0] } } else if (ENVIRONMENT_IS_NODE) { try { var crypto_module = require("crypto"); random_device = function() { return crypto_module["randomBytes"](1)[0] } } catch (e) {} } else {} if (!random_device) { random_device = function() { abort("no cryptographic support found for random_device. consider polyfilling it if you want to use something insecure like Math.random(), e.g. put this in a --pre-js: var crypto = { getRandomValues: function(array) { for (var i = 0; i < array.length; i++) array[i] = (Math.random()*256)|0 } };") } } FS.createDevice("/dev", "random", random_device); FS.createDevice("/dev", "urandom", random_device); FS.mkdir("/dev/shm"); FS.mkdir("/dev/shm/tmp") }, createSpecialDirectories: function() { FS.mkdir("/proc"); FS.mkdir("/proc/self"); FS.mkdir("/proc/self/fd"); FS.mount({ mount: function() { var node = FS.createNode("/proc/self", "fd", 16384 | 511, 73); node.node_ops = { lookup: function(parent, name) { var fd = +name; var stream = FS.getStream(fd); if (!stream) throw new FS.ErrnoError(8); var ret = { parent: null, mount: { mountpoint: "fake" }, node_ops: { readlink: function() { return stream.path } } }; ret.parent = ret; return ret } }; return node } }, {}, "/proc/self/fd") }, createStandardStreams: function() { if (Module["stdin"]) { FS.createDevice("/dev", "stdin", Module["stdin"]) } else { FS.symlink("/dev/tty", "/dev/stdin") } if (Module["stdout"]) { FS.createDevice("/dev", "stdout", null, Module["stdout"]) } else { FS.symlink("/dev/tty", "/dev/stdout") } if (Module["stderr"]) { FS.createDevice("/dev", "stderr", null, Module["stderr"]) } else { FS.symlink("/dev/tty1", "/dev/stderr") } var stdin = FS.open("/dev/stdin", "r"); var stdout = FS.open("/dev/stdout", "w"); var stderr = FS.open("/dev/stderr", "w"); assert(stdin.fd === 0, "invalid handle for stdin (" + stdin.fd + ")"); assert(stdout.fd === 1, "invalid handle for stdout (" + stdout.fd + ")"); assert(stderr.fd === 2, "invalid handle for stderr (" + stderr.fd + ")") }, ensureErrnoError: function() { if (FS.ErrnoError) return; FS.ErrnoError = function ErrnoError(errno, node) { this.node = node; this.setErrno = function(errno) { this.errno = errno; for (var key in ERRNO_CODES) { if (ERRNO_CODES[key] === errno) { this.code = key; break } } }; this.setErrno(errno); this.message = ERRNO_MESSAGES[errno]; if (this.stack) { Object.defineProperty(this, "stack", { value: (new Error).stack, writable: true }); this.stack = demangleAll(this.stack) } }; FS.ErrnoError.prototype = new Error; FS.ErrnoError.prototype.constructor = FS.ErrnoError; [44].forEach(function(code) { FS.genericErrors[code] = new FS.ErrnoError(code); FS.genericErrors[code].stack = "" }) }, staticInit: function() { FS.ensureErrnoError(); FS.nameTable = new Array(4096); FS.mount(MEMFS, {}, "/"); FS.createDefaultDirectories(); FS.createDefaultDevices(); FS.createSpecialDirectories(); FS.filesystems = { "MEMFS": MEMFS, "IDBFS": IDBFS, "NODEFS": NODEFS, "WORKERFS": WORKERFS } }, init: function(input, output, error) { assert(!FS.init.initialized, "FS.init was previously called. If you want to initialize later with custom parameters, remove any earlier calls (note that one is automatically added to the generated code)"); FS.init.initialized = true; FS.ensureErrnoError(); Module["stdin"] = input || Module["stdin"]; Module["stdout"] = output || Module["stdout"]; Module["stderr"] = error || Module["stderr"]; FS.createStandardStreams() }, quit: function() { FS.init.initialized = false; var fflush = Module["_fflush"]; if (fflush) fflush(0); for (var i = 0; i < FS.streams.length; i++) { var stream = FS.streams[i]; if (!stream) { continue } FS.close(stream) } }, getMode: function(canRead, canWrite) { var mode = 0; if (canRead) mode |= 292 | 73; if (canWrite) mode |= 146; return mode }, joinPath: function(parts, forceRelative) { var path = PATH.join.apply(null, parts); if (forceRelative && path[0] == "/") path = path.substr(1); return path }, absolutePath: function(relative, base) { return PATH_FS.resolve(base, relative) }, standardizePath: function(path) { return PATH.normalize(path) }, findObject: function(path, dontResolveLastLink) { var ret = FS.analyzePath(path, dontResolveLastLink); if (ret.exists) { return ret.object } else { ___setErrNo(ret.error); return null } }, analyzePath: function(path, dontResolveLastLink) { try { var lookup = FS.lookupPath(path, { follow: !dontResolveLastLink }); path = lookup.path } catch (e) {} var ret = { isRoot: false, exists: false, error: 0, name: null, path: null, object: null, parentExists: false, parentPath: null, parentObject: null }; try { var lookup = FS.lookupPath(path, { parent: true }); ret.parentExists = true; ret.parentPath = lookup.path; ret.parentObject = lookup.node; ret.name = PATH.basename(path); lookup = FS.lookupPath(path, { follow: !dontResolveLastLink }); ret.exists = true; ret.path = lookup.path; ret.object = lookup.node; ret.name = lookup.node.name; ret.isRoot = lookup.path === "/" } catch (e) { ret.error = e.errno } return ret }, createFolder: function(parent, name, canRead, canWrite) { var path = PATH.join2(typeof parent === "string" ? parent : FS.getPath(parent), name); var mode = FS.getMode(canRead, canWrite); return FS.mkdir(path, mode) }, createPath: function(parent, path, canRead, canWrite) { parent = typeof parent === "string" ? parent : FS.getPath(parent); var parts = path.split("/").reverse(); while (parts.length) { var part = parts.pop(); if (!part) continue; var current = PATH.join2(parent, part); try { FS.mkdir(current) } catch (e) {} parent = current } return current }, createFile: function(parent, name, properties, canRead, canWrite) { var path = PATH.join2(typeof parent === "string" ? parent : FS.getPath(parent), name); var mode = FS.getMode(canRead, canWrite); return FS.create(path, mode) }, createDataFile: function(parent, name, data, canRead, canWrite, canOwn) { var path = name ? PATH.join2(typeof parent === "string" ? parent : FS.getPath(parent), name) : parent; var mode = FS.getMode(canRead, canWrite); var node = FS.create(path, mode); if (data) { if (typeof data === "string") { var arr = new Array(data.length); for (var i = 0, len = data.length; i < len; ++i) arr[i] = data.charCodeAt(i); data = arr } FS.chmod(node, mode | 146); var stream = FS.open(node, "w"); FS.write(stream, data, 0, data.length, 0, canOwn); FS.close(stream); FS.chmod(node, mode) } return node }, createDevice: function(parent, name, input, output) { var path = PATH.join2(typeof parent === "string" ? parent : FS.getPath(parent), name); var mode = FS.getMode(!!input, !!output); if (!FS.createDevice.major) FS.createDevice.major = 64; var dev = FS.makedev(FS.createDevice.major++, 0); FS.registerDevice(dev, { open: function(stream) { stream.seekable = false }, close: function(stream) { if (output && output.buffer && output.buffer.length) { output(10) } }, read: function(stream, buffer, offset, length, pos) { var bytesRead = 0; for (var i = 0; i < length; i++) { var result; try { result = input() } catch (e) { throw new FS.ErrnoError(29) } if (result === undefined && bytesRead === 0) { throw new FS.ErrnoError(6) } if (result === null || result === undefined) break; bytesRead++; buffer[offset + i] = result } if (bytesRead) { stream.node.timestamp = Date.now() } return bytesRead }, write: function(stream, buffer, offset, length, pos) { for (var i = 0; i < length; i++) { try { output(buffer[offset + i]) } catch (e) { throw new FS.ErrnoError(29) } } if (length) { stream.node.timestamp = Date.now() } return i } }); return FS.mkdev(path, mode, dev) }, createLink: function(parent, name, target, canRead, canWrite) { var path = PATH.join2(typeof parent === "string" ? parent : FS.getPath(parent), name); return FS.symlink(target, path) }, forceLoadFile: function(obj) { if (obj.isDevice || obj.isFolder || obj.link || obj.contents) return true; var success = true; if (typeof XMLHttpRequest !== "undefined") { throw new Error("Lazy loading should have been performed (contents set) in createLazyFile, but it was not. Lazy loading only works in web workers. Use --embed-file or --preload-file in emcc on the main thread.") } else if (read_) { try { obj.contents = intArrayFromString(read_(obj.url), true); obj.usedBytes = obj.contents.length } catch (e) { success = false } } else { throw new Error("Cannot load without read() or XMLHttpRequest.") } if (!success) ___setErrNo(29); return success }, createLazyFile: function(parent, name, url, canRead, canWrite) { function LazyUint8Array() { this.lengthKnown = false; this.chunks = [] } LazyUint8Array.prototype.get = function LazyUint8Array_get(idx) { if (idx > this.length - 1 || idx < 0) { return undefined } var chunkOffset = idx % this.chunkSize; var chunkNum = idx / this.chunkSize | 0; return this.getter(chunkNum)[chunkOffset] }; LazyUint8Array.prototype.setDataGetter = function LazyUint8Array_setDataGetter(getter) { this.getter = getter }; LazyUint8Array.prototype.cacheLength = function LazyUint8Array_cacheLength() { var xhr = new XMLHttpRequest; xhr.open("HEAD", url, false); xhr.send(null); if (!(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304)) throw new Error("Couldn't load " + url + ". Status: " + xhr.status); var datalength = Number(xhr.getResponseHeader("Content-length")); var header; var hasByteServing = (header = xhr.getResponseHeader("Accept-Ranges")) && header === "bytes"; var usesGzip = (header = xhr.getResponseHeader("Content-Encoding")) && header === "gzip"; var chunkSize = 1024 * 1024; if (!hasByteServing) chunkSize = datalength; var doXHR = function(from, to) { if (from > to) throw new Error("invalid range (" + from + ", " + to + ") or no bytes requested!"); if (to > datalength - 1) throw new Error("only " + datalength + " bytes available! programmer error!"); var xhr = new XMLHttpRequest; xhr.open("GET", url, false); if (datalength !== chunkSize) xhr.setRequestHeader("Range", "bytes=" + from + "-" + to); if (typeof Uint8Array != "undefined") xhr.responseType = "arraybuffer"; if (xhr.overrideMimeType) { xhr.overrideMimeType("text/plain; charset=x-user-defined") } xhr.send(null); if (!(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304)) throw new Error("Couldn't load " + url + ". Status: " + xhr.status); if (xhr.response !== undefined) { return new Uint8Array(xhr.response || []) } else { return intArrayFromString(xhr.responseText || "", true) } }; var lazyArray = this; lazyArray.setDataGetter(function(chunkNum) { var start = chunkNum * chunkSize; var end = (chunkNum + 1) * chunkSize - 1; end = Math.min(end, datalength - 1); if (typeof lazyArray.chunks[chunkNum] === "undefined") { lazyArray.chunks[chunkNum] = doXHR(start, end) } if (typeof lazyArray.chunks[chunkNum] === "undefined") throw new Error("doXHR failed!"); return lazyArray.chunks[chunkNum] }); if (usesGzip || !datalength) { chunkSize = datalength = 1; datalength = this.getter(0).length; chunkSize = datalength; console.log("LazyFiles on gzip forces download of the whole file when length is accessed") } this._length = datalength; this._chunkSize = chunkSize; this.lengthKnown = true }; if (typeof XMLHttpRequest !== "undefined") { if (!ENVIRONMENT_IS_WORKER) throw "Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc"; var lazyArray = new LazyUint8Array; Object.defineProperties(lazyArray, { length: { get: function() { if (!this.lengthKnown) { this.cacheLength() } return this._length } }, chunkSize: { get: function() { if (!this.lengthKnown) { this.cacheLength() } return this._chunkSize } } }); var properties = { isDevice: false, contents: lazyArray } } else { var properties = { isDevice: false, url: url } } var node = FS.createFile(parent, name, properties, canRead, canWrite); if (properties.contents) { node.contents = properties.contents } else if (properties.url) { node.contents = null; node.url = properties.url } Object.defineProperties(node, { usedBytes: { get: function() { return this.contents.length } } }); var stream_ops = {}; var keys = Object.keys(node.stream_ops); keys.forEach(function(key) { var fn = node.stream_ops[key]; stream_ops[key] = function forceLoadLazyFile() { if (!FS.forceLoadFile(node)) { throw new FS.ErrnoError(29) } return fn.apply(null, arguments) } }); stream_ops.read = function stream_ops_read(stream, buffer, offset, length, position) { if (!FS.forceLoadFile(node)) { throw new FS.ErrnoError(29) } var contents = stream.node.contents; if (position >= contents.length) return 0; var size = Math.min(contents.length - position, length); assert(size >= 0); if (contents.slice) { for (var i = 0; i < size; i++) { buffer[offset + i] = contents[position + i] } } else { for (var i = 0; i < size; i++) { buffer[offset + i] = contents.get(position + i) } } return size }; node.stream_ops = stream_ops; return node }, createPreloadedFile: function(parent, name, url, canRead, canWrite, onload, onerror, dontCreateFile, canOwn, preFinish) { Browser.init(); var fullname = name ? PATH_FS.resolve(PATH.join2(parent, name)) : parent; var dep = getUniqueRunDependency("cp " + fullname); function processData(byteArray) { function finish(byteArray) { if (preFinish) preFinish(); if (!dontCreateFile) { FS.createDataFile(parent, name, byteArray, canRead, canWrite, canOwn) } if (onload) onload(); removeRunDependency(dep) } var handled = false; Module["preloadPlugins"].forEach(function(plugin) { if (handled) return; if (plugin["canHandle"](fullname)) { plugin["handle"](byteArray, fullname, finish, function() { if (onerror) onerror(); removeRunDependency(dep) }); handled = true } }); if (!handled) finish(byteArray) } addRunDependency(dep); if (typeof url == "string") { Browser.asyncLoad(url, function(byteArray) { processData(byteArray) }, onerror) } else { processData(url) } }, indexedDB: function() { return window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB }, DB_NAME: function() { return "EM_FS_" + window.location.pathname }, DB_VERSION: 20, DB_STORE_NAME: "FILE_DATA", saveFilesToDB: function(paths, onload, onerror) { onload = onload || function() {}; onerror = onerror || function() {}; var indexedDB = FS.indexedDB(); try { var openRequest = indexedDB.open(FS.DB_NAME(), FS.DB_VERSION) } catch (e) { return onerror(e) } openRequest.onupgradeneeded = function openRequest_onupgradeneeded() { console.log("creating db"); var db = openRequest.result; db.createObjectStore(FS.DB_STORE_NAME) }; openRequest.onsuccess = function openRequest_onsuccess() { var db = openRequest.result; var transaction = db.transaction([FS.DB_STORE_NAME], "readwrite"); var files = transaction.objectStore(FS.DB_STORE_NAME); var ok = 0, fail = 0, total = paths.length; function finish() { if (fail == 0) onload(); else onerror() } paths.forEach(function(path) { var putRequest = files.put(FS.analyzePath(path).object.contents, path); putRequest.onsuccess = function putRequest_onsuccess() { ok++; if (ok + fail == total) finish() }; putRequest.onerror = function putRequest_onerror() { fail++; if (ok + fail == total) finish() } }); transaction.onerror = onerror }; openRequest.onerror = onerror }, loadFilesFromDB: function(paths, onload, onerror) { onload = onload || function() {}; onerror = onerror || function() {}; var indexedDB = FS.indexedDB(); try { var openRequest = indexedDB.open(FS.DB_NAME(), FS.DB_VERSION) } catch (e) { return onerror(e) } openRequest.onupgradeneeded = onerror; openRequest.onsuccess = function openRequest_onsuccess() { var db = openRequest.result; try { var transaction = db.transaction([FS.DB_STORE_NAME], "readonly") } catch (e) { onerror(e); return } var files = transaction.objectStore(FS.DB_STORE_NAME); var ok = 0, fail = 0, total = paths.length; function finish() { if (fail == 0) onload(); else onerror() } paths.forEach(function(path) { var getRequest = files.get(path); getRequest.onsuccess = function getRequest_onsuccess() { if (FS.analyzePath(path).exists) { FS.unlink(path) } FS.createDataFile(PATH.dirname(path), PATH.basename(path), getRequest.result, true, true, true); ok++; if (ok + fail == total) finish() }; getRequest.onerror = function getRequest_onerror() { fail++; if (ok + fail == total) finish() } }); transaction.onerror = onerror }; openRequest.onerror = onerror } }; var SYSCALLS = { DEFAULT_POLLMASK: 5, mappings: {}, umask: 511, calculateAt: function(dirfd, path) { if (path[0] !== "/") { var dir; if (dirfd === -100) { dir = FS.cwd() } else { var dirstream = FS.getStream(dirfd); if (!dirstream) throw new FS.ErrnoError(8); dir = dirstream.path } path = PATH.join2(dir, path) } return path }, doStat: function(func, path, buf) { try { var stat = func(path) } catch (e) { if (e && e.node && PATH.normalize(path) !== PATH.normalize(FS.getPath(e.node))) { return -54 } throw e } HEAP32[buf >> 2] = stat.dev; HEAP32[buf + 4 >> 2] = 0; HEAP32[buf + 8 >> 2] = stat.ino; HEAP32[buf + 12 >> 2] = stat.mode; HEAP32[buf + 16 >> 2] = stat.nlink; HEAP32[buf + 20 >> 2] = stat.uid; HEAP32[buf + 24 >> 2] = stat.gid; HEAP32[buf + 28 >> 2] = stat.rdev; HEAP32[buf + 32 >> 2] = 0; tempI64 = [stat.size >>> 0, (tempDouble = stat.size, +Math_abs(tempDouble) >= 1 ? tempDouble > 0 ? (Math_min(+Math_floor(tempDouble / 4294967296), 4294967295) | 0) >>> 0 : ~~+Math_ceil((tempDouble - +(~~tempDouble >>> 0)) / 4294967296) >>> 0 : 0)], HEAP32[buf + 40 >> 2] = tempI64[0], HEAP32[buf + 44 >> 2] = tempI64[1]; HEAP32[buf + 48 >> 2] = 4096; HEAP32[buf + 52 >> 2] = stat.blocks; HEAP32[buf + 56 >> 2] = stat.atime.getTime() / 1e3 | 0; HEAP32[buf + 60 >> 2] = 0; HEAP32[buf + 64 >> 2] = stat.mtime.getTime() / 1e3 | 0; HEAP32[buf + 68 >> 2] = 0; HEAP32[buf + 72 >> 2] = stat.ctime.getTime() / 1e3 | 0; HEAP32[buf + 76 >> 2] = 0; tempI64 = [stat.ino >>> 0, (tempDouble = stat.ino, +Math_abs(tempDouble) >= 1 ? tempDouble > 0 ? (Math_min(+Math_floor(tempDouble / 4294967296), 4294967295) | 0) >>> 0 : ~~+Math_ceil((tempDouble - +(~~tempDouble >>> 0)) / 4294967296) >>> 0 : 0)], HEAP32[buf + 80 >> 2] = tempI64[0], HEAP32[buf + 84 >> 2] = tempI64[1]; return 0 }, doMsync: function(addr, stream, len, flags) { var buffer = new Uint8Array(HEAPU8.subarray(addr, addr + len)); FS.msync(stream, buffer, 0, len, flags) }, doMkdir: function(path, mode) { path = PATH.normalize(path); if (path[path.length - 1] === "/") path = path.substr(0, path.length - 1); FS.mkdir(path, mode, 0); return 0 }, doMknod: function(path, mode, dev) { switch (mode & 61440) { case 32768: case 8192: case 24576: case 4096: case 49152: break; default: return -28 } FS.mknod(path, mode, dev); return 0 }, doReadlink: function(path, buf, bufsize) { if (bufsize <= 0) return -28; var ret = FS.readlink(path); var len = Math.min(bufsize, lengthBytesUTF8(ret)); var endChar = HEAP8[buf + len]; stringToUTF8(ret, buf, bufsize + 1); HEAP8[buf + len] = endChar; return len }, doAccess: function(path, amode) { if (amode & ~7) { return -28 } var node; var lookup = FS.lookupPath(path, { follow: true }); node = lookup.node; if (!node) { return -44 } var perms = ""; if (amode & 4) perms += "r"; if (amode & 2) perms += "w"; if (amode & 1) perms += "x"; if (perms && FS.nodePermissions(node, perms)) { return -2 } return 0 }, doDup: function(path, flags, suggestFD) { var suggest = FS.getStream(suggestFD); if (suggest) FS.close(suggest); return FS.open(path, flags, 0, suggestFD, suggestFD).fd }, doReadv: function(stream, iov, iovcnt, offset) { var ret = 0; for (var i = 0; i < iovcnt; i++) { var ptr = HEAP32[iov + i * 8 >> 2]; var len = HEAP32[iov + (i * 8 + 4) >> 2]; var curr = FS.read(stream, HEAP8, ptr, len, offset); if (curr < 0) return -1; ret += curr; if (curr < len) break } return ret }, doWritev: function(stream, iov, iovcnt, offset) { var ret = 0; for (var i = 0; i < iovcnt; i++) { var ptr = HEAP32[iov + i * 8 >> 2]; var len = HEAP32[iov + (i * 8 + 4) >> 2]; var curr = FS.write(stream, HEAP8, ptr, len, offset); if (curr < 0) return -1; ret += curr } return ret }, varargs: 0, get: function(varargs) { SYSCALLS.varargs += 4; var ret = HEAP32[SYSCALLS.varargs - 4 >> 2]; return ret }, getStr: function() { var ret = UTF8ToString(SYSCALLS.get()); return ret }, getStreamFromFD: function(fd) { if (fd === undefined) fd = SYSCALLS.get(); var stream = FS.getStream(fd); if (!stream) throw new FS.ErrnoError(8); return stream }, get64: function() { var low = SYSCALLS.get(), high = SYSCALLS.get(); if (low >= 0) assert(high === 0); else assert(high === -1); return low }, getZero: function() { assert(SYSCALLS.get() === 0) } }; function ___syscall221(which, varargs) { SYSCALLS.varargs = varargs; try { var stream = SYSCALLS.getStreamFromFD(), cmd = SYSCALLS.get(); switch (cmd) { case 0: { var arg = SYSCALLS.get(); if (arg < 0) { return -28 } var newStream; newStream = FS.open(stream.path, stream.flags, 0, arg); return newStream.fd } case 1: case 2: return 0; case 3: return stream.flags; case 4: { var arg = SYSCALLS.get(); stream.flags |= arg; return 0 } case 12: { var arg = SYSCALLS.get(); var offset = 0; HEAP16[arg + offset >> 1] = 2; return 0 } case 13: case 14: return 0; case 16: case 8: return -28; case 9: ___setErrNo(28); return -1; default: { return -28 } } } catch (e) { if (typeof FS === "undefined" || !(e instanceof FS.ErrnoError)) abort(e); return -e.errno } } function ___syscall3(which, varargs) { SYSCALLS.varargs = varargs; try { var stream = SYSCALLS.getStreamFromFD(), buf = SYSCALLS.get(), count = SYSCALLS.get(); return FS.read(stream, HEAP8, buf, count) } catch (e) { if (typeof FS === "undefined" || !(e instanceof FS.ErrnoError)) abort(e); return -e.errno } } function ___syscall5(which, varargs) { SYSCALLS.varargs = varargs; try { var pathname = SYSCALLS.getStr(), flags = SYSCALLS.get(), mode = SYSCALLS.get(); var stream = FS.open(pathname, flags, mode); return stream.fd } catch (e) { if (typeof FS === "undefined" || !(e instanceof FS.ErrnoError)) abort(e); return -e.errno } } function ___unlock() {} function _fd_close(fd) { try { var stream = SYSCALLS.getStreamFromFD(fd); FS.close(stream); return 0 } catch (e) { if (typeof FS === "undefined" || !(e instanceof FS.ErrnoError)) abort(e); return e.errno } } function ___wasi_fd_close() { return _fd_close.apply(null, arguments) } function _fd_fdstat_get(fd, pbuf) { try { var stream = SYSCALLS.getStreamFromFD(fd); var type = stream.tty ? 2 : FS.isDir(stream.mode) ? 3 : FS.isLink(stream.mode) ? 7 : 4; HEAP8[pbuf >> 0] = type; return 0 } catch (e) { if (typeof FS === "undefined" || !(e instanceof FS.ErrnoError)) abort(e); return e.errno } } function ___wasi_fd_fdstat_get() { return _fd_fdstat_get.apply(null, arguments) } function _fd_seek(fd, offset_low, offset_high, whence, newOffset) { try { var stream = SYSCALLS.getStreamFromFD(fd); var HIGH_OFFSET = 4294967296; var offset = offset_high * HIGH_OFFSET + (offset_low >>> 0); var DOUBLE_LIMIT = 9007199254740992; if (offset <= -DOUBLE_LIMIT || offset >= DOUBLE_LIMIT) { return -61 } FS.llseek(stream, offset, whence); tempI64 = [stream.position >>> 0, (tempDouble = stream.position, +Math_abs(tempDouble) >= 1 ? tempDouble > 0 ? (Math_min(+Math_floor(tempDouble / 4294967296), 4294967295) | 0) >>> 0 : ~~+Math_ceil((tempDouble - +(~~tempDouble >>> 0)) / 4294967296) >>> 0 : 0)], HEAP32[newOffset >> 2] = tempI64[0], HEAP32[newOffset + 4 >> 2] = tempI64[1]; if (stream.getdents && offset === 0 && whence === 0) stream.getdents = null; return 0 } catch (e) { if (typeof FS === "undefined" || !(e instanceof FS.ErrnoError)) abort(e); return e.errno } } function ___wasi_fd_seek() { return _fd_seek.apply(null, arguments) } function _fd_write(fd, iov, iovcnt, pnum) { try { var stream = SYSCALLS.getStreamFromFD(fd); var num = SYSCALLS.doWritev(stream, iov, iovcnt); HEAP32[pnum >> 2] = num; return 0 } catch (e) { if (typeof FS === "undefined" || !(e instanceof FS.ErrnoError)) abort(e); return e.errno } } function ___wasi_fd_write() { return _fd_write.apply(null, arguments) } function __emscripten_fetch_free(id) { delete Fetch.xhrs[id - 1] } function _abort() { abort() } function _clock() { if (_clock.start === undefined) _clock.start = Date.now(); return (Date.now() - _clock.start) * (1e6 / 1e3) | 0 } function _emscripten_get_now() { abort() } function _emscripten_get_now_is_monotonic() { return 0 || ENVIRONMENT_IS_NODE || typeof dateNow !== "undefined" || typeof performance === "object" && performance && typeof performance["now"] === "function" } function _clock_gettime(clk_id, tp) { var now; if (clk_id === 0) { now = Date.now() } else if (clk_id === 1 && _emscripten_get_now_is_monotonic()) { now = _emscripten_get_now() } else { ___setErrNo(28); return -1 } HEAP32[tp >> 2] = now / 1e3 | 0; HEAP32[tp + 4 >> 2] = now % 1e3 * 1e3 * 1e3 | 0; return 0 } function _emscripten_get_heap_size() { return HEAP8.length } function _emscripten_is_main_browser_thread() { return !ENVIRONMENT_IS_WORKER } function abortOnCannotGrowMemory(requestedSize) { abort("Cannot enlarge memory arrays to size " + requestedSize + " bytes (OOM). Either (1) compile with -s TOTAL_MEMORY=X with X higher than the current value " + HEAP8.length + ", (2) compile with -s ALLOW_MEMORY_GROWTH=1 which allows increasing the size at runtime, or (3) if you want malloc to return NULL (0) instead of this abort, compile with -s ABORTING_MALLOC=0 ") } function _emscripten_resize_heap(requestedSize) { abortOnCannotGrowMemory(requestedSize) } var Fetch = { xhrs: [], setu64: function(addr, val) { HEAPU32[addr >> 2] = val; HEAPU32[addr + 4 >> 2] = val / 4294967296 | 0 }, openDatabase: function(dbname, dbversion, onsuccess, onerror) { try { var openRequest = indexedDB.open(dbname, dbversion) } catch (e) { return onerror(e) } openRequest.onupgradeneeded = function(event) { var db = event.target.result; if (db.objectStoreNames.contains("FILES")) { db.deleteObjectStore("FILES") } db.createObjectStore("FILES") }; openRequest.onsuccess = function(event) { onsuccess(event.target.result) }; openRequest.onerror = function(error) { onerror(error) } }, staticInit: function() { var isMainThread = typeof ENVIRONMENT_IS_FETCH_WORKER === "undefined"; var onsuccess = function(db) { Fetch.dbInstance = db; if (isMainThread) { removeRunDependency("library_fetch_init") } }; var onerror = function() { Fetch.dbInstance = false; if (isMainThread) { removeRunDependency("library_fetch_init") } }; Fetch.openDatabase("emscripten_filesystem", 1, onsuccess, onerror); if (typeof ENVIRONMENT_IS_FETCH_WORKER === "undefined" || !ENVIRONMENT_IS_FETCH_WORKER) addRunDependency("library_fetch_init") } }; function __emscripten_fetch_xhr(fetch, onsuccess, onerror, onprogress, onreadystatechange) { var url = HEAPU32[fetch + 8 >> 2]; if (!url) { onerror(fetch, 0, "no url specified!"); return } var url_ = UTF8ToString(url); var fetch_attr = fetch + 112; var requestMethod = UTF8ToString(fetch_attr); if (!requestMethod) requestMethod = "GET"; var userData = HEAPU32[fetch_attr + 32 >> 2]; var fetchAttributes = HEAPU32[fetch_attr + 52 >> 2]; var timeoutMsecs = HEAPU32[fetch_attr + 56 >> 2]; var withCredentials = !!HEAPU32[fetch_attr + 60 >> 2]; var destinationPath = HEAPU32[fetch_attr + 64 >> 2]; var userName = HEAPU32[fetch_attr + 68 >> 2]; var password = HEAPU32[fetch_attr + 72 >> 2]; var requestHeaders = HEAPU32[fetch_attr + 76 >> 2]; var overriddenMimeType = HEAPU32[fetch_attr + 80 >> 2]; var dataPtr = HEAPU32[fetch_attr + 84 >> 2]; var dataLength = HEAPU32[fetch_attr + 88 >> 2]; var fetchAttrLoadToMemory = !!(fetchAttributes & 1); var fetchAttrStreamData = !!(fetchAttributes & 2); var fetchAttrPersistFile = !!(fetchAttributes & 4); var fetchAttrAppend = !!(fetchAttributes & 8); var fetchAttrReplace = !!(fetchAttributes & 16); var fetchAttrSynchronous = !!(fetchAttributes & 64); var fetchAttrWaitable = !!(fetchAttributes & 128); var userNameStr = userName ? UTF8ToString(userName) : undefined; var passwordStr = password ? UTF8ToString(password) : undefined; var overriddenMimeTypeStr = overriddenMimeType ? UTF8ToString(overriddenMimeType) : undefined; var xhr = new XMLHttpRequest; xhr.withCredentials = withCredentials; xhr.open(requestMethod, url_, !fetchAttrSynchronous, userNameStr, passwordStr); if (!fetchAttrSynchronous) xhr.timeout = timeoutMsecs; xhr.url_ = url_; assert(!fetchAttrStreamData, "streaming uses moz-chunked-arraybuffer which is no longer supported; TODO: rewrite using fetch()"); xhr.responseType = "arraybuffer"; if (overriddenMimeType) { xhr.overrideMimeType(overriddenMimeTypeStr) } if (requestHeaders) { for (;;) { var key = HEAPU32[requestHeaders >> 2]; if (!key) break; var value = HEAPU32[requestHeaders + 4 >> 2]; if (!value) break; requestHeaders += 8; var keyStr = UTF8ToString(key); var valueStr = UTF8ToString(value); xhr.setRequestHeader(keyStr, valueStr) } } Fetch.xhrs.push(xhr); var id = Fetch.xhrs.length; HEAPU32[fetch + 0 >> 2] = id; var data = dataPtr && dataLength ? HEAPU8.slice(dataPtr, dataPtr + dataLength) : null; xhr.onload = function(e) { var len = xhr.response ? xhr.response.byteLength : 0; var ptr = 0; var ptrLen = 0; if (fetchAttrLoadToMemory && !fetchAttrStreamData) { ptrLen = len; ptr = _malloc(ptrLen); HEAPU8.set(new Uint8Array(xhr.response), ptr) } HEAPU32[fetch + 12 >> 2] = ptr; Fetch.setu64(fetch + 16, ptrLen); Fetch.setu64(fetch + 24, 0); if (len) { Fetch.setu64(fetch + 32, len) } HEAPU16[fetch + 40 >> 1] = xhr.readyState; if (xhr.readyState === 4 && xhr.status === 0) { if (len > 0) xhr.status = 200; else xhr.status = 404 } HEAPU16[fetch + 42 >> 1] = xhr.status; if (xhr.statusText) stringToUTF8(xhr.statusText, fetch + 44, 64); if (xhr.status >= 200 && xhr.status < 300) { if (onsuccess) onsuccess(fetch, xhr, e) } else { if (onerror) onerror(fetch, xhr, e) } }; xhr.onerror = function(e) { var status = xhr.status; if (xhr.readyState === 4 && status === 0) status = 404; HEAPU32[fetch + 12 >> 2] = 0; Fetch.setu64(fetch + 16, 0); Fetch.setu64(fetch + 24, 0); Fetch.setu64(fetch + 32, 0); HEAPU16[fetch + 40 >> 1] = xhr.readyState; HEAPU16[fetch + 42 >> 1] = status; if (onerror) onerror(fetch, xhr, e) }; xhr.ontimeout = function(e) { if (onerror) onerror(fetch, xhr, e) }; xhr.onprogress = function(e) { var ptrLen = fetchAttrLoadToMemory && fetchAttrStreamData && xhr.response ? xhr.response.byteLength : 0; var ptr = 0; if (fetchAttrLoadToMemory && fetchAttrStreamData) { ptr = _malloc(ptrLen); HEAPU8.set(new Uint8Array(xhr.response), ptr) } HEAPU32[fetch + 12 >> 2] = ptr; Fetch.setu64(fetch + 16, ptrLen); Fetch.setu64(fetch + 24, e.loaded - ptrLen); Fetch.setu64(fetch + 32, e.total); HEAPU16[fetch + 40 >> 1] = xhr.readyState; if (xhr.readyState >= 3 && xhr.status === 0 && e.loaded > 0) xhr.status = 200; HEAPU16[fetch + 42 >> 1] = xhr.status; if (xhr.statusText) stringToUTF8(xhr.statusText, fetch + 44, 64); if (onprogress) onprogress(fetch, xhr, e) }; xhr.onreadystatechange = function(e) { HEAPU16[fetch + 40 >> 1] = xhr.readyState; if (xhr.readyState >= 2) { HEAPU16[fetch + 42 >> 1] = xhr.status } if (onreadystatechange) onreadystatechange(fetch, xhr, e) }; try { xhr.send(data) } catch (e) { if (onerror) onerror(fetch, xhr, e) } } function __emscripten_fetch_cache_data(db, fetch, data, onsuccess, onerror) { if (!db) { onerror(fetch, 0, "IndexedDB not available!"); return } var fetch_attr = fetch + 112; var destinationPath = HEAPU32[fetch_attr + 64 >> 2]; if (!destinationPath) destinationPath = HEAPU32[fetch + 8 >> 2]; var destinationPathStr = UTF8ToString(destinationPath); try { var transaction = db.transaction(["FILES"], "readwrite"); var packages = transaction.objectStore("FILES"); var putRequest = packages.put(data, destinationPathStr); putRequest.onsuccess = function(event) { HEAPU16[fetch + 40 >> 1] = 4; HEAPU16[fetch + 42 >> 1] = 200; stringToUTF8("OK", fetch + 44, 64); onsuccess(fetch, 0, destinationPathStr) }; putRequest.onerror = function(error) { HEAPU16[fetch + 40 >> 1] = 4; HEAPU16[fetch + 42 >> 1] = 413; stringToUTF8("Payload Too Large", fetch + 44, 64); onerror(fetch, 0, error) } } catch (e) { onerror(fetch, 0, e) } } function __emscripten_fetch_load_cached_data(db, fetch, onsuccess, onerror) { if (!db) { onerror(fetch, 0, "IndexedDB not available!"); return } var fetch_attr = fetch + 112; var path = HEAPU32[fetch_attr + 64 >> 2]; if (!path) path = HEAPU32[fetch + 8 >> 2]; var pathStr = UTF8ToString(path); try { var transaction = db.transaction(["FILES"], "readonly"); var packages = transaction.objectStore("FILES"); var getRequest = packages.get(pathStr); getRequest.onsuccess = function(event) { if (event.target.result) { var value = event.target.result; var len = value.byteLength || value.length; var ptr = _malloc(len); HEAPU8.set(new Uint8Array(value), ptr); HEAPU32[fetch + 12 >> 2] = ptr; Fetch.setu64(fetch + 16, len); Fetch.setu64(fetch + 24, 0); Fetch.setu64(fetch + 32, len); HEAPU16[fetch + 40 >> 1] = 4; HEAPU16[fetch + 42 >> 1] = 200; stringToUTF8("OK", fetch + 44, 64); onsuccess(fetch, 0, value) } else { HEAPU16[fetch + 40 >> 1] = 4; HEAPU16[fetch + 42 >> 1] = 404; stringToUTF8("Not Found", fetch + 44, 64); onerror(fetch, 0, "no data") } }; getRequest.onerror = function(error) { HEAPU16[fetch + 40 >> 1] = 4; HEAPU16[fetch + 42 >> 1] = 404; stringToUTF8("Not Found", fetch + 44, 64); onerror(fetch, 0, error) } } catch (e) { onerror(fetch, 0, e) } } function __emscripten_fetch_delete_cached_data(db, fetch, onsuccess, onerror) { if (!db) { onerror(fetch, 0, "IndexedDB not available!"); return } var fetch_attr = fetch + 112; var path = HEAPU32[fetch_attr + 64 >> 2]; if (!path) path = HEAPU32[fetch + 8 >> 2]; var pathStr = UTF8ToString(path); try { var transaction = db.transaction(["FILES"], "readwrite"); var packages = transaction.objectStore("FILES"); var request = packages.delete(pathStr); request.onsuccess = function(event) { var value = event.target.result; HEAPU32[fetch + 12 >> 2] = 0; Fetch.setu64(fetch + 16, 0); Fetch.setu64(fetch + 24, 0); Fetch.setu64(fetch + 32, 0); HEAPU16[fetch + 40 >> 1] = 4; HEAPU16[fetch + 42 >> 1] = 200; stringToUTF8("OK", fetch + 44, 64); onsuccess(fetch, 0, value) }; request.onerror = function(error) { HEAPU16[fetch + 40 >> 1] = 4; HEAPU16[fetch + 42 >> 1] = 404; stringToUTF8("Not Found", fetch + 44, 64); onerror(fetch, 0, error) } } catch (e) { onerror(fetch, 0, e) } } function _emscripten_start_fetch(fetch, successcb, errorcb, progresscb, readystatechangecb) { if (typeof noExitRuntime !== "undefined") noExitRuntime = true; var fetch_attr = fetch + 112; var requestMethod = UTF8ToString(fetch_attr); var onsuccess = HEAPU32[fetch_attr + 36 >> 2]; var onerror = HEAPU32[fetch_attr + 40 >> 2]; var onprogress = HEAPU32[fetch_attr + 44 >> 2]; var onreadystatechange = HEAPU32[fetch_attr + 48 >> 2]; var fetchAttributes = HEAPU32[fetch_attr + 52 >> 2]; var fetchAttrLoadToMemory = !!(fetchAttributes & 1); var fetchAttrStreamData = !!(fetchAttributes & 2); var fetchAttrPersistFile = !!(fetchAttributes & 4); var fetchAttrNoDownload = !!(fetchAttributes & 32); var fetchAttrAppend = !!(fetchAttributes & 8); var fetchAttrReplace = !!(fetchAttributes & 16); var reportSuccess = function(fetch, xhr, e) { if (onsuccess) dynCall_vi(onsuccess, fetch); else if (successcb) successcb(fetch) }; var reportProgress = function(fetch, xhr, e) { if (onprogress) dynCall_vi(onprogress, fetch); else if (progresscb) progresscb(fetch) }; var reportError = function(fetch, xhr, e) { if (onerror) dynCall_vi(onerror, fetch); else if (errorcb) errorcb(fetch) }; var reportReadyStateChange = function(fetch, xhr, e) { if (onreadystatechange) dynCall_vi(onreadystatechange, fetch); else if (readystatechangecb) readystatechangecb(fetch) }; var performUncachedXhr = function(fetch, xhr, e) { __emscripten_fetch_xhr(fetch, reportSuccess, reportError, reportProgress, reportReadyStateChange) }; var cacheResultAndReportSuccess = function(fetch, xhr, e) { var storeSuccess = function(fetch, xhr, e) { if (onsuccess) dynCall_vi(onsuccess, fetch); else if (successcb) successcb(fetch) }; var storeError = function(fetch, xhr, e) { if (onsuccess) dynCall_vi(onsuccess, fetch); else if (successcb) successcb(fetch) }; __emscripten_fetch_cache_data(Fetch.dbInstance, fetch, xhr.response, storeSuccess, storeError) }; var performCachedXhr = function(fetch, xhr, e) { __emscripten_fetch_xhr(fetch, cacheResultAndReportSuccess, reportError, reportProgress, reportReadyStateChange) }; if (requestMethod === "EM_IDB_STORE") { var ptr = HEAPU32[fetch_attr + 84 >> 2]; __emscripten_fetch_cache_data(Fetch.dbInstance, fetch, HEAPU8.slice(ptr, ptr + HEAPU32[fetch_attr + 88 >> 2]), reportSuccess, reportError) } else if (requestMethod === "EM_IDB_DELETE") { __emscripten_fetch_delete_cached_data(Fetch.dbInstance, fetch, reportSuccess, reportError) } else if (!fetchAttrReplace) { __emscripten_fetch_load_cached_data(Fetch.dbInstance, fetch, reportSuccess, fetchAttrNoDownload ? reportError : fetchAttrPersistFile ? performCachedXhr : performUncachedXhr) } else if (!fetchAttrNoDownload) { __emscripten_fetch_xhr(fetch, fetchAttrPersistFile ? cacheResultAndReportSuccess : reportSuccess, reportError, reportProgress, reportReadyStateChange) } else { return 0 } return fetch } var _fabs = Math_abs; function _getenv(name) { if (name === 0) return 0; name = UTF8ToString(name); if (!ENV.hasOwnProperty(name)) return 0; if (_getenv.ret) _free(_getenv.ret); _getenv.ret = allocateUTF8(ENV[name]); return _getenv.ret } function _gettimeofday(ptr) { var now = Date.now(); HEAP32[ptr >> 2] = now / 1e3 | 0; HEAP32[ptr + 4 >> 2] = now % 1e3 * 1e3 | 0; return 0 } var ___tm_timezone = (stringToUTF8("GMT", 1398096, 4), 1398096); function _gmtime_r(time, tmPtr) { var date = new Date(HEAP32[time >> 2] * 1e3); HEAP32[tmPtr >> 2] = date.getUTCSeconds(); HEAP32[tmPtr + 4 >> 2] = date.getUTCMinutes(); HEAP32[tmPtr + 8 >> 2] = date.getUTCHours(); HEAP32[tmPtr + 12 >> 2] = date.getUTCDate(); HEAP32[tmPtr + 16 >> 2] = date.getUTCMonth(); HEAP32[tmPtr + 20 >> 2] = date.getUTCFullYear() - 1900; HEAP32[tmPtr + 24 >> 2] = date.getUTCDay(); HEAP32[tmPtr + 36 >> 2] = 0; HEAP32[tmPtr + 32 >> 2] = 0; var start = Date.UTC(date.getUTCFullYear(), 0, 1, 0, 0, 0, 0); var yday = (date.getTime() - start) / (1e3 * 60 * 60 * 24) | 0; HEAP32[tmPtr + 28 >> 2] = yday; HEAP32[tmPtr + 40 >> 2] = ___tm_timezone; return tmPtr } function _llvm_exp2_f32(x) { return Math.pow(2, x) } function _llvm_exp2_f64(a0) { return _llvm_exp2_f32(a0) } function _llvm_log2_f32(x) { return Math.log(x) / Math.LN2 } function _llvm_stackrestore(p) { var self = _llvm_stacksave; var ret = self.LLVM_SAVEDSTACKS[p]; self.LLVM_SAVEDSTACKS.splice(p, 1); stackRestore(ret) } function _llvm_stacksave() { var self = _llvm_stacksave; if (!self.LLVM_SAVEDSTACKS) { self.LLVM_SAVEDSTACKS = [] } self.LLVM_SAVEDSTACKS.push(stackSave()); return self.LLVM_SAVEDSTACKS.length - 1 } var _llvm_trunc_f64 = Math_trunc; function _tzset() { if (_tzset.called) return; _tzset.called = true; HEAP32[__get_timezone() >> 2] = (new Date).getTimezoneOffset() * 60; var currentYear = (new Date).getFullYear(); var winter = new Date(currentYear, 0, 1); var summer = new Date(currentYear, 6, 1); HEAP32[__get_daylight() >> 2] = Number(winter.getTimezoneOffset() != summer.getTimezoneOffset()); function extractZone(date) { var match = date.toTimeString().match(/\(([A-Za-z ]+)\)$/); return match ? match[1] : "GMT" } var winterName = extractZone(winter); var summerName = extractZone(summer); var winterNamePtr = allocate(intArrayFromString(winterName), "i8", ALLOC_NORMAL); var summerNamePtr = allocate(intArrayFromString(summerName), "i8", ALLOC_NORMAL); if (summer.getTimezoneOffset() < winter.getTimezoneOffset()) { HEAP32[__get_tzname() >> 2] = winterNamePtr; HEAP32[__get_tzname() + 4 >> 2] = summerNamePtr } else { HEAP32[__get_tzname() >> 2] = summerNamePtr; HEAP32[__get_tzname() + 4 >> 2] = winterNamePtr } } function _localtime_r(time, tmPtr) { _tzset(); var date = new Date(HEAP32[time >> 2] * 1e3); HEAP32[tmPtr >> 2] = date.getSeconds(); HEAP32[tmPtr + 4 >> 2] = date.getMinutes(); HEAP32[tmPtr + 8 >> 2] = date.getHours(); HEAP32[tmPtr + 12 >> 2] = date.getDate(); HEAP32[tmPtr + 16 >> 2] = date.getMonth(); HEAP32[tmPtr + 20 >> 2] = date.getFullYear() - 1900; HEAP32[tmPtr + 24 >> 2] = date.getDay(); var start = new Date(date.getFullYear(), 0, 1); var yday = (date.getTime() - start.getTime()) / (1e3 * 60 * 60 * 24) | 0; HEAP32[tmPtr + 28 >> 2] = yday; HEAP32[tmPtr + 36 >> 2] = -(date.getTimezoneOffset() * 60); var summerOffset = new Date(date.getFullYear(), 6, 1).getTimezoneOffset(); var winterOffset = start.getTimezoneOffset(); var dst = (summerOffset != winterOffset && date.getTimezoneOffset() == Math.min(winterOffset, summerOffset)) | 0; HEAP32[tmPtr + 32 >> 2] = dst; var zonePtr = HEAP32[__get_tzname() + (dst ? 4 : 0) >> 2]; HEAP32[tmPtr + 40 >> 2] = zonePtr; return tmPtr } function _emscripten_memcpy_big(dest, src, num) { HEAPU8.set(HEAPU8.subarray(src, src + num), dest) } function _usleep(useconds) { var msec = useconds / 1e3; if ((ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) && self["performance"] && self["performance"]["now"]) { var start = self["performance"]["now"](); while (self["performance"]["now"]() - start < msec) {} } else { var start = Date.now(); while (Date.now() - start < msec) {} } return 0 } Module["_usleep"] = _usleep; function _nanosleep(rqtp, rmtp) { if (rqtp === 0) { ___setErrNo(28); return -1 } var seconds = HEAP32[rqtp >> 2]; var nanoseconds = HEAP32[rqtp + 4 >> 2]; if (nanoseconds < 0 || nanoseconds > 999999999 || seconds < 0) { ___setErrNo(28); return -1 } if (rmtp !== 0) { HEAP32[rmtp >> 2] = 0; HEAP32[rmtp + 4 >> 2] = 0 } return _usleep(seconds * 1e6 + nanoseconds / 1e3) } function _pthread_cond_destroy() { return 0 } function _pthread_cond_init() { return 0 } function _pthread_create() { return 6 } function _pthread_join() {} function __isLeapYear(year) { return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0) } function __arraySum(array, index) { var sum = 0; for (var i = 0; i <= index; sum += array[i++]); return sum } var __MONTH_DAYS_LEAP = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; var __MONTH_DAYS_REGULAR = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; function __addDays(date, days) { var newDate = new Date(date.getTime()); while (days > 0) { var leap = __isLeapYear(newDate.getFullYear()); var currentMonth = newDate.getMonth(); var daysInCurrentMonth = (leap ? __MONTH_DAYS_LEAP : __MONTH_DAYS_REGULAR)[currentMonth]; if (days > daysInCurrentMonth - newDate.getDate()) { days -= daysInCurrentMonth - newDate.getDate() + 1; newDate.setDate(1); if (currentMonth < 11) { newDate.setMonth(currentMonth + 1) } else { newDate.setMonth(0); newDate.setFullYear(newDate.getFullYear() + 1) } } else { newDate.setDate(newDate.getDate() + days); return newDate } } return newDate } function _strftime(s, maxsize, format, tm) { var tm_zone = HEAP32[tm + 40 >> 2]; var date = { tm_sec: HEAP32[tm >> 2], tm_min: HEAP32[tm + 4 >> 2], tm_hour: HEAP32[tm + 8 >> 2], tm_mday: HEAP32[tm + 12 >> 2], tm_mon: HEAP32[tm + 16 >> 2], tm_year: HEAP32[tm + 20 >> 2], tm_wday: HEAP32[tm + 24 >> 2], tm_yday: HEAP32[tm + 28 >> 2], tm_isdst: HEAP32[tm + 32 >> 2], tm_gmtoff: HEAP32[tm + 36 >> 2], tm_zone: tm_zone ? UTF8ToString(tm_zone) : "" }; var pattern = UTF8ToString(format); var EXPANSION_RULES_1 = { "%c": "%a %b %d %H:%M:%S %Y", "%D": "%m/%d/%y", "%F": "%Y-%m-%d", "%h": "%b", "%r": "%I:%M:%S %p", "%R": "%H:%M", "%T": "%H:%M:%S", "%x": "%m/%d/%y", "%X": "%H:%M:%S", "%Ec": "%c", "%EC": "%C", "%Ex": "%m/%d/%y", "%EX": "%H:%M:%S", "%Ey": "%y", "%EY": "%Y", "%Od": "%d", "%Oe": "%e", "%OH": "%H", "%OI": "%I", "%Om": "%m", "%OM": "%M", "%OS": "%S", "%Ou": "%u", "%OU": "%U", "%OV": "%V", "%Ow": "%w", "%OW": "%W", "%Oy": "%y" }; for (var rule in EXPANSION_RULES_1) { pattern = pattern.replace(new RegExp(rule, "g"), EXPANSION_RULES_1[rule]) } var WEEKDAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; var MONTHS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; function leadingSomething(value, digits, character) { var str = typeof value === "number" ? value.toString() : value || ""; while (str.length < digits) { str = character[0] + str } return str } function leadingNulls(value, digits) { return leadingSomething(value, digits, "0") } function compareByDay(date1, date2) { function sgn(value) { return value < 0 ? -1 : value > 0 ? 1 : 0 } var compare; if ((compare = sgn(date1.getFullYear() - date2.getFullYear())) === 0) { if ((compare = sgn(date1.getMonth() - date2.getMonth())) === 0) { compare = sgn(date1.getDate() - date2.getDate()) } } return compare } function getFirstWeekStartDate(janFourth) { switch (janFourth.getDay()) { case 0: return new Date(janFourth.getFullYear() - 1, 11, 29); case 1: return janFourth; case 2: return new Date(janFourth.getFullYear(), 0, 3); case 3: return new Date(janFourth.getFullYear(), 0, 2); case 4: return new Date(janFourth.getFullYear(), 0, 1); case 5: return new Date(janFourth.getFullYear() - 1, 11, 31); case 6: return new Date(janFourth.getFullYear() - 1, 11, 30) } } function getWeekBasedYear(date) { var thisDate = __addDays(new Date(date.tm_year + 1900, 0, 1), date.tm_yday); var janFourthThisYear = new Date(thisDate.getFullYear(), 0, 4); var janFourthNextYear = new Date(thisDate.getFullYear() + 1, 0, 4); var firstWeekStartThisYear = getFirstWeekStartDate(janFourthThisYear); var firstWeekStartNextYear = getFirstWeekStartDate(janFourthNextYear); if (compareByDay(firstWeekStartThisYear, thisDate) <= 0) { if (compareByDay(firstWeekStartNextYear, thisDate) <= 0) { return thisDate.getFullYear() + 1 } else { return thisDate.getFullYear() } } else { return thisDate.getFullYear() - 1 } } var EXPANSION_RULES_2 = { "%a": function(date) { return WEEKDAYS[date.tm_wday].substring(0, 3) }, "%A": function(date) { return WEEKDAYS[date.tm_wday] }, "%b": function(date) { return MONTHS[date.tm_mon].substring(0, 3) }, "%B": function(date) { return MONTHS[date.tm_mon] }, "%C": function(date) { var year = date.tm_year + 1900; return leadingNulls(year / 100 | 0, 2) }, "%d": function(date) { return leadingNulls(date.tm_mday, 2) }, "%e": function(date) { return leadingSomething(date.tm_mday, 2, " ") }, "%g": function(date) { return getWeekBasedYear(date).toString().substring(2) }, "%G": function(date) { return getWeekBasedYear(date) }, "%H": function(date) { return leadingNulls(date.tm_hour, 2) }, "%I": function(date) { var twelveHour = date.tm_hour; if (twelveHour == 0) twelveHour = 12; else if (twelveHour > 12) twelveHour -= 12; return leadingNulls(twelveHour, 2) }, "%j": function(date) { return leadingNulls(date.tm_mday + __arraySum(__isLeapYear(date.tm_year + 1900) ? __MONTH_DAYS_LEAP : __MONTH_DAYS_REGULAR, date.tm_mon - 1), 3) }, "%m": function(date) { return leadingNulls(date.tm_mon + 1, 2) }, "%M": function(date) { return leadingNulls(date.tm_min, 2) }, "%n": function() { return "\n" }, "%p": function(date) { if (date.tm_hour >= 0 && date.tm_hour < 12) { return "AM" } else { return "PM" } }, "%S": function(date) { return leadingNulls(date.tm_sec, 2) }, "%t": function() { return "\t" }, "%u": function(date) { return date.tm_wday || 7 }, "%U": function(date) { var janFirst = new Date(date.tm_year + 1900, 0, 1); var firstSunday = janFirst.getDay() === 0 ? janFirst : __addDays(janFirst, 7 - janFirst.getDay()); var endDate = new Date(date.tm_year + 1900, date.tm_mon, date.tm_mday); if (compareByDay(firstSunday, endDate) < 0) { var februaryFirstUntilEndMonth = __arraySum(__isLeapYear(endDate.getFullYear()) ? __MONTH_DAYS_LEAP : __MONTH_DAYS_REGULAR, endDate.getMonth() - 1) - 31; var firstSundayUntilEndJanuary = 31 - firstSunday.getDate(); var days = firstSundayUntilEndJanuary + februaryFirstUntilEndMonth + endDate.getDate(); return leadingNulls(Math.ceil(days / 7), 2) } return compareByDay(firstSunday, janFirst) === 0 ? "01" : "00" }, "%V": function(date) { var janFourthThisYear = new Date(date.tm_year + 1900, 0, 4); var janFourthNextYear = new Date(date.tm_year + 1901, 0, 4); var firstWeekStartThisYear = getFirstWeekStartDate(janFourthThisYear); var firstWeekStartNextYear = getFirstWeekStartDate(janFourthNextYear); var endDate = __addDays(new Date(date.tm_year + 1900, 0, 1), date.tm_yday); if (compareByDay(endDate, firstWeekStartThisYear) < 0) { return "53" } if (compareByDay(firstWeekStartNextYear, endDate) <= 0) { return "01" } var daysDifference; if (firstWeekStartThisYear.getFullYear() < date.tm_year + 1900) { daysDifference = date.tm_yday + 32 - firstWeekStartThisYear.getDate() } else { daysDifference = date.tm_yday + 1 - firstWeekStartThisYear.getDate() } return leadingNulls(Math.ceil(daysDifference / 7), 2) }, "%w": function(date) { return date.tm_wday }, "%W": function(date) { var janFirst = new Date(date.tm_year, 0, 1); var firstMonday = janFirst.getDay() === 1 ? janFirst : __addDays(janFirst, janFirst.getDay() === 0 ? 1 : 7 - janFirst.getDay() + 1); var endDate = new Date(date.tm_year + 1900, date.tm_mon, date.tm_mday); if (compareByDay(firstMonday, endDate) < 0) { var februaryFirstUntilEndMonth = __arraySum(__isLeapYear(endDate.getFullYear()) ? __MONTH_DAYS_LEAP : __MONTH_DAYS_REGULAR, endDate.getMonth() - 1) - 31; var firstMondayUntilEndJanuary = 31 - firstMonday.getDate(); var days = firstMondayUntilEndJanuary + februaryFirstUntilEndMonth + endDate.getDate(); return leadingNulls(Math.ceil(days / 7), 2) } return compareByDay(firstMonday, janFirst) === 0 ? "01" : "00" }, "%y": function(date) { return (date.tm_year + 1900).toString().substring(2) }, "%Y": function(date) { return date.tm_year + 1900 }, "%z": function(date) { var off = date.tm_gmtoff; var ahead = off >= 0; off = Math.abs(off) / 60; off = off / 60 * 100 + off % 60; return (ahead ? "+" : "-") + String("0000" + off).slice(-4) }, "%Z": function(date) { return date.tm_zone }, "%%": function() { return "%" } }; for (var rule in EXPANSION_RULES_2) { if (pattern.indexOf(rule) >= 0) { pattern = pattern.replace(new RegExp(rule, "g"), EXPANSION_RULES_2[rule](date)) } } var bytes = intArrayFromString(pattern, false); if (bytes.length > maxsize) { return 0 } writeArrayToMemory(bytes, s); return bytes.length - 1 } function _sysconf(name) { switch (name) { case 30: return PAGE_SIZE; case 85: var maxHeapSize = 2 * 1024 * 1024 * 1024 - 65536; maxHeapSize = HEAPU8.length; return maxHeapSize / PAGE_SIZE; case 132: case 133: case 12: case 137: case 138: case 15: case 235: case 16: case 17: case 18: case 19: case 20: case 149: case 13: case 10: case 236: case 153: case 9: case 21: case 22: case 159: case 154: case 14: case 77: case 78: case 139: case 80: case 81: case 82: case 68: case 67: case 164: case 11: case 29: case 47: case 48: case 95: case 52: case 51: case 46: return 200809; case 79: return 0; case 27: case 246: case 127: case 128: case 23: case 24: case 160: case 161: case 181: case 182: case 242: case 183: case 184: case 243: case 244: case 245: case 165: case 178: case 179: case 49: case 50: case 168: case 169: case 175: case 170: case 171: case 172: case 97: case 76: case 32: case 173: case 35: return -1; case 176: case 177: case 7: case 155: case 8: case 157: case 125: case 126: case 92: case 93: case 129: case 130: case 131: case 94: case 91: return 1; case 74: case 60: case 69: case 70: case 4: return 1024; case 31: case 42: case 72: return 32; case 87: case 26: case 33: return 2147483647; case 34: case 1: return 47839; case 38: case 36: return 99; case 43: case 37: return 2048; case 0: return 2097152; case 3: return 65536; case 28: return 32768; case 44: return 32767; case 75: return 16384; case 39: return 1e3; case 89: return 700; case 71: return 256; case 40: return 255; case 2: return 100; case 180: return 64; case 25: return 20; case 5: return 16; case 6: return 6; case 73: return 4; case 84: { if (typeof navigator === "object") return navigator["hardwareConcurrency"] || 1; return 1 } } ___setErrNo(28); return -1 } function _time(ptr) { var ret = Date.now() / 1e3 | 0; if (ptr) { HEAP32[ptr >> 2] = ret } return ret } FS.staticInit(); if (ENVIRONMENT_HAS_NODE) { var fs = require("fs"); var NODEJS_PATH = require("path"); NODEFS.staticInit() } if (ENVIRONMENT_IS_NODE) { _emscripten_get_now = function _emscripten_get_now_actual() { var t = process["hrtime"](); return t[0] * 1e3 + t[1] / 1e6 } } else if (typeof dateNow !== "undefined") { _emscripten_get_now = dateNow } else if (typeof performance === "object" && performance && typeof performance["now"] === "function") { _emscripten_get_now = function() { return performance["now"]() } } else { _emscripten_get_now = Date.now } Fetch.staticInit(); function intArrayFromString(stringy, dontAddNull, length) { var len = length > 0 ? length : lengthBytesUTF8(stringy) + 1; var u8array = new Array(len); var numBytesWritten = stringToUTF8Array(stringy, u8array, 0, u8array.length); if (dontAddNull) u8array.length = numBytesWritten; return u8array } var debug_table_dd = [0, "jsCall_dd_0", "jsCall_dd_1", "jsCall_dd_2", "jsCall_dd_3", "jsCall_dd_4", "jsCall_dd_5", "jsCall_dd_6", "jsCall_dd_7", "jsCall_dd_8", "jsCall_dd_9", "jsCall_dd_10", "jsCall_dd_11", "jsCall_dd_12", "jsCall_dd_13", "jsCall_dd_14", "jsCall_dd_15", "jsCall_dd_16", "jsCall_dd_17", "jsCall_dd_18", "jsCall_dd_19", "jsCall_dd_20", "jsCall_dd_21", "jsCall_dd_22", "jsCall_dd_23", "jsCall_dd_24", "jsCall_dd_25", "jsCall_dd_26", "jsCall_dd_27", "jsCall_dd_28", "jsCall_dd_29", "jsCall_dd_30", "jsCall_dd_31", "jsCall_dd_32", "jsCall_dd_33", "jsCall_dd_34", "_sinh", "_cosh", "_tanh", "_sin", "_cos", "_tan", "_atan", "_asin", "_acos", "_exp", "_log", "_fabs", "_etime", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var debug_table_did = [0, "jsCall_did_0", "jsCall_did_1", "jsCall_did_2", "jsCall_did_3", "jsCall_did_4", "jsCall_did_5", "jsCall_did_6", "jsCall_did_7", "jsCall_did_8", "jsCall_did_9", "jsCall_did_10", "jsCall_did_11", "jsCall_did_12", "jsCall_did_13", "jsCall_did_14", "jsCall_did_15", "jsCall_did_16", "jsCall_did_17", "jsCall_did_18", "jsCall_did_19", "jsCall_did_20", "jsCall_did_21", "jsCall_did_22", "jsCall_did_23", "jsCall_did_24", "jsCall_did_25", "jsCall_did_26", "jsCall_did_27", "jsCall_did_28", "jsCall_did_29", "jsCall_did_30", "jsCall_did_31", "jsCall_did_32", "jsCall_did_33", "jsCall_did_34", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var debug_table_didd = [0, "jsCall_didd_0", "jsCall_didd_1", "jsCall_didd_2", "jsCall_didd_3", "jsCall_didd_4", "jsCall_didd_5", "jsCall_didd_6", "jsCall_didd_7", "jsCall_didd_8", "jsCall_didd_9", "jsCall_didd_10", "jsCall_didd_11", "jsCall_didd_12", "jsCall_didd_13", "jsCall_didd_14", "jsCall_didd_15", "jsCall_didd_16", "jsCall_didd_17", "jsCall_didd_18", "jsCall_didd_19", "jsCall_didd_20", "jsCall_didd_21", "jsCall_didd_22", "jsCall_didd_23", "jsCall_didd_24", "jsCall_didd_25", "jsCall_didd_26", "jsCall_didd_27", "jsCall_didd_28", "jsCall_didd_29", "jsCall_didd_30", "jsCall_didd_31", "jsCall_didd_32", "jsCall_didd_33", "jsCall_didd_34", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var debug_table_fii = [0, "jsCall_fii_0", "jsCall_fii_1", "jsCall_fii_2", "jsCall_fii_3", "jsCall_fii_4", "jsCall_fii_5", "jsCall_fii_6", "jsCall_fii_7", "jsCall_fii_8", "jsCall_fii_9", "jsCall_fii_10", "jsCall_fii_11", "jsCall_fii_12", "jsCall_fii_13", "jsCall_fii_14", "jsCall_fii_15", "jsCall_fii_16", "jsCall_fii_17", "jsCall_fii_18", "jsCall_fii_19", "jsCall_fii_20", "jsCall_fii_21", "jsCall_fii_22", "jsCall_fii_23", "jsCall_fii_24", "jsCall_fii_25", "jsCall_fii_26", "jsCall_fii_27", "jsCall_fii_28", "jsCall_fii_29", "jsCall_fii_30", "jsCall_fii_31", "jsCall_fii_32", "jsCall_fii_33", "jsCall_fii_34", "_sbr_sum_square_c", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var debug_table_fiii = [0, "jsCall_fiii_0", "jsCall_fiii_1", "jsCall_fiii_2", "jsCall_fiii_3", "jsCall_fiii_4", "jsCall_fiii_5", "jsCall_fiii_6", "jsCall_fiii_7", "jsCall_fiii_8", "jsCall_fiii_9", "jsCall_fiii_10", "jsCall_fiii_11", "jsCall_fiii_12", "jsCall_fiii_13", "jsCall_fiii_14", "jsCall_fiii_15", "jsCall_fiii_16", "jsCall_fiii_17", "jsCall_fiii_18", "jsCall_fiii_19", "jsCall_fiii_20", "jsCall_fiii_21", "jsCall_fiii_22", "jsCall_fiii_23", "jsCall_fiii_24", "jsCall_fiii_25", "jsCall_fiii_26", "jsCall_fiii_27", "jsCall_fiii_28", "jsCall_fiii_29", "jsCall_fiii_30", "jsCall_fiii_31", "jsCall_fiii_32", "jsCall_fiii_33", "jsCall_fiii_34", "_avpriv_scalarproduct_float_c", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var debug_table_ii = [0, "jsCall_ii_0", "jsCall_ii_1", "jsCall_ii_2", "jsCall_ii_3", "jsCall_ii_4", "jsCall_ii_5", "jsCall_ii_6", "jsCall_ii_7", "jsCall_ii_8", "jsCall_ii_9", "jsCall_ii_10", "jsCall_ii_11", "jsCall_ii_12", "jsCall_ii_13", "jsCall_ii_14", "jsCall_ii_15", "jsCall_ii_16", "jsCall_ii_17", "jsCall_ii_18", "jsCall_ii_19", "jsCall_ii_20", "jsCall_ii_21", "jsCall_ii_22", "jsCall_ii_23", "jsCall_ii_24", "jsCall_ii_25", "jsCall_ii_26", "jsCall_ii_27", "jsCall_ii_28", "jsCall_ii_29", "jsCall_ii_30", "jsCall_ii_31", "jsCall_ii_32", "jsCall_ii_33", "jsCall_ii_34", "_avi_probe", "_avi_read_header", "_avi_read_close", "_av_default_item_name", "_ff_avio_child_class_next", "_flv_probe", "_flv_read_header", "_flv_read_close", "_live_flv_probe", "_h264_probe", "_ff_raw_video_read_header", "_hevc_probe", "_mpeg4video_probe", "_matroska_probe", "_matroska_read_header", "_matroska_read_close", "_mov_probe", "_mov_read_header", "_mov_read_close", "_mp3_read_probe", "_mp3_read_header", "_mpegps_probe", "_mpegps_read_header", "_mpegts_probe", "_mpegts_read_header", "_mpegts_read_close", "_mpegvideo_probe", "_format_to_name", "_format_child_class_next", "_get_category", "_pcm_read_header", "_urlcontext_to_name", "_ff_urlcontext_child_class_next", "_sws_context_to_name", "_ff_bsf_child_class_next", "_hevc_mp4toannexb_init", "_hevc_init_thread_copy", "_hevc_decode_init", "_hevc_decode_free", "_decode_init", "_context_to_name", "_codec_child_class_next", "_get_category_2911", "_pcm_decode_init", "_pcm_decode_close", "_aac_decode_init", "_aac_decode_close", "_init", "_context_to_name_6198", "_resample_flush", "___stdio_close", "___emscripten_stdout_close", "_releaseSniffStreamFunc", "_naluLListLengthFunc", "_hflv_releaseFunc", "_hflv_getBufferLength", "_g711_releaseFunc", "_g711_decodeVideoFrameFunc", "_g711_getBufferLength", "_initializeDecoderFunc", "__getFrame", "_closeVideoFunc", "_releaseFunc", "_initializeDemuxerFunc", "_getPacketFunc", "_releaseDemuxerFunc", "_io_short_seek", "_avio_rb16", "_avio_rl16", "_av_buffer_allocz", "_frame_worker_thread", "_av_buffer_alloc", "_thread_worker", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var debug_table_iid = [0, "jsCall_iid_0", "jsCall_iid_1", "jsCall_iid_2", "jsCall_iid_3", "jsCall_iid_4", "jsCall_iid_5", "jsCall_iid_6", "jsCall_iid_7", "jsCall_iid_8", "jsCall_iid_9", "jsCall_iid_10", "jsCall_iid_11", "jsCall_iid_12", "jsCall_iid_13", "jsCall_iid_14", "jsCall_iid_15", "jsCall_iid_16", "jsCall_iid_17", "jsCall_iid_18", "jsCall_iid_19", "jsCall_iid_20", "jsCall_iid_21", "jsCall_iid_22", "jsCall_iid_23", "jsCall_iid_24", "jsCall_iid_25", "jsCall_iid_26", "jsCall_iid_27", "jsCall_iid_28", "jsCall_iid_29", "jsCall_iid_30", "jsCall_iid_31", "jsCall_iid_32", "jsCall_iid_33", "jsCall_iid_34", "_seekBufferFunc", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var debug_table_iidiiii = [0, "jsCall_iidiiii_0", "jsCall_iidiiii_1", "jsCall_iidiiii_2", "jsCall_iidiiii_3", "jsCall_iidiiii_4", "jsCall_iidiiii_5", "jsCall_iidiiii_6", "jsCall_iidiiii_7", "jsCall_iidiiii_8", "jsCall_iidiiii_9", "jsCall_iidiiii_10", "jsCall_iidiiii_11", "jsCall_iidiiii_12", "jsCall_iidiiii_13", "jsCall_iidiiii_14", "jsCall_iidiiii_15", "jsCall_iidiiii_16", "jsCall_iidiiii_17", "jsCall_iidiiii_18", "jsCall_iidiiii_19", "jsCall_iidiiii_20", "jsCall_iidiiii_21", "jsCall_iidiiii_22", "jsCall_iidiiii_23", "jsCall_iidiiii_24", "jsCall_iidiiii_25", "jsCall_iidiiii_26", "jsCall_iidiiii_27", "jsCall_iidiiii_28", "jsCall_iidiiii_29", "jsCall_iidiiii_30", "jsCall_iidiiii_31", "jsCall_iidiiii_32", "jsCall_iidiiii_33", "jsCall_iidiiii_34", "_fmt_fp", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var debug_table_iii = [0, "jsCall_iii_0", "jsCall_iii_1", "jsCall_iii_2", "jsCall_iii_3", "jsCall_iii_4", "jsCall_iii_5", "jsCall_iii_6", "jsCall_iii_7", "jsCall_iii_8", "jsCall_iii_9", "jsCall_iii_10", "jsCall_iii_11", "jsCall_iii_12", "jsCall_iii_13", "jsCall_iii_14", "jsCall_iii_15", "jsCall_iii_16", "jsCall_iii_17", "jsCall_iii_18", "jsCall_iii_19", "jsCall_iii_20", "jsCall_iii_21", "jsCall_iii_22", "jsCall_iii_23", "jsCall_iii_24", "jsCall_iii_25", "jsCall_iii_26", "jsCall_iii_27", "jsCall_iii_28", "jsCall_iii_29", "jsCall_iii_30", "jsCall_iii_31", "jsCall_iii_32", "jsCall_iii_33", "jsCall_iii_34", "_avi_read_packet", "_ff_avio_child_next", "_flv_read_packet", "_ff_raw_read_partial_packet", "_matroska_read_packet", "_mov_read_packet", "_mp3_read_packet", "_mpegps_read_packet", "_mpegts_read_packet", "_mpegts_raw_read_packet", "_format_child_next", "_ff_pcm_read_packet", "_urlcontext_child_next", "_bsf_child_next", "_hevc_mp4toannexb_filter", "_hevc_update_thread_context", "_null_filter", "_codec_child_next", "_initSniffStreamFunc", "_hflv_initFunc", "_hflv_getPacketFunc", "_g711_initFunc", "_io_read_pause", "_descriptor_compare", "_hls_decode_entry", "_avcodec_default_get_format", "_ff_startcode_find_candidate_c", "_color_table_compare"]; var debug_table_iiii = [0, "jsCall_iiii_0", "jsCall_iiii_1", "jsCall_iiii_2", "jsCall_iiii_3", "jsCall_iiii_4", "jsCall_iiii_5", "jsCall_iiii_6", "jsCall_iiii_7", "jsCall_iiii_8", "jsCall_iiii_9", "jsCall_iiii_10", "jsCall_iiii_11", "jsCall_iiii_12", "jsCall_iiii_13", "jsCall_iiii_14", "jsCall_iiii_15", "jsCall_iiii_16", "jsCall_iiii_17", "jsCall_iiii_18", "jsCall_iiii_19", "jsCall_iiii_20", "jsCall_iiii_21", "jsCall_iiii_22", "jsCall_iiii_23", "jsCall_iiii_24", "jsCall_iiii_25", "jsCall_iiii_26", "jsCall_iiii_27", "jsCall_iiii_28", "jsCall_iiii_29", "jsCall_iiii_30", "jsCall_iiii_31", "jsCall_iiii_32", "jsCall_iiii_33", "jsCall_iiii_34", "_mov_read_aclr", "_mov_read_avid", "_mov_read_ares", "_mov_read_avss", "_mov_read_av1c", "_mov_read_chpl", "_mov_read_stco", "_mov_read_colr", "_mov_read_ctts", "_mov_read_default", "_mov_read_dpxe", "_mov_read_dref", "_mov_read_elst", "_mov_read_enda", "_mov_read_fiel", "_mov_read_adrm", "_mov_read_ftyp", "_mov_read_glbl", "_mov_read_hdlr", "_mov_read_ilst", "_mov_read_jp2h", "_mov_read_mdat", "_mov_read_mdhd", "_mov_read_meta", "_mov_read_moof", "_mov_read_moov", "_mov_read_mvhd", "_mov_read_svq3", "_mov_read_alac", "_mov_read_pasp", "_mov_read_sidx", "_mov_read_stps", "_mov_read_strf", "_mov_read_stsc", "_mov_read_stsd", "_mov_read_stss", "_mov_read_stsz", "_mov_read_stts", "_mov_read_tkhd", "_mov_read_tfdt", "_mov_read_tfhd", "_mov_read_trak", "_mov_read_tmcd", "_mov_read_chap", "_mov_read_trex", "_mov_read_trun", "_mov_read_wave", "_mov_read_esds", "_mov_read_dac3", "_mov_read_dec3", "_mov_read_ddts", "_mov_read_wide", "_mov_read_wfex", "_mov_read_cmov", "_mov_read_chan", "_mov_read_dvc1", "_mov_read_sbgp", "_mov_read_uuid", "_mov_read_targa_y216", "_mov_read_free", "_mov_read_custom", "_mov_read_frma", "_mov_read_senc", "_mov_read_saiz", "_mov_read_saio", "_mov_read_pssh", "_mov_read_schm", "_mov_read_tenc", "_mov_read_dfla", "_mov_read_st3d", "_mov_read_sv3d", "_mov_read_dops", "_mov_read_smdm", "_mov_read_coll", "_mov_read_vpcc", "_mov_read_mdcv", "_mov_read_clli", "_h264_split", "_hevc_split", "_set_compensation", "___stdio_write", "_sn_write", "_read_stream_live", "_read_stream_vod", "_getSniffStreamPacketFunc", "_hflv_read_stream_live", "_g711_read_stream_live", "_setCodecTypeFunc", "_read_packet", "_io_write_packet", "_io_read_packet", "_dyn_buf_write", "_mov_read_keys", "_mov_read_udta_string", "_ff_crcA001_update", "_avcodec_default_get_buffer2", "_do_read", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var debug_table_iiiii = [0, "jsCall_iiiii_0", "jsCall_iiiii_1", "jsCall_iiiii_2", "jsCall_iiiii_3", "jsCall_iiiii_4", "jsCall_iiiii_5", "jsCall_iiiii_6", "jsCall_iiiii_7", "jsCall_iiiii_8", "jsCall_iiiii_9", "jsCall_iiiii_10", "jsCall_iiiii_11", "jsCall_iiiii_12", "jsCall_iiiii_13", "jsCall_iiiii_14", "jsCall_iiiii_15", "jsCall_iiiii_16", "jsCall_iiiii_17", "jsCall_iiiii_18", "jsCall_iiiii_19", "jsCall_iiiii_20", "jsCall_iiiii_21", "jsCall_iiiii_22", "jsCall_iiiii_23", "jsCall_iiiii_24", "jsCall_iiiii_25", "jsCall_iiiii_26", "jsCall_iiiii_27", "jsCall_iiiii_28", "jsCall_iiiii_29", "jsCall_iiiii_30", "jsCall_iiiii_31", "jsCall_iiiii_32", "jsCall_iiiii_33", "jsCall_iiiii_34", "_hevc_decode_frame", "_decode_frame", "_pcm_decode_frame", "_aac_decode_frame", "_hflv_pushBufferFunc", "_g711_pushBufferFunc", "_demuxBoxFunc", "_mov_metadata_int8_no_padding", "_mov_metadata_track_or_disc_number", "_mov_metadata_gnre", "_mov_metadata_int8_bypass_padding", "_lum_planar_vscale", "_chr_planar_vscale", "_any_vscale", "_packed_vscale", "_gamma_convert", "_lum_convert", "_lum_h_scale", "_chr_convert", "_chr_h_scale", "_no_chr_scale", "_hls_decode_entry_wpp", 0, 0, 0, 0, 0, 0]; var debug_table_iiiiii = [0, "jsCall_iiiiii_0", "jsCall_iiiiii_1", "jsCall_iiiiii_2", "jsCall_iiiiii_3", "jsCall_iiiiii_4", "jsCall_iiiiii_5", "jsCall_iiiiii_6", "jsCall_iiiiii_7", "jsCall_iiiiii_8", "jsCall_iiiiii_9", "jsCall_iiiiii_10", "jsCall_iiiiii_11", "jsCall_iiiiii_12", "jsCall_iiiiii_13", "jsCall_iiiiii_14", "jsCall_iiiiii_15", "jsCall_iiiiii_16", "jsCall_iiiiii_17", "jsCall_iiiiii_18", "jsCall_iiiiii_19", "jsCall_iiiiii_20", "jsCall_iiiiii_21", "jsCall_iiiiii_22", "jsCall_iiiiii_23", "jsCall_iiiiii_24", "jsCall_iiiiii_25", "jsCall_iiiiii_26", "jsCall_iiiiii_27", "jsCall_iiiiii_28", "jsCall_iiiiii_29", "jsCall_iiiiii_30", "jsCall_iiiiii_31", "jsCall_iiiiii_32", "jsCall_iiiiii_33", "jsCall_iiiiii_34", "_pushBufferFunc", "_g711_setSniffStreamCodecTypeFunc", "_decodeCodecContextFunc", "_io_open_default", "_avcodec_default_execute2", "_thread_execute2", "_sbr_lf_gen", "_resample_common_int16", "_resample_linear_int16", "_resample_common_int32", "_resample_linear_int32", "_resample_common_float", "_resample_linear_float", "_resample_common_double", "_resample_linear_double", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var debug_table_iiiiiii = [0, "jsCall_iiiiiii_0", "jsCall_iiiiiii_1", "jsCall_iiiiiii_2", "jsCall_iiiiiii_3", "jsCall_iiiiiii_4", "jsCall_iiiiiii_5", "jsCall_iiiiiii_6", "jsCall_iiiiiii_7", "jsCall_iiiiiii_8", "jsCall_iiiiiii_9", "jsCall_iiiiiii_10", "jsCall_iiiiiii_11", "jsCall_iiiiiii_12", "jsCall_iiiiiii_13", "jsCall_iiiiiii_14", "jsCall_iiiiiii_15", "jsCall_iiiiiii_16", "jsCall_iiiiiii_17", "jsCall_iiiiiii_18", "jsCall_iiiiiii_19", "jsCall_iiiiiii_20", "jsCall_iiiiiii_21", "jsCall_iiiiiii_22", "jsCall_iiiiiii_23", "jsCall_iiiiiii_24", "jsCall_iiiiiii_25", "jsCall_iiiiiii_26", "jsCall_iiiiiii_27", "jsCall_iiiiiii_28", "jsCall_iiiiiii_29", "jsCall_iiiiiii_30", "jsCall_iiiiiii_31", "jsCall_iiiiiii_32", "jsCall_iiiiiii_33", "jsCall_iiiiiii_34", "_h264_parse", "_hevc_parse", "_mpegaudio_parse", "_multiple_resample", "_invert_initial_buffer", "_hflv_decodeVideoFrameFunc", "_avcodec_default_execute", "_thread_execute", "_sbr_x_gen", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var debug_table_iiiiiiidiiddii = [0, "jsCall_iiiiiiidiiddii_0", "jsCall_iiiiiiidiiddii_1", "jsCall_iiiiiiidiiddii_2", "jsCall_iiiiiiidiiddii_3", "jsCall_iiiiiiidiiddii_4", "jsCall_iiiiiiidiiddii_5", "jsCall_iiiiiiidiiddii_6", "jsCall_iiiiiiidiiddii_7", "jsCall_iiiiiiidiiddii_8", "jsCall_iiiiiiidiiddii_9", "jsCall_iiiiiiidiiddii_10", "jsCall_iiiiiiidiiddii_11", "jsCall_iiiiiiidiiddii_12", "jsCall_iiiiiiidiiddii_13", "jsCall_iiiiiiidiiddii_14", "jsCall_iiiiiiidiiddii_15", "jsCall_iiiiiiidiiddii_16", "jsCall_iiiiiiidiiddii_17", "jsCall_iiiiiiidiiddii_18", "jsCall_iiiiiiidiiddii_19", "jsCall_iiiiiiidiiddii_20", "jsCall_iiiiiiidiiddii_21", "jsCall_iiiiiiidiiddii_22", "jsCall_iiiiiiidiiddii_23", "jsCall_iiiiiiidiiddii_24", "jsCall_iiiiiiidiiddii_25", "jsCall_iiiiiiidiiddii_26", "jsCall_iiiiiiidiiddii_27", "jsCall_iiiiiiidiiddii_28", "jsCall_iiiiiiidiiddii_29", "jsCall_iiiiiiidiiddii_30", "jsCall_iiiiiiidiiddii_31", "jsCall_iiiiiiidiiddii_32", "jsCall_iiiiiiidiiddii_33", "jsCall_iiiiiiidiiddii_34", "_resample_init", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var debug_table_iiiiiiii = [0, "jsCall_iiiiiiii_0", "jsCall_iiiiiiii_1", "jsCall_iiiiiiii_2", "jsCall_iiiiiiii_3", "jsCall_iiiiiiii_4", "jsCall_iiiiiiii_5", "jsCall_iiiiiiii_6", "jsCall_iiiiiiii_7", "jsCall_iiiiiiii_8", "jsCall_iiiiiiii_9", "jsCall_iiiiiiii_10", "jsCall_iiiiiiii_11", "jsCall_iiiiiiii_12", "jsCall_iiiiiiii_13", "jsCall_iiiiiiii_14", "jsCall_iiiiiiii_15", "jsCall_iiiiiiii_16", "jsCall_iiiiiiii_17", "jsCall_iiiiiiii_18", "jsCall_iiiiiiii_19", "jsCall_iiiiiiii_20", "jsCall_iiiiiiii_21", "jsCall_iiiiiiii_22", "jsCall_iiiiiiii_23", "jsCall_iiiiiiii_24", "jsCall_iiiiiiii_25", "jsCall_iiiiiiii_26", "jsCall_iiiiiiii_27", "jsCall_iiiiiiii_28", "jsCall_iiiiiiii_29", "jsCall_iiiiiiii_30", "jsCall_iiiiiiii_31", "jsCall_iiiiiiii_32", "jsCall_iiiiiiii_33", "jsCall_iiiiiiii_34", "_decodeVideoFrameFunc", "_hflv_setSniffStreamCodecTypeFunc", "_swscale", "_ff_sws_alphablendaway", "_yuv2rgb_c_32", "_yuva2rgba_c", "_yuv2rgb_c_bgr48", "_yuv2rgb_c_48", "_yuva2argb_c", "_yuv2rgb_c_24_rgb", "_yuv2rgb_c_24_bgr", "_yuv2rgb_c_16_ordered_dither", "_yuv2rgb_c_15_ordered_dither", "_yuv2rgb_c_12_ordered_dither", "_yuv2rgb_c_8_ordered_dither", "_yuv2rgb_c_4_ordered_dither", "_yuv2rgb_c_4b_ordered_dither", "_yuv2rgb_c_1_ordered_dither", "_planarToP01xWrapper", "_planar8ToP01xleWrapper", "_yvu9ToYv12Wrapper", "_bgr24ToYv12Wrapper", "_rgbToRgbWrapper", "_planarRgbToplanarRgbWrapper", "_planarRgbToRgbWrapper", "_planarRgbaToRgbWrapper", "_Rgb16ToPlanarRgb16Wrapper", "_planarRgb16ToRgb16Wrapper", "_rgbToPlanarRgbWrapper", "_bayer_to_rgb24_wrapper", "_bayer_to_yv12_wrapper", "_bswap_16bpc", "_palToRgbWrapper", "_yuv422pToYuy2Wrapper", "_yuv422pToUyvyWrapper", "_uint_y_to_float_y_wrapper", "_float_y_to_uint_y_wrapper", "_planarToYuy2Wrapper", "_planarToUyvyWrapper", "_yuyvToYuv420Wrapper", "_uyvyToYuv420Wrapper", "_yuyvToYuv422Wrapper", "_uyvyToYuv422Wrapper", "_packedCopyWrapper", "_planarCopyWrapper", "_planarToNv12Wrapper", "_planarToNv24Wrapper", "_nv12ToPlanarWrapper", "_nv24ToPlanarWrapper", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var debug_table_iiiiiiiid = [0, "jsCall_iiiiiiiid_0", "jsCall_iiiiiiiid_1", "jsCall_iiiiiiiid_2", "jsCall_iiiiiiiid_3", "jsCall_iiiiiiiid_4", "jsCall_iiiiiiiid_5", "jsCall_iiiiiiiid_6", "jsCall_iiiiiiiid_7", "jsCall_iiiiiiiid_8", "jsCall_iiiiiiiid_9", "jsCall_iiiiiiiid_10", "jsCall_iiiiiiiid_11", "jsCall_iiiiiiiid_12", "jsCall_iiiiiiiid_13", "jsCall_iiiiiiiid_14", "jsCall_iiiiiiiid_15", "jsCall_iiiiiiiid_16", "jsCall_iiiiiiiid_17", "jsCall_iiiiiiiid_18", "jsCall_iiiiiiiid_19", "jsCall_iiiiiiiid_20", "jsCall_iiiiiiiid_21", "jsCall_iiiiiiiid_22", "jsCall_iiiiiiiid_23", "jsCall_iiiiiiiid_24", "jsCall_iiiiiiiid_25", "jsCall_iiiiiiiid_26", "jsCall_iiiiiiiid_27", "jsCall_iiiiiiiid_28", "jsCall_iiiiiiiid_29", "jsCall_iiiiiiiid_30", "jsCall_iiiiiiiid_31", "jsCall_iiiiiiiid_32", "jsCall_iiiiiiiid_33", "jsCall_iiiiiiiid_34", "_setSniffStreamCodecTypeFunc", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var debug_table_iiiiij = [0, "jsCall_iiiiij_0", "jsCall_iiiiij_1", "jsCall_iiiiij_2", "jsCall_iiiiij_3", "jsCall_iiiiij_4", "jsCall_iiiiij_5", "jsCall_iiiiij_6", "jsCall_iiiiij_7", "jsCall_iiiiij_8", "jsCall_iiiiij_9", "jsCall_iiiiij_10", "jsCall_iiiiij_11", "jsCall_iiiiij_12", "jsCall_iiiiij_13", "jsCall_iiiiij_14", "jsCall_iiiiij_15", "jsCall_iiiiij_16", "jsCall_iiiiij_17", "jsCall_iiiiij_18", "jsCall_iiiiij_19", "jsCall_iiiiij_20", "jsCall_iiiiij_21", "jsCall_iiiiij_22", "jsCall_iiiiij_23", "jsCall_iiiiij_24", "jsCall_iiiiij_25", "jsCall_iiiiij_26", "jsCall_iiiiij_27", "jsCall_iiiiij_28", "jsCall_iiiiij_29", "jsCall_iiiiij_30", "jsCall_iiiiij_31", "jsCall_iiiiij_32", "jsCall_iiiiij_33", "jsCall_iiiiij_34", "_mpegts_push_data", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var debug_table_iiiji = [0, "jsCall_iiiji_0", "jsCall_iiiji_1", "jsCall_iiiji_2", "jsCall_iiiji_3", "jsCall_iiiji_4", "jsCall_iiiji_5", "jsCall_iiiji_6", "jsCall_iiiji_7", "jsCall_iiiji_8", "jsCall_iiiji_9", "jsCall_iiiji_10", "jsCall_iiiji_11", "jsCall_iiiji_12", "jsCall_iiiji_13", "jsCall_iiiji_14", "jsCall_iiiji_15", "jsCall_iiiji_16", "jsCall_iiiji_17", "jsCall_iiiji_18", "jsCall_iiiji_19", "jsCall_iiiji_20", "jsCall_iiiji_21", "jsCall_iiiji_22", "jsCall_iiiji_23", "jsCall_iiiji_24", "jsCall_iiiji_25", "jsCall_iiiji_26", "jsCall_iiiji_27", "jsCall_iiiji_28", "jsCall_iiiji_29", "jsCall_iiiji_30", "jsCall_iiiji_31", "jsCall_iiiji_32", "jsCall_iiiji_33", "jsCall_iiiji_34", "_avi_read_seek", "_flv_read_seek", "_matroska_read_seek", "_mov_read_seek", "_mp3_seek", "_ff_pcm_read_seek", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var debug_table_iiijjji = [0, "jsCall_iiijjji_0", "jsCall_iiijjji_1", "jsCall_iiijjji_2", "jsCall_iiijjji_3", "jsCall_iiijjji_4", "jsCall_iiijjji_5", "jsCall_iiijjji_6", "jsCall_iiijjji_7", "jsCall_iiijjji_8", "jsCall_iiijjji_9", "jsCall_iiijjji_10", "jsCall_iiijjji_11", "jsCall_iiijjji_12", "jsCall_iiijjji_13", "jsCall_iiijjji_14", "jsCall_iiijjji_15", "jsCall_iiijjji_16", "jsCall_iiijjji_17", "jsCall_iiijjji_18", "jsCall_iiijjji_19", "jsCall_iiijjji_20", "jsCall_iiijjji_21", "jsCall_iiijjji_22", "jsCall_iiijjji_23", "jsCall_iiijjji_24", "jsCall_iiijjji_25", "jsCall_iiijjji_26", "jsCall_iiijjji_27", "jsCall_iiijjji_28", "jsCall_iiijjji_29", "jsCall_iiijjji_30", "jsCall_iiijjji_31", "jsCall_iiijjji_32", "jsCall_iiijjji_33", "jsCall_iiijjji_34", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var debug_table_jii = [0, "jsCall_jii_0", "jsCall_jii_1", "jsCall_jii_2", "jsCall_jii_3", "jsCall_jii_4", "jsCall_jii_5", "jsCall_jii_6", "jsCall_jii_7", "jsCall_jii_8", "jsCall_jii_9", "jsCall_jii_10", "jsCall_jii_11", "jsCall_jii_12", "jsCall_jii_13", "jsCall_jii_14", "jsCall_jii_15", "jsCall_jii_16", "jsCall_jii_17", "jsCall_jii_18", "jsCall_jii_19", "jsCall_jii_20", "jsCall_jii_21", "jsCall_jii_22", "jsCall_jii_23", "jsCall_jii_24", "jsCall_jii_25", "jsCall_jii_26", "jsCall_jii_27", "jsCall_jii_28", "jsCall_jii_29", "jsCall_jii_30", "jsCall_jii_31", "jsCall_jii_32", "jsCall_jii_33", "jsCall_jii_34", "_get_out_samples", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var debug_table_jiiij = [0, "jsCall_jiiij_0", "jsCall_jiiij_1", "jsCall_jiiij_2", "jsCall_jiiij_3", "jsCall_jiiij_4", "jsCall_jiiij_5", "jsCall_jiiij_6", "jsCall_jiiij_7", "jsCall_jiiij_8", "jsCall_jiiij_9", "jsCall_jiiij_10", "jsCall_jiiij_11", "jsCall_jiiij_12", "jsCall_jiiij_13", "jsCall_jiiij_14", "jsCall_jiiij_15", "jsCall_jiiij_16", "jsCall_jiiij_17", "jsCall_jiiij_18", "jsCall_jiiij_19", "jsCall_jiiij_20", "jsCall_jiiij_21", "jsCall_jiiij_22", "jsCall_jiiij_23", "jsCall_jiiij_24", "jsCall_jiiij_25", "jsCall_jiiij_26", "jsCall_jiiij_27", "jsCall_jiiij_28", "jsCall_jiiij_29", "jsCall_jiiij_30", "jsCall_jiiij_31", "jsCall_jiiij_32", "jsCall_jiiij_33", "jsCall_jiiij_34", "_mpegps_read_dts", "_mpegts_get_dts", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var debug_table_jiiji = [0, "jsCall_jiiji_0", "jsCall_jiiji_1", "jsCall_jiiji_2", "jsCall_jiiji_3", "jsCall_jiiji_4", "jsCall_jiiji_5", "jsCall_jiiji_6", "jsCall_jiiji_7", "jsCall_jiiji_8", "jsCall_jiiji_9", "jsCall_jiiji_10", "jsCall_jiiji_11", "jsCall_jiiji_12", "jsCall_jiiji_13", "jsCall_jiiji_14", "jsCall_jiiji_15", "jsCall_jiiji_16", "jsCall_jiiji_17", "jsCall_jiiji_18", "jsCall_jiiji_19", "jsCall_jiiji_20", "jsCall_jiiji_21", "jsCall_jiiji_22", "jsCall_jiiji_23", "jsCall_jiiji_24", "jsCall_jiiji_25", "jsCall_jiiji_26", "jsCall_jiiji_27", "jsCall_jiiji_28", "jsCall_jiiji_29", "jsCall_jiiji_30", "jsCall_jiiji_31", "jsCall_jiiji_32", "jsCall_jiiji_33", "jsCall_jiiji_34", "_io_read_seek", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var debug_table_jij = [0, "jsCall_jij_0", "jsCall_jij_1", "jsCall_jij_2", "jsCall_jij_3", "jsCall_jij_4", "jsCall_jij_5", "jsCall_jij_6", "jsCall_jij_7", "jsCall_jij_8", "jsCall_jij_9", "jsCall_jij_10", "jsCall_jij_11", "jsCall_jij_12", "jsCall_jij_13", "jsCall_jij_14", "jsCall_jij_15", "jsCall_jij_16", "jsCall_jij_17", "jsCall_jij_18", "jsCall_jij_19", "jsCall_jij_20", "jsCall_jij_21", "jsCall_jij_22", "jsCall_jij_23", "jsCall_jij_24", "jsCall_jij_25", "jsCall_jij_26", "jsCall_jij_27", "jsCall_jij_28", "jsCall_jij_29", "jsCall_jij_30", "jsCall_jij_31", "jsCall_jij_32", "jsCall_jij_33", "jsCall_jij_34", "_get_delay", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var debug_table_jiji = [0, "jsCall_jiji_0", "jsCall_jiji_1", "jsCall_jiji_2", "jsCall_jiji_3", "jsCall_jiji_4", "jsCall_jiji_5", "jsCall_jiji_6", "jsCall_jiji_7", "jsCall_jiji_8", "jsCall_jiji_9", "jsCall_jiji_10", "jsCall_jiji_11", "jsCall_jiji_12", "jsCall_jiji_13", "jsCall_jiji_14", "jsCall_jiji_15", "jsCall_jiji_16", "jsCall_jiji_17", "jsCall_jiji_18", "jsCall_jiji_19", "jsCall_jiji_20", "jsCall_jiji_21", "jsCall_jiji_22", "jsCall_jiji_23", "jsCall_jiji_24", "jsCall_jiji_25", "jsCall_jiji_26", "jsCall_jiji_27", "jsCall_jiji_28", "jsCall_jiji_29", "jsCall_jiji_30", "jsCall_jiji_31", "jsCall_jiji_32", "jsCall_jiji_33", "jsCall_jiji_34", "___stdio_seek", "___emscripten_stdout_seek", "_seek_in_buffer", "_io_seek", "_dyn_buf_seek", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var debug_table_v = [0, "jsCall_v_0", "jsCall_v_1", "jsCall_v_2", "jsCall_v_3", "jsCall_v_4", "jsCall_v_5", "jsCall_v_6", "jsCall_v_7", "jsCall_v_8", "jsCall_v_9", "jsCall_v_10", "jsCall_v_11", "jsCall_v_12", "jsCall_v_13", "jsCall_v_14", "jsCall_v_15", "jsCall_v_16", "jsCall_v_17", "jsCall_v_18", "jsCall_v_19", "jsCall_v_20", "jsCall_v_21", "jsCall_v_22", "jsCall_v_23", "jsCall_v_24", "jsCall_v_25", "jsCall_v_26", "jsCall_v_27", "jsCall_v_28", "jsCall_v_29", "jsCall_v_30", "jsCall_v_31", "jsCall_v_32", "jsCall_v_33", "jsCall_v_34", "_init_ff_cos_tabs_16", "_init_ff_cos_tabs_32", "_init_ff_cos_tabs_64", "_init_ff_cos_tabs_128", "_init_ff_cos_tabs_256", "_init_ff_cos_tabs_512", "_init_ff_cos_tabs_1024", "_init_ff_cos_tabs_2048", "_init_ff_cos_tabs_4096", "_init_ff_cos_tabs_8192", "_init_ff_cos_tabs_16384", "_init_ff_cos_tabs_32768", "_init_ff_cos_tabs_65536", "_init_ff_cos_tabs_131072", "_introduce_mine", "_introduceMineFunc", "_av_format_init_next", "_av_codec_init_static", "_av_codec_init_next", "_ff_init_mpadsp_tabs_float", "_ff_init_mpadsp_tabs_fixed", "_aac_static_table_init", "_AV_CRC_8_ATM_init_table_once", "_AV_CRC_8_EBU_init_table_once", "_AV_CRC_16_ANSI_init_table_once", "_AV_CRC_16_CCITT_init_table_once", "_AV_CRC_24_IEEE_init_table_once", "_AV_CRC_32_IEEE_init_table_once", "_AV_CRC_32_IEEE_LE_init_table_once", "_AV_CRC_16_ANSI_LE_init_table_once", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var debug_table_vdiidiiiii = [0, "jsCall_vdiidiiiii_0", "jsCall_vdiidiiiii_1", "jsCall_vdiidiiiii_2", "jsCall_vdiidiiiii_3", "jsCall_vdiidiiiii_4", "jsCall_vdiidiiiii_5", "jsCall_vdiidiiiii_6", "jsCall_vdiidiiiii_7", "jsCall_vdiidiiiii_8", "jsCall_vdiidiiiii_9", "jsCall_vdiidiiiii_10", "jsCall_vdiidiiiii_11", "jsCall_vdiidiiiii_12", "jsCall_vdiidiiiii_13", "jsCall_vdiidiiiii_14", "jsCall_vdiidiiiii_15", "jsCall_vdiidiiiii_16", "jsCall_vdiidiiiii_17", "jsCall_vdiidiiiii_18", "jsCall_vdiidiiiii_19", "jsCall_vdiidiiiii_20", "jsCall_vdiidiiiii_21", "jsCall_vdiidiiiii_22", "jsCall_vdiidiiiii_23", "jsCall_vdiidiiiii_24", "jsCall_vdiidiiiii_25", "jsCall_vdiidiiiii_26", "jsCall_vdiidiiiii_27", "jsCall_vdiidiiiii_28", "jsCall_vdiidiiiii_29", "jsCall_vdiidiiiii_30", "jsCall_vdiidiiiii_31", "jsCall_vdiidiiiii_32", "jsCall_vdiidiiiii_33", "jsCall_vdiidiiiii_34", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var debug_table_vdiidiiiiii = [0, "jsCall_vdiidiiiiii_0", "jsCall_vdiidiiiiii_1", "jsCall_vdiidiiiiii_2", "jsCall_vdiidiiiiii_3", "jsCall_vdiidiiiiii_4", "jsCall_vdiidiiiiii_5", "jsCall_vdiidiiiiii_6", "jsCall_vdiidiiiiii_7", "jsCall_vdiidiiiiii_8", "jsCall_vdiidiiiiii_9", "jsCall_vdiidiiiiii_10", "jsCall_vdiidiiiiii_11", "jsCall_vdiidiiiiii_12", "jsCall_vdiidiiiiii_13", "jsCall_vdiidiiiiii_14", "jsCall_vdiidiiiiii_15", "jsCall_vdiidiiiiii_16", "jsCall_vdiidiiiiii_17", "jsCall_vdiidiiiiii_18", "jsCall_vdiidiiiiii_19", "jsCall_vdiidiiiiii_20", "jsCall_vdiidiiiiii_21", "jsCall_vdiidiiiiii_22", "jsCall_vdiidiiiiii_23", "jsCall_vdiidiiiiii_24", "jsCall_vdiidiiiiii_25", "jsCall_vdiidiiiiii_26", "jsCall_vdiidiiiiii_27", "jsCall_vdiidiiiiii_28", "jsCall_vdiidiiiiii_29", "jsCall_vdiidiiiiii_30", "jsCall_vdiidiiiiii_31", "jsCall_vdiidiiiiii_32", "jsCall_vdiidiiiiii_33", "jsCall_vdiidiiiiii_34", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var debug_table_vi = [0, "jsCall_vi_0", "jsCall_vi_1", "jsCall_vi_2", "jsCall_vi_3", "jsCall_vi_4", "jsCall_vi_5", "jsCall_vi_6", "jsCall_vi_7", "jsCall_vi_8", "jsCall_vi_9", "jsCall_vi_10", "jsCall_vi_11", "jsCall_vi_12", "jsCall_vi_13", "jsCall_vi_14", "jsCall_vi_15", "jsCall_vi_16", "jsCall_vi_17", "jsCall_vi_18", "jsCall_vi_19", "jsCall_vi_20", "jsCall_vi_21", "jsCall_vi_22", "jsCall_vi_23", "jsCall_vi_24", "jsCall_vi_25", "jsCall_vi_26", "jsCall_vi_27", "jsCall_vi_28", "jsCall_vi_29", "jsCall_vi_30", "jsCall_vi_31", "jsCall_vi_32", "jsCall_vi_33", "jsCall_vi_34", "_free_geobtag", "_free_apic", "_free_chapter", "_free_priv", "_hevc_decode_flush", "_flush", "_flush_3915", "_fft4", "_fft8", "_fft16", "_fft32", "_fft64", "_fft128", "_fft256", "_fft512", "_fft1024", "_fft2048", "_fft4096", "_fft8192", "_fft16384", "_fft32768", "_fft65536", "_fft131072", "_h264_close", "_hevc_parser_close", "_ff_parse_close", "_resample_free", "_logRequest_downloadSucceeded", "_logRequest_downloadFailed", "_downloadSucceeded", "_downloadFailed", "_transform_4x4_luma_9", "_idct_4x4_dc_9", "_idct_8x8_dc_9", "_idct_16x16_dc_9", "_idct_32x32_dc_9", "_transform_4x4_luma_10", "_idct_4x4_dc_10", "_idct_8x8_dc_10", "_idct_16x16_dc_10", "_idct_32x32_dc_10", "_transform_4x4_luma_12", "_idct_4x4_dc_12", "_idct_8x8_dc_12", "_idct_16x16_dc_12", "_idct_32x32_dc_12", "_transform_4x4_luma_8", "_idct_4x4_dc_8", "_idct_8x8_dc_8", "_idct_16x16_dc_8", "_idct_32x32_dc_8", "_main_function", "_sbr_sum64x5_c", "_sbr_neg_odd_64_c", "_sbr_qmf_pre_shuffle_c", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var debug_table_vii = [0, "jsCall_vii_0", "jsCall_vii_1", "jsCall_vii_2", "jsCall_vii_3", "jsCall_vii_4", "jsCall_vii_5", "jsCall_vii_6", "jsCall_vii_7", "jsCall_vii_8", "jsCall_vii_9", "jsCall_vii_10", "jsCall_vii_11", "jsCall_vii_12", "jsCall_vii_13", "jsCall_vii_14", "jsCall_vii_15", "jsCall_vii_16", "jsCall_vii_17", "jsCall_vii_18", "jsCall_vii_19", "jsCall_vii_20", "jsCall_vii_21", "jsCall_vii_22", "jsCall_vii_23", "jsCall_vii_24", "jsCall_vii_25", "jsCall_vii_26", "jsCall_vii_27", "jsCall_vii_28", "jsCall_vii_29", "jsCall_vii_30", "jsCall_vii_31", "jsCall_vii_32", "jsCall_vii_33", "jsCall_vii_34", "_io_close_default", "_lumRangeFromJpeg_c", "_lumRangeToJpeg_c", "_lumRangeFromJpeg16_c", "_lumRangeToJpeg16_c", "_decode_data_free", "_dequant_9", "_idct_4x4_9", "_idct_8x8_9", "_idct_16x16_9", "_idct_32x32_9", "_dequant_10", "_idct_4x4_10", "_idct_8x8_10", "_idct_16x16_10", "_idct_32x32_10", "_dequant_12", "_idct_4x4_12", "_idct_8x8_12", "_idct_16x16_12", "_idct_32x32_12", "_dequant_8", "_idct_4x4_8", "_idct_8x8_8", "_idct_16x16_8", "_idct_32x32_8", "_ff_dct32_fixed", "_imdct_and_windowing", "_apply_ltp", "_update_ltp", "_imdct_and_windowing_ld", "_imdct_and_windowing_eld", "_imdct_and_windowing_960", "_ff_dct32_float", "_dct32_func", "_dct_calc_I_c", "_dct_calc_II_c", "_dct_calc_III_c", "_dst_calc_I_c", "_fft_permute_c", "_fft_calc_c", "_ff_h264_chroma_dc_dequant_idct_9_c", "_ff_h264_chroma422_dc_dequant_idct_9_c", "_ff_h264_chroma_dc_dequant_idct_10_c", "_ff_h264_chroma422_dc_dequant_idct_10_c", "_ff_h264_chroma_dc_dequant_idct_12_c", "_ff_h264_chroma422_dc_dequant_idct_12_c", "_ff_h264_chroma_dc_dequant_idct_14_c", "_ff_h264_chroma422_dc_dequant_idct_14_c", "_ff_h264_chroma_dc_dequant_idct_8_c", "_ff_h264_chroma422_dc_dequant_idct_8_c", "_hevc_pps_free", "_rdft_calc_c", "_sbr_qmf_post_shuffle_c", "_sbr_qmf_deint_neg_c", "_sbr_autocorrelate_c", "_av_buffer_default_free", "_pool_release_buffer", "_sha1_transform", "_sha256_transform", "_pop_arg_long_double", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var debug_table_viidi = [0, "jsCall_viidi_0", "jsCall_viidi_1", "jsCall_viidi_2", "jsCall_viidi_3", "jsCall_viidi_4", "jsCall_viidi_5", "jsCall_viidi_6", "jsCall_viidi_7", "jsCall_viidi_8", "jsCall_viidi_9", "jsCall_viidi_10", "jsCall_viidi_11", "jsCall_viidi_12", "jsCall_viidi_13", "jsCall_viidi_14", "jsCall_viidi_15", "jsCall_viidi_16", "jsCall_viidi_17", "jsCall_viidi_18", "jsCall_viidi_19", "jsCall_viidi_20", "jsCall_viidi_21", "jsCall_viidi_22", "jsCall_viidi_23", "jsCall_viidi_24", "jsCall_viidi_25", "jsCall_viidi_26", "jsCall_viidi_27", "jsCall_viidi_28", "jsCall_viidi_29", "jsCall_viidi_30", "jsCall_viidi_31", "jsCall_viidi_32", "jsCall_viidi_33", "jsCall_viidi_34", "_vector_dmac_scalar_c", "_vector_dmul_scalar_c", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var debug_table_viifi = [0, "jsCall_viifi_0", "jsCall_viifi_1", "jsCall_viifi_2", "jsCall_viifi_3", "jsCall_viifi_4", "jsCall_viifi_5", "jsCall_viifi_6", "jsCall_viifi_7", "jsCall_viifi_8", "jsCall_viifi_9", "jsCall_viifi_10", "jsCall_viifi_11", "jsCall_viifi_12", "jsCall_viifi_13", "jsCall_viifi_14", "jsCall_viifi_15", "jsCall_viifi_16", "jsCall_viifi_17", "jsCall_viifi_18", "jsCall_viifi_19", "jsCall_viifi_20", "jsCall_viifi_21", "jsCall_viifi_22", "jsCall_viifi_23", "jsCall_viifi_24", "jsCall_viifi_25", "jsCall_viifi_26", "jsCall_viifi_27", "jsCall_viifi_28", "jsCall_viifi_29", "jsCall_viifi_30", "jsCall_viifi_31", "jsCall_viifi_32", "jsCall_viifi_33", "jsCall_viifi_34", "_vector_fmac_scalar_c", "_vector_fmul_scalar_c", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var debug_table_viii = [0, "jsCall_viii_0", "jsCall_viii_1", "jsCall_viii_2", "jsCall_viii_3", "jsCall_viii_4", "jsCall_viii_5", "jsCall_viii_6", "jsCall_viii_7", "jsCall_viii_8", "jsCall_viii_9", "jsCall_viii_10", "jsCall_viii_11", "jsCall_viii_12", "jsCall_viii_13", "jsCall_viii_14", "jsCall_viii_15", "jsCall_viii_16", "jsCall_viii_17", "jsCall_viii_18", "jsCall_viii_19", "jsCall_viii_20", "jsCall_viii_21", "jsCall_viii_22", "jsCall_viii_23", "jsCall_viii_24", "jsCall_viii_25", "jsCall_viii_26", "jsCall_viii_27", "jsCall_viii_28", "jsCall_viii_29", "jsCall_viii_30", "jsCall_viii_31", "jsCall_viii_32", "jsCall_viii_33", "jsCall_viii_34", "_avcHandleFrame", "_handleFrame", "_sdt_cb", "_pat_cb", "_pmt_cb", "_scte_data_cb", "_m4sl_cb", "_chrRangeFromJpeg_c", "_chrRangeToJpeg_c", "_chrRangeFromJpeg16_c", "_chrRangeToJpeg16_c", "_rgb15to16_c", "_rgb15tobgr24_c", "_rgb15to32_c", "_rgb16tobgr24_c", "_rgb16to32_c", "_rgb16to15_c", "_rgb24tobgr16_c", "_rgb24tobgr15_c", "_rgb24tobgr32_c", "_rgb32to16_c", "_rgb32to15_c", "_rgb32tobgr24_c", "_rgb24to15_c", "_rgb24to16_c", "_rgb24tobgr24_c", "_shuffle_bytes_0321_c", "_shuffle_bytes_2103_c", "_shuffle_bytes_1230_c", "_shuffle_bytes_3012_c", "_shuffle_bytes_3210_c", "_rgb32tobgr16_c", "_rgb32tobgr15_c", "_rgb48tobgr48_bswap", "_rgb48tobgr64_bswap", "_rgb48to64_bswap", "_rgb64to48_bswap", "_rgb48tobgr48_nobswap", "_rgb48tobgr64_nobswap", "_rgb48to64_nobswap", "_rgb64tobgr48_nobswap", "_rgb64tobgr48_bswap", "_rgb64to48_nobswap", "_rgb12to15", "_rgb15to24", "_rgb16to24", "_rgb32to24", "_rgb24to32", "_rgb12tobgr12", "_rgb15tobgr15", "_rgb16tobgr15", "_rgb15tobgr16", "_rgb16tobgr16", "_rgb15tobgr32", "_rgb16tobgr32", "_add_residual4x4_9", "_add_residual8x8_9", "_add_residual16x16_9", "_add_residual32x32_9", "_transform_rdpcm_9", "_add_residual4x4_10", "_add_residual8x8_10", "_add_residual16x16_10", "_add_residual32x32_10", "_transform_rdpcm_10", "_add_residual4x4_12", "_add_residual8x8_12", "_add_residual16x16_12", "_add_residual32x32_12", "_transform_rdpcm_12", "_add_residual4x4_8", "_add_residual8x8_8", "_add_residual16x16_8", "_add_residual32x32_8", "_transform_rdpcm_8", "_just_return", "_bswap_buf", "_bswap16_buf", "_ff_imdct_calc_c", "_ff_imdct_half_c", "_ff_mdct_calc_c", "_ff_h264_add_pixels4_16_c", "_ff_h264_add_pixels4_8_c", "_ff_h264_add_pixels8_16_c", "_ff_h264_add_pixels8_8_c", "_ff_h264_idct_add_9_c", "_ff_h264_idct8_add_9_c", "_ff_h264_idct_dc_add_9_c", "_ff_h264_idct8_dc_add_9_c", "_ff_h264_luma_dc_dequant_idct_9_c", "_ff_h264_idct_add_10_c", "_ff_h264_idct8_add_10_c", "_ff_h264_idct_dc_add_10_c", "_ff_h264_idct8_dc_add_10_c", "_ff_h264_luma_dc_dequant_idct_10_c", "_ff_h264_idct_add_12_c", "_ff_h264_idct8_add_12_c", "_ff_h264_idct_dc_add_12_c", "_ff_h264_idct8_dc_add_12_c", "_ff_h264_luma_dc_dequant_idct_12_c", "_ff_h264_idct_add_14_c", "_ff_h264_idct8_add_14_c", "_ff_h264_idct_dc_add_14_c", "_ff_h264_idct8_dc_add_14_c", "_ff_h264_luma_dc_dequant_idct_14_c", "_ff_h264_idct_add_8_c", "_ff_h264_idct8_add_8_c", "_ff_h264_idct_dc_add_8_c", "_ff_h264_idct8_dc_add_8_c", "_ff_h264_luma_dc_dequant_idct_8_c", "_sbr_qmf_deint_bfly_c", "_ps_add_squares_c", "_butterflies_float_c", "_cpy1", "_cpy2", "_cpy4", "_cpy8", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var debug_table_viiid = [0, "jsCall_viiid_0", "jsCall_viiid_1", "jsCall_viiid_2", "jsCall_viiid_3", "jsCall_viiid_4", "jsCall_viiid_5", "jsCall_viiid_6", "jsCall_viiid_7", "jsCall_viiid_8", "jsCall_viiid_9", "jsCall_viiid_10", "jsCall_viiid_11", "jsCall_viiid_12", "jsCall_viiid_13", "jsCall_viiid_14", "jsCall_viiid_15", "jsCall_viiid_16", "jsCall_viiid_17", "jsCall_viiid_18", "jsCall_viiid_19", "jsCall_viiid_20", "jsCall_viiid_21", "jsCall_viiid_22", "jsCall_viiid_23", "jsCall_viiid_24", "jsCall_viiid_25", "jsCall_viiid_26", "jsCall_viiid_27", "jsCall_viiid_28", "jsCall_viiid_29", "jsCall_viiid_30", "jsCall_viiid_31", "jsCall_viiid_32", "jsCall_viiid_33", "jsCall_viiid_34", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var debug_table_viiii = [0, "jsCall_viiii_0", "jsCall_viiii_1", "jsCall_viiii_2", "jsCall_viiii_3", "jsCall_viiii_4", "jsCall_viiii_5", "jsCall_viiii_6", "jsCall_viiii_7", "jsCall_viiii_8", "jsCall_viiii_9", "jsCall_viiii_10", "jsCall_viiii_11", "jsCall_viiii_12", "jsCall_viiii_13", "jsCall_viiii_14", "jsCall_viiii_15", "jsCall_viiii_16", "jsCall_viiii_17", "jsCall_viiii_18", "jsCall_viiii_19", "jsCall_viiii_20", "jsCall_viiii_21", "jsCall_viiii_22", "jsCall_viiii_23", "jsCall_viiii_24", "jsCall_viiii_25", "jsCall_viiii_26", "jsCall_viiii_27", "jsCall_viiii_28", "jsCall_viiii_29", "jsCall_viiii_30", "jsCall_viiii_31", "jsCall_viiii_32", "jsCall_viiii_33", "jsCall_viiii_34", "_planar_rgb9le_to_y", "_planar_rgb10le_to_a", "_planar_rgb10le_to_y", "_planar_rgb12le_to_a", "_planar_rgb12le_to_y", "_planar_rgb14le_to_y", "_planar_rgb16le_to_a", "_planar_rgb16le_to_y", "_planar_rgb9be_to_y", "_planar_rgb10be_to_a", "_planar_rgb10be_to_y", "_planar_rgb12be_to_a", "_planar_rgb12be_to_y", "_planar_rgb14be_to_y", "_planar_rgb16be_to_a", "_planar_rgb16be_to_y", "_planar_rgb_to_a", "_planar_rgb_to_y", "_gray8aToPacked32", "_gray8aToPacked32_1", "_gray8aToPacked24", "_sws_convertPalette8ToPacked32", "_sws_convertPalette8ToPacked24", "_intra_pred_2_9", "_intra_pred_3_9", "_intra_pred_4_9", "_intra_pred_5_9", "_pred_planar_0_9", "_pred_planar_1_9", "_pred_planar_2_9", "_pred_planar_3_9", "_intra_pred_2_10", "_intra_pred_3_10", "_intra_pred_4_10", "_intra_pred_5_10", "_pred_planar_0_10", "_pred_planar_1_10", "_pred_planar_2_10", "_pred_planar_3_10", "_intra_pred_2_12", "_intra_pred_3_12", "_intra_pred_4_12", "_intra_pred_5_12", "_pred_planar_0_12", "_pred_planar_1_12", "_pred_planar_2_12", "_pred_planar_3_12", "_intra_pred_2_8", "_intra_pred_3_8", "_intra_pred_4_8", "_intra_pred_5_8", "_pred_planar_0_8", "_pred_planar_1_8", "_pred_planar_2_8", "_pred_planar_3_8", "_apply_tns", "_windowing_and_mdct_ltp", "_h264_v_loop_filter_luma_intra_9_c", "_h264_h_loop_filter_luma_intra_9_c", "_h264_h_loop_filter_luma_mbaff_intra_9_c", "_h264_v_loop_filter_chroma_intra_9_c", "_h264_h_loop_filter_chroma_intra_9_c", "_h264_h_loop_filter_chroma422_intra_9_c", "_h264_h_loop_filter_chroma_mbaff_intra_9_c", "_h264_h_loop_filter_chroma422_mbaff_intra_9_c", "_h264_v_loop_filter_luma_intra_10_c", "_h264_h_loop_filter_luma_intra_10_c", "_h264_h_loop_filter_luma_mbaff_intra_10_c", "_h264_v_loop_filter_chroma_intra_10_c", "_h264_h_loop_filter_chroma_intra_10_c", "_h264_h_loop_filter_chroma422_intra_10_c", "_h264_h_loop_filter_chroma_mbaff_intra_10_c", "_h264_h_loop_filter_chroma422_mbaff_intra_10_c", "_h264_v_loop_filter_luma_intra_12_c", "_h264_h_loop_filter_luma_intra_12_c", "_h264_h_loop_filter_luma_mbaff_intra_12_c", "_h264_v_loop_filter_chroma_intra_12_c", "_h264_h_loop_filter_chroma_intra_12_c", "_h264_h_loop_filter_chroma422_intra_12_c", "_h264_h_loop_filter_chroma_mbaff_intra_12_c", "_h264_h_loop_filter_chroma422_mbaff_intra_12_c", "_h264_v_loop_filter_luma_intra_14_c", "_h264_h_loop_filter_luma_intra_14_c", "_h264_h_loop_filter_luma_mbaff_intra_14_c", "_h264_v_loop_filter_chroma_intra_14_c", "_h264_h_loop_filter_chroma_intra_14_c", "_h264_h_loop_filter_chroma422_intra_14_c", "_h264_h_loop_filter_chroma_mbaff_intra_14_c", "_h264_h_loop_filter_chroma422_mbaff_intra_14_c", "_h264_v_loop_filter_luma_intra_8_c", "_h264_h_loop_filter_luma_intra_8_c", "_h264_h_loop_filter_luma_mbaff_intra_8_c", "_h264_v_loop_filter_chroma_intra_8_c", "_h264_h_loop_filter_chroma_intra_8_c", "_h264_h_loop_filter_chroma422_intra_8_c", "_h264_h_loop_filter_chroma_mbaff_intra_8_c", "_h264_h_loop_filter_chroma422_mbaff_intra_8_c", "_fft15_c", "_mdct15", "_imdct15_half", "_ps_mul_pair_single_c", "_ps_hybrid_analysis_ileave_c", "_ps_hybrid_synthesis_deint_c", "_vector_fmul_c", "_vector_dmul_c", "_vector_fmul_reverse_c", "_av_log_default_callback", "_mix6to2_s16", "_mix8to2_s16", "_mix6to2_clip_s16", "_mix8to2_clip_s16", "_mix6to2_float", "_mix8to2_float", "_mix6to2_double", "_mix8to2_double", "_mix6to2_s32", "_mix8to2_s32", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var debug_table_viiiifii = [0, "jsCall_viiiifii_0", "jsCall_viiiifii_1", "jsCall_viiiifii_2", "jsCall_viiiifii_3", "jsCall_viiiifii_4", "jsCall_viiiifii_5", "jsCall_viiiifii_6", "jsCall_viiiifii_7", "jsCall_viiiifii_8", "jsCall_viiiifii_9", "jsCall_viiiifii_10", "jsCall_viiiifii_11", "jsCall_viiiifii_12", "jsCall_viiiifii_13", "jsCall_viiiifii_14", "jsCall_viiiifii_15", "jsCall_viiiifii_16", "jsCall_viiiifii_17", "jsCall_viiiifii_18", "jsCall_viiiifii_19", "jsCall_viiiifii_20", "jsCall_viiiifii_21", "jsCall_viiiifii_22", "jsCall_viiiifii_23", "jsCall_viiiifii_24", "jsCall_viiiifii_25", "jsCall_viiiifii_26", "jsCall_viiiifii_27", "jsCall_viiiifii_28", "jsCall_viiiifii_29", "jsCall_viiiifii_30", "jsCall_viiiifii_31", "jsCall_viiiifii_32", "jsCall_viiiifii_33", "jsCall_viiiifii_34", "_sbr_hf_gen_c", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var debug_table_viiiii = [0, "jsCall_viiiii_0", "jsCall_viiiii_1", "jsCall_viiiii_2", "jsCall_viiiii_3", "jsCall_viiiii_4", "jsCall_viiiii_5", "jsCall_viiiii_6", "jsCall_viiiii_7", "jsCall_viiiii_8", "jsCall_viiiii_9", "jsCall_viiiii_10", "jsCall_viiiii_11", "jsCall_viiiii_12", "jsCall_viiiii_13", "jsCall_viiiii_14", "jsCall_viiiii_15", "jsCall_viiiii_16", "jsCall_viiiii_17", "jsCall_viiiii_18", "jsCall_viiiii_19", "jsCall_viiiii_20", "jsCall_viiiii_21", "jsCall_viiiii_22", "jsCall_viiiii_23", "jsCall_viiiii_24", "jsCall_viiiii_25", "jsCall_viiiii_26", "jsCall_viiiii_27", "jsCall_viiiii_28", "jsCall_viiiii_29", "jsCall_viiiii_30", "jsCall_viiiii_31", "jsCall_viiiii_32", "jsCall_viiiii_33", "jsCall_viiiii_34", "_conv_AV_SAMPLE_FMT_U8_to_AV_SAMPLE_FMT_U8", "_conv_AV_SAMPLE_FMT_U8_to_AV_SAMPLE_FMT_S16", "_conv_AV_SAMPLE_FMT_U8_to_AV_SAMPLE_FMT_S32", "_conv_AV_SAMPLE_FMT_U8_to_AV_SAMPLE_FMT_FLT", "_conv_AV_SAMPLE_FMT_U8_to_AV_SAMPLE_FMT_DBL", "_conv_AV_SAMPLE_FMT_U8_to_AV_SAMPLE_FMT_S64", "_conv_AV_SAMPLE_FMT_S16_to_AV_SAMPLE_FMT_U8", "_conv_AV_SAMPLE_FMT_S16_to_AV_SAMPLE_FMT_S16", "_conv_AV_SAMPLE_FMT_S16_to_AV_SAMPLE_FMT_S32", "_conv_AV_SAMPLE_FMT_S16_to_AV_SAMPLE_FMT_FLT", "_conv_AV_SAMPLE_FMT_S16_to_AV_SAMPLE_FMT_DBL", "_conv_AV_SAMPLE_FMT_S16_to_AV_SAMPLE_FMT_S64", "_conv_AV_SAMPLE_FMT_S32_to_AV_SAMPLE_FMT_U8", "_conv_AV_SAMPLE_FMT_S32_to_AV_SAMPLE_FMT_S16", "_conv_AV_SAMPLE_FMT_S32_to_AV_SAMPLE_FMT_S32", "_conv_AV_SAMPLE_FMT_S32_to_AV_SAMPLE_FMT_FLT", "_conv_AV_SAMPLE_FMT_S32_to_AV_SAMPLE_FMT_DBL", "_conv_AV_SAMPLE_FMT_S32_to_AV_SAMPLE_FMT_S64", "_conv_AV_SAMPLE_FMT_FLT_to_AV_SAMPLE_FMT_U8", "_conv_AV_SAMPLE_FMT_FLT_to_AV_SAMPLE_FMT_S16", "_conv_AV_SAMPLE_FMT_FLT_to_AV_SAMPLE_FMT_S32", "_conv_AV_SAMPLE_FMT_FLT_to_AV_SAMPLE_FMT_FLT", "_conv_AV_SAMPLE_FMT_FLT_to_AV_SAMPLE_FMT_DBL", "_conv_AV_SAMPLE_FMT_FLT_to_AV_SAMPLE_FMT_S64", "_conv_AV_SAMPLE_FMT_DBL_to_AV_SAMPLE_FMT_U8", "_conv_AV_SAMPLE_FMT_DBL_to_AV_SAMPLE_FMT_S16", "_conv_AV_SAMPLE_FMT_DBL_to_AV_SAMPLE_FMT_S32", "_conv_AV_SAMPLE_FMT_DBL_to_AV_SAMPLE_FMT_FLT", "_conv_AV_SAMPLE_FMT_DBL_to_AV_SAMPLE_FMT_DBL", "_conv_AV_SAMPLE_FMT_DBL_to_AV_SAMPLE_FMT_S64", "_conv_AV_SAMPLE_FMT_S64_to_AV_SAMPLE_FMT_U8", "_conv_AV_SAMPLE_FMT_S64_to_AV_SAMPLE_FMT_S16", "_conv_AV_SAMPLE_FMT_S64_to_AV_SAMPLE_FMT_S32", "_conv_AV_SAMPLE_FMT_S64_to_AV_SAMPLE_FMT_FLT", "_conv_AV_SAMPLE_FMT_S64_to_AV_SAMPLE_FMT_DBL", "_conv_AV_SAMPLE_FMT_S64_to_AV_SAMPLE_FMT_S64", "_planar_rgb9le_to_uv", "_planar_rgb10le_to_uv", "_planar_rgb12le_to_uv", "_planar_rgb14le_to_uv", "_planar_rgb16le_to_uv", "_planar_rgb9be_to_uv", "_planar_rgb10be_to_uv", "_planar_rgb12be_to_uv", "_planar_rgb14be_to_uv", "_planar_rgb16be_to_uv", "_planar_rgb_to_uv", "_yuv2p010l1_LE_c", "_yuv2p010l1_BE_c", "_yuv2plane1_16LE_c", "_yuv2plane1_16BE_c", "_yuv2plane1_9LE_c", "_yuv2plane1_9BE_c", "_yuv2plane1_10LE_c", "_yuv2plane1_10BE_c", "_yuv2plane1_12LE_c", "_yuv2plane1_12BE_c", "_yuv2plane1_14LE_c", "_yuv2plane1_14BE_c", "_yuv2plane1_floatBE_c", "_yuv2plane1_floatLE_c", "_yuv2plane1_8_c", "_bayer_bggr8_to_rgb24_copy", "_bayer_bggr8_to_rgb24_interpolate", "_bayer_bggr16le_to_rgb24_copy", "_bayer_bggr16le_to_rgb24_interpolate", "_bayer_bggr16be_to_rgb24_copy", "_bayer_bggr16be_to_rgb24_interpolate", "_bayer_rggb8_to_rgb24_copy", "_bayer_rggb8_to_rgb24_interpolate", "_bayer_rggb16le_to_rgb24_copy", "_bayer_rggb16le_to_rgb24_interpolate", "_bayer_rggb16be_to_rgb24_copy", "_bayer_rggb16be_to_rgb24_interpolate", "_bayer_gbrg8_to_rgb24_copy", "_bayer_gbrg8_to_rgb24_interpolate", "_bayer_gbrg16le_to_rgb24_copy", "_bayer_gbrg16le_to_rgb24_interpolate", "_bayer_gbrg16be_to_rgb24_copy", "_bayer_gbrg16be_to_rgb24_interpolate", "_bayer_grbg8_to_rgb24_copy", "_bayer_grbg8_to_rgb24_interpolate", "_bayer_grbg16le_to_rgb24_copy", "_bayer_grbg16le_to_rgb24_interpolate", "_bayer_grbg16be_to_rgb24_copy", "_bayer_grbg16be_to_rgb24_interpolate", "_hevc_h_loop_filter_chroma_9", "_hevc_v_loop_filter_chroma_9", "_hevc_h_loop_filter_chroma_10", "_hevc_v_loop_filter_chroma_10", "_hevc_h_loop_filter_chroma_12", "_hevc_v_loop_filter_chroma_12", "_hevc_h_loop_filter_chroma_8", "_hevc_v_loop_filter_chroma_8", "_ff_mpadsp_apply_window_float", "_ff_mpadsp_apply_window_fixed", "_worker_func", "_sbr_hf_assemble", "_sbr_hf_inverse_filter", "_ff_h264_idct_add16_9_c", "_ff_h264_idct8_add4_9_c", "_ff_h264_idct_add8_9_c", "_ff_h264_idct_add8_422_9_c", "_ff_h264_idct_add16intra_9_c", "_h264_v_loop_filter_luma_9_c", "_h264_h_loop_filter_luma_9_c", "_h264_h_loop_filter_luma_mbaff_9_c", "_h264_v_loop_filter_chroma_9_c", "_h264_h_loop_filter_chroma_9_c", "_h264_h_loop_filter_chroma422_9_c", "_h264_h_loop_filter_chroma_mbaff_9_c", "_h264_h_loop_filter_chroma422_mbaff_9_c", "_ff_h264_idct_add16_10_c", "_ff_h264_idct8_add4_10_c", "_ff_h264_idct_add8_10_c", "_ff_h264_idct_add8_422_10_c", "_ff_h264_idct_add16intra_10_c", "_h264_v_loop_filter_luma_10_c", "_h264_h_loop_filter_luma_10_c", "_h264_h_loop_filter_luma_mbaff_10_c", "_h264_v_loop_filter_chroma_10_c", "_h264_h_loop_filter_chroma_10_c", "_h264_h_loop_filter_chroma422_10_c", "_h264_h_loop_filter_chroma_mbaff_10_c", "_h264_h_loop_filter_chroma422_mbaff_10_c", "_ff_h264_idct_add16_12_c", "_ff_h264_idct8_add4_12_c", "_ff_h264_idct_add8_12_c", "_ff_h264_idct_add8_422_12_c", "_ff_h264_idct_add16intra_12_c", "_h264_v_loop_filter_luma_12_c", "_h264_h_loop_filter_luma_12_c", "_h264_h_loop_filter_luma_mbaff_12_c", "_h264_v_loop_filter_chroma_12_c", "_h264_h_loop_filter_chroma_12_c", "_h264_h_loop_filter_chroma422_12_c", "_h264_h_loop_filter_chroma_mbaff_12_c", "_h264_h_loop_filter_chroma422_mbaff_12_c", "_ff_h264_idct_add16_14_c", "_ff_h264_idct8_add4_14_c", "_ff_h264_idct_add8_14_c", "_ff_h264_idct_add8_422_14_c", "_ff_h264_idct_add16intra_14_c", "_h264_v_loop_filter_luma_14_c", "_h264_h_loop_filter_luma_14_c", "_h264_h_loop_filter_luma_mbaff_14_c", "_h264_v_loop_filter_chroma_14_c", "_h264_h_loop_filter_chroma_14_c", "_h264_h_loop_filter_chroma422_14_c", "_h264_h_loop_filter_chroma_mbaff_14_c", "_h264_h_loop_filter_chroma422_mbaff_14_c", "_ff_h264_idct_add16_8_c", "_ff_h264_idct8_add4_8_c", "_ff_h264_idct_add8_8_c", "_ff_h264_idct_add8_422_8_c", "_ff_h264_idct_add16intra_8_c", "_h264_v_loop_filter_luma_8_c", "_h264_h_loop_filter_luma_8_c", "_h264_h_loop_filter_luma_mbaff_8_c", "_h264_v_loop_filter_chroma_8_c", "_h264_h_loop_filter_chroma_8_c", "_h264_h_loop_filter_chroma422_8_c", "_h264_h_loop_filter_chroma_mbaff_8_c", "_h264_h_loop_filter_chroma422_mbaff_8_c", "_postrotate_c", "_sbr_hf_g_filt_c", "_ps_hybrid_analysis_c", "_ps_stereo_interpolate_c", "_ps_stereo_interpolate_ipdopd_c", "_vector_fmul_window_c", "_vector_fmul_add_c", "_copy_s16", "_copy_clip_s16", "_copy_float", "_copy_double", "_copy_s32", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var debug_table_viiiiidd = [0, "jsCall_viiiiidd_0", "jsCall_viiiiidd_1", "jsCall_viiiiidd_2", "jsCall_viiiiidd_3", "jsCall_viiiiidd_4", "jsCall_viiiiidd_5", "jsCall_viiiiidd_6", "jsCall_viiiiidd_7", "jsCall_viiiiidd_8", "jsCall_viiiiidd_9", "jsCall_viiiiidd_10", "jsCall_viiiiidd_11", "jsCall_viiiiidd_12", "jsCall_viiiiidd_13", "jsCall_viiiiidd_14", "jsCall_viiiiidd_15", "jsCall_viiiiidd_16", "jsCall_viiiiidd_17", "jsCall_viiiiidd_18", "jsCall_viiiiidd_19", "jsCall_viiiiidd_20", "jsCall_viiiiidd_21", "jsCall_viiiiidd_22", "jsCall_viiiiidd_23", "jsCall_viiiiidd_24", "jsCall_viiiiidd_25", "jsCall_viiiiidd_26", "jsCall_viiiiidd_27", "jsCall_viiiiidd_28", "jsCall_viiiiidd_29", "jsCall_viiiiidd_30", "jsCall_viiiiidd_31", "jsCall_viiiiidd_32", "jsCall_viiiiidd_33", "jsCall_viiiiidd_34", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var debug_table_viiiiiddi = [0, "jsCall_viiiiiddi_0", "jsCall_viiiiiddi_1", "jsCall_viiiiiddi_2", "jsCall_viiiiiddi_3", "jsCall_viiiiiddi_4", "jsCall_viiiiiddi_5", "jsCall_viiiiiddi_6", "jsCall_viiiiiddi_7", "jsCall_viiiiiddi_8", "jsCall_viiiiiddi_9", "jsCall_viiiiiddi_10", "jsCall_viiiiiddi_11", "jsCall_viiiiiddi_12", "jsCall_viiiiiddi_13", "jsCall_viiiiiddi_14", "jsCall_viiiiiddi_15", "jsCall_viiiiiddi_16", "jsCall_viiiiiddi_17", "jsCall_viiiiiddi_18", "jsCall_viiiiiddi_19", "jsCall_viiiiiddi_20", "jsCall_viiiiiddi_21", "jsCall_viiiiiddi_22", "jsCall_viiiiiddi_23", "jsCall_viiiiiddi_24", "jsCall_viiiiiddi_25", "jsCall_viiiiiddi_26", "jsCall_viiiiiddi_27", "jsCall_viiiiiddi_28", "jsCall_viiiiiddi_29", "jsCall_viiiiiddi_30", "jsCall_viiiiiddi_31", "jsCall_viiiiiddi_32", "jsCall_viiiiiddi_33", "jsCall_viiiiiddi_34", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var debug_table_viiiiii = [0, "jsCall_viiiiii_0", "jsCall_viiiiii_1", "jsCall_viiiiii_2", "jsCall_viiiiii_3", "jsCall_viiiiii_4", "jsCall_viiiiii_5", "jsCall_viiiiii_6", "jsCall_viiiiii_7", "jsCall_viiiiii_8", "jsCall_viiiiii_9", "jsCall_viiiiii_10", "jsCall_viiiiii_11", "jsCall_viiiiii_12", "jsCall_viiiiii_13", "jsCall_viiiiii_14", "jsCall_viiiiii_15", "jsCall_viiiiii_16", "jsCall_viiiiii_17", "jsCall_viiiiii_18", "jsCall_viiiiii_19", "jsCall_viiiiii_20", "jsCall_viiiiii_21", "jsCall_viiiiii_22", "jsCall_viiiiii_23", "jsCall_viiiiii_24", "jsCall_viiiiii_25", "jsCall_viiiiii_26", "jsCall_viiiiii_27", "jsCall_viiiiii_28", "jsCall_viiiiii_29", "jsCall_viiiiii_30", "jsCall_viiiiii_31", "jsCall_viiiiii_32", "jsCall_viiiiii_33", "jsCall_viiiiii_34", "_read_geobtag", "_read_apic", "_read_chapter", "_read_priv", "_ff_hyscale_fast_c", "_bswap16Y_c", "_read_ya16le_gray_c", "_read_ya16be_gray_c", "_read_ayuv64le_Y_c", "_yuy2ToY_c", "_uyvyToY_c", "_bgr24ToY_c", "_bgr16leToY_c", "_bgr16beToY_c", "_bgr15leToY_c", "_bgr15beToY_c", "_bgr12leToY_c", "_bgr12beToY_c", "_rgb24ToY_c", "_rgb16leToY_c", "_rgb16beToY_c", "_rgb15leToY_c", "_rgb15beToY_c", "_rgb12leToY_c", "_rgb12beToY_c", "_palToY_c", "_monoblack2Y_c", "_monowhite2Y_c", "_bgr32ToY_c", "_bgr321ToY_c", "_rgb32ToY_c", "_rgb321ToY_c", "_rgb48BEToY_c", "_rgb48LEToY_c", "_bgr48BEToY_c", "_bgr48LEToY_c", "_rgb64BEToY_c", "_rgb64LEToY_c", "_bgr64BEToY_c", "_bgr64LEToY_c", "_p010LEToY_c", "_p010BEToY_c", "_grayf32ToY16_c", "_grayf32ToY16_bswap_c", "_rgba64leToA_c", "_rgba64beToA_c", "_rgbaToA_c", "_abgrToA_c", "_read_ya16le_alpha_c", "_read_ya16be_alpha_c", "_read_ayuv64le_A_c", "_palToA_c", "_put_pcm_9", "_hevc_h_loop_filter_luma_9", "_hevc_v_loop_filter_luma_9", "_put_pcm_10", "_hevc_h_loop_filter_luma_10", "_hevc_v_loop_filter_luma_10", "_put_pcm_12", "_hevc_h_loop_filter_luma_12", "_hevc_v_loop_filter_luma_12", "_put_pcm_8", "_hevc_h_loop_filter_luma_8", "_hevc_v_loop_filter_luma_8", "_pred_dc_9", "_pred_angular_0_9", "_pred_angular_1_9", "_pred_angular_2_9", "_pred_angular_3_9", "_pred_dc_10", "_pred_angular_0_10", "_pred_angular_1_10", "_pred_angular_2_10", "_pred_angular_3_10", "_pred_dc_12", "_pred_angular_0_12", "_pred_angular_1_12", "_pred_angular_2_12", "_pred_angular_3_12", "_pred_dc_8", "_pred_angular_0_8", "_pred_angular_1_8", "_pred_angular_2_8", "_pred_angular_3_8", "_ff_imdct36_blocks_float", "_ff_imdct36_blocks_fixed", "_weight_h264_pixels16_9_c", "_weight_h264_pixels8_9_c", "_weight_h264_pixels4_9_c", "_weight_h264_pixels2_9_c", "_weight_h264_pixels16_10_c", "_weight_h264_pixels8_10_c", "_weight_h264_pixels4_10_c", "_weight_h264_pixels2_10_c", "_weight_h264_pixels16_12_c", "_weight_h264_pixels8_12_c", "_weight_h264_pixels4_12_c", "_weight_h264_pixels2_12_c", "_weight_h264_pixels16_14_c", "_weight_h264_pixels8_14_c", "_weight_h264_pixels4_14_c", "_weight_h264_pixels2_14_c", "_weight_h264_pixels16_8_c", "_weight_h264_pixels8_8_c", "_weight_h264_pixels4_8_c", "_weight_h264_pixels2_8_c", "_sbr_hf_apply_noise_0", "_sbr_hf_apply_noise_1", "_sbr_hf_apply_noise_2", "_sbr_hf_apply_noise_3", "_aes_decrypt", "_aes_encrypt", "_image_copy_plane", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var debug_table_viiiiiifi = [0, "jsCall_viiiiiifi_0", "jsCall_viiiiiifi_1", "jsCall_viiiiiifi_2", "jsCall_viiiiiifi_3", "jsCall_viiiiiifi_4", "jsCall_viiiiiifi_5", "jsCall_viiiiiifi_6", "jsCall_viiiiiifi_7", "jsCall_viiiiiifi_8", "jsCall_viiiiiifi_9", "jsCall_viiiiiifi_10", "jsCall_viiiiiifi_11", "jsCall_viiiiiifi_12", "jsCall_viiiiiifi_13", "jsCall_viiiiiifi_14", "jsCall_viiiiiifi_15", "jsCall_viiiiiifi_16", "jsCall_viiiiiifi_17", "jsCall_viiiiiifi_18", "jsCall_viiiiiifi_19", "jsCall_viiiiiifi_20", "jsCall_viiiiiifi_21", "jsCall_viiiiiifi_22", "jsCall_viiiiiifi_23", "jsCall_viiiiiifi_24", "jsCall_viiiiiifi_25", "jsCall_viiiiiifi_26", "jsCall_viiiiiifi_27", "jsCall_viiiiiifi_28", "jsCall_viiiiiifi_29", "jsCall_viiiiiifi_30", "jsCall_viiiiiifi_31", "jsCall_viiiiiifi_32", "jsCall_viiiiiifi_33", "jsCall_viiiiiifi_34", "_ps_decorrelate_c", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var debug_table_viiiiiii = [0, "jsCall_viiiiiii_0", "jsCall_viiiiiii_1", "jsCall_viiiiiii_2", "jsCall_viiiiiii_3", "jsCall_viiiiiii_4", "jsCall_viiiiiii_5", "jsCall_viiiiiii_6", "jsCall_viiiiiii_7", "jsCall_viiiiiii_8", "jsCall_viiiiiii_9", "jsCall_viiiiiii_10", "jsCall_viiiiiii_11", "jsCall_viiiiiii_12", "jsCall_viiiiiii_13", "jsCall_viiiiiii_14", "jsCall_viiiiiii_15", "jsCall_viiiiiii_16", "jsCall_viiiiiii_17", "jsCall_viiiiiii_18", "jsCall_viiiiiii_19", "jsCall_viiiiiii_20", "jsCall_viiiiiii_21", "jsCall_viiiiiii_22", "jsCall_viiiiiii_23", "jsCall_viiiiiii_24", "jsCall_viiiiiii_25", "jsCall_viiiiiii_26", "jsCall_viiiiiii_27", "jsCall_viiiiiii_28", "jsCall_viiiiiii_29", "jsCall_viiiiiii_30", "jsCall_viiiiiii_31", "jsCall_viiiiiii_32", "jsCall_viiiiiii_33", "jsCall_viiiiiii_34", "_hScale8To15_c", "_hScale8To19_c", "_hScale16To19_c", "_hScale16To15_c", "_yuy2ToUV_c", "_yvy2ToUV_c", "_uyvyToUV_c", "_nv12ToUV_c", "_nv21ToUV_c", "_palToUV_c", "_bswap16UV_c", "_read_ayuv64le_UV_c", "_p010LEToUV_c", "_p010BEToUV_c", "_p016LEToUV_c", "_p016BEToUV_c", "_gbr24pToUV_half_c", "_rgb64BEToUV_half_c", "_rgb64LEToUV_half_c", "_bgr64BEToUV_half_c", "_bgr64LEToUV_half_c", "_rgb48BEToUV_half_c", "_rgb48LEToUV_half_c", "_bgr48BEToUV_half_c", "_bgr48LEToUV_half_c", "_bgr32ToUV_half_c", "_bgr321ToUV_half_c", "_bgr24ToUV_half_c", "_bgr16leToUV_half_c", "_bgr16beToUV_half_c", "_bgr15leToUV_half_c", "_bgr15beToUV_half_c", "_bgr12leToUV_half_c", "_bgr12beToUV_half_c", "_rgb32ToUV_half_c", "_rgb321ToUV_half_c", "_rgb24ToUV_half_c", "_rgb16leToUV_half_c", "_rgb16beToUV_half_c", "_rgb15leToUV_half_c", "_rgb15beToUV_half_c", "_rgb12leToUV_half_c", "_rgb12beToUV_half_c", "_rgb64BEToUV_c", "_rgb64LEToUV_c", "_bgr64BEToUV_c", "_bgr64LEToUV_c", "_rgb48BEToUV_c", "_rgb48LEToUV_c", "_bgr48BEToUV_c", "_bgr48LEToUV_c", "_bgr32ToUV_c", "_bgr321ToUV_c", "_bgr24ToUV_c", "_bgr16leToUV_c", "_bgr16beToUV_c", "_bgr15leToUV_c", "_bgr15beToUV_c", "_bgr12leToUV_c", "_bgr12beToUV_c", "_rgb32ToUV_c", "_rgb321ToUV_c", "_rgb24ToUV_c", "_rgb16leToUV_c", "_rgb16beToUV_c", "_rgb15leToUV_c", "_rgb15beToUV_c", "_rgb12leToUV_c", "_rgb12beToUV_c", "_yuv2p010lX_LE_c", "_yuv2p010lX_BE_c", "_yuv2p010cX_c", "_yuv2planeX_16LE_c", "_yuv2planeX_16BE_c", "_yuv2p016cX_c", "_yuv2planeX_9LE_c", "_yuv2planeX_9BE_c", "_yuv2planeX_10LE_c", "_yuv2planeX_10BE_c", "_yuv2planeX_12LE_c", "_yuv2planeX_12BE_c", "_yuv2planeX_14LE_c", "_yuv2planeX_14BE_c", "_yuv2planeX_floatBE_c", "_yuv2planeX_floatLE_c", "_yuv2planeX_8_c", "_yuv2nv12cX_c", "_sao_edge_filter_9", "_put_hevc_pel_pixels_9", "_put_hevc_qpel_h_9", "_put_hevc_qpel_v_9", "_put_hevc_qpel_hv_9", "_put_hevc_epel_h_9", "_put_hevc_epel_v_9", "_put_hevc_epel_hv_9", "_sao_edge_filter_10", "_put_hevc_pel_pixels_10", "_put_hevc_qpel_h_10", "_put_hevc_qpel_v_10", "_put_hevc_qpel_hv_10", "_put_hevc_epel_h_10", "_put_hevc_epel_v_10", "_put_hevc_epel_hv_10", "_sao_edge_filter_12", "_put_hevc_pel_pixels_12", "_put_hevc_qpel_h_12", "_put_hevc_qpel_v_12", "_put_hevc_qpel_hv_12", "_put_hevc_epel_h_12", "_put_hevc_epel_v_12", "_put_hevc_epel_hv_12", "_sao_edge_filter_8", "_put_hevc_pel_pixels_8", "_put_hevc_qpel_h_8", "_put_hevc_qpel_v_8", "_put_hevc_qpel_hv_8", "_put_hevc_epel_h_8", "_put_hevc_epel_v_8", "_put_hevc_epel_hv_8", "_sum2_s16", "_sum2_clip_s16", "_sum2_float", "_sum2_double", "_sum2_s32", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var debug_table_viiiiiiii = [0, "jsCall_viiiiiiii_0", "jsCall_viiiiiiii_1", "jsCall_viiiiiiii_2", "jsCall_viiiiiiii_3", "jsCall_viiiiiiii_4", "jsCall_viiiiiiii_5", "jsCall_viiiiiiii_6", "jsCall_viiiiiiii_7", "jsCall_viiiiiiii_8", "jsCall_viiiiiiii_9", "jsCall_viiiiiiii_10", "jsCall_viiiiiiii_11", "jsCall_viiiiiiii_12", "jsCall_viiiiiiii_13", "jsCall_viiiiiiii_14", "jsCall_viiiiiiii_15", "jsCall_viiiiiiii_16", "jsCall_viiiiiiii_17", "jsCall_viiiiiiii_18", "jsCall_viiiiiiii_19", "jsCall_viiiiiiii_20", "jsCall_viiiiiiii_21", "jsCall_viiiiiiii_22", "jsCall_viiiiiiii_23", "jsCall_viiiiiiii_24", "jsCall_viiiiiiii_25", "jsCall_viiiiiiii_26", "jsCall_viiiiiiii_27", "jsCall_viiiiiiii_28", "jsCall_viiiiiiii_29", "jsCall_viiiiiiii_30", "jsCall_viiiiiiii_31", "jsCall_viiiiiiii_32", "jsCall_viiiiiiii_33", "jsCall_viiiiiiii_34", "_ff_hcscale_fast_c", "_bayer_bggr8_to_yv12_copy", "_bayer_bggr8_to_yv12_interpolate", "_bayer_bggr16le_to_yv12_copy", "_bayer_bggr16le_to_yv12_interpolate", "_bayer_bggr16be_to_yv12_copy", "_bayer_bggr16be_to_yv12_interpolate", "_bayer_rggb8_to_yv12_copy", "_bayer_rggb8_to_yv12_interpolate", "_bayer_rggb16le_to_yv12_copy", "_bayer_rggb16le_to_yv12_interpolate", "_bayer_rggb16be_to_yv12_copy", "_bayer_rggb16be_to_yv12_interpolate", "_bayer_gbrg8_to_yv12_copy", "_bayer_gbrg8_to_yv12_interpolate", "_bayer_gbrg16le_to_yv12_copy", "_bayer_gbrg16le_to_yv12_interpolate", "_bayer_gbrg16be_to_yv12_copy", "_bayer_gbrg16be_to_yv12_interpolate", "_bayer_grbg8_to_yv12_copy", "_bayer_grbg8_to_yv12_interpolate", "_bayer_grbg16le_to_yv12_copy", "_bayer_grbg16le_to_yv12_interpolate", "_bayer_grbg16be_to_yv12_copy", "_bayer_grbg16be_to_yv12_interpolate", "_sao_band_filter_9", "_put_hevc_pel_uni_pixels_9", "_put_hevc_qpel_uni_h_9", "_put_hevc_qpel_uni_v_9", "_put_hevc_qpel_uni_hv_9", "_put_hevc_epel_uni_h_9", "_put_hevc_epel_uni_v_9", "_put_hevc_epel_uni_hv_9", "_sao_band_filter_10", "_put_hevc_pel_uni_pixels_10", "_put_hevc_qpel_uni_h_10", "_put_hevc_qpel_uni_v_10", "_put_hevc_qpel_uni_hv_10", "_put_hevc_epel_uni_h_10", "_put_hevc_epel_uni_v_10", "_put_hevc_epel_uni_hv_10", "_sao_band_filter_12", "_put_hevc_pel_uni_pixels_12", "_put_hevc_qpel_uni_h_12", "_put_hevc_qpel_uni_v_12", "_put_hevc_qpel_uni_hv_12", "_put_hevc_epel_uni_h_12", "_put_hevc_epel_uni_v_12", "_put_hevc_epel_uni_hv_12", "_sao_band_filter_8", "_put_hevc_pel_uni_pixels_8", "_put_hevc_qpel_uni_h_8", "_put_hevc_qpel_uni_v_8", "_put_hevc_qpel_uni_hv_8", "_put_hevc_epel_uni_h_8", "_put_hevc_epel_uni_v_8", "_put_hevc_epel_uni_hv_8", "_biweight_h264_pixels16_9_c", "_biweight_h264_pixels8_9_c", "_biweight_h264_pixels4_9_c", "_biweight_h264_pixels2_9_c", "_biweight_h264_pixels16_10_c", "_biweight_h264_pixels8_10_c", "_biweight_h264_pixels4_10_c", "_biweight_h264_pixels2_10_c", "_biweight_h264_pixels16_12_c", "_biweight_h264_pixels8_12_c", "_biweight_h264_pixels4_12_c", "_biweight_h264_pixels2_12_c", "_biweight_h264_pixels16_14_c", "_biweight_h264_pixels8_14_c", "_biweight_h264_pixels4_14_c", "_biweight_h264_pixels2_14_c", "_biweight_h264_pixels16_8_c", "_biweight_h264_pixels8_8_c", "_biweight_h264_pixels4_8_c", "_biweight_h264_pixels2_8_c", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var debug_table_viiiiiiiid = [0, "jsCall_viiiiiiiid_0", "jsCall_viiiiiiiid_1", "jsCall_viiiiiiiid_2", "jsCall_viiiiiiiid_3", "jsCall_viiiiiiiid_4", "jsCall_viiiiiiiid_5", "jsCall_viiiiiiiid_6", "jsCall_viiiiiiiid_7", "jsCall_viiiiiiiid_8", "jsCall_viiiiiiiid_9", "jsCall_viiiiiiiid_10", "jsCall_viiiiiiiid_11", "jsCall_viiiiiiiid_12", "jsCall_viiiiiiiid_13", "jsCall_viiiiiiiid_14", "jsCall_viiiiiiiid_15", "jsCall_viiiiiiiid_16", "jsCall_viiiiiiiid_17", "jsCall_viiiiiiiid_18", "jsCall_viiiiiiiid_19", "jsCall_viiiiiiiid_20", "jsCall_viiiiiiiid_21", "jsCall_viiiiiiiid_22", "jsCall_viiiiiiiid_23", "jsCall_viiiiiiiid_24", "jsCall_viiiiiiiid_25", "jsCall_viiiiiiiid_26", "jsCall_viiiiiiiid_27", "jsCall_viiiiiiiid_28", "jsCall_viiiiiiiid_29", "jsCall_viiiiiiiid_30", "jsCall_viiiiiiiid_31", "jsCall_viiiiiiiid_32", "jsCall_viiiiiiiid_33", "jsCall_viiiiiiiid_34", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var debug_table_viiiiiiiidi = [0, "jsCall_viiiiiiiidi_0", "jsCall_viiiiiiiidi_1", "jsCall_viiiiiiiidi_2", "jsCall_viiiiiiiidi_3", "jsCall_viiiiiiiidi_4", "jsCall_viiiiiiiidi_5", "jsCall_viiiiiiiidi_6", "jsCall_viiiiiiiidi_7", "jsCall_viiiiiiiidi_8", "jsCall_viiiiiiiidi_9", "jsCall_viiiiiiiidi_10", "jsCall_viiiiiiiidi_11", "jsCall_viiiiiiiidi_12", "jsCall_viiiiiiiidi_13", "jsCall_viiiiiiiidi_14", "jsCall_viiiiiiiidi_15", "jsCall_viiiiiiiidi_16", "jsCall_viiiiiiiidi_17", "jsCall_viiiiiiiidi_18", "jsCall_viiiiiiiidi_19", "jsCall_viiiiiiiidi_20", "jsCall_viiiiiiiidi_21", "jsCall_viiiiiiiidi_22", "jsCall_viiiiiiiidi_23", "jsCall_viiiiiiiidi_24", "jsCall_viiiiiiiidi_25", "jsCall_viiiiiiiidi_26", "jsCall_viiiiiiiidi_27", "jsCall_viiiiiiiidi_28", "jsCall_viiiiiiiidi_29", "jsCall_viiiiiiiidi_30", "jsCall_viiiiiiiidi_31", "jsCall_viiiiiiiidi_32", "jsCall_viiiiiiiidi_33", "jsCall_viiiiiiiidi_34", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var debug_table_viiiiiiiii = [0, "jsCall_viiiiiiiii_0", "jsCall_viiiiiiiii_1", "jsCall_viiiiiiiii_2", "jsCall_viiiiiiiii_3", "jsCall_viiiiiiiii_4", "jsCall_viiiiiiiii_5", "jsCall_viiiiiiiii_6", "jsCall_viiiiiiiii_7", "jsCall_viiiiiiiii_8", "jsCall_viiiiiiiii_9", "jsCall_viiiiiiiii_10", "jsCall_viiiiiiiii_11", "jsCall_viiiiiiiii_12", "jsCall_viiiiiiiii_13", "jsCall_viiiiiiiii_14", "jsCall_viiiiiiiii_15", "jsCall_viiiiiiiii_16", "jsCall_viiiiiiiii_17", "jsCall_viiiiiiiii_18", "jsCall_viiiiiiiii_19", "jsCall_viiiiiiiii_20", "jsCall_viiiiiiiii_21", "jsCall_viiiiiiiii_22", "jsCall_viiiiiiiii_23", "jsCall_viiiiiiiii_24", "jsCall_viiiiiiiii_25", "jsCall_viiiiiiiii_26", "jsCall_viiiiiiiii_27", "jsCall_viiiiiiiii_28", "jsCall_viiiiiiiii_29", "jsCall_viiiiiiiii_30", "jsCall_viiiiiiiii_31", "jsCall_viiiiiiiii_32", "jsCall_viiiiiiiii_33", "jsCall_viiiiiiiii_34", "_yuv2rgba32_full_1_c", "_yuv2rgbx32_full_1_c", "_yuv2argb32_full_1_c", "_yuv2xrgb32_full_1_c", "_yuv2bgra32_full_1_c", "_yuv2bgrx32_full_1_c", "_yuv2abgr32_full_1_c", "_yuv2xbgr32_full_1_c", "_yuv2rgba64le_full_1_c", "_yuv2rgbx64le_full_1_c", "_yuv2rgba64be_full_1_c", "_yuv2rgbx64be_full_1_c", "_yuv2bgra64le_full_1_c", "_yuv2bgrx64le_full_1_c", "_yuv2bgra64be_full_1_c", "_yuv2bgrx64be_full_1_c", "_yuv2rgb24_full_1_c", "_yuv2bgr24_full_1_c", "_yuv2rgb48le_full_1_c", "_yuv2bgr48le_full_1_c", "_yuv2rgb48be_full_1_c", "_yuv2bgr48be_full_1_c", "_yuv2bgr4_byte_full_1_c", "_yuv2rgb4_byte_full_1_c", "_yuv2bgr8_full_1_c", "_yuv2rgb8_full_1_c", "_yuv2rgbx64le_1_c", "_yuv2rgba64le_1_c", "_yuv2rgbx64be_1_c", "_yuv2rgba64be_1_c", "_yuv2bgrx64le_1_c", "_yuv2bgra64le_1_c", "_yuv2bgrx64be_1_c", "_yuv2bgra64be_1_c", "_yuv2rgba32_1_c", "_yuv2rgbx32_1_c", "_yuv2rgba32_1_1_c", "_yuv2rgbx32_1_1_c", "_yuv2rgb16_1_c", "_yuv2rgb15_1_c", "_yuv2rgb12_1_c", "_yuv2rgb8_1_c", "_yuv2rgb4_1_c", "_yuv2rgb4b_1_c", "_yuv2rgb48le_1_c", "_yuv2rgb48be_1_c", "_yuv2bgr48le_1_c", "_yuv2bgr48be_1_c", "_yuv2rgb24_1_c", "_yuv2bgr24_1_c", "_yuv2monowhite_1_c", "_yuv2monoblack_1_c", "_yuv2yuyv422_1_c", "_yuv2yvyu422_1_c", "_yuv2uyvy422_1_c", "_yuv2ya8_1_c", "_yuv2ya16le_1_c", "_yuv2ya16be_1_c", "_yuy2toyv12_c", "_put_hevc_pel_bi_pixels_9", "_put_hevc_qpel_bi_h_9", "_put_hevc_qpel_bi_v_9", "_put_hevc_qpel_bi_hv_9", "_put_hevc_epel_bi_h_9", "_put_hevc_epel_bi_v_9", "_put_hevc_epel_bi_hv_9", "_put_hevc_pel_bi_pixels_10", "_put_hevc_qpel_bi_h_10", "_put_hevc_qpel_bi_v_10", "_put_hevc_qpel_bi_hv_10", "_put_hevc_epel_bi_h_10", "_put_hevc_epel_bi_v_10", "_put_hevc_epel_bi_hv_10", "_put_hevc_pel_bi_pixels_12", "_put_hevc_qpel_bi_h_12", "_put_hevc_qpel_bi_v_12", "_put_hevc_qpel_bi_hv_12", "_put_hevc_epel_bi_h_12", "_put_hevc_epel_bi_v_12", "_put_hevc_epel_bi_hv_12", "_put_hevc_pel_bi_pixels_8", "_put_hevc_qpel_bi_h_8", "_put_hevc_qpel_bi_v_8", "_put_hevc_qpel_bi_hv_8", "_put_hevc_epel_bi_h_8", "_put_hevc_epel_bi_v_8", "_put_hevc_epel_bi_hv_8", 0, 0, 0, 0, 0]; var debug_table_viiiiiiiiii = [0, "jsCall_viiiiiiiiii_0", "jsCall_viiiiiiiiii_1", "jsCall_viiiiiiiiii_2", "jsCall_viiiiiiiiii_3", "jsCall_viiiiiiiiii_4", "jsCall_viiiiiiiiii_5", "jsCall_viiiiiiiiii_6", "jsCall_viiiiiiiiii_7", "jsCall_viiiiiiiiii_8", "jsCall_viiiiiiiiii_9", "jsCall_viiiiiiiiii_10", "jsCall_viiiiiiiiii_11", "jsCall_viiiiiiiiii_12", "jsCall_viiiiiiiiii_13", "jsCall_viiiiiiiiii_14", "jsCall_viiiiiiiiii_15", "jsCall_viiiiiiiiii_16", "jsCall_viiiiiiiiii_17", "jsCall_viiiiiiiiii_18", "jsCall_viiiiiiiiii_19", "jsCall_viiiiiiiiii_20", "jsCall_viiiiiiiiii_21", "jsCall_viiiiiiiiii_22", "jsCall_viiiiiiiiii_23", "jsCall_viiiiiiiiii_24", "jsCall_viiiiiiiiii_25", "jsCall_viiiiiiiiii_26", "jsCall_viiiiiiiiii_27", "jsCall_viiiiiiiiii_28", "jsCall_viiiiiiiiii_29", "jsCall_viiiiiiiiii_30", "jsCall_viiiiiiiiii_31", "jsCall_viiiiiiiiii_32", "jsCall_viiiiiiiiii_33", "jsCall_viiiiiiiiii_34", "_yuv2rgba32_full_2_c", "_yuv2rgbx32_full_2_c", "_yuv2argb32_full_2_c", "_yuv2xrgb32_full_2_c", "_yuv2bgra32_full_2_c", "_yuv2bgrx32_full_2_c", "_yuv2abgr32_full_2_c", "_yuv2xbgr32_full_2_c", "_yuv2rgba64le_full_2_c", "_yuv2rgbx64le_full_2_c", "_yuv2rgba64be_full_2_c", "_yuv2rgbx64be_full_2_c", "_yuv2bgra64le_full_2_c", "_yuv2bgrx64le_full_2_c", "_yuv2bgra64be_full_2_c", "_yuv2bgrx64be_full_2_c", "_yuv2rgb24_full_2_c", "_yuv2bgr24_full_2_c", "_yuv2rgb48le_full_2_c", "_yuv2bgr48le_full_2_c", "_yuv2rgb48be_full_2_c", "_yuv2bgr48be_full_2_c", "_yuv2bgr4_byte_full_2_c", "_yuv2rgb4_byte_full_2_c", "_yuv2bgr8_full_2_c", "_yuv2rgb8_full_2_c", "_yuv2rgbx64le_2_c", "_yuv2rgba64le_2_c", "_yuv2rgbx64be_2_c", "_yuv2rgba64be_2_c", "_yuv2bgrx64le_2_c", "_yuv2bgra64le_2_c", "_yuv2bgrx64be_2_c", "_yuv2bgra64be_2_c", "_yuv2rgba32_2_c", "_yuv2rgbx32_2_c", "_yuv2rgba32_1_2_c", "_yuv2rgbx32_1_2_c", "_yuv2rgb16_2_c", "_yuv2rgb15_2_c", "_yuv2rgb12_2_c", "_yuv2rgb8_2_c", "_yuv2rgb4_2_c", "_yuv2rgb4b_2_c", "_yuv2rgb48le_2_c", "_yuv2rgb48be_2_c", "_yuv2bgr48le_2_c", "_yuv2bgr48be_2_c", "_yuv2rgb24_2_c", "_yuv2bgr24_2_c", "_yuv2monowhite_2_c", "_yuv2monoblack_2_c", "_yuv2yuyv422_2_c", "_yuv2yvyu422_2_c", "_yuv2uyvy422_2_c", "_yuv2ya8_2_c", "_yuv2ya16le_2_c", "_yuv2ya16be_2_c", "_vu9_to_vu12_c", "_yvu9_to_yuy2_c", "_ff_emulated_edge_mc_8", "_ff_emulated_edge_mc_16", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var debug_table_viiiiiiiiiii = [0, "jsCall_viiiiiiiiiii_0", "jsCall_viiiiiiiiiii_1", "jsCall_viiiiiiiiiii_2", "jsCall_viiiiiiiiiii_3", "jsCall_viiiiiiiiiii_4", "jsCall_viiiiiiiiiii_5", "jsCall_viiiiiiiiiii_6", "jsCall_viiiiiiiiiii_7", "jsCall_viiiiiiiiiii_8", "jsCall_viiiiiiiiiii_9", "jsCall_viiiiiiiiiii_10", "jsCall_viiiiiiiiiii_11", "jsCall_viiiiiiiiiii_12", "jsCall_viiiiiiiiiii_13", "jsCall_viiiiiiiiiii_14", "jsCall_viiiiiiiiiii_15", "jsCall_viiiiiiiiiii_16", "jsCall_viiiiiiiiiii_17", "jsCall_viiiiiiiiiii_18", "jsCall_viiiiiiiiiii_19", "jsCall_viiiiiiiiiii_20", "jsCall_viiiiiiiiiii_21", "jsCall_viiiiiiiiiii_22", "jsCall_viiiiiiiiiii_23", "jsCall_viiiiiiiiiii_24", "jsCall_viiiiiiiiiii_25", "jsCall_viiiiiiiiiii_26", "jsCall_viiiiiiiiiii_27", "jsCall_viiiiiiiiiii_28", "jsCall_viiiiiiiiiii_29", "jsCall_viiiiiiiiiii_30", "jsCall_viiiiiiiiiii_31", "jsCall_viiiiiiiiiii_32", "jsCall_viiiiiiiiiii_33", "jsCall_viiiiiiiiiii_34", "_put_hevc_pel_uni_w_pixels_9", "_put_hevc_qpel_uni_w_h_9", "_put_hevc_qpel_uni_w_v_9", "_put_hevc_qpel_uni_w_hv_9", "_put_hevc_epel_uni_w_h_9", "_put_hevc_epel_uni_w_v_9", "_put_hevc_epel_uni_w_hv_9", "_put_hevc_pel_uni_w_pixels_10", "_put_hevc_qpel_uni_w_h_10", "_put_hevc_qpel_uni_w_v_10", "_put_hevc_qpel_uni_w_hv_10", "_put_hevc_epel_uni_w_h_10", "_put_hevc_epel_uni_w_v_10", "_put_hevc_epel_uni_w_hv_10", "_put_hevc_pel_uni_w_pixels_12", "_put_hevc_qpel_uni_w_h_12", "_put_hevc_qpel_uni_w_v_12", "_put_hevc_qpel_uni_w_hv_12", "_put_hevc_epel_uni_w_h_12", "_put_hevc_epel_uni_w_v_12", "_put_hevc_epel_uni_w_hv_12", "_put_hevc_pel_uni_w_pixels_8", "_put_hevc_qpel_uni_w_h_8", "_put_hevc_qpel_uni_w_v_8", "_put_hevc_qpel_uni_w_hv_8", "_put_hevc_epel_uni_w_h_8", "_put_hevc_epel_uni_w_v_8", "_put_hevc_epel_uni_w_hv_8"]; var debug_table_viiiiiiiiiiii = [0, "jsCall_viiiiiiiiiiii_0", "jsCall_viiiiiiiiiiii_1", "jsCall_viiiiiiiiiiii_2", "jsCall_viiiiiiiiiiii_3", "jsCall_viiiiiiiiiiii_4", "jsCall_viiiiiiiiiiii_5", "jsCall_viiiiiiiiiiii_6", "jsCall_viiiiiiiiiiii_7", "jsCall_viiiiiiiiiiii_8", "jsCall_viiiiiiiiiiii_9", "jsCall_viiiiiiiiiiii_10", "jsCall_viiiiiiiiiiii_11", "jsCall_viiiiiiiiiiii_12", "jsCall_viiiiiiiiiiii_13", "jsCall_viiiiiiiiiiii_14", "jsCall_viiiiiiiiiiii_15", "jsCall_viiiiiiiiiiii_16", "jsCall_viiiiiiiiiiii_17", "jsCall_viiiiiiiiiiii_18", "jsCall_viiiiiiiiiiii_19", "jsCall_viiiiiiiiiiii_20", "jsCall_viiiiiiiiiiii_21", "jsCall_viiiiiiiiiiii_22", "jsCall_viiiiiiiiiiii_23", "jsCall_viiiiiiiiiiii_24", "jsCall_viiiiiiiiiiii_25", "jsCall_viiiiiiiiiiii_26", "jsCall_viiiiiiiiiiii_27", "jsCall_viiiiiiiiiiii_28", "jsCall_viiiiiiiiiiii_29", "jsCall_viiiiiiiiiiii_30", "jsCall_viiiiiiiiiiii_31", "jsCall_viiiiiiiiiiii_32", "jsCall_viiiiiiiiiiii_33", "jsCall_viiiiiiiiiiii_34", "_yuv2rgba32_full_X_c", "_yuv2rgbx32_full_X_c", "_yuv2argb32_full_X_c", "_yuv2xrgb32_full_X_c", "_yuv2bgra32_full_X_c", "_yuv2bgrx32_full_X_c", "_yuv2abgr32_full_X_c", "_yuv2xbgr32_full_X_c", "_yuv2rgba64le_full_X_c", "_yuv2rgbx64le_full_X_c", "_yuv2rgba64be_full_X_c", "_yuv2rgbx64be_full_X_c", "_yuv2bgra64le_full_X_c", "_yuv2bgrx64le_full_X_c", "_yuv2bgra64be_full_X_c", "_yuv2bgrx64be_full_X_c", "_yuv2rgb24_full_X_c", "_yuv2bgr24_full_X_c", "_yuv2rgb48le_full_X_c", "_yuv2bgr48le_full_X_c", "_yuv2rgb48be_full_X_c", "_yuv2bgr48be_full_X_c", "_yuv2bgr4_byte_full_X_c", "_yuv2rgb4_byte_full_X_c", "_yuv2bgr8_full_X_c", "_yuv2rgb8_full_X_c", "_yuv2gbrp_full_X_c", "_yuv2gbrp16_full_X_c", "_yuv2rgbx64le_X_c", "_yuv2rgba64le_X_c", "_yuv2rgbx64be_X_c", "_yuv2rgba64be_X_c", "_yuv2bgrx64le_X_c", "_yuv2bgra64le_X_c", "_yuv2bgrx64be_X_c", "_yuv2bgra64be_X_c", "_yuv2rgba32_X_c", "_yuv2rgbx32_X_c", "_yuv2rgba32_1_X_c", "_yuv2rgbx32_1_X_c", "_yuv2rgb16_X_c", "_yuv2rgb15_X_c", "_yuv2rgb12_X_c", "_yuv2rgb8_X_c", "_yuv2rgb4_X_c", "_yuv2rgb4b_X_c", "_yuv2rgb48le_X_c", "_yuv2rgb48be_X_c", "_yuv2bgr48le_X_c", "_yuv2bgr48be_X_c", "_yuv2rgb24_X_c", "_yuv2bgr24_X_c", "_yuv2monowhite_X_c", "_yuv2ayuv64le_X_c", "_yuv2monoblack_X_c", "_yuv2yuyv422_X_c", "_yuv2yvyu422_X_c", "_yuv2uyvy422_X_c", "_yuv2ya8_X_c", "_yuv2ya16le_X_c", "_yuv2ya16be_X_c", "_sao_edge_restore_0_9", "_sao_edge_restore_1_9", "_sao_edge_restore_0_10", "_sao_edge_restore_1_10", "_sao_edge_restore_0_12", "_sao_edge_restore_1_12", "_sao_edge_restore_0_8", "_sao_edge_restore_1_8", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var debug_table_viiiiiiiiiiiiii = [0, "jsCall_viiiiiiiiiiiiii_0", "jsCall_viiiiiiiiiiiiii_1", "jsCall_viiiiiiiiiiiiii_2", "jsCall_viiiiiiiiiiiiii_3", "jsCall_viiiiiiiiiiiiii_4", "jsCall_viiiiiiiiiiiiii_5", "jsCall_viiiiiiiiiiiiii_6", "jsCall_viiiiiiiiiiiiii_7", "jsCall_viiiiiiiiiiiiii_8", "jsCall_viiiiiiiiiiiiii_9", "jsCall_viiiiiiiiiiiiii_10", "jsCall_viiiiiiiiiiiiii_11", "jsCall_viiiiiiiiiiiiii_12", "jsCall_viiiiiiiiiiiiii_13", "jsCall_viiiiiiiiiiiiii_14", "jsCall_viiiiiiiiiiiiii_15", "jsCall_viiiiiiiiiiiiii_16", "jsCall_viiiiiiiiiiiiii_17", "jsCall_viiiiiiiiiiiiii_18", "jsCall_viiiiiiiiiiiiii_19", "jsCall_viiiiiiiiiiiiii_20", "jsCall_viiiiiiiiiiiiii_21", "jsCall_viiiiiiiiiiiiii_22", "jsCall_viiiiiiiiiiiiii_23", "jsCall_viiiiiiiiiiiiii_24", "jsCall_viiiiiiiiiiiiii_25", "jsCall_viiiiiiiiiiiiii_26", "jsCall_viiiiiiiiiiiiii_27", "jsCall_viiiiiiiiiiiiii_28", "jsCall_viiiiiiiiiiiiii_29", "jsCall_viiiiiiiiiiiiii_30", "jsCall_viiiiiiiiiiiiii_31", "jsCall_viiiiiiiiiiiiii_32", "jsCall_viiiiiiiiiiiiii_33", "jsCall_viiiiiiiiiiiiii_34", "_put_hevc_pel_bi_w_pixels_9", "_put_hevc_qpel_bi_w_h_9", "_put_hevc_qpel_bi_w_v_9", "_put_hevc_qpel_bi_w_hv_9", "_put_hevc_epel_bi_w_h_9", "_put_hevc_epel_bi_w_v_9", "_put_hevc_epel_bi_w_hv_9", "_put_hevc_pel_bi_w_pixels_10", "_put_hevc_qpel_bi_w_h_10", "_put_hevc_qpel_bi_w_v_10", "_put_hevc_qpel_bi_w_hv_10", "_put_hevc_epel_bi_w_h_10", "_put_hevc_epel_bi_w_v_10", "_put_hevc_epel_bi_w_hv_10", "_put_hevc_pel_bi_w_pixels_12", "_put_hevc_qpel_bi_w_h_12", "_put_hevc_qpel_bi_w_v_12", "_put_hevc_qpel_bi_w_hv_12", "_put_hevc_epel_bi_w_h_12", "_put_hevc_epel_bi_w_v_12", "_put_hevc_epel_bi_w_hv_12", "_put_hevc_pel_bi_w_pixels_8", "_put_hevc_qpel_bi_w_h_8", "_put_hevc_qpel_bi_w_v_8", "_put_hevc_qpel_bi_w_hv_8", "_put_hevc_epel_bi_w_h_8", "_put_hevc_epel_bi_w_v_8", "_put_hevc_epel_bi_w_hv_8"]; var debug_table_viiijj = [0, "jsCall_viiijj_0", "jsCall_viiijj_1", "jsCall_viiijj_2", "jsCall_viiijj_3", "jsCall_viiijj_4", "jsCall_viiijj_5", "jsCall_viiijj_6", "jsCall_viiijj_7", "jsCall_viiijj_8", "jsCall_viiijj_9", "jsCall_viiijj_10", "jsCall_viiijj_11", "jsCall_viiijj_12", "jsCall_viiijj_13", "jsCall_viiijj_14", "jsCall_viiijj_15", "jsCall_viiijj_16", "jsCall_viiijj_17", "jsCall_viiijj_18", "jsCall_viiijj_19", "jsCall_viiijj_20", "jsCall_viiijj_21", "jsCall_viiijj_22", "jsCall_viiijj_23", "jsCall_viiijj_24", "jsCall_viiijj_25", "jsCall_viiijj_26", "jsCall_viiijj_27", "jsCall_viiijj_28", "jsCall_viiijj_29", "jsCall_viiijj_30", "jsCall_viiijj_31", "jsCall_viiijj_32", "jsCall_viiijj_33", "jsCall_viiijj_34", "_resample_one_int16", "_resample_one_int32", "_resample_one_float", "_resample_one_double", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var debug_tables = { "dd": debug_table_dd, "did": debug_table_did, "didd": debug_table_didd, "fii": debug_table_fii, "fiii": debug_table_fiii, "ii": debug_table_ii, "iid": debug_table_iid, "iidiiii": debug_table_iidiiii, "iii": debug_table_iii, "iiii": debug_table_iiii, "iiiii": debug_table_iiiii, "iiiiii": debug_table_iiiiii, "iiiiiii": debug_table_iiiiiii, "iiiiiiidiiddii": debug_table_iiiiiiidiiddii, "iiiiiiii": debug_table_iiiiiiii, "iiiiiiiid": debug_table_iiiiiiiid, "iiiiij": debug_table_iiiiij, "iiiji": debug_table_iiiji, "iiijjji": debug_table_iiijjji, "jii": debug_table_jii, "jiiij": debug_table_jiiij, "jiiji": debug_table_jiiji, "jij": debug_table_jij, "jiji": debug_table_jiji, "v": debug_table_v, "vdiidiiiii": debug_table_vdiidiiiii, "vdiidiiiiii": debug_table_vdiidiiiiii, "vi": debug_table_vi, "vii": debug_table_vii, "viidi": debug_table_viidi, "viifi": debug_table_viifi, "viii": debug_table_viii, "viiid": debug_table_viiid, "viiii": debug_table_viiii, "viiiifii": debug_table_viiiifii, "viiiii": debug_table_viiiii, "viiiiidd": debug_table_viiiiidd, "viiiiiddi": debug_table_viiiiiddi, "viiiiii": debug_table_viiiiii, "viiiiiifi": debug_table_viiiiiifi, "viiiiiii": debug_table_viiiiiii, "viiiiiiii": debug_table_viiiiiiii, "viiiiiiiid": debug_table_viiiiiiiid, "viiiiiiiidi": debug_table_viiiiiiiidi, "viiiiiiiii": debug_table_viiiiiiiii, "viiiiiiiiii": debug_table_viiiiiiiiii, "viiiiiiiiiii": debug_table_viiiiiiiiiii, "viiiiiiiiiiii": debug_table_viiiiiiiiiiii, "viiiiiiiiiiiiii": debug_table_viiiiiiiiiiiiii, "viiijj": debug_table_viiijj }; function nullFunc_dd(x) { abortFnPtrError(x, "dd") } function nullFunc_did(x) { abortFnPtrError(x, "did") } function nullFunc_didd(x) { abortFnPtrError(x, "didd") } function nullFunc_fii(x) { abortFnPtrError(x, "fii") } function nullFunc_fiii(x) { abortFnPtrError(x, "fiii") } function nullFunc_ii(x) { abortFnPtrError(x, "ii") } function nullFunc_iid(x) { abortFnPtrError(x, "iid") } function nullFunc_iidiiii(x) { abortFnPtrError(x, "iidiiii") } function nullFunc_iii(x) { abortFnPtrError(x, "iii") } function nullFunc_iiii(x) { abortFnPtrError(x, "iiii") } function nullFunc_iiiii(x) { abortFnPtrError(x, "iiiii") } function nullFunc_iiiiii(x) { abortFnPtrError(x, "iiiiii") } function nullFunc_iiiiiii(x) { abortFnPtrError(x, "iiiiiii") } function nullFunc_iiiiiiidiiddii(x) { abortFnPtrError(x, "iiiiiiidiiddii") } function nullFunc_iiiiiiii(x) { abortFnPtrError(x, "iiiiiiii") } function nullFunc_iiiiiiiid(x) { abortFnPtrError(x, "iiiiiiiid") } function nullFunc_iiiiij(x) { abortFnPtrError(x, "iiiiij") } function nullFunc_iiiji(x) { abortFnPtrError(x, "iiiji") } function nullFunc_iiijjji(x) { abortFnPtrError(x, "iiijjji") } function nullFunc_jii(x) { abortFnPtrError(x, "jii") } function nullFunc_jiiij(x) { abortFnPtrError(x, "jiiij") } function nullFunc_jiiji(x) { abortFnPtrError(x, "jiiji") } function nullFunc_jij(x) { abortFnPtrError(x, "jij") } function nullFunc_jiji(x) { abortFnPtrError(x, "jiji") } function nullFunc_v(x) { abortFnPtrError(x, "v") } function nullFunc_vdiidiiiii(x) { abortFnPtrError(x, "vdiidiiiii") } function nullFunc_vdiidiiiiii(x) { abortFnPtrError(x, "vdiidiiiiii") } function nullFunc_vi(x) { abortFnPtrError(x, "vi") } function nullFunc_vii(x) { abortFnPtrError(x, "vii") } function nullFunc_viidi(x) { abortFnPtrError(x, "viidi") } function nullFunc_viifi(x) { abortFnPtrError(x, "viifi") } function nullFunc_viii(x) { abortFnPtrError(x, "viii") } function nullFunc_viiid(x) { abortFnPtrError(x, "viiid") } function nullFunc_viiii(x) { abortFnPtrError(x, "viiii") } function nullFunc_viiiifii(x) { abortFnPtrError(x, "viiiifii") } function nullFunc_viiiii(x) { abortFnPtrError(x, "viiiii") } function nullFunc_viiiiidd(x) { abortFnPtrError(x, "viiiiidd") } function nullFunc_viiiiiddi(x) { abortFnPtrError(x, "viiiiiddi") } function nullFunc_viiiiii(x) { abortFnPtrError(x, "viiiiii") } function nullFunc_viiiiiifi(x) { abortFnPtrError(x, "viiiiiifi") } function nullFunc_viiiiiii(x) { abortFnPtrError(x, "viiiiiii") } function nullFunc_viiiiiiii(x) { abortFnPtrError(x, "viiiiiiii") } function nullFunc_viiiiiiiid(x) { abortFnPtrError(x, "viiiiiiiid") } function nullFunc_viiiiiiiidi(x) { abortFnPtrError(x, "viiiiiiiidi") } function nullFunc_viiiiiiiii(x) { abortFnPtrError(x, "viiiiiiiii") } function nullFunc_viiiiiiiiii(x) { abortFnPtrError(x, "viiiiiiiiii") } function nullFunc_viiiiiiiiiii(x) { abortFnPtrError(x, "viiiiiiiiiii") } function nullFunc_viiiiiiiiiiii(x) { abortFnPtrError(x, "viiiiiiiiiiii") } function nullFunc_viiiiiiiiiiiiii(x) { abortFnPtrError(x, "viiiiiiiiiiiiii") } function nullFunc_viiijj(x) { abortFnPtrError(x, "viiijj") } function jsCall_dd(index, a1) { return functionPointers[index](a1) } function jsCall_did(index, a1, a2) { return functionPointers[index](a1, a2) } function jsCall_didd(index, a1, a2, a3) { return functionPointers[index](a1, a2, a3) } function jsCall_fii(index, a1, a2) { return functionPointers[index](a1, a2) } function jsCall_fiii(index, a1, a2, a3) { return functionPointers[index](a1, a2, a3) } function jsCall_ii(index, a1) { return functionPointers[index](a1) } function jsCall_iid(index, a1, a2) { return functionPointers[index](a1, a2) } function jsCall_iidiiii(index, a1, a2, a3, a4, a5, a6) { return functionPointers[index](a1, a2, a3, a4, a5, a6) } function jsCall_iii(index, a1, a2) { return functionPointers[index](a1, a2) } function jsCall_iiii(index, a1, a2, a3) { return functionPointers[index](a1, a2, a3) } function jsCall_iiiii(index, a1, a2, a3, a4) { return functionPointers[index](a1, a2, a3, a4) } function jsCall_iiiiii(index, a1, a2, a3, a4, a5) { return functionPointers[index](a1, a2, a3, a4, a5) } function jsCall_iiiiiii(index, a1, a2, a3, a4, a5, a6) { return functionPointers[index](a1, a2, a3, a4, a5, a6) } function jsCall_iiiiiiidiiddii(index, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13) { return functionPointers[index](a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13) } function jsCall_iiiiiiii(index, a1, a2, a3, a4, a5, a6, a7) { return functionPointers[index](a1, a2, a3, a4, a5, a6, a7) } function jsCall_iiiiiiiid(index, a1, a2, a3, a4, a5, a6, a7, a8) { return functionPointers[index](a1, a2, a3, a4, a5, a6, a7, a8) } function jsCall_iiiiij(index, a1, a2, a3, a4, a5) { return functionPointers[index](a1, a2, a3, a4, a5) } function jsCall_iiiji(index, a1, a2, a3, a4) { return functionPointers[index](a1, a2, a3, a4) } function jsCall_iiijjji(index, a1, a2, a3, a4, a5, a6) { return functionPointers[index](a1, a2, a3, a4, a5, a6) } function jsCall_jii(index, a1, a2) { return functionPointers[index](a1, a2) } function jsCall_jiiij(index, a1, a2, a3, a4) { return functionPointers[index](a1, a2, a3, a4) } function jsCall_jiiji(index, a1, a2, a3, a4) { return functionPointers[index](a1, a2, a3, a4) } function jsCall_jij(index, a1, a2) { return functionPointers[index](a1, a2) } function jsCall_jiji(index, a1, a2, a3) { return functionPointers[index](a1, a2, a3) } function jsCall_v(index) { functionPointers[index]() } function jsCall_vdiidiiiii(index, a1, a2, a3, a4, a5, a6, a7, a8, a9) { functionPointers[index](a1, a2, a3, a4, a5, a6, a7, a8, a9) } function jsCall_vdiidiiiiii(index, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) { functionPointers[index](a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) } function jsCall_vi(index, a1) { functionPointers[index](a1) } function jsCall_vii(index, a1, a2) { functionPointers[index](a1, a2) } function jsCall_viidi(index, a1, a2, a3, a4) { functionPointers[index](a1, a2, a3, a4) } function jsCall_viifi(index, a1, a2, a3, a4) { functionPointers[index](a1, a2, a3, a4) } function jsCall_viii(index, a1, a2, a3) { functionPointers[index](a1, a2, a3) } function jsCall_viiid(index, a1, a2, a3, a4) { functionPointers[index](a1, a2, a3, a4) } function jsCall_viiii(index, a1, a2, a3, a4) { functionPointers[index](a1, a2, a3, a4) } function jsCall_viiiifii(index, a1, a2, a3, a4, a5, a6, a7) { functionPointers[index](a1, a2, a3, a4, a5, a6, a7) } function jsCall_viiiii(index, a1, a2, a3, a4, a5) { functionPointers[index](a1, a2, a3, a4, a5) } function jsCall_viiiiidd(index, a1, a2, a3, a4, a5, a6, a7) { functionPointers[index](a1, a2, a3, a4, a5, a6, a7) } function jsCall_viiiiiddi(index, a1, a2, a3, a4, a5, a6, a7, a8) { functionPointers[index](a1, a2, a3, a4, a5, a6, a7, a8) } function jsCall_viiiiii(index, a1, a2, a3, a4, a5, a6) { functionPointers[index](a1, a2, a3, a4, a5, a6) } function jsCall_viiiiiifi(index, a1, a2, a3, a4, a5, a6, a7, a8) { functionPointers[index](a1, a2, a3, a4, a5, a6, a7, a8) } function jsCall_viiiiiii(index, a1, a2, a3, a4, a5, a6, a7) { functionPointers[index](a1, a2, a3, a4, a5, a6, a7) } function jsCall_viiiiiiii(index, a1, a2, a3, a4, a5, a6, a7, a8) { functionPointers[index](a1, a2, a3, a4, a5, a6, a7, a8) } function jsCall_viiiiiiiid(index, a1, a2, a3, a4, a5, a6, a7, a8, a9) { functionPointers[index](a1, a2, a3, a4, a5, a6, a7, a8, a9) } function jsCall_viiiiiiiidi(index, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) { functionPointers[index](a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) } function jsCall_viiiiiiiii(index, a1, a2, a3, a4, a5, a6, a7, a8, a9) { functionPointers[index](a1, a2, a3, a4, a5, a6, a7, a8, a9) } function jsCall_viiiiiiiiii(index, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) { functionPointers[index](a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) } function jsCall_viiiiiiiiiii(index, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11) { functionPointers[index](a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11) } function jsCall_viiiiiiiiiiii(index, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12) { functionPointers[index](a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12) } function jsCall_viiiiiiiiiiiiii(index, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14) { functionPointers[index](a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14) } function jsCall_viiijj(index, a1, a2, a3, a4, a5) { functionPointers[index](a1, a2, a3, a4, a5) } var asmGlobalArg = {}; var asmLibraryArg = { "___buildEnvironment": ___buildEnvironment, "___lock": ___lock, "___syscall221": ___syscall221, "___syscall3": ___syscall3, "___syscall5": ___syscall5, "___unlock": ___unlock, "___wasi_fd_close": ___wasi_fd_close, "___wasi_fd_fdstat_get": ___wasi_fd_fdstat_get, "___wasi_fd_seek": ___wasi_fd_seek, "___wasi_fd_write": ___wasi_fd_write, "__emscripten_fetch_free": __emscripten_fetch_free, "__memory_base": 1024, "__table_base": 0, "_abort": _abort, "_clock": _clock, "_clock_gettime": _clock_gettime, "_emscripten_asm_const_i": _emscripten_asm_const_i, "_emscripten_get_heap_size": _emscripten_get_heap_size, "_emscripten_is_main_browser_thread": _emscripten_is_main_browser_thread, "_emscripten_memcpy_big": _emscripten_memcpy_big, "_emscripten_resize_heap": _emscripten_resize_heap, "_emscripten_start_fetch": _emscripten_start_fetch, "_fabs": _fabs, "_getenv": _getenv, "_gettimeofday": _gettimeofday, "_gmtime_r": _gmtime_r, "_llvm_exp2_f64": _llvm_exp2_f64, "_llvm_log2_f32": _llvm_log2_f32, "_llvm_stackrestore": _llvm_stackrestore, "_llvm_stacksave": _llvm_stacksave, "_llvm_trunc_f64": _llvm_trunc_f64, "_localtime_r": _localtime_r, "_nanosleep": _nanosleep, "_pthread_cond_destroy": _pthread_cond_destroy, "_pthread_cond_init": _pthread_cond_init, "_pthread_create": _pthread_create, "_pthread_join": _pthread_join, "_strftime": _strftime, "_sysconf": _sysconf, "_time": _time, "abortStackOverflow": abortStackOverflow, "getTempRet0": getTempRet0, "jsCall_dd": jsCall_dd, "jsCall_did": jsCall_did, "jsCall_didd": jsCall_didd, "jsCall_fii": jsCall_fii, "jsCall_fiii": jsCall_fiii, "jsCall_ii": jsCall_ii, "jsCall_iid": jsCall_iid, "jsCall_iidiiii": jsCall_iidiiii, "jsCall_iii": jsCall_iii, "jsCall_iiii": jsCall_iiii, "jsCall_iiiii": jsCall_iiiii, "jsCall_iiiiii": jsCall_iiiiii, "jsCall_iiiiiii": jsCall_iiiiiii, "jsCall_iiiiiiidiiddii": jsCall_iiiiiiidiiddii, "jsCall_iiiiiiii": jsCall_iiiiiiii, "jsCall_iiiiiiiid": jsCall_iiiiiiiid, "jsCall_iiiiij": jsCall_iiiiij, "jsCall_iiiji": jsCall_iiiji, "jsCall_iiijjji": jsCall_iiijjji, "jsCall_jii": jsCall_jii, "jsCall_jiiij": jsCall_jiiij, "jsCall_jiiji": jsCall_jiiji, "jsCall_jij": jsCall_jij, "jsCall_jiji": jsCall_jiji, "jsCall_v": jsCall_v, "jsCall_vdiidiiiii": jsCall_vdiidiiiii, "jsCall_vdiidiiiiii": jsCall_vdiidiiiiii, "jsCall_vi": jsCall_vi, "jsCall_vii": jsCall_vii, "jsCall_viidi": jsCall_viidi, "jsCall_viifi": jsCall_viifi, "jsCall_viii": jsCall_viii, "jsCall_viiid": jsCall_viiid, "jsCall_viiii": jsCall_viiii, "jsCall_viiiifii": jsCall_viiiifii, "jsCall_viiiii": jsCall_viiiii, "jsCall_viiiiidd": jsCall_viiiiidd, "jsCall_viiiiiddi": jsCall_viiiiiddi, "jsCall_viiiiii": jsCall_viiiiii, "jsCall_viiiiiifi": jsCall_viiiiiifi, "jsCall_viiiiiii": jsCall_viiiiiii, "jsCall_viiiiiiii": jsCall_viiiiiiii, "jsCall_viiiiiiiid": jsCall_viiiiiiiid, "jsCall_viiiiiiiidi": jsCall_viiiiiiiidi, "jsCall_viiiiiiiii": jsCall_viiiiiiiii, "jsCall_viiiiiiiiii": jsCall_viiiiiiiiii, "jsCall_viiiiiiiiiii": jsCall_viiiiiiiiiii, "jsCall_viiiiiiiiiiii": jsCall_viiiiiiiiiiii, "jsCall_viiiiiiiiiiiiii": jsCall_viiiiiiiiiiiiii, "jsCall_viiijj": jsCall_viiijj, "memory": wasmMemory, "nullFunc_dd": nullFunc_dd, "nullFunc_did": nullFunc_did, "nullFunc_didd": nullFunc_didd, "nullFunc_fii": nullFunc_fii, "nullFunc_fiii": nullFunc_fiii, "nullFunc_ii": nullFunc_ii, "nullFunc_iid": nullFunc_iid, "nullFunc_iidiiii": nullFunc_iidiiii, "nullFunc_iii": nullFunc_iii, "nullFunc_iiii": nullFunc_iiii, "nullFunc_iiiii": nullFunc_iiiii, "nullFunc_iiiiii": nullFunc_iiiiii, "nullFunc_iiiiiii": nullFunc_iiiiiii, "nullFunc_iiiiiiidiiddii": nullFunc_iiiiiiidiiddii, "nullFunc_iiiiiiii": nullFunc_iiiiiiii, "nullFunc_iiiiiiiid": nullFunc_iiiiiiiid, "nullFunc_iiiiij": nullFunc_iiiiij, "nullFunc_iiiji": nullFunc_iiiji, "nullFunc_iiijjji": nullFunc_iiijjji, "nullFunc_jii": nullFunc_jii, "nullFunc_jiiij": nullFunc_jiiij, "nullFunc_jiiji": nullFunc_jiiji, "nullFunc_jij": nullFunc_jij, "nullFunc_jiji": nullFunc_jiji, "nullFunc_v": nullFunc_v, "nullFunc_vdiidiiiii": nullFunc_vdiidiiiii, "nullFunc_vdiidiiiiii": nullFunc_vdiidiiiiii, "nullFunc_vi": nullFunc_vi, "nullFunc_vii": nullFunc_vii, "nullFunc_viidi": nullFunc_viidi, "nullFunc_viifi": nullFunc_viifi, "nullFunc_viii": nullFunc_viii, "nullFunc_viiid": nullFunc_viiid, "nullFunc_viiii": nullFunc_viiii, "nullFunc_viiiifii": nullFunc_viiiifii, "nullFunc_viiiii": nullFunc_viiiii, "nullFunc_viiiiidd": nullFunc_viiiiidd, "nullFunc_viiiiiddi": nullFunc_viiiiiddi, "nullFunc_viiiiii": nullFunc_viiiiii, "nullFunc_viiiiiifi": nullFunc_viiiiiifi, "nullFunc_viiiiiii": nullFunc_viiiiiii, "nullFunc_viiiiiiii": nullFunc_viiiiiiii, "nullFunc_viiiiiiiid": nullFunc_viiiiiiiid, "nullFunc_viiiiiiiidi": nullFunc_viiiiiiiidi, "nullFunc_viiiiiiiii": nullFunc_viiiiiiiii, "nullFunc_viiiiiiiiii": nullFunc_viiiiiiiiii, "nullFunc_viiiiiiiiiii": nullFunc_viiiiiiiiiii, "nullFunc_viiiiiiiiiiii": nullFunc_viiiiiiiiiiii, "nullFunc_viiiiiiiiiiiiii": nullFunc_viiiiiiiiiiiiii, "nullFunc_viiijj": nullFunc_viiijj, "table": wasmTable }; var asm = Module["asm"](asmGlobalArg, asmLibraryArg, buffer); Module["asm"] = asm; var _AVPlayerInit = Module["_AVPlayerInit"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_AVPlayerInit"].apply(null, arguments) }; var _AVSniffHttpFlvInit = Module["_AVSniffHttpFlvInit"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_AVSniffHttpFlvInit"].apply(null, arguments) }; var _AVSniffHttpG711Init = Module["_AVSniffHttpG711Init"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_AVSniffHttpG711Init"].apply(null, arguments) }; var _AVSniffStreamInit = Module["_AVSniffStreamInit"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_AVSniffStreamInit"].apply(null, arguments) }; var ___emscripten_environ_constructor = Module["___emscripten_environ_constructor"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["___emscripten_environ_constructor"].apply(null, arguments) }; var ___errno_location = Module["___errno_location"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["___errno_location"].apply(null, arguments) }; var __get_daylight = Module["__get_daylight"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["__get_daylight"].apply(null, arguments) }; var __get_timezone = Module["__get_timezone"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["__get_timezone"].apply(null, arguments) }; var __get_tzname = Module["__get_tzname"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["__get_tzname"].apply(null, arguments) }; var _closeVideo = Module["_closeVideo"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_closeVideo"].apply(null, arguments) }; var _decodeCodecContext = Module["_decodeCodecContext"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_decodeCodecContext"].apply(null, arguments) }; var _decodeG711Frame = Module["_decodeG711Frame"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_decodeG711Frame"].apply(null, arguments) }; var _decodeHttpFlvVideoFrame = Module["_decodeHttpFlvVideoFrame"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_decodeHttpFlvVideoFrame"].apply(null, arguments) }; var _decodeVideoFrame = Module["_decodeVideoFrame"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_decodeVideoFrame"].apply(null, arguments) }; var _demuxBox = Module["_demuxBox"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_demuxBox"].apply(null, arguments) }; var _exitMissile = Module["_exitMissile"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_exitMissile"].apply(null, arguments) }; var _exitTsMissile = Module["_exitTsMissile"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_exitTsMissile"].apply(null, arguments) }; var _fflush = Module["_fflush"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_fflush"].apply(null, arguments) }; var _free = Module["_free"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_free"].apply(null, arguments) }; var _getAudioCodecID = Module["_getAudioCodecID"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_getAudioCodecID"].apply(null, arguments) }; var _getBufferLengthApi = Module["_getBufferLengthApi"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_getBufferLengthApi"].apply(null, arguments) }; var _getExtensionInfo = Module["_getExtensionInfo"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_getExtensionInfo"].apply(null, arguments) }; var _getG711BufferLengthApi = Module["_getG711BufferLengthApi"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_getG711BufferLengthApi"].apply(null, arguments) }; var _getMediaInfo = Module["_getMediaInfo"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_getMediaInfo"].apply(null, arguments) }; var _getPPS = Module["_getPPS"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_getPPS"].apply(null, arguments) }; var _getPPSLen = Module["_getPPSLen"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_getPPSLen"].apply(null, arguments) }; var _getPacket = Module["_getPacket"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_getPacket"].apply(null, arguments) }; var _getSEI = Module["_getSEI"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_getSEI"].apply(null, arguments) }; var _getSEILen = Module["_getSEILen"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_getSEILen"].apply(null, arguments) }; var _getSPS = Module["_getSPS"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_getSPS"].apply(null, arguments) }; var _getSPSLen = Module["_getSPSLen"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_getSPSLen"].apply(null, arguments) }; var _getSniffHttpFlvPkg = Module["_getSniffHttpFlvPkg"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_getSniffHttpFlvPkg"].apply(null, arguments) }; var _getSniffHttpFlvPkgNoCheckProbe = Module["_getSniffHttpFlvPkgNoCheckProbe"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_getSniffHttpFlvPkgNoCheckProbe"].apply(null, arguments) }; var _getSniffStreamPkg = Module["_getSniffStreamPkg"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_getSniffStreamPkg"].apply(null, arguments) }; var _getSniffStreamPkgNoCheckProbe = Module["_getSniffStreamPkgNoCheckProbe"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_getSniffStreamPkgNoCheckProbe"].apply(null, arguments) }; var _getVLC = Module["_getVLC"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_getVLC"].apply(null, arguments) }; var _getVLCLen = Module["_getVLCLen"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_getVLCLen"].apply(null, arguments) }; var _getVPS = Module["_getVPS"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_getVPS"].apply(null, arguments) }; var _getVPSLen = Module["_getVPSLen"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_getVPSLen"].apply(null, arguments) }; var _getVideoCodecID = Module["_getVideoCodecID"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_getVideoCodecID"].apply(null, arguments) }; var _initTsMissile = Module["_initTsMissile"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_initTsMissile"].apply(null, arguments) }; var _initializeDecoder = Module["_initializeDecoder"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_initializeDecoder"].apply(null, arguments) }; var _initializeDemuxer = Module["_initializeDemuxer"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_initializeDemuxer"].apply(null, arguments) }; var _initializeSniffG711Module = Module["_initializeSniffG711Module"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_initializeSniffG711Module"].apply(null, arguments) }; var _initializeSniffHttpFlvModule = Module["_initializeSniffHttpFlvModule"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_initializeSniffHttpFlvModule"].apply(null, arguments) }; var _initializeSniffHttpFlvModuleWithAOpt = Module["_initializeSniffHttpFlvModuleWithAOpt"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_initializeSniffHttpFlvModuleWithAOpt"].apply(null, arguments) }; var _initializeSniffStreamModule = Module["_initializeSniffStreamModule"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_initializeSniffStreamModule"].apply(null, arguments) }; var _initializeSniffStreamModuleWithAOpt = Module["_initializeSniffStreamModuleWithAOpt"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_initializeSniffStreamModuleWithAOpt"].apply(null, arguments) }; var _main = Module["_main"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_main"].apply(null, arguments) }; var _malloc = Module["_malloc"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_malloc"].apply(null, arguments) }; var _naluLListLength = Module["_naluLListLength"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_naluLListLength"].apply(null, arguments) }; var _pushSniffG711FlvData = Module["_pushSniffG711FlvData"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_pushSniffG711FlvData"].apply(null, arguments) }; var _pushSniffHttpFlvData = Module["_pushSniffHttpFlvData"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_pushSniffHttpFlvData"].apply(null, arguments) }; var _pushSniffStreamData = Module["_pushSniffStreamData"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_pushSniffStreamData"].apply(null, arguments) }; var _registerPlayer = Module["_registerPlayer"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_registerPlayer"].apply(null, arguments) }; var _release = Module["_release"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_release"].apply(null, arguments) }; var _releaseG711 = Module["_releaseG711"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_releaseG711"].apply(null, arguments) }; var _releaseHttpFLV = Module["_releaseHttpFLV"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_releaseHttpFLV"].apply(null, arguments) }; var _releaseSniffHttpFlv = Module["_releaseSniffHttpFlv"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_releaseSniffHttpFlv"].apply(null, arguments) }; var _releaseSniffStream = Module["_releaseSniffStream"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_releaseSniffStream"].apply(null, arguments) }; var _setCodecType = Module["_setCodecType"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["_setCodecType"].apply(null, arguments) }; var establishStackSpace = Module["establishStackSpace"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["establishStackSpace"].apply(null, arguments) }; var stackAlloc = Module["stackAlloc"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["stackAlloc"].apply(null, arguments) }; var stackRestore = Module["stackRestore"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["stackRestore"].apply(null, arguments) }; var stackSave = Module["stackSave"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["stackSave"].apply(null, arguments) }; var dynCall_v = Module["dynCall_v"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["dynCall_v"].apply(null, arguments) }; var dynCall_vi = Module["dynCall_vi"] = function() { assert(runtimeInitialized, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"); assert(!runtimeExited, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"); return Module["asm"]["dynCall_vi"].apply(null, arguments) }; Module["asm"] = asm; if (!Object.getOwnPropertyDescriptor(Module, "intArrayFromString")) Module["intArrayFromString"] = function() { abort("'intArrayFromString' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "intArrayToString")) Module["intArrayToString"] = function() { abort("'intArrayToString' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; Module["ccall"] = ccall; Module["cwrap"] = cwrap; if (!Object.getOwnPropertyDescriptor(Module, "setValue")) Module["setValue"] = function() { abort("'setValue' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "getValue")) Module["getValue"] = function() { abort("'getValue' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "allocate")) Module["allocate"] = function() { abort("'allocate' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "getMemory")) Module["getMemory"] = function() { abort("'getMemory' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ). Alternatively, forcing filesystem support (-s FORCE_FILESYSTEM=1) can export this for you") }; if (!Object.getOwnPropertyDescriptor(Module, "AsciiToString")) Module["AsciiToString"] = function() { abort("'AsciiToString' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "stringToAscii")) Module["stringToAscii"] = function() { abort("'stringToAscii' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "UTF8ArrayToString")) Module["UTF8ArrayToString"] = function() { abort("'UTF8ArrayToString' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "UTF8ToString")) Module["UTF8ToString"] = function() { abort("'UTF8ToString' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "stringToUTF8Array")) Module["stringToUTF8Array"] = function() { abort("'stringToUTF8Array' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "stringToUTF8")) Module["stringToUTF8"] = function() { abort("'stringToUTF8' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "lengthBytesUTF8")) Module["lengthBytesUTF8"] = function() { abort("'lengthBytesUTF8' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "UTF16ToString")) Module["UTF16ToString"] = function() { abort("'UTF16ToString' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "stringToUTF16")) Module["stringToUTF16"] = function() { abort("'stringToUTF16' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "lengthBytesUTF16")) Module["lengthBytesUTF16"] = function() { abort("'lengthBytesUTF16' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "UTF32ToString")) Module["UTF32ToString"] = function() { abort("'UTF32ToString' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "stringToUTF32")) Module["stringToUTF32"] = function() { abort("'stringToUTF32' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "lengthBytesUTF32")) Module["lengthBytesUTF32"] = function() { abort("'lengthBytesUTF32' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "allocateUTF8")) Module["allocateUTF8"] = function() { abort("'allocateUTF8' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "stackTrace")) Module["stackTrace"] = function() { abort("'stackTrace' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "addOnPreRun")) Module["addOnPreRun"] = function() { abort("'addOnPreRun' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "addOnInit")) Module["addOnInit"] = function() { abort("'addOnInit' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "addOnPreMain")) Module["addOnPreMain"] = function() { abort("'addOnPreMain' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "addOnExit")) Module["addOnExit"] = function() { abort("'addOnExit' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "addOnPostRun")) Module["addOnPostRun"] = function() { abort("'addOnPostRun' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "writeStringToMemory")) Module["writeStringToMemory"] = function() { abort("'writeStringToMemory' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "writeArrayToMemory")) Module["writeArrayToMemory"] = function() { abort("'writeArrayToMemory' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "writeAsciiToMemory")) Module["writeAsciiToMemory"] = function() { abort("'writeAsciiToMemory' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "addRunDependency")) Module["addRunDependency"] = function() { abort("'addRunDependency' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ). Alternatively, forcing filesystem support (-s FORCE_FILESYSTEM=1) can export this for you") }; if (!Object.getOwnPropertyDescriptor(Module, "removeRunDependency")) Module["removeRunDependency"] = function() { abort("'removeRunDependency' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ). Alternatively, forcing filesystem support (-s FORCE_FILESYSTEM=1) can export this for you") }; if (!Object.getOwnPropertyDescriptor(Module, "ENV")) Module["ENV"] = function() { abort("'ENV' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "FS")) Module["FS"] = function() { abort("'FS' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "FS_createFolder")) Module["FS_createFolder"] = function() { abort("'FS_createFolder' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ). Alternatively, forcing filesystem support (-s FORCE_FILESYSTEM=1) can export this for you") }; if (!Object.getOwnPropertyDescriptor(Module, "FS_createPath")) Module["FS_createPath"] = function() { abort("'FS_createPath' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ). Alternatively, forcing filesystem support (-s FORCE_FILESYSTEM=1) can export this for you") }; if (!Object.getOwnPropertyDescriptor(Module, "FS_createDataFile")) Module["FS_createDataFile"] = function() { abort("'FS_createDataFile' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ). Alternatively, forcing filesystem support (-s FORCE_FILESYSTEM=1) can export this for you") }; if (!Object.getOwnPropertyDescriptor(Module, "FS_createPreloadedFile")) Module["FS_createPreloadedFile"] = function() { abort("'FS_createPreloadedFile' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ). Alternatively, forcing filesystem support (-s FORCE_FILESYSTEM=1) can export this for you") }; if (!Object.getOwnPropertyDescriptor(Module, "FS_createLazyFile")) Module["FS_createLazyFile"] = function() { abort("'FS_createLazyFile' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ). Alternatively, forcing filesystem support (-s FORCE_FILESYSTEM=1) can export this for you") }; if (!Object.getOwnPropertyDescriptor(Module, "FS_createLink")) Module["FS_createLink"] = function() { abort("'FS_createLink' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ). Alternatively, forcing filesystem support (-s FORCE_FILESYSTEM=1) can export this for you") }; if (!Object.getOwnPropertyDescriptor(Module, "FS_createDevice")) Module["FS_createDevice"] = function() { abort("'FS_createDevice' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ). Alternatively, forcing filesystem support (-s FORCE_FILESYSTEM=1) can export this for you") }; if (!Object.getOwnPropertyDescriptor(Module, "FS_unlink")) Module["FS_unlink"] = function() { abort("'FS_unlink' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ). Alternatively, forcing filesystem support (-s FORCE_FILESYSTEM=1) can export this for you") }; if (!Object.getOwnPropertyDescriptor(Module, "GL")) Module["GL"] = function() { abort("'GL' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "dynamicAlloc")) Module["dynamicAlloc"] = function() { abort("'dynamicAlloc' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "loadDynamicLibrary")) Module["loadDynamicLibrary"] = function() { abort("'loadDynamicLibrary' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "loadWebAssemblyModule")) Module["loadWebAssemblyModule"] = function() { abort("'loadWebAssemblyModule' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "getLEB")) Module["getLEB"] = function() { abort("'getLEB' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "getFunctionTables")) Module["getFunctionTables"] = function() { abort("'getFunctionTables' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "alignFunctionTables")) Module["alignFunctionTables"] = function() { abort("'alignFunctionTables' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "registerFunctions")) Module["registerFunctions"] = function() { abort("'registerFunctions' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; Module["addFunction"] = addFunction; Module["removeFunction"] = removeFunction; if (!Object.getOwnPropertyDescriptor(Module, "getFuncWrapper")) Module["getFuncWrapper"] = function() { abort("'getFuncWrapper' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "prettyPrint")) Module["prettyPrint"] = function() { abort("'prettyPrint' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "makeBigInt")) Module["makeBigInt"] = function() { abort("'makeBigInt' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "dynCall")) Module["dynCall"] = function() { abort("'dynCall' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "getCompilerSetting")) Module["getCompilerSetting"] = function() { abort("'getCompilerSetting' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "stackSave")) Module["stackSave"] = function() { abort("'stackSave' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "stackRestore")) Module["stackRestore"] = function() { abort("'stackRestore' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "stackAlloc")) Module["stackAlloc"] = function() { abort("'stackAlloc' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "establishStackSpace")) Module["establishStackSpace"] = function() { abort("'establishStackSpace' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "print")) Module["print"] = function() { abort("'print' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "printErr")) Module["printErr"] = function() { abort("'printErr' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "getTempRet0")) Module["getTempRet0"] = function() { abort("'getTempRet0' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "setTempRet0")) Module["setTempRet0"] = function() { abort("'setTempRet0' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "callMain")) Module["callMain"] = function() { abort("'callMain' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "abort")) Module["abort"] = function() { abort("'abort' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "Pointer_stringify")) Module["Pointer_stringify"] = function() { abort("'Pointer_stringify' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "warnOnce")) Module["warnOnce"] = function() { abort("'warnOnce' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "ALLOC_NORMAL")) Object.defineProperty(Module, "ALLOC_NORMAL", { configurable: true, get: function() { abort("'ALLOC_NORMAL' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") } }); if (!Object.getOwnPropertyDescriptor(Module, "ALLOC_STACK")) Object.defineProperty(Module, "ALLOC_STACK", { configurable: true, get: function() { abort("'ALLOC_STACK' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") } }); if (!Object.getOwnPropertyDescriptor(Module, "ALLOC_DYNAMIC")) Object.defineProperty(Module, "ALLOC_DYNAMIC", { configurable: true, get: function() { abort("'ALLOC_DYNAMIC' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") } }); if (!Object.getOwnPropertyDescriptor(Module, "ALLOC_NONE")) Object.defineProperty(Module, "ALLOC_NONE", { configurable: true, get: function() { abort("'ALLOC_NONE' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") } }); if (!Object.getOwnPropertyDescriptor(Module, "calledRun")) Object.defineProperty(Module, "calledRun", { configurable: true, get: function() { abort("'calledRun' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ). Alternatively, forcing filesystem support (-s FORCE_FILESYSTEM=1) can export this for you") } }); var calledRun; function ExitStatus(status) { this.name = "ExitStatus"; this.message = "Program terminated with exit(" + status + ")"; this.status = status } var calledMain = false; dependenciesFulfilled = function runCaller() { if (!calledRun) run(); if (!calledRun) dependenciesFulfilled = runCaller }; function callMain(args) { assert(runDependencies == 0, 'cannot call main when async dependencies remain! (listen on Module["onRuntimeInitialized"])'); assert(__ATPRERUN__.length == 0, "cannot call main when preRun functions remain to be called"); args = args || []; var argc = args.length + 1; var argv = stackAlloc((argc + 1) * 4); HEAP32[argv >> 2] = allocateUTF8OnStack(thisProgram); for (var i = 1; i < argc; i++) { HEAP32[(argv >> 2) + i] = allocateUTF8OnStack(args[i - 1]) } HEAP32[(argv >> 2) + argc] = 0; try { var ret = Module["_main"](argc, argv); exit(ret, true) } catch (e) { if (e instanceof ExitStatus) { return } else if (e == "SimulateInfiniteLoop") { noExitRuntime = true; return } else { var toLog = e; if (e && typeof e === "object" && e.stack) { toLog = [e, e.stack] } err("exception thrown: " + toLog); quit_(1, e) } } finally { calledMain = true } } function run(args) { args = args || arguments_; if (runDependencies > 0) { return } writeStackCookie(); preRun(); if (runDependencies > 0) return; function doRun() { if (calledRun) return; calledRun = true; if (ABORT) return; initRuntime(); preMain(); if (Module["onRuntimeInitialized"]) Module["onRuntimeInitialized"](); if (shouldRunNow) callMain(args); postRun() } if (Module["setStatus"]) { Module["setStatus"]("Running..."); setTimeout(function() { setTimeout(function() { Module["setStatus"]("") }, 1); doRun() }, 1) } else { doRun() } checkStackCookie() } Module["run"] = run; function checkUnflushedContent() { var print = out; var printErr = err; var has = false; out = err = function(x) { has = true }; try { var flush = Module["_fflush"]; if (flush) flush(0); ["stdout", "stderr"].forEach(function(name) { var info = FS.analyzePath("/dev/" + name); if (!info) return; var stream = info.object; var rdev = stream.rdev; var tty = TTY.ttys[rdev]; if (tty && tty.output && tty.output.length) { has = true } }) } catch (e) {} out = print; err = printErr; if (has) { warnOnce("stdio streams had content in them that was not flushed. you should set EXIT_RUNTIME to 1 (see the FAQ), or make sure to emit a newline when you printf etc.") } } function exit(status, implicit) { checkUnflushedContent(); if (implicit && noExitRuntime && status === 0) { return } if (noExitRuntime) { if (!implicit) { err("exit(" + status + ") called, but EXIT_RUNTIME is not set, so halting execution but not exiting the runtime or preventing further async execution (build with EXIT_RUNTIME=1, if you want a true shutdown)") } } else { ABORT = true; EXITSTATUS = status; exitRuntime(); if (Module["onExit"]) Module["onExit"](status) } quit_(status, new ExitStatus(status)) } if (Module["preInit"]) { if (typeof Module["preInit"] == "function") Module["preInit"] = [Module["preInit"]]; while (Module["preInit"].length > 0) { Module["preInit"].pop()() } } var shouldRunNow = true; if (Module["noInitialRun"]) shouldRunNow = false; noExitRuntime = true; run(); ================================================ FILE: web/public/static/js/jessibuca/decoder.js ================================================ !function(e,r){"object"==typeof exports&&"undefined"!=typeof module?r(require("path"),require("fs"),require("crypto")):"function"==typeof define&&define.amd?define(["path","fs","crypto"],r):r((e="undefined"!=typeof globalThis?globalThis:e||self).path,e.fs,e.crypto$1)}(this,(function(e,r,t){"use strict";function n(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var o=n(e),i=n(r),a=n(t);function s(e,r){return e(r={exports:{}},r.exports),r.exports}var l=s((function(e){var r=void 0!==r?r:{},t=(r={print:function(e){console.log("Jessibuca: [worker]:",e)},printErr:function(e){console.warn("Jessibuca: [worker]:",e),postMessage({cmd:"wasmError",message:e})}},Object.assign({},r)),n="./this.program",s="object"==typeof window,l="function"==typeof importScripts,u="object"==typeof process&&"object"==typeof process.versions&&"string"==typeof process.versions.node,c=!s&&!u&&!l;if(r.ENVIRONMENT)throw new Error("Module.ENVIRONMENT has been deprecated. To force the environment, use the ENVIRONMENT compile-time option (for example, -sENVIRONMENT=web or -sENVIRONMENT=node)");var d,f,p,m,h,g,v="";if(u){if("object"!=typeof process)throw new Error("not compiled for this environment (did you build to HTML and try to run it not on the web, or set ENVIRONMENT to something - like node - and run it someplace else - like on the web?)");v=l?o.default.dirname(v)+"/":__dirname+"/",g=()=>{h||(m=i.default,h=o.default)},d=function(e,r){return g(),e=h.normalize(e),m.readFileSync(e,r?void 0:"utf8")},p=e=>{var r=d(e,!0);return r.buffer||(r=new Uint8Array(r)),F(r.buffer),r},f=(e,r,t)=>{g(),e=h.normalize(e),m.readFile(e,(function(e,n){e?t(e):r(n.buffer)}))},process.argv.length>1&&(n=process.argv[1].replace(/\\/g,"/")),process.argv.slice(2),e.exports=r,process.on("uncaughtException",(function(e){if(!(e instanceof kt))throw e})),process.on("unhandledRejection",(function(e){throw e})),r.inspect=function(){return"[Emscripten Module object]"}}else if(c){if("object"==typeof process||"object"==typeof window||"function"==typeof importScripts)throw new Error("not compiled for this environment (did you build to HTML and try to run it not on the web, or set ENVIRONMENT to something - like node - and run it someplace else - like on the web?)");"undefined"!=typeof read&&(d=function(e){return read(e)}),p=function(e){let r;return"function"==typeof readbuffer?new Uint8Array(readbuffer(e)):(r=read(e,"binary"),F("object"==typeof r),r)},f=function(e,r,t){setTimeout((()=>r(p(e))),0)},"undefined"!=typeof scriptArgs&&scriptArgs,"undefined"!=typeof print&&("undefined"==typeof console&&(console={}),console.log=print,console.warn=console.error="undefined"!=typeof printErr?printErr:print)}else{if(!s&&!l)throw new Error("environment detection error");if(l?v=self.location.href:"undefined"!=typeof document&&document.currentScript&&(v=document.currentScript.src),v=0!==v.indexOf("blob:")?v.substr(0,v.replace(/[?#].*/,"").lastIndexOf("/")+1):"","object"!=typeof window&&"function"!=typeof importScripts)throw new Error("not compiled for this environment (did you build to HTML and try to run it not on the web, or set ENVIRONMENT to something - like node - and run it someplace else - like on the web?)");d=e=>{var r=new XMLHttpRequest;return r.open("GET",e,!1),r.send(null),r.responseText},l&&(p=e=>{var r=new XMLHttpRequest;return r.open("GET",e,!1),r.responseType="arraybuffer",r.send(null),new Uint8Array(r.response)}),f=(e,r,t)=>{var n=new XMLHttpRequest;n.open("GET",e,!0),n.responseType="arraybuffer",n.onload=()=>{200==n.status||0==n.status&&n.response?r(n.response):t()},n.onerror=t,n.send(null)}}var y,E,w,b=r.print||console.log.bind(console),_=r.printErr||console.warn.bind(console);function T(e){T.shown||(T.shown={}),T.shown[e]||(T.shown[e]=1,_(e))}function k(e,t){Object.getOwnPropertyDescriptor(r,e)||Object.defineProperty(r,e,{configurable:!0,get:function(){ge("Module."+e+" has been replaced with plain "+t+" (the initial value can be provided on Module, but after startup the value is only looked for on a local variable of that name)")}})}function S(e,r){var t="'"+e+"' was not exported. add it to EXPORTED_RUNTIME_METHODS (see the FAQ)";return r&&(t+=". Alternatively, forcing filesystem support (-sFORCE_FILESYSTEM) can export this for you"),t}function C(e,t){Object.getOwnPropertyDescriptor(r,e)||Object.defineProperty(r,e,{configurable:!0,get:function(){ge(S(e,t))}})}function P(e,t){Object.getOwnPropertyDescriptor(r,e)||(r[e]=()=>ge(S(e,t)))}Object.assign(r,t),t=null,y="fetchSettings",Object.getOwnPropertyDescriptor(r,y)&&ge("`Module."+y+"` was supplied but `"+y+"` not included in INCOMING_MODULE_JS_API"),r.arguments&&r.arguments,k("arguments","arguments_"),r.thisProgram&&(n=r.thisProgram),k("thisProgram","thisProgram"),r.quit&&r.quit,k("quit","quit_"),F(void 0===r.memoryInitializerPrefixURL,"Module.memoryInitializerPrefixURL option was removed, use Module.locateFile instead"),F(void 0===r.pthreadMainPrefixURL,"Module.pthreadMainPrefixURL option was removed, use Module.locateFile instead"),F(void 0===r.cdInitializerPrefixURL,"Module.cdInitializerPrefixURL option was removed, use Module.locateFile instead"),F(void 0===r.filePackagePrefixURL,"Module.filePackagePrefixURL option was removed, use Module.locateFile instead"),F(void 0===r.read,"Module.read option was removed (modify read_ in JS)"),F(void 0===r.readAsync,"Module.readAsync option was removed (modify readAsync in JS)"),F(void 0===r.readBinary,"Module.readBinary option was removed (modify readBinary in JS)"),F(void 0===r.setWindowTitle,"Module.setWindowTitle option was removed (modify setWindowTitle in JS)"),F(void 0===r.TOTAL_MEMORY,"Module.TOTAL_MEMORY has been renamed Module.INITIAL_MEMORY"),k("read","read_"),k("readAsync","readAsync"),k("readBinary","readBinary"),k("setWindowTitle","setWindowTitle"),F(!c,"shell environment detected but not enabled at build time. Add 'shell' to `-sENVIRONMENT` to enable."),r.wasmBinary&&(E=r.wasmBinary),k("wasmBinary","wasmBinary"),r.noExitRuntime,k("noExitRuntime","noExitRuntime"),"object"!=typeof WebAssembly&&ge("no native wasm support detected");var A=!1;function F(e,r){e||ge("Assertion failed"+(r?": "+r:""))}var D="undefined"!=typeof TextDecoder?new TextDecoder("utf8"):void 0;function O(e,r,t){for(var n=r+t,o=r;e[o]&&!(o>=n);)++o;if(o-r>16&&e.buffer&&D)return D.decode(e.subarray(r,o));for(var i="";r>10,56320|1023&u)}}else i+=String.fromCharCode((31&a)<<6|s)}else i+=String.fromCharCode(a)}return i}function R(e,r){return e?O(U,e,r):""}function M(e,r,t,n){if(!(n>0))return 0;for(var o=t,i=t+n-1,a=0;a=55296&&s<=57343)s=65536+((1023&s)<<10)|1023&e.charCodeAt(++a);if(s<=127){if(t>=i)break;r[t++]=s}else if(s<=2047){if(t+1>=i)break;r[t++]=192|s>>6,r[t++]=128|63&s}else if(s<=65535){if(t+2>=i)break;r[t++]=224|s>>12,r[t++]=128|s>>6&63,r[t++]=128|63&s}else{if(t+3>=i)break;s>1114111&&T("Invalid Unicode code point 0x"+s.toString(16)+" encountered when serializing a JS string to a UTF-8 string in wasm memory! (Valid unicode code points should be in range 0-0x10FFFF)."),r[t++]=240|s>>18,r[t++]=128|s>>12&63,r[t++]=128|s>>6&63,r[t++]=128|63&s}}return r[t]=0,t-o}function N(e,r,t){return F("number"==typeof t,"stringToUTF8(str, outPtr, maxBytesToWrite) is missing the third parameter that specifies the length of the output buffer!"),M(e,U,r,t)}function I(e){for(var r=0,t=0;t=55296&&n<=57343&&(n=65536+((1023&n)<<10)|1023&e.charCodeAt(++t)),n<=127?++r:r+=n<=2047?2:n<=65535?3:4}return r}var L,x,U,B,j,$,W,z,H,G="undefined"!=typeof TextDecoder?new TextDecoder("utf-16le"):void 0;function V(e,r){F(e%2==0,"Pointer passed to UTF16ToString must be aligned to two bytes!");for(var t=e,n=t>>1,o=n+r/2;!(n>=o)&&j[n];)++n;if((t=n<<1)-e>32&&G)return G.decode(U.subarray(e,t));for(var i="",a=0;!(a>=r/2);++a){var s=B[e+2*a>>1];if(0==s)break;i+=String.fromCharCode(s)}return i}function q(e,r,t){if(F(r%2==0,"Pointer passed to stringToUTF16 must be aligned to two bytes!"),F("number"==typeof t,"stringToUTF16(str, outPtr, maxBytesToWrite) is missing the third parameter that specifies the length of the output buffer!"),void 0===t&&(t=2147483647),t<2)return 0;for(var n=r,o=(t-=2)<2*e.length?t/2:e.length,i=0;i>1]=a,r+=2}return B[r>>1]=0,r-n}function Y(e){return 2*e.length}function X(e,r){F(e%4==0,"Pointer passed to UTF32ToString must be aligned to four bytes!");for(var t=0,n="";!(t>=r/4);){var o=$[e+4*t>>2];if(0==o)break;if(++t,o>=65536){var i=o-65536;n+=String.fromCharCode(55296|i>>10,56320|1023&i)}else n+=String.fromCharCode(o)}return n}function K(e,r,t){if(F(r%4==0,"Pointer passed to stringToUTF32 must be aligned to four bytes!"),F("number"==typeof t,"stringToUTF32(str, outPtr, maxBytesToWrite) is missing the third parameter that specifies the length of the output buffer!"),void 0===t&&(t=2147483647),t<4)return 0;for(var n=r,o=n+t-4,i=0;i=55296&&a<=57343)a=65536+((1023&a)<<10)|1023&e.charCodeAt(++i);if($[r>>2]=a,(r+=4)+4>o)break}return $[r>>2]=0,r-n}function J(e){for(var r=0,t=0;t=55296&&n<=57343&&++t,r+=4}return r}function Q(e){var r=I(e)+1,t=ht(r);return t&&M(e,x,t,r),t}function Z(e){L=e,r.HEAP8=x=new Int8Array(e),r.HEAP16=B=new Int16Array(e),r.HEAP32=$=new Int32Array(e),r.HEAPU8=U=new Uint8Array(e),r.HEAPU16=j=new Uint16Array(e),r.HEAPU32=W=new Uint32Array(e),r.HEAPF32=z=new Float32Array(e),r.HEAPF64=H=new Float64Array(e)}var ee=5242880;r.TOTAL_STACK&&F(ee===r.TOTAL_STACK,"the stack size can no longer be determined at runtime");var re,te=r.INITIAL_MEMORY||67108864;function ne(){var e=Tt();F(!(3&e)),$[e>>2]=34821223,$[e+4>>2]=2310721022,$[0]=1668509029}function oe(){if(!A){var e=Tt(),r=W[e>>2],t=W[e+4>>2];34821223==r&&2310721022==t||ge("Stack overflow! Stack cookie has been overwritten, expected hex dwords 0x89BACDFE and 0x2135467, but received 0x"+t.toString(16)+" 0x"+r.toString(16)),1668509029!==$[0]&&ge("Runtime error: The application has corrupted its heap memory area (address zero)!")}}k("INITIAL_MEMORY","INITIAL_MEMORY"),F(te>=ee,"INITIAL_MEMORY should be larger than TOTAL_STACK, was "+te+"! (TOTAL_STACK="+ee+")"),F("undefined"!=typeof Int32Array&&"undefined"!=typeof Float64Array&&null!=Int32Array.prototype.subarray&&null!=Int32Array.prototype.set,"JS engine does not provide full typed array support"),F(!r.wasmMemory,"Use of `wasmMemory` detected. Use -sIMPORTED_MEMORY to define wasmMemory externally"),F(67108864==te,"Detected runtime INITIAL_MEMORY setting. Use -sIMPORTED_MEMORY to define wasmMemory dynamically"),function(){var e=new Int16Array(1),r=new Int8Array(e.buffer);if(e[0]=25459,115!==r[0]||99!==r[1])throw"Runtime error: expected the system to be little-endian! (Run with -sSUPPORT_BIG_ENDIAN to bypass)"}();var ie=[],ae=[],se=[],le=!1;F(Math.imul,"This browser does not support Math.imul(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill"),F(Math.fround,"This browser does not support Math.fround(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill"),F(Math.clz32,"This browser does not support Math.clz32(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill"),F(Math.trunc,"This browser does not support Math.trunc(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill");var ue=0,ce=null,de=null,fe={};function pe(e){for(var r=e;;){if(!fe[e])return e;e=r+Math.random()}}function me(e){ue++,r.monitorRunDependencies&&r.monitorRunDependencies(ue),e?(F(!fe[e]),fe[e]=1,null===ce&&"undefined"!=typeof setInterval&&(ce=setInterval((function(){if(A)return clearInterval(ce),void(ce=null);var e=!1;for(var r in fe)e||(e=!0,_("still waiting on run dependencies:")),_("dependency: "+r);e&&_("(end of list)")}),1e4))):_("warning: run dependency added without ID")}function he(e){if(ue--,r.monitorRunDependencies&&r.monitorRunDependencies(ue),e?(F(fe[e]),delete fe[e]):_("warning: run dependency removed without ID"),0==ue&&(null!==ce&&(clearInterval(ce),ce=null),de)){var t=de;de=null,t()}}function ge(e){throw r.onAbort&&r.onAbort(e),_(e="Aborted("+e+")"),A=!0,new WebAssembly.RuntimeError(e)}var ve,ye,Ee;function we(e){return e.startsWith("data:application/octet-stream;base64,")}function be(e){return e.startsWith("file://")}function _e(e,t){return function(){var n=e,o=t;return t||(o=r.asm),F(le,"native function `"+n+"` called before runtime initialization"),o[e]||F(o[e],"exported native function `"+n+"` not found"),o[e].apply(null,arguments)}}function Te(e){try{if(e==ve&&E)return new Uint8Array(E);if(p)return p(e);throw"both async and sync fetching of the wasm failed"}catch(e){ge(e)}}function ke(e){for(;e.length>0;){var t=e.shift();if("function"!=typeof t){var n=t.func;"number"==typeof n?void 0===t.arg?Ce(n)():Ce(n)(t.arg):n(void 0===t.arg?null:t.arg)}else t(r)}}function Se(e){return e.replace(/\b_Z[\w\d_]+/g,(function(e){var r,t=(r=e,T("warning: build with -sDEMANGLE_SUPPORT to link in libcxxabi demangling"),r);return e===t?e:t+" ["+e+"]"}))}function Ce(e){return re.get(e)}we(ve="decoder.wasm")||(ve=function(e){return r.locateFile?r.locateFile(e,v):v+e}(ve));var Pe={isAbs:e=>"/"===e.charAt(0),splitPath:e=>/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/.exec(e).slice(1),normalizeArray:(e,r)=>{for(var t=0,n=e.length-1;n>=0;n--){var o=e[n];"."===o?e.splice(n,1):".."===o?(e.splice(n,1),t++):t&&(e.splice(n,1),t--)}if(r)for(;t;t--)e.unshift("..");return e},normalize:e=>{var r=Pe.isAbs(e),t="/"===e.substr(-1);return(e=Pe.normalizeArray(e.split("/").filter((e=>!!e)),!r).join("/"))||r||(e="."),e&&t&&(e+="/"),(r?"/":"")+e},dirname:e=>{var r=Pe.splitPath(e),t=r[0],n=r[1];return t||n?(n&&(n=n.substr(0,n.length-1)),t+n):"."},basename:e=>{if("/"===e)return"/";var r=(e=(e=Pe.normalize(e)).replace(/\/$/,"")).lastIndexOf("/");return-1===r?e:e.substr(r+1)},join:function(){var e=Array.prototype.slice.call(arguments,0);return Pe.normalize(e.join("/"))},join2:(e,r)=>Pe.normalize(e+"/"+r)};var Ae={resolve:function(){for(var e="",r=!1,t=arguments.length-1;t>=-1&&!r;t--){var n=t>=0?arguments[t]:Ne.cwd();if("string"!=typeof n)throw new TypeError("Arguments to path.resolve must be strings");if(!n)return"";e=n+"/"+e,r=Pe.isAbs(n)}return(r?"/":"")+(e=Pe.normalizeArray(e.split("/").filter((e=>!!e)),!r).join("/"))||"."},relative:(e,r)=>{function t(e){for(var r=0;r=0&&""===e[t];t--);return r>t?[]:e.slice(r,t-r+1)}e=Ae.resolve(e).substr(1),r=Ae.resolve(r).substr(1);for(var n=t(e.split("/")),o=t(r.split("/")),i=Math.min(n.length,o.length),a=i,s=0;s0?t.slice(0,n).toString("utf-8"):null}else"undefined"!=typeof window&&"function"==typeof window.prompt?null!==(r=window.prompt("Input: "))&&(r+="\n"):"function"==typeof readline&&null!==(r=readline())&&(r+="\n");if(!r)return null;e.input=ft(r,!0)}return e.input.shift()},put_char:function(e,r){null===r||10===r?(b(O(e.output,0)),e.output=[]):0!=r&&e.output.push(r)},flush:function(e){e.output&&e.output.length>0&&(b(O(e.output,0)),e.output=[])}},default_tty1_ops:{put_char:function(e,r){null===r||10===r?(_(O(e.output,0)),e.output=[]):0!=r&&e.output.push(r)},flush:function(e){e.output&&e.output.length>0&&(_(O(e.output,0)),e.output=[])}}};function De(e){e=function(e,r){return F(r,"alignment argument is required"),Math.ceil(e/r)*r}(e,65536);var r=wt(65536,e);return r?(function(e,r){U.fill(0,e,e+r)}(r,e),r):0}var Oe={ops_table:null,mount:function(e){return Oe.createNode(null,"/",16895,0)},createNode:function(e,r,t,n){if(Ne.isBlkdev(t)||Ne.isFIFO(t))throw new Ne.ErrnoError(63);Oe.ops_table||(Oe.ops_table={dir:{node:{getattr:Oe.node_ops.getattr,setattr:Oe.node_ops.setattr,lookup:Oe.node_ops.lookup,mknod:Oe.node_ops.mknod,rename:Oe.node_ops.rename,unlink:Oe.node_ops.unlink,rmdir:Oe.node_ops.rmdir,readdir:Oe.node_ops.readdir,symlink:Oe.node_ops.symlink},stream:{llseek:Oe.stream_ops.llseek}},file:{node:{getattr:Oe.node_ops.getattr,setattr:Oe.node_ops.setattr},stream:{llseek:Oe.stream_ops.llseek,read:Oe.stream_ops.read,write:Oe.stream_ops.write,allocate:Oe.stream_ops.allocate,mmap:Oe.stream_ops.mmap,msync:Oe.stream_ops.msync}},link:{node:{getattr:Oe.node_ops.getattr,setattr:Oe.node_ops.setattr,readlink:Oe.node_ops.readlink},stream:{}},chrdev:{node:{getattr:Oe.node_ops.getattr,setattr:Oe.node_ops.setattr},stream:Ne.chrdev_stream_ops}});var o=Ne.createNode(e,r,t,n);return Ne.isDir(o.mode)?(o.node_ops=Oe.ops_table.dir.node,o.stream_ops=Oe.ops_table.dir.stream,o.contents={}):Ne.isFile(o.mode)?(o.node_ops=Oe.ops_table.file.node,o.stream_ops=Oe.ops_table.file.stream,o.usedBytes=0,o.contents=null):Ne.isLink(o.mode)?(o.node_ops=Oe.ops_table.link.node,o.stream_ops=Oe.ops_table.link.stream):Ne.isChrdev(o.mode)&&(o.node_ops=Oe.ops_table.chrdev.node,o.stream_ops=Oe.ops_table.chrdev.stream),o.timestamp=Date.now(),e&&(e.contents[r]=o,e.timestamp=o.timestamp),o},getFileDataAsTypedArray:function(e){return e.contents?e.contents.subarray?e.contents.subarray(0,e.usedBytes):new Uint8Array(e.contents):new Uint8Array(0)},expandFileStorage:function(e,r){var t=e.contents?e.contents.length:0;if(!(t>=r)){r=Math.max(r,t*(t<1048576?2:1.125)>>>0),0!=t&&(r=Math.max(r,256));var n=e.contents;e.contents=new Uint8Array(r),e.usedBytes>0&&e.contents.set(n.subarray(0,e.usedBytes),0)}},resizeFileStorage:function(e,r){if(e.usedBytes!=r)if(0==r)e.contents=null,e.usedBytes=0;else{var t=e.contents;e.contents=new Uint8Array(r),t&&e.contents.set(t.subarray(0,Math.min(r,e.usedBytes))),e.usedBytes=r}},node_ops:{getattr:function(e){var r={};return r.dev=Ne.isChrdev(e.mode)?e.id:1,r.ino=e.id,r.mode=e.mode,r.nlink=1,r.uid=0,r.gid=0,r.rdev=e.rdev,Ne.isDir(e.mode)?r.size=4096:Ne.isFile(e.mode)?r.size=e.usedBytes:Ne.isLink(e.mode)?r.size=e.link.length:r.size=0,r.atime=new Date(e.timestamp),r.mtime=new Date(e.timestamp),r.ctime=new Date(e.timestamp),r.blksize=4096,r.blocks=Math.ceil(r.size/r.blksize),r},setattr:function(e,r){void 0!==r.mode&&(e.mode=r.mode),void 0!==r.timestamp&&(e.timestamp=r.timestamp),void 0!==r.size&&Oe.resizeFileStorage(e,r.size)},lookup:function(e,r){throw Ne.genericErrors[44]},mknod:function(e,r,t,n){return Oe.createNode(e,r,t,n)},rename:function(e,r,t){if(Ne.isDir(e.mode)){var n;try{n=Ne.lookupNode(r,t)}catch(e){}if(n)for(var o in n.contents)throw new Ne.ErrnoError(55)}delete e.parent.contents[e.name],e.parent.timestamp=Date.now(),e.name=t,r.contents[t]=e,r.timestamp=e.parent.timestamp,e.parent=r},unlink:function(e,r){delete e.contents[r],e.timestamp=Date.now()},rmdir:function(e,r){var t=Ne.lookupNode(e,r);for(var n in t.contents)throw new Ne.ErrnoError(55);delete e.contents[r],e.timestamp=Date.now()},readdir:function(e){var r=[".",".."];for(var t in e.contents)e.contents.hasOwnProperty(t)&&r.push(t);return r},symlink:function(e,r,t){var n=Oe.createNode(e,r,41471,0);return n.link=t,n},readlink:function(e){if(!Ne.isLink(e.mode))throw new Ne.ErrnoError(28);return e.link}},stream_ops:{read:function(e,r,t,n,o){var i=e.node.contents;if(o>=e.node.usedBytes)return 0;var a=Math.min(e.node.usedBytes-o,n);if(F(a>=0),a>8&&i.subarray)r.set(i.subarray(o,o+a),t);else for(var s=0;s0||n+t1&&void 0!==arguments[1]?arguments[1]:{};if(!(e=Ae.resolve(Ne.cwd(),e)))return{path:"",node:null};if(r=Object.assign({follow_mount:!0,recurse_count:0},r),r.recurse_count>8)throw new Ne.ErrnoError(32);for(var t=Pe.normalizeArray(e.split("/").filter((e=>!!e)),!1),n=Ne.root,o="/",i=0;i40)throw new Ne.ErrnoError(32)}}return{path:o,node:n}},getPath:e=>{for(var r;;){if(Ne.isRoot(e)){var t=e.mount.mountpoint;return r?"/"!==t[t.length-1]?t+"/"+r:t+r:t}r=r?e.name+"/"+r:e.name,e=e.parent}},hashName:(e,r)=>{for(var t=0,n=0;n>>0)%Ne.nameTable.length},hashAddNode:e=>{var r=Ne.hashName(e.parent.id,e.name);e.name_next=Ne.nameTable[r],Ne.nameTable[r]=e},hashRemoveNode:e=>{var r=Ne.hashName(e.parent.id,e.name);if(Ne.nameTable[r]===e)Ne.nameTable[r]=e.name_next;else for(var t=Ne.nameTable[r];t;){if(t.name_next===e){t.name_next=e.name_next;break}t=t.name_next}},lookupNode:(e,r)=>{var t=Ne.mayLookup(e);if(t)throw new Ne.ErrnoError(t,e);for(var n=Ne.hashName(e.id,r),o=Ne.nameTable[n];o;o=o.name_next){var i=o.name;if(o.parent.id===e.id&&i===r)return o}return Ne.lookup(e,r)},createNode:(e,r,t,n)=>{F("object"==typeof e);var o=new Ne.FSNode(e,r,t,n);return Ne.hashAddNode(o),o},destroyNode:e=>{Ne.hashRemoveNode(e)},isRoot:e=>e===e.parent,isMountpoint:e=>!!e.mounted,isFile:e=>32768==(61440&e),isDir:e=>16384==(61440&e),isLink:e=>40960==(61440&e),isChrdev:e=>8192==(61440&e),isBlkdev:e=>24576==(61440&e),isFIFO:e=>4096==(61440&e),isSocket:e=>!(49152&~e),flagModes:{r:0,"r+":2,w:577,"w+":578,a:1089,"a+":1090},modeStringToFlags:e=>{var r=Ne.flagModes[e];if(void 0===r)throw new Error("Unknown file open mode: "+e);return r},flagsToPermissionString:e=>{var r=["r","w","rw"][3&e];return 512&e&&(r+="w"),r},nodePermissions:(e,r)=>Ne.ignorePermissions||(!r.includes("r")||292&e.mode)&&(!r.includes("w")||146&e.mode)&&(!r.includes("x")||73&e.mode)?0:2,mayLookup:e=>{var r=Ne.nodePermissions(e,"x");return r||(e.node_ops.lookup?0:2)},mayCreate:(e,r)=>{try{Ne.lookupNode(e,r);return 20}catch(e){}return Ne.nodePermissions(e,"wx")},mayDelete:(e,r,t)=>{var n;try{n=Ne.lookupNode(e,r)}catch(e){return e.errno}var o=Ne.nodePermissions(e,"wx");if(o)return o;if(t){if(!Ne.isDir(n.mode))return 54;if(Ne.isRoot(n)||Ne.getPath(n)===Ne.cwd())return 10}else if(Ne.isDir(n.mode))return 31;return 0},mayOpen:(e,r)=>e?Ne.isLink(e.mode)?32:Ne.isDir(e.mode)&&("r"!==Ne.flagsToPermissionString(r)||512&r)?31:Ne.nodePermissions(e,Ne.flagsToPermissionString(r)):44,MAX_OPEN_FDS:4096,nextfd:function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0,r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:Ne.MAX_OPEN_FDS;for(var t=e;t<=r;t++)if(!Ne.streams[t])return t;throw new Ne.ErrnoError(33)},getStream:e=>Ne.streams[e],createStream:(e,r,t)=>{Ne.FSStream||(Ne.FSStream=function(){this.shared={}},Ne.FSStream.prototype={object:{get:function(){return this.node},set:function(e){this.node=e}},isRead:{get:function(){return 1!=(2097155&this.flags)}},isWrite:{get:function(){return!!(2097155&this.flags)}},isAppend:{get:function(){return 1024&this.flags}},flags:{get:function(){return this.shared.flags},set:function(e){this.shared.flags=e}},position:{get function(){return this.shared.position},set:function(e){this.shared.position=e}}}),e=Object.assign(new Ne.FSStream,e);var n=Ne.nextfd(r,t);return e.fd=n,Ne.streams[n]=e,e},closeStream:e=>{Ne.streams[e]=null},chrdev_stream_ops:{open:e=>{var r=Ne.getDevice(e.node.rdev);e.stream_ops=r.stream_ops,e.stream_ops.open&&e.stream_ops.open(e)},llseek:()=>{throw new Ne.ErrnoError(70)}},major:e=>e>>8,minor:e=>255&e,makedev:(e,r)=>e<<8|r,registerDevice:(e,r)=>{Ne.devices[e]={stream_ops:r}},getDevice:e=>Ne.devices[e],getMounts:e=>{for(var r=[],t=[e];t.length;){var n=t.pop();r.push(n),t.push.apply(t,n.mounts)}return r},syncfs:(e,r)=>{"function"==typeof e&&(r=e,e=!1),Ne.syncFSRequests++,Ne.syncFSRequests>1&&_("warning: "+Ne.syncFSRequests+" FS.syncfs operations in flight at once, probably just doing extra work");var t=Ne.getMounts(Ne.root.mount),n=0;function o(e){return F(Ne.syncFSRequests>0),Ne.syncFSRequests--,r(e)}function i(e){if(e)return i.errored?void 0:(i.errored=!0,o(e));++n>=t.length&&o(null)}t.forEach((r=>{if(!r.type.syncfs)return i(null);r.type.syncfs(r,e,i)}))},mount:(e,r,t)=>{if("string"==typeof e)throw e;var n,o="/"===t,i=!t;if(o&&Ne.root)throw new Ne.ErrnoError(10);if(!o&&!i){var a=Ne.lookupPath(t,{follow_mount:!1});if(t=a.path,n=a.node,Ne.isMountpoint(n))throw new Ne.ErrnoError(10);if(!Ne.isDir(n.mode))throw new Ne.ErrnoError(54)}var s={type:e,opts:r,mountpoint:t,mounts:[]},l=e.mount(s);return l.mount=s,s.root=l,o?Ne.root=l:n&&(n.mounted=s,n.mount&&n.mount.mounts.push(s)),l},unmount:e=>{var r=Ne.lookupPath(e,{follow_mount:!1});if(!Ne.isMountpoint(r.node))throw new Ne.ErrnoError(28);var t=r.node,n=t.mounted,o=Ne.getMounts(n);Object.keys(Ne.nameTable).forEach((e=>{for(var r=Ne.nameTable[e];r;){var t=r.name_next;o.includes(r.mount)&&Ne.destroyNode(r),r=t}})),t.mounted=null;var i=t.mount.mounts.indexOf(n);F(-1!==i),t.mount.mounts.splice(i,1)},lookup:(e,r)=>e.node_ops.lookup(e,r),mknod:(e,r,t)=>{var n=Ne.lookupPath(e,{parent:!0}).node,o=Pe.basename(e);if(!o||"."===o||".."===o)throw new Ne.ErrnoError(28);var i=Ne.mayCreate(n,o);if(i)throw new Ne.ErrnoError(i);if(!n.node_ops.mknod)throw new Ne.ErrnoError(63);return n.node_ops.mknod(n,o,r,t)},create:(e,r)=>(r=void 0!==r?r:438,r&=4095,r|=32768,Ne.mknod(e,r,0)),mkdir:(e,r)=>(r=void 0!==r?r:511,r&=1023,r|=16384,Ne.mknod(e,r,0)),mkdirTree:(e,r)=>{for(var t=e.split("/"),n="",o=0;o(void 0===t&&(t=r,r=438),r|=8192,Ne.mknod(e,r,t)),symlink:(e,r)=>{if(!Ae.resolve(e))throw new Ne.ErrnoError(44);var t=Ne.lookupPath(r,{parent:!0}).node;if(!t)throw new Ne.ErrnoError(44);var n=Pe.basename(r),o=Ne.mayCreate(t,n);if(o)throw new Ne.ErrnoError(o);if(!t.node_ops.symlink)throw new Ne.ErrnoError(63);return t.node_ops.symlink(t,n,e)},rename:(e,r)=>{var t,n,o=Pe.dirname(e),i=Pe.dirname(r),a=Pe.basename(e),s=Pe.basename(r);if(t=Ne.lookupPath(e,{parent:!0}).node,n=Ne.lookupPath(r,{parent:!0}).node,!t||!n)throw new Ne.ErrnoError(44);if(t.mount!==n.mount)throw new Ne.ErrnoError(75);var l,u=Ne.lookupNode(t,a),c=Ae.relative(e,i);if("."!==c.charAt(0))throw new Ne.ErrnoError(28);if("."!==(c=Ae.relative(r,o)).charAt(0))throw new Ne.ErrnoError(55);try{l=Ne.lookupNode(n,s)}catch(e){}if(u!==l){var d=Ne.isDir(u.mode),f=Ne.mayDelete(t,a,d);if(f)throw new Ne.ErrnoError(f);if(f=l?Ne.mayDelete(n,s,d):Ne.mayCreate(n,s))throw new Ne.ErrnoError(f);if(!t.node_ops.rename)throw new Ne.ErrnoError(63);if(Ne.isMountpoint(u)||l&&Ne.isMountpoint(l))throw new Ne.ErrnoError(10);if(n!==t&&(f=Ne.nodePermissions(t,"w")))throw new Ne.ErrnoError(f);Ne.hashRemoveNode(u);try{t.node_ops.rename(u,n,s)}catch(e){throw e}finally{Ne.hashAddNode(u)}}},rmdir:e=>{var r=Ne.lookupPath(e,{parent:!0}).node,t=Pe.basename(e),n=Ne.lookupNode(r,t),o=Ne.mayDelete(r,t,!0);if(o)throw new Ne.ErrnoError(o);if(!r.node_ops.rmdir)throw new Ne.ErrnoError(63);if(Ne.isMountpoint(n))throw new Ne.ErrnoError(10);r.node_ops.rmdir(r,t),Ne.destroyNode(n)},readdir:e=>{var r=Ne.lookupPath(e,{follow:!0}).node;if(!r.node_ops.readdir)throw new Ne.ErrnoError(54);return r.node_ops.readdir(r)},unlink:e=>{var r=Ne.lookupPath(e,{parent:!0}).node;if(!r)throw new Ne.ErrnoError(44);var t=Pe.basename(e),n=Ne.lookupNode(r,t),o=Ne.mayDelete(r,t,!1);if(o)throw new Ne.ErrnoError(o);if(!r.node_ops.unlink)throw new Ne.ErrnoError(63);if(Ne.isMountpoint(n))throw new Ne.ErrnoError(10);r.node_ops.unlink(r,t),Ne.destroyNode(n)},readlink:e=>{var r=Ne.lookupPath(e).node;if(!r)throw new Ne.ErrnoError(44);if(!r.node_ops.readlink)throw new Ne.ErrnoError(28);return Ae.resolve(Ne.getPath(r.parent),r.node_ops.readlink(r))},stat:(e,r)=>{var t=Ne.lookupPath(e,{follow:!r}).node;if(!t)throw new Ne.ErrnoError(44);if(!t.node_ops.getattr)throw new Ne.ErrnoError(63);return t.node_ops.getattr(t)},lstat:e=>Ne.stat(e,!0),chmod:(e,r,t)=>{var n;"string"==typeof e?n=Ne.lookupPath(e,{follow:!t}).node:n=e;if(!n.node_ops.setattr)throw new Ne.ErrnoError(63);n.node_ops.setattr(n,{mode:4095&r|-4096&n.mode,timestamp:Date.now()})},lchmod:(e,r)=>{Ne.chmod(e,r,!0)},fchmod:(e,r)=>{var t=Ne.getStream(e);if(!t)throw new Ne.ErrnoError(8);Ne.chmod(t.node,r)},chown:(e,r,t,n)=>{var o;"string"==typeof e?o=Ne.lookupPath(e,{follow:!n}).node:o=e;if(!o.node_ops.setattr)throw new Ne.ErrnoError(63);o.node_ops.setattr(o,{timestamp:Date.now()})},lchown:(e,r,t)=>{Ne.chown(e,r,t,!0)},fchown:(e,r,t)=>{var n=Ne.getStream(e);if(!n)throw new Ne.ErrnoError(8);Ne.chown(n.node,r,t)},truncate:(e,r)=>{if(r<0)throw new Ne.ErrnoError(28);var t;"string"==typeof e?t=Ne.lookupPath(e,{follow:!0}).node:t=e;if(!t.node_ops.setattr)throw new Ne.ErrnoError(63);if(Ne.isDir(t.mode))throw new Ne.ErrnoError(31);if(!Ne.isFile(t.mode))throw new Ne.ErrnoError(28);var n=Ne.nodePermissions(t,"w");if(n)throw new Ne.ErrnoError(n);t.node_ops.setattr(t,{size:r,timestamp:Date.now()})},ftruncate:(e,r)=>{var t=Ne.getStream(e);if(!t)throw new Ne.ErrnoError(8);if(!(2097155&t.flags))throw new Ne.ErrnoError(28);Ne.truncate(t.node,r)},utime:(e,r,t)=>{var n=Ne.lookupPath(e,{follow:!0}).node;n.node_ops.setattr(n,{timestamp:Math.max(r,t)})},open:(e,t,n,o,i)=>{if(""===e)throw new Ne.ErrnoError(44);var a;if(n=void 0===n?438:n,n=64&(t="string"==typeof t?Ne.modeStringToFlags(t):t)?4095&n|32768:0,"object"==typeof e)a=e;else{e=Pe.normalize(e);try{a=Ne.lookupPath(e,{follow:!(131072&t)}).node}catch(e){}}var s=!1;if(64&t)if(a){if(128&t)throw new Ne.ErrnoError(20)}else a=Ne.mknod(e,n,0),s=!0;if(!a)throw new Ne.ErrnoError(44);if(Ne.isChrdev(a.mode)&&(t&=-513),65536&t&&!Ne.isDir(a.mode))throw new Ne.ErrnoError(54);if(!s){var l=Ne.mayOpen(a,t);if(l)throw new Ne.ErrnoError(l)}512&t&&Ne.truncate(a,0),t&=-131713;var u=Ne.createStream({node:a,path:Ne.getPath(a),flags:t,seekable:!0,position:0,stream_ops:a.stream_ops,ungotten:[],error:!1},o,i);return u.stream_ops.open&&u.stream_ops.open(u),!r.logReadFiles||1&t||(Ne.readFiles||(Ne.readFiles={}),e in Ne.readFiles||(Ne.readFiles[e]=1)),u},close:e=>{if(Ne.isClosed(e))throw new Ne.ErrnoError(8);e.getdents&&(e.getdents=null);try{e.stream_ops.close&&e.stream_ops.close(e)}catch(e){throw e}finally{Ne.closeStream(e.fd)}e.fd=null},isClosed:e=>null===e.fd,llseek:(e,r,t)=>{if(Ne.isClosed(e))throw new Ne.ErrnoError(8);if(!e.seekable||!e.stream_ops.llseek)throw new Ne.ErrnoError(70);if(0!=t&&1!=t&&2!=t)throw new Ne.ErrnoError(28);return e.position=e.stream_ops.llseek(e,r,t),e.ungotten=[],e.position},read:(e,r,t,n,o)=>{if(n<0||o<0)throw new Ne.ErrnoError(28);if(Ne.isClosed(e))throw new Ne.ErrnoError(8);if(1==(2097155&e.flags))throw new Ne.ErrnoError(8);if(Ne.isDir(e.node.mode))throw new Ne.ErrnoError(31);if(!e.stream_ops.read)throw new Ne.ErrnoError(28);var i=void 0!==o;if(i){if(!e.seekable)throw new Ne.ErrnoError(70)}else o=e.position;var a=e.stream_ops.read(e,r,t,n,o);return i||(e.position+=a),a},write:(e,r,t,n,o,i)=>{if(n<0||o<0)throw new Ne.ErrnoError(28);if(Ne.isClosed(e))throw new Ne.ErrnoError(8);if(!(2097155&e.flags))throw new Ne.ErrnoError(8);if(Ne.isDir(e.node.mode))throw new Ne.ErrnoError(31);if(!e.stream_ops.write)throw new Ne.ErrnoError(28);e.seekable&&1024&e.flags&&Ne.llseek(e,0,2);var a=void 0!==o;if(a){if(!e.seekable)throw new Ne.ErrnoError(70)}else o=e.position;var s=e.stream_ops.write(e,r,t,n,o,i);return a||(e.position+=s),s},allocate:(e,r,t)=>{if(Ne.isClosed(e))throw new Ne.ErrnoError(8);if(r<0||t<=0)throw new Ne.ErrnoError(28);if(!(2097155&e.flags))throw new Ne.ErrnoError(8);if(!Ne.isFile(e.node.mode)&&!Ne.isDir(e.node.mode))throw new Ne.ErrnoError(43);if(!e.stream_ops.allocate)throw new Ne.ErrnoError(138);e.stream_ops.allocate(e,r,t)},mmap:(e,r,t,n,o,i)=>{if(2&o&&!(2&i)&&2!=(2097155&e.flags))throw new Ne.ErrnoError(2);if(1==(2097155&e.flags))throw new Ne.ErrnoError(2);if(!e.stream_ops.mmap)throw new Ne.ErrnoError(43);return e.stream_ops.mmap(e,r,t,n,o,i)},msync:(e,r,t,n,o)=>e&&e.stream_ops.msync?e.stream_ops.msync(e,r,t,n,o):0,munmap:e=>0,ioctl:(e,r,t)=>{if(!e.stream_ops.ioctl)throw new Ne.ErrnoError(59);return e.stream_ops.ioctl(e,r,t)},readFile:function(e){let r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if(r.flags=r.flags||0,r.encoding=r.encoding||"binary","utf8"!==r.encoding&&"binary"!==r.encoding)throw new Error('Invalid encoding type "'+r.encoding+'"');var t,n=Ne.open(e,r.flags),o=Ne.stat(e).size,i=new Uint8Array(o);return Ne.read(n,i,0,o,0),"utf8"===r.encoding?t=O(i,0):"binary"===r.encoding&&(t=i),Ne.close(n),t},writeFile:function(e,r){let t=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};t.flags=t.flags||577;var n=Ne.open(e,t.flags,t.mode);if("string"==typeof r){var o=new Uint8Array(I(r)+1),i=M(r,o,0,o.length);Ne.write(n,o,0,i,void 0,t.canOwn)}else{if(!ArrayBuffer.isView(r))throw new Error("Unsupported data type");Ne.write(n,r,0,r.byteLength,void 0,t.canOwn)}Ne.close(n)},cwd:()=>Ne.currentPath,chdir:e=>{var r=Ne.lookupPath(e,{follow:!0});if(null===r.node)throw new Ne.ErrnoError(44);if(!Ne.isDir(r.node.mode))throw new Ne.ErrnoError(54);var t=Ne.nodePermissions(r.node,"x");if(t)throw new Ne.ErrnoError(t);Ne.currentPath=r.path},createDefaultDirectories:()=>{Ne.mkdir("/tmp"),Ne.mkdir("/home"),Ne.mkdir("/home/web_user")},createDefaultDevices:()=>{Ne.mkdir("/dev"),Ne.registerDevice(Ne.makedev(1,3),{read:()=>0,write:(e,r,t,n,o)=>n}),Ne.mkdev("/dev/null",Ne.makedev(1,3)),Fe.register(Ne.makedev(5,0),Fe.default_tty_ops),Fe.register(Ne.makedev(6,0),Fe.default_tty1_ops),Ne.mkdev("/dev/tty",Ne.makedev(5,0)),Ne.mkdev("/dev/tty1",Ne.makedev(6,0));var e=function(){if("object"==typeof crypto&&"function"==typeof crypto.getRandomValues){var e=new Uint8Array(1);return function(){return crypto.getRandomValues(e),e[0]}}if(u)try{var r=a.default;return function(){return r.randomBytes(1)[0]}}catch(e){}return function(){ge("no cryptographic support found for randomDevice. consider polyfilling it if you want to use something insecure like Math.random(), e.g. put this in a --pre-js: var crypto = { getRandomValues: function(array) { for (var i = 0; i < array.length; i++) array[i] = (Math.random()*256)|0 } };")}}();Ne.createDevice("/dev","random",e),Ne.createDevice("/dev","urandom",e),Ne.mkdir("/dev/shm"),Ne.mkdir("/dev/shm/tmp")},createSpecialDirectories:()=>{Ne.mkdir("/proc");var e=Ne.mkdir("/proc/self");Ne.mkdir("/proc/self/fd"),Ne.mount({mount:()=>{var r=Ne.createNode(e,"fd",16895,73);return r.node_ops={lookup:(e,r)=>{var t=+r,n=Ne.getStream(t);if(!n)throw new Ne.ErrnoError(8);var o={parent:null,mount:{mountpoint:"fake"},node_ops:{readlink:()=>n.path}};return o.parent=o,o}},r}},{},"/proc/self/fd")},createStandardStreams:()=>{r.stdin?Ne.createDevice("/dev","stdin",r.stdin):Ne.symlink("/dev/tty","/dev/stdin"),r.stdout?Ne.createDevice("/dev","stdout",null,r.stdout):Ne.symlink("/dev/tty","/dev/stdout"),r.stderr?Ne.createDevice("/dev","stderr",null,r.stderr):Ne.symlink("/dev/tty1","/dev/stderr");var e=Ne.open("/dev/stdin",0),t=Ne.open("/dev/stdout",1),n=Ne.open("/dev/stderr",1);F(0===e.fd,"invalid handle for stdin ("+e.fd+")"),F(1===t.fd,"invalid handle for stdout ("+t.fd+")"),F(2===n.fd,"invalid handle for stderr ("+n.fd+")")},ensureErrnoError:()=>{Ne.ErrnoError||(Ne.ErrnoError=function(e,r){this.node=r,this.setErrno=function(e){for(var r in this.errno=e,Me)if(Me[r]===e){this.code=r;break}},this.setErrno(e),this.message=Re[e],this.stack&&(Object.defineProperty(this,"stack",{value:(new Error).stack,writable:!0}),this.stack=Se(this.stack))},Ne.ErrnoError.prototype=new Error,Ne.ErrnoError.prototype.constructor=Ne.ErrnoError,[44].forEach((e=>{Ne.genericErrors[e]=new Ne.ErrnoError(e),Ne.genericErrors[e].stack=""})))},staticInit:()=>{Ne.ensureErrnoError(),Ne.nameTable=new Array(4096),Ne.mount(Oe,{},"/"),Ne.createDefaultDirectories(),Ne.createDefaultDevices(),Ne.createSpecialDirectories(),Ne.filesystems={MEMFS:Oe}},init:(e,t,n)=>{F(!Ne.init.initialized,"FS.init was previously called. If you want to initialize later with custom parameters, remove any earlier calls (note that one is automatically added to the generated code)"),Ne.init.initialized=!0,Ne.ensureErrnoError(),r.stdin=e||r.stdin,r.stdout=t||r.stdout,r.stderr=n||r.stderr,Ne.createStandardStreams()},quit:()=>{Ne.init.initialized=!1,Et();for(var e=0;e{var t=0;return e&&(t|=365),r&&(t|=146),t},findObject:(e,r)=>{var t=Ne.analyzePath(e,r);return t.exists?t.object:null},analyzePath:(e,r)=>{try{e=(n=Ne.lookupPath(e,{follow:!r})).path}catch(e){}var t={isRoot:!1,exists:!1,error:0,name:null,path:null,object:null,parentExists:!1,parentPath:null,parentObject:null};try{var n=Ne.lookupPath(e,{parent:!0});t.parentExists=!0,t.parentPath=n.path,t.parentObject=n.node,t.name=Pe.basename(e),n=Ne.lookupPath(e,{follow:!r}),t.exists=!0,t.path=n.path,t.object=n.node,t.name=n.node.name,t.isRoot="/"===n.path}catch(e){t.error=e.errno}return t},createPath:(e,r,t,n)=>{e="string"==typeof e?e:Ne.getPath(e);for(var o=r.split("/").reverse();o.length;){var i=o.pop();if(i){var a=Pe.join2(e,i);try{Ne.mkdir(a)}catch(e){}e=a}}return a},createFile:(e,r,t,n,o)=>{var i=Pe.join2("string"==typeof e?e:Ne.getPath(e),r),a=Ne.getMode(n,o);return Ne.create(i,a)},createDataFile:(e,r,t,n,o,i)=>{var a=r;e&&(e="string"==typeof e?e:Ne.getPath(e),a=r?Pe.join2(e,r):e);var s=Ne.getMode(n,o),l=Ne.create(a,s);if(t){if("string"==typeof t){for(var u=new Array(t.length),c=0,d=t.length;c{var o=Pe.join2("string"==typeof e?e:Ne.getPath(e),r),i=Ne.getMode(!!t,!!n);Ne.createDevice.major||(Ne.createDevice.major=64);var a=Ne.makedev(Ne.createDevice.major++,0);return Ne.registerDevice(a,{open:e=>{e.seekable=!1},close:e=>{n&&n.buffer&&n.buffer.length&&n(10)},read:(e,r,n,o,i)=>{for(var a=0,s=0;s{for(var a=0;a{if(e.isDevice||e.isFolder||e.link||e.contents)return!0;if("undefined"!=typeof XMLHttpRequest)throw new Error("Lazy loading should have been performed (contents set) in createLazyFile, but it was not. Lazy loading only works in web workers. Use --embed-file or --preload-file in emcc on the main thread.");if(!d)throw new Error("Cannot load without read() or XMLHttpRequest.");try{e.contents=ft(d(e.url),!0),e.usedBytes=e.contents.length}catch(e){throw new Ne.ErrnoError(29)}},createLazyFile:(e,r,t,n,o)=>{function i(){this.lengthKnown=!1,this.chunks=[]}if(i.prototype.get=function(e){if(!(e>this.length-1||e<0)){var r=e%this.chunkSize,t=e/this.chunkSize|0;return this.getter(t)[r]}},i.prototype.setDataGetter=function(e){this.getter=e},i.prototype.cacheLength=function(){var e=new XMLHttpRequest;if(e.open("HEAD",t,!1),e.send(null),!(e.status>=200&&e.status<300||304===e.status))throw new Error("Couldn't load "+t+". Status: "+e.status);var r,n=Number(e.getResponseHeader("Content-length")),o=(r=e.getResponseHeader("Accept-Ranges"))&&"bytes"===r,i=(r=e.getResponseHeader("Content-Encoding"))&&"gzip"===r,a=1048576;o||(a=n);var s=this;s.setDataGetter((e=>{var r=e*a,o=(e+1)*a-1;if(o=Math.min(o,n-1),void 0===s.chunks[e]&&(s.chunks[e]=((e,r)=>{if(e>r)throw new Error("invalid range ("+e+", "+r+") or no bytes requested!");if(r>n-1)throw new Error("only "+n+" bytes available! programmer error!");var o=new XMLHttpRequest;if(o.open("GET",t,!1),n!==a&&o.setRequestHeader("Range","bytes="+e+"-"+r),o.responseType="arraybuffer",o.overrideMimeType&&o.overrideMimeType("text/plain; charset=x-user-defined"),o.send(null),!(o.status>=200&&o.status<300||304===o.status))throw new Error("Couldn't load "+t+". Status: "+o.status);return void 0!==o.response?new Uint8Array(o.response||[]):ft(o.responseText||"",!0)})(r,o)),void 0===s.chunks[e])throw new Error("doXHR failed!");return s.chunks[e]})),!i&&n||(a=n=1,n=this.getter(0).length,a=n,b("LazyFiles on gzip forces download of the whole file when length is accessed")),this._length=n,this._chunkSize=a,this.lengthKnown=!0},"undefined"!=typeof XMLHttpRequest){if(!l)throw"Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc";var a=new i;Object.defineProperties(a,{length:{get:function(){return this.lengthKnown||this.cacheLength(),this._length}},chunkSize:{get:function(){return this.lengthKnown||this.cacheLength(),this._chunkSize}}});var s={isDevice:!1,contents:a}}else s={isDevice:!1,url:t};var u=Ne.createFile(e,r,s,n,o);s.contents?u.contents=s.contents:s.url&&(u.contents=null,u.url=s.url),Object.defineProperties(u,{usedBytes:{get:function(){return this.contents.length}}});var c={};return Object.keys(u.stream_ops).forEach((e=>{var r=u.stream_ops[e];c[e]=function(){return Ne.forceLoadFile(u),r.apply(null,arguments)}})),c.read=(e,r,t,n,o)=>{Ne.forceLoadFile(u);var i=e.node.contents;if(o>=i.length)return 0;var a=Math.min(i.length-o,n);if(F(a>=0),i.slice)for(var s=0;s{var c=r?Ae.resolve(Pe.join2(e,r)):e,d=pe("cp "+c);function p(t){function f(t){u&&u(),s||Ne.createDataFile(e,r,t,n,o,l),i&&i(),he(d)}Browser.handledByPreloadPlugin(t,c,f,(()=>{a&&a(),he(d)}))||f(t)}me(d),"string"==typeof t?function(e,r,t,n){var o=n?"":pe("al "+e);f(e,(function(t){F(t,'Loading data file "'+e+'" failed (no arrayBuffer).'),r(new Uint8Array(t)),o&&he(o)}),(function(r){if(!t)throw'Loading data file "'+e+'" failed.';t()})),o&&me(o)}(t,(e=>p(e)),a):p(t)},indexedDB:()=>window.indexedDB||window.mozIndexedDB||window.webkitIndexedDB||window.msIndexedDB,DB_NAME:()=>"EM_FS_"+window.location.pathname,DB_VERSION:20,DB_STORE_NAME:"FILE_DATA",saveFilesToDB:(e,r,t)=>{r=r||(()=>{}),t=t||(()=>{});var n=Ne.indexedDB();try{var o=n.open(Ne.DB_NAME(),Ne.DB_VERSION)}catch(e){return t(e)}o.onupgradeneeded=()=>{b("creating db"),o.result.createObjectStore(Ne.DB_STORE_NAME)},o.onsuccess=()=>{var n=o.result.transaction([Ne.DB_STORE_NAME],"readwrite"),i=n.objectStore(Ne.DB_STORE_NAME),a=0,s=0,l=e.length;function u(){0==s?r():t()}e.forEach((e=>{var r=i.put(Ne.analyzePath(e).object.contents,e);r.onsuccess=()=>{++a+s==l&&u()},r.onerror=()=>{s++,a+s==l&&u()}})),n.onerror=t},o.onerror=t},loadFilesFromDB:(e,r,t)=>{r=r||(()=>{}),t=t||(()=>{});var n=Ne.indexedDB();try{var o=n.open(Ne.DB_NAME(),Ne.DB_VERSION)}catch(e){return t(e)}o.onupgradeneeded=t,o.onsuccess=()=>{var n=o.result;try{var i=n.transaction([Ne.DB_STORE_NAME],"readonly")}catch(e){return void t(e)}var a=i.objectStore(Ne.DB_STORE_NAME),s=0,l=0,u=e.length;function c(){0==l?r():t()}e.forEach((e=>{var r=a.get(e);r.onsuccess=()=>{Ne.analyzePath(e).exists&&Ne.unlink(e),Ne.createDataFile(Pe.dirname(e),Pe.basename(e),r.result,!0,!0,!0),++s+l==u&&c()},r.onerror=()=>{l++,s+l==u&&c()}})),i.onerror=t},o.onerror=t},absolutePath:()=>{ge("FS.absolutePath has been removed; use PATH_FS.resolve instead")},createFolder:()=>{ge("FS.createFolder has been removed; use FS.mkdir instead")},createLink:()=>{ge("FS.createLink has been removed; use FS.symlink instead")},joinPath:()=>{ge("FS.joinPath has been removed; use PATH.join instead")},mmapAlloc:()=>{ge("FS.mmapAlloc has been replaced by the top level function mmapAlloc")},standardizePath:()=>{ge("FS.standardizePath has been removed; use PATH.normalize instead")}},Ie={DEFAULT_POLLMASK:5,calculateAt:function(e,r,t){if(Pe.isAbs(r))return r;var n;if(-100===e)n=Ne.cwd();else{var o=Ne.getStream(e);if(!o)throw new Ne.ErrnoError(8);n=o.path}if(0==r.length){if(!t)throw new Ne.ErrnoError(44);return n}return Pe.join2(n,r)},doStat:function(e,r,t){try{var n=e(r)}catch(e){if(e&&e.node&&Pe.normalize(r)!==Pe.normalize(Ne.getPath(e.node)))return-54;throw e}return $[t>>2]=n.dev,$[t+4>>2]=0,$[t+8>>2]=n.ino,$[t+12>>2]=n.mode,$[t+16>>2]=n.nlink,$[t+20>>2]=n.uid,$[t+24>>2]=n.gid,$[t+28>>2]=n.rdev,$[t+32>>2]=0,Ee=[n.size>>>0,(ye=n.size,+Math.abs(ye)>=1?ye>0?(0|Math.min(+Math.floor(ye/4294967296),4294967295))>>>0:~~+Math.ceil((ye-+(~~ye>>>0))/4294967296)>>>0:0)],$[t+40>>2]=Ee[0],$[t+44>>2]=Ee[1],$[t+48>>2]=4096,$[t+52>>2]=n.blocks,$[t+56>>2]=n.atime.getTime()/1e3|0,$[t+60>>2]=0,$[t+64>>2]=n.mtime.getTime()/1e3|0,$[t+68>>2]=0,$[t+72>>2]=n.ctime.getTime()/1e3|0,$[t+76>>2]=0,Ee=[n.ino>>>0,(ye=n.ino,+Math.abs(ye)>=1?ye>0?(0|Math.min(+Math.floor(ye/4294967296),4294967295))>>>0:~~+Math.ceil((ye-+(~~ye>>>0))/4294967296)>>>0:0)],$[t+80>>2]=Ee[0],$[t+84>>2]=Ee[1],0},doMsync:function(e,r,t,n,o){var i=U.slice(e,e+t);Ne.msync(r,i,o,t,n)},doMknod:function(e,r,t){switch(61440&r){case 32768:case 8192:case 24576:case 4096:case 49152:break;default:return-28}return Ne.mknod(e,r,t),0},doReadlink:function(e,r,t){if(t<=0)return-28;var n=Ne.readlink(e),o=Math.min(t,I(n)),i=x[r+o];return N(n,r,t+1),x[r+o]=i,o},doAccess:function(e,r){if(-8&r)return-28;var t=Ne.lookupPath(e,{follow:!0}).node;if(!t)return-44;var n="";return 4&r&&(n+="r"),2&r&&(n+="w"),1&r&&(n+="x"),n&&Ne.nodePermissions(t,n)?-2:0},doReadv:function(e,r,t,n){for(var o=0,i=0;i>2],s=$[r+4>>2];r+=8;var l=Ne.read(e,x,a,s,n);if(l<0)return-1;if(o+=l,l>2],s=$[r+4>>2];r+=8;var l=Ne.write(e,x,a,s,n);if(l<0)return-1;o+=l}return o},varargs:void 0,get:function(){return F(null!=Ie.varargs),Ie.varargs+=4,$[Ie.varargs-4>>2]},getStr:function(e){return R(e)},getStreamFromFD:function(e){var r=Ne.getStream(e);if(!r)throw new Ne.ErrnoError(8);return r}};function Le(e){switch(e){case 1:return 0;case 2:return 1;case 4:return 2;case 8:return 3;default:throw new TypeError("Unknown type size: "+e)}}var xe=void 0;function Ue(e){for(var r="",t=e;U[t];)r+=xe[U[t++]];return r}var Be={},je={},$e={};function We(e){if(void 0===e)return"_unknown";var r=(e=e.replace(/[^a-zA-Z0-9_]/g,"$")).charCodeAt(0);return r>=48&&r<=57?"_"+e:e}function ze(e,r){return e=We(e),new Function("body","return function "+e+'() {\n "use strict"; return body.apply(this, arguments);\n};\n')(r)}function He(e,r){var t=ze(r,(function(e){this.name=r,this.message=e;var t=new Error(e).stack;void 0!==t&&(this.stack=this.toString()+"\n"+t.replace(/^Error(:[^\n]*)?\n/,""))}));return t.prototype=Object.create(e.prototype),t.prototype.constructor=t,t.prototype.toString=function(){return void 0===this.message?this.name:this.name+": "+this.message},t}var Ge=void 0;function Ve(e){throw new Ge(e)}var qe=void 0;function Ye(e){throw new qe(e)}function Xe(e,r,t){function n(r){var n=t(r);n.length!==e.length&&Ye("Mismatched type converter count");for(var o=0;o{je.hasOwnProperty(e)?o[r]=je[e]:(i.push(e),Be.hasOwnProperty(e)||(Be[e]=[]),Be[e].push((()=>{o[r]=je[e],++a===i.length&&n(o)})))})),0===i.length&&n(o)}function Ke(e,r){let t=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};if(!("argPackAdvance"in r))throw new TypeError("registerType registeredInstance requires argPackAdvance");var n=r.name;if(e||Ve('type "'+n+'" must have a positive integer typeid pointer'),je.hasOwnProperty(e)){if(t.ignoreDuplicateRegistrations)return;Ve("Cannot register type '"+n+"' twice")}if(je[e]=r,delete $e[e],Be.hasOwnProperty(e)){var o=Be[e];delete Be[e],o.forEach((e=>e()))}}function Je(e){if(!(this instanceof Er))return!1;if(!(e instanceof Er))return!1;for(var r=this.$$.ptrType.registeredClass,t=this.$$.ptr,n=e.$$.ptrType.registeredClass,o=e.$$.ptr;r.baseClass;)t=r.upcast(t),r=r.baseClass;for(;n.baseClass;)o=n.upcast(o),n=n.baseClass;return r===n&&t===o}function Qe(e){Ve(e.$$.ptrType.registeredClass.name+" instance already deleted")}var Ze=!1;function er(e){}function rr(e){e.count.value-=1,0===e.count.value&&function(e){e.smartPtr?e.smartPtrType.rawDestructor(e.smartPtr):e.ptrType.registeredClass.rawDestructor(e.ptr)}(e)}function tr(e,r,t){if(r===t)return e;if(void 0===t.baseClass)return null;var n=tr(e,r,t.baseClass);return null===n?null:t.downcast(n)}var nr={};function or(){return Object.keys(cr).length}function ir(){var e=[];for(var r in cr)cr.hasOwnProperty(r)&&e.push(cr[r]);return e}var ar=[];function sr(){for(;ar.length;){var e=ar.pop();e.$$.deleteScheduled=!1,e.delete()}}var lr=void 0;function ur(e){lr=e,ar.length&&lr&&lr(sr)}var cr={};function dr(e,r){return r=function(e,r){for(void 0===r&&Ve("ptr should not be undefined");e.baseClass;)r=e.upcast(r),e=e.baseClass;return r}(e,r),cr[r]}function fr(e,r){return r.ptrType&&r.ptr||Ye("makeClassHandle requires ptr and ptrType"),!!r.smartPtrType!==!!r.smartPtr&&Ye("Both smartPtrType and smartPtr must be specified"),r.count={value:1},mr(Object.create(e,{$$:{value:r}}))}function pr(e){var r=this.getPointee(e);if(!r)return this.destructor(e),null;var t=dr(this.registeredClass,r);if(void 0!==t){if(0===t.$$.count.value)return t.$$.ptr=r,t.$$.smartPtr=e,t.clone();var n=t.clone();return this.destructor(e),n}function o(){return this.isSmartPointer?fr(this.registeredClass.instancePrototype,{ptrType:this.pointeeType,ptr:r,smartPtrType:this,smartPtr:e}):fr(this.registeredClass.instancePrototype,{ptrType:this,ptr:e})}var i,a=this.registeredClass.getActualType(r),s=nr[a];if(!s)return o.call(this);i=this.isConst?s.constPointerType:s.pointerType;var l=tr(r,this.registeredClass,i.registeredClass);return null===l?o.call(this):this.isSmartPointer?fr(i.registeredClass.instancePrototype,{ptrType:i,ptr:l,smartPtrType:this,smartPtr:e}):fr(i.registeredClass.instancePrototype,{ptrType:i,ptr:l})}function mr(e){return"undefined"==typeof FinalizationRegistry?(mr=e=>e,e):(Ze=new FinalizationRegistry((e=>{console.warn(e.leakWarning.stack.replace(/^Error: /,"")),rr(e.$$)})),mr=e=>{var r=e.$$;if(!!r.smartPtr){var t={$$:r},n=r.ptrType.registeredClass;t.leakWarning=new Error("Embind found a leaked C++ instance "+n.name+" <0x"+r.ptr.toString(16)+">.\nWe'll free it automatically in this case, but this functionality is not reliable across various environments.\nMake sure to invoke .delete() manually once you're done with the instance instead.\nOriginally allocated"),"captureStackTrace"in Error&&Error.captureStackTrace(t.leakWarning,pr),Ze.register(e,t,e)}return e},er=e=>Ze.unregister(e),mr(e))}function hr(){if(this.$$.ptr||Qe(this),this.$$.preservePointerOnDelete)return this.$$.count.value+=1,this;var e,r=mr(Object.create(Object.getPrototypeOf(this),{$$:{value:(e=this.$$,{count:e.count,deleteScheduled:e.deleteScheduled,preservePointerOnDelete:e.preservePointerOnDelete,ptr:e.ptr,ptrType:e.ptrType,smartPtr:e.smartPtr,smartPtrType:e.smartPtrType})}}));return r.$$.count.value+=1,r.$$.deleteScheduled=!1,r}function gr(){this.$$.ptr||Qe(this),this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete&&Ve("Object already scheduled for deletion"),er(this),rr(this.$$),this.$$.preservePointerOnDelete||(this.$$.smartPtr=void 0,this.$$.ptr=void 0)}function vr(){return!this.$$.ptr}function yr(){return this.$$.ptr||Qe(this),this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete&&Ve("Object already scheduled for deletion"),ar.push(this),1===ar.length&&lr&&lr(sr),this.$$.deleteScheduled=!0,this}function Er(){}function wr(e,r,t){if(void 0===e[r].overloadTable){var n=e[r];e[r]=function(){return e[r].overloadTable.hasOwnProperty(arguments.length)||Ve("Function '"+t+"' called with an invalid number of arguments ("+arguments.length+") - expects one of ("+e[r].overloadTable+")!"),e[r].overloadTable[arguments.length].apply(this,arguments)},e[r].overloadTable=[],e[r].overloadTable[n.argCount]=n}}function br(e,r,t,n,o,i,a,s){this.name=e,this.constructor=r,this.instancePrototype=t,this.rawDestructor=n,this.baseClass=o,this.getActualType=i,this.upcast=a,this.downcast=s,this.pureVirtualFunctions=[]}function _r(e,r,t){for(;r!==t;)r.upcast||Ve("Expected null or instance of "+t.name+", got an instance of "+r.name),e=r.upcast(e),r=r.baseClass;return e}function Tr(e,r){if(null===r)return this.isReference&&Ve("null is not a valid "+this.name),0;r.$$||Ve('Cannot pass "'+qr(r)+'" as a '+this.name),r.$$.ptr||Ve("Cannot pass deleted object as a pointer of type "+this.name);var t=r.$$.ptrType.registeredClass;return _r(r.$$.ptr,t,this.registeredClass)}function kr(e,r){var t;if(null===r)return this.isReference&&Ve("null is not a valid "+this.name),this.isSmartPointer?(t=this.rawConstructor(),null!==e&&e.push(this.rawDestructor,t),t):0;r.$$||Ve('Cannot pass "'+qr(r)+'" as a '+this.name),r.$$.ptr||Ve("Cannot pass deleted object as a pointer of type "+this.name),!this.isConst&&r.$$.ptrType.isConst&&Ve("Cannot convert argument of type "+(r.$$.smartPtrType?r.$$.smartPtrType.name:r.$$.ptrType.name)+" to parameter type "+this.name);var n=r.$$.ptrType.registeredClass;if(t=_r(r.$$.ptr,n,this.registeredClass),this.isSmartPointer)switch(void 0===r.$$.smartPtr&&Ve("Passing raw pointer to smart pointer is illegal"),this.sharingPolicy){case 0:r.$$.smartPtrType===this?t=r.$$.smartPtr:Ve("Cannot convert argument of type "+(r.$$.smartPtrType?r.$$.smartPtrType.name:r.$$.ptrType.name)+" to parameter type "+this.name);break;case 1:t=r.$$.smartPtr;break;case 2:if(r.$$.smartPtrType===this)t=r.$$.smartPtr;else{var o=r.clone();t=this.rawShare(t,Vr.toHandle((function(){o.delete()}))),null!==e&&e.push(this.rawDestructor,t)}break;default:Ve("Unsupporting sharing policy")}return t}function Sr(e,r){if(null===r)return this.isReference&&Ve("null is not a valid "+this.name),0;r.$$||Ve('Cannot pass "'+qr(r)+'" as a '+this.name),r.$$.ptr||Ve("Cannot pass deleted object as a pointer of type "+this.name),r.$$.ptrType.isConst&&Ve("Cannot convert argument of type "+r.$$.ptrType.name+" to parameter type "+this.name);var t=r.$$.ptrType.registeredClass;return _r(r.$$.ptr,t,this.registeredClass)}function Cr(e){return this.fromWireType(W[e>>2])}function Pr(e){return this.rawGetPointee&&(e=this.rawGetPointee(e)),e}function Ar(e){this.rawDestructor&&this.rawDestructor(e)}function Fr(e){null!==e&&e.delete()}function Dr(e,r,t,n,o,i,a,s,l,u,c){this.name=e,this.registeredClass=r,this.isReference=t,this.isConst=n,this.isSmartPointer=o,this.pointeeType=i,this.sharingPolicy=a,this.rawGetPointee=s,this.rawConstructor=l,this.rawShare=u,this.rawDestructor=c,o||void 0!==r.baseClass?this.toWireType=kr:n?(this.toWireType=Tr,this.destructorFunction=null):(this.toWireType=Sr,this.destructorFunction=null)}function Or(e,t,n){return e.includes("j")?function(e,t,n){F("dynCall_"+e in r,"bad function pointer type - no table for sig '"+e+"'"),n&&n.length?F(n.length===e.substring(1).replace(/j/g,"--").length):F(1==e.length);var o=r["dynCall_"+e];return n&&n.length?o.apply(null,[t].concat(n)):o.call(null,t)}(e,t,n):(F(Ce(t),"missing table entry in dynCall: "+t),Ce(t).apply(null,n))}function Rr(e,r){var t=(e=Ue(e)).includes("j")?function(e,r){F(e.includes("j"),"getDynCaller should only be called with i64 sigs");var t=[];return function(){return t.length=0,Object.assign(t,arguments),Or(e,r,t)}}(e,r):Ce(r);return"function"!=typeof t&&Ve("unknown function pointer with signature "+e+": "+r),t}var Mr=void 0;function Nr(e){var r=yt(e),t=Ue(r);return mt(r),t}function Ir(e,r){var t=[],n={};throw r.forEach((function e(r){n[r]||je[r]||($e[r]?$e[r].forEach(e):(t.push(r),n[r]=!0))})),new Mr(e+": "+t.map(Nr).join([", "]))}function Lr(e,r){for(var t=[],n=0;n>2)+n]);return t}function xr(e){for(;e.length;){var r=e.pop();e.pop()(r)}}function Ur(e,r){if(!(e instanceof Function))throw new TypeError("new_ called with constructor type "+typeof e+" which is not a function");var t=ze(e.name||"unknownFunctionName",(function(){}));t.prototype=e.prototype;var n=new t,o=e.apply(n,r);return o instanceof Object?o:n}function Br(e,r,t,n,o){var i=r.length;i<2&&Ve("argTypes array size mismatch! Must at least get return value and 'this' types!");for(var a=null!==r[1]&&null!==t,s=!1,l=1;l0?", ":"")+d),f+=(u?"var rv = ":"")+"invoker(fn"+(d.length>0?", ":"")+d+");\n",s)f+="runDestructors(destructors);\n";else for(l=a?1:2;l4&&0==--Wr[e].refcount&&(Wr[e]=void 0,$r.push(e))}function Hr(){for(var e=0,r=5;r(e||Ve("Cannot use deleted val. handle = "+e),Wr[e].value),toHandle:e=>{switch(e){case void 0:return 1;case null:return 2;case!0:return 3;case!1:return 4;default:var r=$r.length?$r.pop():Wr.length;return Wr[r]={refcount:1,value:e},r}}};function qr(e){if(null===e)return"null";var r=typeof e;return"object"===r||"array"===r||"function"===r?e.toString():""+e}function Yr(e,r){switch(r){case 2:return function(e){return this.fromWireType(z[e>>2])};case 3:return function(e){return this.fromWireType(H[e>>3])};default:throw new TypeError("Unknown float type: "+e)}}function Xr(e,r,t){switch(r){case 0:return t?function(e){return x[e]}:function(e){return U[e]};case 1:return t?function(e){return B[e>>1]}:function(e){return j[e>>1]};case 2:return t?function(e){return $[e>>2]}:function(e){return W[e>>2]};default:throw new TypeError("Unknown integer type: "+e)}}function Kr(e,r){var t=je[e];return void 0===t&&Ve(r+" has unknown type "+Nr(e)),t}var Jr={};var Qr=[];var Zr=[];function et(e,r){return F(r===(0|r)),(e>>>0)+4294967296*r}function rt(e,r){if(e<=0)return e;var t=r<=32?Math.abs(1<=t&&(r<=32||e>t)&&(e=-2*t+e),e}function tt(e,r){return e>=0?e:r<=32?2*Math.abs(1<>3]),n+=8):"i64"==e?(r=[$[n>>2],$[n+4>>2]],n+=8):(F(!(3&n)),e="i32",r=$[n>>2],n+=4),r}for(var i,a,s,l,u,c,d=[];;){var f=t;if(0===(i=x[t|0]))break;if(a=x[t+1|0],37==i){var p=!1,m=!1,h=!1,g=!1,v=!1;e:for(;;){switch(a){case 43:p=!0;break;case 45:m=!0;break;case 35:h=!0;break;case 48:if(g)break e;g=!0;break;case 32:v=!0;break;default:break e}t++,a=x[t+1|0]}var y=0;if(42==a)y=o("i32"),t++,a=x[t+1|0];else for(;a>=48&&a<=57;)y=10*y+(a-48),t++,a=x[t+1|0];var E,w=!1,b=-1;if(46==a){if(b=0,w=!0,t++,42==(a=x[t+1|0]))b=o("i32"),t++;else for(;;){var _=x[t+1|0];if(_<48||_>57)break;b=10*b+(_-48),t++}a=x[t+1|0]}switch(b<0&&(b=6,w=!1),String.fromCharCode(a)){case"h":104==x[t+2|0]?(t++,E=1):E=2;break;case"l":108==x[t+2|0]?(t++,E=8):E=4;break;case"L":case"q":case"j":E=8;break;case"z":case"t":case"I":E=4;break;default:E=null}switch(E&&t++,a=x[t+1|0],String.fromCharCode(a)){case"d":case"i":case"u":case"o":case"x":case"X":case"p":var T=100==a||105==a;if(s=o("i"+8*(E=E||4)),8==E&&(s=117==a?(u=s[0],c=s[1],(u>>>0)+4294967296*(c>>>0)):et(s[0],s[1])),E<=4)s=(T?rt:tt)(s&Math.pow(256,E)-1,8*E);var k=Math.abs(s),S="";if(100==a||105==a)A=rt(s,8*E).toString(10);else if(117==a)A=tt(s,8*E).toString(10),s=Math.abs(s);else if(111==a)A=(h?"0":"")+k.toString(8);else if(120==a||88==a){if(S=h&&0!=s?"0x":"",s<0){s=-s,A=(k-1).toString(16);for(var C=[],P=0;P=0&&(p?S="+"+S:v&&(S=" "+S)),"-"==A.charAt(0)&&(S="-"+S,A=A.substr(1));S.length+A.lengthR&&R>=-4?(a=(103==a?"f":"F").charCodeAt(0),b-=R+1):(a=(103==a?"e":"E").charCodeAt(0),b--),O=Math.min(b,20)}101==a||69==a?(A=s.toExponential(O),/[eE][-+]\d$/.test(A)&&(A=A.slice(0,-1)+"0"+A.slice(-1))):102!=a&&70!=a||(A=s.toFixed(O),0===s&&((l=s)<0||0===l&&1/l==-1/0)&&(A="-"+A));var M=A.split("e");if(D&&!h)for(;M[0].length>1&&M[0].includes(".")&&("0"==M[0].slice(-1)||"."==M[0].slice(-1));)M[0]=M[0].slice(0,-1);else for(h&&-1==A.indexOf(".")&&(M[0]+=".");b>O++;)M[0]+="0";A=M[0]+(M.length>1?"e"+M[1]:""),69==a&&(A=A.toUpperCase()),s>=0&&(p?A="+"+A:v&&(A=" "+A))}else A=(s<0?"-":"")+"inf",g=!1;for(;A.length0;)d.push(32);m||d.push(o("i8"));break;case"n":var L=o("i32*");$[L>>2]=d.length;break;case"%":d.push(i);break;default:for(P=f;P=4)){r+=d+"\n";continue}f=g[1],p=g[2],m=g[3],h=0|g[4]}var v=!1;if(8&e){var y=emscripten_source_map.originalPositionFor({line:m,column:h});(v=y&&y.source)&&(64&e&&(y.source=y.source.substring(y.source.replace(/\\/g,"/").lastIndexOf("/")+1)),r+=" at "+f+" ("+y.source+":"+y.line+":"+y.column+")\n")}(16&e||!v)&&(64&e&&(p=p.substring(p.replace(/\\/g,"/").lastIndexOf("/")+1)),r+=(v?" = "+f:" at "+f)+" ("+p+":"+m+":"+h+")\n"),128&e&&i[0]&&(i[1]==f&&i[2].length>0&&(r=r.replace(/\s+$/,""),r+=" with values: "+i[1]+i[2]+"\n"),i=ot(i[0]))}return r=r.replace(/\s+$/,"")}function at(e){try{return w.grow(e-L.byteLength+65535>>>16),Z(w.buffer),1}catch(r){_("emscripten_realloc_buffer: Attempted to grow heap from "+L.byteLength+" bytes to "+e+" bytes, but got error: "+r)}}var st={};function lt(){if(!lt.strings){var e={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:("object"==typeof navigator&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8",_:n||"./this.program"};for(var r in st)void 0===st[r]?delete e[r]:e[r]=st[r];var t=[];for(var r in e)t.push(r+"="+e[r]);lt.strings=t}return lt.strings}var ut=function(e,r,t,n){e||(e=this),this.parent=e,this.mount=e.mount,this.mounted=null,this.id=Ne.nextInode++,this.name=r,this.mode=t,this.node_ops={},this.stream_ops={},this.rdev=n},ct=365,dt=146;function ft(e,r,t){var n=t>0?t:I(e)+1,o=new Array(n),i=M(e,o,0,o.length);return r&&(o.length=i),o}Object.defineProperties(ut.prototype,{read:{get:function(){return(this.mode&ct)===ct},set:function(e){e?this.mode|=ct:this.mode&=-366}},write:{get:function(){return(this.mode&dt)===dt},set:function(e){e?this.mode|=dt:this.mode&=-147}},isFolder:{get:function(){return Ne.isDir(this.mode)}},isDevice:{get:function(){return Ne.isChrdev(this.mode)}}}),Ne.FSNode=ut,Ne.staticInit(),Me={EPERM:63,ENOENT:44,ESRCH:71,EINTR:27,EIO:29,ENXIO:60,E2BIG:1,ENOEXEC:45,EBADF:8,ECHILD:12,EAGAIN:6,EWOULDBLOCK:6,ENOMEM:48,EACCES:2,EFAULT:21,ENOTBLK:105,EBUSY:10,EEXIST:20,EXDEV:75,ENODEV:43,ENOTDIR:54,EISDIR:31,EINVAL:28,ENFILE:41,EMFILE:33,ENOTTY:59,ETXTBSY:74,EFBIG:22,ENOSPC:51,ESPIPE:70,EROFS:69,EMLINK:34,EPIPE:64,EDOM:18,ERANGE:68,ENOMSG:49,EIDRM:24,ECHRNG:106,EL2NSYNC:156,EL3HLT:107,EL3RST:108,ELNRNG:109,EUNATCH:110,ENOCSI:111,EL2HLT:112,EDEADLK:16,ENOLCK:46,EBADE:113,EBADR:114,EXFULL:115,ENOANO:104,EBADRQC:103,EBADSLT:102,EDEADLOCK:16,EBFONT:101,ENOSTR:100,ENODATA:116,ETIME:117,ENOSR:118,ENONET:119,ENOPKG:120,EREMOTE:121,ENOLINK:47,EADV:122,ESRMNT:123,ECOMM:124,EPROTO:65,EMULTIHOP:36,EDOTDOT:125,EBADMSG:9,ENOTUNIQ:126,EBADFD:127,EREMCHG:128,ELIBACC:129,ELIBBAD:130,ELIBSCN:131,ELIBMAX:132,ELIBEXEC:133,ENOSYS:52,ENOTEMPTY:55,ENAMETOOLONG:37,ELOOP:32,EOPNOTSUPP:138,EPFNOSUPPORT:139,ECONNRESET:15,ENOBUFS:42,EAFNOSUPPORT:5,EPROTOTYPE:67,ENOTSOCK:57,ENOPROTOOPT:50,ESHUTDOWN:140,ECONNREFUSED:14,EADDRINUSE:3,ECONNABORTED:13,ENETUNREACH:40,ENETDOWN:38,ETIMEDOUT:73,EHOSTDOWN:142,EHOSTUNREACH:23,EINPROGRESS:26,EALREADY:7,EDESTADDRREQ:17,EMSGSIZE:35,EPROTONOSUPPORT:66,ESOCKTNOSUPPORT:137,EADDRNOTAVAIL:4,ENETRESET:39,EISCONN:30,ENOTCONN:53,ETOOMANYREFS:141,EUSERS:136,EDQUOT:19,ESTALE:72,ENOTSUP:138,ENOMEDIUM:148,EILSEQ:25,EOVERFLOW:61,ECANCELED:11,ENOTRECOVERABLE:56,EOWNERDEAD:62,ESTRPIPE:135},function(){for(var e=new Array(256),r=0;r<256;++r)e[r]=String.fromCharCode(r);xe=e}(),Ge=r.BindingError=He(Error,"BindingError"),qe=r.InternalError=He(Error,"InternalError"),Er.prototype.isAliasOf=Je,Er.prototype.clone=hr,Er.prototype.delete=gr,Er.prototype.isDeleted=vr,Er.prototype.deleteLater=yr,r.getInheritedInstanceCount=or,r.getLiveInheritedInstances=ir,r.flushPendingDeletes=sr,r.setDelayFunction=ur,Dr.prototype.getPointee=Pr,Dr.prototype.destructor=Ar,Dr.prototype.argPackAdvance=8,Dr.prototype.readValueFromPointer=Cr,Dr.prototype.deleteObject=Fr,Dr.prototype.fromWireType=pr,Mr=r.UnboundTypeError=He(Error,"UnboundTypeError"),r.count_emval_handles=Hr,r.get_first_emval=Gr;var pt={__syscall_fcntl64:function(e,r,t){Ie.varargs=t;try{var n=Ie.getStreamFromFD(e);switch(r){case 0:return(o=Ie.get())<0?-28:Ne.createStream(n,o).fd;case 1:case 2:case 6:case 7:return 0;case 3:return n.flags;case 4:var o=Ie.get();return n.flags|=o,0;case 5:o=Ie.get();return B[o+0>>1]=2,0;case 16:case 8:default:return-28;case 9:return i=28,$[vt()>>2]=i,-1}}catch(e){if(void 0===Ne||!(e instanceof Ne.ErrnoError))throw e;return-e.errno}var i},__syscall_openat:function(e,r,t,n){Ie.varargs=n;try{r=Ie.getStr(r),r=Ie.calculateAt(e,r);var o=n?Ie.get():0;return Ne.open(r,t,o).fd}catch(e){if(void 0===Ne||!(e instanceof Ne.ErrnoError))throw e;return-e.errno}},_embind_register_bigint:function(e,r,t,n,o){},_embind_register_bool:function(e,r,t,n,o){var i=Le(t);Ke(e,{name:r=Ue(r),fromWireType:function(e){return!!e},toWireType:function(e,r){return r?n:o},argPackAdvance:8,readValueFromPointer:function(e){var n;if(1===t)n=x;else if(2===t)n=B;else{if(4!==t)throw new TypeError("Unknown boolean type size: "+r);n=$}return this.fromWireType(n[e>>i])},destructorFunction:null})},_embind_register_class:function(e,t,n,o,i,a,s,l,u,c,d,f,p){d=Ue(d),a=Rr(i,a),l&&(l=Rr(s,l)),c&&(c=Rr(u,c)),p=Rr(f,p);var m=We(d);!function(e,t,n){r.hasOwnProperty(e)?((void 0===n||void 0!==r[e].overloadTable&&void 0!==r[e].overloadTable[n])&&Ve("Cannot register public name '"+e+"' twice"),wr(r,e,e),r.hasOwnProperty(n)&&Ve("Cannot register multiple overloads of a function with the same number of arguments ("+n+")!"),r[e].overloadTable[n]=t):(r[e]=t,void 0!==n&&(r[e].numArguments=n))}(m,(function(){Ir("Cannot construct "+d+" due to unbound types",[o])})),Xe([e,t,n],o?[o]:[],(function(t){var n,i;t=t[0],i=o?(n=t.registeredClass).instancePrototype:Er.prototype;var s=ze(m,(function(){if(Object.getPrototypeOf(this)!==u)throw new Ge("Use 'new' to construct "+d);if(void 0===f.constructor_body)throw new Ge(d+" has no accessible constructor");var e=f.constructor_body[arguments.length];if(void 0===e)throw new Ge("Tried to invoke ctor of "+d+" with invalid number of parameters ("+arguments.length+") - expected ("+Object.keys(f.constructor_body).toString()+") parameters instead!");return e.apply(this,arguments)})),u=Object.create(i,{constructor:{value:s}});s.prototype=u;var f=new br(d,s,u,p,n,a,l,c),h=new Dr(d,f,!0,!1,!1),g=new Dr(d+"*",f,!1,!1,!1),v=new Dr(d+" const*",f,!1,!0,!1);return nr[e]={pointerType:g,constPointerType:v},function(e,t,n){r.hasOwnProperty(e)||Ye("Replacing nonexistant public symbol"),void 0!==r[e].overloadTable&&void 0!==n?r[e].overloadTable[n]=t:(r[e]=t,r[e].argCount=n)}(m,s),[h,g,v]}))},_embind_register_class_constructor:function(e,r,t,n,o,i){F(r>0);var a=Lr(r,t);o=Rr(n,o),Xe([],[e],(function(e){var t="constructor "+(e=e[0]).name;if(void 0===e.registeredClass.constructor_body&&(e.registeredClass.constructor_body=[]),void 0!==e.registeredClass.constructor_body[r-1])throw new Ge("Cannot register multiple constructors with identical number of parameters ("+(r-1)+") for class '"+e.name+"'! Overload resolution is currently only performed using the parameter count, not actual type info!");return e.registeredClass.constructor_body[r-1]=()=>{Ir("Cannot construct "+e.name+" due to unbound types",a)},Xe([],a,(function(n){return n.splice(1,0,null),e.registeredClass.constructor_body[r-1]=Br(t,n,null,o,i),[]})),[]}))},_embind_register_class_function:function(e,r,t,n,o,i,a,s){var l=Lr(t,n);r=Ue(r),i=Rr(o,i),Xe([],[e],(function(e){var n=(e=e[0]).name+"."+r;function o(){Ir("Cannot call "+n+" due to unbound types",l)}r.startsWith("@@")&&(r=Symbol[r.substring(2)]),s&&e.registeredClass.pureVirtualFunctions.push(r);var u=e.registeredClass.instancePrototype,c=u[r];return void 0===c||void 0===c.overloadTable&&c.className!==e.name&&c.argCount===t-2?(o.argCount=t-2,o.className=e.name,u[r]=o):(wr(u,r,n),u[r].overloadTable[t-2]=o),Xe([],l,(function(o){var s=Br(n,o,e,i,a);return void 0===u[r].overloadTable?(s.argCount=t-2,u[r]=s):u[r].overloadTable[t-2]=s,[]})),[]}))},_embind_register_class_property:function(e,r,t,n,o,i,a,s,l,u){r=Ue(r),o=Rr(n,o),Xe([],[e],(function(e){var n=(e=e[0]).name+"."+r,c={get:function(){Ir("Cannot access "+n+" due to unbound types",[t,a])},enumerable:!0,configurable:!0};return c.set=l?()=>{Ir("Cannot access "+n+" due to unbound types",[t,a])}:e=>{Ve(n+" is a read-only property")},Object.defineProperty(e.registeredClass.instancePrototype,r,c),Xe([],l?[t,a]:[t],(function(t){var a=t[0],c={get:function(){var r=jr(this,e,n+" getter");return a.fromWireType(o(i,r))},enumerable:!0};if(l){l=Rr(s,l);var d=t[1];c.set=function(r){var t=jr(this,e,n+" setter"),o=[];l(u,t,d.toWireType(o,r)),xr(o)}}return Object.defineProperty(e.registeredClass.instancePrototype,r,c),[]})),[]}))},_embind_register_emval:function(e,r){Ke(e,{name:r=Ue(r),fromWireType:function(e){var r=Vr.toValue(e);return zr(e),r},toWireType:function(e,r){return Vr.toHandle(r)},argPackAdvance:8,readValueFromPointer:Cr,destructorFunction:null})},_embind_register_float:function(e,r,t){var n=Le(t);Ke(e,{name:r=Ue(r),fromWireType:function(e){return e},toWireType:function(e,r){if("number"!=typeof r&&"boolean"!=typeof r)throw new TypeError('Cannot convert "'+qr(r)+'" to '+this.name);return r},argPackAdvance:8,readValueFromPointer:Yr(r,n),destructorFunction:null})},_embind_register_integer:function(e,r,t,n,o){r=Ue(r),-1===o&&(o=4294967295);var i=Le(t),a=e=>e;if(0===n){var s=32-8*t;a=e=>e<>>s}var l=r.includes("unsigned"),u=(e,t)=>{if("number"!=typeof e&&"boolean"!=typeof e)throw new TypeError('Cannot convert "'+qr(e)+'" to '+t);if(eo)throw new TypeError('Passing a number "'+qr(e)+'" from JS side to C/C++ side to an argument of type "'+r+'", which is outside the valid range ['+n+", "+o+"]!")};Ke(e,{name:r,fromWireType:a,toWireType:l?function(e,r){return u(r,this.name),r>>>0}:function(e,r){return u(r,this.name),r},argPackAdvance:8,readValueFromPointer:Xr(r,i,0!==n),destructorFunction:null})},_embind_register_memory_view:function(e,r,t){var n=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array][r];function o(e){var r=W,t=r[e>>=2],o=r[e+1];return new n(L,o,t)}Ke(e,{name:t=Ue(t),fromWireType:o,argPackAdvance:8,readValueFromPointer:o},{ignoreDuplicateRegistrations:!0})},_embind_register_std_string:function(e,r){var t="std::string"===(r=Ue(r));Ke(e,{name:r,fromWireType:function(e){var r,n=W[e>>2];if(t)for(var o=e+4,i=0;i<=n;++i){var a=e+4+i;if(i==n||0==U[a]){var s=R(o,a-o);void 0===r?r=s:(r+=String.fromCharCode(0),r+=s),o=a+1}}else{var l=new Array(n);for(i=0;iI(r):()=>r.length)(),i=ht(4+o+1);if(W[i>>2]=o,t&&n)N(r,i+4,o+1);else if(n)for(var a=0;a255&&(mt(i),Ve("String has UTF-16 code units that do not fit in 8 bits")),U[i+4+a]=s}else for(a=0;aj,s=1):4===r&&(n=X,o=K,a=J,i=()=>W,s=2),Ke(e,{name:t,fromWireType:function(e){for(var t,o=W[e>>2],a=i(),l=e+4,u=0;u<=o;++u){var c=e+4+u*r;if(u==o||0==a[c>>s]){var d=n(l,c-l);void 0===t?t=d:(t+=String.fromCharCode(0),t+=d),l=c+r}}return mt(e),t},toWireType:function(e,n){"string"!=typeof n&&Ve("Cannot pass non-string to C++ string type "+t);var i=a(n),l=ht(4+i+r);return W[l>>2]=i>>s,o(n,l+4,i+r),null!==e&&e.push(mt,l),l},argPackAdvance:8,readValueFromPointer:Cr,destructorFunction:function(e){mt(e)}})},_embind_register_void:function(e,r){Ke(e,{isVoid:!0,name:r=Ue(r),argPackAdvance:0,fromWireType:function(){},toWireType:function(e,r){}})},_emscripten_date_now:function(){return Date.now()},_emval_as:function(e,r,t){e=Vr.toValue(e),r=Kr(r,"emval::as");var n=[],o=Vr.toHandle(n);return $[t>>2]=o,r.toWireType(n,e)},_emval_call_void_method:function(e,r,t,n){var o,i;(e=Qr[e])(r=Vr.toValue(r),t=void 0===(i=Jr[o=t])?Ue(o):i,null,n)},_emval_decref:zr,_emval_get_method_caller:function(e,r){var t=function(e,r){for(var t=new Array(e),n=0;n>2)+n],"parameter "+n);return t}(e,r),n=t[0],o=n.name+"_$"+t.slice(1).map((function(e){return e.name})).join("_")+"$",i=Zr[o];if(void 0!==i)return i;for(var a=["retType"],s=[n],l="",u=0;u4&&(Wr[e].refcount+=1)},_emval_run_destructors:function(e){xr(Vr.toValue(e)),zr(e)},_emval_take_value:function(e,r){var t=(e=Kr(e,"_emval_take_value")).readValueFromPointer(r);return Vr.toHandle(t)},_gmtime_js:function(e,r){var t=new Date(1e3*$[e>>2]);$[r>>2]=t.getUTCSeconds(),$[r+4>>2]=t.getUTCMinutes(),$[r+8>>2]=t.getUTCHours(),$[r+12>>2]=t.getUTCDate(),$[r+16>>2]=t.getUTCMonth(),$[r+20>>2]=t.getUTCFullYear()-1900,$[r+24>>2]=t.getUTCDay();var n=Date.UTC(t.getUTCFullYear(),0,1,0,0,0,0),o=(t.getTime()-n)/864e5|0;$[r+28>>2]=o},_localtime_js:function(e,r){var t=new Date(1e3*$[e>>2]);$[r>>2]=t.getSeconds(),$[r+4>>2]=t.getMinutes(),$[r+8>>2]=t.getHours(),$[r+12>>2]=t.getDate(),$[r+16>>2]=t.getMonth(),$[r+20>>2]=t.getFullYear()-1900,$[r+24>>2]=t.getDay();var n=new Date(t.getFullYear(),0,1),o=(t.getTime()-n.getTime())/864e5|0;$[r+28>>2]=o,$[r+36>>2]=-60*t.getTimezoneOffset();var i=new Date(t.getFullYear(),6,1).getTimezoneOffset(),a=n.getTimezoneOffset(),s=0|(i!=a&&t.getTimezoneOffset()==Math.min(a,i));$[r+32>>2]=s},_mktime_js:function(e){var r=new Date($[e+20>>2]+1900,$[e+16>>2],$[e+12>>2],$[e+8>>2],$[e+4>>2],$[e>>2],0),t=$[e+32>>2],n=r.getTimezoneOffset(),o=new Date(r.getFullYear(),0,1),i=new Date(r.getFullYear(),6,1).getTimezoneOffset(),a=o.getTimezoneOffset(),s=Math.min(a,i);if(t<0)$[e+32>>2]=Number(i!=a&&s==n);else if(t>0!=(s==n)){var l=Math.max(a,i),u=t>0?s:l;r.setTime(r.getTime()+6e4*(u-n))}$[e+24>>2]=r.getDay();var c=(r.getTime()-o.getTime())/864e5|0;return $[e+28>>2]=c,$[e>>2]=r.getSeconds(),$[e+4>>2]=r.getMinutes(),$[e+8>>2]=r.getHours(),$[e+12>>2]=r.getDate(),$[e+16>>2]=r.getMonth(),r.getTime()/1e3|0},_tzset_js:function e(r,t,n){e.called||(e.called=!0,function(e,r,t){var n=(new Date).getFullYear(),o=new Date(n,0,1),i=new Date(n,6,1),a=o.getTimezoneOffset(),s=i.getTimezoneOffset(),l=Math.max(a,s);function u(e){var r=e.toTimeString().match(/\(([A-Za-z ]+)\)$/);return r?r[1]:"GMT"}$[e>>2]=60*l,$[r>>2]=Number(a!=s);var c=u(o),d=u(i),f=Q(c),p=Q(d);s>2]=f,$[t+4>>2]=p):($[t>>2]=p,$[t+4>>2]=f)}(r,t,n))},abort:function(){ge("native code called abort()")},emscripten_log:function(e,r,t){!function(e,r){24&e&&(r=r.replace(/\s+$/,""),r+=(r.length>0?"\n":"")+it(e)),1&e?4&e?console.error(r):2&e?console.warn(r):512&e?console.info(r):256&e?console.debug(r):console.log(r):6&e?_(r):b(r)}(e,O(nt(r,t),0))},emscripten_resize_heap:function(e){var r=U.length;F((e>>>=0)>r);var t,n,o=2147483648;if(e>o)return _("Cannot enlarge memory, asked to go up to "+e+" bytes, but the limit is "+o+" bytes!"),!1;for(var i=1;i<=4;i*=2){var a=r*(1+.2/i);a=Math.min(a,e+100663296);var s=Math.min(o,(t=Math.max(e,a))+((n=65536)-t%n)%n);if(at(s))return!0}return _("Failed to grow the heap from "+r+" bytes to "+s+" bytes, not enough memory!"),!1},environ_get:function(e,r){var t=0;return lt().forEach((function(n,o){var i=r+t;$[e+4*o>>2]=i,function(e,r,t){for(var n=0;n>2]=t.length;var n=0;return t.forEach((function(e){n+=e.length+1})),$[r>>2]=n,0},fd_close:function(e){try{var r=Ie.getStreamFromFD(e);return Ne.close(r),0}catch(e){if(void 0===Ne||!(e instanceof Ne.ErrnoError))throw e;return e.errno}},fd_fdstat_get:function(e,r){try{var t=Ie.getStreamFromFD(e),n=t.tty?2:Ne.isDir(t.mode)?3:Ne.isLink(t.mode)?7:4;return x[r|0]=n,0}catch(e){if(void 0===Ne||!(e instanceof Ne.ErrnoError))throw e;return e.errno}},fd_read:function(e,r,t,n){try{var o=Ie.getStreamFromFD(e),i=Ie.doReadv(o,r,t);return $[n>>2]=i,0}catch(e){if(void 0===Ne||!(e instanceof Ne.ErrnoError))throw e;return e.errno}},fd_seek:function(e,r,t,n,o){try{var i=Ie.getStreamFromFD(e),a=4294967296*t+(r>>>0),s=9007199254740992;return a<=-s||a>=s?-61:(Ne.llseek(i,a,n),Ee=[i.position>>>0,(ye=i.position,+Math.abs(ye)>=1?ye>0?(0|Math.min(+Math.floor(ye/4294967296),4294967295))>>>0:~~+Math.ceil((ye-+(~~ye>>>0))/4294967296)>>>0:0)],$[o>>2]=Ee[0],$[o+4>>2]=Ee[1],i.getdents&&0===a&&0===n&&(i.getdents=null),0)}catch(e){if(void 0===Ne||!(e instanceof Ne.ErrnoError))throw e;return e.errno}},fd_write:function(e,r,t,n){try{var o=Ie.getStreamFromFD(e),i=Ie.doWritev(o,r,t);return $[n>>2]=i,0}catch(e){if(void 0===Ne||!(e instanceof Ne.ErrnoError))throw e;return e.errno}},setTempRet0:function(e){}};!function(){var e={env:pt,wasi_snapshot_preview1:pt};function t(e,t){var n,o=e.exports;r.asm=o,F(w=r.asm.memory,"memory not found in wasm exports"),Z(w.buffer),F(re=r.asm.__indirect_function_table,"table not found in wasm exports"),n=r.asm.__wasm_call_ctors,ae.unshift(n),he("wasm-instantiate")}me("wasm-instantiate");var n=r;function o(e){F(r===n,"the Module object should not be replaced during async compilation - perhaps the order of HTML elements is wrong?"),n=null,t(e.instance)}function i(r){return function(){if(!E&&(s||l)){if("function"==typeof fetch&&!be(ve))return fetch(ve,{credentials:"same-origin"}).then((function(e){if(!e.ok)throw"failed to load wasm binary file at '"+ve+"'";return e.arrayBuffer()})).catch((function(){return Te(ve)}));if(f)return new Promise((function(e,r){f(ve,(function(r){e(new Uint8Array(r))}),r)}))}return Promise.resolve().then((function(){return Te(ve)}))}().then((function(r){return WebAssembly.instantiate(r,e)})).then((function(e){return e})).then(r,(function(e){_("failed to asynchronously prepare wasm: "+e),be(ve)&&_("warning: Loading from a file URI ("+ve+") is not supported in most browsers. See https://emscripten.org/docs/getting_started/FAQ.html#how-do-i-run-a-local-webserver-for-testing-why-does-my-program-stall-in-downloading-or-preparing"),ge(e)}))}if(r.instantiateWasm)try{return r.instantiateWasm(e,t)}catch(e){return _("Module.instantiateWasm callback failed with error: "+e),!1}E||"function"!=typeof WebAssembly.instantiateStreaming||we(ve)||be(ve)||"function"!=typeof fetch?i(o):fetch(ve,{credentials:"same-origin"}).then((function(r){return WebAssembly.instantiateStreaming(r,e).then(o,(function(e){return _("wasm streaming compile failed: "+e),_("falling back to ArrayBuffer instantiation"),i(o)}))}))}(),r.___wasm_call_ctors=_e("__wasm_call_ctors");var mt=r._free=_e("free"),ht=r._malloc=_e("malloc"),gt=r._strlen=_e("strlen"),vt=r.___errno_location=_e("__errno_location"),yt=r.___getTypeName=_e("__getTypeName");r.___embind_register_native_and_builtin_types=_e("__embind_register_native_and_builtin_types");var Et=r.___stdio_exit=_e("__stdio_exit"),wt=r._emscripten_builtin_memalign=_e("emscripten_builtin_memalign"),bt=r._emscripten_stack_init=function(){return(bt=r._emscripten_stack_init=r.asm.emscripten_stack_init).apply(null,arguments)};r._emscripten_stack_get_free=function(){return(r._emscripten_stack_get_free=r.asm.emscripten_stack_get_free).apply(null,arguments)},r._emscripten_stack_get_base=function(){return(r._emscripten_stack_get_base=r.asm.emscripten_stack_get_base).apply(null,arguments)};var _t,Tt=r._emscripten_stack_get_end=function(){return(Tt=r._emscripten_stack_get_end=r.asm.emscripten_stack_get_end).apply(null,arguments)};function kt(e){this.name="ExitStatus",this.message="Program terminated with exit("+e+")",this.status=e}function St(e){function t(){_t||(_t=!0,r.calledRun=!0,A||(oe(),F(!le),le=!0,r.noFSInit||Ne.init.initialized||Ne.init(),Ne.ignorePermissions=!1,ke(ae),r.onRuntimeInitialized&&r.onRuntimeInitialized(),F(!r._main,'compiled without a main, but one is present. if you added it from JS, use Module["onRuntimeInitialized"]'),function(){if(oe(),r.postRun)for("function"==typeof r.postRun&&(r.postRun=[r.postRun]);r.postRun.length;)e=r.postRun.shift(),se.unshift(e);var e;ke(se)}()))}ue>0||(bt(),ne(),function(){if(r.preRun)for("function"==typeof r.preRun&&(r.preRun=[r.preRun]);r.preRun.length;)e=r.preRun.shift(),ie.unshift(e);var e;ke(ie)}(),ue>0||(r.setStatus?(r.setStatus("Running..."),setTimeout((function(){setTimeout((function(){r.setStatus("")}),1),t()}),1)):t(),oe()))}if(r.stackSave=_e("stackSave"),r.stackRestore=_e("stackRestore"),r.stackAlloc=_e("stackAlloc"),r.dynCall_ijiii=_e("dynCall_ijiii"),r.dynCall_viiijj=_e("dynCall_viiijj"),r.dynCall_jij=_e("dynCall_jij"),r.dynCall_jii=_e("dynCall_jii"),r.dynCall_jiji=_e("dynCall_jiji"),r._ff_h264_cabac_tables=112940,P("intArrayFromString",!1),P("intArrayToString",!1),P("ccall",!1),P("cwrap",!1),P("setValue",!1),P("getValue",!1),P("allocate",!1),P("UTF8ArrayToString",!1),P("UTF8ToString",!1),P("stringToUTF8Array",!1),P("stringToUTF8",!1),P("lengthBytesUTF8",!1),P("stackTrace",!1),P("addOnPreRun",!1),P("addOnInit",!1),P("addOnPreMain",!1),P("addOnExit",!1),P("addOnPostRun",!1),P("writeStringToMemory",!1),P("writeArrayToMemory",!1),P("writeAsciiToMemory",!1),P("addRunDependency",!0),P("removeRunDependency",!0),P("FS_createFolder",!1),P("FS_createPath",!0),P("FS_createDataFile",!0),P("FS_createPreloadedFile",!0),P("FS_createLazyFile",!0),P("FS_createLink",!1),P("FS_createDevice",!0),P("FS_unlink",!0),P("getLEB",!1),P("getFunctionTables",!1),P("alignFunctionTables",!1),P("registerFunctions",!1),P("addFunction",!1),P("removeFunction",!1),P("prettyPrint",!1),P("dynCall",!1),P("getCompilerSetting",!1),P("print",!1),P("printErr",!1),P("getTempRet0",!1),P("setTempRet0",!1),P("callMain",!1),P("abort",!1),P("keepRuntimeAlive",!1),P("ptrToString",!1),P("zeroMemory",!1),P("stringToNewUTF8",!1),P("emscripten_realloc_buffer",!1),P("ENV",!1),P("ERRNO_CODES",!1),P("ERRNO_MESSAGES",!1),P("setErrNo",!1),P("inetPton4",!1),P("inetNtop4",!1),P("inetPton6",!1),P("inetNtop6",!1),P("readSockaddr",!1),P("writeSockaddr",!1),P("DNS",!1),P("getHostByName",!1),P("Protocols",!1),P("Sockets",!1),P("getRandomDevice",!1),P("traverseStack",!1),P("UNWIND_CACHE",!1),P("convertPCtoSourceLocation",!1),P("readAsmConstArgsArray",!1),P("readAsmConstArgs",!1),P("mainThreadEM_ASM",!1),P("jstoi_q",!1),P("jstoi_s",!1),P("getExecutableName",!1),P("listenOnce",!1),P("autoResumeAudioContext",!1),P("dynCallLegacy",!1),P("getDynCaller",!1),P("dynCall",!1),P("setWasmTableEntry",!1),P("getWasmTableEntry",!1),P("handleException",!1),P("runtimeKeepalivePush",!1),P("runtimeKeepalivePop",!1),P("callUserCallback",!1),P("maybeExit",!1),P("safeSetTimeout",!1),P("asmjsMangle",!1),P("asyncLoad",!1),P("alignMemory",!1),P("mmapAlloc",!1),P("reallyNegative",!1),P("unSign",!1),P("reSign",!1),P("formatString",!1),P("PATH",!1),P("PATH_FS",!1),P("SYSCALLS",!1),P("getSocketFromFD",!1),P("getSocketAddress",!1),P("JSEvents",!1),P("registerKeyEventCallback",!1),P("specialHTMLTargets",!1),P("maybeCStringToJsString",!1),P("findEventTarget",!1),P("findCanvasEventTarget",!1),P("getBoundingClientRect",!1),P("fillMouseEventData",!1),P("registerMouseEventCallback",!1),P("registerWheelEventCallback",!1),P("registerUiEventCallback",!1),P("registerFocusEventCallback",!1),P("fillDeviceOrientationEventData",!1),P("registerDeviceOrientationEventCallback",!1),P("fillDeviceMotionEventData",!1),P("registerDeviceMotionEventCallback",!1),P("screenOrientation",!1),P("fillOrientationChangeEventData",!1),P("registerOrientationChangeEventCallback",!1),P("fillFullscreenChangeEventData",!1),P("registerFullscreenChangeEventCallback",!1),P("registerRestoreOldStyle",!1),P("hideEverythingExceptGivenElement",!1),P("restoreHiddenElements",!1),P("setLetterbox",!1),P("currentFullscreenStrategy",!1),P("restoreOldWindowedStyle",!1),P("softFullscreenResizeWebGLRenderTarget",!1),P("doRequestFullscreen",!1),P("fillPointerlockChangeEventData",!1),P("registerPointerlockChangeEventCallback",!1),P("registerPointerlockErrorEventCallback",!1),P("requestPointerLock",!1),P("fillVisibilityChangeEventData",!1),P("registerVisibilityChangeEventCallback",!1),P("registerTouchEventCallback",!1),P("fillGamepadEventData",!1),P("registerGamepadEventCallback",!1),P("registerBeforeUnloadEventCallback",!1),P("fillBatteryEventData",!1),P("battery",!1),P("registerBatteryEventCallback",!1),P("setCanvasElementSize",!1),P("getCanvasElementSize",!1),P("demangle",!1),P("demangleAll",!1),P("jsStackTrace",!1),P("stackTrace",!1),P("getEnvStrings",!1),P("checkWasiClock",!1),P("writeI53ToI64",!1),P("writeI53ToI64Clamped",!1),P("writeI53ToI64Signaling",!1),P("writeI53ToU64Clamped",!1),P("writeI53ToU64Signaling",!1),P("readI53FromI64",!1),P("readI53FromU64",!1),P("convertI32PairToI53",!1),P("convertU32PairToI53",!1),P("dlopenMissingError",!1),P("setImmediateWrapped",!1),P("clearImmediateWrapped",!1),P("polyfillSetImmediate",!1),P("uncaughtExceptionCount",!1),P("exceptionLast",!1),P("exceptionCaught",!1),P("ExceptionInfo",!1),P("exception_addRef",!1),P("exception_decRef",!1),P("Browser",!1),P("setMainLoop",!1),P("wget",!1),P("FS",!1),P("MEMFS",!1),P("TTY",!1),P("PIPEFS",!1),P("SOCKFS",!1),P("_setNetworkCallback",!1),P("tempFixedLengthArray",!1),P("miniTempWebGLFloatBuffers",!1),P("heapObjectForWebGLType",!1),P("heapAccessShiftForWebGLHeap",!1),P("GL",!1),P("emscriptenWebGLGet",!1),P("computeUnpackAlignedImageSize",!1),P("emscriptenWebGLGetTexPixelData",!1),P("emscriptenWebGLGetUniform",!1),P("webglGetUniformLocation",!1),P("webglPrepareUniformLocationsBeforeFirstUse",!1),P("webglGetLeftBracePos",!1),P("emscriptenWebGLGetVertexAttrib",!1),P("writeGLArray",!1),P("AL",!1),P("SDL_unicode",!1),P("SDL_ttfContext",!1),P("SDL_audio",!1),P("SDL",!1),P("SDL_gfx",!1),P("GLUT",!1),P("EGL",!1),P("GLFW_Window",!1),P("GLFW",!1),P("GLEW",!1),P("IDBStore",!1),P("runAndAbortIfError",!1),P("InternalError",!1),P("BindingError",!1),P("UnboundTypeError",!1),P("PureVirtualError",!1),P("init_embind",!1),P("throwInternalError",!1),P("throwBindingError",!1),P("throwUnboundTypeError",!1),P("ensureOverloadTable",!1),P("exposePublicSymbol",!1),P("replacePublicSymbol",!1),P("extendError",!1),P("createNamedFunction",!1),P("registeredInstances",!1),P("getBasestPointer",!1),P("registerInheritedInstance",!1),P("unregisterInheritedInstance",!1),P("getInheritedInstance",!1),P("getInheritedInstanceCount",!1),P("getLiveInheritedInstances",!1),P("registeredTypes",!1),P("awaitingDependencies",!1),P("typeDependencies",!1),P("registeredPointers",!1),P("registerType",!1),P("whenDependentTypesAreResolved",!1),P("embind_charCodes",!1),P("embind_init_charCodes",!1),P("readLatin1String",!1),P("getTypeName",!1),P("heap32VectorToArray",!1),P("requireRegisteredType",!1),P("getShiftFromSize",!1),P("integerReadValueFromPointer",!1),P("enumReadValueFromPointer",!1),P("floatReadValueFromPointer",!1),P("simpleReadValueFromPointer",!1),P("runDestructors",!1),P("new_",!1),P("craftInvokerFunction",!1),P("embind__requireFunction",!1),P("tupleRegistrations",!1),P("structRegistrations",!1),P("genericPointerToWireType",!1),P("constNoSmartPtrRawPointerToWireType",!1),P("nonConstNoSmartPtrRawPointerToWireType",!1),P("init_RegisteredPointer",!1),P("RegisteredPointer",!1),P("RegisteredPointer_getPointee",!1),P("RegisteredPointer_destructor",!1),P("RegisteredPointer_deleteObject",!1),P("RegisteredPointer_fromWireType",!1),P("runDestructor",!1),P("releaseClassHandle",!1),P("finalizationRegistry",!1),P("detachFinalizer_deps",!1),P("detachFinalizer",!1),P("attachFinalizer",!1),P("makeClassHandle",!1),P("init_ClassHandle",!1),P("ClassHandle",!1),P("ClassHandle_isAliasOf",!1),P("throwInstanceAlreadyDeleted",!1),P("ClassHandle_clone",!1),P("ClassHandle_delete",!1),P("deletionQueue",!1),P("ClassHandle_isDeleted",!1),P("ClassHandle_deleteLater",!1),P("flushPendingDeletes",!1),P("delayFunction",!1),P("setDelayFunction",!1),P("RegisteredClass",!1),P("shallowCopyInternalPointer",!1),P("downcastPointer",!1),P("upcastPointer",!1),P("validateThis",!1),P("char_0",!1),P("char_9",!1),P("makeLegalFunctionName",!1),P("emval_handle_array",!1),P("emval_free_list",!1),P("emval_symbols",!1),P("init_emval",!1),P("count_emval_handles",!1),P("get_first_emval",!1),P("getStringOrSymbol",!1),P("Emval",!1),P("emval_newers",!1),P("craftEmvalAllocator",!1),P("emval_get_global",!1),P("emval_methodCallers",!1),P("emval_registeredMethods",!1),P("warnOnce",!1),P("stackSave",!1),P("stackRestore",!1),P("stackAlloc",!1),P("AsciiToString",!1),P("stringToAscii",!1),P("UTF16ToString",!1),P("stringToUTF16",!1),P("lengthBytesUTF16",!1),P("UTF32ToString",!1),P("stringToUTF32",!1),P("lengthBytesUTF32",!1),P("allocateUTF8",!1),P("allocateUTF8OnStack",!1),r.writeStackCookie=ne,r.checkStackCookie=oe,C("ALLOC_NORMAL",!1),C("ALLOC_STACK",!1),de=function e(){_t||St(),_t||(de=e)},r.run=St,r.preInit)for("function"==typeof r.preInit&&(r.preInit=[r.preInit]);r.preInit.length>0;)r.preInit.pop()();St(),e.exports=r}));const u=1e3,c=1e3,d=!1,f=!1,p=!1,m=!1,h="initVideo",g="render",v="playAudio",y="initAudio",E="audioCode",w="videoCode",b=1,_=2,T="init",k="decode",S="audioDecode",C="videoDecode",P="close",A="updateConfig",F="key",D="delta";s((function(e){!function(){var r="undefined"!=typeof window&&void 0!==window.document?window.document:{},t=e.exports,n=function(){for(var e,t=[["requestFullscreen","exitFullscreen","fullscreenElement","fullscreenEnabled","fullscreenchange","fullscreenerror"],["webkitRequestFullscreen","webkitExitFullscreen","webkitFullscreenElement","webkitFullscreenEnabled","webkitfullscreenchange","webkitfullscreenerror"],["webkitRequestFullScreen","webkitCancelFullScreen","webkitCurrentFullScreenElement","webkitCancelFullScreen","webkitfullscreenchange","webkitfullscreenerror"],["mozRequestFullScreen","mozCancelFullScreen","mozFullScreenElement","mozFullScreenEnabled","mozfullscreenchange","mozfullscreenerror"],["msRequestFullscreen","msExitFullscreen","msFullscreenElement","msFullscreenEnabled","MSFullscreenChange","MSFullscreenError"]],n=0,o=t.length,i={};n{try{if("object"==typeof WebAssembly&&"function"==typeof WebAssembly.instantiate){const e=new WebAssembly.Module(Uint8Array.of(0,97,115,109,1,0,0,0));if(e instanceof WebAssembly.Module)return new WebAssembly.Instance(e)instanceof WebAssembly.Instance}}catch(e){}})(),Date.now||(Date.now=function(){return(new Date).getTime()}),l.postRun=function(){var e=[],r=[],t={};"VideoEncoder"in self&&(t={hasInit:!1,isEmitInfo:!1,offscreenCanvas:null,offscreenCanvasCtx:null,decoder:new VideoDecoder({output:function(e){if(n.isDestroyed)return;t.isEmitInfo||(n.opt.debug&&console.log("Jb: [worker] Webcodecs Video Decoder initSize"),postMessage({cmd:h,w:e.codedWidth,h:e.codedHeight}),t.isEmitInfo=!0,t.offscreenCanvas=new OffscreenCanvas(e.codedWidth,e.codedHeight),t.offscreenCanvasCtx=t.offscreenCanvas.getContext("2d")),t.offscreenCanvasCtx.drawImage(e,0,0,e.codedWidth,e.codedHeight);let r=t.offscreenCanvas.transferToImageBitmap();postMessage({cmd:g,buffer:r,delay:n.delay,ts:0},[r]),setTimeout((function(){e.close?e.close():e.destroy()}),100)},error:function(e){console.error(e)}}),decode:function(e,r){const o=e[0]>>4==1;if(t.hasInit){const n=new EncodedVideoChunk({data:e.slice(5),timestamp:r,type:o?F:D});t.decoder.decode(n)}else if(o&&0===e[1]){const r=15&e[0];n.setVideoCodec(r);const o=function(e){let r=e.subarray(1,4),t="avc1.";for(let e=0;e<3;e++){let n=r[e].toString(16);n.length<2&&(n="0"+n),t+=n}return{codec:t,description:e}}(e.slice(5));t.decoder.configure(o),t.hasInit=!0}},reset(){t.hasInit=!1,t.isEmitInfo=!1,t.offscreenCanvas=null,t.offscreenCanvasCtx=null}});var n={isDestroyed:!1,opt:{debug:d,useOffscreen:p,useWCS:f,videoBuffer:u,openWebglAlignment:m,videoBufferDelay:c},useOffscreen:function(){return n.opt.useOffscreen&&"undefined"!=typeof OffscreenCanvas},initAudioPlanar:function(e,t){postMessage({cmd:y,sampleRate:t,channels:e});var n=[],o=0;this.playAudioPlanar=function(t,i,a){for(var s=i,u=[],c=0,d=0;d<2;d++){var f=l.HEAPU32[(t>>2)+d]>>2;u[d]=l.HEAPF32.subarray(f,f+s)}if(o){if(!(s>=(i=1024-o)))return o+=s,r[0]=Float32Array.of(...r[0],...u[0]),void(2==e&&(r[1]=Float32Array.of(...r[1],...u[1])));n[0]=Float32Array.of(...r[0],...u[0].subarray(0,i)),2==e&&(n[1]=Float32Array.of(...r[1],...u[1].subarray(0,i))),postMessage({cmd:v,buffer:n,ts:a},n.map((e=>e.buffer))),c=i,s-=i}for(o=s;o>=1024;o-=1024)n[0]=u[0].slice(c,c+=1024),2==e&&(n[1]=u[1].slice(c-1024,c)),postMessage({cmd:v,buffer:n,ts:a},n.map((e=>e.buffer)));o&&(r[0]=u[0].slice(c),2==e&&(r[1]=u[1].slice(c)))}},setVideoCodec:function(e){postMessage({cmd:w,code:e})},setAudioCodec:function(e){postMessage({cmd:E,code:e})},setVideoSize:function(e,r){postMessage({cmd:h,w:e,h:r});var t=e*r,o=t>>2;n.useOffscreen()?(this.offscreenCanvas=new OffscreenCanvas(e,r),this.offscreenCanvasGL=this.offscreenCanvas.getContext("webgl"),this.webglObj=((e,r)=>{var t=["attribute vec4 vertexPos;","attribute vec4 texturePos;","varying vec2 textureCoord;","void main()","{","gl_Position = vertexPos;","textureCoord = texturePos.xy;","}"].join("\n"),n=["precision highp float;","varying highp vec2 textureCoord;","uniform sampler2D ySampler;","uniform sampler2D uSampler;","uniform sampler2D vSampler;","const mat4 YUV2RGB = mat4","(","1.1643828125, 0, 1.59602734375, -.87078515625,","1.1643828125, -.39176171875, -.81296875, .52959375,","1.1643828125, 2.017234375, 0, -1.081390625,","0, 0, 0, 1",");","void main(void) {","highp float y = texture2D(ySampler, textureCoord).r;","highp float u = texture2D(uSampler, textureCoord).r;","highp float v = texture2D(vSampler, textureCoord).r;","gl_FragColor = vec4(y, u, v, 1) * YUV2RGB;","}"].join("\n");r&&e.pixelStorei(e.UNPACK_ALIGNMENT,1);var o=e.createShader(e.VERTEX_SHADER);e.shaderSource(o,t),e.compileShader(o),e.getShaderParameter(o,e.COMPILE_STATUS)||console.log("Vertex shader failed to compile: "+e.getShaderInfoLog(o));var i=e.createShader(e.FRAGMENT_SHADER);e.shaderSource(i,n),e.compileShader(i),e.getShaderParameter(i,e.COMPILE_STATUS)||console.log("Fragment shader failed to compile: "+e.getShaderInfoLog(i));var a=e.createProgram();e.attachShader(a,o),e.attachShader(a,i),e.linkProgram(a),e.getProgramParameter(a,e.LINK_STATUS)||console.log("Program failed to compile: "+e.getProgramInfoLog(a)),e.useProgram(a);var s=e.createBuffer();e.bindBuffer(e.ARRAY_BUFFER,s),e.bufferData(e.ARRAY_BUFFER,new Float32Array([1,1,-1,1,1,-1,-1,-1]),e.STATIC_DRAW);var l=e.getAttribLocation(a,"vertexPos");e.enableVertexAttribArray(l),e.vertexAttribPointer(l,2,e.FLOAT,!1,0,0);var u=e.createBuffer();e.bindBuffer(e.ARRAY_BUFFER,u),e.bufferData(e.ARRAY_BUFFER,new Float32Array([1,0,0,0,1,1,0,1]),e.STATIC_DRAW);var c=e.getAttribLocation(a,"texturePos");function d(r,t){var n=e.createTexture();return e.bindTexture(e.TEXTURE_2D,n),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,e.LINEAR),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.LINEAR),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE),e.bindTexture(e.TEXTURE_2D,null),e.uniform1i(e.getUniformLocation(a,r),t),n}e.enableVertexAttribArray(c),e.vertexAttribPointer(c,2,e.FLOAT,!1,0,0);var f=d("ySampler",0),p=d("uSampler",1),m=d("vSampler",2);return{render:function(r,t,n,o,i){e.viewport(0,0,r,t),e.activeTexture(e.TEXTURE0),e.bindTexture(e.TEXTURE_2D,f),e.texImage2D(e.TEXTURE_2D,0,e.LUMINANCE,r,t,0,e.LUMINANCE,e.UNSIGNED_BYTE,n),e.activeTexture(e.TEXTURE1),e.bindTexture(e.TEXTURE_2D,p),e.texImage2D(e.TEXTURE_2D,0,e.LUMINANCE,r/2,t/2,0,e.LUMINANCE,e.UNSIGNED_BYTE,o),e.activeTexture(e.TEXTURE2),e.bindTexture(e.TEXTURE_2D,m),e.texImage2D(e.TEXTURE_2D,0,e.LUMINANCE,r/2,t/2,0,e.LUMINANCE,e.UNSIGNED_BYTE,i),e.drawArrays(e.TRIANGLE_STRIP,0,4)},destroy:function(){try{e.deleteProgram(a),e.deleteBuffer(s),e.deleteBuffer(u),e.deleteTexture(f),e.deleteTexture(p),e.deleteTexture(m)}catch(e){}}}})(this.offscreenCanvasGL,n.opt.openWebglAlignment),this.draw=function(n,i,a,s){const u=l.HEAPU8.subarray(i,i+t),c=l.HEAPU8.subarray(a,a+o),d=l.HEAPU8.subarray(s,s+o);this.webglObj.render(e,r,u,c,d);let f=this.offscreenCanvas.transferToImageBitmap();postMessage({cmd:g,buffer:f,delay:this.delay,ts:n},[f])}):this.draw=function(e,r,n,i){const a=[Uint8Array.from(l.HEAPU8.subarray(r,r+t)),Uint8Array.from(l.HEAPU8.subarray(n,n+o)),Uint8Array.from(l.HEAPU8.subarray(i,i+o))];postMessage({cmd:g,output:a,delay:this.delay,ts:e},a.map((e=>e.buffer)))}},getDelay:function(e){if(!e)return-1;if(this.firstTimestamp){if(e){const r=Date.now()-this.startTimestamp,t=e-this.firstTimestamp;this.delay=r>=t?r-t:t-r}}else this.firstTimestamp=e,this.startTimestamp=Date.now(),this.delay=-1;return this.delay},resetDelay:function(){this.firstTimestamp=null,this.startTimestamp=null,this.delay=-1},init:function(){n.opt.debug&&console.log("Jb: [worker] init");const r=e=>{n.opt.useWCS&&n.useOffscreen()&&e.type===_&&t.decode?t.decode(e.payload,e.ts):e.decoder.decode(e.payload,e.ts)};this.stopId=setInterval((()=>{if(!n.isDestroyed&&e.length)if(this.dropping){for((t=e.shift()).type===b&&0===t.payload[1]&&r(t);!t.isIFrame&&e.length;)(t=e.shift()).type===b&&0===t.payload[1]&&r(t);t.isIFrame&&(this.dropping=!1,r(t))}else{var t=e[0];if(-1===this.getDelay(t.ts))e.shift(),r(t);else if(this.delay>n.opt.videoBuffer+n.opt.videoBufferDelay)this.resetDelay(),this.dropping=!0;else for(;e.length&&(t=e[0],this.getDelay(t.ts)>n.opt.videoBuffer);)e.shift(),r(t)}}),10)},close:function(){n.isDestroyed=!0,n.opt.debug&&console.log("Jb: [worker]: close"),clearInterval(this.stopId),this.stopId=null,o.clear&&o.clear(),o.delete&&o.delete(),i.clear&&i.clear(),i.delete&&i.delete(),t.reset&&t.reset(),this.firstTimestamp=null,this.startTimestamp=null,this.delay=-1,this.dropping=!1,this.webglObj&&(this.webglObj.destroy(),this.offscreenCanvas=null,this.offscreenCanvasGL=null,this.offscreenCanvasCtx=null),e=[],r=[],delete this.playAudioPlanar,delete this.draw},pushBuffer:function(r,t){t.type===b?e.push({ts:t.ts,payload:r,decoder:o,type:b}):t.type===_&&e.push({ts:t.ts,payload:r,decoder:i,type:_,isIFrame:t.isIFrame})}},o=new l.AudioDecoder(n),i=new l.VideoDecoder(n);postMessage({cmd:T}),self.onmessage=function(e){var r=e.data;switch(r.cmd){case T:try{n.opt=Object.assign(n.opt,JSON.parse(r.opt))}catch(e){}o.sample_rate=r.sampleRate,n.init();break;case k:n.pushBuffer(r.buffer,r.options);break;case S:o.decode(r.buffer,r.ts);break;case C:i.decode(r.buffer,r.ts);break;case P:n.close();break;case A:n.opt[r.key]=r.value}}}})); ================================================ FILE: web/public/static/js/jessibuca/jessibuca.d.ts ================================================ declare namespace Jessibuca { /** 超时信息 */ enum TIMEOUT { /** 当play()的时候,如果没有数据返回 */ loadingTimeout = 'loadingTimeout', /** 当播放过程中,如果超过timeout之后没有数据渲染 */ delayTimeout = 'delayTimeout', } /** 错误信息 */ enum ERROR { /** 播放错误,url 为空的时候,调用 play 方法 */ playError = 'playError', /** http 请求失败 */ fetchError = 'fetchError', /** websocket 请求失败 */ websocketError = 'websocketError', /** webcodecs 解码 h265 失败 */ webcodecsH265NotSupport = 'webcodecsH265NotSupport', /** mediaSource 解码 h265 失败 */ mediaSourceH265NotSupport = 'mediaSourceH265NotSupport', /** wasm 解码失败 */ wasmDecodeError = 'wasmDecodeError', } interface Config { /** * 播放器容器 * * 若为 string ,则底层调用的是 document.getElementById('id') * */ container: HTMLElement | string; /** * 设置最大缓冲时长,单位秒,播放器会自动消除延迟 */ videoBuffer?: number; /** * worker地址 * * 默认引用的是根目录下面的decoder.js文件 ,decoder.js 与 decoder.wasm文件必须是放在同一个目录下面。 */ decoder?: string; /** * 是否不使用离屏模式(提升渲染能力) */ forceNoOffscreen?: boolean; /** * 是否开启当页面的'visibilityState'变为'hidden'的时候,自动暂停播放。 */ hiddenAutoPause?: boolean; /** * 是否有音频,如果设置`false`,则不对音频数据解码,提升性能。 */ hasAudio?: boolean; /** * 设置旋转角度,只支持,0(默认),180,270 三个值 */ rotate?: boolean; /** * 1. 当为`true`的时候:视频画面做等比缩放后,高或宽对齐canvas区域,画面不被拉伸,但有黑边。 等同于 `setScaleMode(1)` * 2. 当为`false`的时候:视频画面完全填充canvas区域,画面会被拉伸。等同于 `setScaleMode(0)` */ isResize?: boolean; /** * 1. 当为`true`的时候:视频画面做等比缩放后,完全填充canvas区域,画面不被拉伸,没有黑边,但画面显示不全。等同于 `setScaleMode(2)` */ isFullResize?: boolean; /** * 1. 当为`true`的时候:ws协议不检验是否以.flv为依据,进行协议解析。 */ isFlv?: boolean; /** * 是否开启控制台调试打 */ debug?: boolean; /** * 1. 设置超时时长, 单位秒 * 2. 在连接成功之前(loading)和播放中途(heart),如果超过设定时长无数据返回,则回调timeout事件 */ timeout?: number; /** * 1. 设置超时时长, 单位秒 * 2. 在连接成功之前,如果超过设定时长无数据返回,则回调timeout事件 */ heartTimeout?: number; /** * 1. 设置超时时长, 单位秒 * 2. 在连接成功之前,如果超过设定时长无数据返回,则回调timeout事件 */ loadingTimeout?: number; /** * 是否支持屏幕的双击事件,触发全屏,取消全屏事件 */ supportDblclickFullscreen?: boolean; /** * 是否显示网 */ showBandwidth?: boolean; /** * 配置操作按钮 */ operateBtns?: { /** 是否显示全屏按钮 */ fullscreen?: boolean; /** 是否显示截图按钮 */ screenshot?: boolean; /** 是否显示播放暂停按钮 */ play?: boolean; /** 是否显示声音按钮 */ audio?: boolean; /** 是否显示录制按 */ record?: boolean; }; /** * 开启屏幕常亮,在手机浏览器上, canvas标签渲染视频并不会像video标签那样保持屏幕常亮 */ keepScreenOn?: boolean; /** * 是否开启声音,默认是关闭声音播放的 */ isNotMute?: boolean; /** * 加载过程中文案 */ loadingText?: string; /** * 背景图片 */ background?: string; /** * 是否开启MediaSource硬解码 * * 视频编码只支持H.264视频(Safari on iOS不支持) * * 不支持 forceNoOffscreen 为 false (开启离屏渲染) */ useMSE?: boolean; /** * 是否开启Webcodecs硬解码 * * 视频编码只支持H.264视频 (需在chrome 94版本以上,需要https或者localhost环境) * * 支持 forceNoOffscreen 为 false (开启离屏渲染) * */ useWCS?: boolean; /** * 是否开启键盘快捷键 * 目前支持的键盘快捷键有:esc -> 退出全屏;arrowUp -> 声音增加;arrowDown -> 声音减少; */ hotKey?: boolean; /** * 在使用MSE或者Webcodecs 播放H265的时候,是否自动降级到wasm模式。 * 设置为false 则直接关闭播放,抛出Error 异常,设置为true 则会自动切换成wasm模式播放。 */ autoWasm?: boolean; /** * heartTimeout 心跳超时之后自动再播放,不再抛出异常,而直接重新播放视频地址。 */ heartTimeoutReplay?: boolean, /** * heartTimeoutReplay 从试次数,超过之后,不再自动播放 */ heartTimeoutReplayTimes?: number, /** * loadingTimeout loading之后自动再播放,不再抛出异常,而直接重新播放视频地址。 */ loadingTimeoutReplay?: boolean, /** * heartTimeoutReplay 从试次数,超过之后,不再自动播放 */ loadingTimeoutReplayTimes?: number /** * wasm解码报错之后,不再抛出异常,而是直接重新播放视频地址。 */ wasmDecodeErrorReplay?: boolean, /** * https://github.com/langhuihui/jessibuca/issues/152 解决方案 * 例如:WebGL图像预处理默认每次取4字节的数据,但是540x960分辨率下的U、V分量宽度是540/2=270不能被4整除,导致绿屏。 */ openWebglAlignment?: boolean, /** * webcodecs硬解码是否通过video标签渲染 */ wcsUseVideoRender?: boolean, /** * 底部控制台是否自动隐藏 */ controlAutoHide?: boolean, /** * 录制的视频格式 */ recordType?: 'webm' | 'mp4', /** * 是否使用web全屏(旋转90度)(只会在移动端生效)。 */ useWebFullScreen?: boolean, /** * 是否自动使用系统全屏 */ autoUseSystemFullScreen?: boolean, } } declare class Jessibuca { constructor(config?: Jessibuca.Config); /** * 是否开启控制台调试打印 @example // 开启 jessibuca.setDebug(true) // 关闭 jessibuca.setDebug(false) */ setDebug(flag: boolean): void; /** * 静音 @example jessibuca.mute() */ mute(): void; /** * 取消静音 @example jessibuca.cancelMute() */ cancelMute(): void; /** * 留给上层用户操作来触发音频恢复的方法。 * * iPhone,chrome等要求自动播放时,音频必须静音,需要由一个真实的用户交互操作来恢复,不能使用代码。 * * https://developers.google.com/web/updates/2017/09/autoplay-policy-changes */ audioResume(): void; /** * * 设置超时时长, 单位秒 * 在连接成功之前和播放中途,如果超过设定时长无数据返回,则回调timeout事件 @example jessibuca.setTimeout(10) jessibuca.on('timeout',function(){ // }); */ setTimeout(): void; /** * @param mode * 0 视频画面完全填充canvas区域,画面会被拉伸 等同于参数 `isResize` 为false * * 1 视频画面做等比缩放后,高或宽对齐canvas区域,画面不被拉伸,但有黑边 等同于参数 `isResize` 为true * * 2 视频画面做等比缩放后,完全填充canvas区域,画面不被拉伸,没有黑边,但画面显示不全 等同于参数 `isFullResize` 为true @example jessibuca.setScaleMode(0) jessibuca.setScaleMode(1) jessibuca.setScaleMode(2) */ setScaleMode(mode: number): void; /** * 暂停播放 * * 可以在pause 之后,再调用 `play()`方法就继续播放之前的流。 @example jessibuca.pause().then(()=>{ console.log('pause success') jessibuca.play().then(()=>{ }).catch((e)=>{ }) }).catch((e)=>{ console.log('pause error',e); }) */ pause(): Promise; /** * 关闭视频,不释放底层资源 @example jessibuca.close(); */ close(): void; /** * 关闭视频,释放底层资源 @example jessibuca.destroy() */ destroy(): void; /** * 清理画布为黑色背景 @example jessibuca.clearView() */ clearView(): void; /** * 播放视频 @example jessibuca.play('url').then(()=>{ console.log('play success') }).catch((e)=>{ console.log('play error',e) }) // 添加请求头 jessibuca.play('url',{headers:{'Authorization':'test111'}}).then(()=>{ console.log('play success') }).catch((e)=>{ console.log('play error',e) }) */ play(url?: string, options?: { headers: Object }): Promise; /** * 重新调整视图大小 */ resize(): void; /** * 设置最大缓冲时长,单位秒,播放器会自动消除延迟。 * * 等同于 `videoBuffer` 参数。 * @example // 设置 200ms 缓冲 jessibuca.setBufferTime(0.2) */ setBufferTime(time: number): void; /** * 设置旋转角度,只支持,0(默认) ,180,270 三个值。 * * > 可用于实现监控画面小窗和全屏效果,由于iOS没有全屏API,此方法可以模拟页面内全屏效果而且多端效果一致。 * @example jessibuca.setRotate(0) jessibuca.setRotate(90) jessibuca.setRotate(270) */ setRotate(deg: number): void; /** * * 设置音量大小,取值0 — 1 * * > 区别于 mute 和 cancelMute 方法,虽然设置setVolume(0) 也能达到 mute方法,但是mute 方法是不调用底层播放音频的,能提高性能。而setVolume(0)只是把声音设置为0 ,以达到效果。 * @param volume 当为0时,完全无声;当为1时,最大音量,默认值 @example jessibuca.setVolume(0.2) jessibuca.setVolume(0) jessibuca.setVolume(1) */ setVolume(volume: number): void; /** * 返回是否加载完毕 @example var result = jessibuca.hasLoaded() console.log(result) // true */ hasLoaded(): boolean; /** * 开启屏幕常亮,在手机浏览器上, canvas标签渲染视频并不会像video标签那样保持屏幕常亮。 * H5目前在chrome\edge 84, android chrome 84及以上有原生亮屏API, 需要是https页面 * 其余平台为模拟实现,此时为兼容实现,并不保证所有浏览器都支持 @example jessibuca.setKeepScreenOn() */ setKeepScreenOn(): boolean; /** * 全屏(取消全屏)播放视频 @example jessibuca.setFullscreen(true) // jessibuca.setFullscreen(false) */ setFullscreen(flag: boolean): void; /** * * 截图,调用后弹出下载框保存截图 * @param filename 可选参数, 保存的文件名, 默认 `时间戳` * @param format 可选参数, 截图的格式,可选png或jpeg或者webp ,默认 `png` * @param quality 可选参数, 当格式是jpeg或者webp时,压缩质量,取值0 ~ 1 ,默认 `0.92` * @param type 可选参数, 可选download或者base64或者blob,默认`download` @example jessibuca.screenshot("test","png",0.5) const base64 = jessibuca.screenshot("test","png",0.5,'base64') const fileBlob = jessibuca.screenshot("test",'blob') */ screenshot(filename?: string, format?: string, quality?: number, type?: string): void; /** * 开始录制。 * @param fileName 可选,默认时间戳 * @param fileType 可选,默认webm,支持webm 和mp4 格式 @example jessibuca.startRecord('xxx','webm') */ startRecord(fileName: string, fileType: string): void; /** * 暂停录制并下载。 @example jessibuca.stopRecordAndSave() */ stopRecordAndSave(): void; /** * 返回是否正在播放中状态。 @example var result = jessibuca.isPlaying() console.log(result) // true */ isPlaying(): boolean; /** * 返回是否静音。 @example var result = jessibuca.isMute() console.log(result) // true */ isMute(): boolean; /** * 返回是否正在录制。 @example var result = jessibuca.isRecording() console.log(result) // true */ isRecording(): boolean; /** * 切换底部控制条 隐藏/显示 * @param isShow * * @example * jessibuca.toggleControlBar(true) // 显示 * jessibuca.toggleControlBar(false) // 隐藏 * jessibuca.toggleControlBar() // 切换 隐藏/显示 */ toggleControlBar(isShow:boolean): void; /** * 获取底部控制条是否显示 */ getControlBarShow(): boolean; /** * 监听 jessibuca 初始化事件 * @example * jessibuca.on("load",function(){console.log('load')}) */ on(event: 'load', callback: () => void): void; /** * 视频播放持续时间,单位ms * @example * jessibuca.on('timeUpdate',function (ts) {console.log('timeUpdate',ts);}) */ on(event: 'timeUpdate', callback: () => void): void; /** * 当解析出视频信息时回调,2个回调参数 * @example * jessibuca.on("videoInfo",function(data){console.log('width:',data.width,'height:',data.width)}) */ on(event: 'videoInfo', callback: (data: { /** 视频宽 */ width: number; /** 视频高 */ height: number; }) => void): void; /** * 当解析出音频信息时回调,2个回调参数 * @example * jessibuca.on("audioInfo",function(data){console.log('numOfChannels:',data.numOfChannels,'sampleRate',data.sampleRate)}) */ on(event: 'audioInfo', callback: (data: { /** 声频通道 */ numOfChannels: number; /** 采样率 */ sampleRate: number; }) => void): void; /** * 信息,包含错误信息 * @example * jessibuca.on("log",function(data){console.log('data:',data)}) */ on(event: 'log', callback: () => void): void; /** * 错误信息 * @example * jessibuca.on("error",function(error){ if(error === Jessibuca.ERROR.fetchError){ // } else if(error === Jessibuca.ERROR.webcodecsH265NotSupport){ // } console.log('error:',error) }) */ on(event: 'error', callback: (err: Jessibuca.ERROR) => void): void; /** * 当前网速, 单位KB 每秒1次, * @example * jessibuca.on("kBps",function(data){console.log('kBps:',data)}) */ on(event: 'kBps', callback: (value: number) => void): void; /** * 渲染开始 * @example * jessibuca.on("start",function(){console.log('start render')}) */ on(event: 'start', callback: () => void): void; /** * 当设定的超时时间内无数据返回,则回调 * @example * jessibuca.on("timeout",function(error){console.log('timeout:',error)}) */ on(event: 'timeout', callback: (error: Jessibuca.TIMEOUT) => void): void; /** * 当play()的时候,如果没有数据返回,则回调 * @example * jessibuca.on("loadingTimeout",function(){console.log('timeout')}) */ on(event: 'loadingTimeout', callback: () => void): void; /** * 当播放过程中,如果超过timeout之后没有数据渲染,则抛出异常。 * @example * jessibuca.on("delayTimeout",function(){console.log('timeout')}) */ on(event: 'delayTimeout', callback: () => void): void; /** * 当前是否全屏 * @example * jessibuca.on("fullscreen",function(flag){console.log('is fullscreen',flag)}) */ on(event: 'fullscreen', callback: () => void): void; /** * 触发播放事件 * @example * jessibuca.on("play",function(flag){console.log('play')}) */ on(event: 'play', callback: () => void): void; /** * 触发暂停事件 * @example * jessibuca.on("pause",function(flag){console.log('pause')}) */ on(event: 'pause', callback: () => void): void; /** * 触发声音事件,返回boolean值 * @example * jessibuca.on("mute",function(flag){console.log('is mute',flag)}) */ on(event: 'mute', callback: () => void): void; /** * 流状态统计,流开始播放后回调,每秒1次。 * @example * jessibuca.on("stats",function(s){console.log("stats is",s)}) */ on(event: 'stats', callback: (stats: { /** 当前缓冲区时长,单位毫秒 */ buf: number; /** 当前视频帧率 */ fps: number; /** 当前音频码率,单位byte */ abps: number; /** 当前视频码率,单位byte */ vbps: number; /** 当前视频帧pts,单位毫秒 */ ts: number; }) => void): void; /** * 渲染性能统计,流开始播放后回调,每秒1次。 * @param performance 0: 表示卡顿,1: 表示流畅,2: 表示非常流程 * @example * jessibuca.on("performance",function(performance){console.log("performance is",performance)}) */ on(event: 'performance', callback: (performance: 0 | 1 | 2) => void): void; /** * 录制开始的事件 * @example * jessibuca.on("recordStart",function(){console.log("record start")}) */ on(event: 'recordStart', callback: () => void): void; /** * 录制结束的事件 * @example * jessibuca.on("recordEnd",function(){console.log("record end")}) */ on(event: 'recordEnd', callback: () => void): void; /** * 录制的时候,返回的录制时长,1s一次 * @example * jessibuca.on("recordingTimestamp",function(timestamp){console.log("recordingTimestamp is",timestamp)}) */ on(event: 'recordingTimestamp', callback: (timestamp: number) => void): void; /** * 监听调用play方法 经过 初始化-> 网络请求-> 解封装 -> 解码 -> 渲染 一系列过程的时间消耗 * @param event * @param callback */ on(event: 'playToRenderTimes', callback: (times: { playInitStart: number, // 1 初始化 playStart: number, // 2 初始化 streamStart: number, // 3 网络请求 streamResponse: number, // 4 网络请求 demuxStart: number, // 5 解封装 decodeStart: number, // 6 解码 videoStart: number, // 7 渲染 playTimestamp: number,// playStart- playInitStart streamTimestamp: number,// streamStart - playStart streamResponseTimestamp: number,// streamResponse - streamStart demuxTimestamp: number, // demuxStart - streamResponse decodeTimestamp: number, // decodeStart - demuxStart videoTimestamp: number,// videoStart - decodeStart allTimestamp: number // videoStart - playInitStart }) => void): void /** * 监听方法 * @example jessibuca.on("load",function(){console.log('load')}) */ on(event: string, callback: Function): void; } export default Jessibuca; ================================================ FILE: web/public/static/js/jessibuca/jessibuca.js ================================================ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).jessibuca=t()}(this,(function(){"use strict";var e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{};function t(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}function i(e,t){return e(t={exports:{}},t.exports),t.exports}var o=i((function(e){function t(i){return e.exports=t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},e.exports.__esModule=!0,e.exports.default=e.exports,t(i)}e.exports=t,e.exports.__esModule=!0,e.exports.default=e.exports}));t(o);var r=i((function(e){var t=o.default;e.exports=function(e,i){if("object"!=t(e)||!e)return e;var o=e[Symbol.toPrimitive];if(void 0!==o){var r=o.call(e,i||"default");if("object"!=t(r))return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===i?String:Number)(e)},e.exports.__esModule=!0,e.exports.default=e.exports}));t(r);var s=i((function(e){var t=o.default;e.exports=function(e){var i=r(e,"string");return"symbol"==t(i)?i:i+""},e.exports.__esModule=!0,e.exports.default=e.exports}));t(s);var a=t(i((function(e){e.exports=function(e,t,i){return(t=s(t))in e?Object.defineProperty(e,t,{value:i,enumerable:!0,configurable:!0,writable:!0}):e[t]=i,e},e.exports.__esModule=!0,e.exports.default=e.exports})));const n=0,A=1,d="flv",c="m7s",l="mp4",u="webm",h="jessibuca",p='"3.3.23"',g={videoBuffer:1e3,videoBufferDelay:1e3,isResize:!0,isFullResize:!1,isFlv:!1,debug:!1,hotKey:!1,loadingTimeout:10,heartTimeout:5,timeout:10,loadingTimeoutReplay:!0,heartTimeoutReplay:!0,loadingTimeoutReplayTimes:3,heartTimeoutReplayTimes:3,supportDblclickFullscreen:!1,showBandwidth:!1,keepScreenOn:!1,isNotMute:!1,hasAudio:!0,hasVideo:!0,operateBtns:{fullscreen:!1,screenshot:!1,play:!1,audio:!1,record:!1},controlAutoHide:!1,hasControl:!1,loadingText:"",background:"",decoder:"decoder.js",url:"",rotate:0,forceNoOffscreen:!0,hiddenAutoPause:!1,protocol:A,demuxType:d,useWCS:!1,wcsUseVideoRender:!1,useMSE:!1,useOffscreen:!1,autoWasm:!0,wasmDecodeErrorReplay:!0,openWebglAlignment:!1,wasmDecodeAudioSyncVideo:!1,recordType:u,useWebFullScreen:!1,loadingDecoderWorkerTimeout:10,autoUseSystemFullScreen:!0},m="init",f="initVideo",b="render",y="playAudio",v="initAudio",w="audioCode",S="videoCode",E="wasmError",B="Invalid NAL unit size",C=1,R=2,k=8,T=9,I="init",x="decode",D="audioDecode",j="close",L="updateConfig",F={fullscreen:"fullscreen$2",webFullscreen:"webFullscreen",decoderWorkerInit:"decoderWorkerInit",play:"play",playing:"playing",pause:"pause",mute:"mute",load:"load",loading:"loading",videoInfo:"videoInfo",timeUpdate:"timeUpdate",audioInfo:"audioInfo",log:"log",error:"error",kBps:"kBps",timeout:"timeout",delayTimeout:"delayTimeout",loadingTimeout:"loadingTimeout",stats:"stats",performance:"performance",record:"record",recording:"recording",recordingTimestamp:"recordingTimestamp",recordStart:"recordStart",recordEnd:"recordEnd",recordCreateError:"recordCreateError",buffer:"buffer",videoFrame:"videoFrame",start:"start",metadata:"metadata",resize:"resize",streamEnd:"streamEnd",streamSuccess:"streamSuccess",streamMessage:"streamMessage",streamError:"streamError",volumechange:"volumechange",volume:"volume",destroy:"destroy",mseSourceOpen:"mseSourceOpen",mseSourceClose:"mseSourceClose",mseSourceBufferError:"mseSourceBufferError",mseSourceBufferBusy:"mseSourceBufferBusy",mseSourceBufferFull:"mseSourceBufferFull",videoWaiting:"videoWaiting",videoTimeUpdate:"videoTimeUpdate",videoSyncAudio:"videoSyncAudio",playToRenderTimes:"playToRenderTimes"},M={load:F.load,timeUpdate:F.timeUpdate,videoInfo:F.videoInfo,audioInfo:F.audioInfo,error:F.error,kBps:F.kBps,log:F.log,start:F.start,timeout:F.timeout,loadingTimeout:F.loadingTimeout,delayTimeout:F.delayTimeout,fullscreen:"fullscreen",webFullscreen:F.webFullscreen,play:F.play,pause:F.pause,mute:F.mute,stats:F.stats,volumechange:F.volumechange,performance:F.performance,recordingTimestamp:F.recordingTimestamp,recordStart:F.recordStart,recordEnd:F.recordEnd,playToRenderTimes:F.playToRenderTimes,volume:F.volume},O={playError:"playIsNotPauseOrUrlIsNull",fetchError:"fetchError",websocketError:"websocketError",webcodecsH265NotSupport:"webcodecsH265NotSupport",webcodecsConfigureError:"webcodecsConfigureError",webcodecsDecodeError:"webcodecsDecodeError",webcodecsWidthOrHeightChange:"webcodecsWidthOrHeightChange",mediaSourceH265NotSupport:"mediaSourceH265NotSupport",mediaSourceFull:F.mseSourceBufferFull,mseSourceBufferError:F.mseSourceBufferError,mediaSourceAppendBufferError:"mediaSourceAppendBufferError",mediaSourceBufferListLarge:"mediaSourceBufferListLarge",mediaSourceAppendBufferEndTimeout:"mediaSourceAppendBufferEndTimeout",wasmDecodeError:"wasmDecodeError",webglAlignmentError:"webglAlignmentError",webglContextLostError:"webglContextLostError",webglInitError:"webglInitError"},V="notConnect",U="open",Q="close",W="error",J={download:"download",base64:"base64",blob:"blob"},G={7:"H264(AVC)",12:"H265(HEVC)"},P=12,N={10:"AAC",7:"ALAW",8:"MULAW"},H=38,z=0,Y=1,X=2,q="webcodecs",Z="webgl",K="offscreen",_="key",$="delta",ee='video/mp4; codecs="avc1.64002A"',te="ended",ie="open",oe="closed",re=1e3,se=27,ae=38,ne=40,Ae="A key frame is required after configure() or flush()",de="Cannot call 'decode' on a closed codec",ce="The user aborted a request",le="AbortError",ue="AbortError",he=0,pe=1,ge=3,me=16;class fe{constructor(e){this.log=function(t){if(e._opt&&e._opt.debug){for(var i=arguments.length,o=new Array(i>1?i-1:0),r=1;r1?i-1:0),r=1;r1?t-1:0),o=1;o3&&void 0!==arguments[3]?arguments[3]:{};if(!e)return;if(Array.isArray(t))return t.map((t=>this.proxy(e,t,i,o)));e.addEventListener(t,i,o);const r=()=>e.removeEventListener(t,i,o);return this.destroys.push(r),r}destroy(){this.master.debug&&this.master.debug.log("Events","destroy"),this.destroys.forEach((e=>e()))}}var ye=i((function(e){!function(){var t="undefined"!=typeof window&&void 0!==window.document?window.document:{},i=e.exports,o=function(){for(var e,i=[["requestFullscreen","exitFullscreen","fullscreenElement","fullscreenEnabled","fullscreenchange","fullscreenerror"],["webkitRequestFullscreen","webkitExitFullscreen","webkitFullscreenElement","webkitFullscreenEnabled","webkitfullscreenchange","webkitfullscreenerror"],["webkitRequestFullScreen","webkitCancelFullScreen","webkitCurrentFullScreenElement","webkitCancelFullScreen","webkitfullscreenchange","webkitfullscreenerror"],["mozRequestFullScreen","mozCancelFullScreen","mozFullScreenElement","mozFullScreenEnabled","mozfullscreenchange","mozfullscreenerror"],["msRequestFullscreen","msExitFullscreen","msFullscreenElement","msFullscreenEnabled","MSFullscreenChange","MSFullscreenError"]],o=0,r=i.length,s={};o0&&void 0!==arguments[0]?arguments[0]:"").split(","),t=atob(e[1]),i=e[0].replace("data:","").replace(";base64","");let o=t.length,r=new Uint8Array(o);for(;o--;)r[o]=t.charCodeAt(o);return new File([r],"file",{type:i})}function Se(){return(new Date).getTime()}function Ee(e,t,i){return Math.max(Math.min(e,Math.max(t,i)),Math.min(t,i))}function Be(e,t,i){if(e)return"object"==typeof t&&Object.keys(t).forEach((i=>{Be(e,i,t[i])})),e.style[t]=i,e}function Ce(e,t){let i=!(arguments.length>2&&void 0!==arguments[2])||arguments[2];if(!e)return 0;const o=getComputedStyle(e,null).getPropertyValue(t);return i?parseFloat(o):o}function Re(){return performance&&"function"==typeof performance.now?performance.now():Date.now()}function ke(e){let t=0,i=Re();return o=>{t+=o;const r=Re(),s=r-i;s>=1e3&&(e(t/s*1e3),i=r,t=0)}}function Te(){return/iphone|ipod|android.*mobile|windows.*phone|blackberry.*mobile/i.test(window.navigator.userAgent.toLowerCase())}function Ie(e){return ye.isFullscreen&&ye.element===e}function xe(e){return null==e}function De(e){return!0===e||!1===e}function je(e){return!xe(e)}function Le(e){var t;if(e>-1){var i=Math.floor(e/3600),o=Math.floor(e/60)%60,r=e%60;t=i<10?"0"+i+":":i+":",o<10&&(t+="0"),t+=o+":",(r=Math.round(r))<10&&(t+="0"),t+=r.toFixed(0)}return t}function Fe(e){const t=e||window.event;return t.target||t.srcElement}function Me(e){let t=!1;return e&&(e.innerHTML="",e.parentNode&&(e.parentNode.removeChild(e),t=!0)),t}function Oe(e,t){let i=[];i[0]=t?28:44,i[1]=1,i[2]=0,i[3]=0,i[4]=0;const o=new Uint8Array(i.length+e.byteLength);return o.set(i,0),o.set(e,i.length),o}function Ve(e){return!0!==e&&"true"!==e}ye.isEnabled,(()=>{try{if("object"==typeof WebAssembly&&"function"==typeof WebAssembly.instantiate){const e=new WebAssembly.Module(Uint8Array.of(0,97,115,109,1,0,0,0));if(e instanceof WebAssembly.Module)return new WebAssembly.Instance(e)instanceof WebAssembly.Instance}}catch(e){}})();class Ue{on(e,t,i){const o=this.e||(this.e={});return(o[e]||(o[e]=[])).push({fn:t,ctx:i}),this}once(e,t,i){const o=this;function r(){o.off(e,r);for(var s=arguments.length,a=new Array(s),n=0;n1?i-1:0),r=1;r{delete i[e]})),void delete this.e;const o=i[e],r=[];if(o&&t)for(let e=0,i=o.length;e=200&&t.status<=299}function Pe(e){try{e.dispatchEvent(new MouseEvent("click"))}catch(i){var t=document.createEvent("MouseEvents");t.initMouseEvent("click",!0,!0,window,0,0,0,80,20,!1,!1,!1,!1,0,null),e.dispatchEvent(t)}}var Ne=We.navigator&&/Macintosh/.test(navigator.userAgent)&&/AppleWebKit/.test(navigator.userAgent)&&!/Safari/.test(navigator.userAgent),He="object"!=typeof window||window!==We?function(){}:"download"in HTMLAnchorElement.prototype&&!Ne?function(e,t,i){var o=We.URL||We.webkitURL,r=document.createElementNS("http://www.w3.org/1999/xhtml","a");t=t||e.name||"download",r.download=t,r.rel="noopener","string"==typeof e?(r.href=e,r.origin!==location.origin?Ge(r.href)?Je(e,t,i):Pe(r,r.target="_blank"):Pe(r)):(r.href=o.createObjectURL(e),setTimeout((function(){o.revokeObjectURL(r.href)}),4e4),setTimeout((function(){Pe(r)}),0))}:"msSaveOrOpenBlob"in navigator?function(e,t,i){if(t=t||e.name||"download","string"==typeof e)if(Ge(e))Je(e,t,i);else{var o=document.createElement("a");o.href=e,o.target="_blank",setTimeout((function(){Pe(o)}))}else navigator.msSaveOrOpenBlob(function(e,t){return void 0===t?t={autoBom:!1}:"object"!=typeof t&&(console.warn("Deprecated: Expected third argument to be a object"),t={autoBom:!t}),t.autoBom&&/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(e.type)?new Blob([String.fromCharCode(65279),e],{type:e.type}):e}(e,i),t)}:function(e,t,i,o){if((o=o||open("","_blank"))&&(o.document.title=o.document.body.innerText="downloading..."),"string"==typeof e)return Je(e,t,i);var r="application/octet-stream"===e.type,s=/constructor/i.test(We.HTMLElement)||We.safari,a=/CriOS\/[\d]+/.test(navigator.userAgent);if((a||r&&s||Ne)&&"undefined"!=typeof FileReader){var n=new FileReader;n.onloadend=function(){var e=n.result;e=a?e:e.replace(/^data:[^;]*;/,"data:attachment/file;"),o?o.location.href=e:location=e,o=null},n.readAsDataURL(e)}else{var A=We.URL||We.webkitURL,d=A.createObjectURL(e);o?o.location=d:location.href=d,o=null,setTimeout((function(){A.revokeObjectURL(d)}),4e4)}};class ze extends Qe{constructor(e){super(),this.player=e;const t=document.createElement("canvas");t.style.position="absolute",t.style.top=0,t.style.left=0,this.$videoElement=t,e.$container.appendChild(this.$videoElement),this.context2D=null,this.contextGl=null,this.contextGlRender=null,this.contextGlDestroy=null,this.bitmaprenderer=null,this.renderType=null,this.isContextGlRenderLost=!1,this.videoInfo={width:"",height:"",encType:""},this._initCanvasRender(),this.player.debug.log("CanvasVideo","init")}async destroy(){super.destroy(),this.contextGl&&(this.contextGl=null),this.context2D&&(this.context2D=null),this.contextGlRender&&(this.contextGlDestroy&&this.contextGlDestroy(),this.contextGlDestroy=null,this.contextGlRender=null),this.bitmaprenderer&&(this.bitmaprenderer=null),this.renderType=null,this.isContextGlRenderLost=!1,this.player.debug.log("CanvasVideoLoader","destroy")}_initContextGl(){if(this.contextGl=function(e){let t=null;const i=["webgl","experimental-webgl","moz-webgl","webkit-3d"];let o=0;for(;!t&&o{var i=["attribute vec4 vertexPos;","attribute vec4 texturePos;","varying vec2 textureCoord;","void main()","{","gl_Position = vertexPos;","textureCoord = texturePos.xy;","}"].join("\n"),o=["precision highp float;","varying highp vec2 textureCoord;","uniform sampler2D ySampler;","uniform sampler2D uSampler;","uniform sampler2D vSampler;","const mat4 YUV2RGB = mat4","(","1.1643828125, 0, 1.59602734375, -.87078515625,","1.1643828125, -.39176171875, -.81296875, .52959375,","1.1643828125, 2.017234375, 0, -1.081390625,","0, 0, 0, 1",");","void main(void) {","highp float y = texture2D(ySampler, textureCoord).r;","highp float u = texture2D(uSampler, textureCoord).r;","highp float v = texture2D(vSampler, textureCoord).r;","gl_FragColor = vec4(y, u, v, 1) * YUV2RGB;","}"].join("\n");t&&e.pixelStorei(e.UNPACK_ALIGNMENT,1);var r=e.createShader(e.VERTEX_SHADER);e.shaderSource(r,i),e.compileShader(r),e.getShaderParameter(r,e.COMPILE_STATUS)||console.log("Vertex shader failed to compile: "+e.getShaderInfoLog(r));var s=e.createShader(e.FRAGMENT_SHADER);e.shaderSource(s,o),e.compileShader(s),e.getShaderParameter(s,e.COMPILE_STATUS)||console.log("Fragment shader failed to compile: "+e.getShaderInfoLog(s));var a=e.createProgram();e.attachShader(a,r),e.attachShader(a,s),e.linkProgram(a),e.getProgramParameter(a,e.LINK_STATUS)||console.log("Program failed to compile: "+e.getProgramInfoLog(a)),e.useProgram(a);var n=e.createBuffer();e.bindBuffer(e.ARRAY_BUFFER,n),e.bufferData(e.ARRAY_BUFFER,new Float32Array([1,1,-1,1,1,-1,-1,-1]),e.STATIC_DRAW);var A=e.getAttribLocation(a,"vertexPos");e.enableVertexAttribArray(A),e.vertexAttribPointer(A,2,e.FLOAT,!1,0,0);var d=e.createBuffer();e.bindBuffer(e.ARRAY_BUFFER,d),e.bufferData(e.ARRAY_BUFFER,new Float32Array([1,0,0,0,1,1,0,1]),e.STATIC_DRAW);var c=e.getAttribLocation(a,"texturePos");function l(t,i){var o=e.createTexture();return e.bindTexture(e.TEXTURE_2D,o),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,e.LINEAR),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.LINEAR),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE),e.bindTexture(e.TEXTURE_2D,null),e.uniform1i(e.getUniformLocation(a,t),i),o}e.enableVertexAttribArray(c),e.vertexAttribPointer(c,2,e.FLOAT,!1,0,0);var u=l("ySampler",0),h=l("uSampler",1),p=l("vSampler",2);return{render:function(t,i,o,r,s){e.viewport(0,0,t,i),e.activeTexture(e.TEXTURE0),e.bindTexture(e.TEXTURE_2D,u),e.texImage2D(e.TEXTURE_2D,0,e.LUMINANCE,t,i,0,e.LUMINANCE,e.UNSIGNED_BYTE,o),e.activeTexture(e.TEXTURE1),e.bindTexture(e.TEXTURE_2D,h),e.texImage2D(e.TEXTURE_2D,0,e.LUMINANCE,t/2,i/2,0,e.LUMINANCE,e.UNSIGNED_BYTE,r),e.activeTexture(e.TEXTURE2),e.bindTexture(e.TEXTURE_2D,p),e.texImage2D(e.TEXTURE_2D,0,e.LUMINANCE,t/2,i/2,0,e.LUMINANCE,e.UNSIGNED_BYTE,s),e.drawArrays(e.TRIANGLE_STRIP,0,4)},destroy:function(){try{e.deleteProgram(a),e.deleteBuffer(n),e.deleteBuffer(d),e.deleteTexture(u),e.deleteTexture(h),e.deleteTexture(p)}catch(e){}}}})(this.contextGl,this.player._opt.openWebglAlignment);this.contextGlRender=e.render,this.contextGlDestroy=e.destroy}else this.player.debug.error("CanvasVideoLoader","init webgl fail"),this.player.emitError(O.webglInitError)}_initContext2D(){this.context2D=this.$videoElement.getContext("2d")}_initCanvasRender(){this.player._opt.useWCS&&!this._supportOffscreen()?(this.renderType=q,this._initContext2D()):this._supportOffscreen()?(this.renderType=K,this._bindOffscreen()):(this.renderType=Z,this._initContextGl())}_supportOffscreen(){return"function"==typeof this.$videoElement.transferControlToOffscreen&&this.player._opt.useOffscreen}_bindOffscreen(){this.bitmaprenderer=this.$videoElement.getContext("bitmaprenderer")}initCanvasViewSize(){this.$videoElement.width=this.videoInfo.width,this.$videoElement.height=this.videoInfo.height,this.resize()}render(e){switch(this.player.videoTimestamp=e.ts,this.renderType){case K:this.bitmaprenderer.transferFromImageBitmap(e.buffer);break;case Z:if(this.isContextGlRenderLost)return;try{this.contextGlRender(this.$videoElement.width,this.$videoElement.height,e.output[0],e.output[1],e.output[2])}catch(e){this.player.debug.error("CanvasVideoLoader","webgl render error and emit webglContextLostError",e),this.isContextGlRenderLost=!0,this.player.emitError(O.webglContextLostError)}break;case q:this.context2D.drawImage(e.videoFrame,0,0,this.$videoElement.width,this.$videoElement.height),(t=e.videoFrame).close?t.close():t.destroy&&t.destroy()}var t}screenshot(e,t,i,o){e=e||Se(),o=o||J.download;const r={png:"image/png",jpeg:"image/jpeg",webp:"image/webp"};let s=.92;!r[t]&&J[t]&&(o=t,t="png",i=void 0),"string"==typeof i&&(o=i,i=void 0),void 0!==i&&(s=Number(i));const a=this.$videoElement.toDataURL(r[t]||r.png,s);if(o===J.base64)return a;{const t=we(a);if(o===J.blob)return t;o===J.download&&He(t,e)}}clearView(){switch(this.renderType){case K:(function(e,t){const i=document.createElement("canvas");return i.width=e,i.height=t,window.createImageBitmap(i,0,0,e,t)})(this.$videoElement.width,this.$videoElement.height).then((e=>{this.bitmaprenderer.transferFromImageBitmap(e)}));break;case Z:this.contextGl.clear(this.contextGl.COLOR_BUFFER_BIT);break;case q:this.context2D.clearRect(0,0,this.$videoElement.width,this.$videoElement.height)}}resize(){this.player.debug.log("canvasVideo","resize");const e=this.player._opt;let t=this.player.width,i=this.player.height;this.player.isControlBarShow()&&(Te()&&this.player.fullscreen&&e.useWebFullScreen?t-=H:i-=H);let o=this.$videoElement.width,r=this.$videoElement.height;const s=e.rotate;let a=(t-o)/2,n=(i-r)/2;270!==s&&90!==s||(o=this.$videoElement.height,r=this.$videoElement.width);const A=t/o,d=i/r;let c=A>d?d:A;e.isResize||A!==d&&(c=A+","+d),e.isFullResize&&(c=A>d?A:d);let l="scale("+c+")";s&&(l+=" rotate("+s+"deg)"),this.$videoElement.style.transform=l,this.$videoElement.style.left=a+"px",this.$videoElement.style.top=n+"px"}}class Ye extends Qe{constructor(e){super(),this.player=e;const t=document.createElement("video"),i=document.createElement("canvas");t.muted=!0,t.disablePictureInPicture=!0,function(){const e=window.navigator.userAgent.toLowerCase();return/android/i.test(e)}()&&(t.poster="noposter"),t.style.position="absolute",t.style.top=0,t.style.left=0,this._delayPlay=!1,e.$container.appendChild(t),this.videoInfo={width:"",height:"",encType:""};const o=this.player._opt;o.useWCS&&o.wcsUseVideoRender&&(this.trackGenerator=new MediaStreamTrackGenerator({kind:"video"}),t.srcObject=new MediaStream([this.trackGenerator]),this.vwriter=this.trackGenerator.writable.getWriter()),this.$videoElement=t,this.$canvasElement=i,this.canvasContext=i.getContext("2d"),this.fixChromeVideoFlashBug(),this.resize();const{proxy:r}=this.player.events;r(this.$videoElement,"canplay",(()=>{this.player.debug.log("Video","canplay"),this._delayPlay&&(this.player.debug.log("Video","canplay and _delayPlay is true and next play()"),this._play())})),r(this.$videoElement,"waiting",(()=>{this.player.debug.log("Video","waiting")})),r(this.$videoElement,"timeupdate",(e=>{const t=parseInt(e.timeStamp,10);this.player.emit(F.timeUpdate,t),!this.isPlaying()&&this.init&&(this.player.debug.log("Video","timeupdate and this.isPlaying is false and retry play"),this.$videoElement.play())})),this.player.debug.log("Video","init")}async destroy(){super.destroy(),this.$canvasElement=null,this.canvasContext=null,this.$videoElement&&(this.$videoElement.pause(),this.$videoElement.currentTime=0,this.$videoElement.src="",this.$videoElement.removeAttribute("src"),this.$videoElement=null),this.trackGenerator&&(this.trackGenerator.stop(),this.trackGenerator=null),this.vwriter&&(await this.vwriter.close(),this.vwriter=null),this.player.debug.log("Video","destroy")}fixChromeVideoFlashBug(){const e=function(){const e=navigator.userAgent.toLowerCase(),t={type:"unknown",version:"unknown"},i={IE:window.ActiveXObject||"ActiveXObject"in window,Chrome:e.indexOf("chrome")>-1&&e.indexOf("safari")>-1,Firefox:e.indexOf("firefox")>-1,Opera:e.indexOf("opera")>-1,Safari:e.indexOf("safari")>-1&&-1==e.indexOf("chrome"),Edge:e.indexOf("edge")>-1,QQBrowser:/qqbrowser/.test(e),WeixinBrowser:/MicroMessenger/i.test(e)};for(let o in i)if(i[o]){let i="";if("IE"===o)i=e.match(/(msie\s|trident.*rv:)([\w.]+)/)[2];else if("Chrome"===o){for(let e in navigator.mimeTypes)"application/360softmgrplugin"===navigator.mimeTypes[e].type&&(o="360");i=e.match(/chrome\/([\d.]+)/)[1]}else"Firefox"===o?i=e.match(/firefox\/([\d.]+)/)[1]:"Opera"===o?i=e.match(/opera\/([\d.]+)/)[1]:"Safari"===o?i=e.match(/version\/([\d.]+)/)[1]:"Edge"===o?i=e.match(/edge\/([\d.]+)/)[1]:"QQBrowser"===o&&(i=e.match(/qqbrowser\/([\d.]+)/)[1]);t.type=o,t.version=parseInt(i)}return t}().type.toLowerCase();if("chrome"===e||"edge"===e){const e=this.player.$container;e.style.backdropFilter="blur(0px)",e.style.translateZ="0"}}play(){if(this.$videoElement){const e=this._getVideoReadyState();if(this.player.debug.log("Video",`play and readyState: ${e}`),0===e)return this.player.debug.warn("Video","readyState is 0 and set _delayPlay to true"),void(this._delayPlay=!0);this._play()}}_getVideoReadyState(){let e=0;return this.$videoElement&&(e=this.$videoElement.readyState),e}_play(){this.$videoElement&&this.$videoElement.play().then((()=>{this._delayPlay=!1,this.player.debug.log("Video","_play success"),setTimeout((()=>{this.isPlaying()||(this.player.debug.warn("Video","play failed and retry play"),this._play())}),100)})).catch((e=>{this.player.debug.error("Video","_play error",e)}))}pause(e){e?this.$videoElement&&this.$videoElement.pause():setTimeout((()=>{this.$videoElement&&this.$videoElement.pause()}),100)}clearView(){}screenshot(e,t,i,o){e=e||Se(),o=o||J.download;const r={png:"image/png",jpeg:"image/jpeg",webp:"image/webp"};let s=.92;!r[t]&&J[t]&&(o=t,t="png",i=void 0),"string"==typeof i&&(o=i,i=void 0),void 0!==i&&(s=Number(i));const a=this.$videoElement;let n=this.$canvasElement;n.width=a.videoWidth,n.height=a.videoHeight,this.canvasContext.drawImage(a,0,0,n.width,n.height);const A=n.toDataURL(r[t]||r.png,s);if(this.canvasContext.clearRect(0,0,n.width,n.height),n.width=0,n.height=0,o===J.base64)return A;{const t=we(A);if(o===J.blob)return t;o===J.download&&He(t,e)}}initCanvasViewSize(){this.resize()}render(e){this.vwriter&&(this.vwriter.write(e.videoFrame),e.videoFrame.close())}resize(){let e=this.player.width,t=this.player.height;const i=this.player._opt,o=i.rotate;this.player.isControlBarShow()&&(Te()&&this.player.fullscreen&&i.useWebFullScreen?e-=H:t-=H),this.$videoElement.width=e,this.$videoElement.height=t,270!==o&&90!==o||(this.$videoElement.width=t,this.$videoElement.height=e);let r=(e-this.$videoElement.width)/2,s=(t-this.$videoElement.height)/2,a="contain";i.isResize||(a="fill"),i.isFullResize&&(a="none"),this.$videoElement.style.objectFit=a,this.$videoElement.style.transform="rotate("+o+"deg)",this.$videoElement.style.left=r+"px",this.$videoElement.style.top=s+"px"}isPlaying(){return this.$videoElement&&!this.$videoElement.paused}}class Xe{constructor(e){return new(Xe.getLoaderFactory(e._opt))(e)}static getLoaderFactory(e){return e.useMSE||e.useWCS&&!e.useOffscreen&&e.wcsUseVideoRender?Ye:ze}}class qe extends Ue{constructor(e){super(),this.bufferList=[],this.player=e,this.scriptNode=null,this.hasInitScriptNode=!1,this.audioContextChannel=null,this.audioContext=new(window.AudioContext||window.webkitAudioContext),this.gainNode=this.audioContext.createGain();const t=this.audioContext.createBufferSource();t.buffer=this.audioContext.createBuffer(1,1,22050),t.connect(this.audioContext.destination),t.noteOn?t.noteOn(0):t.start(0),this.audioBufferSourceNode=t,this.mediaStreamAudioDestinationNode=this.audioContext.createMediaStreamDestination(),this.audioEnabled(!0),this.gainNode.gain.value=0,this._prevVolume=null,this.playing=!1,this.audioSyncVideoOption={diff:null},this.audioInfo={encType:"",channels:"",sampleRate:""},this.init=!1,this.hasAudio=!1,this.on(F.videoSyncAudio,(e=>{this.audioSyncVideoOption=e})),this.player.debug.log("AudioContext","init")}resetInit(){this.init=!1,this.audioInfo={encType:"",channels:"",sampleRate:""}}async destroy(){this.closeAudio(),this.resetInit(),this.audioContext&&(await this.audioContext.close(),this.audioContext=null),this.gainNode=null,this.hasAudio=!1,this.playing=!1,this.scriptNode&&(this.scriptNode.onaudioprocess=ve,this.scriptNode=null),this.audioBufferSourceNode=null,this.mediaStreamAudioDestinationNode=null,this.hasInitScriptNode=!1,this.audioSyncVideoOption={diff:null},this._prevVolume=null,this.off(),this.player.debug.log("AudioContext","destroy")}updateAudioInfo(e){e.encTypeCode&&(this.audioInfo.encType=N[e.encTypeCode],this.audioInfo.encTypeCode=e.encTypeCode),e.channels&&(this.audioInfo.channels=e.channels),e.sampleRate&&(this.audioInfo.sampleRate=e.sampleRate),this.audioInfo.sampleRate&&this.audioInfo.channels&&this.audioInfo.encType&&!this.init&&(this.player.emit(F.audioInfo,this.audioInfo),this.init=!0)}get isPlaying(){return this.playing}get isMute(){return 0===this.gainNode.gain.value}get volume(){return this.gainNode.gain.value}get bufferSize(){return this.bufferList.length}initScriptNode(){if(this.playing=!0,this.hasInitScriptNode)return;const e=this.audioInfo.channels,t=this.audioContext.createScriptProcessor(1024,0,e);t.onaudioprocess=t=>{const i=t.outputBuffer;if(this.bufferList.length&&this.playing){if(!this.player._opt.useWCS&&!this.player._opt.useMSE&&this.player._opt.wasmDecodeAudioSyncVideo){if(this.audioSyncVideoOption.diff>re)return void this.player.debug.warn("AudioContext",`audioSyncVideoOption more than diff :${this.audioSyncVideoOption.diff}, waiting`);if(this.audioSyncVideoOption.diff<-1e3){this.player.debug.warn("AudioContext",`audioSyncVideoOption less than diff :${this.audioSyncVideoOption.diff}, dropping`);let e=this.bufferList.shift();for(;e.ts-this.player.videoTimestamp<-1e3&&this.bufferList.length>0;)e=this.bufferList.shift();if(0===this.bufferList.length)return}}if(0===this.bufferList.length)return;const t=this.bufferList.shift();t&&t.ts&&(this.player.audioTimestamp=t.ts);for(let o=0;o0?this.player.emit(F.mute,!1):this._prevVolume>0&&0===e&&this.player.emit(F.mute,!0),this.gainNode.gain.value=e,this.gainNode.gain.setValueAtTime(e,this.audioContext.currentTime),this.player.emit(F.volumechange,this.player.volume),this.player.emit(F.volume,this.player.volume),this._prevVolume=e)}closeAudio(){this.hasInitScriptNode&&(this.scriptNode&&this.scriptNode.disconnect(this.gainNode),this.gainNode&&this.gainNode.disconnect(this.audioContext.destination),this.gainNode&&this.gainNode.disconnect(this.mediaStreamAudioDestinationNode)),this.clear()}audioEnabled(e){e?"suspended"===this.audioContext.state&&this.audioContext.resume():"running"===this.audioContext.state&&this.audioContext.suspend()}isStateRunning(){return"running"===this.audioContext.state}isStateSuspended(){return"suspended"===this.audioContext.state}clear(){this.bufferList=[]}play(e,t){this.isMute||(this.hasAudio=!0,this.bufferList.push({buffer:e,ts:t}),this.bufferList.length>20&&(this.player.debug.warn("AudioContext",`bufferList is large: ${this.bufferList.length}`),this.bufferList.length>50&&this.bufferList.shift()))}pause(){this.audioSyncVideoOption={diff:null},this.playing=!1,this.clear()}resume(){this.playing=!0}getLastVolume(){return this._prevVolume}}class Ze{constructor(e){return new(Ze.getLoaderFactory())(e)}static getLoaderFactory(){return qe}}class Ke extends Ue{constructor(e){super(),this.player=e,this.playing=!1,this.abortController=new AbortController,this.streamRate=ke((t=>{e.emit(F.kBps,(t/1e3).toFixed(2))})),e.debug.log("FetchStream","init")}async destroy(){this.abort(),this.off(),this.streamRate=null,this.player.debug.log("FetchStream","destroy")}fetchStream(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const{demux:i}=this.player;this.player.debug.log("FetchStream","fetchStream",e,JSON.stringify(t)),this.player._times.streamStart=Se(),this.abortController||(this.abortController=new AbortController);const o=Object.assign({signal:this.abortController.signal},{headers:t.headers||{}});fetch(e,o).then((e=>{if(Ve(function(e){return e.ok&&e.status>=200&&e.status<=299}(e)))return this.player.debug.log("FetchStream",`fetch response status is ${e.status} and ok is ${e.ok} and emit error and next abort()`),this.abort(),this.emit(O.fetchError,`fetch response status is ${e.status} and ok is ${e.ok}`),void this.player.emit(F.error,O.fetchError);const t=e.body.getReader();this.emit(F.streamSuccess);const o=()=>{t.read().then((e=>{let{done:t,value:r}=e;t?i.close():(this.streamRate&&this.streamRate(8*r.byteLength),i.dispatch(r),o())})).catch((e=>{i.close();const t=e.toString();-1===t.indexOf(ce)&&-1===t.indexOf(le)&&e.name!==ue&&(this.abort(),this.emit(O.fetchError,e),this.player.emit(F.error,O.fetchError))}))};o()})).catch((e=>{"AbortError"!==e.name&&(i.close(),this.abort(),this.emit(O.fetchError,e),this.player.emit(F.error,O.fetchError))}))}abort(){this.abortController&&(this.abortController.abort(),this.abortController=null)}}class _e extends Ue{constructor(e){super(),this.player=e,this.socket=null,this.socketStatus=V,this.wsUrl=null,this.streamRate=ke((t=>{e.emit(F.kBps,(t/1e3).toFixed(2))})),e.debug.log("WebsocketLoader","init")}async destroy(){this.socket&&(this.socket.close(1e3,"Client disconnecting"),this.socket=null),this.socketStatus=V,this.streamRate=null,this.wsUrl=null,this.off(),this.player.debug.log("websocketLoader","destroy")}_createWebSocket(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};const t=this.player,{debug:i,events:{proxy:o},demux:r}=t,s=e.protocols||[];this.socket=new WebSocket(this.wsUrl,s),this.socket.binaryType="arraybuffer",o(this.socket,"open",(()=>{this.emit(F.streamSuccess),i.log("websocketLoader","socket open"),this.socketStatus=U})),o(this.socket,"message",(e=>{this.streamRate&&this.streamRate(8*e.data.byteLength),this._handleMessage(e.data)})),o(this.socket,"close",(()=>{i.log("websocketLoader","socket close"),this.emit(F.streamEnd),this.socketStatus=Q})),o(this.socket,"error",(e=>{i.log("websocketLoader","socket error"),this.emit(O.websocketError,e),this.player.emit(F.error,O.websocketError),this.socketStatus=W,r.close(),i.log("websocketLoader","socket error:",e)}))}_handleMessage(e){const{demux:t}=this.player;t?t.dispatch(e):this.player.debug.warn("websocketLoader","websocket handle message demux is null")}fetchStream(e,t){this.player._times.streamStart=Se(),this.wsUrl=e,this._createWebSocket(t)}}class $e{constructor(e){return new($e.getLoaderFactory(e._opt.protocol))(e)}static getLoaderFactory(e){return e===A?Ke:e===n?_e:void 0}}var et=i((function(t){function i(e,t){if(!e)throw"First parameter is required.";t=new o(e,t=t||{type:"video"});var s=this;function a(i){i&&(t.initCallback=function(){i(),i=t.initCallback=null});var o=new r(e,t);(h=new o(e,t)).record(),u("recording"),t.disableLogs||console.log("Initialized recorderType:",h.constructor.name,"for output-type:",t.type)}function n(e){if(e=e||function(){},h){if("paused"===s.state)return s.resumeRecording(),void setTimeout((function(){n(e)}),1);"recording"===s.state||t.disableLogs||console.warn('Recording state should be: "recording", however current state is: ',s.state),t.disableLogs||console.log("Stopped recording "+t.type+" stream."),"gif"!==t.type?h.stop(i):(h.stop(),i()),u("stopped")}else g();function i(i){if(h){Object.keys(h).forEach((function(e){"function"!=typeof h[e]&&(s[e]=h[e])}));var o=h.blob;if(!o){if(!i)throw"Recording failed.";h.blob=o=i}if(o&&!t.disableLogs&&console.log(o.type,"->",b(o.size)),e){var r;try{r=l.createObjectURL(o)}catch(e){}"function"==typeof e.call?e.call(s,r):e(r)}t.autoWriteToDisk&&d((function(e){var i={};i[t.type+"Blob"]=e,x.Store(i)}))}else"function"==typeof e.call?e.call(s,""):e("")}}function A(e){postMessage((new FileReaderSync).readAsDataURL(e))}function d(e,i){if(!e)throw"Pass a callback function over getDataURL.";var o=i?i.blob:(h||{}).blob;if(!o)return t.disableLogs||console.warn("Blob encoder did not finish its job yet."),void setTimeout((function(){d(e,i)}),1e3);if("undefined"==typeof Worker||navigator.mozGetUserMedia){var r=new FileReader;r.readAsDataURL(o),r.onload=function(t){e(t.target.result)}}else{var s=function(e){try{var t=l.createObjectURL(new Blob([e.toString(),"this.onmessage = function (eee) {"+e.name+"(eee.data);}"],{type:"application/javascript"})),i=new Worker(t);return l.revokeObjectURL(t),i}catch(e){}}(A);s.onmessage=function(t){e(t.data)},s.postMessage(o)}}function c(e){e=e||0,"paused"!==s.state?"stopped"!==s.state&&(e>=s.recordingDuration?n(s.onRecordingStopped):(e+=1e3,setTimeout((function(){c(e)}),1e3))):setTimeout((function(){c(e)}),1e3)}function u(e){s&&(s.state=e,"function"==typeof s.onStateChanged.call?s.onStateChanged.call(s,e):s.onStateChanged(e))}var h,p='It seems that recorder is destroyed or "startRecording" is not invoked for '+t.type+" recorder.";function g(){!0!==t.disableLogs&&console.warn(p)}var m={startRecording:function(i){return t.disableLogs||console.log("RecordRTC version: ",s.version),i&&(t=new o(e,i)),t.disableLogs||console.log("started recording "+t.type+" stream."),h?(h.clearRecordedData(),h.record(),u("recording"),s.recordingDuration&&c(),s):(a((function(){s.recordingDuration&&c()})),s)},stopRecording:n,pauseRecording:function(){h?"recording"===s.state?(u("paused"),h.pause(),t.disableLogs||console.log("Paused recording.")):t.disableLogs||console.warn("Unable to pause the recording. Recording state: ",s.state):g()},resumeRecording:function(){h?"paused"===s.state?(u("recording"),h.resume(),t.disableLogs||console.log("Resumed recording.")):t.disableLogs||console.warn("Unable to resume the recording. Recording state: ",s.state):g()},initRecorder:a,setRecordingDuration:function(e,t){if(void 0===e)throw"recordingDuration is required.";if("number"!=typeof e)throw"recordingDuration must be a number.";return s.recordingDuration=e,s.onRecordingStopped=t||function(){},{onRecordingStopped:function(e){s.onRecordingStopped=e}}},clearRecordedData:function(){h?(h.clearRecordedData(),t.disableLogs||console.log("Cleared old recorded data.")):g()},getBlob:function(){if(h)return h.blob;g()},getDataURL:d,toURL:function(){if(h)return l.createObjectURL(h.blob);g()},getInternalRecorder:function(){return h},save:function(e){h?y(h.blob,e):g()},getFromDisk:function(e){h?i.getFromDisk(t.type,e):g()},setAdvertisementArray:function(e){t.advertisement=[];for(var i=e.length,o=0;o-1&&"netscape"in window&&/ rv:/.test(navigator.userAgent),g=!h&&!u&&!!navigator.webkitGetUserMedia||v()||-1!==navigator.userAgent.toLowerCase().indexOf("chrome/"),m=/^((?!chrome|android).)*safari/i.test(navigator.userAgent);m&&!g&&-1!==navigator.userAgent.indexOf("CriOS")&&(m=!1,g=!0);var f=window.MediaStream;function b(e){if(0===e)return"0 Bytes";var t=parseInt(Math.floor(Math.log(e)/Math.log(1e3)),10);return(e/Math.pow(1e3,t)).toPrecision(3)+" "+["Bytes","KB","MB","GB","TB"][t]}function y(e,t){if(!e)throw"Blob object is required.";if(!e.type)try{e.type="video/webm"}catch(e){}var i=(e.type||"video/webm").split("/")[1];if(-1!==i.indexOf(";")&&(i=i.split(";")[0]),t&&-1!==t.indexOf(".")){var o=t.split(".");t=o[0],i=o[1]}var r=(t||Math.round(9999999999*Math.random())+888888888)+"."+i;if(void 0!==navigator.msSaveOrOpenBlob)return navigator.msSaveOrOpenBlob(e,r);if(void 0!==navigator.msSaveBlob)return navigator.msSaveBlob(e,r);var s=document.createElement("a");s.href=l.createObjectURL(e),s.download=r,s.style="display:none;opacity:0;color:transparent;",(document.body||document.documentElement).appendChild(s),"function"==typeof s.click?s.click():(s.target="_blank",s.dispatchEvent(new MouseEvent("click",{view:window,bubbles:!0,cancelable:!0}))),l.revokeObjectURL(s.href)}function v(){return"undefined"!=typeof window&&"object"==typeof window.process&&"renderer"===window.process.type||(!("undefined"==typeof process||"object"!=typeof process.versions||!process.versions.electron)||"object"==typeof navigator&&"string"==typeof navigator.userAgent&&navigator.userAgent.indexOf("Electron")>=0)}function w(e,t){return e&&e.getTracks?e.getTracks().filter((function(e){return e.kind===(t||"audio")})):[]}function S(e,t){"srcObject"in t?t.srcObject=e:"mozSrcObject"in t?t.mozSrcObject=e:t.srcObject=e}void 0===f&&"undefined"!=typeof webkitMediaStream&&(f=webkitMediaStream),void 0!==f&&void 0===f.prototype.stop&&(f.prototype.stop=function(){this.getTracks().forEach((function(e){e.stop()}))}),void 0!==i&&(i.invokeSaveAsDialog=y,i.getTracks=w,i.getSeekableBlob=function(e,t){if("undefined"==typeof EBML)throw new Error("Please link: https://www.webrtc-experiment.com/EBML.js");var i=new EBML.Reader,o=new EBML.Decoder,r=EBML.tools,s=new FileReader;s.onload=function(e){o.decode(this.result).forEach((function(e){i.read(e)})),i.stop();var s=r.makeMetadataSeekable(i.metadatas,i.duration,i.cues),a=this.result.slice(i.metadataSize),n=new Blob([s,a],{type:"video/webm"});t(n)},s.readAsArrayBuffer(e)},i.bytesToSize=b,i.isElectron=v);var E={};function B(){if(p||m||u)return!0;var e,t,i=navigator.userAgent,o=""+parseFloat(navigator.appVersion),r=parseInt(navigator.appVersion,10);return(g||h)&&(e=i.indexOf("Chrome"),o=i.substring(e+7)),-1!==(t=o.indexOf(";"))&&(o=o.substring(0,t)),-1!==(t=o.indexOf(" "))&&(o=o.substring(0,t)),r=parseInt(""+o,10),isNaN(r)&&(o=""+parseFloat(navigator.appVersion),r=parseInt(navigator.appVersion,10)),r>=49}function C(e,t){var i=this;if(void 0===e)throw'First argument "MediaStream" is required.';if("undefined"==typeof MediaRecorder)throw"Your browser does not support the Media Recorder API. Please try other modules e.g. WhammyRecorder or StereoAudioRecorder.";if("audio"===(t=t||{mimeType:"video/webm"}).type){var o;if(w(e,"video").length&&w(e,"audio").length)navigator.mozGetUserMedia?(o=new f).addTrack(w(e,"audio")[0]):o=new f(w(e,"audio")),e=o;t.mimeType&&-1!==t.mimeType.toString().toLowerCase().indexOf("audio")||(t.mimeType=g?"audio/webm":"audio/ogg"),t.mimeType&&"audio/ogg"!==t.mimeType.toString().toLowerCase()&&navigator.mozGetUserMedia&&(t.mimeType="audio/ogg")}var r,s=[];function a(){i.timestamps.push((new Date).getTime()),"function"==typeof t.onTimeStamp&&t.onTimeStamp(i.timestamps[i.timestamps.length-1],i.timestamps)}function n(e){return r&&r.mimeType?r.mimeType:e.mimeType||"video/webm"}function A(){s=[],r=null,i.timestamps=[]}this.getArrayOfBlobs=function(){return s},this.record=function(){i.blob=null,i.clearRecordedData(),i.timestamps=[],d=[],s=[];var o=t;t.disableLogs||console.log("Passing following config over MediaRecorder API.",o),r&&(r=null),g&&!B()&&(o="video/vp8"),"function"==typeof MediaRecorder.isTypeSupported&&o.mimeType&&(MediaRecorder.isTypeSupported(o.mimeType)||(t.disableLogs||console.warn("MediaRecorder API seems unable to record mimeType:",o.mimeType),o.mimeType="audio"===t.type?"audio/webm":"video/webm"));try{r=new MediaRecorder(e,o),t.mimeType=o.mimeType}catch(t){r=new MediaRecorder(e)}o.mimeType&&!MediaRecorder.isTypeSupported&&"canRecordMimeType"in r&&!1===r.canRecordMimeType(o.mimeType)&&(t.disableLogs||console.warn("MediaRecorder API seems unable to record mimeType:",o.mimeType)),r.ondataavailable=function(e){if(e.data&&d.push("ondataavailable: "+b(e.data.size)),"number"!=typeof t.timeSlice)!e.data||!e.data.size||e.data.size<100||i.blob?i.recordingCallback&&(i.recordingCallback(new Blob([],{type:n(o)})),i.recordingCallback=null):(i.blob=t.getNativeBlob?e.data:new Blob([e.data],{type:n(o)}),i.recordingCallback&&(i.recordingCallback(i.blob),i.recordingCallback=null));else if(e.data&&e.data.size&&(s.push(e.data),a(),"function"==typeof t.ondataavailable)){var r=t.getNativeBlob?e.data:new Blob([e.data],{type:n(o)});t.ondataavailable(r)}},r.onstart=function(){d.push("started")},r.onpause=function(){d.push("paused")},r.onresume=function(){d.push("resumed")},r.onstop=function(){d.push("stopped")},r.onerror=function(e){e&&(e.name||(e.name="UnknownError"),d.push("error: "+e),t.disableLogs||(-1!==e.name.toString().toLowerCase().indexOf("invalidstate")?console.error("The MediaRecorder is not in a state in which the proposed operation is allowed to be executed.",e):-1!==e.name.toString().toLowerCase().indexOf("notsupported")?console.error("MIME type (",o.mimeType,") is not supported.",e):-1!==e.name.toString().toLowerCase().indexOf("security")?console.error("MediaRecorder security error",e):"OutOfMemory"===e.name?console.error("The UA has exhaused the available memory. User agents SHOULD provide as much additional information as possible in the message attribute.",e):"IllegalStreamModification"===e.name?console.error("A modification to the stream has occurred that makes it impossible to continue recording. An example would be the addition of a Track while recording is occurring. User agents SHOULD provide as much additional information as possible in the message attribute.",e):"OtherRecordingError"===e.name?console.error("Used for an fatal error other than those listed above. User agents SHOULD provide as much additional information as possible in the message attribute.",e):"GenericError"===e.name?console.error("The UA cannot provide the codec or recording option that has been requested.",e):console.error("MediaRecorder Error",e)),function(){if(!i.manuallyStopped&&r&&"inactive"===r.state)return delete t.timeslice,void r.start(6e5);setTimeout(void 0,1e3)}(),"inactive"!==r.state&&"stopped"!==r.state&&r.stop())},"number"==typeof t.timeSlice?(a(),r.start(t.timeSlice)):r.start(36e5),t.initCallback&&t.initCallback()},this.timestamps=[],this.stop=function(e){e=e||function(){},i.manuallyStopped=!0,r&&(this.recordingCallback=e,"recording"===r.state&&r.stop(),"number"==typeof t.timeSlice&&setTimeout((function(){i.blob=new Blob(s,{type:n(t)}),i.recordingCallback(i.blob)}),100))},this.pause=function(){r&&"recording"===r.state&&r.pause()},this.resume=function(){r&&"paused"===r.state&&r.resume()},this.clearRecordedData=function(){r&&"recording"===r.state&&i.stop(A),A()},this.getInternalRecorder=function(){return r},this.blob=null,this.getState=function(){return r&&r.state||"inactive"};var d=[];this.getAllStates=function(){return d},void 0===t.checkForInactiveTracks&&(t.checkForInactiveTracks=!1);i=this;!function o(){if(r&&!1!==t.checkForInactiveTracks)return!1===function(){if("active"in e){if(!e.active)return!1}else if("ended"in e&&e.ended)return!1;return!0}()?(t.disableLogs||console.log("MediaStream seems stopped."),void i.stop()):void setTimeout(o,1e3)}(),this.name="MediaStreamRecorder",this.toString=function(){return this.name}}function R(e,t){if(!w(e,"audio").length)throw"Your stream has no audio tracks.";var o,r=this,s=[],a=[],n=!1,A=0,d=2,c=(t=t||{}).desiredSampRate;function u(){if(!1===t.checkForInactiveTracks)return!0;if("active"in e){if(!e.active)return!1}else if("ended"in e&&e.ended)return!1;return!0}function h(e,t){function i(e,t){var i,o=e.numberOfAudioChannels,r=e.leftBuffers.slice(0),s=e.rightBuffers.slice(0),a=e.sampleRate,n=e.internalInterleavedLength,A=e.desiredSampRate;function d(e,t,i){var o=Math.round(e.length*(t/i)),r=[],s=Number((e.length-1)/(o-1));r[0]=e[0];for(var a=1;a96e3)&&(t.disableLogs||console.log("sample-rate must be under range 22050 and 96000.")),t.disableLogs||t.desiredSampRate&&console.log("Desired sample-rate: "+t.desiredSampRate);var y=!1;function v(){s=[],a=[],A=0,E=!1,n=!1,y=!1,p=null,r.leftchannel=s,r.rightchannel=a,r.numberOfAudioChannels=d,r.desiredSampRate=c,r.sampleRate=b,r.recordingLength=A,B={left:[],right:[],recordingLength:0}}function S(){o&&(o.onaudioprocess=null,o.disconnect(),o=null),g&&(g.disconnect(),g=null),v()}this.pause=function(){y=!0},this.resume=function(){if(!1===u())throw"Please make sure MediaStream is active.";if(!n)return t.disableLogs||console.log("Seems recording has been restarted."),void this.record();y=!1},this.clearRecordedData=function(){t.checkForInactiveTracks=!1,n&&this.stop(S),S()},this.name="StereoAudioRecorder",this.toString=function(){return this.name};var E=!1;o.onaudioprocess=function(e){if(!y)if(!1===u()&&(t.disableLogs||console.log("MediaStream seems stopped."),o.disconnect(),n=!1),n){E||(E=!0,t.onAudioProcessStarted&&t.onAudioProcessStarted(),t.initCallback&&t.initCallback());var i=e.inputBuffer.getChannelData(0),c=new Float32Array(i);if(s.push(c),2===d){var l=e.inputBuffer.getChannelData(1),h=new Float32Array(l);a.push(h)}A+=f,r.recordingLength=A,void 0!==t.timeSlice&&(B.recordingLength+=f,B.left.push(c),2===d&&B.right.push(h))}else g&&(g.disconnect(),g=null)},p.createMediaStreamDestination?o.connect(p.createMediaStreamDestination()):o.connect(p.destination),this.leftchannel=s,this.rightchannel=a,this.numberOfAudioChannels=d,this.desiredSampRate=c,this.sampleRate=b,r.recordingLength=A;var B={left:[],right:[],recordingLength:0};function C(){n&&"function"==typeof t.ondataavailable&&void 0!==t.timeSlice&&(B.left.length?(h({desiredSampRate:c,sampleRate:b,numberOfAudioChannels:d,internalInterleavedLength:B.recordingLength,leftBuffers:B.left,rightBuffers:1===d?[]:B.right},(function(e,i){var o=new Blob([i],{type:"audio/wav"});t.ondataavailable(o),setTimeout(C,t.timeSlice)})),B={left:[],right:[],recordingLength:0}):setTimeout(C,t.timeSlice))}}function k(e,t){if("undefined"==typeof html2canvas)throw"Please link: https://www.webrtc-experiment.com/screenshot.js";(t=t||{}).frameInterval||(t.frameInterval=10);var i=!1;["captureStream","mozCaptureStream","webkitCaptureStream"].forEach((function(e){e in document.createElement("canvas")&&(i=!0)}));var o,r,s,a=!(!window.webkitRTCPeerConnection&&!window.webkitGetUserMedia||!window.chrome),n=50,A=navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);if(a&&A&&A[2]&&(n=parseInt(A[2],10)),a&&n<52&&(i=!1),t.useWhammyRecorder&&(i=!1),i)if(t.disableLogs||console.log("Your browser supports both MediRecorder API and canvas.captureStream!"),e instanceof HTMLCanvasElement)o=e;else{if(!(e instanceof CanvasRenderingContext2D))throw"Please pass either HTMLCanvasElement or CanvasRenderingContext2D.";o=e.canvas}else navigator.mozGetUserMedia&&(t.disableLogs||console.error("Canvas recording is NOT supported in Firefox."));this.record=function(){if(s=!0,i&&!t.useWhammyRecorder){var e;"captureStream"in o?e=o.captureStream(25):"mozCaptureStream"in o?e=o.mozCaptureStream(25):"webkitCaptureStream"in o&&(e=o.webkitCaptureStream(25));try{var a=new f;a.addTrack(w(e,"video")[0]),e=a}catch(e){}if(!e)throw"captureStream API are NOT available.";(r=new C(e,{mimeType:t.mimeType||"video/webm"})).record()}else h.frames=[],u=(new Date).getTime(),l();t.initCallback&&t.initCallback()},this.getWebPImages=function(i){if("canvas"===e.nodeName.toLowerCase()){var o=h.frames.length;h.frames.forEach((function(e,i){var r=o-i;t.disableLogs||console.log(r+"/"+o+" frames remaining"),t.onEncodingCallback&&t.onEncodingCallback(r,o);var s=e.image.toDataURL("image/webp",1);h.frames[i].image=s})),t.disableLogs||console.log("Generating WebM"),i()}else i()},this.stop=function(e){s=!1;var o=this;i&&r?r.stop(e):this.getWebPImages((function(){h.compile((function(i){t.disableLogs||console.log("Recording finished!"),o.blob=i,o.blob.forEach&&(o.blob=new Blob([],{type:"video/webm"})),e&&e(o.blob),h.frames=[]}))}))};var d=!1;function c(){h.frames=[],s=!1,d=!1}function l(){if(d)return u=(new Date).getTime(),setTimeout(l,500);if("canvas"===e.nodeName.toLowerCase()){var i=(new Date).getTime()-u;return u=(new Date).getTime(),h.frames.push({image:(o=document.createElement("canvas"),r=o.getContext("2d"),o.width=e.width,o.height=e.height,r.drawImage(e,0,0),o),duration:i}),void(s&&setTimeout(l,t.frameInterval))}var o,r;html2canvas(e,{grabMouse:void 0===t.showMousePointer||t.showMousePointer,onrendered:function(e){var i=(new Date).getTime()-u;if(!i)return setTimeout(l,t.frameInterval);u=(new Date).getTime(),h.frames.push({image:e.toDataURL("image/webp",1),duration:i}),s&&setTimeout(l,t.frameInterval)}})}this.pause=function(){d=!0,r instanceof C&&r.pause()},this.resume=function(){d=!1,r instanceof C?r.resume():s||this.record()},this.clearRecordedData=function(){s&&this.stop(c),c()},this.name="CanvasRecorder",this.toString=function(){return this.name};var u=(new Date).getTime(),h=new I.Video(100)}function T(e,t){function i(e){e=void 0!==e?e:10;var t=(new Date).getTime()-A;return t?s?(A=(new Date).getTime(),setTimeout(i,100)):(A=(new Date).getTime(),n.paused&&n.play(),l.drawImage(n,0,0,c.width,c.height),d.frames.push({duration:t,image:c.toDataURL("image/webp")}),void(r||setTimeout(i,e,e))):setTimeout(i,e,e)}function o(e,t,i,o,r){var s=document.createElement("canvas");s.width=c.width,s.height=c.height;var a,n,A,d=s.getContext("2d"),l=[],u=-1===t,h=t&&t>0&&t<=e.length?t:e.length,p=0,g=0,m=0,f=Math.sqrt(Math.pow(255,2)+Math.pow(255,2)+Math.pow(255,2)),b=i&&i>=0&&i<=1?i:0,y=o&&o>=0&&o<=1?o:0,v=!1;n=-1,A=(a={length:h,functionToLoop:function(t,i){var o,r,s,a=function(){!v&&s-o<=s*y||(u&&(v=!0),l.push(e[i])),t()};if(v)a();else{var n=new Image;n.onload=function(){d.drawImage(n,0,0,c.width,c.height);var e=d.getImageData(0,0,c.width,c.height);o=0,r=e.data.length,s=e.data.length/4;for(var t=0;t127)throw"TrackNumber > 127 not supported";return[128|e.trackNum,e.timecode>>8,255&e.timecode,t].map((function(e){return String.fromCharCode(e)})).join("")+e.frame}({discardable:0,frame:e.data.slice(4),invisible:0,keyframe:1,lacing:0,trackNum:1,timecode:Math.round(t)});return t+=e.duration,{data:i,id:163}})))}function i(e){for(var t=[];e>0;)t.push(255&e),e>>=8;return new Uint8Array(t.reverse())}function o(e){var t=[];e=(e.length%8?new Array(9-e.length%8).join("0"):"")+e;for(var i=0;i1?2*i[0].width:i[0].width;var o=1;3!==e&&4!==e||(o=2),5!==e&&6!==e||(o=3),7!==e&&8!==e||(o=4),9!==e&&10!==e||(o=5),a.height=i[0].height*o}else a.width=A.width||360,a.height=A.height||240;t&&t instanceof HTMLVideoElement&&p(t),i.forEach((function(e,t){p(e,t)})),setTimeout(h,A.frameInterval)}}function p(e,t){if(!s){var i=0,o=0,r=e.width,a=e.height;1===t&&(i=e.width),2===t&&(o=e.height),3===t&&(i=e.width,o=e.height),4===t&&(o=2*e.height),5===t&&(i=e.width,o=2*e.height),6===t&&(o=3*e.height),7===t&&(i=e.width,o=3*e.height),void 0!==e.stream.left&&(i=e.stream.left),void 0!==e.stream.top&&(o=e.stream.top),void 0!==e.stream.width&&(r=e.stream.width),void 0!==e.stream.height&&(a=e.stream.height),n.drawImage(e,i,o,r,a),"function"==typeof e.stream.onRender&&e.stream.onRender(n,i,o,r,a,t)}}function g(e){var t=document.createElement("video");return function(e,t){"srcObject"in t?t.srcObject=e:"mozSrcObject"in t?t.mozSrcObject=e:t.srcObject=e}(e,t),t.className=o,t.muted=!0,t.volume=0,t.width=e.width||A.width||360,t.height=e.height||A.height||240,t.play(),t}function m(e){r=[],(e=e||t).forEach((function(e){if(e.getTracks().filter((function(e){return"video"===e.kind})).length){var t=g(e);t.stream=e,r.push(t)}}))}void 0!==d?u.AudioContext=d:"undefined"!=typeof webkitAudioContext&&(u.AudioContext=webkitAudioContext),this.startDrawingFrames=function(){h()},this.appendStreams=function(e){if(!e)throw"First parameter is required.";e instanceof Array||(e=[e]),e.forEach((function(e){var i=new l;if(e.getTracks().filter((function(e){return"video"===e.kind})).length){var o=g(e);o.stream=e,r.push(o),i.addTrack(e.getTracks().filter((function(e){return"video"===e.kind}))[0])}if(e.getTracks().filter((function(e){return"audio"===e.kind})).length){var s=A.audioContext.createMediaStreamSource(e);A.audioDestination=A.audioContext.createMediaStreamDestination(),s.connect(A.audioDestination),i.addTrack(A.audioDestination.stream.getTracks().filter((function(e){return"audio"===e.kind}))[0])}t.push(i)}))},this.releaseStreams=function(){r=[],s=!0,A.gainNode&&(A.gainNode.disconnect(),A.gainNode=null),A.audioSources.length&&(A.audioSources.forEach((function(e){e.disconnect()})),A.audioSources=[]),A.audioDestination&&(A.audioDestination.disconnect(),A.audioDestination=null),A.audioContext&&A.audioContext.close(),A.audioContext=null,n.clearRect(0,0,a.width,a.height),a.stream&&(a.stream.stop(),a.stream=null)},this.resetVideoStreams=function(e){!e||e instanceof Array||(e=[e]),m(e)},this.name="MultiStreamsMixer",this.toString=function(){return this.name},this.getMixedStream=function(){s=!1;var e=function(){var e;m(),"captureStream"in a?e=a.captureStream():"mozCaptureStream"in a?e=a.mozCaptureStream():A.disableLogs||console.error("Upgrade to latest Chrome or otherwise enable this flag: chrome://flags/#enable-experimental-web-platform-features");var t=new l;return e.getTracks().filter((function(e){return"video"===e.kind})).forEach((function(e){t.addTrack(e)})),a.stream=t,t}(),i=function(){u.AudioContextConstructor||(u.AudioContextConstructor=new u.AudioContext);A.audioContext=u.AudioContextConstructor,A.audioSources=[],!0===A.useGainNode&&(A.gainNode=A.audioContext.createGain(),A.gainNode.connect(A.audioContext.destination),A.gainNode.gain.value=0);var e=0;if(t.forEach((function(t){if(t.getTracks().filter((function(e){return"audio"===e.kind})).length){e++;var i=A.audioContext.createMediaStreamSource(t);!0===A.useGainNode&&i.connect(A.gainNode),A.audioSources.push(i)}})),!e)return;return A.audioDestination=A.audioContext.createMediaStreamDestination(),A.audioSources.forEach((function(e){e.connect(A.audioDestination)})),A.audioDestination.stream}();return i&&i.getTracks().filter((function(e){return"audio"===e.kind})).forEach((function(t){e.addTrack(t)})),t.forEach((function(e){e.fullcanvas})),e}}function L(e,t){e=e||[];var i,o,r=this;(t=t||{elementClass:"multi-streams-mixer",mimeType:"video/webm",video:{width:360,height:240}}).frameInterval||(t.frameInterval=10),t.video||(t.video={}),t.video.width||(t.video.width=360),t.video.height||(t.video.height=240),this.record=function(){var r;i=new j(e,t.elementClass||"multi-streams-mixer"),(r=[],e.forEach((function(e){w(e,"video").forEach((function(e){r.push(e)}))})),r).length&&(i.frameInterval=t.frameInterval||10,i.width=t.video.width||360,i.height=t.video.height||240,i.startDrawingFrames()),t.previewStream&&"function"==typeof t.previewStream&&t.previewStream(i.getMixedStream()),(o=new C(i.getMixedStream(),t)).record()},this.stop=function(e){o&&o.stop((function(t){r.blob=t,e(t),r.clearRecordedData()}))},this.pause=function(){o&&o.pause()},this.resume=function(){o&&o.resume()},this.clearRecordedData=function(){o&&(o.clearRecordedData(),o=null),i&&(i.releaseStreams(),i=null)},this.addStreams=function(r){if(!r)throw"First parameter is required.";r instanceof Array||(r=[r]),e.concat(r),o&&i&&(i.appendStreams(r),t.previewStream&&"function"==typeof t.previewStream&&t.previewStream(i.getMixedStream()))},this.resetVideoStreams=function(e){i&&(!e||e instanceof Array||(e=[e]),i.resetVideoStreams(e))},this.getMixer=function(){return i},this.name="MultiStreamRecorder",this.toString=function(){return this.name}}function F(e,t){var i,o,r;function s(){return new ReadableStream({start:function(o){var r=document.createElement("canvas"),s=document.createElement("video"),a=!0;s.srcObject=e,s.muted=!0,s.height=t.height,s.width=t.width,s.volume=0,s.onplaying=function(){r.width=t.width,r.height=t.height;var e=r.getContext("2d"),n=1e3/t.frameRate,A=setInterval((function(){if(i&&(clearInterval(A),o.close()),a&&(a=!1,t.onVideoProcessStarted&&t.onVideoProcessStarted()),e.drawImage(s,0,0),"closed"!==o._controlledReadableStream.state)try{o.enqueue(e.getImageData(0,0,t.width,t.height))}catch(e){}}),n)},s.play()}})}function a(e,A){if(!t.workerPath&&!A)return i=!1,void fetch("https://unpkg.com/webm-wasm@latest/dist/webm-worker.js").then((function(t){t.arrayBuffer().then((function(t){a(e,t)}))}));if(!t.workerPath&&A instanceof ArrayBuffer){var d=new Blob([A],{type:"text/javascript"});t.workerPath=l.createObjectURL(d)}t.workerPath||console.error("workerPath parameter is missing."),(o=new Worker(t.workerPath)).postMessage(t.webAssemblyPath||"https://unpkg.com/webm-wasm@latest/dist/webm-wasm.wasm"),o.addEventListener("message",(function(e){"READY"===e.data?(o.postMessage({width:t.width,height:t.height,bitrate:t.bitrate||1200,timebaseDen:t.frameRate||30,realtime:t.realtime}),s().pipeTo(new WritableStream({write:function(e){i?console.error("Got image, but recorder is finished!"):o.postMessage(e.data.buffer,[e.data.buffer])}}))):e.data&&(r||n.push(e.data))}))}"undefined"!=typeof ReadableStream&&"undefined"!=typeof WritableStream||console.error("Following polyfill is strongly recommended: https://unpkg.com/@mattiasbuelens/web-streams-polyfill/dist/polyfill.min.js"),(t=t||{}).width=t.width||640,t.height=t.height||480,t.frameRate=t.frameRate||30,t.bitrate=t.bitrate||1200,t.realtime=t.realtime||!0,this.record=function(){n=[],r=!1,this.blob=null,a(e),"function"==typeof t.initCallback&&t.initCallback()},this.pause=function(){r=!0},this.resume=function(){r=!1};var n=[];this.stop=function(e){i=!0;var t=this;!function(e){o?(o.addEventListener("message",(function(t){null===t.data&&(o.terminate(),o=null,e&&e())})),o.postMessage(null)):e&&e()}((function(){t.blob=new Blob(n,{type:"video/webm"}),e(t.blob)}))},this.name="WebAssemblyRecorder",this.toString=function(){return this.name},this.clearRecordedData=function(){n=[],r=!1,this.blob=null},this.blob=null}void 0!==i&&(i.DiskStorage=x),void 0!==i&&(i.GifRecorder=D),void 0===i&&(t.exports=j),void 0!==i&&(i.MultiStreamRecorder=L),void 0!==i&&(i.RecordRTCPromisesHandler=function(e,t){if(!this)throw'Use "new RecordRTCPromisesHandler()"';if(void 0===e)throw'First argument "MediaStream" is required.';var o=this;o.recordRTC=new i(e,t),this.startRecording=function(){return new Promise((function(e,t){try{o.recordRTC.startRecording(),e()}catch(e){t(e)}}))},this.stopRecording=function(){return new Promise((function(e,t){try{o.recordRTC.stopRecording((function(i){o.blob=o.recordRTC.getBlob(),o.blob&&o.blob.size?e(i):t("Empty blob.",o.blob)}))}catch(e){t(e)}}))},this.pauseRecording=function(){return new Promise((function(e,t){try{o.recordRTC.pauseRecording(),e()}catch(e){t(e)}}))},this.resumeRecording=function(){return new Promise((function(e,t){try{o.recordRTC.resumeRecording(),e()}catch(e){t(e)}}))},this.getDataURL=function(e){return new Promise((function(e,t){try{o.recordRTC.getDataURL((function(t){e(t)}))}catch(e){t(e)}}))},this.getBlob=function(){return new Promise((function(e,t){try{e(o.recordRTC.getBlob())}catch(e){t(e)}}))},this.getInternalRecorder=function(){return new Promise((function(e,t){try{e(o.recordRTC.getInternalRecorder())}catch(e){t(e)}}))},this.reset=function(){return new Promise((function(e,t){try{e(o.recordRTC.reset())}catch(e){t(e)}}))},this.destroy=function(){return new Promise((function(e,t){try{e(o.recordRTC.destroy())}catch(e){t(e)}}))},this.getState=function(){return new Promise((function(e,t){try{e(o.recordRTC.getState())}catch(e){t(e)}}))},this.blob=null,this.version="5.6.2"}),void 0!==i&&(i.WebAssemblyRecorder=F)}));class tt extends Ue{constructor(e){super(),this.player=e,this.fileName="",this.fileType=e._opt.recordType||u,this.isRecording=!1,this.recordingTimestamp=0,this.recordingInterval=null,this.recorder=null,e.debug.log("Recorder","init")}destroy(){this._reset(),this.player.debug.log("Recorder","destroy")}setFileName(e,t){this.fileName=e,l!==t&&u!==t||(this.fileType=t)}get recording(){return this.isRecording}get recordTime(){return this.recordingTimestamp}startRecord(){const e=this.player.debug,t={type:"video",mimeType:"video/webm;codecs=h264",onTimeStamp:t=>{e.log("Recorder","record timestamp :"+t)},disableLogs:!this.player._opt.debug};try{const e=this.player.video.$videoElement.captureStream(25);if(this.player.audio&&this.player.audio.mediaStreamAudioDestinationNode&&this.player.audio.mediaStreamAudioDestinationNode.stream&&!this.player.audio.isStateSuspended()&&this.player.audio.hasAudio&&this.player._opt.hasAudio){const t=this.player.audio.mediaStreamAudioDestinationNode.stream;if(t.getAudioTracks().length>0){const i=t.getAudioTracks()[0];i&&i.enabled&&e.addTrack(i)}}this.recorder=et(e,t)}catch(t){e.error("Recorder","startRecord error",t),this.emit(F.recordCreateError)}this.recorder&&(this.isRecording=!0,this.player.emit(F.recording,!0),this.recorder.startRecording(),e.log("Recorder","start recording"),this.player.emit(F.recordStart),this.recordingInterval=window.setInterval((()=>{this.recordingTimestamp+=1,this.player.emit(F.recordingTimestamp,this.recordingTimestamp)}),1e3))}stopRecordAndSave(){this.recorder&&this.isRecording&&this.recorder.stopRecording((()=>{this.player.debug.log("Recorder","stop recording"),this.player.emit(F.recordEnd);const e=(this.fileName||Se())+"."+(this.fileType||u);He(this.recorder.getBlob(),e),this._reset(),this.player.emit(F.recording,!1)}))}_reset(){this.isRecording=!1,this.recordingTimestamp=0,this.recorder&&(this.recorder.destroy(),this.recorder=null),this.fileName=null,this.recordingInterval&&clearInterval(this.recordingInterval),this.recordingInterval=null}}class it{constructor(e){return new(it.getLoaderFactory())(e)}static getLoaderFactory(){return tt}}class ot{constructor(e){this.player=e,this.decoderWorker=new Worker(e._opt.decoder),this._initDecoderWorker(),e.debug.log("decoderWorker","init")}async destroy(){this.decoderWorker&&(this.decoderWorker.postMessage({cmd:j}),this.decoderWorker.terminate(),this.decoderWorker=null),this.player.debug.log("decoderWorker","destroy")}_initDecoderWorker(){const{debug:e,events:{proxy:t}}=this.player;this.decoderWorker.onmessage=t=>{const i=t.data;switch(i.cmd){case m:e.log("decoderWorker","onmessage:",m),this.player.loaded||this.player.emit(F.load),this.player.emit(F.decoderWorkerInit),this._initWork();break;case S:e.log("decoderWorker","onmessage:",S,i.code),this.player._times.decodeStart||(this.player._times.decodeStart=Se()),this.player.video.updateVideoInfo({encTypeCode:i.code});break;case w:e.log("decoderWorker","onmessage:",w,i.code),this.player.audio&&this.player.audio.updateAudioInfo({encTypeCode:i.code});break;case f:if(e.log("decoderWorker","onmessage:",f,`width:${i.w},height:${i.h}`),this.player.video.updateVideoInfo({width:i.w,height:i.h}),!this.player._opt.openWebglAlignment&&i.w/2%4!=0)return void this.player.emit(O.webglAlignmentError);this.player.video.initCanvasViewSize();break;case v:e.log("decoderWorker","onmessage:",v,`channels:${i.channels},sampleRate:${i.sampleRate}`),this.player.audio&&(this.player.audio.updateAudioInfo(i),this.player.audio.initScriptNode(i));break;case b:this.player.handleRender(),this.player.video.render(i),this.player.emit(F.timeUpdate,i.ts),this.player.updateStats({fps:!0,ts:i.ts,buf:i.delay}),this.player._times.videoStart||(this.player._times.videoStart=Se(),this.player.handlePlayToRenderTimes());break;case y:this.player.playing&&this.player.audio&&this.player.audio.play(i.buffer,i.ts);break;case E:i.message&&-1!==i.message.indexOf(B)&&this.player.emitError(O.wasmDecodeError);break;default:this.player[i.cmd]&&this.player[i.cmd](i)}}}_initWork(){const e={debug:this.player._opt.debug,useOffscreen:this.player._opt.useOffscreen,useWCS:this.player._opt.useWCS,videoBuffer:this.player._opt.videoBuffer,videoBufferDelay:this.player._opt.videoBufferDelay,openWebglAlignment:this.player._opt.openWebglAlignment};this.decoderWorker.postMessage({cmd:I,opt:JSON.stringify(e),sampleRate:this.player.audio&&this.player.audio.audioContext.sampleRate||0})}decodeVideo(e,t,i){const o={type:R,ts:Math.max(t,0),isIFrame:i};this.decoderWorker.postMessage({cmd:x,buffer:e,options:o},[e.buffer])}decodeAudio(e,t){this.player._opt.useWCS||this.player._opt.useMSE?this._decodeAudioNoDelay(e,t):this._decodeAudio(e,t)}_decodeAudio(e,t){const i={type:C,ts:Math.max(t,0)};this.decoderWorker.postMessage({cmd:x,buffer:e,options:i},[e.buffer])}_decodeAudioNoDelay(e,t){this.decoderWorker.postMessage({cmd:D,buffer:e,ts:Math.max(t,0)},[e.buffer])}updateWorkConfig(e){this.decoderWorker.postMessage({cmd:L,key:e.key,value:e.value})}}class rt extends Ue{constructor(e){super(),this.player=e,this.stopId=null,this.firstTimestamp=null,this.startTimestamp=null,this.delay=-1,this.bufferList=[],this.dropping=!1,this.initInterval()}destroy(){this.stopId&&(clearInterval(this.stopId),this.stopId=null),this.firstTimestamp=null,this.startTimestamp=null,this.delay=-1,this.bufferList=[],this.dropping=!1,this.off(),this.player.debug.log("CommonDemux","destroy")}getDelay(e){if(!e)return-1;if(this.firstTimestamp){if(e){const t=Date.now()-this.startTimestamp,i=e-this.firstTimestamp;this.delay=t>=i?t-i:i-t}}else this.firstTimestamp=e,this.startTimestamp=Date.now(),this.delay=-1;return this.delay}resetDelay(){this.firstTimestamp=null,this.startTimestamp=null,this.delay=-1,this.dropping=!1}initInterval(){this.player.debug.log("common dumex","init Interval");let e=()=>{let e;const t=this.player._opt.videoBuffer,i=this.player._opt.videoBufferDelay;if(!this.player.isDestroyedOrClosed())if(this.player._opt.useMSE&&this.player.mseDecoder&&this.player.mseDecoder.getSourceBufferUpdating())this.player.debug.warn("CommonDemux",`_loop getSourceBufferUpdating is true and bufferList length is ${this.bufferList.length}`);else if(this.bufferList.length)if(this.dropping){for(e=this.bufferList.shift(),e.type===C&&0===e.payload[1]&&this._doDecoderDecode(e);!e.isIFrame&&this.bufferList.length;)e=this.bufferList.shift(),e.type===C&&0===e.payload[1]&&this._doDecoderDecode(e);e.isIFrame&&this.getDelay(e.ts)<=Math.min(t,200)&&(this.dropping=!1,this._doDecoderDecode(e))}else e=this.bufferList[0],-1===this.getDelay(e.ts)?(this.bufferList.shift(),this._doDecoderDecode(e)):this.delay>t+i?(this.resetDelay(),this.dropping=!0):(e=this.bufferList[0],this.getDelay(e.ts)>t&&(this.bufferList.shift(),this._doDecoderDecode(e)))};e(),this.stopId=setInterval(e,10)}_doDecode(e,t,i,o,r){const s=this.player;let a={ts:i,cts:r,type:t,isIFrame:!1};s._opt.useWCS&&!s._opt.useOffscreen||s._opt.useMSE?(t===R&&(a.isIFrame=o),this.pushBuffer(e,a)):t===R?s.decoderWorker&&s.decoderWorker.decodeVideo(e,i,o):t===C&&s._opt.hasAudio&&s.decoderWorker&&s.decoderWorker.decodeAudio(e,i)}_doDecoderDecode(e){const t=this.player,{webcodecsDecoder:i,mseDecoder:o}=t;e.type===C?t._opt.hasAudio&&t.decoderWorker&&t.decoderWorker.decodeAudio(e.payload,e.ts):e.type===R&&(t._opt.useWCS&&!t._opt.useOffscreen?i.decodeVideo(e.payload,e.ts,e.isIFrame):t._opt.useMSE&&o.decodeVideo(e.payload,e.ts,e.isIFrame,e.cts))}pushBuffer(e,t){t.type===C?this.bufferList.push({ts:t.ts,payload:e,type:C}):t.type===R&&this.bufferList.push({ts:t.ts,cts:t.cts,payload:e,type:R,isIFrame:t.isIFrame})}close(){}_decodeEnhancedH265Video(e,t){const i=e[0],o=48&i,r=15&i,s=e.slice(1,5),a=new ArrayBuffer(4),n=new Uint32Array(a),A="a"==String.fromCharCode(s[0]);if(r===he){if(o===me){const t=e.slice(5);if(!A){const e=new Uint8Array(5+t.length);e.set([28,0,0,0,0],0),e.set(t,5),this._doDecode(e,R,0,!0,0)}}}else if(r===pe){let i=e,r=0;const s=o===me;if(!A){n[0]=e[4],n[1]=e[3],n[2]=e[2],n[3]=0,r=n[0];i=Oe(e.slice(8),s),this._doDecode(i,R,t,s,r)}}else if(r===ge){const i=o===me;let r=Oe(e.slice(5),i);this._doDecode(r,R,t,i,0)}}_isEnhancedH265Header(e){return!(128&~e)}}class st extends rt{constructor(e){super(e),this.input=this._inputFlv(),this.flvDemux=this.dispatchFlvData(this.input),e.debug.log("FlvDemux","init")}destroy(){super.destroy(),this.input=null,this.flvDemux=null,this.player.debug.log("FlvDemux","destroy")}dispatch(e){this.flvDemux(e)}*_inputFlv(){yield 9;const e=new ArrayBuffer(4),t=new Uint8Array(e),i=new Uint32Array(e),o=this.player;for(;;){t[3]=0;const e=yield 15,r=e[4];t[0]=e[7],t[1]=e[6],t[2]=e[5];const s=i[0];t[0]=e[10],t[1]=e[9],t[2]=e[8];let a=i[0];16777215===a&&(t[3]=e[11],a=i[0]);const n=yield s;switch(r){case k:o._opt.hasAudio&&(o.updateStats({abps:n.byteLength}),n.byteLength>0&&this._doDecode(n,C,a));break;case T:if(o._times.demuxStart||(o._times.demuxStart=Se()),o._opt.hasVideo){o.updateStats({vbps:n.byteLength});const e=n[0];if(this._isEnhancedH265Header(e))this._decodeEnhancedH265Video(n,a);else{const e=n[0]>>4==1;if(n.byteLength>0){i[0]=n[4],i[1]=n[3],i[2]=n[2],i[3]=0;let t=i[0];this._doDecode(n,R,a,e,t)}}}}}}dispatchFlvData(e){let t=e.next(),i=null;return o=>{let r=new Uint8Array(o);if(i){let e=new Uint8Array(i.length+r.length);e.set(i),e.set(r,i.length),r=e,i=null}for(;r.length>=t.value;){let i=r.slice(t.value);t=e.next(r.slice(0,t.value)),r=i}r.length>0&&(i=r)}}close(){this.input&&this.input.return(null)}}class at extends rt{constructor(e){super(e),e.debug.log("M7sDemux","init")}destroy(){super.destroy(),this.player.debug.log("M7sDemux","destroy")}dispatch(e){const t=this.player,i=new DataView(e),o=i.getUint8(0),r=i.getUint32(1,!1),s=new ArrayBuffer(4),a=new Uint32Array(s);switch(o){case C:if(t._opt.hasAudio){const i=new Uint8Array(e,5);t.updateStats({abps:i.byteLength}),i.byteLength>0&&this._doDecode(i,o,r)}break;case R:if(t._opt.hasVideo)if(t._times.demuxStart||(t._times.demuxStart=Se()),i.byteLength>5){const s=new Uint8Array(e,5),n=s[0];if(this._isEnhancedH265Header(n))this._decodeEnhancedH265Video(s,r);else{const e=i.getUint8(5)>>4==1;t.updateStats({vbps:s.byteLength}),a[0]=s[4],a[1]=s[3],a[2]=s[2],a[3]=0;let n=a[0];this._doDecode(s,o,r,e,n)}}else this.player.debug.warn("M7sDemux","dispatch","dv byteLength is",i.byteLength)}}}class nt{constructor(e){return new(nt.getLoaderFactory(e._opt.demuxType))(e)}static getLoaderFactory(e){return e===c?at:e===d?st:void 0}}class At{constructor(e){this.TAG="ExpGolomb",this._buffer=e,this._buffer_index=0,this._total_bytes=e.byteLength,this._total_bits=8*e.byteLength,this._current_word=0,this._current_word_bits_left=0}destroy(){this._buffer=null}_fillCurrentWord(){let e=this._total_bytes-this._buffer_index,t=Math.min(4,e),i=new Uint8Array(4);i.set(this._buffer.subarray(this._buffer_index,this._buffer_index+t)),this._current_word=new DataView(i.buffer).getUint32(0,!1),this._buffer_index+=t,this._current_word_bits_left=8*t}readBits(e){if(e<=this._current_word_bits_left){let t=this._current_word>>>32-e;return this._current_word<<=e,this._current_word_bits_left-=e,t}let t=this._current_word_bits_left?this._current_word:0;t>>>=32-this._current_word_bits_left;let i=e-this._current_word_bits_left;this._fillCurrentWord();let o=Math.min(i,this._current_word_bits_left),r=this._current_word>>>32-o;return this._current_word<<=o,this._current_word_bits_left-=o,t=t<>>e)return this._current_word<<=e,this._current_word_bits_left-=e,e;return this._fillCurrentWord(),e+this._skipLeadingZero()}readUEG(){let e=this._skipLeadingZero();return this.readBits(e+1)-1}readSEG(){let e=this.readUEG();return 1&e?e+1>>>1:-1*(e>>>1)}}class dt{static _ebsp2rbsp(e){let t=e,i=t.byteLength,o=new Uint8Array(i),r=0;for(let e=0;e=2&&3===t[e]&&0===t[e-1]&&0===t[e-2]||(o[r]=t[e],r++);return new Uint8Array(o.buffer,0,r)}static parseSPS(e){let t=dt._ebsp2rbsp(e),i=new At(t);i.readByte();let o=i.readByte();i.readByte();let r=i.readByte();i.readUEG();let s=dt.getProfileString(o),a=dt.getLevelString(r),n=1,A=420,d=[0,420,422,444],c=8;if((100===o||110===o||122===o||244===o||44===o||83===o||86===o||118===o||128===o||138===o||144===o)&&(n=i.readUEG(),3===n&&i.readBits(1),n<=3&&(A=d[n]),c=i.readUEG()+8,i.readUEG(),i.readBits(1),i.readBool())){let e=3!==n?8:12;for(let t=0;t0&&e<16?(v=[1,12,10,16,40,24,20,32,80,18,15,64,160,4,3,2][e-1],w=[1,11,11,11,33,11,11,11,33,11,11,33,99,3,2,1][e-1]):255===e&&(v=i.readByte()<<8|i.readByte(),w=i.readByte()<<8|i.readByte())}if(i.readBool()&&i.readBool(),i.readBool()&&(i.readBits(4),i.readBool()&&i.readBits(24)),i.readBool()&&(i.readUEG(),i.readUEG()),i.readBool()){let e=i.readBits(32),t=i.readBits(32);E=i.readBool(),B=t,C=2*e,S=B/C}}let R=1;1===v&&1===w||(R=v/w);let k=0,T=0;if(0===n)k=1,T=2-g;else{k=3===n?1:2,T=(1===n?2:1)*(2-g)}let I=16*(h+1),x=16*(p+1)*(2-g);I-=(m+f)*k,x-=(b+y)*T;let D=Math.ceil(I*R);return i.destroy(),i=null,{profile_string:s,level_string:a,bit_depth:c,ref_frames:u,chroma_format:A,chroma_format_string:dt.getChromaFormatString(A),frame_rate:{fixed:E,fps:S,fps_den:C,fps_num:B},sar_ratio:{width:v,height:w},codec_size:{width:I,height:x},present_size:{width:D,height:x}}}static _skipScalingList(e,t){let i=8,o=8,r=0;for(let s=0;s ${t.codecWidth}, height ${i.height}-> ${t.codecHeight}`),void this.player.emit(O.webcodecsWidthOrHeightChange)}if(!this.isDecodeFirstIIframe&&i&&(this.isDecodeFirstIIframe=!0),this.isDecodeFirstIIframe){const o=new EncodedVideoChunk({data:e.slice(5),timestamp:t,type:i?_:$});this.player.emit(F.timeUpdate,t);try{if(this.isDecodeStateClosed())return void this.player.debug.warn("Webcodecs","VideoDecoder isDecodeStateClosed true");this.decoder.decode(o)}catch(e){this.player.debug.error("Webcodecs","VideoDecoder",e),(-1!==e.toString().indexOf(Ae)||-1!==e.toString().indexOf(de))&&this.player.emitError(O.webcodecsDecodeError)}}else this.player.debug.warn("Webcodecs","VideoDecoder isDecodeFirstIIframe false")}else if(i&&0===e[1]){const t=15&e[0];if(this.player.video.updateVideoInfo({encTypeCode:t}),t===P)return void this.emit(O.webcodecsH265NotSupport);this.player._times.decodeStart||(this.player._times.decodeStart=Se());const i=function(e){let t=e.subarray(1,4),i="avc1.";for(let e=0;e<3;e++){let o=t[e].toString(16);o.length<2&&(o="0"+o),i+=o}return{codec:i,description:e}}(e.slice(5));this.player.debug.log("Webcodecs","VideoDecoder configure",i);try{this.decoder.configure(i)}catch(e){return this.player.debug.error("Webcodecs","VideoDecoder configure",e),void this.player.emit(O.webcodecsConfigureError)}this.hasInit=!0}}isDecodeStateClosed(){return"closed"===this.decoder.state}}const ut={play:"播放",pause:"暂停",audio:"",mute:"",screenshot:"截图",loading:"加载",fullscreen:"全屏",fullscreenExit:"退出全屏",record:"录制",recordStop:"停止录制"};var ht=Object.keys(ut).reduce(((e,t)=>(e[t]=`\n \n ${ut[t]?`${ut[t]}`:""}\n`,e)),{}),pt=(e,t)=>{const{events:{proxy:i}}=e,o=document.createElement("object");o.setAttribute("aria-hidden","true"),o.setAttribute("tabindex",-1),o.type="text/html",o.data="about:blank",Be(o,{display:"block",position:"absolute",top:"0",left:"0",height:"100%",width:"100%",overflow:"hidden",pointerEvents:"none",zIndex:"-1"});let r=e.width,s=e.height;i(o,"load",(()=>{i(o.contentDocument.defaultView,"resize",(()=>{e.width===r&&e.height===s||(r=e.width,s=e.height,e.emit(F.resize),n())}))})),e.$container.appendChild(o),e.on(F.destroy,(()=>{e.$container.removeChild(o)})),e.on(F.volumechange,(()=>{!function(e){if(0===e)Be(t.$volumeOn,"display","none"),Be(t.$volumeOff,"display","flex"),Be(t.$volumeHandle,"top","48px");else if(t.$volumeHandle&&t.$volumePanel){const i=Ce(t.$volumePanel,"height")||60,o=Ce(t.$volumeHandle,"height"),r=i-(i-o)*e-o;Be(t.$volumeHandle,"top",`${r}px`),Be(t.$volumeOn,"display","flex"),Be(t.$volumeOff,"display","none")}t.$volumePanelText&&(t.$volumePanelText.innerHTML=parseInt(100*e))}(e.volume)})),e.on(F.loading,(e=>{Be(t.$loading,"display",e?"flex":"none"),Be(t.$poster,"display","none"),e&&Be(t.$playBig,"display","none")}));const a=i=>{let o=De(i)?i:e.fullscreen;Be(t.$fullscreenExit,"display",o?"flex":"none"),Be(t.$fullscreen,"display",o?"none":"flex")},n=()=>{Te()&&t.$controls&&e._opt.useWebFullScreen&&setTimeout((()=>{if(e.fullscreen){let i=e.height/2-e.width+19,o=e.height/2-19;t.$controls.style.transform=`translateX(${-i}px) translateY(-${o}px) rotate(-90deg)`}else t.$controls.style.transform="translateX(0) translateY(0) rotate(0)"}),10)};try{ye.on("change",a),e.events.destroys.push((()=>{ye.off("change",a)}))}catch(e){}e.on(F.webFullscreen,(e=>{a(e),n()})),e.on(F.recording,(()=>{Be(t.$record,"display",e.recording?"none":"flex"),Be(t.$recordStop,"display",e.recording?"flex":"none"),Be(t.$recording,"display",e.recording?"flex":"none"),!e.recording&&t.$recordingTime&&(t.$recordingTime.innerHTML=Le(0))})),e.on(F.recordingTimestamp,(e=>{t.$recordingTime&&(t.$recordingTime.innerHTML=Le(e))})),e.on(F.playing,(e=>{Be(t.$play,"display",e?"none":"flex"),Be(t.$playBig,"display",e?"none":"block"),Be(t.$pause,"display",e?"flex":"none"),Be(t.$screenshot,"display",e?"flex":"none"),Be(t.$record,"display",e?"flex":"none"),Be(t.$qualityMenu,"display",e?"flex":"none"),Be(t.$volume,"display",e?"flex":"none"),a(),e||t.$speed&&(t.$speed.innerHTML=function(e){if(null==e||""===e||0===parseInt(e)||isNaN(parseInt(e)))return"0KB/s";let t=parseFloat(e);return t=t.toFixed(2),t+"KB/s"}(""))})),e.on(F.kBps,(e=>{const i=function(e){if(null==e||""===e||0===parseFloat(e)||"NaN"===e)return"0 KB/s";const t=["B/s","KB/s","MB/s","GB/s","TB/s","PB/s","EB/s","ZB/s","YB/s"];let i=0;const o=parseFloat(e/8);i=Math.floor(Math.log(o)/Math.log(1024));let r=o/Math.pow(1024,i);return r=r.toFixed(2),r+(t[i]||t[0])}(1e3*e);t.$speed&&(t.$speed.innerHTML=i)}))};function gt(e,t){void 0===t&&(t={});var i=t.insertAt;if(e&&"undefined"!=typeof document){var o=document.head||document.getElementsByTagName("head")[0],r=document.createElement("style");r.type="text/css","top"===i&&o.firstChild?o.insertBefore(r,o.firstChild):o.appendChild(r),r.styleSheet?r.styleSheet.cssText=e:r.appendChild(document.createTextNode(e))}}gt('@keyframes rotation{0%{-webkit-transform:rotate(0deg)}to{-webkit-transform:rotate(1turn)}}@keyframes magentaPulse{0%{background-color:#630030;-webkit-box-shadow:0 0 9px #333}50%{background-color:#a9014b;-webkit-box-shadow:0 0 18px #a9014b}to{background-color:#630030;-webkit-box-shadow:0 0 9px #333}}.jessibuca-container .jessibuca-icon{cursor:pointer;width:16px;height:16px}.jessibuca-container .jessibuca-poster{position:absolute;z-index:10;left:0;top:0;right:0;bottom:0;height:100%;width:100%;background-position:50%;background-repeat:no-repeat;background-size:contain;pointer-events:none}.jessibuca-container .jessibuca-play-big{position:absolute;display:none;height:100%;width:100%;background:rgba(0,0,0,.4)}.jessibuca-container .jessibuca-play-big:after{cursor:pointer;content:"";position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);display:block;width:48px;height:48px;background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAACgklEQVRoQ+3ZPYsTQRjA8eeZZCFlWttAwCIkZOaZJt8hlvkeHrlccuAFT6wEG0FQOeQQLCIWih6chQgKgkkKIyqKCVYip54IWmiQkTmyYhFvd3Zn3yDb7szu/7cv7GaDkPEFM94PK0DSZ9DzDAyHw7uI2HRDlVJX5/N5r9FoHCYdr/fvCRiNRmpJ6AEidoUQ15NG+AH8BgD2n9AHANAmohdJQfwAfgGA4xF4bjabnW21Whob62ILoKNfAsAGEd2PU2ATcNSNiDf0/cE5/xAHxDpgEf0NADaJ6HLUiKgAbvcjpdSGlPJZVJCoAUfdSqkLxWLxTLlc/mkbEgtgET1TSnWklLdtIuIEuN23crlcp16vv7cBSQKgu38AwBYRXQyLSArg3hsjRDxNRE+CQhIF/BN9qVAobFYqle+mkLQAdLd+8K0T0U0TRJoAbvc9fVkJId75gaQRoLv1C2STiPTb7rFLWgE6+g0RncwyYEJEtawCvjDGmpzzp5kD6NfxfD7frtVqB17xen2a7oG3ALBm+oMoFQBEPD+dTvtBfpImDXjIGFvjnD/3c7ksG5MU4HDxWeZa0HB3XhKAXcdxOn5vUi9gnIDXSqm2lHLPK8pkfVyAbSLqm4T5HRs1YB8RO0KIid8g03FRAT4rpbpSyh3TINPxUQB2GGM9zvkn05gg420CJovLZT9ISNA5tgB9ItoOGhFmnh/AcZ/X9xhj65zzV2Eiwsz1A1j2B8dHAOgS0W6YnduY6wkYj8d3lFKn/j66Ea84jtOrVqtfbQSE3YYnYDAY5Eql0hYAnNDv6kKIx2F3anO+J8DmzqLY1goQxVE12ebqDJgcrSjGrs5AFEfVZJt/AF0m+jHzUTtnAAAAAElFTkSuQmCC");background-repeat:no-repeat;background-position:50%}.jessibuca-container .jessibuca-play-big:hover:after{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAACEElEQVRoQ+2ZXStEQRjH/3/yIXwDdz7J+i7kvdisXCk3SiFJW27kglBcSFFKbqwQSa4krykuKB09Naf2Yndn5jgzc06d53Znd36/mWfeniVyHsw5PwqB0DOonYEoijYBlOpAFwCMkHwLDS/9mwhEDUCfAAyTXA4tYSLwC6CtCegegH6S56FETAR+AHRoACcBTJAUWa+RloBAXwAYIrnt0yBNgZi7qtbHgw8RFwLC/QFglOScawlXAjH3gUqrE1cirgVi7mkAYyS/0xbxJSDcdwAGSa6nKeFTIOZeUyL3aYiEEBDuLwDjJGf+KxFKIOY+BdBL8iipSGiBmHtWbbuftiJZERBuOfgGSK7aSGRJIObeUml1ayKSRQHhlgtkiaTcdltGVgUE+ppkV54FaiS78yrwqlLoOI8Cch2XV548W7WRpTVwA6DP9kGUFYEpAOUkT9LQAvtq1M+0udKkQSgBqSlJWWYxKXj8vRACK+o6bbRIdYI+Ba7U7rKjg7L53JdAhWTZBsy0rWuBXZUuNVMg23auBF7UIl2yBbJt70JAoKV6/WwLk6R9mgKSJlJ1kLTxFmkJyCla8UZd15GJQKvyumyJ8gy8DAEvfZoINPqD41EtUjmUgoaJwAaAnjrKebVI34OSq85NBNqlCAWgE0CV5GEWwI3vQlmCbcSinYFCwPEIFDPgeIC1P1/MgHaIHDf4Aydx2TF7wnKeAAAAAElFTkSuQmCC")}.jessibuca-container .jessibuca-recording{display:none;position:absolute;left:50%;top:0;padding:0 3px;transform:translateX(-50%);justify-content:space-around;align-items:center;width:95px;height:20px;background:#000;opacity:1;border-radius:0 0 8px 8px;z-index:1}.jessibuca-container .jessibuca-recording .jessibuca-recording-red-point{width:8px;height:8px;background:#ff1f1f;border-radius:50%;animation:magentaPulse 1s linear infinite}.jessibuca-container .jessibuca-recording .jessibuca-recording-time{font-size:14px;font-weight:500;color:#ddd}.jessibuca-container .jessibuca-recording .jessibuca-icon-recordStop{width:16px;height:16px;cursor:pointer}.jessibuca-container .jessibuca-loading{display:none;flex-direction:column;justify-content:center;align-items:center;position:absolute;z-index:20;left:0;top:0;right:0;bottom:0;width:100%;height:100%;pointer-events:none}.jessibuca-container .jessibuca-loading-text{line-height:20px;font-size:13px;color:#fff;margin-top:10px}.jessibuca-container .jessibuca-controls{background-color:#161616;box-sizing:border-box;display:flex;flex-direction:column;justify-content:flex-end;position:absolute;z-index:40;left:0;right:0;bottom:0;height:38px;width:100%;padding-left:13px;padding-right:13px;font-size:14px;color:#fff;opacity:0;visibility:hidden;-webkit-user-select:none;-moz-user-select:none;user-select:none}.jessibuca-container .jessibuca-controls .jessibuca-controls-item{position:relative;display:flex;justify-content:center;padding:0 8px}.jessibuca-container .jessibuca-controls .jessibuca-controls-item:hover .icon-title-tips{visibility:visible;opacity:1}.jessibuca-container .jessibuca-controls .jessibuca-fullscreen,.jessibuca-container .jessibuca-controls .jessibuca-fullscreen-exit,.jessibuca-container .jessibuca-controls .jessibuca-icon-audio,.jessibuca-container .jessibuca-controls .jessibuca-microphone-close,.jessibuca-container .jessibuca-controls .jessibuca-pause,.jessibuca-container .jessibuca-controls .jessibuca-play,.jessibuca-container .jessibuca-controls .jessibuca-record,.jessibuca-container .jessibuca-controls .jessibuca-record-stop,.jessibuca-container .jessibuca-controls .jessibuca-screenshot{display:none}.jessibuca-container .jessibuca-controls .jessibuca-icon-audio,.jessibuca-container .jessibuca-controls .jessibuca-icon-mute{z-index:1}.jessibuca-container .jessibuca-controls .jessibuca-controls-bottom{display:flex;justify-content:space-between;height:100%}.jessibuca-container .jessibuca-controls .jessibuca-controls-bottom .jessibuca-controls-left,.jessibuca-container .jessibuca-controls .jessibuca-controls-bottom .jessibuca-controls-right{display:flex;align-items:center}.jessibuca-container.jessibuca-controls-show .jessibuca-controls{opacity:1;visibility:visible}.jessibuca-container.jessibuca-controls-show-auto-hide .jessibuca-controls{opacity:.8;visibility:visible;display:none}.jessibuca-container.jessibuca-hide-cursor *{cursor:none!important}.jessibuca-container .jessibuca-icon-loading{width:50px;height:50px;background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAHHklEQVRoQ91bfYwdVRX/nTvbPuuqlEQM0q4IRYMSP0KkaNTEEAokNUEDFr9iEIOiuCC2++4dl+Tti9nOmbfWFgryESPhH7V+IIpG8SN+Fr8qqKgQEKoUkQREwXTLs8495mze1tf35s2bfTu7ndf758y55/x+c879OvcMYYnbxMTEy4IgOImIxkRkrYisNsasUrPe+wNE9C8ielRE9iVJsndmZubBpYRES6E8DMNXeu83ENHrAJwO4OUARvrY+i+ABwDcLSJ7jDF3RlF0f9H4CiNcrVZPCIJgk4hcCOCNBQH9EYBveO93NRqNx4rQuWjCExMT64IguEJE3kdEq4sA1alDRDTsb02SZOfMzMxDi7ExMGFr7THGGCciVwKYG5PL0HTMb69UKtNTU1Ozg9gbiLC1diMRXQ/gxEGMFtDnQRHZHMfxHQvVtWDCzrkdANSredvfRWQ3Ee0F8DCAJwDs994nQRCM6qxNROu892uI6A0ATs2rWER2xHF8VV55lctN2Dl3LICvA3hzDgMPENFXROT2SqVyb71efzZHnzkRnRNGRkY2isj5AM7K0e/HAN7OzP/MIZuP8OTk5FiSJDpjnpylVER+YIzZEUXRN/MY7ydTrVbXE9FlRPT+LFkiesh7f1Ycx4/009nXw9balxDRLwC8OEPZ/SLi4jjWCCi8WWtfA2CKiN6WofzxIAhePz09/dfMj5P1slqtPj8IgntEZF0vORH51Ozs7NU7d+5sFs60Q2EYhpeKyDUZq8LDInJ6HMdP98KS6WHn3E8BvKlHZx2X72Xmry410Xb91trTiOjLAF7Rw+5uZu6FufcYds7pl7wiTSkRPSUi5zHzr5eT7LytWq32gmaz+a0MZ1zDzB9LxZ72sFqtbjDGfLcHmWeI6IwoinTfe8RarVYzzWbzJxnb2A3M/P1OgF0hPT4+XhkdHd0H4LgUNv8xxpy5devW3x4xpm2Gt2zZMjoyMnJ363DSCemJ/fv3j3XOLV2EnXMNXQ57hPIFURTdVgay8xhaq4geKVem4Jph5mr788MIV6vVtcYY9W5XI6Iboij6SJnIzmNxzl0E4Itp2IIgWDs9Pf23+XeHEQ7D8EYR+VBKx8eYeU0ZybaR1s3OxhSMNzLzh7sIb968+YUrVqxQ7z6na6ATlS6UOzG2Qlv366bj3bMHDx4c27Zt25P6/JCHnXO6Cf90yhe6l5lfXWbvto3nm4no0hSHXRVFkR56/k/YWvsbItJ0zGFNRC6K4/hLQ0JYt8FdW0si2hNF0RmHCLcSbWnr6pPM/CIAMgyEFaNz7tsAzuvEmyTJKZotmQtpa+04EV2bQuo6Zh4fFrItwu8C8PmUSP1oHMfXzxEOw3CXiGzqFPLen9NoNL43TIQ19UREmmRY0YF7FzO/k5xzLwWgYdCZaZj13h/faDT+PUyEW15OO/T8MQiCjUr4HAC6Ee/MG/+MmfNkN0r3Pay124jo4x3ADuiBRwl/EMBNKTF/SxzHl5SOTQ5AzrnLANyQsjxdooRrmk1I0TPFzPUc+ksnYq09l4i+k8aJrLXbiajr7EhEV0ZRlDZzl45gJyDNhRljfpkCdLt6WF2vIdDZPsDMnys9uxSA1tpXEdHvU1599qgknHHqu/moDOlWNkTTyu2rTGKMOfeonLQ0lFunv08AOBPAXu/9jkajsafnsgTgVma+eBjHcBbmrI3HXcxc1D1vab5b1tbyQKVSOb5erz9TGrQFAMk8POhWLI7jOwuwUxoV/Y6Hn2Hmy0uDtgAgc4RbZQt/Ttl7PrVy5crj6vW6L8BWKVS057TuAqAX0p3t3cz8hVKgLQDEIcLW2suJ6LoUnX9i5tMKsFUKFYcIZ6VpAWxiZr2xG/p2WCI+4yDxeKVSWXM0jOXDCE9OTq5JkuTRNDcS0U1RFKWdqobK612XaWEYflJEru7BYuhDu4tw66ShxSFpd0laD7meme8ZKre2gU0teXDOnQ2gV3q2FBfig37wnjUevVI/auhIlzwMSnYOe1bnPkUtWrXznuUualkM2b6EtWzJGKMlBaf0MrScZUuLJduXsAq07l1/DuCEDIP3iUi4VIVpRRCd19G3Ek8FtfTQe//DrAI1lSu69LBIogsirMK1Wm11s9n8GoC35AByH4DbvPe3r1q16g8LKS7NoXtRIrk83G4ha/bugURL93cD+Mt8+TAR6YT3j0ql8rtBC70HZb1gwmooDMO3eu+vJaKTBjXc6rfPe39ho9H41SL15O4+EOFWiGv5n2sViz83t8VuwWW9pRyY8Dxu59zJIqJVAhcP+JPHI8y8bL8SLJrwPHH9jYeI3kFEF+Ssmp/rqjN7HMe6lV2WVhjhdrRhGJ7a+lFrPYDXAtB667Q/X5723p+tNwLLwrbf1rIIEBryxpgTkyQZA6DlFccS0fMA6G84d6RVvBZht5eO/wEB1Kvsoc6vtAAAAABJRU5ErkJggg==") no-repeat 50%;background-size:100% 100%;animation:rotation 1s linear infinite}.jessibuca-container .jessibuca-icon-screenshot{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAE5UlEQVRoQ+1YW2sdVRT+1s7JxbsoVkEUrIIX0ouz15zYNA+N1RdtQfCltlUfvLbqL/BCwZ8grbHtizQqPojgBSr0JkiMmT2nxgapqBURtPVCq7HxJCeZJVPmxDlzZubMmXOSEsnAvOy917fXt9e39tp7E5b4R0vcfywTuNgRbBgBx3HuJqLVzPzmYjprjHkcwAlmLqXNm4XAISLaSESPaq2HF4OE67rbRGRYRA7btn1fbgLGmKsA/Azg0gBkGzO/vZAkHMd5hIiqc5wHcCMz/5k0Z2oExsfHV1QqldPAf8lORNu11m8tBAljzFYAYWxRSl1vWdZvuQj4RsYYF4AVBlgIOVVlE55HRIxt23ZuCfmGjuOsJ6LPoiAistW27XfaEYmIbOYhPc9bXywWR1oiEJDYQkR1zrYjEjGyqfqbKd8a7kJVtLgQ+30i8pht2wfyRKIdmJkJBPkQTbILfudJ7CTZNBvVpggEcgpvc/ML38zESbLJsxBNE/A9biX0rdjGyTQXgbxyapdsarb0PMlXtWnGoXbKpm0Essqp3bJpK4E0OXmed3+hUBDP8w5FI91M0rdcyLLILElOCbaZilSWeXMncRx4klTCY1spfG3dhZJWx3GcDUR0EEB3ZMw0ET2gtT6SZWWzjmlrBIJCl0hAKfWgZVmHszqXZVxbCSxpCS2JJA6umIhe8ZKKVLPbaBJ+S9toqVRa53nedgAbAKwIwH4FcAzAa0R0l4i8F7PPz189k6RFRA+LyNcAXojDV0oNW5b1eW4Cxpg9AHZkSaaa6hhzb065uDSCH2LmRB8Sk9gY4293g43Qo/1pV80m8yQMfZSZ781cB1zXHRKRZ2IMpgD8A+DamL4ZItqitX4/jbQx5iEA7wLoihn3V/ACckWMJN/QWj9b1x5tGBsbW6uUOh5pPy0iL3Z2dn6ilJqanp5ep5TaJSLhF4NppdRNaU8gPmapVLrO87yfIoXuWyJ6uVKp+HmFjo6OQSJ6FcBtYT+UUmstyxqvkWuUgDFmP4AnQu2/e563qlgs+u9DNZ8xZhRAX7VRRPbath0XuXk7Y8xeAE+FgL6fnJzsHRwcLIfBR0ZGLunq6poAsDLUvp+Zw7b1r9PGmJMAbg8Z7WDmoThZuK67WkS+DD18fcPMdzSQUBR/EzN/nIC/SUQ+DPXV4dclsTHmHAD/SfHCNzc3t7Kvr++HJKeMMacA3BL0nyuXyzcPDAxMxo0fHR29slAo/Ajg6qD/fE9Pzw29vb1/x42fmJi4vFwu+5G/LOg/y8zXNJLQ2dAES5JANMQ7mfn1jBI6ycx3NiMhItqstf4oAX+ziHwQ6qvDj5NQNIn/ALCKmX+JSeIvABRD7fuY+ekGBPYBeDI05tTMzExvf3+/vz2Hk91/ET8RSeI6/DoCpVJpjed5fmKGvzMAXpqdnT3oed5Ud3d3v4jsAqBr9Ei0Rmv9VRqBBPzvROQVETnq2xJRdRu9tRF+bCVOKWT+Kvl/TSIFk6SW/LAjKfjV5K8rZABi8dOOEv7FI7Z8x6zwEWbemLbyMfJr5qiSiJ96oclymBOR3bZtP9+M89WxxpjdAHY2sN3DzM8ljWl4I3Nd9x7/OE1ENcdpETnmH3e11n41zv0l4J8RkU+J6AAz+xtF4teQQG7PFslwmcAiLfSyhC72Qv9/I/Avns2OT7QJskoAAAAASUVORK5CYII=") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-screenshot:hover{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAED0lEQVRoQ+2ZycsdRRTFf2ejqHFAMQqiYBTUoElUHLNx3GgCgpuYRF2o0UT9CxwQ/BMkMSbZSKLiQgQHUDCJgjiAxiEiESdEcJbEedgcKaj3UV+/6q7u/jovPPkK3qbr1ql76p5bt6qemPKmKfefeQKHOoLFCNg+H1gi6fFJOmv7VmCvpD1N87Yh8ApwNXCzpB2TIGF7DRDm2inpmt4EbB8LfAMcGUHWSHryYJKwfRMwmuMP4BRJv9TN2RgB2wuB72BWsq+V9MTBIGF7NZBiGzhJ0o+9CIRBtt8FLqgADC6nRDbpVO9Iuqi3hCKB5cDrGZDVkp4aIhIV2aSQyyW9MScCkcQqIOfsnCORkc3I31b5VtyFRmg1IQ7dt0ja3icSQ2C2JhAjUU2ykd+dE7tBNp2i2olAJJFuc+nCt564QTadF6IzgUhiVGiqyinKaQjZpJP2ItBXTkPJZhACXeU0pGwGI9BWTkPLZlACBTldG4o5EA6E1dY66edcyNrs8Q36zg1vVaTazNs7iXPgDVJJzYs7VRvHRzaDEohyugJ4CTi84sg/wHWSdnVxsGQ7aQLXS9pZcqpL/6AEplpCU5HE8YpJ9YrXUKQ6baN1+HPaRm1fBqwFQnKGK2ZoPwCvAo8Ai4FnMpPMHMwapHUj8DFwbw3+Dklv9iZgexOwvktSRduxU2VDlErwmyXV+lCbxLbDdndlCT3TX3vV7JgnKfRuSVflfMkSsL0ZuDMz4E/gL+CETN+/wCpJzzaRtn0D8DRwWMbu1/gCcnSm7zFJd1W/jxGwvQx4r2IYnlbuA14GAomQFw8B6YtBKFSnNj2BxEJ3IvB1pdB9CjwQ8yqYhcg/DJxZ8WOZpA/SbzkC24DbEqOfgPMkBRKzmu23gEuSj1sk5SI3Y2J7C3BHMuZz4FxJf6fgto8APgIWJd+3SUrHjr9O294HnJUMWi8pSGqs2V4CvJ88fH0i6eyChKr4KyS9WIO/Ang+6RvDz0XgABCeFEdtkaQv65yy/QVweuwPY0+T9FuNQ8cAXwHHxf7wdHiypN9r7BfEl8GjYv9+SceXJLQ/mSDYTh2Baog3SHq0pYT2STqno4RWSnqhBn8l8FzSN4bfJol/jkn8bXUS228DFyfft0paVyCwFbg9sQkSDEkctueZZju8iO+tJPEYfo7A0piYKd73wP3xnB+20cvjNnphxdmlkj4sEMjhfwY8COyOY0fb6Bkl/K6FLKxS+M1KpDhJY8mvrG5doRwlf66QZfGbjhLh4pEt35kV3iUp/IvTunU8qtTil/7gaHOY2yjpntaez9b5RmBDYewmSXfX2RRvZLYvbThOh+NuqMa9Ww1+yLnXgO2SwkZR24oEens2oYHzBCa00PMSOtQL/f+NwH+Hg8hAnbrYgQAAAABJRU5ErkJggg==") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-play{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAACgklEQVRoQ+3ZPYsTQRjA8eeZZCFlWttAwCIkZOaZJt8hlvkeHrlccuAFT6wEG0FQOeQQLCIWih6chQgKgkkKIyqKCVYip54IWmiQkTmyYhFvd3Zn3yDb7szu/7cv7GaDkPEFM94PK0DSZ9DzDAyHw7uI2HRDlVJX5/N5r9FoHCYdr/fvCRiNRmpJ6AEidoUQ15NG+AH8BgD2n9AHANAmohdJQfwAfgGA4xF4bjabnW21Whob62ILoKNfAsAGEd2PU2ATcNSNiDf0/cE5/xAHxDpgEf0NADaJ6HLUiKgAbvcjpdSGlPJZVJCoAUfdSqkLxWLxTLlc/mkbEgtgET1TSnWklLdtIuIEuN23crlcp16vv7cBSQKgu38AwBYRXQyLSArg3hsjRDxNRE+CQhIF/BN9qVAobFYqle+mkLQAdLd+8K0T0U0TRJoAbvc9fVkJId75gaQRoLv1C2STiPTb7rFLWgE6+g0RncwyYEJEtawCvjDGmpzzp5kD6NfxfD7frtVqB17xen2a7oG3ALBm+oMoFQBEPD+dTvtBfpImDXjIGFvjnD/3c7ksG5MU4HDxWeZa0HB3XhKAXcdxOn5vUi9gnIDXSqm2lHLPK8pkfVyAbSLqm4T5HRs1YB8RO0KIid8g03FRAT4rpbpSyh3TINPxUQB2GGM9zvkn05gg420CJovLZT9ISNA5tgB9ItoOGhFmnh/AcZ/X9xhj65zzV2Eiwsz1A1j2B8dHAOgS0W6YnduY6wkYj8d3lFKn/j66Ea84jtOrVqtfbQSE3YYnYDAY5Eql0hYAnNDv6kKIx2F3anO+J8DmzqLY1goQxVE12ebqDJgcrSjGrs5AFEfVZJt/AF0m+jHzUTtnAAAAAElFTkSuQmCC") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-play:hover{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAACEElEQVRoQ+2ZXStEQRjH/3/yIXwDdz7J+i7kvdisXCk3SiFJW27kglBcSFFKbqwQSa4krykuKB09Naf2Yndn5jgzc06d53Znd36/mWfeniVyHsw5PwqB0DOonYEoijYBlOpAFwCMkHwLDS/9mwhEDUCfAAyTXA4tYSLwC6CtCegegH6S56FETAR+AHRoACcBTJAUWa+RloBAXwAYIrnt0yBNgZi7qtbHgw8RFwLC/QFglOScawlXAjH3gUqrE1cirgVi7mkAYyS/0xbxJSDcdwAGSa6nKeFTIOZeUyL3aYiEEBDuLwDjJGf+KxFKIOY+BdBL8iipSGiBmHtWbbuftiJZERBuOfgGSK7aSGRJIObeUml1ayKSRQHhlgtkiaTcdltGVgUE+ppkV54FaiS78yrwqlLoOI8Cch2XV548W7WRpTVwA6DP9kGUFYEpAOUkT9LQAvtq1M+0udKkQSgBqSlJWWYxKXj8vRACK+o6bbRIdYI+Ba7U7rKjg7L53JdAhWTZBsy0rWuBXZUuNVMg23auBF7UIl2yBbJt70JAoKV6/WwLk6R9mgKSJlJ1kLTxFmkJyCla8UZd15GJQKvyumyJ8gy8DAEvfZoINPqD41EtUjmUgoaJwAaAnjrKebVI34OSq85NBNqlCAWgE0CV5GEWwI3vQlmCbcSinYFCwPEIFDPgeIC1P1/MgHaIHDf4Aydx2TF7wnKeAAAAAElFTkSuQmCC") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-pause{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAABA0lEQVRoQ+1YwQqCUBAcfWXXsLr2AXWTPXno8yVB8AP6Aa3oHI+kCDqYaawJljSe133uzO44bx0M/HEG/v1gAd9mkAyQgY4I/F8LJUlyrQFtD2AtIkcNoFEU+Z7n7QD4DfFHEVlocrVmgAUAIAOl3mILPcDgEFcUhyrUKMGUUcroc3NQRimj9XJBGaWMvvPydKN0o6/9QTdKN6rZANxj6EbpRulGuZnjYqs8BbyR8Ub2Izeys+u6yyAIDpo/ehzHM2NMDsA0xFsRmWhyfTIDWSXxCEBmrd2EYXjSHJqm6bQoii2AOYBL5Z0xgFxEVppcrQvQJO0zhgX0iXbdWWSADHRE4AZQ731AhEUeNwAAAABJRU5ErkJggg==") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-pause:hover{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAA7klEQVRoQ+2YSwrCQBBEX6HiVvxsPYDewfN7By/gD9ciQkvERQwJdBSiYs0mEDo96aruombEjy/9+P/jAj7NoBkwA28i8H8tFBFRA9oeWEo6ZgCNiDGwAYpn3TpKmmVytWbABQBmoNRbbqEHGB7iiuJYhRol2DJqGX1uDsuoZdRmLuNZSzGWUcuoZdRHSp/IylNgK2ErYSthK3FHwLcSvpXIjoLt9Jfa6TMwl3TIMBkRE2AH9BriL5KGmVyvWIltJXEfKN6tJJ0ym0bECFgDU+Ba+WZQFCdpkcnVuoBM0i5jXECXaNftZQbMwJsI3AAPN3dAQflHegAAAABJRU5ErkJggg==") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-record{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAC+UlEQVRoQ+1ZS2sTURT+zlDJYE3XSq219QHVuEjnJDT+Bff9Abqw2voAEfGxqygUqWhVFHGl/yMLu9BwByxk5SNI66ML6U7axjhHbmhgWiftncxoOiV3FcI53z3f/e65594zhIQPSnj86BBot4IdBToKRFyBnbeFlFIScVEiuYvIWC6Xe2YK8pcC7SYA4CMzH4mDQBXAqilQBDsLQLfPf9FxnF4i8kwwmypARI+Wl5dvmIBEsUmlUkNE9NaHsVCpVAZGR0d/m+A2JSAid3K53E0TkCg2pVKpz7KseR/GfKVSGYxMAMA0M1+JEpyJb6lUOm5ZVnkrAsVisaunp+esiByr1Wp3R0ZGvmifzZK4XQQWHMc52MgBpdQuAOcAXABwuB400ZTjONdaIjA7O5u2bVsnWU1EujzP+5nP5xdMVjvIJkCBD8x8VCm1G8AYgAkAAxt8Z5j5YmgCSqlTAJ4D2OcD/AXgATNfbYVEAIFPIvKKiE4D6GuCea8xX6gtpJT6DmBvECgRFRzHeROWRAABE4iWCbwHEFhkPM/L5vP5dyaz+23+KwHXdR3P854S0YG1ILSCuthNMfNM2OC1/RYENLY+ygcBnPfht6ZAA6BYLNr6dyqVokKhsGpaNQ2TWJstreXaE2aed133sojcj41AKyvdzCdAgSXLsk4MDw9/a/i4rntbRPxFNZoC/5jAV2be759DKTUJ4FZSFFi0bbs/k8noy2R9dAjEuWU2YgXkQOK3kD6BMsysi2Z9JC2Jdcw/ALzwPO+xvmcl7Rj177JVEbkO4BARjSflFDJJuW1dBxJPoCIiL4noDIB1BS0pW6j+oJmbm+uuVqvjRKQfLr0bZHnIzJf0f6HeAybahrUJqAPruhLlcnnPysqKfpXp11n/Gv62zoHAroS+AafT6QkiGrIsazKbzX7eVIHEt1US39gCkOzWYthkjNE+tuZujDGZQ8XRXn8N4KT5lLFZ6uaYPt+nwyDuvC80YdhvB9uOAu1WoaNAR4GIK/AHvdr+QAexB7EAAAAASUVORK5CYII=") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-record:hover{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAACfUlEQVRoQ+2ZSYsUQRCFvycK4nJXXEbHBdwO4kn/gv9CD467ICIutxEFkREdFUU86T/xojcPntyQcT2INw+uISFVkD1Wd2dWlU7nUHlqisiX+fJFZGREi8yHMt8/HYG5VrBToFOg4QnMPxcyM2t4KE2nT0i6EwvylwIjQOCFpE1tEPgGfI0FamC3AFgazP8IrJL0KwZzkAI3gLMxIA1ttgCPA4w3wHpJP2NwBxG4KOlcDEgTGzNbA8wEGP57vA0CU5JONtlczFwz2wY8HUbAzBYCB4CtwCVJb33OIAXmioC70LoyBsxsEXAQOApsLIhelnS6FgEzW+5BBvwA/FS+SPJFa40KBZ5L2mxmS4AJ4IjHxCzwaUnHkgmY2V7gLrAyAPwOXJN0qg6DCgIvgQfAPsDjo2pcKddLciEz+wCs6AO6W9KjVBIVBGIgahN4BvRLMjslPYlZPbT53wR2AbeBtcUmXEFPdh5U06mbd/shBBzbr/Jx4FCAX0+BEsDMFocEYrNmFcE+BD4XsXZL0oyZnQCutkagzkn3m1NBwDe/Q9L74MAuFEqUn5op8I8JvJO0elacTALnc1HAH3Njkvwx+WeYWUegTa/pwaqIgexdyIN4uyRPmqULZRXEvulPwD3gpr+zcrtGQxfzRHYG2AAczuUWiom3kc4D2RN4BdwH9gM9CS0XFyoLGu9UuN974eIFVDiuSzruH5LqgRhtU20q8kBPV8LMlhVVmVdnYwX+SMdAZVeieAF7eeltmElJr4cpkH1bJfvGVvatxdR4bMu+teZuWxtKxWncXn8I7EldtQV7vz79fp9KwZp//9CksB8F206BuVahU6BToOEJ/Ab7+KdABdTt8AAAAABJRU5ErkJggg==") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-recordStop{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAGDElEQVRoQ82ZaahVVRTHf//moKKggQawcmg0olGl0awvRoMVBRGFlQ1YQZIZqRVKmJmFgVk59EFQykYjgmajbJ7n2WiAbKKCBq0Vfznndd723Lvvve/5bMH9cvfaa63/2WuvaYteoIjYHDgEOAAYDOwIbA/4f9PvwHfAt8DbwGvAS5L8f49Ine6OCO89CTgFOBrYqU1Z3wBPAUskPdDm3i72jgBExCXAWGBQp4qTfR8CMyXd0a68tgBExEjgBmCfdhW1yP8eMFHS/S3y0xKAiNgQmA2MaUHwB8DnwNfAbwX/FsDOwG7Ani3I8ElcLOnvHG8WQET0Ax4C9msi7BHgbuAFSXaHhhQRewBDgZOBE5qwvuV1SSuayWsKICIcVZ4Atq4R8mdxKnMkfZT7UnXrEeE7dD7gO7VpDc/PwAhJrzaS3xBAROzrUFcJhVUZjhrjJX3cieHpnogYUNytUTXy/gAOlvROna5aABHhGG5f3qZmk33ztt4wvAbIBcCcBicxSNLKdK0RgNeB/RPmVcBxkp5eF8aXMiPiKODRGpd6XZJduhutBSAipgNX1Bg/tJkv9iao4u4tBzZJ5N4oaXz1v24AImIvwLE4peGSnDX7jCLC2f3JGoV7S3q//D8F8DJwULJpgiQnrz6niLgSmJYofkXSwWsBiIgRwGPNmPscARARDqGp7zu0Orz/l4kjYhlweGLk4Ebhq8oXEc6wGwH/tAhyA2C1JGfsphQRTqBvJkzLJB3ZBaBIKGkGXSqpWab013FWvacooXO21K07256WS4QRsRQ4PhHgsPrxmjsQEZOB6xKGIZJebGZVRDwOHNOJ5ZU9j0s6NqPnUJcpCc9kSVNKAA5ZQyoMn0gamDMsIj4rCrQca7P1zyT1zwmIiE+AKt9yScNUFGuuZaoxd7okR4Ccfzq997S0fleSy5acrjQ//QUMNADXH/cmu0dKcoWZE+r2MKs8I+YdSW5Dc7rcizycMI0ygKuA6ysLjiT9JX3RgtC+BLArYJet5q4JBuBG5aKKsV/ZryWt/p8BcJj2R3VjVNJsA1gEnFH5821JzZqXLtaI6LMTsNIafYsM4L6iOyoNe1FSNSI1PIj1AMCh1CG1pPsNYEkxGin/fFVSWg/VglgPAF4BDqwYs8QAFgDnVP78SJIzbJbWAwBXC9VRzgIDcLVXjfm/AP0kuR/NhbY+uwMR4e7QDf6WFaOmGYBHJbcnlh7USvPSlycQEXYdu1CVxhiARxzPJwsXSarrTbux9TEAh3qH/CqtKSU2Az5NZpsPSTqxBRdy49/SfWki60NJ2WFXTUXqwdmAsphbCJxZUeIGfltJvg8NKSIMfPcc0Mx6tpiLiK2AH4qeoxS3UNJZJYC6emicpJkZAOOAGT0EcLmkmzvQM8oz1BLAxsX8vjqBWynJ86FcJDoLGO4OC8jOMgthnrX696Qkn35Oh+dB21aYfgJ2kLSqqzCKiGuAaxNJkyRNzSlYl+sNmq2pkiZZbxWAJ8g/Aj6NksI+3kplui5AFL2271m1AvVJb1fmqXSsMhGYkhjznqSeNi0d4YsIz3/SCNXNK+omcy5ZPVKv0r2STu3Iig431dRolrRCkvuCLqoD4BlM3Th7nqTzOrSnrW0RcSdQp+tASX4gbAzAK8Ub2KwarQ8Cp0vy20CvU5FUFwN1SfRSSbemSpu9D9wCXFZjpacDoyU925sIIuIw4K5k8lCqmCWpzpbmb2QRMRc4t4GhfiOYJunLngCJiF2Aq4ELG8iZL6mRDflHvohwpnXGrSM/VM8DFkt6rh0gxRd3K3s24BBeRzMkpaP+bnzZR77iTvgLuOR29mxEDnmer7rk9dPT98CvBbNreGdSD8s8WT4i81rpjD5G0vzcR2kJQAHCs5ubgKZjwERhednrHvAa2eaPMFaSm6UstQyglBQRDm92qWwJnNXencGnZpdp67W+bQAVIKOLCz6sTUNTdjdTcyW5N2+bOgZQAeLHQLuV5/UeM6ZZPDXKfa1nqs/4QUXSG21bXdnQYwBV5RHhy2rXcmh0E+5GxOTGyCWwp34fSCovd09sX7P3X2uzPXCoLsVMAAAAAElFTkSuQmCC") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-recordStop:hover{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAHn0lEQVRoQ81ZbYxcVRl+nnvu7ErSEmtqDdKwO3e2LWJLSEuFNiofFv9AUIpfiSFqCzt31lITGgEjHxKIKVirqXbnzpZSf5BAoHwIhpiAgDVSwBaU1rZLd+7skiIJKCWVpOzOPfc1d3dn986dO3Nn9kvuz3ve87zPc857znnPe4gZ+BZvlzPMed4XDG2sBGWFAGcRXET6ZwTwIsZpgbxL4B0ID/nKf8370Hz1xE08PV33nDKACDOO/roQ15K4TASfbQWLxL9E8AKJvcWs+WQrfcO2UxKQcfSNAn8TwKVTdVzdT/oJbi/aZl+reC0JsArelRDeC8jnW3XUnL0cofC2Ys58ojl7oDkBj4hKv697CXQnA8sxCEsE3hbKh4E9hfMEOBuUNMBzkzAE6Ct9SvXgW9RJtokC0r+VDqb8pyByfgOwZ0g84mv1cqmH/Y2cpntlmUG9BgauEcHVdW3JN6RsXF3axKFGeA0FdBVGVvpi/AnAJ2NAhkHpBU3H7eabSSMV1271yVL63g0C3gigPcbmA/r+umJP28F6+HUFZPLDy4XqVQCjW2HkexJQN7s2j0+FeLRPZqd0idL3Algfg/cRRa8u5toPx/mKFZDJyyKhPgZgQU0nssfNqvxMEK8RktdZoThxM2G0qaUDG/hetC1WgOXo1wG5IGJcNkS+OpBLvTgb5CuYXfnypT75x2hICfh6yVYrEwWknfJ9BH8cJU/fX9MoFmdS1Pja2w+gLYwrkF+U7NTN4X9VM9CxUz6nlD5So5JyeTGbemEmSSZhZQrly0T4fNROa3Xe0A95tPK/SoDleH8DcGF1J97q2ipYYHP+WY6+BZCtEccHXNtcXSPA6iuvg89nGxnPuQIAlqMPAhKJfVnn2qlge588iS3H2wfgS1XxJXpFve0rbNexS9JKwzQIvxmRvsDQCt7QDSwl2ad7h8+nof4Rsdvn2uYlEwKCAwW+jp6gT7u2Wf+kBBCcqjT8RwFZkUQktp18AzS+mXQQWo73NICrqjHU0uAcGl0DlqPvAOSusIFP/+LBbNsrjYhZjvccgK9MiXylk+A5N2de0QijszBykSHGy1XRQd5RzKq7RwVkHG+/ABdPGBADbtZckkTMcjw3mIgku0btArgl28wkYViONxBQndSN/SXbXMvRZM3UQS4zuedS7nOzqVuSQfXh6afW/Kdrq+VJvmLOpxFQLaHleEH+8VgE4ErXNp9JArUcfQiQROeNcXjYtVXiGhq7i+AP1ZsM1tNy9E8A+XmowfdFZQZzHPw4CejMS6dBHYRs6OzirbTyXi+IXIjsiXPeUekX76L3cRJw6Z1ivnWWDgb17BCvXloF7yEIvjP5k4dcWzW6vEyYzmUIje+W0ZB9KFgDjwO4JqTqFdc2J3ekBtMw9wK8YCu9KETpiWAG9kJwbejnQdc2I/lQvIr/g4ADAFaF2OwNZmAPgO9P/pQ3XTu1LCn+60xpM90iNs3tQmP+yv2RUs4eWk55K8Dwnn/Kb1cdgz/gB0ls5nIGzumVBaahgwv+/AleIluZcbxuAQpV+6vvX9jM5WUuBWR6R1aJYQQhFOKPbnY55TU++FL1aDPn2irublplNpcCrILOQaQ3TMCArGXnHvmEGtHFcG2TxFPFrPm15BAqHwPY1HqpjyX9rp1KLHbFZKRv++2qazwb9R4E8N2Qk7IxohYObOapRiLSjlckYCUJbdTeTDLXtUPO9Nv0fwCYIawHXdu8riIgJh/iFtdW2xsKKOgtFNk2HQEQ3uTm1K9a9UPB+qCGOipgVUFSJ0W/W1WBE7zn5sxFSeTSee86EpdT4ImBxFpmgEcfSgglwPMl2wxmv+FnOV5QD1oYMjq5gOozB7MsTyRGVkHfCZGfVe1G4O1FW92T5GA22+MuWwK5p2Snbh8djIrz83bKvI+Ufh9AKrxT+aKsZjLT2RAxdtfWxeoMFJ7frj5dOaeqyioZR98mkLurycgR107N0ntAUuiUj0bL8YxERU1p0Sp4gxB0VEETj7lZ8xuzMcr1MGNytCBehtys2Vkd5hGE8bJeXDl7t2ub18+FiEze2yVEjS+D/qqBbNtrDQUEjWNvYLIjSlaA36sR9e2BzRyeDSHBocph/TCBmkOU4OairX4T9Vv3fcByyr8G+KMaosSAaNlQ6kn9ZSZFWIXyFyH8XbjyUMEXkR2lXKqWS2R11/CxHO9+ABtjiQryMNRWN8u3piOka5cs9rX+KQA7Fod4wM2a8RySBIyGU768TcgtdUieJrEbvjxczKX+2oqQ8REPrrLfAzAvri8h24p2Klrqj+wvTXhNO95GjqXcqp45KUcF3CfAAaEcN+H/25e2/wb2BkfmezAWUrgEgtWEfDnhtVJD0O3mzAeS6CW+UlYArMLwCoj6JYCGZcCIw8pij3vAq8dtH6g3udn2Q0nkg/amBVTA0gXveopsaea9txkCkzZynOC2Vl/rWxYwMSN5b8PoAifWtkY0Yi14CcT9rm0Gd/OWvykLqHjq7Bu5QIm6QkQuAbG85hSPUiKGIDhM8s+a+tnB7ra/t8w61GHaAsLOl+2W+WVdPpfaWCzBE63BM0fbfTlF4KQo/0RKpY71b+To4p6J73/tXyc1fevA3AAAAABJRU5ErkJggg==") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-fullscreen{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAHTElEQVRoQ+1Zb4xcVRX/nZl5u2/LrrO0EFKoBYpVaRu3u/e+3WlDZJdIRLQhNLIiEggxqURIjGmqTTAmWiRpjH4wghq+KIQYupYQEvEDmEVdyu7OfbPbzQaEYqtSwTb4Z3aV7s6b9445mzvm7XRm3oy7oanZ82ny5txzz++ec8+/S7jIiS5y/bEG4EJbcJkFpqenryqXy6cbKBUB+AeANIBuAG8AuAzAn06ePOkNDw+H9dZOTU11h2H4EwB7ALwL4FIA7wFw7O9aSxkAE9H9SqnHazGc50LGGFFQlGuW/pbNZq/aunXrYtICY8xmAD8C8HEAnUn8sf9/oLX+SiKAQqFweRRFvwewvgbzmwA+BOAkgEsAZAG85rpubseOHaVmlTHGfBTAYwA6gKU7WCaiOWaWPT9mv1eLO6S1/mYiAGPMddYtUtXMRPRVx3F+FkXRup07d/7FGDMEYExrHTSrfIVvfHx8Uy6XO22MWae1fu/IkSPpbdu2pRcWFmpakYgeVEo92gyAdQCKADI1HZL581rrp4lIfHPV6Pjx45cEQfCvBgL3a62/nwhgZmbm0lKp9OeYf56rMqmc9v4oikb6+/v/uhoIGigvAUGChdBBrfXhRAD5fL6XiCZsZDhHRAeY+VBVlIiYeTQMw725XG5uJSDqKc/M9xDR1wFsF/lEdKdS6ulEABMTExvS6fQMgCsBhPPz825nZ+dnieinANrjApj5mSAI7t61a9fC/+JSDZS/t62t7WgQBH+0IVoA7GsqjDIz+b4vCyXcnSuXy9fmcrkz+Xz+TgB3ENHeqlN43HXdB7dv3x60AqKR8p7nPXHixIn2YrEo7itRipn5057n/SrRAhbA320eEAGbtdbvyvfJycn16XR6BIBEnzg9PD8//63BwcGwGRBJylcEG2MkbEtUFAS3NgVAmI0xkl23Wt/bppR6rSK0UChcGUXRcwBUFYjDWuuDSffBHpBk82XEzPfKyVc+Wlf+HQDJGQLgDs/zjiZawJrudQBXAzirlNpIRMs2nJiY+HA6nRYQH4kJ7NZaS/htSBLlgiB4jJnFJZeoWnn7jYwxDxCRJK/LmXnI87yXEgHEzHs2m81urlce5PP5fiL6BYAPAmhrJZmNjo5murq6ngdwcy3lK0rKYc7Nze1n5gNE9Cml1HgiAGviguu6A0nlge/7N83Nzf12aGionHTy1f+Pjo5KdBuOu00tGZKpmfmHAJ5oygJjY2Nd3d3di0nKt6rwSvjFK6Iocnp7e/+ZaIGVbHSh1q51ZBfq5Cv7rllgzQIrPIGLwoUkqdVLqssASCKbnp6+ure3VyrSRGLmVHWpkbioRYbx8fErHMcZbKofsGMVKRHu01pLc1+XJMGUSqXPEdGTrZQSIlAycVdX1+FSqXRw9+7dUvXWJFE+k8lI53e71vrZphKZMeYPMvvJZDK3SfNea1GsZpoH8EWl1NFmLTE7O9u2sLDwNoANAA65rvtwrcw/NTV1TRiGp2w/8AXP836eCMAWWicAXENEvymXy/sGBgakvP4v1ajnzzDzl7TWzyX1A1KquK4r7hkf2xxQSn2vem2sHwijKLqlv7//xUQAtpyW6YBMJUJm3hNvJBo0I3XL3fim1kVfAHB9/Dsz3+95nkztlsgClYr1BgBRKpW6oa+v75VEAMJgjDkrNbj8jndCzXZSSXfU930l/bRtWyvsC+KKAEYq98kYIzy3W4abtNajiQCsBQTAByzzsNZ6ZLWUrygwOTl5YyqVEgXjriQjzVcdx9nb09Nz1vf9F5j5EzK5Y+ZBz/NeTgRw7Nixjra2NpkLycBW5jK3OY7zUq2hU6NmJMkK8r/v+3uYWXrsZdMOAM86jnN3EAS/BjAgjgDgy1rrHycCsBNkCZ9X2DtwIxGNVS9cqfLWPalQKNzFzN8GcK2dQCxtRUTSxPQx827L+13P876WCMA27W8BOG82Wlm8GsrHZNHIyEhqy5YtvwTwyXqWI6KHlFKPJAKwYVSiULVZl9aupvJxZexIU+J8TRBE9B2l1DcSAdjLKneg1nh9fzabfbRYLG4qlUpvd3R0bCqXy7tOnTr1VKOHjVqb2jC5j4gmwzAM0+l0OgzDVCqVkvGhuO8yYuZHPM97KBGA7/vXM/O0TBpqMMvo+x17waWGkhLgMrGK1vrJpCRWkRcrD+STvCvIXiJLhgNdddzoAa21vCmcR8uKOWPMRgBSPrRSpcpY8T6l1FNJ0UfeBTKZjNyxlqg60cUXL1PUupBsIO9XMkqX96v4mFvcS0Z+Mg86TUTtzCxvCh1E9BmllPxXk+zrzxQRzTBzJxG5zCzuIjJ32DG+WCOuk1hFqoKlfNSMBWSU5zDzFnEPInqLmSWpbZANARzRWr8jQHt6ev4tAuX34uLi+iiKiknjdskzlepzdna2s729PSgWi24YhuszmYxn99sYRdHSGx0RnUmlUqf7+vqO1zuYVlylJbO/X8xrAN6vk15zoQt90v+3FvgPXUePXrKTg9MAAAAASUVORK5CYII=") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-fullscreen:hover{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAFvklEQVRoQ+2ZaaiVVRSGn9fS0iabCNO0eSaosAmplKJRxMiygSQCixQipBKMoDRBon5EI/0pQ8JuRQTVj4omo+FH04/muVum2GCDWVYr3ss+8t3vfud8+3guXi6cBYc7nD2sd6+11/BuMcxFw1x/ugCG2oL9LBAR44HeFkr9B/wMbAOMBT4B9gC+BiZL+rfZ3Ijw+PuB6cA6YFdgAzAy/V41NQB/rpL0QNWAAS4UEVbQm+XKj8B4SX/VTYiIicC9wMnAjnXjC9/fKemaWgARsSfwEbBbxeDPgAOBL4AdgF2AD4ETJP2dq0xEHArcA4yGvjv4D/Br2vOo9P/ycosl3ZQD4IDkFiMqBl8LPASMkfRdREwFVknalKt8Y1xETJDUGxFea0NE2CX9aWbF+ZLuzgEwBlgPbNtEqYuAlZLsl4MmEWGL/t5iwQWS7sgB4Iv1TcE//yyZ1Ke9AOiR9MNgIGihvAOCrWJZKGlZDoCjgTdTZLDy1wGLS1HCkehF4DxJ9t0tlhbKXwbcAByRFp8taWUOgN2B94G9AZ/A9sD5wIPAdqUFngAuBTZuiUu1UH4O8DjwVQrR3nZuVhiNCEcFT3S4swX2k7QmImYDs3zqJRCOzfOBTe2AaKW8pOUR4cPy/tbH9+0cSc/mWMATfkp5wAtMlLQuAXNo7QEcfYqyBLjZFssBUad8IVI5bDsqWs7OAuCREeHselCaeLgkx/o+iQi71lPAsSUQyyQtrLsM6SB8h8oyxydf2Meu/CrgnGGZJcluNUDKpYRN9zEwCVgLjJPUb8OIODiBOKSw2lhJDr8tJSIc5ZzE7JIN6ad8OijrNQ9w8nJynSrppRwAjXhs5e0+lYklIo4DHgP2AUa1k8wiwjnmGeB0YIDyBSv4MB2yHQnPkvRGDgAjfxs4vq48iIhpwCuSXAq0JRHh6HZB0W2qFnCmBu4CludaYCen8zrl29K2w8Hp0o+U9EutBTrca0imdzuyITn2wqZdC3Qt0OEJDAsXcnHXLKmWSwn/PUmSK9JaiYgR5VKjdlKbAyJiL+DU3H7AtIpLhMslublvKinBXAg83E4pkWodZ2J3WO60XPVWSlLend9MSU9mJbKI+DxxPzPcvDdJ8Y2a6TfgCjcguZaIiFHA94ArTnd7S6oyf0TsC3yZ+oFLJD1SCyAVWp8Cnvxy6oRcXm+Winp+DXClK9S6fiAiXKrYPYu0jYu128tzI6LRD7gzPFPS8zkAXAGaHXDF6InTi41Ei2akablbAm8XfQ44rKSMmTezdn2SgLpinQK4nJ8i6fVaAGmyS2nX4JbNnVBuJ1V3RyPCzZD7abetDdmYXNFsRx/PFBEeMzMNmCbJRMIAqWpoDGDnNNIlb89gKV844VMSiKIrmdL8ILEdayPCljotMXeOQq/lADDdZ17IhK1daAbgTqiKdGrajNRZIZ2wSV732GW2w9HGbMcL7kvSJb5a0n05AEzqOnw69hqAT2pVxcSOlE8AbP2LgVvMfiQGorGVm5hjgJPSP26TdH0OADft3wJV3GhjfsfKF1zJILzX08AZLSy3SNLSHACOPnaXslkHXfmiMqnZd5xvBuJWSTfmAHCC8h2ootfdYJshnpASkX+eCKxo9bBRtWkKk3OBt5KrmgO1JUwf2n3LslTSohwAjs/vmmmoGGyGYnW64Da9SwBfdlOBLieyGOtCeeAt/K7gvbyWyQEnuiqZJ8l0zAAph9FxgMuHdqpUx23XTivqoo/fBdIdqxta/r5foit+WQZgF/IlNgFlxfx+VaS57V5O8eaD/Jbmu2Lqw+H3XEn+rlLS6887iTz285ILOruL1zwyrWFrFHWyVXwv+/JRjgVM5Vnp/ZN7GIyTmgsvb/iopNVObJL+8IIpyfnOrK+j2yNidKP6jAiD8CF5Xc+fnA7PXtB4o3Od1SvpvWYH046rtGv2rTK+C2CrHHOLTboW6FqgwxP4Hz4mJ0+J869tAAAAAElFTkSuQmCC") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-fullscreenExit{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAADd0lEQVRoQ+2Zz2sdVRTHv+fJBDW6anDVXen6wZszYxYBiYgtFGst3VSDunKjpS0GpUlqfjVpsVVs6aaL0or4YxMVFCJZ2ZLdPUP+gq5bQnTxtNAkfTnlhnnlkmQy9yV9780rudt77tzv5/y4v4bQ4Y06XD/2ANodwec/AiJygJnvtdvTWfPnRkBEJAiCN8rl8kMfiPn5+Ve7u7v3rays0Orq6lJfX99/PuN2auMDoAD+BvA2M6/mTWSMOUtE48D6AjHGzN/kjdlNvy+AnWOOmQ/lTSYiEwDOWzsimgrDcCRvzG76GwGw8/zJzO9sN6GInAMwbW1UdSSKoqndCMwb6wNwGsB39Q+p6h/M/C4R2dTa1AoHYBWKyCkA1+pqiWi2Wq0e7e/vf7yRoJAAKcQggMtuJKIoOtoxACnE0/xOi/SXMAxPuhCFjUBdpIjYVWXSEf0TM3/g9BeriDMKdSPEz8z8vrU1xgwT0YXCrEJZy1iSJKOqOub0/8jMA0mSfKKqNwoPkHp7ioiGHIhRIvpHVa93BEBa2JcAfOlALAHo6RgAKzRJkk9V1S6xL7kpV4idOM31taxaIKJHqmpPnMMA9hcOQES2PDJkAT1XAAC+ZebPfWB3auNzmLObVsNRUNUXVHUujuM7OxXnMy4XwOcj29mIyOuq+lapVGrYCelKpkEQ3CyXy4tbzdN0AGPMxr2iYZ+sra3FcRybtgCIiK2BKw2rdgaUSqWoUqlIkQAepFDdAF7cBq5ERI9rtdr1OI7tmE2t6SmUEYFHAEaexYW/1QC2EF+ru5GIvg7D0D2GNJxprQY4o6qv1I/b6SpzOYqiLxpWng5oOQAzXxWRWwA+dkRfYOb1p5hGW6sBJpn5KytSRG4D+KguWFXHoyhy7xdeLC0F2ChSRL4H8OFuINoKYIUbY34gogHH3eeZef1K6tPaDpCm068A3nMEDzHzxY4BUNWSiPxORO6z5aDPPlGICNQ9bYyZIaLjjudzIQoFkKbTbwCO+UI0HcB9J/LdeY0xs0R02IGYYObRrWqiFQCfEZEtSHsfmGZm+4qxbbM/hQD8BeBNa0hEM2EYnmgLgP3lFARBT1dXly4vL//b29tbzQNIU+llAHeJaLFSqRzJes5vegR8xGbZLCwsHKzVav8z8/0sm0ID+MDvAfh4qZk2exFopnd9vv0ELrXBQO7fD10AAAAASUVORK5CYII=") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-fullscreenExit:hover{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAC/ElEQVRoQ+2Zy49NQRCHvx+ReK6IlZ34E7CUiCAR4xEbTLCyQRATYswwb2IQZDYWgojHZpCQECts+ResiQwLj0RClNSkb9Lu3HtPz7mZc8+V6eXt6tP1VVV3VdcVbT7U5vozC9BqD/7/HjCzlZLet9rS9fbP9ICZvQPWSfqRAmFmS4ClMHm+JiR9S1mXVyYFwIBXwEZJv7I2MrPjQH8A6JN0OWtNM/OpAL7HS0mbsjYzswGgN8gNS+rJWtPM/HQAfJ9nkrY22tDMTgMjQaZH0nAzCmatTQE4ClyNPvQU2CbJQ2vKKB2Aa2hmR4DrkbbPgQ5Jv6sJSgkQILqA0dgTkjraBiBAxPHtPz2UtDuGKK0HKkqamd8qg5HS9yXtjebLdYjrHNRqiAeS9gQvnQGGSnML1bvGzOwc0BfN35PUaWYHgRulBwjW9ju+O4JwqM/AWFsABIgLwKkIYgJY1jYAAeJQuGIXVIVcKTKxh8WfBin9J+AVpx/eFWUEqFkyNACKp0rhgWYArkg6kQibSyylmPOklQdibijBX+fSLHFRJkDid+qKmdlaYENOI0zeEcBNSZ9qbVIEQHWuyGOTNZLetgrAz8ClPFpHa1ZL8rf5lFGEB2oBfAxQi4D5DeDmAP7mGJPka0oD4LnDr9imH/xFe8AP4vLIjBclxWXItCOtaIBjwOKo3HaFRyWdnLbmYUHhAJKumdkt4ECk9JCkSitmWixFAwxKOjt5uZvdBvZH2vZLit8XSSBFA/yjpJndAfY1A9FSgOCJu0BnBNErqfIkzfRCywECxCNgR6Rtt6TzmdqHBmyKXG4ZM4sTWc04NzNPWE+AuG3ZlZInSuGBinXMbBzYGVkrE6JUACGcHgPbUyGKAIj7REmZ18y897o5ghiQ5E/bltRChwE/kF7Xj0jyLkbDYWbzgBfA+iA4LmlXqwD8LydvszjAF0lfswBCKC0E3gBeP22p186f8RBKUbaejJmtAr5L+lBPptQAKfCzAClWmkmZWQ/MpHVTvv0X9iFAQGQyevIAAAAASUVORK5CYII=") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-audio{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAACrUlEQVRoQ+2ZPYgTURCAZzbBXJnCeL2Cnb87b9MEtPBUrrMQFAtrtT5/ClGs9LBWWz0RtbBUFCF4oJDsbO68wsLA2YqQSmLlvpEHu7IuMdlLcus+yUKKhJfZ+ebnvZl5CJY/aLn+MAP41x7M1QPMfFtr/crzvHfTAs8FoNPp1LTWzwHgqIg0lFLvrQHwfX8BER8DwC6jNCIecF13wwoA3/dvIuKNpLJa60Oe560XGoCZd4rICiKeTCtaeABmPg4AJmRqg6xcaABmvg4At4aFRyEBhoVM4UMoCplHADCfJTEL5YEsIVNID5iQAYCHALCYxeq5b6PMfF5EBAAEESthGK7W6/XPRpFWq7W3VCqtZg2ZcT3g+/6i4zjzIlLSWn/yPO/DIGMNLCWY2Sj/+xGRK0qpZfNDEASnROTFVi0fr8+aA8z8Ld6KEfGt67oLYwMAwEUium8EREn7OgeAjwCwPyo/nrque3YSgAtE9GDaAM1mc65arc4Zuf1+P2w0Gt9jJZl5DQAORt+fENG5wgEw8zUAMB/zbBBRwyqAIAjuiMjlSOlNItpjFUCqWl0josMzgChR/9hGAWBbknjmAdPhDdqa0gfZzAMJKyVP4v8hhJYRcSni+0JEu63ahZj5anyQici6UuqIVQDdbrfS6/UqRulyufyTiH5sF8AlIro37VpoWEHIzGZ2tM+sEZFnSqkzk9RCS0R01wjIsZz+mug53hDRia0AnI4bGgDYISItz/M2jYC8Gpp2u30MEWuO4zha665Sqp0ZYFStX/iWchRAItFGzoHSsrJ2ZFl1mHg6bfVYJeGJv85CC++BpIJZ5kSFC6G0ha0e7mYJqcJ7IOkRay84UhD2XjHFIFZf8iW9YcYoYRi+tO6aNeupOs66iU/icV46zf/MAKZpzXFk/QL+JG1PUPhRiQAAAABJRU5ErkJggg==") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-audio:hover{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAACSElEQVRoQ+2Zu4sUQRCHf5+C+gf4yBXMfMYHGvjCzEBQDIzV+HwEohipGKupD0QNDE8UEwUFTe68wEDhTMVUMFJ+0tArzbjs9u3Ojt0wBR0M9MzUV1XdXVWNKhcq1189wP/2YKcesH1d0nPgdVvgnQDY3iTpqaT9kuaAt9UA2D4o6aGkzVHpXcByFQC2r0q60lB2D7BUNIDtjZIeSDoyRNGyAWwfiiET4n6YlAtg+7Kka2PCozyAMSHT5CkLIIbMfUlbMhdmOQCZIVOeB2LI3JN0NNPq6bTZe8D2aUmOY72kN8DnoIXt7eF5FSEzkQdsB+OEsFwr6RPwbpixhqYStoPyqVwAbkaAY5KeTWD5wStZHrD9XdJgK34FhBP9H8kFOAvciQBhn3/RAcBHSTvjfx4DJ6cBOAPcbRvA9gZJYQT5DfwYKGl7UdLu+PwIOFUiwCVJYQRZBuZqA7gh6XxUegXYVhtAmq0uAnt7gLhQm9vorBZx74Hcc6D3QLKH/z2JGyVnlYs4pCfzEe4rsLW2XehicpAtAftqAwiZbhhBfgE/ZwVwDrjddi40KiG0HXpHO+KcJ8CJaXKheeBWBOgqnf6W1BwvgcOrATieFDTrJL0HViJAVwXNgVgPrJH0BfiQDTDKtREiNK7KLSnHASQLLacP1PxcVkWWq8PU3emq2yqJJ0b1Qsv2QKpdZp+orBBqmrfq5m5mSJXtgUZI1XnB0YCo94opCal6L/ka3ghtlIXqrllzT9VJ5k19Ek/y0zbf6QHatOYk3/oDujC8QMWgjf4AAAAASUVORK5CYII=") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-mute{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAKYklEQVRoQ+1Z+3NV1Rld397nXJIbIGBARTQgohGNQZJLEtFSMmpfan10aJ1OZzqd/jOd/g3t9AetD2KLCiigNFUgj/tIQoh1SqBRwVqNYgp53XvP2V9nped0Lpebl/LQmZ4ZZpjkZJ+99voe61tb8C1/5Fu+f/wfwPVm8DIG+vv7H1bVWufcp9baUefcWCqVKi5lo11dXV5NTc06EblPRNoAtABYqapD1tq9zrmelpaWaRHRpaxb6d3LAGSz2d+IyAbn3FljTG+xWEy3t7efW+yHuru7q621t3med7+qPgigGcCdAPIAuowxzyUSiaONjY2Fxa4533uVABwEsA3ARQDHAez1fb9769atn823kKrKyZMnVxUKhdtFJKWq3wWQAnAzgBoAH6vqQWvtH8nAUlmd69uXAcjlci+q6sMA1gL4BMB+Vd2fSCR6K4HYs2eP3bRp0zJjDN/f7Jzjphk2PPkN0YcDACOqekhVO5PJZPZqMvBLAI8BeATAagBnARwRkT97ntdXDmJ4eHj59PT0emPMVufcA9y8iNwBoA6AjQCEAE5dEwDpdPo2EXlQRJ4G8B0A6yImDqjqvnImstnsOlVtFZHvA9gJ4C4AfhnlLAJnABxW1T3V1dWZq8aAqppMJrM+AvE4gB8CuKGUCd/3jzU1NX3JuB8cHNwchuGjBKyq7QCWV4jXawcg/ng6nb7ZWrtTVX8C4CEAtxCEiLzBZAzD8ERNTc1YoVBY6ZxjtXkyYoDvxaETL3ftAfDLvb29t1prufnHohBZQxCqmmVJVNVjQRB8VF1dXeece0hVfxAlcD1wSZe/dgCy2Wy97/sz1topAIWpqambRKTDGPOsqu4AUAvgPICMiBxU1SMzMzMfJJPJG1SVYB+P6n8pE6xCpxebA8PDw4mJiYkqHqLnedPzldxKZfRXqvqliJwtFosjXEBVG0Xkp9wcgMYoLr4EMAjgDRE5PD09PVpTU1MXhiHrP6sY8+G2kjIaJ/HLCyXxiRMnbiwWi7cqk0zkbCqV+nzRfSCbzXay6ojISQDHVq5c+Y+JiYl1zrmnnHNPiwjre5yoFwAwnN6MQfi+v8bzvF0EoaqsYgw7wyokIm86515aCEAul9vinNtujHFBEKTb2tpOLQXApwA+EJHjzrnX8/l8jicbBAE3z4S+P+qs8ZrjERMHABxiOFVVVd2oqruMMT9WVTY2gjgXFYCXAfTNFxa5XI7sMRT57Nu+fXt6KQAosNj2uwB0iki3tXZ1GIbPAOA/hlCybMF/A8gxnBjnQRB86Ps+QbAZMrG3RlqIDfGlCxcu9OzatcsNDg5S4NWqqm+tpbgbb2pqmh4YGHjIOfczfoPvt7S0HF0qgDEROaKqPK1jUeKyzj8jIk1lDJQzsb8ExHrn3E4RmZUmqsqceWV0dLS3oaGhKp/P3yMid3N9Y8xnVKuFQoHgm0WEADwRefGrAPhYRP5CBoIg6BaRWmstw4EMUOhValYEEjNxwDl3yPf9j4MguMkYs9M5x80yPA9fvHhxqKamZo21ltKd+ULBNyoiB/L5fMbzvDuMMVQCy5xzf2ptbe1eKgPUP7MACoVCj+d5q4wxTwCIc2DFPMqUOdEP4HWWWM/zzhWLRXb2LSISOOeGkskkf7YhyitulKLvfRF5XkQOOeduFpEnVLVaRF5taWnpXSqAD6NG1VksFnuXCIDfIog0O7Yx5kgYhp8ZYyipYa39Ynx8fKa2trbBOccDeRbA7QCGVfX3IkLgdSLCUsxcey2VSvVdawD8XtwnWJ2YR2dqa2svnjt3jsrUiwAwJH8OYBMBAPgdN/xNAVCaE2855w4mk8m/UYVGM8RG6iwRoXznxDYLwDm3T0TWiAibZlJEXrseIVTKeJwTrzKcEonEaYIYGhpanc/nycCvRaRRVf8uIn+IBiiG0DcGAMF8QW3IzYVheKitrW2UP0yn048YY34BoDV655UwDF83xqyKc4A5cb0ZiNn4XFXfBfCC53lHtm3bNp7NZjm5dQCgHE+q6lFjzEHn3IqIgerrmcSVCgfdjTe5Kd/3M9PT0zO+76+PbBdK8DOq2kPpEZXRqq+aAx+xjLIPhGHYW9LIWPYoC+brA/O0CLhosnuHGkdV+4wxDC+OpRxlLyQSidGZmZnN1tonnXMJ+kjNzc0EVfGpZKtQC/2LjYzzK0VdJCWeiqrGffN04rm+w3mAQ00imtZo0bxFJpxzRycnJ8fr6uqqwzBU3/enpqamUiKyW0SoYjtTqRTL8JIA0E75K4A9xpjjFFwAqIXIAAGUi7n5Tp2/m4yaG4f9G6OXeUizboeI9J4+ffrT3bt3kyFkMpkHjDEssRKG4StLlRKcxCglqAD3MoRokVhr2fJ3A6CYK3cdFgLAuYGHwpLqAWDcU/9QwB02xuwLw/Dd1tZWgmJ1utcY8wgNBpbelpaWoaUwMCAiH3Hudc4dcc4Ne55H04oDCk+ldKBZaOPx78kAxdowLUsRIQBWn1nLRkTeJtu+7x+n28GJrFAo3Gmttc65kVQqRfCLC6FMJvPbSDWeofCanJz854oVK2hwcd79UVTyKL4Yz4t9ZiJfiALxqIgkVPVRAN8r8Z32s+aLSF8ikaCqTUxOTi6bmpqa7Ojo4N8vDkB/fz/dNYbRuLX2cw4YuVyuyhhzZxiG7SLCmZdT2UYArNOLeWjkciamOfaqqn5ijGmKGOXAE7sdbxtj9pY6gP8di+d2sS+rQl1dXVVr1651Y2NjrqOjg9UDXKSnp2d1IpHgpptVdbuI0DKnilwVzbzzAZm1VTgTR0NSfxAEN/i+z1mA1S2eCRgqByImepubm8cWOp1F39Awod57771ksVjkgH+3qpIpzrtbANy0QGLPAqC85ogYy2P6Tr7vP6iqnDViB5DNjjlBWdHb1tbGPjHns2gA8QpUkhs3blxrjOHGyQJ1zD2RhcIGV2nNS4ytVCrVIyKzJTM2zyIvlt4qq9MsE5W82HIkSwYQh1Qul1sJoF5EtkbOA9mgLGbFKl/3EgATExN9peHZ19e3ng5gpH8uYWIuVzwG8pUAxH+czWbpJqwPw/DeyMjaDoD/Z7MqrVIEMOvMOef2VLofKGMidsU5Qx+iig2CoGf58uXjjY2NE6UsfC0AXIgh1dDQQEeOecEEZ25QL3HKihveggCYY319fbdUYIJ9gobYc6p6prW1lU32f8/XBhCvxAGF10uqui262GNusGpRhvDhnM24fkFE0nMZW2TC8zzmAjs/c4ylukdVOa29H88SVySEyhMqm81yBKSpu4VMiMgOVaX0YCOcva4yxjw/3x0ZmcjlcrxnI5Ps+mtUdYTgwzD8sLwqXTEGSqtUfX09PR/aKIxldvAGOt0A3nHOvRwEwfEdO3ZMz1UbR0ZGlp0/f/4WEam31vL+4by19hQ7dPnNzhUHEG9qYGBgVRAEd0UNj2YYWThjjHmrUChk2tvbKfDmfHjX7Pt+te/7nAnYUKcqhd1VA8Dkrq+vXxcxQdnAewbOAb1BEAwtBCAq16azs3N2j5TalSTFVQMw3+leyd996wH8BxA4v3x6wGifAAAAAElFTkSuQmCC") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-mute:hover{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAHsUlEQVRoQ+2Z969VVRCFv7H33nvvvfcSe2+xxJgY4z9j/Bs0/mABFQXBhl1sgNjQSCyoiL2BDaxs873MJsfDuZd7gfeQxJ3cvAfv3HP22rNmzZo5wRq+Yg3fP/8DWN0RXCYCpZSzgM2Br4GPgW8j4s9hNlpKWQfYETgUOB44GtgMmA1MBF4BFkdEGea+Xdd2AbgF2B2YD0wHZkbEZ4M+qJSyIbArcARwMnAUsC/wO/AscCfwQkT8Meg9+13XBeBx4EjgZ+ClPLGXI+KbfjcqpXivLYA9gWOA0/PnDsDGwOeA977bCAwb1V7P7gIwDpBG2wJfAg/nZ3oXiFLK2sD6ef0+uWlp48kbSddfwAfAVOB+YNZoRuBG4CLgbGDLpNLTwIPAjDaIUsomwM7A4cCJyfm9ga0Bwbn+Bt4fKwDyV+5eAZyayWgkHgGmmBdNEKUUk/U44DzgNGA/YN1WyBWBucATwH3Aq6MZgbXyRAVxMXABsFUrEi9GxILkvbQ5JwGfABiR9ho7APXJpRSTzxO9CjgF2ClBPJrJ+JYSm/Io2Mvyeq+r1Km3G3sAPrmUsktu3pyQItskiFkpiS8CnybfBXl+5sBu8K8qP3YASik+/DdgEaBWbw+cCVwHnJRF7gd5nJEwwT9JmglC2hmRZiRUoQ8HzYFSynrABhk+C17PQtolozcBC/Kklb7FwCHANbk5f3d5zZuAlDI5rdoqj/pvxMwHBaHKaE3ie5eXxKWU7QCjb6WeHxHfDVMH1GlV521AinyUSnR5Jqr6XhP1JzUdeKwBQpqdkSBUMf+tMAjA68YPAOBA4FhgSToBJbhzdUVADyQlrMKTgdfyZJVVE1qLYGWta2FGQpm1UPldT1AQl2ZhE4R2xGgZAetJT1qUUoyeVDQCUyJi5jAA/JJlX99iNF7OgnYl4EcKbdS64Y8JtNJpXoKwGJrYFjm9kPliBDRznq4GT+No3ZCqHoY/zaVr8xnjI+KFYQEojz7M05JGPsQICOCwVgTakdB6mBOCsEIrxdWamDMT0iSapAcBB+T99Vq6Vb8nTQWgqx23IgCMwDONCAhAOghAo9dVrARSI1Hp5H1UMUG4WekpODcqrQQm1aw5ioDfU920Ih6YHuuBiJAFA+fASOY3ABhuXeYljRzYtNcNkwavZ/4YRblvJExM5dTN+38aPTfpx9/nAHdlHgnI52nNJ0WEtn4oAIax5oBfHgaAD5LLJp72WRDSoyb+91ln9s8Dsb5owd8Bbk/gyrFSbK49FBEzxhpAs05IC/NIGbXH0JnKbQFIyeuBvRLAbW44VW+1A2jmxJMZjXd1odlD7JER0L7bsRkBAeh4zQ9ltEZgzCnUjLh0MicmJZ0+TBD2Gkbg5pTm94A7snmSQv8ZAIKR956iEjs1IlQczaJ14obsJ7xGibV4mnOVQpNXRxJ35Zx+Zhpwj5GIiIWlFOVSo6j5ky4WLBNflTMCqtBqS+IuEMqnfshEVe91vUqsYxddsImubJsDyqjFTgBD54AevymjtZDphbQF/epAnxIxYh+sMc9nsiqPUse2VOeqOZRednk2SNrqiREhqKHqwFdZyOxfNXUC0I0KwGFVr0rc6zkWMM2bG7Jbsy6oTEZC2pjo0sUiah/iWObqdLH3R4QyPBQA7fRz2YBXANWNCqBt5vqdun/7NTepadOpujykOu2QItoMI+RyuuFh6ZYnDGslPAHD7Mk4BvTmypoAPBXNXHvqsDwAUsND8aQtYvJeu2Ak9EZq/7SIEJTqdHCOdewjTHjtx8AReCP7XBsVT8gC45BLWfNUmg3N8jZe/24E5Lb38nAEoPrIfYE9VaOd0w6jZHGTbh9EhNcMDODWDKeKIPIvsh/Qo1+Ykqf5ks+DLtXG++lwjazfdRRzbgOENcIaYGLrar1GN/prRPj9gQHIP2lkuNVuGwzlzBOxU7LntSvTCph4gyyHAwLQF1mRPVGpaERteOq0w0hI26UTQGdP/abYXS2lmzWZlkSE6iEnvc7S76alkP2q2q2LtGrK1X6rjlWsATZJWguHZfYCqlvtCeoE0Eg4AbSx6rsGfkNTSnGTqo+8tYsyUsqdPt+mpV9iVwBWWVvEEXuccyersEWrTgAtdkZipHOLCOtEzzUwgHqHdJImtRs3Cs5F7bYsRBa4rnu2B1uO10ckszE8U+Xs3FSnnrPYNpKhATQoZUNu+bcyGwk/5ong2vdtA5DjTXqqSnUo1o5E51S8AlkhAI1oSBsfrm6b4OaGvyuDTZUSQHMyt8z7gVYk6lTc4uaoRoXSTiyMiF+aUVgpABkNtdpCZ16Y4OaGUbHLqnkxCABzzHFkOxLSyeT31dTciLCOLF0rDaARDVVKVXJq4Rsac0PV0ke57LOVUe207906B1sZCXPBnDDHlGpP325tTu0lVgmF2glVSlGlPEUT3Eg4DFbvBVdfVzl56PmOLNXOg/D7RtQa4YxW8PPaqrTKItBSKR8qCLksJWzgLWbaaOvASxFhgexcpRQrsAehSCgWTsOdj/7YfrOzygE0gFjgfN0kDaSVUbAaa6N9xaTB67nyXbP0UQxUrEVdtBtNACa3Rc9ISCOLne5Tdzt7eQBSIEzsukedwTIvxkcNQL/TXZV/W+MB/AMANfVPjBGemwAAAABJRU5ErkJggg==") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-text{font-size:14px;width:30px}.jessibuca-container .jessibuca-speed{font-size:14px;color:#fff}.jessibuca-container .jessibuca-quality-menu-list{position:absolute;left:50%;bottom:100%;visibility:hidden;opacity:0;transform:translateX(-50%);transition:visibility .3s,opacity .3s;background-color:rgba(0,0,0,.5);border-radius:4px}.jessibuca-container .jessibuca-quality-menu-list.jessibuca-quality-menu-shown{visibility:visible;opacity:1}.jessibuca-container .icon-title-tips{pointer-events:none;position:absolute;left:50%;bottom:100%;visibility:hidden;opacity:0;transform:translateX(-50%);transition:visibility .3s ease 0s,opacity .3s ease 0s;background-color:rgba(0,0,0,.5);border-radius:4px}.jessibuca-container .icon-title{display:inline-block;padding:5px 10px;font-size:12px;white-space:nowrap;color:#fff}.jessibuca-container .jessibuca-quality-menu{padding:8px 0}.jessibuca-container .jessibuca-quality-menu-item{display:block;height:25px;margin:0;padding:0 10px;cursor:pointer;font-size:14px;text-align:center;width:50px;color:hsla(0,0%,100%,.5);transition:color .3s,background-color .3s}.jessibuca-container .jessibuca-quality-menu-item:hover{background-color:hsla(0,0%,100%,.2)}.jessibuca-container .jessibuca-quality-menu-item:focus{outline:none}.jessibuca-container .jessibuca-quality-menu-item.jessibuca-quality-menu-item-active{color:#2298fc}.jessibuca-container .jessibuca-volume-panel-wrap{position:absolute;left:50%;bottom:100%;visibility:hidden;opacity:0;transform:translateX(-50%) translateY(22%);transition:visibility .3s,opacity .3s;background-color:rgba(0,0,0,.5);border-radius:4px;height:120px;width:50px;overflow:hidden}.jessibuca-container .jessibuca-volume-panel-wrap.jessibuca-volume-panel-wrap-show{visibility:visible;opacity:1}.jessibuca-container .jessibuca-volume-panel{cursor:pointer;position:absolute;top:21px;height:60px;width:50px;overflow:hidden}.jessibuca-container .jessibuca-volume-panel-text{position:absolute;left:0;top:0;width:50px;height:20px;line-height:20px;text-align:center;color:#fff;font-size:12px}.jessibuca-container .jessibuca-volume-panel-handle{position:absolute;top:48px;left:50%;width:12px;height:12px;border-radius:12px;margin-left:-6px;background:#fff}.jessibuca-container .jessibuca-volume-panel-handle:before{bottom:-54px;background:#fff}.jessibuca-container .jessibuca-volume-panel-handle:after{bottom:6px;background:hsla(0,0%,100%,.2)}.jessibuca-container .jessibuca-volume-panel-handle:after,.jessibuca-container .jessibuca-volume-panel-handle:before{content:"";position:absolute;display:block;left:50%;width:3px;margin-left:-1px;height:60px}.jessibuca-container.jessibuca-fullscreen-web .jessibuca-controls{width:100vh}.jessibuca-container.jessibuca-fullscreen-web .jessibuca-play-big:after{transform:translate(-50%,-50%) rotate(270deg)}.jessibuca-container.jessibuca-fullscreen-web .jessibuca-loading{flex-direction:row}.jessibuca-container.jessibuca-fullscreen-web .jessibuca-loading-text{transform:rotate(270deg)}');class mt{constructor(e){var t;this.player=e,((e,t)=>{e._opt.hasControl&&e._opt.controlAutoHide?e.$container.classList.add("jessibuca-controls-show-auto-hide"):e.$container.classList.add("jessibuca-controls-show");const i=e._opt,o=i.operateBtns;e.$container.insertAdjacentHTML("beforeend",`\n ${i.background?`
    `:""}\n
    \n ${ht.loading}\n ${i.loadingText?`
    ${i.loadingText}
    `:""}\n
    \n ${i.hasControl&&o.play?'
    ':""}\n ${i.hasControl?`\n
    \n
    \n
    00:00:01
    \n
    ${ht.recordStop}
    \n
    \n `:""}\n ${i.hasControl?`\n
    \n
    \n
    \n ${i.showBandwidth?'
    ':""}\n
    \n
    \n ${o.audio?`\n
    \n ${ht.audio}\n ${ht.mute}\n
    \n
    \n
    \n
    \n
    \n
    \n
    \n `:""}\n ${o.play?`
    ${ht.play}
    ${ht.pause}
    `:""}\n ${o.screenshot?`
    ${ht.screenshot}
    `:""}\n ${o.record?`
    ${ht.record}
    ${ht.recordStop}
    `:""}\n ${o.fullscreen?`
    ${ht.fullscreen}
    ${ht.fullscreenExit}
    `:""}\n
    \n
    \n
    \n `:""}\n\n `),Object.defineProperty(t,"$poster",{value:e.$container.querySelector(".jessibuca-poster")}),Object.defineProperty(t,"$loading",{value:e.$container.querySelector(".jessibuca-loading")}),Object.defineProperty(t,"$play",{value:e.$container.querySelector(".jessibuca-play")}),Object.defineProperty(t,"$playBig",{value:e.$container.querySelector(".jessibuca-play-big")}),Object.defineProperty(t,"$recording",{value:e.$container.querySelector(".jessibuca-recording")}),Object.defineProperty(t,"$recordingTime",{value:e.$container.querySelector(".jessibuca-recording-time")}),Object.defineProperty(t,"$recordingStop",{value:e.$container.querySelector(".jessibuca-recording-stop")}),Object.defineProperty(t,"$pause",{value:e.$container.querySelector(".jessibuca-pause")}),Object.defineProperty(t,"$controls",{value:e.$container.querySelector(".jessibuca-controls")}),Object.defineProperty(t,"$fullscreen",{value:e.$container.querySelector(".jessibuca-fullscreen")}),Object.defineProperty(t,"$fullscreen",{value:e.$container.querySelector(".jessibuca-fullscreen")}),Object.defineProperty(t,"$volume",{value:e.$container.querySelector(".jessibuca-volume")}),Object.defineProperty(t,"$volumePanelWrap",{value:e.$container.querySelector(".jessibuca-volume-panel-wrap")}),Object.defineProperty(t,"$volumePanelText",{value:e.$container.querySelector(".jessibuca-volume-panel-text")}),Object.defineProperty(t,"$volumePanel",{value:e.$container.querySelector(".jessibuca-volume-panel")}),Object.defineProperty(t,"$volumeHandle",{value:e.$container.querySelector(".jessibuca-volume-panel-handle")}),Object.defineProperty(t,"$volumeOn",{value:e.$container.querySelector(".jessibuca-icon-audio")}),Object.defineProperty(t,"$volumeOff",{value:e.$container.querySelector(".jessibuca-icon-mute")}),Object.defineProperty(t,"$fullscreen",{value:e.$container.querySelector(".jessibuca-fullscreen")}),Object.defineProperty(t,"$fullscreenExit",{value:e.$container.querySelector(".jessibuca-fullscreen-exit")}),Object.defineProperty(t,"$record",{value:e.$container.querySelector(".jessibuca-record")}),Object.defineProperty(t,"$recordStop",{value:e.$container.querySelector(".jessibuca-record-stop")}),Object.defineProperty(t,"$screenshot",{value:e.$container.querySelector(".jessibuca-screenshot")}),Object.defineProperty(t,"$speed",{value:e.$container.querySelector(".jessibuca-speed")})})(e,this),t=this,Object.defineProperty(t,"controlsRect",{get:()=>t.$controls.getBoundingClientRect()}),pt(e,this),((e,t)=>{const{events:{proxy:i},debug:o}=e;function r(e){const{bottom:i,height:o}=t.$volumePanel.getBoundingClientRect(),{height:r}=t.$volumeHandle.getBoundingClientRect();return Ee(i-e.y-r/2,0,o-r/2)/(o-r)}if(i(window,["click","contextmenu"],(i=>{i.composedPath().indexOf(e.$container)>-1?t.isFocus=!0:t.isFocus=!1})),i(window,"orientationchange",(()=>{setTimeout((()=>{e.resize()}),300)})),i(t.$controls,"click",(e=>{e.stopPropagation()})),i(t.$pause,"click",(t=>{e.pause()})),i(t.$play,"click",(t=>{e.play(),e.resumeAudioAfterPause()})),i(t.$playBig,"click",(t=>{e.play(),e.resumeAudioAfterPause()})),i(t.$volume,"mouseover",(()=>{t.$volumePanelWrap.classList.add("jessibuca-volume-panel-wrap-show")})),i(t.$volume,"mouseout",(()=>{t.$volumePanelWrap.classList.remove("jessibuca-volume-panel-wrap-show")})),i(t.$volumeOn,"click",(i=>{i.stopPropagation(),Be(t.$volumeOn,"display","none"),Be(t.$volumeOff,"display","block");const o=e.volume;e.volume=0,e._lastVolume=o})),i(t.$volumeOff,"click",(i=>{i.stopPropagation(),Be(t.$volumeOn,"display","block"),Be(t.$volumeOff,"display","none"),e.volume=e.lastVolume||.5})),i(t.$screenshot,"click",(t=>{t.stopPropagation(),e.video.screenshot()})),i(t.$volumePanel,"click",(t=>{t.stopPropagation(),e.volume=r(t)})),i(t.$volumeHandle,"mousedown",(()=>{t.isVolumeDroging=!0})),i(t.$volumeHandle,"mousemove",(i=>{t.isVolumeDroging&&(e.volume=r(i))})),i(document,"mouseup",(()=>{t.isVolumeDroging&&(t.isVolumeDroging=!1)})),i(t.$record,"click",(t=>{t.stopPropagation(),e.recording=!0})),i(t.$recordStop,"click",(t=>{t.stopPropagation(),e.recording=!1})),i(t.$recordingStop,"click",(t=>{t.stopPropagation(),e.recording=!1})),i(t.$fullscreen,"click",(t=>{t.stopPropagation(),e.fullscreen=!0})),i(t.$fullscreenExit,"click",(t=>{t.stopPropagation(),e.fullscreen=!1})),e._opt.hasControl&&e._opt.controlAutoHide){i(e.$container,"mouseover",(()=>{e.fullscreen||(Be(t.$controls,"display","block"),r())})),i(e.$container,"mousemove",(()=>{e.$container&&t.$controls&&(e.fullscreen,"none"===t.$controls.style.display&&(Be(t.$controls,"display","block"),r()))})),i(e.$container,"mouseout",(()=>{s(),Be(t.$controls,"display","none")}));let o=null;const r=()=>{s(),o=setTimeout((()=>{Be(t.$controls,"display","none")}),5e3)},s=()=>{o&&(clearTimeout(o),o=null)}}})(e,this),e._opt.hotKey&&((e,t)=>{const{events:{proxy:i}}=e,o={};function r(e,t){o[e]?o[e].push(t):o[e]=[t]}r(se,(()=>{e.fullscreen&&(e.fullscreen=!1)})),r(ae,(()=>{e.volume+=.05})),r(ne,(()=>{e.volume-=.05})),i(window,"keydown",(e=>{if(t.isFocus){const t=document.activeElement.tagName.toUpperCase(),i=document.activeElement.getAttribute("contenteditable");if("INPUT"!==t&&"TEXTAREA"!==t&&""!==i&&"true"!==i){const t=o[e.keyCode];t&&(e.preventDefault(),t.forEach((e=>e())))}}}))})(e,this),this.player.debug.log("Control","init")}destroy(){if(this.$poster){if(!Me(this.$poster)){const e=this.player.$container.querySelector(".jessibuca-poster");e&&this.player.$container&&this.player.$container.removeChild(e)}}if(this.$loading){if(!Me(this.$loading)){const e=this.player.$container.querySelector(".jessibuca-loading");e&&this.player.$container&&this.player.$container.removeChild(e)}}if(this.$controls){if(!Me(this.$controls)){const e=this.player.$container.querySelector(".jessibuca-controls");e&&this.player.$container&&this.player.$container.removeChild(e)}}if(this.$recording){if(!Me(this.$recording)){const e=this.player.$container.querySelector(".jessibuca-recording");e&&this.player.$container&&this.player.$container.removeChild(e)}}if(this.$playBig){if(!Me(this.$playBig)){const e=this.player.$container.querySelector(".jessibuca-play-big");e&&this.player.$container&&this.player.$container.removeChild(e)}}this.player.$container&&(this.player.$container.classList.remove("jessibuca-controls-show-auto-hide"),this.player.$container.classList.remove("jessibuca-controls-show")),this.player.debug.log("control","destroy")}autoSize(){const e=this.player;e.$container.style.padding="0 0";const t=e.width,i=e.height,o=t/i,r=e.video.$videoElement.width/e.video.$videoElement.height;if(o>r){const o=(t-i*r)/2;e.$container.style.padding=`0 ${o}px`}else{const o=(i-t/r)/2;e.$container.style.padding=`${o}px 0`}}toggleBar(e){this.$controls&&(De(e)||(e="none"===Ce(this.$controls,"display",!1)),Be(this.$controls,"display",e?"flex":"none"))}getBarIsShow(){let e=!1;return this.$controls&&(e="none"!==Ce(this.$controls,"display",!1)),e}}gt(".jessibuca-container{position:relative;display:block;width:100%;height:100%;overflow:hidden}.jessibuca-container.jessibuca-fullscreen-web{position:fixed;z-index:9999;left:0;top:0;right:0;bottom:0;width:100vw!important;height:100vh!important;background:#000}");class ft{static init(){ft.types={avc1:[],avcC:[],hvc1:[],hvcC:[],btrt:[],dinf:[],dref:[],esds:[],ftyp:[],hdlr:[],mdat:[],mdhd:[],mdia:[],mfhd:[],minf:[],moof:[],moov:[],mp4a:[],mvex:[],mvhd:[],sdtp:[],stbl:[],stco:[],stsc:[],stsd:[],stsz:[],stts:[],tfdt:[],tfhd:[],traf:[],trak:[],trun:[],trex:[],tkhd:[],vmhd:[],smhd:[]};for(let e in ft.types)ft.types.hasOwnProperty(e)&&(ft.types[e]=[e.charCodeAt(0),e.charCodeAt(1),e.charCodeAt(2),e.charCodeAt(3)]);let e=ft.constants={};e.FTYP=new Uint8Array([105,115,111,109,0,0,0,1,105,115,111,109,97,118,99,49]),e.STSD_PREFIX=new Uint8Array([0,0,0,0,0,0,0,1]),e.STTS=new Uint8Array([0,0,0,0,0,0,0,0]),e.STSC=e.STCO=e.STTS,e.STSZ=new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0]),e.HDLR_VIDEO=new Uint8Array([0,0,0,0,0,0,0,0,118,105,100,101,0,0,0,0,0,0,0,0,0,0,0,0,86,105,100,101,111,72,97,110,100,108,101,114,0]),e.HDLR_AUDIO=new Uint8Array([0,0,0,0,0,0,0,0,115,111,117,110,0,0,0,0,0,0,0,0,0,0,0,0,83,111,117,110,100,72,97,110,100,108,101,114,0]),e.DREF=new Uint8Array([0,0,0,0,0,0,0,1,0,0,0,12,117,114,108,32,0,0,0,1]),e.SMHD=new Uint8Array([0,0,0,0,0,0,0,0]),e.VMHD=new Uint8Array([0,0,0,1,0,0,0,0,0,0,0,0])}static box(e){let t=8,i=null,o=Array.prototype.slice.call(arguments,1),r=o.length;for(let e=0;e>>24&255,i[1]=t>>>16&255,i[2]=t>>>8&255,i[3]=255&t,i.set(e,4);let s=8;for(let e=0;e>>24&255,e>>>16&255,e>>>8&255,255&e,t>>>24&255,t>>>16&255,t>>>8&255,255&t,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255]))}static trak(e){return ft.box(ft.types.trak,ft.tkhd(e),ft.mdia(e))}static tkhd(e){let t=e.id,i=e.duration,o=e.presentWidth,r=e.presentHeight;return ft.box(ft.types.tkhd,new Uint8Array([0,0,0,7,0,0,0,0,0,0,0,0,t>>>24&255,t>>>16&255,t>>>8&255,255&t,0,0,0,0,i>>>24&255,i>>>16&255,i>>>8&255,255&i,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,0,0,0,o>>>8&255,255&o,0,0,r>>>8&255,255&r,0,0]))}static mdia(e){return ft.box(ft.types.mdia,ft.mdhd(e),ft.hdlr(e),ft.minf(e))}static mdhd(e){let t=e.timescale,i=e.duration;return ft.box(ft.types.mdhd,new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0,t>>>24&255,t>>>16&255,t>>>8&255,255&t,i>>>24&255,i>>>16&255,i>>>8&255,255&i,85,196,0,0]))}static hdlr(e){let t=null;return t="audio"===e.type?ft.constants.HDLR_AUDIO:ft.constants.HDLR_VIDEO,ft.box(ft.types.hdlr,t)}static minf(e){let t=null;return t="audio"===e.type?ft.box(ft.types.smhd,ft.constants.SMHD):ft.box(ft.types.vmhd,ft.constants.VMHD),ft.box(ft.types.minf,t,ft.dinf(),ft.stbl(e))}static dinf(){return ft.box(ft.types.dinf,ft.box(ft.types.dref,ft.constants.DREF))}static stbl(e){return ft.box(ft.types.stbl,ft.stsd(e),ft.box(ft.types.stts,ft.constants.STTS),ft.box(ft.types.stsc,ft.constants.STSC),ft.box(ft.types.stsz,ft.constants.STSZ),ft.box(ft.types.stco,ft.constants.STCO))}static stsd(e){return"audio"===e.type?ft.box(ft.types.stsd,ft.constants.STSD_PREFIX,ft.mp4a(e)):"avc"===e.videoType?ft.box(ft.types.stsd,ft.constants.STSD_PREFIX,ft.avc1(e)):ft.box(ft.types.stsd,ft.constants.STSD_PREFIX,ft.hvc1(e))}static mp4a(e){let t=e.channelCount,i=e.audioSampleRate,o=new Uint8Array([0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,t,0,16,0,0,0,0,i>>>8&255,255&i,0,0]);return ft.box(ft.types.mp4a,o,ft.esds(e))}static esds(e){let t=e.config||[],i=t.length,o=new Uint8Array([0,0,0,0,3,23+i,0,1,0,4,15+i,64,21,0,0,0,0,0,0,0,0,0,0,0,5].concat([i]).concat(t).concat([6,1,2]));return ft.box(ft.types.esds,o)}static avc1(e){let t=e.avcc;const i=e.codecWidth,o=e.codecHeight;let r=new Uint8Array([0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,i>>>8&255,255&i,o>>>8&255,255&o,0,72,0,0,0,72,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,24,255,255]);return ft.box(ft.types.avc1,r,ft.box(ft.types.avcC,t))}static hvc1(e){let t=e.avcc;const i=e.codecWidth,o=e.codecHeight;let r=new Uint8Array([0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,i>>>8&255,255&i,o>>>8&255,255&o,0,72,0,0,0,72,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,24,255,255]);return ft.box(ft.types.hvc1,r,ft.box(ft.types.hvcC,t))}static mvex(e){return ft.box(ft.types.mvex,ft.trex(e))}static trex(e){let t=e.id,i=new Uint8Array([0,0,0,0,t>>>24&255,t>>>16&255,t>>>8&255,255&t,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,1]);return ft.box(ft.types.trex,i)}static moof(e,t){return ft.box(ft.types.moof,ft.mfhd(e.sequenceNumber),ft.traf(e,t))}static mfhd(e){let t=new Uint8Array([0,0,0,0,e>>>24&255,e>>>16&255,e>>>8&255,255&e]);return ft.box(ft.types.mfhd,t)}static traf(e,t){let i=e.id,o=ft.box(ft.types.tfhd,new Uint8Array([0,0,0,0,i>>>24&255,i>>>16&255,i>>>8&255,255&i])),r=ft.box(ft.types.tfdt,new Uint8Array([0,0,0,0,t>>>24&255,t>>>16&255,t>>>8&255,255&t])),s=ft.sdtp(e),a=ft.trun(e,s.byteLength+16+16+8+16+8+8);return ft.box(ft.types.traf,o,r,a,s)}static sdtp(e){let t=new Uint8Array(5),i=e.flags;return t[4]=i.isLeading<<6|i.dependsOn<<4|i.isDependedOn<<2|i.hasRedundancy,ft.box(ft.types.sdtp,t)}static trun(e,t){let i=new Uint8Array(28);t+=36,i.set([0,0,15,1,0,0,0,1,t>>>24&255,t>>>16&255,t>>>8&255,255&t],0);let o=e.duration,r=e.size,s=e.flags,a=e.cts;return i.set([o>>>24&255,o>>>16&255,o>>>8&255,255&o,r>>>24&255,r>>>16&255,r>>>8&255,255&r,s.isLeading<<2|s.dependsOn,s.isDependedOn<<6|s.hasRedundancy<<4|s.isNonSync,0,0,a>>>24&255,a>>>16&255,a>>>8&255,255&a],12),ft.box(ft.types.trun,i)}static mdat(e){return ft.box(ft.types.mdat,e)}}ft.init();class bt extends Ue{constructor(e){super(),this.player=e,this.isAvc=!0,this.mediaSource=new window.MediaSource,this.sourceBuffer=null,this.hasInit=!1,this.isInitInfo=!1,this.cacheTrack={},this.timeInit=!1,this.sequenceNumber=0,this.mediaSourceOpen=!1,this.dropping=!1,this.firstRenderTime=null,this.mediaSourceAppendBufferError=!1,this.mediaSourceAppendBufferFull=!1,this.isDecodeFirstIIframe=!1,this.player.video.$videoElement.src=window.URL.createObjectURL(this.mediaSource);const{debug:t,events:{proxy:i}}=e;i(this.mediaSource,"sourceopen",(()=>{this.mediaSourceOpen=!0,this.player.emit(F.mseSourceOpen)})),i(this.mediaSource,"sourceclose",(()=>{this.player.emit(F.mseSourceClose)})),e.debug.log("MediaSource","init")}destroy(){this.stop(),this.mediaSource=null,this.mediaSourceOpen=!1,this.sourceBuffer=null,this.hasInit=!1,this.isInitInfo=!1,this.sequenceNumber=0,this.cacheTrack=null,this.timeInit=!1,this.mediaSourceAppendBufferError=!1,this.mediaSourceAppendBufferFull=!1,this.isDecodeFirstIIframe=!1,this.off(),this.player.debug.log("MediaSource","destroy")}get state(){return this.mediaSource&&this.mediaSource.readyState}get isStateOpen(){return this.state===ie}get isStateClosed(){return this.state===oe}get isStateEnded(){return this.state===te}get duration(){return this.mediaSource&&this.mediaSource.duration}set duration(e){this.mediaSource.duration=e}decodeVideo(e,t,i,o){const r=this.player;if(!(!r||r&&r.isDestroyedOrClosed()))if(this.hasInit){if(i&&0===e[1]){let t=ct(e.slice(5));const i=this.player.video.videoInfo;i&&i.width&&i.height&&t&&t.codecWidth&&t.codecHeight&&(t.codecWidth!==i.width||t.codecHeight!==i.height)&&(this.player.debug.warn("MediaSource",`width or height is update, width ${i.width}-> ${t.codecWidth}, height ${i.height}-> ${t.codecHeight}`),this.isInitInfo=!1,this.player.video.init=!1)}if(!this.isDecodeFirstIIframe&&i&&(this.isDecodeFirstIIframe=!0),this.isDecodeFirstIIframe){null===this.firstRenderTime&&(this.firstRenderTime=t);const r=t-this.firstRenderTime;this._decodeVideo(e,r,i,o)}else this.player.debug.warn("MediaSource","decodeVideo isDecodeFirstIIframe false")}else if(i&&0===e[1]){const o=15&e[0];if(r.video.updateVideoInfo({encTypeCode:o}),o===P)return void this.emit(O.mediaSourceH265NotSupport);r._times.decodeStart||(r._times.decodeStart=Se()),this._decodeConfigurationRecord(e,t,i,o),this.hasInit=!0}}_decodeConfigurationRecord(e,t,i,o){let r=e.slice(5),s={};s=ct(r);const a={id:1,type:"video",timescale:1e3,duration:0,avcc:r,codecWidth:s.codecWidth,codecHeight:s.codecHeight,videoType:s.videoType},n=ft.generateInitSegment(a);this.isAvc=!0,this.appendBuffer(n.buffer),this.sequenceNumber=0,this.cacheTrack=null,this.timeInit=!1}_decodeVideo(e,t,i,o){const r=this.player;let s=e.slice(5),a=s.byteLength;const n=r.video.$videoElement,A=r._opt.videoBufferDelay;if(n.buffered.length>1&&(this.removeBuffer(n.buffered.start(0),n.buffered.end(0)),this.timeInit=!1),this.dropping&&t-this.cacheTrack.dts>A)this.dropping=!1,this.cacheTrack={};else if(this.cacheTrack&&t>=this.cacheTrack.dts){let e=8+this.cacheTrack.size,i=new Uint8Array(e);i[0]=e>>>24&255,i[1]=e>>>16&255,i[2]=e>>>8&255,i[3]=255&e,i.set(ft.types.mdat,4),i.set(this.cacheTrack.data,8),this.cacheTrack.duration=t-this.cacheTrack.dts;let o=ft.moof(this.cacheTrack,this.cacheTrack.dts),s=new Uint8Array(o.byteLength+i.byteLength);s.set(o,0),s.set(i,o.byteLength),this.appendBuffer(s.buffer),r.handleRender(),r.updateStats({fps:!0,ts:t,buf:r.demux&&r.demux.delay||0}),r._times.videoStart||(r._times.videoStart=Se(),r.handlePlayToRenderTimes())}else r.debug.log("MediaSource","timeInit set false , cacheTrack = {}"),this.timeInit=!1,this.cacheTrack={};this.cacheTrack||(this.cacheTrack={}),this.cacheTrack.id=1,this.cacheTrack.sequenceNumber=++this.sequenceNumber,this.cacheTrack.size=a,this.cacheTrack.dts=t,this.cacheTrack.cts=o,this.cacheTrack.isKeyframe=i,this.cacheTrack.data=s,this.cacheTrack.flags={isLeading:0,dependsOn:i?2:1,isDependedOn:i?1:0,hasRedundancy:0,isNonSync:i?0:1},this.timeInit||1!==n.buffered.length||(r.debug.log("MediaSource","timeInit set true"),this.timeInit=!0,n.currentTime=n.buffered.end(0)),!this.isInitInfo&&n.videoWidth>0&&n.videoHeight>0&&(r.debug.log("MediaSource",`updateVideoInfo: ${n.videoWidth},${n.videoHeight}`),r.video.updateVideoInfo({width:n.videoWidth,height:n.videoHeight}),r.video.initCanvasViewSize(),this.isInitInfo=!0)}appendBuffer(e){const{debug:t,events:{proxy:i}}=this.player;if(null===this.sourceBuffer&&(this.sourceBuffer=this.mediaSource.addSourceBuffer(ee),i(this.sourceBuffer,"error",(e=>{t.error("MediaSource","sourceBuffer error",e),this.player.emit(F.mseSourceBufferError,e)}))),this.mediaSourceAppendBufferError)t.error("MediaSource","this.mediaSourceAppendBufferError is true");else if(this.mediaSourceAppendBufferFull)t.error("MediaSource","this.mediaSourceAppendBufferFull is true");else if(!1===this.sourceBuffer.updating&&this.isStateOpen)try{this.sourceBuffer.appendBuffer(e)}catch(e){t.warn("MediaSource","this.sourceBuffer.appendBuffer()",e.code,e),22===e.code?(this.stop(),this.mediaSourceAppendBufferFull=!0,this.emit(O.mediaSourceFull)):11===e.code?(this.stop(),this.mediaSourceAppendBufferError=!0,this.emit(O.mediaSourceAppendBufferError)):(t.error("MediaSource","appendBuffer error",e),this.player.emit(F.mseSourceBufferError,e))}else this.isStateClosed?this.player.emitError(O.mseSourceBufferError,"mediaSource is not attached to video or mediaSource is closed"):this.isStateEnded?this.player.emitError(O.mseSourceBufferError,"mediaSource is closed"):!0===this.sourceBuffer.updating&&this.player.emit(F.mseSourceBufferBusy)}stop(){this.abortSourceBuffer(),this.removeSourceBuffer(),this.endOfStream()}dropSourceBuffer(e){const t=this.player.video.$videoElement;this.dropping=e,t.buffered.length>0&&t.buffered.end(0)-t.currentTime>1&&(this.player.debug.warn("MediaSource","dropSourceBuffer",`$video.buffered.end(0) is ${t.buffered.end(0)} - $video.currentTime ${t.currentTime}`),t.currentTime=t.buffered.end(0))}removeBuffer(e,t){if(this.isStateOpen&&!1===this.sourceBuffer.updating)try{this.sourceBuffer.remove(e,t)}catch(e){this.player.debug.warn("MediaSource","removeBuffer() error",e)}else this.player.debug.warn("MediaSource","removeBuffer() this.isStateOpen is",this.isStateOpen,"this.sourceBuffer.updating",this.sourceBuffer.updating)}endOfStream(){const e=this.player.video&&this.player.video.$videoElement;if(this.isStateOpen&&e&&e.readyState>=1)try{this.mediaSource.endOfStream()}catch(e){this.player.debug.warn("MediaSource","endOfStream() error",e)}}abortSourceBuffer(){this.isStateOpen&&this.sourceBuffer&&(this.sourceBuffer.abort(),this.sourceBuffer=null)}removeSourceBuffer(){if(!this.isStateClosed&&this.mediaSource&&this.sourceBuffer)try{this.mediaSource.removeSourceBuffer(this.sourceBuffer)}catch(e){this.player.debug.warn("MediaSource","removeSourceBuffer() error",e)}}getSourceBufferUpdating(){return this.sourceBuffer&&this.sourceBuffer.updating}}const yt=()=>"undefined"!=typeof navigator&&parseFloat((""+(/CPU.*OS ([0-9_]{3,4})[0-9_]{0,1}|(CPU like).*AppleWebKit.*Mobile/i.exec(navigator.userAgent)||[0,""])[1]).replace("undefined","3_2").replace("_",".").replace("_",""))<10&&!window.MSStream,vt=()=>"wakeLock"in navigator;class wt{constructor(e){if(this.player=e,this.enabled=!1,vt()){this._wakeLock=null;const e=()=>{null!==this._wakeLock&&"visible"===document.visibilityState&&this.enable()};document.addEventListener("visibilitychange",e),document.addEventListener("fullscreenchange",e)}else yt()?this.noSleepTimer=null:(this.noSleepVideo=document.createElement("video"),this.noSleepVideo.setAttribute("title","No Sleep"),this.noSleepVideo.setAttribute("playsinline",""),this._addSourceToVideo(this.noSleepVideo,"webm","data:video/webm;base64,GkXfowEAAAAAAAAfQoaBAUL3gQFC8oEEQvOBCEKChHdlYm1Ch4EEQoWBAhhTgGcBAAAAAAAVkhFNm3RALE27i1OrhBVJqWZTrIHfTbuMU6uEFlSua1OsggEwTbuMU6uEHFO7a1OsghV17AEAAAAAAACkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVSalmAQAAAAAAAEUq17GDD0JATYCNTGF2ZjU1LjMzLjEwMFdBjUxhdmY1NS4zMy4xMDBzpJBlrrXf3DCDVB8KcgbMpcr+RImIQJBgAAAAAAAWVK5rAQAAAAAAD++uAQAAAAAAADLXgQFzxYEBnIEAIrWcg3VuZIaFVl9WUDiDgQEj44OEAmJaAOABAAAAAAAABrCBsLqBkK4BAAAAAAAPq9eBAnPFgQKcgQAitZyDdW5khohBX1ZPUkJJU4OBAuEBAAAAAAAAEZ+BArWIQOdwAAAAAABiZIEgY6JPbwIeVgF2b3JiaXMAAAAAAoC7AAAAAAAAgLUBAAAAAAC4AQN2b3JiaXMtAAAAWGlwaC5PcmcgbGliVm9yYmlzIEkgMjAxMDExMDEgKFNjaGF1ZmVudWdnZXQpAQAAABUAAABlbmNvZGVyPUxhdmM1NS41Mi4xMDIBBXZvcmJpcyVCQ1YBAEAAACRzGCpGpXMWhBAaQlAZ4xxCzmvsGUJMEYIcMkxbyyVzkCGkoEKIWyiB0JBVAABAAACHQXgUhIpBCCGEJT1YkoMnPQghhIg5eBSEaUEIIYQQQgghhBBCCCGERTlokoMnQQgdhOMwOAyD5Tj4HIRFOVgQgydB6CCED0K4moOsOQghhCQ1SFCDBjnoHITCLCiKgsQwuBaEBDUojILkMMjUgwtCiJqDSTX4GoRnQXgWhGlBCCGEJEFIkIMGQcgYhEZBWJKDBjm4FITLQagahCo5CB+EIDRkFQCQAACgoiiKoigKEBqyCgDIAAAQQFEUx3EcyZEcybEcCwgNWQUAAAEACAAAoEiKpEiO5EiSJFmSJVmSJVmS5omqLMuyLMuyLMsyEBqyCgBIAABQUQxFcRQHCA1ZBQBkAAAIoDiKpViKpWiK54iOCISGrAIAgAAABAAAEDRDUzxHlETPVFXXtm3btm3btm3btm3btm1blmUZCA1ZBQBAAAAQ0mlmqQaIMAMZBkJDVgEACAAAgBGKMMSA0JBVAABAAACAGEoOogmtOd+c46BZDppKsTkdnEi1eZKbirk555xzzsnmnDHOOeecopxZDJoJrTnnnMSgWQqaCa0555wnsXnQmiqtOeeccc7pYJwRxjnnnCateZCajbU555wFrWmOmkuxOeecSLl5UptLtTnnnHPOOeecc84555zqxekcnBPOOeecqL25lpvQxTnnnE/G6d6cEM4555xzzjnnnHPOOeecIDRkFQAABABAEIaNYdwpCNLnaCBGEWIaMulB9+gwCRqDnELq0ehopJQ6CCWVcVJKJwgNWQUAAAIAQAghhRRSSCGFFFJIIYUUYoghhhhyyimnoIJKKqmooowyyyyzzDLLLLPMOuyssw47DDHEEEMrrcRSU2011lhr7jnnmoO0VlprrbVSSimllFIKQkNWAQAgAAAEQgYZZJBRSCGFFGKIKaeccgoqqIDQkFUAACAAgAAAAABP8hzRER3RER3RER3RER3R8RzPESVREiVREi3TMjXTU0VVdWXXlnVZt31b2IVd933d933d+HVhWJZlWZZlWZZlWZZlWZZlWZYgNGQVAAACAAAghBBCSCGFFFJIKcYYc8w56CSUEAgNWQUAAAIACAAAAHAUR3EcyZEcSbIkS9IkzdIsT/M0TxM9URRF0zRV0RVdUTdtUTZl0zVdUzZdVVZtV5ZtW7Z125dl2/d93/d93/d93/d93/d9XQdCQ1YBABIAADqSIymSIimS4ziOJElAaMgqAEAGAEAAAIriKI7jOJIkSZIlaZJneZaomZrpmZ4qqkBoyCoAABAAQAAAAAAAAIqmeIqpeIqoeI7oiJJomZaoqZoryqbsuq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq4LhIasAgAkAAB0JEdyJEdSJEVSJEdygNCQVQCADACAAAAcwzEkRXIsy9I0T/M0TxM90RM901NFV3SB0JBVAAAgAIAAAAAAAAAMybAUy9EcTRIl1VItVVMt1VJF1VNVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVN0zRNEwgNWQkAkAEAkBBTLS3GmgmLJGLSaqugYwxS7KWxSCpntbfKMYUYtV4ah5RREHupJGOKQcwtpNApJq3WVEKFFKSYYyoVUg5SIDRkhQAQmgHgcBxAsixAsiwAAAAAAAAAkDQN0DwPsDQPAAAAAAAAACRNAyxPAzTPAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABA0jRA8zxA8zwAAAAAAAAA0DwP8DwR8EQRAAAAAAAAACzPAzTRAzxRBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABA0jRA8zxA8zwAAAAAAAAAsDwP8EQR0DwRAAAAAAAAACzPAzxRBDzRAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAEOAAABBgIRQasiIAiBMAcEgSJAmSBM0DSJYFTYOmwTQBkmVB06BpME0AAAAAAAAAAAAAJE2DpkHTIIoASdOgadA0iCIAAAAAAAAAAAAAkqZB06BpEEWApGnQNGgaRBEAAAAAAAAAAAAAzzQhihBFmCbAM02IIkQRpgkAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAGHAAAAgwoQwUGrIiAIgTAHA4imUBAIDjOJYFAACO41gWAABYliWKAABgWZooAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAYcAAACDChDBQashIAiAIAcCiKZQHHsSzgOJYFJMmyAJYF0DyApgFEEQAIAAAocAAACLBBU2JxgEJDVgIAUQAABsWxLE0TRZKkaZoniiRJ0zxPFGma53meacLzPM80IYqiaJoQRVE0TZimaaoqME1VFQAAUOAAABBgg6bE4gCFhqwEAEICAByKYlma5nmeJ4qmqZokSdM8TxRF0TRNU1VJkqZ5niiKommapqqyLE3zPFEURdNUVVWFpnmeKIqiaaqq6sLzPE8URdE0VdV14XmeJ4qiaJqq6roQRVE0TdNUTVV1XSCKpmmaqqqqrgtETxRNU1Vd13WB54miaaqqq7ouEE3TVFVVdV1ZBpimaaqq68oyQFVV1XVdV5YBqqqqruu6sgxQVdd1XVmWZQCu67qyLMsCAAAOHAAAAoygk4wqi7DRhAsPQKEhKwKAKAAAwBimFFPKMCYhpBAaxiSEFEImJaXSUqogpFJSKRWEVEoqJaOUUmopVRBSKamUCkIqJZVSAADYgQMA2IGFUGjISgAgDwCAMEYpxhhzTiKkFGPOOScRUoox55yTSjHmnHPOSSkZc8w556SUzjnnnHNSSuacc845KaVzzjnnnJRSSuecc05KKSWEzkEnpZTSOeecEwAAVOAAABBgo8jmBCNBhYasBABSAQAMjmNZmuZ5omialiRpmud5niiapiZJmuZ5nieKqsnzPE8URdE0VZXneZ4oiqJpqirXFUXTNE1VVV2yLIqmaZqq6rowTdNUVdd1XZimaaqq67oubFtVVdV1ZRm2raqq6rqyDFzXdWXZloEsu67s2rIAAPAEBwCgAhtWRzgpGgssNGQlAJABAEAYg5BCCCFlEEIKIYSUUggJAAAYcAAACDChDBQashIASAUAAIyx1lprrbXWQGettdZaa62AzFprrbXWWmuttdZaa6211lJrrbXWWmuttdZaa6211lprrbXWWmuttdZaa6211lprrbXWWmuttdZaa6211lprrbXWWmstpZRSSimllFJKKaWUUkoppZRSSgUA+lU4APg/2LA6wknRWGChISsBgHAAAMAYpRhzDEIppVQIMeacdFRai7FCiDHnJKTUWmzFc85BKCGV1mIsnnMOQikpxVZjUSmEUlJKLbZYi0qho5JSSq3VWIwxqaTWWoutxmKMSSm01FqLMRYjbE2ptdhqq7EYY2sqLbQYY4zFCF9kbC2m2moNxggjWywt1VprMMYY3VuLpbaaizE++NpSLDHWXAAAd4MDAESCjTOsJJ0VjgYXGrISAAgJACAQUooxxhhzzjnnpFKMOeaccw5CCKFUijHGnHMOQgghlIwx5pxzEEIIIYRSSsaccxBCCCGEkFLqnHMQQgghhBBKKZ1zDkIIIYQQQimlgxBCCCGEEEoopaQUQgghhBBCCKmklEIIIYRSQighlZRSCCGEEEIpJaSUUgohhFJCCKGElFJKKYUQQgillJJSSimlEkoJJYQSUikppRRKCCGUUkpKKaVUSgmhhBJKKSWllFJKIYQQSikFAAAcOAAABBhBJxlVFmGjCRcegEJDVgIAZAAAkKKUUiktRYIipRikGEtGFXNQWoqocgxSzalSziDmJJaIMYSUk1Qy5hRCDELqHHVMKQYtlRhCxhik2HJLoXMOAAAAQQCAgJAAAAMEBTMAwOAA4XMQdAIERxsAgCBEZohEw0JweFAJEBFTAUBigkIuAFRYXKRdXECXAS7o4q4DIQQhCEEsDqCABByccMMTb3jCDU7QKSp1IAAAAAAADADwAACQXAAREdHMYWRobHB0eHyAhIiMkAgAAAAAABcAfAAAJCVAREQ0cxgZGhscHR4fICEiIyQBAIAAAgAAAAAggAAEBAQAAAAAAAIAAAAEBB9DtnUBAAAAAAAEPueBAKOFggAAgACjzoEAA4BwBwCdASqwAJAAAEcIhYWIhYSIAgIABhwJ7kPfbJyHvtk5D32ych77ZOQ99snIe+2TkPfbJyHvtk5D32ych77ZOQ99YAD+/6tQgKOFggADgAqjhYIAD4AOo4WCACSADqOZgQArADECAAEQEAAYABhYL/QACIBDmAYAAKOFggA6gA6jhYIAT4AOo5mBAFMAMQIAARAQABgAGFgv9AAIgEOYBgAAo4WCAGSADqOFggB6gA6jmYEAewAxAgABEBAAGAAYWC/0AAiAQ5gGAACjhYIAj4AOo5mBAKMAMQIAARAQABgAGFgv9AAIgEOYBgAAo4WCAKSADqOFggC6gA6jmYEAywAxAgABEBAAGAAYWC/0AAiAQ5gGAACjhYIAz4AOo4WCAOSADqOZgQDzADECAAEQEAAYABhYL/QACIBDmAYAAKOFggD6gA6jhYIBD4AOo5iBARsAEQIAARAQFGAAYWC/0AAiAQ5gGACjhYIBJIAOo4WCATqADqOZgQFDADECAAEQEAAYABhYL/QACIBDmAYAAKOFggFPgA6jhYIBZIAOo5mBAWsAMQIAARAQABgAGFgv9AAIgEOYBgAAo4WCAXqADqOFggGPgA6jmYEBkwAxAgABEBAAGAAYWC/0AAiAQ5gGAACjhYIBpIAOo4WCAbqADqOZgQG7ADECAAEQEAAYABhYL/QACIBDmAYAAKOFggHPgA6jmYEB4wAxAgABEBAAGAAYWC/0AAiAQ5gGAACjhYIB5IAOo4WCAfqADqOZgQILADECAAEQEAAYABhYL/QACIBDmAYAAKOFggIPgA6jhYICJIAOo5mBAjMAMQIAARAQABgAGFgv9AAIgEOYBgAAo4WCAjqADqOFggJPgA6jmYECWwAxAgABEBAAGAAYWC/0AAiAQ5gGAACjhYICZIAOo4WCAnqADqOZgQKDADECAAEQEAAYABhYL/QACIBDmAYAAKOFggKPgA6jhYICpIAOo5mBAqsAMQIAARAQABgAGFgv9AAIgEOYBgAAo4WCArqADqOFggLPgA6jmIEC0wARAgABEBAUYABhYL/QACIBDmAYAKOFggLkgA6jhYIC+oAOo5mBAvsAMQIAARAQABgAGFgv9AAIgEOYBgAAo4WCAw+ADqOZgQMjADECAAEQEAAYABhYL/QACIBDmAYAAKOFggMkgA6jhYIDOoAOo5mBA0sAMQIAARAQABgAGFgv9AAIgEOYBgAAo4WCA0+ADqOFggNkgA6jmYEDcwAxAgABEBAAGAAYWC/0AAiAQ5gGAACjhYIDeoAOo4WCA4+ADqOZgQObADECAAEQEAAYABhYL/QACIBDmAYAAKOFggOkgA6jhYIDuoAOo5mBA8MAMQIAARAQABgAGFgv9AAIgEOYBgAAo4WCA8+ADqOFggPkgA6jhYID+oAOo4WCBA+ADhxTu2sBAAAAAAAAEbuPs4EDt4r3gQHxghEr8IEK"),this._addSourceToVideo(this.noSleepVideo,"mp4","data:video/mp4;base64,AAAAHGZ0eXBNNFYgAAACAGlzb21pc28yYXZjMQAAAAhmcmVlAAAGF21kYXTeBAAAbGliZmFhYyAxLjI4AABCAJMgBDIARwAAArEGBf//rdxF6b3m2Ui3lizYINkj7u94MjY0IC0gY29yZSAxNDIgcjIgOTU2YzhkOCAtIEguMjY0L01QRUctNCBBVkMgY29kZWMgLSBDb3B5bGVmdCAyMDAzLTIwMTQgLSBodHRwOi8vd3d3LnZpZGVvbGFuLm9yZy94MjY0Lmh0bWwgLSBvcHRpb25zOiBjYWJhYz0wIHJlZj0zIGRlYmxvY2s9MTowOjAgYW5hbHlzZT0weDE6MHgxMTEgbWU9aGV4IHN1Ym1lPTcgcHN5PTEgcHN5X3JkPTEuMDA6MC4wMCBtaXhlZF9yZWY9MSBtZV9yYW5nZT0xNiBjaHJvbWFfbWU9MSB0cmVsbGlzPTEgOHg4ZGN0PTAgY3FtPTAgZGVhZHpvbmU9MjEsMTEgZmFzdF9wc2tpcD0xIGNocm9tYV9xcF9vZmZzZXQ9LTIgdGhyZWFkcz02IGxvb2thaGVhZF90aHJlYWRzPTEgc2xpY2VkX3RocmVhZHM9MCBucj0wIGRlY2ltYXRlPTEgaW50ZXJsYWNlZD0wIGJsdXJheV9jb21wYXQ9MCBjb25zdHJhaW5lZF9pbnRyYT0wIGJmcmFtZXM9MCB3ZWlnaHRwPTAga2V5aW50PTI1MCBrZXlpbnRfbWluPTI1IHNjZW5lY3V0PTQwIGludHJhX3JlZnJlc2g9MCByY19sb29rYWhlYWQ9NDAgcmM9Y3JmIG1idHJlZT0xIGNyZj0yMy4wIHFjb21wPTAuNjAgcXBtaW49MCBxcG1heD02OSBxcHN0ZXA9NCB2YnZfbWF4cmF0ZT03NjggdmJ2X2J1ZnNpemU9MzAwMCBjcmZfbWF4PTAuMCBuYWxfaHJkPW5vbmUgZmlsbGVyPTAgaXBfcmF0aW89MS40MCBhcT0xOjEuMDAAgAAAAFZliIQL8mKAAKvMnJycnJycnJycnXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXiEASZACGQAjgCEASZACGQAjgAAAAAdBmjgX4GSAIQBJkAIZACOAAAAAB0GaVAX4GSAhAEmQAhkAI4AhAEmQAhkAI4AAAAAGQZpgL8DJIQBJkAIZACOAIQBJkAIZACOAAAAABkGagC/AySEASZACGQAjgAAAAAZBmqAvwMkhAEmQAhkAI4AhAEmQAhkAI4AAAAAGQZrAL8DJIQBJkAIZACOAAAAABkGa4C/AySEASZACGQAjgCEASZACGQAjgAAAAAZBmwAvwMkhAEmQAhkAI4AAAAAGQZsgL8DJIQBJkAIZACOAIQBJkAIZACOAAAAABkGbQC/AySEASZACGQAjgCEASZACGQAjgAAAAAZBm2AvwMkhAEmQAhkAI4AAAAAGQZuAL8DJIQBJkAIZACOAIQBJkAIZACOAAAAABkGboC/AySEASZACGQAjgAAAAAZBm8AvwMkhAEmQAhkAI4AhAEmQAhkAI4AAAAAGQZvgL8DJIQBJkAIZACOAAAAABkGaAC/AySEASZACGQAjgCEASZACGQAjgAAAAAZBmiAvwMkhAEmQAhkAI4AhAEmQAhkAI4AAAAAGQZpAL8DJIQBJkAIZACOAAAAABkGaYC/AySEASZACGQAjgCEASZACGQAjgAAAAAZBmoAvwMkhAEmQAhkAI4AAAAAGQZqgL8DJIQBJkAIZACOAIQBJkAIZACOAAAAABkGawC/AySEASZACGQAjgAAAAAZBmuAvwMkhAEmQAhkAI4AhAEmQAhkAI4AAAAAGQZsAL8DJIQBJkAIZACOAAAAABkGbIC/AySEASZACGQAjgCEASZACGQAjgAAAAAZBm0AvwMkhAEmQAhkAI4AhAEmQAhkAI4AAAAAGQZtgL8DJIQBJkAIZACOAAAAABkGbgCvAySEASZACGQAjgCEASZACGQAjgAAAAAZBm6AnwMkhAEmQAhkAI4AhAEmQAhkAI4AhAEmQAhkAI4AhAEmQAhkAI4AAAAhubW9vdgAAAGxtdmhkAAAAAAAAAAAAAAAAAAAD6AAABDcAAQAAAQAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAzB0cmFrAAAAXHRraGQAAAADAAAAAAAAAAAAAAABAAAAAAAAA+kAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAALAAAACQAAAAAAAkZWR0cwAAABxlbHN0AAAAAAAAAAEAAAPpAAAAAAABAAAAAAKobWRpYQAAACBtZGhkAAAAAAAAAAAAAAAAAAB1MAAAdU5VxAAAAAAALWhkbHIAAAAAAAAAAHZpZGUAAAAAAAAAAAAAAABWaWRlb0hhbmRsZXIAAAACU21pbmYAAAAUdm1oZAAAAAEAAAAAAAAAAAAAACRkaW5mAAAAHGRyZWYAAAAAAAAAAQAAAAx1cmwgAAAAAQAAAhNzdGJsAAAAr3N0c2QAAAAAAAAAAQAAAJ9hdmMxAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAALAAkABIAAAASAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGP//AAAALWF2Y0MBQsAN/+EAFWdCwA3ZAsTsBEAAAPpAADqYA8UKkgEABWjLg8sgAAAAHHV1aWRraEDyXyRPxbo5pRvPAyPzAAAAAAAAABhzdHRzAAAAAAAAAAEAAAAeAAAD6QAAABRzdHNzAAAAAAAAAAEAAAABAAAAHHN0c2MAAAAAAAAAAQAAAAEAAAABAAAAAQAAAIxzdHN6AAAAAAAAAAAAAAAeAAADDwAAAAsAAAALAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAAiHN0Y28AAAAAAAAAHgAAAEYAAANnAAADewAAA5gAAAO0AAADxwAAA+MAAAP2AAAEEgAABCUAAARBAAAEXQAABHAAAASMAAAEnwAABLsAAATOAAAE6gAABQYAAAUZAAAFNQAABUgAAAVkAAAFdwAABZMAAAWmAAAFwgAABd4AAAXxAAAGDQAABGh0cmFrAAAAXHRraGQAAAADAAAAAAAAAAAAAAACAAAAAAAABDcAAAAAAAAAAAAAAAEBAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAkZWR0cwAAABxlbHN0AAAAAAAAAAEAAAQkAAADcAABAAAAAAPgbWRpYQAAACBtZGhkAAAAAAAAAAAAAAAAAAC7gAAAykBVxAAAAAAALWhkbHIAAAAAAAAAAHNvdW4AAAAAAAAAAAAAAABTb3VuZEhhbmRsZXIAAAADi21pbmYAAAAQc21oZAAAAAAAAAAAAAAAJGRpbmYAAAAcZHJlZgAAAAAAAAABAAAADHVybCAAAAABAAADT3N0YmwAAABnc3RzZAAAAAAAAAABAAAAV21wNGEAAAAAAAAAAQAAAAAAAAAAAAIAEAAAAAC7gAAAAAAAM2VzZHMAAAAAA4CAgCIAAgAEgICAFEAVBbjYAAu4AAAADcoFgICAAhGQBoCAgAECAAAAIHN0dHMAAAAAAAAAAgAAADIAAAQAAAAAAQAAAkAAAAFUc3RzYwAAAAAAAAAbAAAAAQAAAAEAAAABAAAAAgAAAAIAAAABAAAAAwAAAAEAAAABAAAABAAAAAIAAAABAAAABgAAAAEAAAABAAAABwAAAAIAAAABAAAACAAAAAEAAAABAAAACQAAAAIAAAABAAAACgAAAAEAAAABAAAACwAAAAIAAAABAAAADQAAAAEAAAABAAAADgAAAAIAAAABAAAADwAAAAEAAAABAAAAEAAAAAIAAAABAAAAEQAAAAEAAAABAAAAEgAAAAIAAAABAAAAFAAAAAEAAAABAAAAFQAAAAIAAAABAAAAFgAAAAEAAAABAAAAFwAAAAIAAAABAAAAGAAAAAEAAAABAAAAGQAAAAIAAAABAAAAGgAAAAEAAAABAAAAGwAAAAIAAAABAAAAHQAAAAEAAAABAAAAHgAAAAIAAAABAAAAHwAAAAQAAAABAAAA4HN0c3oAAAAAAAAAAAAAADMAAAAaAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAACMc3RjbwAAAAAAAAAfAAAALAAAA1UAAANyAAADhgAAA6IAAAO+AAAD0QAAA+0AAAQAAAAEHAAABC8AAARLAAAEZwAABHoAAASWAAAEqQAABMUAAATYAAAE9AAABRAAAAUjAAAFPwAABVIAAAVuAAAFgQAABZ0AAAWwAAAFzAAABegAAAX7AAAGFwAAAGJ1ZHRhAAAAWm1ldGEAAAAAAAAAIWhkbHIAAAAAAAAAAG1kaXJhcHBsAAAAAAAAAAAAAAAALWlsc3QAAAAlqXRvbwAAAB1kYXRhAAAAAQAAAABMYXZmNTUuMzMuMTAw"),this.noSleepVideo.addEventListener("loadedmetadata",(()=>{this.noSleepVideo.duration<=1?this.noSleepVideo.setAttribute("loop",""):this.noSleepVideo.addEventListener("timeupdate",(()=>{this.noSleepVideo.currentTime>.5&&(this.noSleepVideo.currentTime=Math.random())}))})))}_addSourceToVideo(e,t,i){var o=document.createElement("source");o.src=i,o.type=`video/${t}`,e.appendChild(o)}get isEnabled(){return this.enabled}enable(){const e=this.player.debug;if(vt())return navigator.wakeLock.request("screen").then((t=>{this._wakeLock=t,this.enabled=!0,e.log("wakeLock","Wake Lock active."),this._wakeLock.addEventListener("release",(()=>{e.log("wakeLock","Wake Lock released.")}))})).catch((t=>{throw this.enabled=!1,e.error("wakeLock",`${t.name}, ${t.message}`),t}));if(yt())return this.disable(),this.noSleepTimer=window.setInterval((()=>{document.hidden||(window.location.href=window.location.href.split("#")[0],window.setTimeout(window.stop,0))}),15e3),this.enabled=!0,Promise.resolve();return this.noSleepVideo.play().then((e=>(this.enabled=!0,e))).catch((e=>{throw this.enabled=!1,e}))}disable(){const e=this.player.debug;vt()?(this._wakeLock&&this._wakeLock.release(),this._wakeLock=null):yt()?this.noSleepTimer&&(e.warn("wakeLock","NoSleep now disabled for older iOS devices."),window.clearInterval(this.noSleepTimer),this.noSleepTimer=null):this.noSleepVideo.pause(),this.enabled=!1}}class St extends Ue{constructor(e,t){var i;super(),this.$container=e,this._opt=Object.assign({},g,t),this.debug=new fe(this),this.debug.log("Player","init"),this._opt.forceNoOffscreen=!0,(Te()||/ipad|android(?!.*mobile)|tablet|kindle|silk/i.test(window.navigator.userAgent.toLowerCase()))&&(this.debug.log("Player","isMobile and set _opt.controlAutoHide false"),this._opt.controlAutoHide=!1),this._opt.autoUseSystemFullScreen&&(ye.isEnabled&&this._opt.useWebFullScreen&&(this.debug.log("Player","screenfull.isEnabled is true and _opt.useWebFullScreen is true , set _opt.useWebFullScreen false"),this._opt.useWebFullScreen=!1),Ve(ye.isEnabled)&&Ve(this._opt.useWebFullScreen)&&(this.debug.log("Player","screenfull.isEnabled is false and _opt.useWebFullScreen is false , set _opt.useWebFullScreen true"),this._opt.useWebFullScreen=!0)),this._opt.useWCS&&(this._opt.useWCS="VideoEncoder"in window),this._opt.useMSE&&(this._opt.useMSE=window.MediaSource&&window.MediaSource.isTypeSupported(ee)),this._opt.wcsUseVideoRender&&(this._opt.wcsUseVideoRender=window.MediaStreamTrackGenerator&&"function"==typeof window.MediaStreamTrackGenerator),this._opt.useMSE&&(this._opt.useWCS&&this.debug.log("Player","useWCS set true->false"),this._opt.forceNoOffscreen||this.debug.log("Player","forceNoOffscreen set false->true"),this._opt.useWCS=!1,this._opt.forceNoOffscreen=!0),this._opt.forceNoOffscreen||("undefined"==typeof OffscreenCanvas?(this._opt.forceNoOffscreen=!0,this._opt.useOffscreen=!1):this._opt.useOffscreen=!0),this._opt.hasAudio||(this._opt.operateBtns.audio=!1),this._opt.hasControl=this._hasControl(),this._loading=!1,this._playing=!1,this._hasLoaded=!1,this._destroyed=!1,this._closed=!1,this._checkHeartTimeout=null,this._checkLoadingTimeout=null,this._checkStatsInterval=null,this._startBpsTime=null,this._isPlayingBeforePageHidden=!1,this._stats={buf:0,fps:0,abps:0,vbps:0,ts:0},this._times={playInitStart:"",playStart:"",streamStart:"",streamResponse:"",demuxStart:"",decodeStart:"",videoStart:"",playTimestamp:"",streamTimestamp:"",streamResponseTimestamp:"",demuxTimestamp:"",decodeTimestamp:"",videoTimestamp:"",allTimestamp:""},this._videoTimestamp=0,this._audioTimestamp=0,i=this,Object.defineProperty(i,"rect",{get:()=>{const e=i.$container.getBoundingClientRect();return e.width=Math.max(e.width,i.$container.clientWidth),e.height=Math.max(e.height,i.$container.clientHeight),e}}),["bottom","height","left","right","top","width"].forEach((e=>{Object.defineProperty(i,e,{get:()=>i.rect[e]})})),this.events=new be(this),this.video=new Xe(this),this._opt.hasAudio&&(this.audio=new Ze(this)),this.recorder=new it(this),this._onlyMseOrWcsVideo()?this.loaded=!0:this.decoderWorker=new ot(this),this.stream=null,this.demux=null,this._lastVolume=null,this._opt.useWCS&&(this.webcodecsDecoder=new lt(this),this.loaded=!0),this._opt.useMSE&&(this.mseDecoder=new bt(this),this.loaded=!0),this.control=new mt(this),Te()&&(this.keepScreenOn=new wt(this)),(e=>{try{const t=t=>{Fe(t)===e.$container&&(e.emit(M.fullscreen,e.fullscreen),e.fullscreen?e._opt.useMSE&&e.resize():e.resize())};ye.on("change",t),e.events.destroys.push((()=>{ye.off("change",t)}))}catch(e){}if(e.on(F.decoderWorkerInit,(()=>{e.debug.log("player","has loaded"),e.loaded=!0})),e.on(F.play,(()=>{e.loading=!1})),e.on(F.fullscreen,(t=>{if(t)try{ye.request(e.$container).then((()=>{})).catch((t=>{Te()&&e._opt.useWebFullScreen&&(e.webFullscreen=!0)}))}catch(t){Te()&&e._opt.useWebFullScreen&&(e.webFullscreen=!0)}else try{ye.exit().then((()=>{e.webFullscreen&&(e.webFullscreen=!1)})).catch((()=>{e.webFullscreen=!1}))}catch(t){e.webFullscreen=!1}})),Te()&&e.on(F.webFullscreen,(t=>{t?e.$container.classList.add("jessibuca-fullscreen-web"):e.$container.classList.remove("jessibuca-fullscreen-web"),e.emit(M.fullscreen,e.fullscreen)})),e.on(F.resize,(()=>{e.video&&e.video.resize()})),e._opt.debug){const t=[F.timeUpdate],i=[F.stats,F.playToRenderTimes,F.audioInfo,F.videoInfo];Object.keys(F).forEach((o=>{e.on(F[o],(r=>{t.includes(o)||(i.includes(o)&&(r=JSON.stringify(r)),e.debug.log("player events",F[o],r))}))})),Object.keys(O).forEach((t=>{e.on(O[t],(i=>{e.debug.log("player event error",O[t],i)}))}))}})(this),(e=>{const{_opt:t,debug:i,events:{proxy:o}}=e;t.supportDblclickFullscreen&&o(e.$container,"dblclick",(t=>{const i=Fe(t).nodeName.toLowerCase();"canvas"!==i&&"video"!==i||(e.fullscreen=!e.fullscreen)})),o(document,"visibilitychange",(()=>{t.hiddenAutoPause&&(i.log("visibilitychange",document.visibilityState,e._isPlayingBeforePageHidden),"visible"===document.visibilityState?e._isPlayingBeforePageHidden&&e.play():(e._isPlayingBeforePageHidden=e.playing,e.playing&&e.pause()))})),o(window,"fullscreenchange",(()=>{null!==e.keepScreenOn&&"visible"===document.visibilityState&&e.enableWakeLock()}))})(this),this.debug.log("Player","init and version is",p),this._opt.useWCS&&this.debug.log("Player","use WCS"),this._opt.useMSE&&this.debug.log("Player","use MSE"),this._opt.useOffscreen&&this.debug.log("Player","use offscreen");try{this.debug.log("Player options",JSON.stringify(this._opt))}catch(e){}}async destroy(){this._destroyed=!0,this._loading=!1,this._playing=!1,this._hasLoaded=!1,this._lastVolume=null,this._times={playInitStart:"",playStart:"",streamStart:"",streamResponse:"",demuxStart:"",decodeStart:"",videoStart:"",playTimestamp:"",streamTimestamp:"",streamResponseTimestamp:"",demuxTimestamp:"",decodeTimestamp:"",videoTimestamp:"",allTimestamp:""},this.decoderWorker&&(await this.decoderWorker.destroy(),this.decoderWorker=null),this.video&&(this.video.destroy(),this.video=null),this.audio&&(this.audio.destroy(),this.audio=null),this.stream&&(await this.stream.destroy(),this.stream=null),this.recorder&&(this.recorder.destroy(),this.recorder=null),this.control&&(this.control.destroy(),this.control=null),this.webcodecsDecoder&&(this.webcodecsDecoder.destroy(),this.webcodecsDecoder=null),this.mseDecoder&&(this.mseDecoder.destroy(),this.mseDecoder=null),this.demux&&(this.demux.destroy(),this.demux=null),this.events&&(this.events.destroy(),this.events=null),this.clearCheckHeartTimeout(),this.clearCheckLoadingTimeout(),this.clearStatsInterval(),this.releaseWakeLock(),this.keepScreenOn=null,this.resetStats(),this._audioTimestamp=0,this._videoTimestamp=0,this.emit("destroy"),this.off(),this.debug.log("play","destroy end")}set fullscreen(e){Te()&&this._opt.useWebFullScreen?(this.emit(F.webFullscreen,e),setTimeout((()=>{this.updateOption({rotate:e?270:0}),this.resize()}),10)):this.emit(F.fullscreen,e)}get fullscreen(){return Ie(this.$container)||this.webFullscreen}set webFullscreen(e){this.emit(F.webFullscreen,e)}get webFullscreen(){return this.$container.classList.contains("jessibuca-fullscreen-web")}set loaded(e){this._hasLoaded=e}get loaded(){return this._hasLoaded}set playing(e){e&&(this.loading=!1),this.playing!==e&&(this._playing=e,this.emit(F.playing,e),this.emit(F.volumechange,this.volume),e?this.emit(F.play):this.emit(F.pause))}get playing(){return this._playing}get volume(){return this.audio&&this.audio.volume||0}set volume(e){e!==this.volume&&(this.audio&&this.audio.setVolume(e),this._lastVolume=e)}get lastVolume(){return this._lastVolume}set loading(e){this.loading!==e&&(this._loading=e,this.emit(F.loading,this._loading))}get loading(){return this._loading}set recording(e){e?this.playing&&this.recorder&&this.recorder.startRecord():this.recorder&&this.recorder.stopRecordAndSave()}get recording(){return!!this.recorder&&this.recorder.recording}set audioTimestamp(e){null!==e&&(this._audioTimestamp=e)}get audioTimestamp(){return this._audioTimestamp}set videoTimestamp(e){null!==e&&(this._videoTimestamp=e,this._opt.useWCS||this._opt.useMSE||this.audioTimestamp&&this.videoTimestamp&&this.audio&&this.audio.emit(F.videoSyncAudio,{audioTimestamp:this.audioTimestamp,videoTimestamp:this.videoTimestamp,diff:this.audioTimestamp-this.videoTimestamp}))}get videoTimestamp(){return this._videoTimestamp}get isDebug(){return!0===this._opt.debug}updateOption(e){this._opt=Object.assign({},this._opt,e)}init(){return new Promise(((e,t)=>{this.stream||(this.stream=new $e(this)),this.audio||this._opt.hasAudio&&(this.audio=new Ze(this)),this.demux||(this.demux=new nt(this)),this._opt.useWCS&&(this.webcodecsDecoder||(this.webcodecsDecoder=new lt(this))),this._opt.useMSE&&(this.mseDecoder||(this.mseDecoder=new bt(this))),this.decoderWorker||this._onlyMseOrWcsVideo()?e():(this.decoderWorker=new ot(this),this.debug.log("Player","waiting decoderWorker init"),this.once(F.decoderWorkerInit,(()=>{this.debug.log("Player","decoderWorker init success"),this.loaded=!0,e()})))}))}play(e,t){return new Promise(((i,o)=>{if(!e&&!this._opt.url)return o();this._closed=!1,this.loading=!0,this.playing=!1,this._times.playInitStart=Se(),e||(e=this._opt.url),this._opt.url=e,this.clearCheckHeartTimeout(),this.init().then((()=>{this._times.playStart=Se(),this._opt.isNotMute&&this.mute(!1),this.webcodecsDecoder&&this.webcodecsDecoder.once(O.webcodecsH265NotSupport,(()=>{this.emit(O.webcodecsH265NotSupport),this._opt.autoWasm||this.emit(F.error,O.webcodecsH265NotSupport)})),this.mseDecoder&&(this.mseDecoder.once(O.mediaSourceH265NotSupport,(()=>{this.emit(O.mediaSourceH265NotSupport),this._opt.autoWasm||this.emit(F.error,O.mediaSourceH265NotSupport)})),this.mseDecoder.once(O.mediaSourceFull,(()=>{this.emitError(O.mediaSourceFull)})),this.mseDecoder.once(O.mediaSourceAppendBufferError,(()=>{this.emitError(O.mediaSourceAppendBufferError)})),this.mseDecoder.once(O.mediaSourceBufferListLarge,(()=>{this.emitError(O.mediaSourceBufferListLarge)})),this.mseDecoder.once(O.mediaSourceAppendBufferEndTimeout,(()=>{this.emitError(O.mediaSourceAppendBufferEndTimeout)}))),this.enableWakeLock(),this.stream.fetchStream(e,t),this.checkLoadingTimeout(),this.stream.once(O.fetchError,(e=>{this.emitError(O.fetchError,e)})),this.stream.once(O.websocketError,(e=>{this.emitError(O.websocketError,e)})),this.stream.once(F.streamEnd,(e=>{this.emitError(F.streamEnd,e)})),this.stream.once(F.streamSuccess,(()=>{i(),this._times.streamResponse=Se(),this.video.play(),this.checkStatsInterval()}))})).catch((e=>{o(e)}))}))}close(){return new Promise(((e,t)=>{this._close().then((()=>{this.video&&this.video.clearView(),e()}))}))}resumeAudioAfterPause(){this.lastVolume&&(this.volume=this.lastVolume)}_close(){return new Promise(((e,t)=>{this._closed=!0,this.stream&&(this.stream.destroy(),this.stream=null),this.demux&&(this.demux.destroy(),this.demux=null),this.decoderWorker&&(this.decoderWorker.destroy(),this.decoderWorker=null),this.webcodecsDecoder&&(this.webcodecsDecoder.destroy(),this.webcodecsDecoder=null),this.mseDecoder&&(this.mseDecoder.destroy(),this.mseDecoder=null),this.audio&&(this.audio.destroy(),this.audio=null),this.clearCheckHeartTimeout(),this.clearCheckLoadingTimeout(),this.clearStatsInterval(),this.playing=!1,this.loading=!1,this.recording=!1,this.video&&(this.video.resetInit(),this.video.pause(!0)),this.releaseWakeLock(),this.resetStats(),this._audioTimestamp=0,this._videoTimestamp=0,this._times={playInitStart:"",playStart:"",streamStart:"",streamResponse:"",demuxStart:"",decodeStart:"",videoStart:"",playTimestamp:"",streamTimestamp:"",streamResponseTimestamp:"",demuxTimestamp:"",decodeTimestamp:"",videoTimestamp:"",allTimestamp:""},setTimeout((()=>{e()}),0)}))}pause(){return arguments.length>0&&void 0!==arguments[0]&&arguments[0]?this.close():this._close()}mute(e){if(this.audio){const t=this.audio.getLastVolume();this.audio.mute(e),this._lastVolume=e?0:t||.5}}resize(){this.video.resize()}startRecord(e,t){this.recording||(this.recorder.setFileName(e,t),this.recording=!0)}stopRecordAndSave(){this.recording&&(this.recording=!1)}_hasControl(){let e=!1,t=!1;return Object.keys(this._opt.operateBtns).forEach((e=>{this._opt.operateBtns[e]&&(t=!0)})),(this._opt.showBandwidth||this._opt.text||t)&&(e=!0),e}_onlyMseOrWcsVideo(){return!1===this._opt.hasAudio&&(this._opt.useMSE||this._opt.useWCS&&!this._opt.useOffscreen)}checkHeart(){this.clearCheckHeartTimeout(),this.checkHeartTimeout()}checkHeartTimeout(){this._checkHeartTimeout=setTimeout((()=>{if(this.playing){if(0!==this._stats.fps)return;if(this.isDestroyedOrClosed())return;this.pause().then((()=>{this.emit(F.timeout,F.delayTimeout),this.emit(F.delayTimeout)}))}}),1e3*this._opt.heartTimeout)}checkStatsInterval(){this._checkStatsInterval=setInterval((()=>{this.updateStats()}),1e3)}clearCheckHeartTimeout(){this._checkHeartTimeout&&(clearTimeout(this._checkHeartTimeout),this._checkHeartTimeout=null)}checkLoadingTimeout(){const e=parseFloat((Math.floor(11*Math.random())-5)/10),t=this._opt.loadingTimeout+e;this.debug.log("Player",`checkLoadingTimeout loadingTimeout is ${this._opt.loadingTimeout} and newLoadingTimeout is ${t}`),this._checkLoadingTimeout=setTimeout((()=>{this.playing||this.isDestroyedOrClosed()||this.pause().then((()=>{this.emit(F.timeout,F.loadingTimeout),this.emit(F.loadingTimeout)}))}),1e3*t)}clearCheckLoadingTimeout(){this._checkLoadingTimeout&&(clearTimeout(this._checkLoadingTimeout),this._checkLoadingTimeout=null)}clearStatsInterval(){this._checkStatsInterval&&(clearInterval(this._checkStatsInterval),this._checkStatsInterval=null)}handleRender(){this.isDestroyedOrClosed()||(this.loading&&(this.emit(F.start),this.loading=!1,this.clearCheckLoadingTimeout()),this.playing||(this.playing=!0),this.checkHeart())}updateStats(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};if(this.isDestroyedOrClosed())return;this._startBpsTime||(this._startBpsTime=Se()),je(e.ts)&&(this._stats.ts=e.ts),je(e.buf)&&(this._stats.buf=e.buf),e.fps&&(this._stats.fps+=1),e.abps&&(this._stats.abps+=e.abps),e.vbps&&(this._stats.vbps+=e.vbps);const t=Se();t-this._startBpsTime<1e3||(this.emit(F.stats,this._stats),this.emit(F.performance,function(e){let t=0;return e>=24?t=2:e>=15&&(t=1),t}(this._stats.fps)),this._stats.fps=0,this._stats.abps=0,this._stats.vbps=0,this._startBpsTime=t)}resetStats(){this._startBpsTime=null,this._stats={buf:0,fps:0,abps:0,vbps:0,ts:0}}enableWakeLock(){this._opt.keepScreenOn&&this.keepScreenOn&&this.keepScreenOn.enable()}releaseWakeLock(){this._opt.keepScreenOn&&this.keepScreenOn&&this.keepScreenOn.disable()}handlePlayToRenderTimes(){if(this.isDestroyedOrClosed())return;const e=this._times;e.playTimestamp=e.playStart-e.playInitStart,e.streamTimestamp=e.streamStart-e.playStart,e.streamResponseTimestamp=e.streamResponse-e.streamStart,e.demuxTimestamp=e.demuxStart-e.streamResponse,e.decodeTimestamp=e.decodeStart-e.demuxStart,e.videoTimestamp=e.videoStart-e.decodeStart,e.allTimestamp=e.videoStart-e.playInitStart,this.emit(F.playToRenderTimes,e)}getOption(){return this._opt}emitError(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";this.emit(F.error,e,t),this.emit(e,t)}isControlBarShow(){const e=this._opt.hasControl,t=this._opt.controlAutoHide;let i=e&&!t;return i&&this.control&&(i=this.control.getBarIsShow()),i}getControlBarShow(){let e=!1;return this.control&&(e=this.control.getBarIsShow()),e}toggleControlBar(e){this.control&&(this.control.toggleBar(e),this.resize())}isDestroyed(){return this._destroyed}isClosed(){return this._closed}isDestroyedOrClosed(){return this.isDestroyed()||this.isClosed()}}class Et extends Ue{constructor(e){super();let t=e,i=e.container;if(this.TAG_NAME="Jessibuca","string"==typeof e.container&&(i=document.querySelector(e.container)),!i)throw new Error("Jessibuca need container option");if("CANVAS"===i.nodeName||"VIDEO"===i.nodeName)throw new Error(`Jessibuca container type can not be ${i.nodeName} type`);if(t.videoBuffer>=t.heartTimeout)throw new Error(`Jessibuca videoBuffer ${t.videoBuffer}s must be less than heartTimeout ${t.heartTimeout}s`);if(this._checkHasCreated(i))throw new Error("Jessibuca container has been created and can not be created again",i);if(t.videoBuffer>10&&console.warn("Jessibuca",`videoBuffer ${t.videoBuffer}s is too long, will black screen for ${t.videoBuffer}s , it is recommended to set it to less than 10s`),!i.classList)throw new Error("Jessibuca container option must be DOM Element");var o,r,s;i.classList.add("jessibuca-container"),o=i,r=h,s="xxxxxxxxxxxx4xxx".replace(/[xy]/g,(function(e){var t=16*Math.random()|0;return("x"==e?t:3&t|8).toString(16)})),o&&(o.dataset?o.dataset[r]=s:o.setAttribute("data-"+r,s)),delete t.container,delete t.url,t.forceNoOffscreen=!0,Te()&&(t.controlAutoHide=!1),je(t.videoBuffer)&&(t.videoBuffer=1e3*Number(t.videoBuffer)),je(t.timeout)&&(xe(t.loadingTimeout)&&(t.loadingTimeout=t.timeout),xe(t.heartTimeout)&&(t.heartTimeout=t.timeout)),this._opt=t,this.$container=i,this._loadingTimeoutReplayTimes=0,this._heartTimeoutReplayTimes=0,this.initDecoderWorkerTimeout=null,this._destroyed=!1,this.events=new be(this),this.debug=new fe(this),this._initPlayer(i,t),console.log(`Jessibuca version: ${p}`)}async destroy(){var e,t;this._destroyed=!0,this.off(),this._clearInitDecoderWorkerTimeout(),this.player&&(await this.player.destroy(),this.player=null),this.events&&(this.events.destroy(),this.events=null),this.$container&&(this.$container.classList.remove("jessibuca-container"),this.$container.classList.remove("jessibuca-fullscreen-web"),e=this.$container,t=h,e&&(e.dataset?delete e.dataset[t]:e.removeAttribute("data-"+t)),this.$container.innerHTML="",this.$container=null),this._opt={},this._loadingTimeoutReplayTimes=0,this._heartTimeoutReplayTimes=0}_initPlayer(e,t){this.player=new St(e,t);try{this.debug.log("jessibuca","_initPlayer",JSON.stringify(this.player.getOption()))}catch(e){}this._bindEvents()}_resetPlayer(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};this.player.destroy(),this.player=null,this._opt=Object.assign(this._opt,e),this._opt.url="",this._initPlayer(this.$container,this._opt)}_bindEvents(){Object.keys(M).forEach((e=>{this.player.on(M[e],(t=>{this.emit(e,t)}))}))}isDestroyed(){return this._destroyed}setDebug(e){this.debug.log(this.TAG_NAME,"setDebug()",e),this.player.updateOption({debug:!!e})}mute(){this.debug.log(this.TAG_NAME,"mute()"),this.player.mute(!0)}cancelMute(){this.debug.log(this.TAG_NAME,"cancelMute()"),this.player.mute(!1)}setVolume(e){this.debug.log(this.TAG_NAME,"setVolume()",e),this.player.volume=e}audioResume(){this.debug.log(this.TAG_NAME,"audioResume()"),this.player.audio&&this.player.audio.audioEnabled(!0)}setTimeout(e){this.debug.log(this.TAG_NAME,"setTimeout()",e),e=Number(e),this.player.updateOption({timeout:e,loadingTimeout:e,heartTimeout:e})}setScaleMode(e){e=Number(e),this.debug.log(this.TAG_NAME,"setScaleMode()",e);let t={isFullResize:!1,isResize:!1};switch(e){case z:t.isFullResize=!1,t.isResize=!1;break;case Y:t.isFullResize=!1,t.isResize=!0;break;case X:t.isFullResize=!0,t.isResize=!0}this.player.updateOption(t),this.resize()}pause(){return new Promise(((e,t)=>{this.debug.log(this.TAG_NAME,"pause()"),this.player?this.player.pause().then((()=>{e()})).catch((e=>{t(e)})):t("player is null")}))}async close(){return this.debug.log(this.TAG_NAME,"close()"),await this.destroy(),!0}clearView(){this.debug.log(this.TAG_NAME,"clearView()"),this.player.video.clearView()}play(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return new Promise(((i,o)=>{try{this.debug.log(this.TAG_NAME,`play() ${e}`,JSON.stringify(t))}catch(i){this.debug.log(this.TAG_NAME,`play() ${e}`,t)}if(!this.isDestroyed())return e||this._opt.url?void(e?this._opt.url?e===this._opt.url?this.player.playing?i():(this.clearView(),this.player.play(this._opt.url,this._opt.playOptions).then((()=>{i(),this.player.resumeAudioAfterPause()})).catch((e=>{this.debug.warn("jessibuca","pause -> play and play error",e),this.player.pause().then((()=>{o(e)}))}))):this.player.pause().then((()=>{this.clearView(),this._play(e,t).then((()=>{i()})).catch((e=>{this.debug.warn("jessibuca","this._play error",e),o(e)}))})).catch((e=>{this.debug.warn("jessibuca","this._opt.url is null and pause error",e),o(e)})):this._play(e,t).then((()=>{i()})).catch((e=>{this.debug.warn("jessibuca","this._play error",e),o(e)})):this.player.play(this._opt.url,this._opt.playOptions).then((()=>{i(),this.player.resumeAudioAfterPause()})).catch((e=>{this.debug.warn("jessibuca","url is null and play error",e),this.player.pause().then((()=>{o(e)}))}))):(this.emit(F.error,O.playError),void o("play url is empty"));o("Jessibuca is destroyed")}))}_play(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return new Promise(((i,o)=>{this._opt.url=e,this._opt.playOptions=t;const r=0===e.indexOf("http"),s=r?A:n,a=r||-1!==e.indexOf(".flv")||this.player._opt.isFlv?d:c;this.player.updateOption({protocol:s,demuxType:a}),this.player.once(O.webglAlignmentError,(()=>{this.pause().then((()=>{this.debug.log("Jessibuca","webglAlignmentError"),this._resetPlayer({openWebglAlignment:!0}),this.play(e,t).then((()=>{this.debug.log("Jessibuca","webglAlignmentError and play success")})).catch((()=>{this.debug.log("Jessibuca","webglAlignmentError and play error")}))}))})),this.player.once(O.webglContextLostError,(()=>{this.pause().then((()=>{this.debug.log("Jessibuca","webglContextLostError and paused")})).catch((()=>{this.debug.warn("Jessibuca","webglContextLostError and paused error")}))})),this.player.once(O.webglInitError,(()=>{this.pause().then((()=>{this.debug.log("Jessibuca","webglInitError and paused")})).catch((()=>{this.debug.warn("Jessibuca","webglInitError and paused error")}))})),this.player.once(O.mediaSourceH265NotSupport,(()=>{this.pause().then((()=>{this.player._opt.autoWasm?(this.debug.log("Jessibuca","auto wasm [mse-> wasm] reset player and play"),this._resetPlayer({useMSE:!1,useWCS:!1}),this.play(e,t).then((()=>{this.debug.log("Jessibuca","auto wasm [mse-> wasm] reset player and play success")})).catch((()=>{this.debug.log("Jessibuca","auto wasm [mse-> wasm] reset player and play error")}))):this.debug.log("Jessibuca","media source h265 not support and paused")}))})),this.player.once(O.mediaSourceFull,(()=>{this.pause().then((()=>{this.debug.log("Jessibuca","media source full"),this._resetPlayer(),this.play(e,t).then((()=>{this.debug.log("Jessibuca","media source full and reset player and play success")})).catch((()=>{this.debug.warn("Jessibuca","media source full and reset player and play error")}))}))})),this.player.once(O.mediaSourceAppendBufferError,(()=>{this.pause().then((()=>{this.debug.log("Jessibuca","media source append buffer error"),this._resetPlayer(),this.play(e,t).then((()=>{this.debug.log("Jessibuca","media source append buffer error and reset player and play success")})).catch((()=>{this.debug.warn("Jessibuca","media source append buffer error and reset player and play error")}))}))})),this.player.once(O.mediaSourceBufferListLarge,(()=>{this.pause().then((()=>{this.debug.log("Jessibuca","media source buffer list large"),this._resetPlayer(),this.play(e,t).then((()=>{this.debug.log("Jessibuca","media source buffer list large and reset player and play success")})).catch((()=>{this.debug.warn("Jessibuca","media source buffer list large and reset player and play error")}))}))})),this.player.once(O.mediaSourceAppendBufferEndTimeout,(()=>{this.pause().then((()=>{this.debug.log("Jessibuca","media source append buffer end timeout"),this._resetPlayer(),this.play(e,t).then((()=>{this.debug.log("Jessibuca","media source append buffer end timeout and reset player and play success")})).catch((()=>{this.debug.warn("Jessibuca","media source append buffer end timeout and reset player and play error")}))}))})),this.player.once(O.mseSourceBufferError,(()=>{this.pause().then((()=>{this.player._opt.autoWasm?(this.debug.log("Jessibuca","auto wasm [mse-> wasm] reset player and play"),this._resetPlayer({useMSE:!1}),this.play(e,t).then((()=>{this.debug.log("Jessibuca","auto wasm [mse-> wasm] reset player and play success")})).catch((()=>{this.debug.warn("Jessibuca","auto wasm [mse-> wasm] reset player and play error")}))):this.debug.log("Jessibuca","mse source buffer error and paused")}))})),this.player.once(O.webcodecsH265NotSupport,(()=>{this.pause().then((()=>{this.player._opt.autoWasm?(this.debug.log("Jessibuca","auto wasm [wcs-> wasm] reset player and play"),this._resetPlayer({useWCS:!1,useMSE:!1}),this.play(e,t).then((()=>{this.debug.log("Jessibuca","auto wasm [wcs-> wasm] reset player and play success")})).catch((()=>{this.debug.warn("Jessibuca","auto wasm [wcs-> wasm] reset player and play error")}))):this.debug.log("Jessibuca","webcodecs h265 not support and paused")}))})),this.player.once(O.webcodecsWidthOrHeightChange,(()=>{this.pause().then((()=>{this.debug.log("Jessibuca","webcodecs Width Or Height Change reset player and play"),this._resetPlayer({useWCS:!0}),this.play(e,t).then((()=>{this.debug.log("Jessibuca","webcodecs Width Or Height Change reset player and play success")})).catch((()=>{this.debug.warn("Jessibuca","webcodecs Width Or Height Change reset player and play error")}))}))})),this.player.once(O.webcodecsDecodeError,(()=>{this.pause().then((()=>{this.player._opt.autoWasm?(this.debug.log("Jessibuca","webcodecs decode error reset player and play"),this._resetPlayer({useWCS:!1}),this.play(e,t).then((()=>{this.debug.log("Jessibuca","webcodecs decode error reset player and play success")})).catch((()=>{this.debug.warn("Jessibuca","webcodecs decode error reset player and play error")}))):this.debug.log("Jessibuca","webcodecs decode error and paused")}))})),this.player.once(O.webcodecsConfigureError,(()=>{this.pause().then((()=>{this.player._opt.autoWasm?(this.debug.log("Jessibuca","webcodecs Configure error reset player and play"),this._resetPlayer({useWCS:!1}),this.play(e,t).then((()=>{this.debug.log("Jessibuca","webcodecs Configure error reset player and play success")})).catch((()=>{this.debug.warn("Jessibuca","webcodecs Configure error reset player and play error")}))):this.debug.log("Jessibuca","webcodecs Configure error and paused")}))})),this.player.once(O.wasmDecodeError,(()=>{this.player._opt.wasmDecodeErrorReplay?this.pause().then((()=>{this.debug.log("Jessibuca","wasm decode error and reset player and play"),this._resetPlayer({useWCS:!1}),this.play(e,t).then((()=>{this.debug.log("Jessibuca","wasm decode error and reset player and play success")})).catch((e=>{this.debug.warn("Jessibuca","wasm decode error and reset player and play error")}))})):this.pause().then((()=>{this.debug.log("Jessibuca","wasm decode error and paused")})).catch((e=>{this.debug.warn("Jessibuca","wasm decode error and paused error",e)}))})),this.player.once(O.fetchError,(e=>{this.pause().then((()=>{this.debug.log("Jessibuca","fetch error and pause play")})).catch((e=>{this.debug.warn("Jessibuca","fetch error and pause play error",e)}))})),this.player.once(O.websocketError,(()=>{this.pause().then((()=>{this.debug.log("Jessibuca","websocket Error and pause play")})).catch((e=>{this.debug.warn("Jessibuca","websocket Error and pause play error",e)}))})),this.player.once(F.streamEnd,(()=>{this.pause().then((()=>{this.debug.log("Jessibuca","stream End and pause play")})).catch((e=>{this.debug.warn("Jessibuca","stream End and pause play error",e)}))})),this.player.on(F.delayTimeout,(()=>{this.player._opt.heartTimeoutReplay&&(this._heartTimeoutReplayTimes{this._heartTimeoutReplayTimes=0})).catch((()=>{})))})),this.player.on(F.loadingTimeout,(()=>{this.player._opt.loadingTimeoutReplay&&(this._loadingTimeoutReplayTimes{this._loadingTimeoutReplayTimes=0})).catch((()=>{})))})),this.hasLoaded()?this.player.play(e,t).then((()=>{i()})).catch((e=>{this.debug.warn("Jessibuca","hasLoaded and play error",e),this.player&&this.player.pause().then((()=>{o(e)}))})):(this.debug.log("Jessibuca","_play ant waiting decoderWorkerInit"),this._checkInitDecoderWorkerTimeout(),this.player.once(F.decoderWorkerInit,(()=>{this._clearInitDecoderWorkerTimeout(),this.isDestroyed()||(this.debug.log("Jessibuca","_play decoderWorkerInit success and play"),this.player.play(e,t).then((()=>{i()})).catch((e=>{this.debug.warn("Jessibuca","decoderWorkerInit and play error",e),this.player&&this.player.pause().then((()=>{o(e)}))})))})))}))}resize(){this.player.resize()}setBufferTime(e){e=Number(e),this.debug.log(this.TAG_NAME,"setBufferTime()",e),this.player.updateOption({videoBuffer:1e3*e}),this.player.decoderWorker&&this.player.decoderWorker.updateWorkConfig({key:"videoBuffer",value:1e3*e})}setRotate(e){e=parseInt(e,10),this.debug.log(this.TAG_NAME,"setRotate()",e);this.player._opt.rotate!==e&&-1!==[0,90,180,270].indexOf(e)&&(this.player.updateOption({rotate:e}),this.resize())}hasLoaded(){return this.player.loaded}setKeepScreenOn(){this.debug.log(this.TAG_NAME,"setKeepScreenOn()"),this.player.updateOption({keepScreenOn:!0})}setFullscreen(e){this.debug.log(this.TAG_NAME,"setFullscreen()");const t=!!e;this.player.fullscreen!==t&&(this.player.fullscreen=t)}screenshot(e,t,i,o){return this.debug.log(this.TAG_NAME,"screenshot()",e,t,i,o),this.player.video?this.player.video.screenshot(e,t,i,o):""}startRecord(e,t){return new Promise(((i,o)=>{this.debug.log(this.TAG_NAME,"startRecord()",e,t),this.player.playing?(this.player.startRecord(e,t),i()):o()}))}stopRecordAndSave(){this.debug.log(this.TAG_NAME,"stopRecordAndSave()"),this.player.recording&&this.player.stopRecordAndSave()}isPlaying(){return!!this.player&&this.player.playing}isMute(){return!this.player.audio||this.player.audio.isMute}isRecording(){return this.player.recorder.recording}_checkHasCreated(e){if(!e)return!1;const t=function(e,t){return e?e.dataset?e.dataset[t]:e.getAttribute("data-"+t):""}(e,h);return!!t}toggleControlBar(e){this.isDestroyed()||(this.debug.log(this.TAG_NAME,"toggleControlBar()",e),this.player&&this.player.toggleControlBar(e))}getControlBarShow(){if(this.isDestroyed())return!1;let e=!1;return this.player&&(e=this.player.getControlBarShow()),e}kbps2Speed(e){return Number((1e3*e/8/1024).toFixed(2))}_clearInitDecoderWorkerTimeout(){this.initDecoderWorkerTimeout&&(clearTimeout(this.initDecoderWorkerTimeout),this.initDecoderWorkerTimeout=null)}_checkInitDecoderWorkerTimeout(){this._clearInitDecoderWorkerTimeout(),this.initDecoderWorkerTimeout=setTimeout((()=>{this._handleInitDecoderWorkerTimeout()}),1e3*this.player._opt.loadingDecoderWorkerTimeout)}_handleInitDecoderWorkerTimeout(){this.pause().then((()=>{this.debug.log("Jessibuca","init decoder worker timeout and pause play")})).catch((e=>{this.debug.warn("Jessibuca","init decoder worker timeout and pause play error",e)}))}}return a(Et,"ERROR",O),a(Et,"TIMEOUT",{loadingTimeout:F.loadingTimeout,delayTimeout:F.delayTimeout}),window.Jessibuca=Et,Et})); ================================================ FILE: web/src/App.vue ================================================ ================================================ FILE: web/src/api/cloudRecord.js ================================================ import request from '@/utils/request' // 云端录像API export function getPlayPath(id) { return request({ method: 'get', url: `/api/cloud/record/play/path`, params: { recordId: id } }) } export function queryListByData(params) { const { app, stream, year, month, mediaServerId } = params return request({ method: 'get', url: `/api/cloud/record/date/list`, params: { app: app, stream: stream, year: year, month: month, mediaServerId: mediaServerId } }) } export function loadRecord(params) { const { app, stream, cloudRecordId } = params return request({ method: 'get', url: `/api/cloud/record/loadRecord`, params: { app: app, stream: stream, cloudRecordId: cloudRecordId } }) } export function seek(params) { const { mediaServerId, app, stream, seek, schema } = params return request({ method: 'get', url: `/api/cloud/record/seek`, params: { mediaServerId: mediaServerId, app: app, stream: stream, seek: seek, schema: schema } }) } export function speed(params) { const { mediaServerId, app, stream, speed, schema } = params return request({ method: 'get', url: `/api/cloud/record/speed`, params: { mediaServerId: mediaServerId, app: app, stream: stream, speed: speed, schema: schema } }) } export function addTask(params) { const { app, stream, mediaServerId, startTime, endTime } = params return request({ method: 'get', url: `/api/cloud/record/task/add`, params: { app: app, stream: stream, mediaServerId: mediaServerId, startTime: startTime, endTime: endTime } }) } export function queryTaskList(params) { const { mediaServerId, isEnd } = params return request({ method: 'get', url: `/api/cloud/record/task/list`, params: { mediaServerId: mediaServerId, isEnd: isEnd } }) } export function deleteRecord(ids) { return request({ method: 'delete', url: `/api/cloud/record/delete`, data: { ids: ids } }) } export function queryList(params) { const { app, stream, query, callId, startTime, endTime, mediaServerId, page, count, ascOrder } = params return request({ method: 'get', url: `/api/cloud/record/list`, params: { app: app, stream: stream, query: query, callId: callId, startTime: startTime, endTime: endTime, mediaServerId: mediaServerId, page: page, count: count, ascOrder: ascOrder } }) } ================================================ FILE: web/src/api/commonChannel.js ================================================ import request from '@/utils/request' // 通用通道API export function queryOne(id) { return request({ method: 'get', url: '/api/common/channel/one', params: { id: id } }) } export function getIndustryList() { return request({ method: 'get', url: '/api/common/channel/industry/list' }) } export function getTypeList() { return request({ method: 'get', url: '/api/common/channel/type/list' }) } export function getNetworkIdentificationList() { return request({ method: 'get', url: '/api/common/channel/network/identification/list' }) } export function update(data) { return request({ method: 'post', url: '/api/common/channel/update', data: data }) } export function reset(data) { return request({ method: 'post', url: '/api/common/channel/reset', data: data }) } export function add(data) { return request({ method: 'post', url: '/api/common/channel/add', data: data }) } export function getList(params) { const { page, count, query, online, hasRecordPlan, channelType, civilCode, parentDeviceId } = params return request({ method: 'get', url: '/api/common/channel/list', params: { page: page, count: count, channelType: channelType, query: query, online: online, hasRecordPlan: hasRecordPlan, civilCode: civilCode, parentDeviceId: parentDeviceId } }) } export function getCivilCodeList(params) { const { page, count, channelType, query, online, civilCode } = params return request({ method: 'get', url: '/api/common/channel/civilcode/list', params: { page: page, count: count, channelType: channelType, query: query, online: online, civilCode: civilCode } }) } export function getUnusualCivilCodeList(params) { const { page, count, channelType, query, online } = params return request({ method: 'get', url: '/api/common/channel/civilCode/unusual/list', params: { page: page, count: count, channelType: channelType, query: query, online: online } }) } export function getUnusualParentList(params) { const { page, count, channelType, query, online } = params return request({ method: 'get', url: '/api/common/channel/parent/unusual/list', params: { page: page, count: count, channelType: channelType, query: query, online: online } }) } export function clearUnusualCivilCodeList(params) { const { all, channelIds } = params return request({ method: 'post', url: '/api/common/channel/civilCode/unusual/clear', data: { all: all, channelIds: channelIds } }) } export function clearUnusualParentList(params) { const { all, channelIds } = params return request({ method: 'post', url: '/api/common/channel/parent/unusual/clear', data: { all: all, channelIds: channelIds } }) } export function getParentList(params) { const { page, count, channelType, query, online, groupDeviceId } = params return request({ method: 'get', url: '/api/common/channel/parent/list', params: { page: page, count: count, channelType: channelType, query: query, online: online, groupDeviceId: groupDeviceId } }) } export function addToRegion(params) { const { civilCode, channelIds } = params return request({ method: 'post', url: '/api/common/channel/region/add', data: { civilCode: civilCode, channelIds: channelIds } }) } export function deleteFromRegion(channels) { return request({ method: 'post', url: '/api/common/channel/region/delete', data: { channelIds: channels } }) } export function addDeviceToRegion(params) { const { civilCode, deviceIds } = params return request({ method: 'post', url: '/api/common/channel/region/device/add', data: { civilCode: civilCode, deviceIds: deviceIds } }) } export function deleteDeviceFromRegion(deviceIds) { return request({ method: 'post', url: '/api/common/channel/region/device/delete', data: { deviceIds: deviceIds } }) } export function addToGroup(params) { const { parentId, businessGroup, channelIds } = params return request({ method: 'post', url: '/api/common/channel/group/add', data: { parentId: parentId, businessGroup: businessGroup, channelIds: channelIds } }) } export function deleteFromGroup(channels) { return request({ method: 'post', url: '/api/common/channel/group/delete', data: { channelIds: channels } }) } export function addDeviceToGroup(params) { const { parentId, businessGroup, deviceIds } = params return request({ method: 'post', url: '/api/common/channel/group/device/add', data: { parentId: parentId, businessGroup: businessGroup, deviceIds: deviceIds } }) } export function deleteDeviceFromGroup(deviceIds) { return request({ method: 'post', url: '/api/common/channel/group/device/delete', data: { deviceIds: deviceIds } }) } export function playChannel(channelId) { return request({ method: 'get', url: '/api/common/channel/play', params: { channelId: channelId } }) } export function stopPlayChannel(channelId) { return request({ method: 'get', url: '/api/common/channel/play/stop', params: { channelId: channelId } }) } // 前端控制 export function setSpeedForScan({ channelId, scanId, speed }) { return request({ method: 'get', url: '/api/common/channel/front-end/scan/set/speed', params: { channelId: channelId, scanId: scanId, speed: speed } }) } export function setLeftForScan({ channelId, scanId }) { return request({ method: 'get', url: '/api/common/channel/front-end/scan/set/left', params: { channelId: channelId, scanId: scanId } }) } export function setRightForScan({ channelId, scanId }) { return request({ method: 'get', url: '/api/common/channel/front-end/scan/set/right', params: { channelId: channelId, scanId: scanId } }) } export function startScan({ channelId, scanId }) { return request({ method: 'get', url: '/api/common/channel/front-end/scan/start', params: { channelId: channelId, scanId: scanId } }) } export function stopScan({ channelId, scanId }) { return request({ method: 'get', url: '/api/common/channel/front-end/scan/stop', params: { channelId: channelId, scanId: scanId } }) } export function queryPreset(channelId) { return request({ method: 'get', url: '/api/common/channel/front-end/preset/query', params: { channelId: channelId } }) } export function addPointForCruise({ channelId, tourId, presetId }) { return request({ method: 'get', url: '/api/common/channel/front-end/tour/point/add', params: { channelId: channelId, tourId: tourId, presetId: presetId } }) } export function deletePointForCruise({ channelId, tourId, presetId }) { return request({ method: 'get', url: '/api/common/channel/front-end/tour/point/delete', params: { channelId: channelId, tourId: tourId, presetId: presetId } }) } export function setCruiseSpeed({ channelId, tourId, presetId , speed }) { return request({ method: 'get', url: '/api/common/channel/front-end/tour/speed', params: { channelId: channelId, tourId: tourId, presetId: presetId, speed: speed } }) } export function setCruiseTime({ channelId, tourId, presetId, time }) { return request({ method: 'get', url: '/api/common/channel/front-end/tour/time', params: { channelId: channelId, tourId: tourId, presetId: presetId, time: time } }) } export function startCruise({ channelId, tourId }) { return request({ method: 'get', url: '/api/common/channel/front-end/tour/start', params: { channelId: channelId, tourId: tourId } }) } export function stopCruise({ channelId, tourId }) { return request({ method: 'get', url: '/api/common/channel/front-end/tour/stop', params: { channelId: channelId, tourId: tourId } }) } export function addPreset({ channelId, presetId, presetName }) { return request({ method: 'get', url: '/api/common/channel/front-end/preset/add', params: { channelId: channelId, presetId: presetId, presetName: presetName } }) } export function callPreset({ channelId, presetId }) { return request({ method: 'get', url: '/api/common/channel/front-end/preset/call', params: { channelId: channelId, presetId: presetId } }) } export function deletePreset({ channelId, presetId }) { return request({ method: 'get', url: '/api/common/channel/front-end/preset/delete', params: { channelId: channelId, presetId: presetId } }) } /** * command: on 开启, off 关闭 */ export function auxiliary({ channelId, command, auxiliaryId }) { return request({ method: 'get', url: '/api/common/channel/front-end/auxiliary', params: { channelId: channelId, command: command, auxiliaryId: auxiliaryId } }) } /** * command: on 开启, off 关闭 */ export function wiper({ channelId, command }) { return request({ method: 'get', url: '/api/common/channel/front-end/wiper', params: { channelId: channelId, command: command } }) } export function ptz({ channelId, command, panSpeed, tiltSpeed, zoomSpeed }) { return request({ method: 'get', url: '/api/common/channel/front-end/ptz', params: { channelId: channelId, command: command, panSpeed: panSpeed, tiltSpeed: tiltSpeed, zoomSpeed: zoomSpeed } }) } export function iris({ channelId, command, speed }) { return request({ method: 'get', url: '/api/common/channel/front-end/fi/iris', params: { channelId: channelId, command: command, speed: speed } }) } export function focus({ channelId, command, speed }) { return request({ method: 'get', url: '/api/common/channel/front-end/fi/focus', params: { channelId: channelId, command: command, speed: speed } }) } export function queryRecord({ channelId, startTime, endTime }) { return request({ method: 'get', url: '/api/common/channel/playback/query', params: { channelId: channelId, startTime: startTime, endTime: endTime } }) } export function playback({ channelId, startTime, endTime }) { return request({ method: 'get', url: '/api/common/channel/playback', params: { channelId: channelId, startTime: startTime, endTime: endTime } }) } export function stopPlayback({ channelId, stream }) { return request({ method: 'get', url: '/api/common/channel/playback/stop', params: { channelId: channelId, stream: stream } }) } export function pausePlayback({ channelId, stream}) { return request({ method: 'get', url: '/api/common/channel/playback/pause', params: { channelId: channelId, stream: stream } }) } export function resumePlayback({ channelId, stream}) { return request({ method: 'get', url: '/api/common/channel/playback/resume', params: { channelId: channelId, stream: stream } }) } export function seekPlayback({ channelId, stream, seekTime}) { return request({ method: 'get', url: '/api/common/channel/playback/seek', params: { channelId: channelId, stream: stream, seekTime: seekTime } }) } export function speedPlayback({ channelId, stream, speed}) { return request({ method: 'get', url: '/api/common/channel/playback/speed', params: { channelId: channelId, stream: stream, speed: speed } }) } export function getAllForMap({ query, online, hasRecordPlan, channelType }) { return request({ method: 'get', url: '/api/common/channel/map/list', params: { query: query, online: online, hasRecordPlan: hasRecordPlan, channelType: channelType } }) } export function saveLevel(data) { return request({ method: 'post', url: '/api/common/channel/map/save-level', data: data }) } export function resetLevel() { return request({ method: 'post', url: '/api/common/channel/map/reset-level' }) } export function clearThin(id) { return request({ method: 'get', url: '/api/common/channel/map/thin/clear', params: { id: id } }) } export function thinProgress(id) { return request({ method: 'get', url: '/api/common/channel/map/thin/progress', params: { id: id } }) } export function saveThin(id) { return request({ method: 'get', url: '/api/common/channel/map/thin/save', params: { id: id } }) } export function drawThin(data) { return request({ method: 'post', url: '/api/common/channel/map/thin/draw', data: data }) } export function test() { return request({ method: 'get', url: '/api/sy/camera/list/ids', params: { deviceIds: ['a', 'b', 'c'].join(','), geoCoordSys: 'GCJ02', traditional: true } }) } ================================================ FILE: web/src/api/device.js ================================================ import request from '@/utils/request' // 国标设备API export function queryDeviceSyncStatus(deviceId) { return request({ method: 'get', url: `/api/device/query/sync_status`, params: { deviceId: deviceId } }) } export function queryDevices(params) { const { page, count, query, status } = params return request({ method: 'get', url: `/api/device/query/devices`, params: { page: page, count: count, query: query, status: status } }) } export function deleteDevice(deviceId) { return request({ method: 'delete', url: `/api/device/query/devices/${deviceId}/delete` }) } export function sync(deviceId) { return request({ method: 'get', url: `/api/device/query/devices/${deviceId}/sync` }) } export function updateDeviceTransport(deviceId, streamMode) { return request({ method: 'post', url: `/api/device/query/transport/${deviceId}/${streamMode}` }) } export function setGuard(deviceId) { return request({ method: 'get', url: `/api/device/control/guard`, params: { deviceId: deviceId, guardCmd: 'SetGuard' } }) } export function resetGuard(deviceId) { return request({ method: 'get', url: `/api/device/control/guard`, params: { deviceId: deviceId, guardCmd: 'ResetGuard' } }) } export function subscribeCatalog(params) { const { id, cycle } = params return request({ method: 'get', url: `/api/device/query/subscribe/catalog`, params: { id: id, cycle: cycle } }) } export function subscribeMobilePosition(params) { const { id, cycle, interval } = params return request({ method: 'get', url: `/api/device/query/subscribe/mobile-position`, params: { id: id, cycle: cycle, interval: interval } }) } export function queryBasicParam(deviceId) { return request({ method: 'get', url: `/api/device/config/query/${deviceId}/BasicParam` }) } export function queryChannelOne(params) { const { deviceId, channelDeviceId } = params return request({ method: 'get', url: '/api/device/query/channel/one', params: { deviceId: deviceId, channelDeviceId: channelDeviceId } }) } export function queryChannels(deviceId, params) { const { page, count, query, online, channelType, catalogUnderDevice } = params return request({ method: 'get', url: `/api/device/query/devices/${deviceId}/channels`, params: { page: page, count: count, query: query, online: online, channelType: channelType, catalogUnderDevice: catalogUnderDevice } }) } export function queryHasStreamChannels(params) { const {page, count, query} = params return request({ method: 'get', url: `/api/device/query/streams`, params: { page: page, count: count, query: query } }) } export function deviceRecord(params) { const { deviceId, channelId, recordCmdStr } = params return request({ method: 'get', url: `/api/device/control/record`, params: { deviceId: deviceId, channelId: channelId, recordCmdStr: recordCmdStr } }) } export function querySubChannels(params, deviceId, parentChannelId) { const { page, count, query, online, channelType } = params return request({ method: 'get', url: `/api/device/query/sub_channels/${deviceId}/${parentChannelId}/channels`, params: { page: page, count: count, query: query, online: online, channelType: channelType } }) } export function queryChannelTree(params) { const { parentId, page, count } = params return request({ method: 'get', url: `/api/device/query/tree/channel/${this.deviceId}`, params: { parentId: parentId, page: page, count: count } }) } export function changeChannelAudio(params) { const { channelId, audio } = params return request({ method: 'post', url: `/api/device/query/channel/audio`, params: { channelId: channelId, audio: audio } }) } export function updateChannelStreamIdentification(params) { const { deviceDbId, streamIdentification, id } = params return request({ method: 'post', url: `/api/device/query/channel/stream/identification/update/`, params: { deviceDbId: deviceDbId, id: id, streamIdentification: streamIdentification } }) } export function update(data) { return request({ method: 'post', url: `/api/device/query/device/update`, data: data }) } export function add(data) { return request({ method: 'post', url: `/api/device/query/device/add`, data: data }) } export function queryDeviceOne(deviceId) { return request({ method: 'get', url: `/api/device/query/devices/${deviceId}` }) } export function queryDeviceTree(params, deviceId) { const { page, count, parentId, onlyCatalog } = params return request({ method: 'get', url: `/api/device/query/tree/${deviceId}`, params: { page: page, count: count, parentId: parentId, onlyCatalog: onlyCatalog } }) } ================================================ FILE: web/src/api/frontEnd.js ================================================ import request from '@/utils/request' // 前端控制 export function setSpeedForScan([deviceId, channelDeviceId, scanId, speed]) { return request({ method: 'get', url: `/api/front-end/scan/set/speed/${deviceId}/${channelDeviceId}`, params: { scanId: scanId, speed: speed } }) } export function setLeftForScan([deviceId, channelDeviceId, scanId]) { return request({ method: 'get', url: `/api/front-end/scan/set/left/${deviceId}/${channelDeviceId}`, params: { scanId: scanId } }) } export function setRightForScan([deviceId, channelDeviceId, scanId]) { return request({ method: 'get', url: `/api/front-end/scan/set/right/${deviceId}/${channelDeviceId}`, params: { scanId: scanId } }) } export function startScan([deviceId, channelDeviceId, scanId]) { return request({ method: 'get', url: `/api/front-end/scan/start/${deviceId}/${channelDeviceId}`, params: { scanId: scanId } }) } export function stopScan([deviceId, channelDeviceId, scanId]) { return request({ method: 'get', url: `/api/front-end/scan/stop/${deviceId}/${channelDeviceId}`, params: { scanId: scanId } }) } export function queryPreset([deviceId, channelDeviceId]) { return request({ method: 'get', url: `/api/front-end/preset/query/${deviceId}/${channelDeviceId}` }) } export function addPointForCruise([deviceId, channelDeviceId, cruiseId, presetId]) { return request({ method: 'get', url: `/api/front-end/cruise/point/add/${deviceId}/${channelDeviceId}`, params: { cruiseId: cruiseId, presetId: presetId } }) } export function deletePointForCruise([deviceId, channelDeviceId, cruiseId, presetId]) { return request({ method: 'get', url: `/api/front-end/cruise/point/delete/${deviceId}/${channelDeviceId}`, params: { cruiseId: cruiseId, presetId: presetId } }) } export function setCruiseSpeed([deviceId, channelDeviceId, cruiseId, cruiseSpeed]) { return request({ method: 'get', url: `/api/front-end/cruise/speed/${deviceId}/${channelDeviceId}`, params: { cruiseId: cruiseId, speed: cruiseSpeed } }) } export function setCruiseTime([deviceId, channelDeviceId, cruiseId, cruiseTime]) { return request({ method: 'get', url: `/api/front-end/cruise/time/${deviceId}/${channelDeviceId}`, params: { cruiseId: cruiseId, time: cruiseTime } }) } export function startCruise([deviceId, channelDeviceId, cruiseId]) { return request({ method: 'get', url: `/api/front-end/cruise/start/${deviceId}/${channelDeviceId}`, params: { cruiseId: cruiseId } }) } export function stopCruise([deviceId, channelDeviceId, cruiseId]) { return request({ method: 'get', url: `/api/front-end/cruise/stop/${deviceId}/${channelDeviceId}`, params: { cruiseId: cruiseId } }) } export function addPreset([deviceId, channelDeviceId, presetId]) { return request({ method: 'get', url: `/api/front-end/preset/add/${deviceId}/${channelDeviceId}`, params: { presetId: presetId } }) } export function callPreset([deviceId, channelDeviceId, presetId]) { return request({ method: 'get', url: `/api/front-end/preset/call/${deviceId}/${channelDeviceId}`, params: { presetId: presetId } }) } export function deletePreset([deviceId, channelDeviceId, presetId]) { return request({ method: 'get', url: `/api/front-end/preset/delete/${deviceId}/${channelDeviceId}`, params: { presetId: presetId } }) } /** * command: on 开启, off 关闭 */ export function auxiliary([deviceId, channelDeviceId, command, switchId]) { return request({ method: 'get', url: `/api/front-end/auxiliary/${deviceId}/${channelDeviceId}`, params: { command: command, switchId: switchId } }) } /** * command: on 开启, off 关闭 */ export function wiper([deviceId, channelDeviceId, command]) { return request({ method: 'get', url: `/api/front-end/wiper/${deviceId}/${channelDeviceId}`, params: { command: command } }) } export function ptz([deviceId, channelId, command, horizonSpeed, verticalSpeed, zoomSpeed]) { return request({ method: 'get', url: `/api/front-end/ptz/${deviceId}/${channelId}`, params: { command: command, horizonSpeed: horizonSpeed, verticalSpeed: verticalSpeed, zoomSpeed: zoomSpeed } }) } export function iris([deviceId, channelId, command, speed]) { return request({ method: 'get', url: `/api/front-end/fi/iris/${deviceId}/${channelId}`, params: { command: command, speed: speed } }) } export function focus([deviceId, channelDeviceId, command, speed]) { return request({ method: 'get', url: `/api/front-end/fi/focus/${deviceId}/${channelDeviceId}`, params: { command: command, speed: speed } }) } ================================================ FILE: web/src/api/gbRecord.js ================================================ import request from '@/utils/request' export function query([deviceId, channelId, startTime, endTime]) { return request({ method: 'get', url: '/api/gb_record/query/' + deviceId + '/' + channelId + '?startTime=' + startTime + '&endTime=' + endTime }) } export function startDownLoad([deviceId, channelId, startTime, endTime, downloadSpeed]) { return request({ url: '/api/gb_record/download/start/' + deviceId + '/' + channelId + '?startTime=' + startTime + '&endTime=' + endTime + '&downloadSpeed=' + downloadSpeed }) } export function stopDownLoad(deviceId, channelId, streamId) { return request({ method: 'get', url: '/api/gb_record/download/stop/' + deviceId + '/' + channelId + '/' + streamId }) } export function queryDownloadProgress([deviceId, channelId, streamId]) { return request({ method: 'get', url: `/api/gb_record/download/progress/${deviceId}/${channelId}/${streamId}` }) } ================================================ FILE: web/src/api/group.js ================================================ import request from '@/utils/request' // 分组API export function update(data) { return request({ method: 'post', url: '/api/group/update', data: data }) } export function add(data) { return request({ method: 'post', url: '/api/group/add', data: data }) } export function getTreeList({ query, parent, hasChannel, page, count }) { return request({ method: 'get', url: `/api/group/tree/list`, params: { query: query, parent: parent, hasChannel: hasChannel, page: page, count: count } }) } export function deleteGroup(id) { return request({ method: 'delete', url: `/api/group/delete`, params: { id: id } }) } export function getPath(params) { const { deviceId, businessGroup } = params return request({ method: 'get', url: `/api/group/path`, params: { deviceId: deviceId, businessGroup: businessGroup } }) } export function queryTree(params) { const { page, count, query } = params return request({ method: 'get', url: `/api/group/tree/query`, params: { query: query, page: page, count: count } }) } ================================================ FILE: web/src/api/jtDevice.js ================================================ import request from '@/utils/request' // 部标设备API export function queryDevices({ page, count, query, online }) { return request({ method: 'get', url: '/api/jt1078/terminal/list', params: { page: page, count: count, query: query, online: online } }) } export function queryDeviceById(deviceId) { return request({ method: 'get', url: '/api/jt1078/terminal/query', params: { deviceId: deviceId } }) } export function update(form) { return request({ method: 'post', url: '/api/jt1078/terminal/update', params: form }) } export function add(form) { return request({ method: 'post', url: '/api/jt1078/terminal/add', params: form }) } export function deleteDevice(phoneNumber) { return request({ method: 'delete', url: '/api/jt1078/terminal/delete', params: { phoneNumber: phoneNumber } }) } export function queryChannels(params) { const { page, count, query, deviceId } = params return request({ method: 'get', url: '/api/jt1078/terminal/channel/list', params: { page: page, count: count, query: query, deviceId: deviceId } }) } export function play(params) { const { phoneNumber, channelId, type } = params return request({ method: 'get', url: '/api/jt1078/live/start', params: { phoneNumber: phoneNumber, channelId: channelId, type: type } }) } export function stopPlay(params) { const { phoneNumber, channelId } = params return request({ method: 'get', url: '/api/jt1078/live/stop', params: { phoneNumber: phoneNumber, channelId: channelId } }) } export function updateChannel(data) { return request({ method: 'post', url: '/api/jt1078/terminal/channel/update', data: data }) } export function addChannel(data) { return request({ method: 'post', url: '/api/jt1078/terminal/channel/add', data: data }) } export function ptz(params) { const { phoneNumber, channelId, command, speed } = params return request({ method: 'get', url: '/api/jt1078/ptz', params: { phoneNumber: phoneNumber, channelId: channelId, command: command, speed: speed } }) } export function wiper(params) { const { phoneNumber, channelId, command } = params return request({ method: 'get', url: '/api/jt1078/wiper', params: { phoneNumber: phoneNumber, channelId: channelId, command: command } }) } export function fillLight(params) { const { phoneNumber, channelId, command } = params return request({ method: 'get', url: '/api/jt1078/fill-light', params: { phoneNumber: phoneNumber, channelId: channelId, command: command } }) } export function queryRecordList(params) { const { phoneNumber, channelId, startTime, endTime } = params return request({ method: 'get', url: '/api/jt1078/record/list', params: { phoneNumber: phoneNumber, channelId: channelId, startTime: startTime, endTime: endTime } }) } export function startPlayback(params) { const { phoneNumber, channelId, startTime, endTime, type, rate, playbackType, playbackSpeed } = params return request({ method: 'get', url: '/api/jt1078/playback/start/', params: { phoneNumber: phoneNumber, channelId: channelId, startTime: startTime, endTime: endTime, type: type, rate: rate, playbackType: playbackType, playbackSpeed: playbackSpeed } }) } export function getRecordTempUrl({ phoneNumber, channelId, startTime, endTime, alarmSign, mediaType, streamType, storageType }) { return request({ method: 'get', url: '/api/jt1078/playback/downloadUrl', params: { phoneNumber: phoneNumber, channelId: channelId, startTime: startTime, endTime: endTime, alarmSign: alarmSign, mediaType: mediaType, streamType: streamType, storageType: storageType } }) } export function controlPlayback(params) { const { phoneNumber, channelId, command, playbackSpeed, time } = params return request({ method: 'get', url: '/api/jt1078/playback/control', params: { phoneNumber: phoneNumber, channelId: channelId, command: command, playbackSpeed: playbackSpeed, time: time } }) } export function stopPlayback(params) { const { phoneNumber, channelId, streamId } = params return request({ method: 'get', url: '/api/jt1078/playback/stop/', params: { phoneNumber: phoneNumber, channelId: channelId, streamId: streamId } }) } export function queryConfig(phoneNumber) { return request({ method: 'get', url: '/api/jt1078/config/get', params: { phoneNumber: phoneNumber } }) } export function setConfig(data) { return request({ method: 'post', url: '/api/jt1078/config/set', data: data }) } export function queryAttribute(phoneNumber) { return request({ method: 'get', url: '/api/jt1078/attribute', params: { phoneNumber: phoneNumber } }) } export function linkDetection(phoneNumber) { return request({ method: 'get', url: '/api/jt1078/link-detection', params: { phoneNumber: phoneNumber } }) } export function queryPosition(phoneNumber) { return request({ method: 'get', url: '/api/jt1078/position-info', params: { phoneNumber: phoneNumber } }) } export function sendTextMessage(data) { return request({ method: 'post', url: '/api/jt1078/text-msg', data: data }) } export function telephoneCallback({ phoneNumber, sign, destPhoneNumber }) { return request({ method: 'get', url: '/api/jt1078/telephone-callback', params: { phoneNumber: phoneNumber, sign: sign, destPhoneNumber: destPhoneNumber } }) } export function queryDriverInfo(phoneNumber) { return request({ method: 'get', url: '/api/jt1078/driver-information', params: { phoneNumber: phoneNumber } }) } export function factoryReset(phoneNumber) { return request({ method: 'post', url: '/api/jt1078/control/factory-reset', params: { phoneNumber: phoneNumber } }) } export function reset(phoneNumber) { return request({ method: 'post', url: '/api/jt1078/control/reset', params: { phoneNumber: phoneNumber } }) } export function connection(data) { return request({ method: 'post', url: '/api/jt1078/control/connection', data: data }) } export function controlDoor({ phoneNumber, open}) { return request({ method: 'get', url: '/api/jt1078/control/door', params: { phoneNumber: phoneNumber, open: open } }) } export function queryMediaAttribute(phoneNumber) { return request({ method: 'get', url: '/api/jt1078/media/attribute', params: { phoneNumber: phoneNumber } }) } export function queryMediaData(data) { return request({ method: 'post', url: '/api/jt1078/media/list', data: data }) } export function setPhoneBook(data) { return request({ method: 'post', url: '/api/jt1078/set-phone-book', data: data }) } export function shooting(data) { return request({ method: 'post', url: '/api/jt1078/shooting', data: data }) } export function startTalk({ phoneNumber, channelId }) { return request({ method: 'get', url: '/api/jt1078/talk/start', params: { phoneNumber: phoneNumber, channelId: channelId } }) } export function stopTalk({ phoneNumber, channelId }) { return request({ method: 'get', url: '/api/jt1078/talk/stop', params: { phoneNumber: phoneNumber, channelId: channelId } }) } ================================================ FILE: web/src/api/log.js ================================================ import request from '@/utils/request' export function queryList(params) { const { query, startTime, endTime } = params return request({ method: 'get', url: `/api/log/list`, params: { query: query, startTime: startTime, endTime: endTime } }) } ================================================ FILE: web/src/api/platform.js ================================================ import request from '@/utils/request' export function update(data) { return request({ method: 'post', url: '/api/platform/update', data: data }) } export function add(data) { return request({ method: 'post', url: '/api/platform/add', data: data }) } export function exit(deviceGbId) { return request({ method: 'get', url: `/api/platform/exit/${deviceGbId}` }) } export function remove(id) { return request({ method: 'delete', url: `/api/platform/delete`, params: { id: id } }) } export function pushChannel(id) { return request({ method: 'get', url: `/api/platform/channel/push`, params: { id: id } }) } export function getServerConfig() { return request({ method: 'get', url: `/api/platform/server_config` }) } export function query(params) { const { count, page, query } = params return request({ method: 'get', url: `/api/platform/query`, params: { count: count, page: page, query: query } }) } export function getChannelList(params) { const { page, count, query, online, channelType, platformId, hasShare } = params return request({ method: 'get', url: `/api/platform/channel/list`, params: { page: page, count: count, query: query, online: online, channelType: channelType, platformId: platformId, hasShare: hasShare } }) } export function addChannel(params) { const { platformId, channelIds, all } = params return request({ method: 'post', url: `/api/platform/channel/add`, data: { platformId: platformId, channelIds: channelIds, all: all } }) } export function addChannelByDevice(params) { const { platformId, deviceIds } = params return request({ method: 'post', url: `/api/platform/channel/device/add`, data: { platformId: platformId, deviceIds: deviceIds } }) } export function removeChannelByDevice(params) { const { platformId, deviceIds } = params return request({ method: 'post', url: `/api/platform/channel/device/remove`, data: { platformId: platformId, deviceIds: deviceIds } }) } export function removeChannel(params) { const { platformId, channelIds, all } = params return request({ method: 'delete', url: `/api/platform/channel/remove`, data: { platformId: platformId, channelIds: channelIds, all: all } }) } export function updateCustomChannel(data) { return request({ method: 'post', url: `/api/platform/channel/custom/update`, data: data }) } ================================================ FILE: web/src/api/play.js ================================================ import request from '@/utils/request' // 实时流播放API export function play(deviceId, channelId) { return request({ method: 'get', url: '/api/play/start/' + deviceId + '/' + channelId }) } export function stop(deviceId, channelId) { return request({ method: 'get', url: '/api/play/stop/' + deviceId + "/" + channelId, }) } export function broadcastStart(deviceId, channelId, broadcastMode) { return request({ method: 'get', url: '/api/play/broadcast/' + deviceId + '/' + channelId + "?timeout=30&broadcastMode=" + broadcastMode }) } export function broadcastStop(deviceId, channelId, ) { return request({ method: 'get', url: '/api/play/broadcast/stop/' + deviceId + '/' + channelId }) } ================================================ FILE: web/src/api/playback.js ================================================ import request from '@/utils/request' // 回放流播放API export function play([deviceId, channelId, startTime, endTime]) { return request({ method: 'get', url: '/api/playback/start/' + deviceId + '/' + channelId + '?startTime=' + startTime + '&endTime=' + endTime }) } export function resume(streamId) { return request({ method: 'get', url: '/api/playback/resume/' + streamId }) } export function pause(streamId) { return request({ method: 'get', url: '/api/playback/pause/' + streamId }) } export function setSpeed([streamId, speed]) { return request({ method: 'get', url: `/api/playback/speed/${streamId}/${speed}` }) } export function stop(deviceId, channelId, streamId) { return request({ method: 'get', url: '/api/playback/stop/' + deviceId + '/' + channelId + '/' + streamId }) } ================================================ FILE: web/src/api/recordPlan.js ================================================ import request from '@/utils/request' export function getPlan(id) { return request({ method: 'get', url: '/api/record/plan/get', params: { planId: id } }) } export function addPlan(params) { const { name, planList } = params return request({ method: 'post', url: '/api/record/plan/add', data: { name: name, planItemList: planList } }) } export function update(params) { const { id, name, planList } = params return request({ method: 'post', url: '/api/record/plan/update', data: { id: id, name: name, planItemList: planList } }) } export function queryList(params) { const { page, count, query } = params return request({ method: 'get', url: `/api/record/plan/query`, params: { page: page, count: count, query: query } }) } export function deletePlan(id) { return request({ method: 'delete', url: '/api/record/plan/delete', params: { planId: id } }) } export function queryChannelList(params) { const { page, count, channelType, query, online, planId , hasLink } = params return request({ method: 'get', url: `/api/record/plan/channel/list`, params: { page: page, count: count, query: query, online: online, channelType: channelType, planId: planId, hasLink: hasLink } }) } export function linkPlan(data) { return request({ method: 'post', url: `/api/record/plan/link`, data: data }) } ================================================ FILE: web/src/api/region.js ================================================ import request from '@/utils/request' // 行政区划API export function getTreeList(params) { const {query, parent, hasChannel} = params return request({ method: 'get', url: `/api/region/tree/list`, params: { query: query, parent: parent, hasChannel: hasChannel } }) } export function deleteRegion(id) { return request({ method: "delete", url: `/api/region/delete`, params: { id: id, } }) } export function description(civilCode) { return request({ method: 'get', url: `/api/region/description`, params: { civilCode: civilCode, } }) } export function addByCivilCode(civilCode) { return request({ method: 'get', url: `/api/region/addByCivilCode`, params: { civilCode: civilCode, } }) } export function queryChildListInBase(parent) { return request({ method: 'get', url: "/api/region/base/child/list", params: { parent: parent, } }) } export function update(data) { return request({ method: 'post', url: "/api/region/update", data: data }) } export function add(data) { return request({ method: 'post', url: "/api/region/add", data: data }) } export function queryPath(deviceId) { return request({ method: 'get', url: `/api/region/path`, params: { deviceId: deviceId, } }) } export function queryTree(params) { const { page, count, query } = params return request({ method: 'get', url: `/api/region/tree/query`, params: { query: query, page: page, count: count } }) } ================================================ FILE: web/src/api/role.js ================================================ import request from '@/utils/request' // 云端录像API export function getAll() { return request({ method: 'get', url: '/api/role/all' }) } ================================================ FILE: web/src/api/server.js ================================================ import request from '@/utils/request' // 服务API export function getOnlineMediaServerList() { return request({ method: 'get', url: `/api/server/media_server/online/list` }) } export function getMediaServerList() { return request({ method: 'get', url: `/api/server/media_server/list` }) } export function getMediaServer(id) { return request({ method: 'get', url: `/api/server/media_server/one/` + id }) } export function checkMediaServer(params) { const { ip, httpPort, secret, type } = params return request({ method: 'get', url: `/api/server/media_server/check`, params: { ip: ip, port: httpPort, secret: secret, type: type } }) } export function checkMediaServerRecord(params) { const { ip, port } = params return request({ method: 'get', url: `/api/server/media_server/record/check`, params: { ip: ip, port: port } }) } export function saveMediaServer(formData) { return request({ method: 'post', url: `/api/server/media_server/save`, data: formData }) } export function deleteMediaServer(id) { return request({ method: 'delete', url: `/api/server/media_server/delete`, params: { id: id } }) } export function getSystemConfig() { return request({ method: 'get', url: `/api/server/system/configInfo` }) } export function getMediaInfo(params) { const { app, stream, mediaServerId } = params return request({ method: 'get', url: `/api/server/media_server/media_info`, params: { app: app, stream: stream, mediaServerId: mediaServerId } }) } export function getSystemInfo() { return request({ method: 'get', url: `/api/server/system/info` }) } export function getMediaServerLoad() { return request({ method: 'get', url: `/api/server/media_server/load` }) } export function getResourceInfo() { return request({ method: 'get', url: `/api/server/resource/info` }) } export function info() { return request({ method: 'get', url: `/api/server/info` }) } export function getMapConfig() { return request({ method: 'get', url: `/api/server/map/config` }) } export function getModelList() { return request({ method: 'get', url: `/api/server/map/model-icon/list` }) } ================================================ FILE: web/src/api/streamProxy.js ================================================ import request from '@/utils/request' // 拉流代理API export function queryFfmpegCmdList(mediaServerId) { return request({ method: 'get', url: `/api/proxy/ffmpeg_cmd/list`, params: { mediaServerId: mediaServerId } }) } export function save(data) { return request({ method: 'post', url: `/api/proxy/save`, data: data }) } export function update(data) { return request({ method: 'post', url: `/api/proxy/update`, data: data }) } export function add(data) { return request({ method: 'post', url: `/api/proxy/add`, data: data }) } export function queryList(params) { const { page, count, query, pulling, mediaServerId } = params return request({ method: 'get', url: `/api/proxy/list`, params: { page: page, count: count, query: query, pulling: pulling, mediaServerId: mediaServerId } }) } export function play(id) { return request({ method: 'get', url: `/api/proxy/start`, params: { id: id } }) } export function stopPlay(id) { return request({ method: 'get', url: `/api/proxy/stop`, params: { id: id } }) } export function remove(id) { return request({ method: 'delete', url: '/api/proxy/delete', params: { id: id } }) } ================================================ FILE: web/src/api/streamPush.js ================================================ import request from '@/utils/request' // 推流列表API export function saveToGb(data) { return request({ method: 'post', url: `/api/push/save_to_gb`, data: data }) } export function add(data) { return request({ method: 'post', url: `/api/push/add`, data: data }) } export function update(data) { return request({ method: 'post', url: '/api/push/update', data: data }) } export function queryList(params) { const { page, count, query, pushing, mediaServerId } = params return request({ method: 'get', url: `/api/push/list`, params: { page: page, count: count, query: query, pushing: pushing, mediaServerId: mediaServerId } }) } export function play(id) { return request({ method: 'get', url: '/api/push/start', params: { id: id } }) } export function remove(id) { return request({ method: 'post', url: '/api/push/remove', params: { id: id } }) } export function removeFormGb(data) { return request({ method: 'delete', url: '/api/push/remove_form_gb', data: data }) } export function batchRemove(ids) { return request({ method: 'delete', url: '/api/push/batchRemove', data: { ids: ids } }) } ================================================ FILE: web/src/api/table.js ================================================ import request from '@/utils/request' export function getList(params) { return request({ url: '/vue-admin-template/table/list', method: 'get', params }) } ================================================ FILE: web/src/api/user.js ================================================ import request from '@/utils/request' export function login(params) { return request({ url: '/api/user/login', method: 'get', params: params }) } export function logout() { return request({ url: '/api/user/logout', method: 'get' }) } export function getUserInfo() { return request({ method: 'post', url: '/api/user/userInfo' }) } export function changePushKey(params) { const { pushKey, userId } = params return request({ method: 'post', url: '/api/user/changePushKey', params: { pushKey: pushKey, userId: userId } }) } export function queryList(params) { const { page, count } = params return request({ method: 'get', url: `/api/user/users`, params: { page: page, count: count } }) } export function removeById(id) { return request({ method: 'delete', url: `/api/user/delete?id=${id}` }) } export function add(params) { const { username, password, roleId } = params return request({ method: 'post', url: '/api/user/add', params: { username: username, password: password, roleId: roleId } }) } export function changePassword(params) { const { oldPassword, password } = params return request({ method: 'post', url: '/api/user/changePassword', params: { oldPassword: oldPassword, password: password } }) } export function changePasswordForAdmin(params) { const { password, userId } = params return request({ method: 'post', url: '/api/user/changePasswordForAdmin', params: { password: password, userId: userId } }) } ================================================ FILE: web/src/api/userApiKey.js ================================================ import request from '@/utils/request' export function remark(params) { const { id, remark } = params return request({ method: 'post', url: '/api/userApiKey/remark', params: { id: id, remark: remark } }) } export function queryList(params) { const { page, count } = params return request({ method: 'get', url: `/api/userApiKey/userApiKeys`, params: { page: page, count: count } }) } export function enable(id) { return request({ method: 'post', url: `/api/userApiKey/enable?id=${id}` }) } export function disable(id) { return request({ method: 'post', url: `/api/userApiKey/disable?id=${id}` }) } export function reset(id) { return request({ method: 'post', url: `/api/userApiKey/reset?id=${id}` }) } export function remove(id) { return request({ method: 'delete', url: `/api/userApiKey/delete?id=${id}` }) } export function add(params) { const { userId, app, enable, expiresAt, remark } = params return request({ method: 'post', url: '/api/userApiKey/add', params: { userId: userId, app: app, enable: enable, expiresAt: expiresAt, remark: remark } }) } ================================================ FILE: web/src/components/Breadcrumb/index.vue ================================================ ================================================ FILE: web/src/components/Hamburger/index.vue ================================================ ================================================ FILE: web/src/components/SvgIcon/index.vue ================================================ ================================================ FILE: web/src/directive/el-drag-dialog/drag.js ================================================ export default { bind(el, binding, vnode) { const dialogHeaderEl = el.querySelector('.el-dialog__header') const dragDom = el.querySelector('.el-dialog') dialogHeaderEl.style.cssText += ';cursor:move;' dragDom.style.cssText += ';top:0px;' // 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null); const getStyle = (function() { if (window.document.currentStyle) { return (dom, attr) => dom.currentStyle[attr] } else { return (dom, attr) => getComputedStyle(dom, false)[attr] } })() dialogHeaderEl.onmousedown = (e) => { // 鼠标按下,计算当前元素距离可视区的距离 const disX = e.clientX - dialogHeaderEl.offsetLeft const disY = e.clientY - dialogHeaderEl.offsetTop const dragDomWidth = dragDom.offsetWidth const dragDomHeight = dragDom.offsetHeight const screenWidth = document.body.clientWidth const screenHeight = document.body.clientHeight const minDragDomLeft = dragDom.offsetLeft const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth const minDragDomTop = dragDom.offsetTop const maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomHeight // 获取到的值带px 正则匹配替换 let styL = getStyle(dragDom, 'left') let styT = getStyle(dragDom, 'top') if (styL.includes('%')) { styL = +document.body.clientWidth * (+styL.replace(/\%/g, '') / 100) styT = +document.body.clientHeight * (+styT.replace(/\%/g, '') / 100) } else { styL = +styL.replace(/\px/g, '') styT = +styT.replace(/\px/g, '') } document.onmousemove = function(e) { // 通过事件委托,计算移动的距离 let left = e.clientX - disX let top = e.clientY - disY // 边界处理 if (-(left) > minDragDomLeft) { left = -minDragDomLeft } else if (left > maxDragDomLeft) { left = maxDragDomLeft } if (-(top) > minDragDomTop) { top = -minDragDomTop } else if (top > maxDragDomTop) { top = maxDragDomTop } // 移动当前元素 dragDom.style.cssText += `;left:${left + styL}px;top:${top + styT}px;` // emit onDrag event vnode.child.$emit('dragDialog') } document.onmouseup = function(e) { document.onmousemove = null document.onmouseup = null } } } } ================================================ FILE: web/src/directive/el-drag-dialog/index.js ================================================ import drag from './drag' const install = function(Vue) { Vue.directive('el-drag-dialog', drag) } if (window.Vue) { window['el-drag-dialog'] = drag Vue.use(install); // eslint-disable-line } drag.install = install export default drag ================================================ FILE: web/src/icons/index.js ================================================ import Vue from 'vue' import SvgIcon from '@/components/SvgIcon'// svg component // register globally Vue.component('svg-icon', SvgIcon) const req = require.context('./svg', false, /\.svg$/) const requireAll = requireContext => requireContext.keys().map(requireContext) requireAll(req) ================================================ FILE: web/src/icons/svgo.yml ================================================ # replace default config # multipass: true # full: true plugins: # - name # # or: # - name: false # - name: true # # or: # - name: # param1: 1 # param2: 2 - removeAttrs: attrs: - 'fill' - 'fill-rule' ================================================ FILE: web/src/layout/components/AppMain.vue ================================================ ================================================ FILE: web/src/layout/components/Navbar.vue ================================================ ================================================ FILE: web/src/layout/components/Sidebar/FixiOSBug.js ================================================ export default { computed: { device() { return this.$store.state.app.device } }, mounted() { // In order to fix the click on menu on the ios device will trigger the mouseleave bug // https://github.com/PanJiaChen/vue-element-admin/issues/1135 this.fixBugIniOS() }, methods: { fixBugIniOS() { const $subMenu = this.$refs.subMenu if ($subMenu) { const handleMouseleave = $subMenu.handleMouseleave $subMenu.handleMouseleave = (e) => { if (this.device === 'mobile') { return } handleMouseleave(e) } } } } } ================================================ FILE: web/src/layout/components/Sidebar/Item.vue ================================================ ================================================ FILE: web/src/layout/components/Sidebar/Link.vue ================================================ ================================================ FILE: web/src/layout/components/Sidebar/Logo.vue ================================================ ================================================ FILE: web/src/layout/components/Sidebar/SidebarItem.vue ================================================ ================================================ FILE: web/src/layout/components/Sidebar/index.vue ================================================ ================================================ FILE: web/src/layout/components/TagsView/ScrollPane.vue ================================================ ================================================ FILE: web/src/layout/components/TagsView/index.vue ================================================ ================================================ FILE: web/src/layout/components/dialog/changePassword.vue ================================================ ================================================ FILE: web/src/layout/components/index.js ================================================ export { default as Navbar } from './Navbar' export { default as Sidebar } from './Sidebar' export { default as AppMain } from './AppMain' export { default as TagsView } from './TagsView' ================================================ FILE: web/src/layout/index.vue ================================================ ================================================ FILE: web/src/layout/mixin/ResizeHandler.js ================================================ import store from '@/store' const { body } = document const WIDTH = 992 // refer to Bootstrap's responsive design export default { watch: { $route(route) { if (this.device === 'mobile' && this.sidebar.opened) { store.dispatch('app/closeSideBar', { withoutAnimation: false }) } } }, beforeMount() { window.addEventListener('resize', this.$_resizeHandler) }, beforeDestroy() { window.removeEventListener('resize', this.$_resizeHandler) }, mounted() { const isMobile = this.$_isMobile() if (isMobile) { store.dispatch('app/toggleDevice', 'mobile') store.dispatch('app/closeSideBar', { withoutAnimation: true }) } }, methods: { // use $_ for mixins properties // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential $_isMobile() { const rect = body.getBoundingClientRect() return rect.width - 1 < WIDTH }, $_resizeHandler() { if (!document.hidden) { const isMobile = this.$_isMobile() store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop') if (isMobile) { store.dispatch('app/closeSideBar', { withoutAnimation: true }) } } } } } ================================================ FILE: web/src/main.js ================================================ import Vue from 'vue' import 'normalize.css/normalize.css' // A modern alternative to CSS resets import ElementUI from 'element-ui' import 'element-ui/lib/theme-chalk/index.css' import locale from 'element-ui/lib/locale/lang/en' // lang i18n import '@/styles/index.scss' // global css import App from './App' import store from './store' import router from './router' import '@/icons' // icon import '@/permission' // permission control import VueClipboards from 'vue-clipboards' import Contextmenu from 'vue-contextmenujs' import VueClipboard from 'vue-clipboard2' /** * If you don't want to use mock-server * you want to use MockJs for mock api * you can execute: mockXHR() * * Currently MockJs will be used in the production environment, * please remove it before going online ! ! ! */ if (process.env.NODE_ENV === 'production') { const { mockXHR } = require('../mock') mockXHR() } Vue.use(ElementUI) Vue.use(VueClipboards) Vue.use(Contextmenu) Vue.use(VueClipboard) Vue.config.productionTip = false Vue.prototype.$channelTypeList = { 1: { id: 1, name: '国标设备', style: { color: '#409eff', borderColor: '#b3d8ff' } }, 2: { id: 2, name: '推流设备', style: { color: '#67c23a', borderColor: '#c2e7b0' } }, 3: { id: 3, name: '拉流代理', style: { color: '#e6a23c', borderColor: '#f5dab1' } }, 200: { id: 200, name: '部标设备', style: { color: '#fa6436', borderColor: '#f4997c' } } } new Vue({ el: '#app', router, store, render: h => h(App) }) ================================================ FILE: web/src/permission.js ================================================ import router from './router' import store from './store' import NProgress from 'nprogress' // progress bar import 'nprogress/nprogress.css' // progress bar style import { getToken, getName, getServerId } from '@/utils/auth' // get token from cookie import getPageTitle from '@/utils/get-page-title' NProgress.configure({ showSpinner: false }) // NProgress Configuration const whiteList = ['/login'] // no redirect whitelist router.beforeEach(async(to, from, next) => { // start progress bar NProgress.start() // set page title document.title = getPageTitle(to.meta.title) // determine whether the user has logged in const hasToken = getToken() if (hasToken) { if (to.path === '/login') { // if is logged in, redirect to the home page next({ path: '/' }) NProgress.done() } else { const hasGetUserInfo = store.getters.name if (!hasGetUserInfo) { store.commit('user/SET_NAME', getName()) store.commit('user/SET_SERVER_ID', getServerId()) } next() } } else { /* has no token*/ if (whiteList.indexOf(to.path) !== -1) { // in the free login whitelist, go directly next() } else { // other pages that do not have permission to access are redirected to the login page. next(`/login?redirect=${to.path}`) NProgress.done() } } }) router.afterEach(() => { // finish progress bar NProgress.done() }) ================================================ FILE: web/src/router/index.js ================================================ import Vue from 'vue' import Router from 'vue-router' Vue.use(Router) /* Layout */ import Layout from '@/layout' /** * Note: sub-menu only appear when route children.length >= 1 * Detail see: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html * * hidden: true if set true, item will not show in the sidebar(default is false) * alwaysShow: true if set true, will always show the root menu * if not set alwaysShow, when item has more than one children route, * it will becomes nested mode, otherwise not show the root menu * redirect: noRedirect if set noRedirect will no redirect in the breadcrumb * name:'router-name' the name is used by (must set!!!) * meta : { roles: ['admin','editor'] control the page roles (you can set multiple roles) title: 'title' the name show in sidebar and breadcrumb (recommend set) icon: 'svg-name'/'el-icon-x' the icon show in the sidebar breadcrumb: false if set false, the item will hidden in breadcrumb(default is true) activeMenu: '/example/list' if set path, the sidebar will highlight the path you set } */ /** * constantRoutes * a base page that does not have permission requirements * all roles can be accessed */ export const constantRoutes = [ { path: '/login', component: () => import('@/views/login/index'), hidden: true }, { path: '/404', component: () => import('@/views/404'), hidden: true }, { path: '/', component: Layout, redirect: '/dashboard', children: [{ path: 'dashboard', name: '控制台', component: () => import('@/views/dashboard/index'), meta: { title: '控制台', icon: 'dashboard', affix: true } }] }, { path: '/live', component: Layout, redirect: '/live', children: [{ path: '', name: 'Live', component: () => import('@/views/live/index'), meta: { title: '分屏监控', icon: 'live' } }] }, { path: '/channel', component: Layout, redirect: '/channel', onlyIndex: 0, children: [{ path: '/channel', name: 'Channel', component: () => import('@/views/channel/index'), meta: { title: '通道列表', icon: 'channelManger'} }, { path: '/channel/record/:channelId', name: 'CommonRecord', component: () => import('@/views/channel/record'), meta: { title: '设备录像' } } ] }, { path: '/map', component: Layout, redirect: '/map', children: [{ path: '', name: 'Map', component: () => import('@/views/map/index'), meta: { title: '电子地图', icon: 'map' } }] }, { path: '/device', component: Layout, name: '设备接入', meta: { title: '设备接入', icon: 'devices' }, children: [ { path: '/device', name: 'Device', component: () => import('@/views/device/index'), meta: { title: '国标设备', icon: 'device' } }, { hidden: true, path: '/device/record/:deviceId/:channelDeviceId', name: 'DeviceRecord', component: () => import('@/views/device/channel/record'), meta: { title: '国标录像' } }, { path: '/jtDevice', name: 'JTDevice', component: () => import('@/views/jtDevice/index'), meta: { title: '部标设备', icon: 'jtDevice' } }, { hidden: true, path: '/jtDevice/record/:phoneNumber/:channelId', name: 'JTDeviceRecord', component: () => import('@/views/jtDevice/channel/record'), meta: { title: '部标录像' } }, { path: '/push', name: 'PushList', component: () => import('@/views/streamPush/index'), meta: { title: '推流列表', icon: 'streamPush' } }, { path: '/proxy', name: 'Proxy', component: () => import('@/views/streamProxy/index'), meta: { title: '拉流代理', icon: 'streamProxy' } } ] }, { path: '/commonChannel', component: Layout, redirect: '/commonChannel/region', name: '组织结构', meta: { title: '组织结构', icon: 'tree' }, children: [ { path: 'region', name: 'Region', component: () => import('@/views/channel/region/index'), meta: { title: '行政区划', icon: 'region' } }, { path: 'group', name: 'Group', component: () => import('@/views/channel/group/index'), meta: { title: '业务分组', icon: 'tree' } } ] }, { path: '/recordPlan', component: Layout, redirect: '/recordPlan', children: [ { path: '', name: 'RecordPlan', component: () => import('@/views/recordPlan/index'), meta: { title: '录制计划', icon: 'recordPlan' } } ] }, { path: '/cloudRecord', component: Layout, redirect: '/cloudRecord', onlyIndex: 0, children: [ { path: '/cloudRecord', name: 'CloudRecord', component: () => import('@/views/cloudRecord/index'), meta: { title: '云端录像', icon: 'cloudRecord' } }, { path: '/cloudRecord/detail/:app/:stream', name: 'CloudRecordDetail', component: () => import('@/views/cloudRecord/detail'), meta: { title: '云端录像详情' } } ] }, { path: '/mediaServer', component: Layout, redirect: '/mediaServer', children: [ { path: '', name: 'MediaServer', component: () => import('@/views/mediaServer/index'), meta: { title: '媒体节点', icon: 'mediaServerList' } } ] }, { path: '/platform', component: Layout, redirect: '/platform', children: [ { path: '', name: 'Platform', component: () => import('@/views/platform/index'), meta: { title: '国标级联', icon: 'platform' } } ] }, { path: '/user', component: Layout, redirect: '/user', children: [ { path: '', name: 'User', component: () => import('@/views/user/index'), meta: { title: '用户管理', icon: 'user' } } ] }, // { // path: '/setting', // component: Layout, // redirect: '/setting', // children: [ // { // path: '', // name: '系统设置', // component: () => import('@/views/platform/index'), // meta: { title: '系统设置', icon: 'setting' } // } // ] // }, { path: '/operations', component: Layout, meta: { title: '运维中心', icon: 'operations' }, redirect: '/operations/systemInfo', children: [ { path: '/operations/systemInfo', name: 'OperationsSystemInfo', component: () => import('@/views/operations/systemInfo'), meta: { title: '平台信息', icon: 'systemInfo' } }, { path: '/operations/historyLog', name: 'OperationsHistoryLog', component: () => import('@/views/operations/historyLog'), meta: { title: '历史日志', icon: 'historyLog' } }, { path: '/operations/realLog', name: 'OperationsRealLog', component: () => import('@/views/operations/realLog'), meta: { title: '实时日志', icon: 'realLog' } } ] }, { path: '/play/wasm/:url', name: 'wasmPlayer', hidden: true, component: () => import('@/views/common/jessibuca.vue') }, { path: '/play/rtc/:url', name: 'rtcPlayer', component: () => import('@/views/common/rtcPlayer.vue') }, // 404 page must be placed at the end !!! { path: '*', redirect: '/404', hidden: true } ] const createRouter = () => new Router({ // mode: 'history', // require service support scrollBehavior: () => ({ y: 0 }), routes: constantRoutes }) const router = createRouter() // Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465 export function resetRouter() { const newRouter = createRouter() router.matcher = newRouter.matcher // reset router } export default router ================================================ FILE: web/src/settings.js ================================================ module.exports = { title: 'WVP视频平台', /** * @type {boolean} true | false * @description Whether fix the header */ fixedHeader: false, /** * @type {boolean} true | false * @description Whether show the logo in sidebar */ sidebarLogo: false, tagsView: true } ================================================ FILE: web/src/store/getters.js ================================================ const getters = { sidebar: state => state.app.sidebar, device: state => state.app.device, token: state => state.user.token, showConfirmBoxForLoginLose: state => state.user.showConfirmBoxForLoginLose, serverId: state => state.user.serverId, name: state => state.user.name, visitedViews: state => state.tagsView.visitedViews, cachedViews: state => state.tagsView.cachedViews } export default getters ================================================ FILE: web/src/store/index.js ================================================ import Vue from 'vue' import Vuex from 'vuex' import getters from './getters' import app from './modules/app' import settings from './modules/settings' import user from './modules/user' import tagsView from './modules/tagsView' import commonChanel from './modules/commonChanel' import region from './modules/region' import device from './modules/device' import group from './modules/group' import server from './modules/server' import play from './modules/play' import playback from './modules/playback' import streamPush from './modules/streamPush' import streamProxy from './modules/streamProxy' import recordPlan from './modules/recordPlan' import cloudRecord from './modules/cloudRecord' import platform from './modules/platform' import role from './modules/role' import userApiKeys from './modules/userApiKeys' import gbRecord from './modules/gbRecord' import log from './modules/log' import frontEnd from './modules/frontEnd' import jtDevice from './modules/jtDevice' Vue.use(Vuex) const store = new Vuex.Store({ modules: { app, settings, user, tagsView, commonChanel, region, device, group, server, play, playback, streamPush, streamProxy, recordPlan, cloudRecord, platform, role, userApiKeys, gbRecord, log, frontEnd, jtDevice }, getters }) export default store ================================================ FILE: web/src/store/modules/app.js ================================================ import Cookies from 'js-cookie' const state = { sidebar: { opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true, withoutAnimation: false }, device: 'desktop' } const mutations = { TOGGLE_SIDEBAR: state => { state.sidebar.opened = !state.sidebar.opened state.sidebar.withoutAnimation = false if (state.sidebar.opened) { Cookies.set('sidebarStatus', 1) } else { Cookies.set('sidebarStatus', 0) } }, CLOSE_SIDEBAR: (state, withoutAnimation) => { Cookies.set('sidebarStatus', 0) state.sidebar.opened = false state.sidebar.withoutAnimation = withoutAnimation }, TOGGLE_DEVICE: (state, device) => { state.device = device } } const actions = { toggleSideBar({ commit }) { commit('TOGGLE_SIDEBAR') }, closeSideBar({ commit }, { withoutAnimation }) { commit('CLOSE_SIDEBAR', withoutAnimation) }, toggleDevice({ commit }, device) { commit('TOGGLE_DEVICE', device) } } export default { namespaced: true, state, mutations, actions } ================================================ FILE: web/src/store/modules/cloudRecord.js ================================================ import { addTask, deleteRecord, getPlayPath, loadRecord, queryList, queryListByData, queryTaskList, seek, speed } from '@/api/cloudRecord' const actions = { getPlayPath({ commit }, id) { return new Promise((resolve, reject) => { getPlayPath(id).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, loadRecord({ commit }, params) { return new Promise((resolve, reject) => { loadRecord(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, seek({ commit }, params) { return new Promise((resolve, reject) => { seek(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, speed({ commit }, params) { return new Promise((resolve, reject) => { speed(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, queryListByData({ commit }, params) { return new Promise((resolve, reject) => { queryListByData(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, addTask({ commit }, params) { return new Promise((resolve, reject) => { addTask(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, queryTaskList({ commit }, params) { return new Promise((resolve, reject) => { queryTaskList(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, queryList({ commit }, params) { return new Promise((resolve, reject) => { queryList(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, deleteRecord({ commit }, ids) { return new Promise((resolve, reject) => { deleteRecord(ids).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) } } export default { namespaced: true, actions } ================================================ FILE: web/src/store/modules/commonChanel.js ================================================ import { update, add, reset, queryOne, addDeviceToGroup, deleteDeviceFromGroup, addDeviceToRegion, deleteDeviceFromRegion, getCivilCodeList, getParentList, getUnusualParentList, clearUnusualParentList, getUnusualCivilCodeList, clearUnusualCivilCodeList, getIndustryList, getTypeList, getNetworkIdentificationList, playChannel, addToRegion, deleteFromRegion, addToGroup, deleteFromGroup, getList, addPointForCruise, addPreset, auxiliary, callPreset, deletePointForCruise, deletePreset, focus, iris, ptz, queryPreset, setCruiseSpeed, setCruiseTime, setLeftForScan, setRightForScan, setSpeedForScan, startCruise, startScan, stopCruise, stopScan, wiper, stopPlayChannel, queryRecord, playback, stopPlayback, pausePlayback, resumePlayback, seekPlayback, speedPlayback, getAllForMap, test, saveLevel, resetLevel, clearThin, thinProgress, drawThin, saveThin } from '@/api/commonChannel' const actions = { update({ commit }, formData) { return new Promise((resolve, reject) => { update(formData).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, add({ commit }, formData) { return new Promise((resolve, reject) => { add(formData).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, reset({ commit }, data) { return new Promise((resolve, reject) => { reset(data).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, queryOne({ commit }, id) { return new Promise((resolve, reject) => { queryOne(id).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, addDeviceToGroup({ commit }, params) { return new Promise((resolve, reject) => { addDeviceToGroup(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, addToGroup({ commit }, params) { return new Promise((resolve, reject) => { addToGroup(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, deleteDeviceFromGroup({ commit }, deviceIds) { return new Promise((resolve, reject) => { deleteDeviceFromGroup(deviceIds).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, deleteFromGroup({ commit }, channels) { return new Promise((resolve, reject) => { deleteFromGroup(channels).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, addDeviceToRegion({ commit }, params) { return new Promise((resolve, reject) => { addDeviceToRegion(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, addToRegion({ commit }, params) { return new Promise((resolve, reject) => { addToRegion(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, deleteDeviceFromRegion({ commit }, deviceIds) { return new Promise((resolve, reject) => { deleteDeviceFromRegion(deviceIds).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, deleteFromRegion({ commit }, channels) { return new Promise((resolve, reject) => { deleteFromRegion(channels).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, getCivilCodeList({ commit }, params) { return new Promise((resolve, reject) => { getCivilCodeList(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, getParentList({ commit }, params) { return new Promise((resolve, reject) => { getParentList(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, getUnusualParentList({ commit }, params) { return new Promise((resolve, reject) => { getUnusualParentList(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, clearUnusualParentList({ commit }, params) { return new Promise((resolve, reject) => { clearUnusualParentList(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, getUnusualCivilCodeList({ commit }, params) { return new Promise((resolve, reject) => { getUnusualCivilCodeList(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, clearUnusualCivilCodeList({ commit }, params) { return new Promise((resolve, reject) => { clearUnusualCivilCodeList(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, getIndustryList({ commit }) { return new Promise((resolve, reject) => { getIndustryList().then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, getTypeList({ commit }) { return new Promise((resolve, reject) => { getTypeList().then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, getNetworkIdentificationList({ commit }) { return new Promise((resolve, reject) => { getNetworkIdentificationList().then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, playChannel({ commit }, channelId) { return new Promise((resolve, reject) => { playChannel(channelId).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, stopPlayChannel({ commit }, channelId) { return new Promise((resolve, reject) => { stopPlayChannel(channelId).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, getList({ commit }, param) { return new Promise((resolve, reject) => { getList(param).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, setSpeedForScan({ commit }, params) { return new Promise((resolve, reject) => { setSpeedForScan(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, setLeftForScan({ commit }, params) { return new Promise((resolve, reject) => { setLeftForScan(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, setRightForScan({ commit }, params) { return new Promise((resolve, reject) => { setRightForScan(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, startScan({ commit }, params) { return new Promise((resolve, reject) => { startScan(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, stopScan({ commit }, params) { return new Promise((resolve, reject) => { stopScan(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, addPointForCruise({ commit }, params) { return new Promise((resolve, reject) => { addPointForCruise(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, deletePointForCruise({ commit }, params) { return new Promise((resolve, reject) => { deletePointForCruise(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, setCruiseSpeed({ commit }, params) { return new Promise((resolve, reject) => { setCruiseSpeed(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, setCruiseTime({ commit }, params) { return new Promise((resolve, reject) => { setCruiseTime(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, startCruise({ commit }, params) { return new Promise((resolve, reject) => { startCruise(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, stopCruise({ commit }, params) { return new Promise((resolve, reject) => { stopCruise(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, addPreset({ commit }, params) { return new Promise((resolve, reject) => { addPreset(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, queryPreset({ commit }, params) { return new Promise((resolve, reject) => { queryPreset(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, callPreset({ commit }, params) { return new Promise((resolve, reject) => { callPreset(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, deletePreset({ commit }, params) { return new Promise((resolve, reject) => { deletePreset(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, auxiliary({ commit }, params) { return new Promise((resolve, reject) => { auxiliary(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, wiper({ commit }, params) { return new Promise((resolve, reject) => { wiper(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, ptz({ commit }, params) { return new Promise((resolve, reject) => { ptz(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, iris({ commit }, params) { return new Promise((resolve, reject) => { iris(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, focus({ commit }, params) { return new Promise((resolve, reject) => { focus(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, queryRecord({ commit }, params) { return new Promise((resolve, reject) => { queryRecord(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, playback({ commit }, params) { return new Promise((resolve, reject) => { playback(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, stopPlayback({ commit }, params) { return new Promise((resolve, reject) => { stopPlayback(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, pausePlayback({ commit }, params) { return new Promise((resolve, reject) => { pausePlayback(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, resumePlayback({ commit }, params) { return new Promise((resolve, reject) => { resumePlayback(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, seekPlayback({ commit }, params) { return new Promise((resolve, reject) => { seekPlayback(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, speedPlayback({ commit }, params) { return new Promise((resolve, reject) => { speedPlayback(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, getAllForMap({ commit }, params) { return new Promise((resolve, reject) => { getAllForMap(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, saveLevel({ commit }, data) { return new Promise((resolve, reject) => { saveLevel(data).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, resetLevel({ commit }) { return new Promise((resolve, reject) => { resetLevel().then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, clearThin({ commit }, id) { return new Promise((resolve, reject) => { clearThin(id).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, thinProgress({ commit }, id) { return new Promise((resolve, reject) => { thinProgress(id).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, saveThin({ commit }, id) { return new Promise((resolve, reject) => { saveThin(id).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, drawThin({ commit }, param) { return new Promise((resolve, reject) => { drawThin(param).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, test({ commit }) { return new Promise((resolve, reject) => { test().then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) } } export default { namespaced: true, actions } ================================================ FILE: web/src/store/modules/device.js ================================================ import { add, changeChannelAudio, deleteDevice, deviceRecord, queryBasicParam, queryChannelOne, queryChannels, queryChannelTree, queryDeviceOne, queryDevices, queryDeviceSyncStatus, queryDeviceTree, queryHasStreamChannels, resetGuard, setGuard, subscribeCatalog, subscribeMobilePosition, sync, update, updateChannelStreamIdentification, updateDeviceTransport } from '@/api/device' const actions = { queryDeviceSyncStatus({ commit }, deviceId) { return new Promise((resolve, reject) => { queryDeviceSyncStatus(deviceId).then(response => { // const {data, code, msg} = response resolve(response) }).catch(error => { reject(error) }) }) }, queryDevices({ commit }, params) { return new Promise((resolve, reject) => { queryDevices(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, sync({ commit }, deviceId) { return new Promise((resolve, reject) => { sync(deviceId).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, updateDeviceTransport({ commit }, [deviceId, streamMode]) { return new Promise((resolve, reject) => { updateDeviceTransport(deviceId, streamMode).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, setGuard({ commit }, deviceId) { return new Promise((resolve, reject) => { setGuard(deviceId).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, resetGuard({ commit }, deviceId) { return new Promise((resolve, reject) => { resetGuard(deviceId).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, subscribeCatalog({ commit }, params) { return new Promise((resolve, reject) => { subscribeCatalog(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, subscribeMobilePosition({ commit }, params) { return new Promise((resolve, reject) => { subscribeMobilePosition(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, queryBasicParam({ commit }, deviceId) { return new Promise((resolve, reject) => { queryBasicParam(deviceId).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, queryChannelOne({ commit }, params) { return new Promise((resolve, reject) => { queryChannelOne(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, queryChannels({ commit }, [deviceId, params]) { return new Promise((resolve, reject) => { queryChannels(deviceId, params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, queryHasStreamChannels({commit}, params) { return new Promise((resolve, reject) => { queryHasStreamChannels(params).then(response => { const {data} = response resolve(data) }).catch(error => { reject(error) }) }) }, deviceRecord({ commit }, params) { return new Promise((resolve, reject) => { deviceRecord(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, querySubChannels({ commit }, [params, deviceId, parentChannelId]) { return new Promise((resolve, reject) => { deviceRecord(params, deviceId, parentChannelId).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, queryChannelTree({ commit }, params) { return new Promise((resolve, reject) => { queryChannelTree(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, changeChannelAudio({ commit }, params) { return new Promise((resolve, reject) => { changeChannelAudio(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, updateChannelStreamIdentification({ commit }, params) { return new Promise((resolve, reject) => { updateChannelStreamIdentification(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, update({ commit }, formData) { return new Promise((resolve, reject) => { update(formData).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, add({ commit }, formData) { return new Promise((resolve, reject) => { add(formData).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, queryDeviceOne({ commit }, deviceId) { return new Promise((resolve, reject) => { queryDeviceOne(deviceId).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, queryDeviceTree({ commit }, params, deviceId) { return new Promise((resolve, reject) => { queryDeviceTree(params, deviceId).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, deleteDevice({ commit }, deviceId) { return new Promise((resolve, reject) => { deleteDevice(deviceId).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) } } export default { namespaced: true, actions } ================================================ FILE: web/src/store/modules/frontEnd.js ================================================ import { addPointForCruise, addPreset, auxiliary, callPreset, deletePointForCruise, deletePreset, focus, iris, ptz, queryPreset, setCruiseSpeed, setCruiseTime, setLeftForScan, setRightForScan, setSpeedForScan, startCruise, startScan, stopCruise, stopScan, wiper } from '@/api/frontEnd' const actions = { setSpeedForScan({ commit }, params) { return new Promise((resolve, reject) => { setSpeedForScan(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, setLeftForScan({ commit }, params) { return new Promise((resolve, reject) => { setLeftForScan(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, setRightForScan({ commit }, params) { return new Promise((resolve, reject) => { setRightForScan(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, startScan({ commit }, params) { return new Promise((resolve, reject) => { startScan(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, stopScan({ commit }, params) { return new Promise((resolve, reject) => { stopScan(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, addPointForCruise({ commit }, params) { return new Promise((resolve, reject) => { addPointForCruise(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, deletePointForCruise({ commit }, params) { return new Promise((resolve, reject) => { deletePointForCruise(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, setCruiseSpeed({ commit }, params) { return new Promise((resolve, reject) => { setCruiseSpeed(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, setCruiseTime({ commit }, params) { return new Promise((resolve, reject) => { setCruiseTime(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, startCruise({ commit }, params) { return new Promise((resolve, reject) => { startCruise(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, stopCruise({ commit }, params) { return new Promise((resolve, reject) => { stopCruise(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, addPreset({ commit }, params) { return new Promise((resolve, reject) => { addPreset(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, queryPreset({ commit }, params) { return new Promise((resolve, reject) => { queryPreset(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, callPreset({ commit }, params) { return new Promise((resolve, reject) => { callPreset(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, deletePreset({ commit }, params) { return new Promise((resolve, reject) => { deletePreset(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, auxiliary({ commit }, params) { return new Promise((resolve, reject) => { auxiliary(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, wiper({ commit }, params) { return new Promise((resolve, reject) => { wiper(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, ptz({ commit }, params) { return new Promise((resolve, reject) => { ptz(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, iris({ commit }, params) { return new Promise((resolve, reject) => { iris(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, focus({ commit }, params) { return new Promise((resolve, reject) => { focus(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) } } export default { namespaced: true, actions } ================================================ FILE: web/src/store/modules/gbRecord.js ================================================ import { query, queryDownloadProgress, startDownLoad, stopDownLoad } from '@/api/gbRecord' const actions = { query({ commit }, param) { return new Promise((resolve, reject) => { query(param).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, startDownLoad({ commit }, param) { return new Promise((resolve, reject) => { startDownLoad(param).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, stopDownLoad({ commit }, deviceId, channelId, streamId) { return new Promise((resolve, reject) => { stopDownLoad(deviceId, channelId, streamId).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, queryDownloadProgress({ commit }, param) { return new Promise((resolve, reject) => { queryDownloadProgress(param).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) } } export default { namespaced: true, actions } ================================================ FILE: web/src/store/modules/group.js ================================================ import { getTreeList, update, add, deleteGroup, getPath, queryTree, sync } from '@/api/group' const actions = { update({ commit }, formData) { return new Promise((resolve, reject) => { update(formData).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, add({ commit }, formData) { return new Promise((resolve, reject) => { add(formData).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, getTreeList({ commit }, params) { return new Promise((resolve, reject) => { getTreeList(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, deleteGroup({ commit }, id) { return new Promise((resolve, reject) => { deleteGroup(id).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, getPath({ commit }, params) { return new Promise((resolve, reject) => { getPath(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, queryTree({ commit }, param) { return new Promise((resolve, reject) => { queryTree(param).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) } } export default { namespaced: true, actions } ================================================ FILE: web/src/store/modules/jtDevice.js ================================================ import { add, addChannel, connection, controlDoor, controlPlayback, deleteDevice, factoryReset, fillLight, getRecordTempUrl, linkDetection, play, ptz, queryAttribute, queryChannels, queryConfig, queryDeviceById, queryDevices, queryDriverInfo, queryMediaAttribute, queryMediaData, queryPosition, queryRecordList, reset, sendTextMessage, setConfig, setPhoneBook, shooting, startPlayback, startTalk, stopPlay, stopPlayback, stopTalk, telephoneCallback, update, updateChannel, wiper } from '@/api/jtDevice' const actions = { queryDevices({ commit }, params) { return new Promise((resolve, reject) => { queryDevices(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, add({ commit }, params) { return new Promise((resolve, reject) => { add(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, update({ commit }, params) { return new Promise((resolve, reject) => { update(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, queryDeviceById({ commit }, deviceId) { return new Promise((resolve, reject) => { queryDeviceById(deviceId).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, deleteDevice({ commit }, phoneNumber) { return new Promise((resolve, reject) => { deleteDevice(phoneNumber).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, queryChannels({ commit }, params) { return new Promise((resolve, reject) => { queryChannels(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, play({ commit }, params) { return new Promise((resolve, reject) => { play(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, stopPlay({ commit }, params) { return new Promise((resolve, reject) => { stopPlay(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, updateChannel({ commit }, data) { return new Promise((resolve, reject) => { updateChannel(data).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, addChannel({ commit }, data) { return new Promise((resolve, reject) => { addChannel(data).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, ptz({ commit }, params) { return new Promise((resolve, reject) => { ptz(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, wiper({ commit }, params) { return new Promise((resolve, reject) => { wiper(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, fillLight({ commit }, params) { return new Promise((resolve, reject) => { fillLight(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, queryConfig({ commit }, phoneNumber) { return new Promise((resolve, reject) => { queryConfig(phoneNumber).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, setConfig({ commit }, data) { return new Promise((resolve, reject) => { setConfig(data).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, queryRecordList({ commit }, params) { return new Promise((resolve, reject) => { queryRecordList(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, startPlayback({ commit }, params) { return new Promise((resolve, reject) => { startPlayback(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, controlPlayback({ commit }, params) { return new Promise((resolve, reject) => { controlPlayback(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, stopPlayback({ commit }, params) { return new Promise((resolve, reject) => { stopPlayback(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, getRecordTempUrl({ commit }, params) { return new Promise((resolve, reject) => { getRecordTempUrl(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, queryAttribute({ commit }, phoneNumber) { return new Promise((resolve, reject) => { queryAttribute(phoneNumber).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, linkDetection({ commit }, phoneNumber) { return new Promise((resolve, reject) => { linkDetection(phoneNumber).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, queryPosition({ commit }, phoneNumber) { return new Promise((resolve, reject) => { queryPosition(phoneNumber).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, sendTextMessage({ commit }, data) { return new Promise((resolve, reject) => { sendTextMessage(data).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, telephoneCallback({ commit }, param) { return new Promise((resolve, reject) => { telephoneCallback(param).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, queryDriverInfo({ commit }, phoneNumber) { return new Promise((resolve, reject) => { queryDriverInfo(phoneNumber).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, factoryReset({ commit }, phoneNumber) { return new Promise((resolve, reject) => { factoryReset(phoneNumber).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, reset({ commit }, phoneNumber) { return new Promise((resolve, reject) => { reset(phoneNumber).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, connection({ commit }, data) { return new Promise((resolve, reject) => { connection(data).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, controlDoor({ commit }, param) { return new Promise((resolve, reject) => { controlDoor(param).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, queryMediaAttribute({ commit }, phoneNumber) { return new Promise((resolve, reject) => { queryMediaAttribute(phoneNumber).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, setPhoneBook({ commit }, data) { return new Promise((resolve, reject) => { setPhoneBook(data).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, queryMediaData({ commit }, data) { return new Promise((resolve, reject) => { queryMediaData(data).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, shooting({ commit }, data) { return new Promise((resolve, reject) => { shooting(data).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, startTalk({ commit }, param) { return new Promise((resolve, reject) => { startTalk(param).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, stopTalk({ commit }, param) { return new Promise((resolve, reject) => { stopTalk(param).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) } } export default { namespaced: true, actions } ================================================ FILE: web/src/store/modules/log.js ================================================ import { queryList } from '@/api/log' const actions = { queryList({ commit }, params) { return new Promise((resolve, reject) => { queryList(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) } } export default { namespaced: true, actions } ================================================ FILE: web/src/store/modules/platform.js ================================================ import { add, addChannel, addChannelByDevice, exit, getChannelList, getServerConfig, pushChannel, query, remove, removeChannel, removeChannelByDevice, update, updateCustomChannel } from '@/api/platform' const actions = { update({ commit }, data) { return new Promise((resolve, reject) => { update(data).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, add({ commit }, data) { return new Promise((resolve, reject) => { add(data).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, exit({ commit }, deviceGbId) { return new Promise((resolve, reject) => { exit(deviceGbId).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, remove({ commit }, id) { return new Promise((resolve, reject) => { remove(id).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, pushChannel({ commit }, id) { return new Promise((resolve, reject) => { pushChannel(id).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, getServerConfig({ commit }) { return new Promise((resolve, reject) => { getServerConfig().then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, query({ commit }, params) { return new Promise((resolve, reject) => { query(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, getChannelList({ commit }, params) { return new Promise((resolve, reject) => { getChannelList(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, addChannel({ commit }, params) { return new Promise((resolve, reject) => { addChannel(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, addChannelByDevice({ commit }, params) { return new Promise((resolve, reject) => { addChannelByDevice(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, removeChannelByDevice({ commit }, params) { return new Promise((resolve, reject) => { removeChannelByDevice(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, removeChannel({ commit }, params) { return new Promise((resolve, reject) => { removeChannel(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, updateCustomChannel({ commit }, data) { return new Promise((resolve, reject) => { updateCustomChannel(data).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) } } export default { namespaced: true, actions } ================================================ FILE: web/src/store/modules/play.js ================================================ import { broadcastStart, broadcastStop, play, stop } from '@/api/play' const actions = { play({ commit }, [deviceId, channelId]) { return new Promise((resolve, reject) => { play(deviceId, channelId).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, stop({ commit }, { deviceId, channelId }) { return new Promise((resolve, reject) => { stop(deviceId, channelId).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, broadcastStart({ commit }, [deviceId, channelId, broadcastMode]) { return new Promise((resolve, reject) => { broadcastStart(deviceId, channelId, broadcastMode).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, broadcastStop({ commit }, [deviceId, channelId]) { return new Promise((resolve, reject) => { broadcastStop(deviceId, channelId).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) } } export default { namespaced: true, actions } ================================================ FILE: web/src/store/modules/playback.js ================================================ import { pause, play, resume, setSpeed, stop } from '@/api/playback' const actions = { play({ commit }, data) { return new Promise((resolve, reject) => { play(data).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, resume({ commit }, streamId) { return new Promise((resolve, reject) => { resume(streamId).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, pause({ commit }, streamId) { return new Promise((resolve, reject) => { pause(streamId).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, setSpeed({ commit }, param) { return new Promise((resolve, reject) => { setSpeed(param).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, stop({ commit }, [deviceId, channelId, streamId]) { return new Promise((resolve, reject) => { stop(deviceId, channelId, streamId).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) } } export default { namespaced: true, actions } ================================================ FILE: web/src/store/modules/recordPlan.js ================================================ import { addPlan, deletePlan, getPlan, linkPlan, queryChannelList, queryList, update } from '@/api/recordPlan' const actions = { getPlan({ commit }, id) { return new Promise((resolve, reject) => { getPlan(id).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, addPlan({ commit }, params) { return new Promise((resolve, reject) => { addPlan(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, update({ commit }, params) { return new Promise((resolve, reject) => { update(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, queryList({ commit }, params) { return new Promise((resolve, reject) => { queryList(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, deletePlan({ commit }, id) { return new Promise((resolve, reject) => { deletePlan(id).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, queryChannelList({ commit }, params) { return new Promise((resolve, reject) => { queryChannelList(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, linkPlan({ commit }, data) { return new Promise((resolve, reject) => { linkPlan(data).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) } } export default { namespaced: true, actions } ================================================ FILE: web/src/store/modules/region.js ================================================ import { getTreeList, deleteRegion, description, addByCivilCode, queryChildListInBase, update, add, queryPath, queryTree } from '@/api/region' const actions = { getTreeList({ commit }, data) { return new Promise((resolve, reject) => { getTreeList(data).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, deleteRegion({ commit }, id) { return new Promise((resolve, reject) => { deleteRegion(id).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, description({ commit }, civilCode) { return new Promise((resolve, reject) => { description(civilCode).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, addByCivilCode({ commit }, civilCode) { return new Promise((resolve, reject) => { addByCivilCode(civilCode).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, queryChildListInBase({ commit }, parent) { return new Promise((resolve, reject) => { queryChildListInBase(parent).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, update({ commit }, formData) { return new Promise((resolve, reject) => { update(formData).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, add({ commit }, formData) { return new Promise((resolve, reject) => { add(formData).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, queryPath({ commit }, deviceId) { return new Promise((resolve, reject) => { queryPath(deviceId).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, queryTree({ commit }, param) { return new Promise((resolve, reject) => { queryTree(param).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) } } export default { namespaced: true, actions } ================================================ FILE: web/src/store/modules/role.js ================================================ import { getAll } from '@/api/role' const actions = { getAll({ commit }) { return new Promise((resolve, reject) => { getAll().then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) } } export default { namespaced: true, actions } ================================================ FILE: web/src/store/modules/server.js ================================================ import { checkMediaServer, checkMediaServerRecord, deleteMediaServer, getMapConfig, getMediaInfo, getMediaServer, getMediaServerList, getMediaServerLoad, getModelList, getOnlineMediaServerList, getResourceInfo, getSystemConfig, getSystemInfo, info, saveMediaServer } from '@/api/server' const actions = { getOnlineMediaServerList({ commit }) { return new Promise((resolve, reject) => { getOnlineMediaServerList().then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, getMediaServerList({ commit }) { return new Promise((resolve, reject) => { getMediaServerList().then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, getMediaServer({ commit }, id) { return new Promise((resolve, reject) => { getMediaServer(id).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, checkMediaServer({ commit }, params) { return new Promise((resolve, reject) => { checkMediaServer(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, checkMediaServerRecord({ commit }, params) { return new Promise((resolve, reject) => { checkMediaServerRecord(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, saveMediaServer({ commit }, formData) { return new Promise((resolve, reject) => { saveMediaServer(formData).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, deleteMediaServer({ commit }, id) { return new Promise((resolve, reject) => { deleteMediaServer(id).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, getSystemConfig({ commit }) { return new Promise((resolve, reject) => { getSystemConfig().then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, getMediaInfo({ commit }, params) { return new Promise((resolve, reject) => { getMediaInfo(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, getSystemInfo({ commit }) { return new Promise((resolve, reject) => { getSystemInfo().then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, getMediaServerLoad({ commit }) { return new Promise((resolve, reject) => { getMediaServerLoad().then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, getResourceInfo({ commit }) { return new Promise((resolve, reject) => { getResourceInfo().then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, info({ commit }) { return new Promise((resolve, reject) => { info().then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, getMapConfig({ commit }) { return new Promise((resolve, reject) => { getMapConfig().then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, getModelList({ commit }) { return new Promise((resolve, reject) => { getModelList().then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) } } export default { namespaced: true, actions } ================================================ FILE: web/src/store/modules/settings.js ================================================ import defaultSettings from '@/settings' const { showSettings, fixedHeader, sidebarLogo, tagsViews } = defaultSettings const state = { showSettings: showSettings, fixedHeader: fixedHeader, sidebarLogo: sidebarLogo, tagsViews: tagsViews } const mutations = { CHANGE_SETTING: (state, { key, value }) => { // eslint-disable-next-line no-prototype-builtins if (state.hasOwnProperty(key)) { state[key] = value } } } const actions = { changeSetting({ commit }, data) { commit('CHANGE_SETTING', data) } } export default { namespaced: true, state, mutations, actions } ================================================ FILE: web/src/store/modules/streamProxy.js ================================================ import { add, play, queryFfmpegCmdList, queryList, remove, save, stopPlay, update } from '@/api/streamProxy' const actions = { queryFfmpegCmdList({ commit }, mediaServerId) { return new Promise((resolve, reject) => { queryFfmpegCmdList(mediaServerId).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, save({ commit }, formData) { return new Promise((resolve, reject) => { save(formData).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, update({ commit }, formData) { return new Promise((resolve, reject) => { update(formData).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, add({ commit }, formData) { return new Promise((resolve, reject) => { add(formData).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, queryList({ commit }, params) { return new Promise((resolve, reject) => { queryList(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, play({ commit }, id) { return new Promise((resolve, reject) => { play(id).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, stopPlay({ commit }, id) { return new Promise((resolve, reject) => { stopPlay(id).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, remove({ commit }, id) { return new Promise((resolve, reject) => { remove(id).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) } } export default { namespaced: true, actions } ================================================ FILE: web/src/store/modules/streamPush.js ================================================ import { saveToGb, add, update, queryList, play, remove, removeFormGb, batchRemove } from '@/api/streamPush' const actions = { saveToGb({ commit }, formData) { return new Promise((resolve, reject) => { saveToGb(formData).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, add({ commit }, formData) { return new Promise((resolve, reject) => { add(formData).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, update({ commit }, formData) { return new Promise((resolve, reject) => { update(formData).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, queryList({ commit }, params) { return new Promise((resolve, reject) => { queryList(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, play({ commit }, id) { return new Promise((resolve, reject) => { play(id).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, remove({ commit }, id) { return new Promise((resolve, reject) => { remove(id).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, removeFormGb({ commit }, formData) { return new Promise((resolve, reject) => { removeFormGb(formData).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, batchRemove({ commit }, ids) { return new Promise((resolve, reject) => { batchRemove(ids).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) } } export default { namespaced: true, actions } ================================================ FILE: web/src/store/modules/tagsView.js ================================================ const state = { visitedViews: [], cachedViews: [] } const mutations = { ADD_VISITED_VIEW: (state, view) => { if (state.visitedViews.some(v => v.path === view.path)) return state.visitedViews.push( Object.assign({}, view, { title: view.meta.title || 'no-name' }) ) }, ADD_CACHED_VIEW: (state, view) => { if (state.cachedViews.includes(view.name)) return if (!view.meta.noCache) { state.cachedViews.push(view.name) } }, DEL_VISITED_VIEW: (state, view) => { for (const [i, v] of state.visitedViews.entries()) { if (v.path === view.path) { state.visitedViews.splice(i, 1) break } } }, DEL_CACHED_VIEW: (state, view) => { const index = state.cachedViews.indexOf(view.name) index > -1 && state.cachedViews.splice(index, 1) }, DEL_OTHERS_VISITED_VIEWS: (state, view) => { state.visitedViews = state.visitedViews.filter(v => { return v.meta.affix || v.path === view.path }) }, DEL_OTHERS_CACHED_VIEWS: (state, view) => { const index = state.cachedViews.indexOf(view.name) if (index > -1) { state.cachedViews = state.cachedViews.slice(index, index + 1) } else { // if index = -1, there is no cached tags state.cachedViews = [] } }, DEL_ALL_VISITED_VIEWS: state => { // keep affix tags const affixTags = state.visitedViews.filter(tag => tag.meta.affix) state.visitedViews = affixTags }, DEL_ALL_CACHED_VIEWS: state => { state.cachedViews = [] }, UPDATE_VISITED_VIEW: (state, view) => { for (let v of state.visitedViews) { if (v.path === view.path) { v = Object.assign(v, view) break } } } } const actions = { addView({ dispatch }, view) { dispatch('addVisitedView', view) dispatch('addCachedView', view) }, addVisitedView({ commit }, view) { commit('ADD_VISITED_VIEW', view) }, addCachedView({ commit }, view) { commit('ADD_CACHED_VIEW', view) }, delView({ dispatch, state }, view) { return new Promise(resolve => { dispatch('delVisitedView', view) dispatch('delCachedView', view) resolve({ visitedViews: [...state.visitedViews], cachedViews: [...state.cachedViews] }) }) }, delVisitedView({ commit, state }, view) { return new Promise(resolve => { commit('DEL_VISITED_VIEW', view) resolve([...state.visitedViews]) }) }, delCachedView({ commit, state }, view) { return new Promise(resolve => { commit('DEL_CACHED_VIEW', view) resolve([...state.cachedViews]) }) }, delOthersViews({ dispatch, state }, view) { return new Promise(resolve => { dispatch('delOthersVisitedViews', view) dispatch('delOthersCachedViews', view) resolve({ visitedViews: [...state.visitedViews], cachedViews: [...state.cachedViews] }) }) }, delOthersVisitedViews({ commit, state }, view) { return new Promise(resolve => { commit('DEL_OTHERS_VISITED_VIEWS', view) resolve([...state.visitedViews]) }) }, delOthersCachedViews({ commit, state }, view) { return new Promise(resolve => { commit('DEL_OTHERS_CACHED_VIEWS', view) resolve([...state.cachedViews]) }) }, delAllViews({ dispatch, state }, view) { return new Promise(resolve => { dispatch('delAllVisitedViews', view) dispatch('delAllCachedViews', view) resolve({ visitedViews: [...state.visitedViews], cachedViews: [...state.cachedViews] }) }) }, delAllVisitedViews({ commit, state }) { return new Promise(resolve => { commit('DEL_ALL_VISITED_VIEWS') resolve([...state.visitedViews]) }) }, delAllCachedViews({ commit, state }) { return new Promise(resolve => { commit('DEL_ALL_CACHED_VIEWS') resolve([...state.cachedViews]) }) }, updateVisitedView({ commit }, view) { commit('UPDATE_VISITED_VIEW', view) } } export default { namespaced: true, state, mutations, actions } ================================================ FILE: web/src/store/modules/user.js ================================================ import crypto from 'crypto' import { add, changePassword, changePasswordForAdmin, changePushKey, getUserInfo, login, logout, queryList, removeById } from '@/api/user' import { getToken, setToken, setName, removeToken, removeName, setServerId, removeServerId } from '@/utils/auth' import { resetRouter } from '@/router' const getDefaultState = () => { return { token: getToken(), name: '', serverId: '', showConfirmBoxForLoginLose: true } } const state = getDefaultState() const mutations = { RESET_STATE: (state) => { Object.assign(state, getDefaultState()) }, SET_TOKEN: (state, token) => { state.token = token }, SET_NAME: (state, name) => { state.name = name }, SET_SERVER_ID: (state, serverId) => { state.serverId = serverId }, SET_CONFIRM_BOX: (state, status) => { state.showConfirmBoxForLoginLose = status } } const actions = { // user login login({ commit }, userInfo) { const { username, password } = userInfo return new Promise((resolve, reject) => { login({ username: username.trim(), password: crypto.createHash('md5').update(password, 'utf8').digest('hex') }).then(response => { const { data } = response commit('SET_TOKEN', data.accessToken) commit('SET_NAME', data.username) commit('SET_SERVER_ID', data.serverId) commit('SET_CONFIRM_BOX', true) setToken(data.accessToken) setName(data.username) setServerId(data.serverId) resolve() }).catch(error => { reject(error) }) }) }, // user logout logout({ commit, state }) { return new Promise((resolve, reject) => { logout(state.token).then(() => { removeToken() removeServerId() removeName() resetRouter() commit('RESET_STATE') resolve() }).catch(error => { reject(error) }) }) }, // remove token resetToken({ commit }) { return new Promise(resolve => { removeToken() // must remove token first commit('RESET_STATE') resolve() }) }, getUserInfo({ commit }) { return new Promise((resolve, reject) => { getUserInfo().then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, changePushKey({ commit }, params) { return new Promise((resolve, reject) => { changePushKey(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, queryList({ commit }, params) { return new Promise((resolve, reject) => { queryList(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, removeById({ commit }, id) { return new Promise((resolve, reject) => { removeById(id).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, add({ commit }, params) { return new Promise((resolve, reject) => { add(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, changePassword({ commit }, params) { return new Promise((resolve, reject) => { changePassword(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, changePasswordForAdmin({ commit }, params) { return new Promise((resolve, reject) => { changePasswordForAdmin(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, closeConfirmBoxForLoginLose({ commit }) { commit('SET_CONFIRM_BOX', false) } } export default { namespaced: true, state, mutations, actions } ================================================ FILE: web/src/store/modules/userApiKeys.js ================================================ import { add, disable, enable, queryList, remark, remove, reset } from '@/api/userApiKey' const actions = { remark({ commit }, params) { return new Promise((resolve, reject) => { remark(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, queryList({ commit }, params) { return new Promise((resolve, reject) => { queryList(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, enable({ commit }, id) { return new Promise((resolve, reject) => { enable(id).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, disable({ commit }, id) { return new Promise((resolve, reject) => { disable(id).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, reset({ commit }, id) { return new Promise((resolve, reject) => { reset(id).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, remove({ commit }, id) { return new Promise((resolve, reject) => { remove(id).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) }, add({ commit }, params) { return new Promise((resolve, reject) => { add(params).then(response => { const { data } = response resolve(data) }).catch(error => { reject(error) }) }) } } export default { namespaced: true, actions } ================================================ FILE: web/src/styles/element-ui.scss ================================================ // cover some element-ui styles .el-breadcrumb__inner, .el-breadcrumb__inner a { font-weight: 400 !important; } .el-upload { input[type="file"] { display: none !important; } } .el-upload__input { display: none; } // to fixed https://github.com/ElemeFE/element/issues/2461 .el-dialog { transform: none; left: 0; position: relative; margin: 0 auto; } // refine element ui upload .upload-container { .el-upload { width: 100%; .el-upload-dragger { width: 100%; height: 200px; } } } // dropdown .el-dropdown-menu { a { display: block } } // to fix el-date-picker css style .el-range-separator { box-sizing: content-box; } ================================================ FILE: web/src/styles/iconfont.scss ================================================ @font-face { font-family: "iconfont"; /* Project id 1291092 */ src: url('iconfont.woff2?t=1758784486763') format('woff2') } .iconfont { font-family: "iconfont" !important; font-size: 16px; font-style: normal; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } .icon-mti-duobianxingxuan:before { content: "\e9e7"; } .icon-mti-fengwotu:before { content: "\e9e8"; } .icon-mti-fangda:before { content: "\e9e9"; } .icon-mti-huizhi:before { content: "\e9ea"; } .icon-mti-guijihuifang:before { content: "\e9eb"; } .icon-mti-huodongguiji:before { content: "\e9ed"; } .icon-mti-juliceliang:before { content: "\e9ee"; } .icon-mti-jutai:before { content: "\e9ef"; } .icon-mti-kuangxuan:before { content: "\e9f0"; } .icon-mti-lukuang:before { content: "\e9f1"; } .icon-a-mti-qiehuanshijiaomti-sanwei:before { content: "\e9f2"; } .icon-mti-mianjiceliang:before { content: "\e9f3"; } .icon-mti-lukuang2:before { content: "\e9f4"; } .icon-mti-sandian:before { content: "\e9f7"; } .icon-mti-relitu:before { content: "\e9f8"; } .icon-mti-sanwei:before { content: "\e9f9"; } .icon-mti-quanxuan:before { content: "\e9fa"; } .icon-mti-suoxiao:before { content: "\e9fb"; } .icon-mti-tucengfenlei:before { content: "\e9fc"; } .icon-mti-tianjiadingweiquyu:before { content: "\e9fd"; } .icon-mti-tianjiadingwei:before { content: "\e9fe"; } .icon-mti-tujingdian:before { content: "\e9ff"; } .icon-mti-xinzengdian:before { content: "\ea00"; } .icon-mti-xianxuan:before { content: "\ea01"; } .icon-mti-xuanze:before { content: "\ea02"; } .icon-mti-xinzengxian:before { content: "\ea03"; } .icon-mti-yangshi:before { content: "\ea04"; } .icon-mti-yingyantu:before { content: "\ea05"; } .icon-mti-yunhangguiji:before { content: "\ea06"; } .icon-mti-ziyuanbukong:before { content: "\ea08"; } .icon-mti-zhongdianquyu:before { content: "\ea09"; } .icon-mti-zongheceliang:before { content: "\ea0a"; } .icon-mti-POIxinxi:before { content: "\e9e2"; } .icon-mti-celiang:before { content: "\e9e3"; } .icon-mti-dituqiehuan:before { content: "\e9e4"; } .icon-mti-diliweilan:before { content: "\e9e5"; } .icon-mti-erwei:before { content: "\e9e6"; } .icon-tuceng:before { content: "\e7f2"; } .icon-a-bofangqi1:before { content: "\ec17"; } .icon-sanjiaoxing:before { content: "\e7f1"; } .icon-icon_gps:before { content: "\e7f0"; } .icon-yidingdaoweizhuangtai:before { content: "\e7ef"; } .icon-gps:before { content: "\e8b6"; } .icon-tongdao:before { content: "\e7ee"; } .icon-xiazailiebiao:before { content: "\e7ed"; } .icon-zoom-in:before { content: "\e7eb"; } .icon-zoom-out:before { content: "\e7ec"; } .icon-bianjiao-suoxiao:before { content: "\e8c8"; } .icon-bianjiao-fangda:before { content: "\e8c9"; } .icon-guangquan-:before { content: "\e7e9"; } .icon-guangquan:before { content: "\e7ea"; } .icon-a-mti-1fenpingshi:before { content: "\e7e5"; } .icon-a-mti-4fenpingshi:before { content: "\e7e6"; } .icon-a-mti-6fenpingshi:before { content: "\e7e7"; } .icon-a-mti-9fenpingshi:before { content: "\e7e8"; } .icon-shexiangtou01:before { content: "\e7e1"; } .icon-Group-:before { content: "\e7e2"; } .icon-shexiangtou2:before { content: "\e7e3"; } .icon-shexiangtou3:before { content: "\e7e4"; } .icon-slider:before { content: "\e7e0"; } .icon-slider-right:before { content: "\ea19"; } .icon-list:before { content: "\e7de"; } .icon-tree:before { content: "\e7df"; } .icon-shipin:before { content: "\e7db"; } .icon-shipin1:before { content: "\e7dc"; } .icon-shipin2:before { content: "\e7dd"; } .icon-LC_icon_gps_fill:before { content: "\e7da"; } .icon-jiedianleizhukongzhongxin1:before { content: "\e9d0"; } .icon-jiedianleizhukongzhongxin2:before { content: "\e9d1"; } .icon-jiedianleilianjipingtai:before { content: "\e9d3"; } .icon-jiedianleiquyu:before { content: "\e9d4"; } .icon-shebeileigis:before { content: "\e9ec"; } .icon-shebeileibanqiu:before { content: "\e9f5"; } .icon-shebeileibanqiugis:before { content: "\e9f6"; } .icon-shebeileijiankongdian:before { content: "\ea07"; } .icon-shebeileiqiangjitongdao:before { content: "\ea15"; } .icon-shebeileiqiuji:before { content: "\ea17"; } .icon-shebeileiqiujigis:before { content: "\ea18"; } .icon-xitongxinxi:before { content: "\e7d6"; } .icon-gbaojings:before { content: "\e7d7"; } .icon-gjichus:before { content: "\e7d8"; } .icon-gxunjians:before { content: "\e7d9"; } .icon-ziyuan:before { content: "\e7d5"; } .icon-shexiangtou1:before { content: "\e7d4"; } .icon-wxbzhuye:before { content: "\e7d1"; } .icon-mulu:before { content: "\e7d2"; } .icon-zhibo:before { content: "\e8c1"; } .icon-shexiangtou:before { content: "\e7d3"; } .icon-suoxiao:before { content: "\e79a"; } .icon-shanchu3:before { content: "\e79b"; } .icon-chehui:before { content: "\e79c"; } .icon-wenben:before { content: "\e79d"; } .icon-zhongzuo:before { content: "\e79e"; } .icon-jianqie:before { content: "\e79f"; } .icon-fangda:before { content: "\e7a0"; } .icon-fangdazhanshi:before { content: "\e7a1"; } .icon-qianjin:before { content: "\e7a2"; } .icon-houtui:before { content: "\e7a3"; } .icon-diyigeshipin:before { content: "\e7a4"; } .icon-kuaijin:before { content: "\e7a5"; } .icon-kaishi:before { content: "\e7a7"; } .icon-zuihouyigeshipin:before { content: "\e7a8"; } .icon-zanting:before { content: "\e7a9"; } .icon-zhankai:before { content: "\e7aa"; } .icon-bendisucai:before { content: "\e7ab"; } .icon-luzhi:before { content: "\e7ac"; } .icon-ossziyuan:before { content: "\e7ad"; } .icon-chuangjianzhinengfenxirenwu:before { content: "\e7ae"; } .icon-sousuo3:before { content: "\e7af"; } .icon-gengduo:before { content: "\e7b0"; } .icon-tianjia:before { content: "\e7b1"; } .icon-xiazai:before { content: "\e7b2"; } .icon-biaojibeifen:before { content: "\e7b3"; } .icon-bendisucaibeifen:before { content: "\e7b4"; } .icon-luzhibeifen:before { content: "\e7b5"; } .icon-ossziyuanbeifen:before { content: "\e7b6"; } .icon-bianji3:before { content: "\e7b7"; } .icon-cuti:before { content: "\e7b8"; } .icon-xieti:before { content: "\e7b9"; } .icon-xiahuaxian:before { content: "\e7ba"; } .icon-wuxiaoguo:before { content: "\e7bb"; } .icon-sousuo4:before { content: "\e7bc"; } .icon-gouwuche:before { content: "\e7bd"; } .icon-shuaxin2:before { content: "\e7be"; } .icon-xiaoxi:before { content: "\e7bf"; } .icon-wushouquan:before { content: "\e7c0"; } .icon-tishi2:before { content: "\e7c1"; } .icon-tishi1:before { content: "\e7c2"; } .icon-shouquanchenggong:before { content: "\e7c3"; } .icon-sousuo5:before { content: "\e7c4"; } .icon-shuaxin3:before { content: "\e7c5"; } .icon-xiazai1:before { content: "\e7c6"; } .icon-shangchuan:before { content: "\e7c7"; } .icon-guanbi:before { content: "\e7c8"; } .icon-wangye-loading:before { content: "\e7c9"; } .icon-bianzubeifen3:before { content: "\e7ca"; } .icon-xingzhuangbeifen:before { content: "\e7cb"; } .icon-bianzubeifen:before { content: "\e7cc"; } .icon-zhuanchang:before { content: "\e7cd"; } .icon-meizi:before { content: "\e7ce"; } .icon-daimabeifen:before { content: "\e7cf"; } .icon-suoxiao1:before { content: "\e7d0"; } .icon-ai19:before { content: "\e799"; } .icon-online:before { content: "\e600"; } .icon-xiangqing2:before { content: "\e798"; } .icon-record:before { content: "\e7a6"; } .icon-audio-mute:before { content: "\e792"; } .icon-audio-high:before { content: "\e793"; } .icon-record1:before { content: "\e7f8"; } .icon-audio-line:before { content: "\e794"; } .icon-record2:before { content: "\e795"; } .icon-audio-fill:before { content: "\e796"; } .icon-PTZ:before { content: "\e797"; } .icon-camera1196054easyiconnet:before { content: "\e791"; } .icon-weibiaoti10:before { content: "\e78f"; } .icon-weibiaoti11:before { content: "\e790"; } .icon-page-next1:before { content: "\e69c"; } .icon-page-last1:before { content: "\e69d"; } .icon-ptz-down1:before { content: "\e69e"; } .icon-file-search1:before { content: "\e69f"; } .icon-page-first1:before { content: "\e6a0"; } .icon-fork1:before { content: "\e6a1"; } .icon-ptz-middle1:before { content: "\e6a2"; } .icon-ptz-upright1:before { content: "\e6a3"; } .icon-ptz-downleft1:before { content: "\e6a4"; } .icon-window-restore1:before { content: "\e6a5"; } .icon-plus1:before { content: "\e6a6"; } .icon-ptz-right1:before { content: "\e6a7"; } .icon-stop:before { content: "\e6a8"; } .icon-refresh1:before { content: "\e6a9"; } .icon-tool-polyline1:before { content: "\e6aa"; } .icon-tool-point1:before { content: "\e6ab"; } .icon-minus1:before { content: "\e6ac"; } .icon-ptz-wiper1:before { content: "\e6ad"; } .icon-tool-select1:before { content: "\e6ae"; } .icon-tool-polygon1:before { content: "\e6af"; } .icon-settings1:before { content: "\e6b0"; } .icon-search1:before { content: "\e6b1"; } .icon-ir-vis1:before { content: "\e6b2"; } .icon-ptz-light1:before { content: "\e6b3"; } .icon-ptz-up1:before { content: "\e6b4"; } .icon-ptz-upleft1:before { content: "\e6b5"; } .icon-temp-stream1:before { content: "\e6b6"; } .icon-tool-mouse1:before { content: "\e6b7"; } .icon-zhongyingwenyingwen-01:before { content: "\e6b8"; } .icon-zhongyingwenyingwen02-01:before { content: "\e6b9"; } .icon-crop2:before { content: "\e6ba"; } .icon-expander-down2:before { content: "\e6bb"; } .icon-window-restore2:before { content: "\e6bc"; } .icon-file-jpg2:before { content: "\e6bd"; } .icon-asterisk3:before { content: "\e6be"; } .icon-ffc2:before { content: "\e6bf"; } .icon-file-record2:before { content: "\e6c0"; } .icon-file-stream2:before { content: "\e6c1"; } .icon-fork2:before { content: "\e6c2"; } .icon-file-mp42:before { content: "\e6c3"; } .icon-ir-vis2:before { content: "\e6c4"; } .icon-file-search2:before { content: "\e6c5"; } .icon-pause:before { content: "\e6c6"; } .icon-play1:before { content: "\e6c7"; } .icon-page-previous2:before { content: "\e6c8"; } .icon-page-next2:before { content: "\e6c9"; } .icon-minus2:before { content: "\e6ca"; } .icon-page-last2:before { content: "\e6cb"; } .icon-page-first2:before { content: "\e6cc"; } .icon-ptz-downleft2:before { content: "\e6cd"; } .icon-ptz-downright2:before { content: "\e6ce"; } .icon-ptz-middle2:before { content: "\e6cf"; } .icon-ptz-down2:before { content: "\e6d0"; } .icon-plus2:before { content: "\e6d1"; } .icon-ptz-left2:before { content: "\e6d2"; } .icon-ptz-up2:before { content: "\e6d3"; } .icon-ptz-right2:before { content: "\e6d4"; } .icon-ptz-light2:before { content: "\e6d5"; } .icon-ptz-wiper2:before { content: "\e6d6"; } .icon-ptz-upright2:before { content: "\e6d7"; } .icon-search2:before { content: "\e6d8"; } .icon-refresh2:before { content: "\e6d9"; } .icon-ptz-upleft2:before { content: "\e6da"; } .icon-stop1:before { content: "\e6db"; } .icon-tool-mouse2:before { content: "\e6dc"; } .icon-settings2:before { content: "\e6dd"; } .icon-tool-polygon2:before { content: "\e6de"; } .icon-tool-point2:before { content: "\e6df"; } .icon-temp-stream2:before { content: "\e6e0"; } .icon-tool-polyline2:before { content: "\e6e1"; } .icon-window-maximize2:before { content: "\e6e2"; } .icon-window-minimize2:before { content: "\e6e3"; } .icon-tool-select2:before { content: "\e6e4"; } .icon-video-stream2:before { content: "\e6e5"; } .icon-bianji1:before { content: "\e6e6"; } .icon-caidanzhankai1:before { content: "\e6e7"; } .icon-cha11:before { content: "\e6e8"; } .icon-caidanshouqi1:before { content: "\e6e9"; } .icon-zhongyingwen2zhongwen1:before { content: "\e6ea"; } .icon-bofang011:before { content: "\e6eb"; } .icon-zuo:before { content: "\e6ec"; } .icon-baojing1:before { content: "\e6ed"; } .icon-fuxuankuang-true1:before { content: "\e6ee"; } .icon-bofang2:before { content: "\e6ef"; } .icon-baojingshezhi1:before { content: "\e6f0"; } .icon-jiahao2:before { content: "\e6f1"; } .icon-huifangxuanzhong1:before { content: "\e6f2"; } .icon-cewen1:before { content: "\e6f3"; } .icon-baojingjilu2:before { content: "\e6f4"; } .icon-danxuan1:before { content: "\e6f5"; } .icon-pingmufenge1:before { content: "\e6f6"; } .icon-luxiangguanli1:before { content: "\e6f7"; } .icon-goukuang:before { content: "\e6f8"; } .icon-shanchu11:before { content: "\e6f9"; } .icon-cha02:before { content: "\e6fa"; } .icon-huifang1:before { content: "\e6fb"; } .icon-rili1:before { content: "\e6fc"; } .icon-quanping1:before { content: "\e6fd"; } .icon-jianhao1:before { content: "\e6fe"; } .icon-shijian1:before { content: "\e6ff"; } .icon-shishiyulanxuanzhong1:before { content: "\e700"; } .icon-shouji1:before { content: "\e701"; } .icon-shouyexuanzhong1:before { content: "\e702"; } .icon-luxiang01:before { content: "\e703"; } .icon-shishiyulan:before { content: "\e704"; } .icon-quxiao:before { content: "\e601"; } .icon-sousuo1:before { content: "\e705"; } .icon-file-record:before { content: "\e602"; } .icon-shebeiguanli1:before { content: "\e706"; } .icon-play:before { content: "\e603"; } .icon-suo1:before { content: "\e707"; } .icon-file-stream:before { content: "\e604"; } .icon-tuichudenglu1:before { content: "\e708"; } .icon-ptz-middle:before { content: "\e606"; } .icon-wenhao1:before { content: "\e709"; } .icon-minus:before { content: "\e607"; } .icon-shezhixuanzhong:before { content: "\e70a"; } .icon-fork:before { content: "\e608"; } .icon-shezhiweixuanzhong1:before { content: "\e70b"; } .icon-ptz-up:before { content: "\e609"; } .icon-shuju2:before { content: "\e70c"; } .icon-file-jpg:before { content: "\e60a"; } .icon-xiazai011:before { content: "\e70d"; } .icon-ptz-left:before { content: "\e60b"; } .icon-xiala11:before { content: "\e70e"; } .icon-ptz-down:before { content: "\e60c"; } .icon-shuaxin:before { content: "\e70f"; } .icon-file-search:before { content: "\e60d"; } .icon-pingmufenge01:before { content: "\e710"; } .icon-crop:before { content: "\e60e"; } .icon-yonghu1:before { content: "\e711"; } .icon-asterisk:before { content: "\e60f"; } .icon-wenhao01:before { content: "\e712"; } .icon-expander-down:before { content: "\e610"; } .icon-you:before { content: "\e713"; } .icon-ptz-right:before { content: "\e611"; } .icon-shujuxuanzhong1:before { content: "\e714"; } .icon-ptz-wiper:before { content: "\e612"; } .icon-kuangxuan1:before { content: "\e715"; } .icon-ir-vis:before { content: "\e613"; } .icon-yonghuguanli1:before { content: "\e716"; } .icon-ptz-upleft:before { content: "\e614"; } .icon-zhongyingwenyingwen:before { content: "\e717"; } .icon-ptz-downright:before { content: "\e615"; } .icon-xiala2:before { content: "\e718"; } .icon-search:before { content: "\e616"; } .icon-luxiang:before { content: "\e719"; } .icon-ptz-upright:before { content: "\e617"; } .icon-zanting2:before { content: "\e71a"; } .icon-ptz-downleft:before { content: "\e618"; } .icon-kefu:before { content: "\e71b"; } .icon-tool-point:before { content: "\e619"; } .icon-jiqiren:before { content: "\e71c"; } .icon-ptz-light:before { content: "\e61a"; } .icon-huanliuzhan:before { content: "\e71d"; } .icon-tool-polyline:before { content: "\e61b"; } .icon-shouji2:before { content: "\e71e"; } .icon-file-mp4:before { content: "\e61c"; } .icon-cangku:before { content: "\e71f"; } .icon-window-maximize:before { content: "\e61d"; } .icon-shuaxin11:before { content: "\e720"; } .icon-page-next:before { content: "\e61e"; } .icon-weixiu:before { content: "\e721"; } .icon-ffc:before { content: "\e61f"; } .icon-biandianzhan:before { content: "\e722"; } .icon-tool-mouse:before { content: "\e620"; } .icon-youxiang:before { content: "\e723"; } .icon-settings:before { content: "\e621"; } .icon-qq:before { content: "\e724"; } .icon-page-last:before { content: "\e622"; } .icon-dianhua01:before { content: "\e725"; } .icon-window-restore:before { content: "\e624"; } .icon-fasongyoujian:before { content: "\e726"; } .icon-tool-select:before { content: "\e625"; } .icon-gaotieyunhangcopy:before { content: "\e727"; } .icon-video-stream:before { content: "\e627"; } .icon-dizhi:before { content: "\e728"; } .icon-page-first:before { content: "\e628"; } .icon-anfangbaojingmian:before { content: "\e729"; } .icon-page-previous:before { content: "\e629"; } .icon-piliangcaozuo1:before { content: "\e72a"; } .icon-refresh:before { content: "\e62a"; } .icon-qiyeguanli1:before { content: "\e72b"; } .icon-temp-stream:before { content: "\e62b"; } .icon-luxiangguanli2:before { content: "\e72c"; } .icon-tool-polygon:before { content: "\e62c"; } .icon-quanxianguanli1:before { content: "\e72d"; } .icon-window-minimize:before { content: "\e62d"; } .icon-shezhi1:before { content: "\e72e"; } .icon-plus:before { content: "\e62e"; } .icon-shishi1:before { content: "\e72f"; } .icon-qiyeguanli:before { content: "\e62f"; } .icon-shujuquanxian1:before { content: "\e730"; } .icon-quanxianguanli:before { content: "\e630"; } .icon-shishiyulanxuanzhong2:before { content: "\e731"; } .icon-shujuquanxian:before { content: "\e631"; } .icon-renzheng:before { content: "\e732"; } .icon--_baojinglianxiren:before { content: "\e632"; } .icon-shuju3:before { content: "\e733"; } .icon-yuechi:before { content: "\e633"; } .icon-shouye1:before { content: "\e734"; } .icon-xitongguanli:before { content: "\e634"; } .icon-zuzhi1:before { content: "\e735"; } .icon-zuzhi:before { content: "\e635"; } .icon-zuzhiguanli1:before { content: "\e736"; } .icon-renzheng6:before { content: "\e636"; } .icon-xitongguanli1:before { content: "\e737"; } .icon-yonghuguanli01:before { content: "\e637"; } .icon-yuechi1:before { content: "\e738"; } .icon-baojingmoban:before { content: "\e638"; } .icon-baojinglianxiren:before { content: "\e739"; } .icon-zuzhiguanli:before { content: "\e639"; } .icon-baojingjilu3:before { content: "\e73a"; } .icon-yonghuguanli:before { content: "\e63a"; } .icon-huifangxuanzhong2:before { content: "\e73b"; } .icon-bumenguanli:before { content: "\e63b"; } .icon-caiwu1:before { content: "\e73c"; } .icon-shishi:before { content: "\e63c"; } .icon-baojingguize1:before { content: "\e73d"; } .icon-baojing:before { content: "\e63d"; } .icon-bumenguanli1:before { content: "\e73e"; } .icon-shezhi:before { content: "\e63e"; } .icon-baojing2:before { content: "\e73f"; } .icon-huifangxuanzhong:before { content: "\e63f"; } .icon-yonghuguanli2:before { content: "\e740"; } .icon-luxiangguanli:before { content: "\e640"; } .icon-huifang2:before { content: "\e741"; } .icon-huifang:before { content: "\e642"; } .icon-baojingmoban1:before { content: "\e742"; } .icon-shouye:before { content: "\e643"; } .icon-dingdanxiangqing1:before { content: "\e743"; } .icon-shishiyulanxuanzhong:before { content: "\e644"; } .icon-fapiaoguanli1:before { content: "\e744"; } .icon-caiwu:before { content: "\e645"; } .icon-shiyonggaikuang1:before { content: "\e745"; } .icon-baojingjilu:before { content: "\e646"; } .icon-zengzhifuwu1:before { content: "\e746"; } .icon-baojingguize:before { content: "\e647"; } .icon-yiguanzhu:before { content: "\e747"; } .icon-shuju:before { content: "\e648"; } .icon-baojingtuisongshezhi1:before { content: "\e748"; } .icon-piliangcaozuo:before { content: "\e649"; } .icon-quxiao1:before { content: "\e749"; } .icon-suo:before { content: "\e64a"; } .icon-xiangqing1:before { content: "\e74a"; } .icon-yonghu:before { content: "\e64b"; } .icon-xufei1:before { content: "\e74b"; } .icon-shouji:before { content: "\e64c"; } .icon-zhifu1:before { content: "\e74c"; } .icon-tianjiadian:before { content: "\e64d"; } .icon-kuang:before { content: "\e74d"; } .icon-tianjiaxian:before { content: "\e64e"; } .icon-shouzhimingxi:before { content: "\e74e"; } .icon-tianjiaxuanqu:before { content: "\e64f"; } .icon-shouzhimingxi1:before { content: "\e74f"; } .icon-xuanzeduixiang:before { content: "\e650"; } .icon-daochu:before { content: "\e750"; } .icon-baojing01:before { content: "\e651"; } .icon-daochu1:before { content: "\e751"; } .icon-baojingjilu1:before { content: "\e652"; } .icon-daping:before { content: "\e752"; } .icon-baojingshezhi:before { content: "\e653"; } .icon-shaixuan:before { content: "\e753"; } .icon-cewen:before { content: "\e654"; } .icon-zhifu2:before { content: "\e754"; } .icon-tuichudenglu:before { content: "\e655"; } .icon-shaixuan1:before { content: "\e755"; } .icon-shezhiweixuanzhong:before { content: "\e656"; } .icon-zhifu3:before { content: "\e756"; } .icon-shezhixuanzhong1:before { content: "\e657"; } .icon-xia:before { content: "\e757"; } .icon-shouyexuanzhong:before { content: "\e658"; } .icon-xia1:before { content: "\e758"; } .icon-shujuxuanzhong:before { content: "\e659"; } .icon-yanzhengma:before { content: "\e759"; } .icon-shuju1:before { content: "\e65a"; } .icon-tongxunlu:before { content: "\e75a"; } .icon-bianji:before { content: "\e65b"; } .icon-yanzhengma1:before { content: "\e75b"; } .icon-rili:before { content: "\e65c"; } .icon-tongxunlu1:before { content: "\e75c"; } .icon-shanchu:before { content: "\e65d"; } .icon-yingyongbangding:before { content: "\e75d"; } .icon-jiahao:before { content: "\e65e"; } .icon-yingyongbangding1:before { content: "\e75e"; } .icon-wenhao:before { content: "\e65f"; } .icon-yingyongbangding2:before { content: "\e75f"; } .icon-zhongyingwen:before { content: "\e660"; } .icon-dapingzhanshi:before { content: "\e760"; } .icon-kuangxuan:before { content: "\e661"; } .icon-jiankong:before { content: "\e761"; } .icon-cha1:before { content: "\e662"; } .icon-touxiang:before { content: "\e762"; } .icon-bofang01:before { content: "\e663"; } .icon-lou:before { content: "\e763"; } .icon-caidanzhankai:before { content: "\e664"; } .icon-jiankong1:before { content: "\e764"; } .icon-caidanshouqi:before { content: "\e665"; } .icon-lou1:before { content: "\e765"; } .icon-danxuan:before { content: "\e666"; } .icon-dapingzhanshi1:before { content: "\e766"; } .icon-fuxuankuangxuanzhong:before { content: "\e667"; } .icon-touxiang1:before { content: "\e767"; } .icon-fuxuankuang-true:before { content: "\e668"; } .icon-shebei:before { content: "\e768"; } .icon-jianhao:before { content: "\e669"; } .icon-shebeii:before { content: "\e769"; } .icon-shanchu1:before { content: "\e66a"; } .icon-bianji11:before { content: "\e76a"; } .icon-shijian:before { content: "\e66b"; } .icon-jilu:before { content: "\e76b"; } .icon-jiahao1:before { content: "\e66c"; } .icon-yun:before { content: "\e76c"; } .icon-sousuo:before { content: "\e66d"; } .icon-baojing3:before { content: "\e76d"; } .icon-zhongyingwen2zhongwen:before { content: "\e66e"; } .icon-zhinengyangan:before { content: "\e76e"; } .icon-xiala:before { content: "\e66f"; } .icon-yongdiananquan:before { content: "\e76f"; } .icon-xiala1:before { content: "\e670"; } .icon-zhinengmensuo:before { content: "\e770"; } .icon-xiazai01:before { content: "\e671"; } .icon-xiaokongyujing:before { content: "\e771"; } .icon-pingmufenge02:before { content: "\e672"; } .icon-zhinengdianbiao:before { content: "\e772"; } .icon-shezhi01:before { content: "\e673"; } .icon-zhinengshuibiao:before { content: "\e773"; } .icon-zuixiaohuaxi:before { content: "\e674"; } .icon-shuiyajiance01:before { content: "\e774"; } .icon-zuidahuaxi:before { content: "\e675"; } .icon-zhinengzhaoming:before { content: "\e775"; } .icon-huifuxi:before { content: "\e676"; } .icon-zhinengmenjin:before { content: "\e776"; } .icon-guanbixi:before { content: "\e677"; } .icon-tingchechang:before { content: "\e777"; } .icon-baocunJPG:before { content: "\e678"; } .icon-xiala3:before { content: "\e778"; } .icon-quxian:before { content: "\e679"; } .icon-zhinengkongtiao:before { content: "\e779"; } .icon-tingzhiyulan:before { content: "\e67a"; } .icon-sousuo2:before { content: "\e77a"; } .icon-wenduliuluzhi:before { content: "\e67b"; } .icon-shang1:before { content: "\e77b"; } .icon-shuaxin1:before { content: "\e67c"; } .icon-1_jingdianchuwuweixuanzhong:before { content: "\e77c"; } .icon-shangjiantou:before { content: "\e67d"; } .icon-dianti:before { content: "\e77d"; } .icon-shang:before { content: "\e67e"; } .icon-zhuangtai:before { content: "\e77e"; } .icon-zixun:before { content: "\e67f"; } .icon-keshi:before { content: "\e77f"; } .icon-youxiang01:before { content: "\e680"; } .icon-chongzhijilu:before { content: "\e780"; } .icon-QQ:before { content: "\e681"; } .icon-jingshi:before { content: "\e781"; } .icon-dianhua:before { content: "\e682"; } .icon-bianji2:before { content: "\e782"; } .icon-pingmufenge:before { content: "\e683"; } .icon-fuzhi:before { content: "\e783"; } .icon-gou:before { content: "\e684"; } .icon-guanyu:before { content: "\e784"; } .icon-dingdanxiangqing:before { content: "\e685"; } .icon-shishiyulan-01:before { content: "\e785"; } .icon-shiyonggaikuang:before { content: "\e686"; } .icon-shujuchakan:before { content: "\e786"; } .icon-fapiaoguanli:before { content: "\e687"; } .icon-shanchu2:before { content: "\e787"; } .icon-xiangqing:before { content: "\e688"; } .icon-xitongpeizhi:before { content: "\e788"; } .icon-baojingtuisongshezhi:before { content: "\e689"; } .icon-tezhengwendu:before { content: "\e789"; } .icon-zhifu:before { content: "\e68a"; } .icon-quanzhenwendu:before { content: "\e78a"; } .icon-zengzhifuwu:before { content: "\e68b"; } .icon-fenxiang:before { content: "\e78b"; } .icon-xufei:before { content: "\e68c"; } .icon-fenxiang01:before { content: "\e78c"; } .icon-asterisk1:before { content: "\e68d"; } .icon-wenhao2:before { content: "\e78d"; } .icon-window-maximize1:before { content: "\e68e"; } .icon-dian:before { content: "\e78e"; } .icon-crop1:before { content: "\e68f"; } .icon-asterisk2:before { content: "\e690"; } .icon-file-record1:before { content: "\e691"; } .icon-ffc1:before { content: "\e692"; } .icon-file-mp41:before { content: "\e693"; } .icon-window-minimize1:before { content: "\e694"; } .icon-ptz-downright1:before { content: "\e695"; } .icon-video-stream1:before { content: "\e696"; } .icon-file-jpg1:before { content: "\e697"; } .icon-file-stream1:before { content: "\e698"; } .icon-page-previous1:before { content: "\e699"; } .icon-expander-down1:before { content: "\e69a"; } .icon-ptz-left1:before { content: "\e69b"; } .icon-yinpinwenjian1:before { content: "\e623"; } .icon-yinpinwenjian2:before { content: "\e626"; } .icon-xiazaiyinpinwenjian:before { content: "\e605"; } .icon-yinpinwenjian:before { content: "\e641"; } ================================================ FILE: web/src/styles/index.scss ================================================ @import './variables.scss'; @import './mixin.scss'; @import './transition.scss'; @import './element-ui.scss'; @import './sidebar.scss'; @import './iconfont.scss'; body { height: 100%; -moz-osx-font-smoothing: grayscale; -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif; } label { font-weight: 700; } html { height: 100%; box-sizing: border-box; } #app { height: 100%; } *, *:before, *:after { box-sizing: inherit; } a:focus, a:active { outline: none; } a, a:focus, a:hover { cursor: pointer; color: inherit; text-decoration: none; } div:focus { outline: none; } .clearfix { &:after { visibility: hidden; display: block; font-size: 0; content: " "; clear: both; height: 0; } } // main-container global css .app-container { padding: 20px; } ================================================ FILE: web/src/styles/mixin.scss ================================================ @mixin clearfix { &:after { content: ""; display: table; clear: both; } } @mixin scrollBar { &::-webkit-scrollbar-track-piece { background: #d3dce6; } &::-webkit-scrollbar { width: 6px; } &::-webkit-scrollbar-thumb { background: #99a9bf; border-radius: 20px; } } @mixin relative { position: relative; width: 100%; height: 100%; } ================================================ FILE: web/src/styles/sidebar.scss ================================================ #app { .main-container { min-height: 100%; transition: margin-left .28s; margin-left: $sideBarWidth; position: relative; } .sidebar-container { transition: width 0.28s; width: $sideBarWidth !important; background-color: $menuBg; height: 100%; position: fixed; font-size: 0px; top: 0; bottom: 0; left: 0; z-index: 1001; overflow: hidden; // reset element-ui css .horizontal-collapse-transition { transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out; } .scrollbar-wrapper { overflow-x: hidden !important; } .el-scrollbar__bar.is-vertical { right: 0px; } .el-scrollbar { height: 100%; } &.has-logo { .el-scrollbar { height: calc(100% - 50px); } } .is-horizontal { display: none; } a { display: inline-block; width: 100%; overflow: hidden; } .svg-icon { margin-right: 16px; } .sub-el-icon { margin-right: 12px; margin-left: -2px; } .el-menu { border: none; height: 100%; width: 100% !important; } // menu hover .submenu-title-noDropdown, .el-submenu__title { &:hover { background-color: $menuHover !important; } } .is-active>.el-submenu__title { color: $subMenuActiveText !important; } & .nest-menu .el-submenu>.el-submenu__title, & .el-submenu .el-menu-item { min-width: $sideBarWidth !important; background-color: $subMenuBg !important; &:hover { background-color: $subMenuHover !important; } } } .hideSidebar { .sidebar-container { width: 54px !important; } .main-container { margin-left: 54px; } .submenu-title-noDropdown { padding: 0 !important; position: relative; .el-tooltip { padding: 0 !important; .svg-icon { margin-left: 20px; } .sub-el-icon { margin-left: 19px; } } } .el-submenu { overflow: hidden; &>.el-submenu__title { padding: 0 !important; .svg-icon { margin-left: 20px; } .sub-el-icon { margin-left: 19px; } .el-submenu__icon-arrow { display: none; } } } .el-menu--collapse { .el-submenu { &>.el-submenu__title { &>span { height: 0; width: 0; overflow: hidden; visibility: hidden; display: inline-block; } } } } } .el-menu--collapse .el-menu .el-submenu { min-width: $sideBarWidth !important; } // mobile responsive .mobile { .main-container { margin-left: 0px; } .sidebar-container { transition: transform .28s; width: $sideBarWidth !important; } &.hideSidebar { .sidebar-container { pointer-events: none; transition-duration: 0.3s; transform: translate3d(-$sideBarWidth, 0, 0); } } } .withoutAnimation { .main-container, .sidebar-container { transition: none; } } } // when menu collapsed .el-menu--vertical { &>.el-menu { .svg-icon { margin-right: 16px; } .sub-el-icon { margin-right: 12px; margin-left: -2px; } } .nest-menu .el-submenu>.el-submenu__title, .el-menu-item { &:hover { // you can use $subMenuHover background-color: $menuHover !important; } } // the scroll bar appears when the subMenu is too long >.el-menu--popup { max-height: 100vh; overflow-y: auto; &::-webkit-scrollbar-track-piece { background: #d3dce6; } &::-webkit-scrollbar { width: 6px; } &::-webkit-scrollbar-thumb { background: #99a9bf; border-radius: 20px; } } } ================================================ FILE: web/src/styles/transition.scss ================================================ // global transition css /* fade */ .fade-enter-active, .fade-leave-active { transition: opacity 0.28s; } .fade-enter, .fade-leave-active { opacity: 0; } /* fade-transform */ .fade-transform-leave-active, .fade-transform-enter-active { transition: all .5s; } .fade-transform-enter { opacity: 0; transform: translateX(-30px); } .fade-transform-leave-to { opacity: 0; transform: translateX(30px); } /* breadcrumb transition */ .breadcrumb-enter-active, .breadcrumb-leave-active { transition: all .5s; } .breadcrumb-enter, .breadcrumb-leave-active { opacity: 0; transform: translateX(20px); } .breadcrumb-move { transition: all .5s; } .breadcrumb-leave-active { position: absolute; } ================================================ FILE: web/src/styles/variables.scss ================================================ // sidebar $menuText:#bfcbd9; $menuActiveText:#409EFF; $subMenuActiveText: #f4f4f5; //https://github.com/ElemeFE/element/issues/12951 $menuBg: #304156; $menuHover:#263445; $subMenuBg:#1f2d3d; $subMenuHover:#001528; $sideBarWidth: 210px; // the :export directive is the magic sauce for webpack // https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass :export { menuText: $menuText; menuActiveText: $menuActiveText; subMenuActiveText: $subMenuActiveText; menuBg: $menuBg; menuHover: $menuHover; subMenuBg: $subMenuBg; subMenuHover: $subMenuHover; sideBarWidth: $sideBarWidth; } ================================================ FILE: web/src/utils/auth.js ================================================ import Cookies from 'js-cookie' const TokenKey = 'wvp_token' const NameKey = 'wvp_username' const serverIdKey = 'wvp_server_id' const expires = 30 export function getToken() { console.log('Getting token...') return Cookies.get(TokenKey) } export function setToken(token) { return Cookies.set(TokenKey, token, {expires: expires}) } export function removeToken() { return Cookies.remove(TokenKey) } export function getName() { return Cookies.get(NameKey) } export function setName(name) { return Cookies.set(NameKey, name, {expires: expires}) } export function removeName() { return Cookies.remove(NameKey) } export function getServerId() { return Cookies.get(serverIdKey) } export function setServerId(serverId) { return Cookies.set(serverIdKey, serverId, {expires: expires}) } export function removeServerId() { return Cookies.remove(serverIdKey) } ================================================ FILE: web/src/utils/diff.js ================================================ // utils/diff.js // 返回 newObj 相对于 oldObj 的变化部分(新增/修改)。 // 可选 includeRemoved=true 时把被删除字段以 removedValue 标记返回。 // 使用示例: diff(oldObj, newObj) 或 diff(oldObj, newObj, { includeRemoved: true }) export function diff(oldObj, newObj, options = {}) { const { includeRemoved = false, removedValue = null, comparator } = options function isObject(v) { return v && typeof v === 'object' && !Array.isArray(v) && !(v instanceof Date) } function isDate(v) { return v instanceof Date } function equal(a, b) { if (typeof comparator === 'function') return comparator(a, b) if (isDate(a) && isDate(b)) return a.getTime() === b.getTime() if (Array.isArray(a) && Array.isArray(b)) { if (a.length !== b.length) return false for (let i = 0; i < a.length; i++) if (!equal(a[i], b[i])) return false return true } if (isObject(a) && isObject(b)) { const aKeys = Object.keys(a) const bKeys = Object.keys(b) if (aKeys.length !== bKeys.length) return false for (const k of aKeys) { if (!Object.prototype.hasOwnProperty.call(b, k) || !equal(a[k], b[k])) return false } return true } return a === b } function inner(o, n) { // Normalize undefined -> empty object for easier handling in object branch. const oIsObj = isObject(o) const nIsObj = isObject(n) if (equal(o, n)) return undefined if (oIsObj && nIsObj) { const result = {} const keys = new Set([...Object.keys(o || {}), ...Object.keys(n || {})]) for (const k of keys) { console.log(k) const hasO = Object.prototype.hasOwnProperty.call(o || {}, k) const hasN = Object.prototype.hasOwnProperty.call(n || {}, k) if (hasN) { const sub = inner(hasO ? o[k] : undefined, n[k]) if (sub !== undefined) result[k] = sub } else if (hasO && includeRemoved) { // key existed before but removed now result[k] = removedValue } } return Object.keys(result).length ? result : undefined } if (Array.isArray(o) && Array.isArray(n)) { return equal(o, n) ? undefined : n } if (isDate(n)) return n // Different primitive or type change -> return new value return n } const res = inner(oldObj ?? {}, newObj ?? {}) return res === undefined ? {} : res } export default diff ================================================ FILE: web/src/utils/get-page-title.js ================================================ import defaultSettings from '@/settings' const title = defaultSettings.title || 'WVP视频平台' export default function getPageTitle(pageTitle) { if (pageTitle) { return `${pageTitle} - ${title}` } return `${title}` } ================================================ FILE: web/src/utils/index.js ================================================ /** * Created by PanJiaChen on 16/11/18. */ /** * Parse the time to string * @param {(Object|string|number)} time * @param {string} cFormat * @returns {string | null} */ export function parseTime(time, cFormat) { if (arguments.length === 0 || !time) { return null } const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}' let date if (typeof time === 'object') { date = time } else { if ((typeof time === 'string')) { if ((/^[0-9]+$/.test(time))) { // support "1548221490638" time = parseInt(time) } else { // support safari // https://stackoverflow.com/questions/4310953/invalid-date-in-safari time = time.replace(new RegExp(/-/gm), '/') } } if ((typeof time === 'number') && (time.toString().length === 10)) { time = time * 1000 } date = new Date(time) } const formatObj = { y: date.getFullYear(), m: date.getMonth() + 1, d: date.getDate(), h: date.getHours(), i: date.getMinutes(), s: date.getSeconds(), a: date.getDay() } const time_str = format.replace(/{([ymdhisa])+}/g, (result, key) => { const value = formatObj[key] // Note: getDay() returns 0 on Sunday if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value ] } return value.toString().padStart(2, '0') }) return time_str } /** * @param {number} time * @param {string} option * @returns {string} */ export function formatTime(time, option) { if (('' + time).length === 10) { time = parseInt(time) * 1000 } else { time = +time } const d = new Date(time) const now = Date.now() const diff = (now - d) / 1000 if (diff < 30) { return '刚刚' } else if (diff < 3600) { // less 1 hour return Math.ceil(diff / 60) + '分钟前' } else if (diff < 3600 * 24) { return Math.ceil(diff / 3600) + '小时前' } else if (diff < 3600 * 24 * 2) { return '1天前' } if (option) { return parseTime(time, option) } else { return ( d.getMonth() + 1 + '月' + d.getDate() + '日' + d.getHours() + '时' + d.getMinutes() + '分' ) } } /** * @param {string} url * @returns {Object} */ export function param2Obj(url) { const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ') if (!search) { return {} } const obj = {} const searchArr = search.split('&') searchArr.forEach(v => { const index = v.indexOf('=') if (index !== -1) { const name = v.substring(0, index) const val = v.substring(index + 1, v.length) obj[name] = val } }) return obj } ================================================ FILE: web/src/utils/request.js ================================================ import axios from 'axios' import { MessageBox, Message } from 'element-ui' import store from '@/store' import { getToken } from '@/utils/auth' let showLoginConfirm = false // create an axios instance const service = axios.create({ baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url // withCredentials: true, // send cookies when cross-domain requests timeout: 30000 // request timeout }) // request interceptor service.interceptors.request.use( config => { // do something before request is sent if (store.getters.token && config.url.indexOf('api/user/login') < 0) { config.headers['access-token'] = getToken() } return config }, error => { // do something with request error console.log(error) // for debug return Promise.reject(error) } ) // response interceptor service.interceptors.response.use( /** * If you want to get http information such as headers or status * Please return response => response */ /** * Determine the request status by custom code * Here is just an example * You can also judge the status by HTTP Status Code */ response => { if (response.config.url.indexOf('/api/user/logout') >= 0) { return } const res = response.data if (res.code && res.code !== 0) { throw res.msg } else { return res } }, error => { console.log(error) // for debug if (error.response.status === 401) { if (!showLoginConfirm && store.getters.showConfirmBoxForLoginLose) { // to re-login showLoginConfirm = true MessageBox.confirm('登录已经到期, 是否重新登录', '登录确认', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => { store.dispatch('user/resetToken').then(() => { location.reload() }) }).catch(() => { store.dispatch('user/closeConfirmBoxForLoginLose') Message.warning({ type: 'warning', message: '登录过期提示已经关闭,请注销后重新登录' }) // 清除token, 后续请求不再继续 }) } }else { if (!store.getters.showConfirmBoxForLoginLose) { return } let data = error.response.data if (data && data.msg) { Message.error({ message: data.msg, showClose: true }) }else { Message.error({ message: error.message, showClose: true }) } } // return Promise.reject(error) } ) export default service ================================================ FILE: web/src/utils/validate.js ================================================ /** * Created by PanJiaChen on 16/11/18. */ /** * @param {string} path * @returns {Boolean} */ export function isExternal(path) { return /^(https?:|mailto:|tel:)/.test(path) } /** * @param {string} str * @returns {Boolean} */ export function validUsername(str) { return str.length >= 0 } ================================================ FILE: web/src/views/404.vue ================================================ ================================================ FILE: web/src/views/channel/edit.vue ================================================ ================================================ FILE: web/src/views/channel/group/index.vue ================================================ ================================================ FILE: web/src/views/channel/index.vue ================================================ ================================================ FILE: web/src/views/channel/record.vue ================================================ ================================================ FILE: web/src/views/channel/region/index.vue ================================================ ================================================ FILE: web/src/views/cloudRecord/cloudRecordPlayer.vue ================================================ ================================================ FILE: web/src/views/cloudRecord/detail.vue ================================================ ================================================ FILE: web/src/views/cloudRecord/index.vue ================================================ ================================================ FILE: web/src/views/cloudRecord/playerDialog.vue ================================================ ================================================ FILE: web/src/views/common/CommonChannelEdit.vue ================================================ ================================================ FILE: web/src/views/common/DeviceTree.vue ================================================ ================================================ FILE: web/src/views/common/GroupTree.vue ================================================ ================================================ FILE: web/src/views/common/MapComponent.vue ================================================ ================================================ FILE: web/src/views/common/MapComponent_bak.vue ================================================ ================================================ FILE: web/src/views/common/RegionTree.vue ================================================ ================================================ FILE: web/src/views/common/VideoTimeLine/WindowListItem.vue ================================================ ================================================ FILE: web/src/views/common/VideoTimeLine/constant.js ================================================ // 一小时的毫秒数 export const ONE_HOUR_STAMP = 60 * 60 * 1000 // 时间分辨率,即整个时间轴表示的时间范围 export const ZOOM = [0.5, 1, 2, 6, 12, 24, 72, 360, 720, 8760, 87600]// 半小时、1小时、2小时、6小时、12小时、1天、3天、15天、30天、365天、365*10天 // 时间分辨率对应的每格小时数,即最小格代表多少小时 export const ZOOM_HOUR_GRID = [1 / 60, 1 / 60, 2 / 60, 1 / 6, 0.25, 0.5, 1, 4, 4, 720, 7200] export const MOBILE_ZOOM_HOUR_GRID = [ 1 / 20, 1 / 30, 1 / 20, 1 / 3, 0.5, 2, 4, 4, 4, 720, 7200 ] // 时间分辨率对应的时间显示判断条件 export const ZOOM_DATE_SHOW_RULE = [ () => { // 全部显示 return true }, date => { // 每五分钟显示 return date.getMinutes() % 5 === 0 }, date => { // 每十分钟显示 return date.getMinutes() % 10 === 0 }, date => { // 整点和半点显示 return date.getMinutes() === 0 || date.getMinutes() === 30 }, date => { // 整点显示 return date.getMinutes() === 0 }, date => { // 偶数整点的小时 return date.getHours() % 2 === 0 && date.getMinutes() === 0 }, date => { // 每三小时小时 return date.getHours() % 3 === 0 && date.getMinutes() === 0 }, date => { // 每12小时 return date.getHours() % 12 === 0 && date.getMinutes() === 0 }, date => { // 全不显示 return false }, date => { return true }, date => { return true } ] export const MOBILE_ZOOM_DATE_SHOW_RULE = [ () => { // 全部显示 return true }, date => { // 每五分钟显示 return date.getMinutes() % 5 === 0 }, date => { // 每十分钟显示 return date.getMinutes() % 10 === 0 }, date => { // 整点和半点显示 return date.getMinutes() === 0 || date.getMinutes() === 30 }, date => { // 偶数整点的小时 return date.getHours() % 2 === 0 && date.getMinutes() === 0 }, date => { // 偶数整点的小时 return date.getHours() % 4 === 0 && date.getMinutes() === 0 }, date => { // 每三小时小时 return date.getHours() % 3 === 0 && date.getMinutes() === 0 }, date => { // 每12小时 return date.getHours() % 12 === 0 && date.getMinutes() === 0 }, date => { // 全不显示 return false }, date => { return true }, date => { return true } ] ================================================ FILE: web/src/views/common/VideoTimeLine/index.vue ================================================ ================================================ FILE: web/src/views/common/channelPlayer/chooseChannelForJt.vue ================================================ ================================================ FILE: web/src/views/common/channelPlayer/index.vue ================================================ ================================================ FILE: web/src/views/common/channelPlayer/jtDeviceEdit.vue ================================================ ================================================ FILE: web/src/views/common/channelPlayer/jtDevicePlayer.vue ================================================ ================================================ FILE: web/src/views/common/channelPlayer/ptzCruising.vue ================================================ ================================================ FILE: web/src/views/common/channelPlayer/ptzPreset.vue ================================================ ================================================ FILE: web/src/views/common/channelPlayer/ptzScan.vue ================================================ ================================================ FILE: web/src/views/common/channelPlayer/ptzSwitch.vue ================================================ ================================================ FILE: web/src/views/common/channelPlayer/ptzWiper.vue ================================================ ================================================ FILE: web/src/views/common/h265web.vue ================================================ ================================================ FILE: web/src/views/common/jessibuca.vue ================================================ ================================================ FILE: web/src/views/common/map/DragInteraction.js ================================================ import PointerInteraction from 'ol/interaction/Pointer' import { toLonLat } from './TransformLonLat' class DragInteraction extends PointerInteraction { constructor() { super({ handleDownEvent: (evt) => { const map = evt.map const feature = map.forEachFeatureAtPixel(evt.pixel, (feature) => { if (this.featureIdMap_.has(feature.getId())) { return feature }else { return null } }) if (feature) { this.coordinate_ = evt.coordinate this.feature_ = feature let eventCallback = this.featureIdMap_.get(this.feature_.getId()) if (eventCallback && eventCallback.startEvent) { eventCallback.startEvent(evt) } return !!feature } }, handleDragEvent: (evt) => { const deltaX = evt.coordinate[0] - this.coordinate_[0] const deltaY = evt.coordinate[1] - this.coordinate_[1] const geometry = this.feature_.getGeometry() geometry.translate(deltaX, deltaY) this.coordinate_[0] = evt.coordinate[0] this.coordinate_[1] = evt.coordinate[1] let eventCallback = this.featureIdMap_.get(this.feature_.getId()) if (eventCallback && eventCallback.moveEvent) { eventCallback.moveEvent(evt) } }, handleMoveEvent: (evt) => { if (this.cursor_) { const map = evt.map const feature = map.forEachFeatureAtPixel(evt.pixel, function(feature) { return feature }) const element = evt.map.getTargetElement() if (feature) { if (element.style.cursor != this.cursor_) { this.previousCursor_ = element.style.cursor element.style.cursor = this.cursor_ } } else if (this.previousCursor_ !== undefined) { element.style.cursor = this.previousCursor_ this.previousCursor_ = undefined } } }, handleUpEvent: (evt) => { let eventCallback = this.featureIdMap_.get(this.feature_.getId()) if (eventCallback && eventCallback.endEvent) { evt.lonLat = toLonLat(this.feature_.getGeometry().getCoordinates()) eventCallback.endEvent(evt) } this.coordinate_ = null this.feature_ = null return false } }) /** * @type {import('../src/ol/coordinate.js').Coordinate} * @private */ this.coordinate_ = null /** * @type {string|undefined} * @private */ this.cursor_ = 'pointer' /** * @type {Feature} * @private */ this.feature_ = null /** * @type {string|undefined} * @private */ this.previousCursor_ = undefined this.featureIdMap_ = new Map() this.addFeatureId = (id, moveEndEvent) => { if (this.featureIdMap_.has(id)) { return } this.featureIdMap_.set(id, moveEndEvent) } this.removeFeatureId= (id) => { this.featureIdMap_.delete(id) } this.hasFeatureId= (id) => { this.featureIdMap_.has(id) } } } export default DragInteraction ================================================ FILE: web/src/views/common/map/TransformLonLat.js ================================================ import { fromLonLat as projFromLonLat, toLonLat as projToLonLat } from 'ol/proj' import gcoord from 'gcoord' export function fromLonLat(coordinate) { if (window.coordinateSystem === 'GCJ02') { return projFromLonLat(gcoord.transform(coordinate, gcoord.WGS84, gcoord.GCJ02)) }else { return projFromLonLat(coordinate) } } export function toLonLat(coordinate) { if (window.coordinateSystem === 'GCJ02') { return gcoord.transform(projToLonLat(coordinate), gcoord.GCJ02, gcoord.WGS84) }else { return projToLonLat(coordinate) } } ================================================ FILE: web/src/views/common/mediaInfo.vue ================================================ ================================================ FILE: web/src/views/common/ptzCruising.vue ================================================ ================================================ FILE: web/src/views/common/ptzPreset.vue ================================================ ================================================ FILE: web/src/views/common/ptzScan.vue ================================================ ================================================ FILE: web/src/views/common/ptzSwitch.vue ================================================ ================================================ FILE: web/src/views/common/ptzWiper.vue ================================================ ================================================ FILE: web/src/views/common/rtcPlayer.vue ================================================ ================================================ FILE: web/src/views/common/weekTimePicker.vue ================================================ ================================================ FILE: web/src/views/dashboard/console/ConsoleCPU.vue ================================================ ================================================ FILE: web/src/views/dashboard/console/ConsoleDisk.vue ================================================ ================================================ FILE: web/src/views/dashboard/console/ConsoleMEM.vue ================================================ ================================================ FILE: web/src/views/dashboard/console/ConsoleMediaServer.vue ================================================ ================================================ FILE: web/src/views/dashboard/console/ConsoleNet.vue ================================================ ================================================ FILE: web/src/views/dashboard/console/ConsoleNodeLoad.vue ================================================ ================================================ FILE: web/src/views/dashboard/console/ConsoleResource.vue ================================================ ================================================ FILE: web/src/views/dashboard/index.vue ================================================ ================================================ FILE: web/src/views/device/channel/edit.vue ================================================ ================================================ FILE: web/src/views/device/channel/index.vue ================================================ ================================================ FILE: web/src/views/device/channel/record.vue ================================================ ================================================ FILE: web/src/views/device/edit.vue ================================================ ================================================ FILE: web/src/views/device/index.vue ================================================ ================================================ FILE: web/src/views/device/list.vue ================================================ ================================================ FILE: web/src/views/dialog/GbChannelSelect.vue ================================================ ================================================ FILE: web/src/views/dialog/GbDeviceSelect.vue ================================================ ================================================ FILE: web/src/views/dialog/MediaServerEdit.vue ================================================ ================================================ FILE: web/src/views/dialog/SyncChannelProgress.vue ================================================ ================================================ FILE: web/src/views/dialog/UnusualGroupChannelSelect.vue ================================================ ================================================ FILE: web/src/views/dialog/UnusualRegionChannelSelect.vue ================================================ ================================================ FILE: web/src/views/dialog/addUser.vue ================================================ ================================================ FILE: web/src/views/dialog/addUserApiKey.vue ================================================ ================================================ FILE: web/src/views/dialog/catalogEdit.vue ================================================ ================================================ FILE: web/src/views/dialog/changePasswordForAdmin.vue ================================================ ================================================ FILE: web/src/views/dialog/changePushKey.vue ================================================ ================================================ FILE: web/src/views/dialog/channelCode.vue ================================================ ================================================ FILE: web/src/views/dialog/channelMapInfobox.vue ================================================ ================================================ FILE: web/src/views/dialog/chooseCivilCode.vue ================================================ ================================================ FILE: web/src/views/dialog/chooseGroup.vue ================================================ ================================================ FILE: web/src/views/dialog/chooseTimeRange.vue ================================================ ================================================ FILE: web/src/views/dialog/commonChannelEditDialog.vue ================================================ ================================================ FILE: web/src/views/dialog/configInfo.vue ================================================ ================================================ FILE: web/src/views/dialog/devicePlayer.vue ================================================ ================================================ FILE: web/src/views/dialog/editRecordPlan.vue ================================================ ================================================ FILE: web/src/views/dialog/groupEdit.vue ================================================ ================================================ FILE: web/src/views/dialog/hasStreamChannel.vue ================================================ ================================================ FILE: web/src/views/dialog/importChannel.vue ================================================ ================================================ FILE: web/src/views/dialog/importChannelShowErrorData.vue ================================================ ================================================ FILE: web/src/views/dialog/linkChannelRecord.vue ================================================ ================================================ FILE: web/src/views/dialog/pushStreamEdit.vue ================================================ ================================================ FILE: web/src/views/dialog/queryTrace.vue ================================================ ================================================ FILE: web/src/views/dialog/recordDownload.vue ================================================ ================================================ FILE: web/src/views/dialog/regionCode.vue ================================================ ================================================ FILE: web/src/views/dialog/regionEdit.vue ================================================ ================================================ FILE: web/src/views/dialog/remarkUserApiKey.vue ================================================ ================================================ FILE: web/src/views/dialog/resetChannel.vue ================================================ ================================================ FILE: web/src/views/dialog/shareChannel.vue ================================================ ================================================ FILE: web/src/views/dialog/shareChannelAdd.vue ================================================ ================================================ FILE: web/src/views/form/index.vue ================================================ ================================================ FILE: web/src/views/jtDevice/channel/edit.vue ================================================ ================================================ FILE: web/src/views/jtDevice/channel/index.vue ================================================ ================================================ FILE: web/src/views/jtDevice/channel/record.vue ================================================ ================================================ FILE: web/src/views/jtDevice/deviceParam/alarm.vue ================================================ ================================================ FILE: web/src/views/jtDevice/deviceParam/alarmSign.vue ================================================ ================================================ FILE: web/src/views/jtDevice/deviceParam/awakenParam.vue ================================================ ================================================ FILE: web/src/views/jtDevice/deviceParam/cameraTimer.vue ================================================ ================================================ FILE: web/src/views/jtDevice/deviceParam/canCollectionParam.vue ================================================ ================================================ FILE: web/src/views/jtDevice/deviceParam/carInfo.vue ================================================ ================================================ FILE: web/src/views/jtDevice/deviceParam/communication.vue ================================================ ================================================ FILE: web/src/views/jtDevice/deviceParam/driving.vue ================================================ ================================================ FILE: web/src/views/jtDevice/deviceParam/gnssParam.vue ================================================ ================================================ FILE: web/src/views/jtDevice/deviceParam/imageConfig.vue ================================================ ================================================ FILE: web/src/views/jtDevice/deviceParam/index.vue ================================================ ================================================ FILE: web/src/views/jtDevice/deviceParam/phoneNumber.vue ================================================ ================================================ FILE: web/src/views/jtDevice/deviceParam/position.vue ================================================ ================================================ FILE: web/src/views/jtDevice/deviceParam/server.vue ================================================ ================================================ FILE: web/src/views/jtDevice/deviceParam/videoAlarmSign.vue ================================================ ================================================ FILE: web/src/views/jtDevice/deviceParam/videoParam.vue ================================================ ================================================ FILE: web/src/views/jtDevice/dialog/attribute.vue ================================================ ================================================ FILE: web/src/views/jtDevice/dialog/connectionServer.vue ================================================ ================================================ FILE: web/src/views/jtDevice/dialog/controlDoor.vue ================================================ ================================================ FILE: web/src/views/jtDevice/dialog/driverInfo.vue ================================================ ================================================ FILE: web/src/views/jtDevice/dialog/jtDevicePlayer.vue ================================================ ================================================ FILE: web/src/views/jtDevice/dialog/mediaAttribute.vue ================================================ ================================================ FILE: web/src/views/jtDevice/dialog/phoneBook.vue ================================================ ================================================ FILE: web/src/views/jtDevice/dialog/position.vue ================================================ ================================================ FILE: web/src/views/jtDevice/dialog/queryMediaList.vue ================================================ ================================================ FILE: web/src/views/jtDevice/dialog/queryMediaListDialog.vue ================================================ ================================================ FILE: web/src/views/jtDevice/dialog/shootingNow.vue ================================================ ================================================ FILE: web/src/views/jtDevice/dialog/telephoneCallback.vue ================================================ ================================================ FILE: web/src/views/jtDevice/dialog/textMsg.vue ================================================ ================================================ FILE: web/src/views/jtDevice/edit.vue ================================================ ================================================ FILE: web/src/views/jtDevice/index.vue ================================================ ================================================ FILE: web/src/views/jtDevice/list.vue ================================================ ================================================ FILE: web/src/views/live/index.vue ================================================ ================================================ FILE: web/src/views/login/index.vue ================================================ ================================================ FILE: web/src/views/map/dialog/drawThinProgress.vue ================================================ ================================================ FILE: web/src/views/map/index.vue ================================================ ================================================ FILE: web/src/views/map/queryTrace.vue ================================================ ================================================ FILE: web/src/views/mediaServer/index.vue ================================================ ================================================ FILE: web/src/views/operations/historyLog.vue ================================================ ================================================ FILE: web/src/views/operations/realLog.vue ================================================ ================================================ FILE: web/src/views/operations/showLog.vue ================================================ ================================================ FILE: web/src/views/operations/systemInfo.vue ================================================ ================================================ FILE: web/src/views/platform/edit.vue ================================================ ================================================ FILE: web/src/views/platform/index.vue ================================================ ================================================ FILE: web/src/views/recordPlan/index.vue ================================================ ================================================ FILE: web/src/views/streamProxy/edit.vue ================================================ ================================================ FILE: web/src/views/streamProxy/index.vue ================================================ ================================================ FILE: web/src/views/streamPush/buildPushStreamUrl.vue ================================================ ================================================ FILE: web/src/views/streamPush/edit.vue ================================================ ================================================ FILE: web/src/views/streamPush/index.vue ================================================ ================================================ FILE: web/src/views/user/apiKeyManager.vue ================================================ ================================================ FILE: web/src/views/user/index.vue ================================================ ================================================ FILE: web/tests/unit/.eslintrc.js ================================================ module.exports = { env: { jest: true } } ================================================ FILE: web/tests/unit/components/Breadcrumb.spec.js ================================================ import { mount, createLocalVue } from '@vue/test-utils' import VueRouter from 'vue-router' import ElementUI from 'element-ui' import Breadcrumb from '@/components/Breadcrumb/index.vue' const localVue = createLocalVue() localVue.use(VueRouter) localVue.use(ElementUI) const routes = [ { path: '/', name: 'home', children: [{ path: 'dashboard', name: 'dashboard' }] }, { path: '/menu', name: 'menu', children: [{ path: 'menu1', name: 'menu1', meta: { title: 'menu1' }, children: [{ path: 'menu1-1', name: 'menu1-1', meta: { title: 'menu1-1' } }, { path: 'menu1-2', name: 'menu1-2', redirect: 'noredirect', meta: { title: 'menu1-2' }, children: [{ path: 'menu1-2-1', name: 'menu1-2-1', meta: { title: 'menu1-2-1' } }, { path: 'menu1-2-2', name: 'menu1-2-2' }] }] }] }] const router = new VueRouter({ routes }) describe('Breadcrumb.vue', () => { const wrapper = mount(Breadcrumb, { localVue, router }) it('dashboard', () => { router.push('/dashboard') const len = wrapper.findAll('.el-breadcrumb__inner').length expect(len).toBe(1) }) it('normal route', () => { router.push('/menu/menu1') const len = wrapper.findAll('.el-breadcrumb__inner').length expect(len).toBe(2) }) it('nested route', () => { router.push('/menu/menu1/menu1-2/menu1-2-1') const len = wrapper.findAll('.el-breadcrumb__inner').length expect(len).toBe(4) }) it('no meta.title', () => { router.push('/menu/menu1/menu1-2/menu1-2-2') const len = wrapper.findAll('.el-breadcrumb__inner').length expect(len).toBe(3) }) // it('click link', () => { // router.push('/menu/menu1/menu1-2/menu1-2-2') // const breadcrumbArray = wrapper.findAll('.el-breadcrumb__inner') // const second = breadcrumbArray.at(1) // console.log(breadcrumbArray) // const href = second.find('a').attributes().href // expect(href).toBe('#/menu/menu1') // }) // it('noRedirect', () => { // router.push('/menu/menu1/menu1-2/menu1-2-1') // const breadcrumbArray = wrapper.findAll('.el-breadcrumb__inner') // const redirectBreadcrumb = breadcrumbArray.at(2) // expect(redirectBreadcrumb.contains('a')).toBe(false) // }) it('last breadcrumb', () => { router.push('/menu/menu1/menu1-2/menu1-2-1') const breadcrumbArray = wrapper.findAll('.el-breadcrumb__inner') const redirectBreadcrumb = breadcrumbArray.at(3) expect(redirectBreadcrumb.contains('a')).toBe(false) }) }) ================================================ FILE: web/tests/unit/components/Hamburger.spec.js ================================================ import { shallowMount } from '@vue/test-utils' import Hamburger from '@/components/Hamburger/index.vue' describe('Hamburger.vue', () => { it('toggle click', () => { const wrapper = shallowMount(Hamburger) const mockFn = jest.fn() wrapper.vm.$on('toggleClick', mockFn) wrapper.find('.hamburger').trigger('click') expect(mockFn).toBeCalled() }) it('prop isActive', () => { const wrapper = shallowMount(Hamburger) wrapper.setProps({ isActive: true }) expect(wrapper.contains('.is-active')).toBe(true) wrapper.setProps({ isActive: false }) expect(wrapper.contains('.is-active')).toBe(false) }) }) ================================================ FILE: web/tests/unit/components/SvgIcon.spec.js ================================================ import { shallowMount } from '@vue/test-utils' import SvgIcon from '@/components/SvgIcon/index.vue' describe('SvgIcon.vue', () => { it('iconClass', () => { const wrapper = shallowMount(SvgIcon, { propsData: { iconClass: 'test' } }) expect(wrapper.find('use').attributes().href).toBe('#icon-test') }) it('className', () => { const wrapper = shallowMount(SvgIcon, { propsData: { iconClass: 'test' } }) expect(wrapper.classes().length).toBe(1) wrapper.setProps({ className: 'test' }) expect(wrapper.classes().includes('test')).toBe(true) }) }) ================================================ FILE: web/tests/unit/utils/formatTime.spec.js ================================================ import { formatTime } from '@/utils/index.js' describe('Utils:formatTime', () => { const d = new Date('2018-07-13 17:54:01') // "2018-07-13 17:54:01" const retrofit = 5 * 1000 it('ten digits timestamp', () => { expect(formatTime((d / 1000).toFixed(0))).toBe('7月13日17时54分') }) it('test now', () => { expect(formatTime(+new Date() - 1)).toBe('刚刚') }) it('less two minute', () => { expect(formatTime(+new Date() - 60 * 2 * 1000 + retrofit)).toBe('2分钟前') }) it('less two hour', () => { expect(formatTime(+new Date() - 60 * 60 * 2 * 1000 + retrofit)).toBe('2小时前') }) it('less one day', () => { expect(formatTime(+new Date() - 60 * 60 * 24 * 1 * 1000)).toBe('1天前') }) it('more than one day', () => { expect(formatTime(d)).toBe('7月13日17时54分') }) it('format', () => { expect(formatTime(d, '{y}-{m}-{d} {h}:{i}')).toBe('2018-07-13 17:54') expect(formatTime(d, '{y}-{m}-{d}')).toBe('2018-07-13') expect(formatTime(d, '{y}/{m}/{d} {h}-{i}')).toBe('2018/07/13 17-54') }) }) ================================================ FILE: web/tests/unit/utils/param2Obj.spec.js ================================================ import { param2Obj } from '@/utils/index.js' describe('Utils:param2Obj', () => { const url = 'https://github.com/PanJiaChen/vue-element-admin?name=bill&age=29&sex=1&field=dGVzdA==&key=%E6%B5%8B%E8%AF%95' it('param2Obj test', () => { expect(param2Obj(url)).toEqual({ name: 'bill', age: '29', sex: '1', field: window.btoa('test'), key: '测试' }) }) }) ================================================ FILE: web/tests/unit/utils/parseTime.spec.js ================================================ import { parseTime } from '@/utils/index.js' describe('Utils:parseTime', () => { const d = new Date('2018-07-13 17:54:01') // "2018-07-13 17:54:01" it('timestamp', () => { expect(parseTime(d)).toBe('2018-07-13 17:54:01') }) it('timestamp string', () => { expect(parseTime((d + ''))).toBe('2018-07-13 17:54:01') }) it('ten digits timestamp', () => { expect(parseTime((d / 1000).toFixed(0))).toBe('2018-07-13 17:54:01') }) it('new Date', () => { expect(parseTime(new Date(d))).toBe('2018-07-13 17:54:01') }) it('format', () => { expect(parseTime(d, '{y}-{m}-{d} {h}:{i}')).toBe('2018-07-13 17:54') expect(parseTime(d, '{y}-{m}-{d}')).toBe('2018-07-13') expect(parseTime(d, '{y}/{m}/{d} {h}-{i}')).toBe('2018/07/13 17-54') }) it('get the day of the week', () => { expect(parseTime(d, '{a}')).toBe('五') // 星期五 }) it('get the day of the week', () => { expect(parseTime(+d + 1000 * 60 * 60 * 24 * 2, '{a}')).toBe('日') // 星期日 }) it('empty argument', () => { expect(parseTime()).toBeNull() }) it('null', () => { expect(parseTime(null)).toBeNull() }) }) ================================================ FILE: web/tests/unit/utils/validate.spec.js ================================================ import { validUsername, isExternal } from '@/utils/validate.js' describe('Utils:validate', () => { it('validUsername', () => { expect(validUsername('admin')).toBe(true) expect(validUsername('editor')).toBe(true) expect(validUsername('xxxx')).toBe(false) }) it('isExternal', () => { expect(isExternal('https://github.com/PanJiaChen/vue-element-admin')).toBe(true) expect(isExternal('http://github.com/PanJiaChen/vue-element-admin')).toBe(true) expect(isExternal('github.com/PanJiaChen/vue-element-admin')).toBe(false) expect(isExternal('/dashboard')).toBe(false) expect(isExternal('./dashboard')).toBe(false) expect(isExternal('dashboard')).toBe(false) }) }) ================================================ FILE: web/vue.config.js ================================================ const path = require("path") const defaultSettings = require("./src/settings.js") function resolve(dir) { return path.join(__dirname, dir) } const name = defaultSettings.title || "WVP视频平台" // page title // If your port is set to 80, // use administrator privileges to execute the command line. // For example, Mac: sudo npm run // You can change the port by the following methods: // port = 9528 npm run dev OR npm run dev --port = 9528 const port = process.env.port || process.env.npm_config_port || 9528 // dev port // All configuration item explanations can be find in https://cli.vuejs.org/config/ module.exports = { /** * You will need to set publicPath if you plan to deploy your site under a sub path, * for example GitHub Pages. If you plan to deploy your site to https://foo.github.io/bar/, * then publicPath should be set to "/bar/". * In most cases please use '/' !!! * Detail: https://cli.vuejs.org/config/#publicpath */ publicPath: "/", outputDir: "../src/main/resources/static", assetsDir: "static", lintOnSave: false, // Disable ESLint to avoid warnings productionSourceMap: false, transpileDependencies: ["ol"], devServer: { public: "localhost:" + port, host: "localhost", port: port, open: true, overlay: { warnings: false, errors: false, // Changed to false to hide ESLint errors }, // before: require('./mock/mock-server.js'), proxy: { "/dev-api": { target: "http://127.0.0.1:18080", changeOrigin: true, pathRewrite: { "^/dev-api": "/", }, }, "/static/snap": { target: "http://127.0.0.1:18080", changeOrigin: true, // pathRewrite: { // '^/static/snap': '/static/snap' // } }, }, }, configureWebpack: { // provide the app's title in webpack's name field, so that // it can be accessed in index.html to inject the correct title. name: name, resolve: { alias: { "@": resolve("src"), }, }, }, chainWebpack(config) { // it can improve the speed of the first screen, it is recommended to turn on preload config.plugin("preload").tap(() => [ { rel: "preload", // to ignore runtime.js // https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/cli-service/lib/config/app.js#L171 fileBlacklist: [/\.map$/, /hot-update\.js$/, /runtime\..*\.js$/], include: "initial", }, ]) // when there are many pages, it will cause too many meaningless requests config.plugins.delete("prefetch") // set svg-sprite-loader config.module.rule("svg").exclude.add(resolve("src/icons")).end() config.module .rule("icons") .test(/\.svg$/) .include.add(resolve("src/icons")) .end() .use("svg-sprite-loader") .loader("svg-sprite-loader") .options({ symbolId: "icon-[name]", }) .end() config.when(process.env.NODE_ENV !== "development", (config) => { config .plugin("ScriptExtHtmlWebpackPlugin") .after("html") .use("script-ext-html-webpack-plugin", [ { // `runtime` must same as runtimeChunk name. default is `runtime` inline: /runtime\..*\.js$/, }, ]) .end() config.optimization.splitChunks({ chunks: "all", cacheGroups: { libs: { name: "chunk-libs", test: /[\\/]node_modules[\\/]/, priority: 10, chunks: "initial", // only package third parties that are initially dependent }, elementUI: { name: "chunk-elementUI", // split elementUI into a single package priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app test: /[\\/]node_modules[\\/]_?element-ui(.*)/, // in order to adapt to cnpm }, commons: { name: "chunk-commons", test: resolve("src/components"), // can customize your rules minChunks: 3, // minimum common number priority: 5, reuseExistingChunk: true, }, }, }) // https:// webpack.js.org/configuration/optimization/#optimizationruntimechunk config.optimization.runtimeChunk("single") }) }, } ================================================ FILE: 打包/config/config.ini ================================================ ; auto-generated by mINI class { [api] apiDebug=1 defaultSnap=./www/logo.png downloadRoot=./www secret=034523TF8yT83wh5Wvz73f7 snapRoot=./www/snap/ [cluster] origin_url= retry_count=3 timeout_sec=15 [ffmpeg] bin=/usr/bin/ffmpeg cmd=%s -re -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s cmd2=%s -rtsp_transport tcp -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f rtsp %s log=./ffmpeg/ffmpeg.log restart_sec=0 snap=%s -rtsp_transport tcp -i %s -y -f mjpeg -frames:v 1 %s [general] check_nvidia_dev=1 enableVhost=0 enable_ffmpeg_log=0 flowThreshold=1024 maxStreamWaitMS=15000 mediaServerId=XwFtVZrtZbHJq4UV mergeWriteMS=0 resetWhenRePlay=1 streamNoneReaderDelayMS=20000 unready_frame_cache=100 wait_add_track_ms=3000 wait_track_ready_ms=10000 [hls] broadcastRecordTs=0 deleteDelaySec=10 fastRegister=0 fileBufSize=65536 segDelay=0 segDur=2 segKeep=0 segNum=3 segRetain=5 [hook] alive_interval=10.0 enable=1 on_flow_report= on_http_access= on_play=http://192.168.1.3:18082/index/hook/on_play on_publish=http://192.168.1.3:18082/index/hook/on_publish on_record_mp4=http://192.168.1.3:18082/index/hook/on_record_mp4 on_record_ts= on_rtp_server_timeout=http://192.168.1.3:18082/index/hook/on_rtp_server_timeout on_rtsp_auth= on_rtsp_realm= on_send_rtp_stopped=http://192.168.1.3:18082/index/hook/on_send_rtp_stopped on_server_exited= on_server_keepalive=http://192.168.1.3:18082/index/hook/on_server_keepalive on_server_started=http://192.168.1.3:18082/index/hook/on_server_started on_shell_login= on_stream_changed=http://192.168.1.3:18082/index/hook/on_stream_changed on_stream_none_reader=http://192.168.1.3:18082/index/hook/on_stream_none_reader on_stream_not_found=http://192.168.1.3:18082/index/hook/on_stream_not_found retry=1 retry_delay=3.000000 stream_changed_schemas=rtsp/rtmp/fmp4/ts/hls/hls.fmp4 timeoutSec=20 [http] allow_cross_domains=1 allow_ip_range= charSet=utf-8 dirMenu=1 forbidCacheSuffix= forwarded_ip_header= keepAliveSecond=15 maxReqSize=40960 notFound=404 Not Found

    您访问的资源不存在!


    ZLMediaKit(git hash:f69f3b3/2023-09-09T10:59:27+08:00,branch:master,build time:2023-09-11T15:03:57)
    port=7082 rootPath=./www sendBufSize=65536 sslport=11443 virtualPath= [multicast] addrMax=239.255.255.255 addrMin=239.0.0.0 udpTTL=64 [protocol] add_mute_audio=1 auto_close=0 continue_push_ms=3000 enable_audio=1 enable_fmp4=1 enable_hls=1 enable_hls_fmp4=0 enable_mp4=0 enable_rtmp=1 enable_rtsp=1 enable_ts=1 fmp4_demand=0 hls_demand=0 hls_save_path=./www modify_stamp=1 mp4_as_player=0 mp4_max_second=300 mp4_save_path=./www paced_sender_ms=0 rtmp_demand=0 rtsp_demand=0 ts_demand=0 [record] appName=record enableFmp4=0 fastStart=0 fileBufSize=65536 fileRepeat=0 sampleMS=500 [rtc] datachannel_echo=1 externIP=192.168.1.3 max_bitrate=0 min_bitrate=0 port=11340 preferredCodecA=PCMA,opus,mpeg4-generic preferredCodecV=H264,H265,AV1,VP9,VP8 rembBitRate=0 start_bitrate=0 tcpPort=11340 timeoutSec=15 [rtmp] directProxy=1 enhanced=0 handshakeSecond=15 keepAliveSecond=15 port=11935 sslport=18350 [rtp] audioMtuSize=600 h264_stap_a=1 lowLatency=0 rtpMaxSize=10 videoMtuSize=1400 [rtp_proxy] aac_pt=101 dumpDir=./dump gop_cache=1 h264_pt=98 h265_pt=99 opus_pt=100 port=11000 port_range=30000-40000 ps_pt=96 rtp_g711_dur_ms=100 timeoutSec=15 udp_recv_socket_buffer=4194304 [rtsp] authBasic=0 directProxy=1 handshakeSecond=15 keepAliveSecond=15 lowLatency=0 port=22554 rtpTransportType=-1 sslport=11332 [shell] maxReqSize=1024 port=9900 [srt] latencyMul=4 pktBufSize=8192 port=9900 timeoutSec=5 [transcode] acodec=mpeg4-generic decoder_h264=h264_qsv,h264_videotoolbox,h264_bm,libopenh264 decoder_h265=hevc_qsv,hevc_videotoolbox,hevc_bm enable_ffmpeg_log=0 encoder_h264=h264_qsv,h264_videotoolbox,h264_bm,libx264,libopenh264 encoder_h265=hevc_qsv,hevc_videotoolbox,hevc_bm,libx265 filter= suffix=transport vcodec=H264 ; } --- ================================================ FILE: 数据库/2.6.9/初始化-mysql-2.6.9.sql ================================================ /*建表*/ create table wvp_device ( id serial primary key , device_id character varying(50) not null , name character varying(255), manufacturer character varying(255), model character varying(255), firmware character varying(255), transport character varying(50), stream_mode character varying(50), on_line bool default false, register_time character varying(50), keepalive_time character varying(50), ip character varying(50), create_time character varying(50), update_time character varying(50), port integer, expires integer, subscribe_cycle_for_catalog integer DEFAULT 0, subscribe_cycle_for_mobile_position integer DEFAULT 0, mobile_position_submission_interval integer DEFAULT 5, subscribe_cycle_for_alarm integer DEFAULT 0, host_address character varying(50), charset character varying(50), ssrc_check bool default false, geo_coord_sys character varying(50), media_server_id character varying(50), custom_name character varying(255), sdp_ip character varying(50), local_ip character varying(50), password character varying(255), as_message_channel bool default false, keepalive_interval_time integer, switch_primary_sub_stream bool default false, broadcast_push_after_ack bool default false, constraint uk_device_device unique (device_id) ); create table wvp_device_alarm ( id serial primary key , device_id character varying(50) not null, channel_id character varying(50) not null, alarm_priority character varying(50), alarm_method character varying(50), alarm_time character varying(50), alarm_description character varying(255), longitude double precision, latitude double precision, alarm_type character varying(50), create_time character varying(50) not null ); create table wvp_device_channel ( id serial primary key , channel_id character varying(50) not null, name character varying(255), custom_name character varying(255), manufacture character varying(50), model character varying(50), owner character varying(50), civil_code character varying(50), block character varying(50), address character varying(50), parent_id character varying(50), safety_way integer, register_way integer, cert_num character varying(50), certifiable integer, err_code integer, end_time character varying(50), secrecy character varying(50), ip_address character varying(50), port integer, password character varying(255), ptz_type integer, custom_ptz_type integer, status bool default false, longitude double precision, custom_longitude double precision, latitude double precision, custom_latitude double precision, stream_id character varying(255), device_id character varying(50) not null, parental character varying(50), has_audio bool default false, create_time character varying(50) not null, update_time character varying(50) not null, sub_count integer, longitude_gcj02 double precision, latitude_gcj02 double precision, longitude_wgs84 double precision, latitude_wgs84 double precision, business_group_id character varying(50), gps_time character varying(50), constraint uk_wvp_device_channel_unique_device_channel unique (device_id, channel_id) ); create table wvp_device_mobile_position ( id serial primary key, device_id character varying(50) not null, channel_id character varying(50) not null, device_name character varying(255), time character varying(50), longitude double precision, latitude double precision, altitude double precision, speed double precision, direction double precision, report_source character varying(50), longitude_gcj02 double precision, latitude_gcj02 double precision, longitude_wgs84 double precision, latitude_wgs84 double precision, create_time character varying(50) ); create table wvp_gb_stream ( gb_stream_id serial primary key, app character varying(255) not null, stream character varying(255) not null, gb_id character varying(50) not null, name character varying(255), longitude double precision, latitude double precision, stream_type character varying(50), media_server_id character varying(50), create_time character varying(50), constraint uk_gb_stream_unique_gb_id unique (gb_id), constraint uk_gb_stream_unique_app_stream unique (app, stream) ); create table wvp_log ( id serial primary key , name character varying(50), type character varying(50), uri character varying(200), address character varying(50), result character varying(50), timing bigint, username character varying(50), create_time character varying(50) ); create table wvp_media_server ( id character varying(255) primary key , ip character varying(50), hook_ip character varying(50), sdp_ip character varying(50), stream_ip character varying(50), http_port integer, http_ssl_port integer, rtmp_port integer, rtmp_ssl_port integer, rtp_proxy_port integer, rtsp_port integer, rtsp_ssl_port integer, auto_config bool default false, secret character varying(50), rtp_enable bool default false, rtp_port_range character varying(50), send_rtp_port_range character varying(50), record_assist_port integer, default_server bool default false, create_time character varying(50), update_time character varying(50), hook_alive_interval integer, record_path character varying(255), record_day integer default 7, constraint uk_media_server_unique_ip_http_port unique (ip, http_port) ); create table wvp_platform ( id serial primary key , enable bool default false, name character varying(255), server_gb_id character varying(50), server_gb_domain character varying(50), server_ip character varying(50), server_port integer, device_gb_id character varying(50), device_ip character varying(50), device_port character varying(50), username character varying(255), password character varying(50), expires character varying(50), keep_timeout character varying(50), transport character varying(50), character_set character varying(50), catalog_id character varying(50), ptz bool default false, rtcp bool default false, status bool default false, start_offline_push bool default false, administrative_division character varying(50), catalog_group integer, create_time character varying(50), update_time character varying(50), as_message_channel bool default false, auto_push_channel bool default false, constraint uk_platform_unique_server_gb_id unique (server_gb_id) ); create table wvp_platform_catalog ( id character varying(50), platform_id character varying(50), name character varying(255), parent_id character varying(50), civil_code character varying(50), business_group_id character varying(50), constraint uk_platform_catalog_id_platform_id unique (id, platform_id) ); create table wvp_platform_gb_channel ( id serial primary key , platform_id character varying(50), catalog_id character varying(50), device_channel_id integer, constraint uk_platform_gb_channel_platform_id_catalog_id_device_channel_id unique (platform_id, catalog_id, device_channel_id) ); create table wvp_platform_gb_stream ( id serial primary key, platform_id character varying(50), catalog_id character varying(50), gb_stream_id integer, constraint uk_platform_gb_stream_platform_id_catalog_id_gb_stream_id unique (platform_id, catalog_id, gb_stream_id) ); create table wvp_stream_proxy ( id serial primary key, type character varying(50), app character varying(255), stream character varying(255), url character varying(255), src_url character varying(255), dst_url character varying(255), timeout_ms integer, ffmpeg_cmd_key character varying(255), rtp_type character varying(50), media_server_id character varying(50), enable_audio bool default false, enable_mp4 bool default false, enable bool default false, status boolean, enable_remove_none_reader bool default false, create_time character varying(50), name character varying(255), update_time character varying(50), stream_key character varying(255), enable_disable_none_reader bool default false, constraint uk_stream_proxy_app_stream unique (app, stream) ); create table wvp_stream_push ( id serial primary key, app character varying(255), stream character varying(255), total_reader_count character varying(50), origin_type integer, origin_type_str character varying(50), create_time character varying(50), alive_second integer, media_server_id character varying(50), server_id character varying(50), push_time character varying(50), status bool default false, update_time character varying(50), push_ing bool default false, self bool default false, constraint uk_stream_push_app_stream unique (app, stream) ); create table wvp_cloud_record ( id serial primary key, app character varying(255), stream character varying(255), call_id character varying(255), start_time bigint, end_time bigint, media_server_id character varying(50), file_name character varying(255), folder character varying(255), file_path character varying(255), collect bool default false, file_size bigint, time_len bigint, constraint uk_stream_push_app_stream_path unique (app, stream, file_path) ); create table wvp_user ( id serial primary key, username character varying(255), password character varying(255), role_id integer, create_time character varying(50), update_time character varying(50), push_key character varying(50), constraint uk_user_username unique (username) ); create table wvp_user_role ( id serial primary key, name character varying(50), authority character varying(50), create_time character varying(50), update_time character varying(50) ); create table wvp_resources_tree ( id serial primary key , is_catalog bool default true, device_channel_id integer , gb_stream_id integer, name character varying(255), parentId integer, path character varying(255) ); /*初始数据*/ INSERT INTO wvp_user VALUES (1, 'admin','21232f297a57a5a743894a0e4a801fc3',1,'2021-04-13 14:14:57','2021-04-13 14:14:57','3e80d1762a324d5b0ff636e0bd16f1e3'); INSERT INTO wvp_user_role VALUES (1, 'admin','0','2021-04-13 14:14:57','2021-04-13 14:14:57'); ================================================ FILE: 数据库/2.6.9/初始化-postgresql-kingbase-2.6.9.sql ================================================ /*建表*/ create table wvp_device ( id serial primary key , device_id character varying(50) not null , name character varying(255), manufacturer character varying(255), model character varying(255), firmware character varying(255), transport character varying(50), stream_mode character varying(50), on_line bool default false, register_time character varying(50), keepalive_time character varying(50), ip character varying(50), create_time character varying(50), update_time character varying(50), port integer, expires integer, subscribe_cycle_for_catalog integer DEFAULT 0, subscribe_cycle_for_mobile_position integer DEFAULT 0, mobile_position_submission_interval integer DEFAULT 5, subscribe_cycle_for_alarm integer DEFAULT 0, host_address character varying(50), charset character varying(50), ssrc_check bool default false, geo_coord_sys character varying(50), media_server_id character varying(50), custom_name character varying(255), sdp_ip character varying(50), local_ip character varying(50), password character varying(255), as_message_channel bool default false, keepalive_interval_time integer, switch_primary_sub_stream bool default false, broadcast_push_after_ack bool default false, constraint uk_device_device unique (device_id) ); create table wvp_device_alarm ( id serial primary key , device_id character varying(50) not null, channel_id character varying(50) not null, alarm_priority character varying(50), alarm_method character varying(50), alarm_time character varying(50), alarm_description character varying(255), longitude double precision, latitude double precision, alarm_type character varying(50), create_time character varying(50) not null ); create table wvp_device_channel ( id serial primary key , channel_id character varying(50) not null, name character varying(255), custom_name character varying(255), manufacture character varying(50), model character varying(50), owner character varying(50), civil_code character varying(50), block character varying(50), address character varying(50), parent_id character varying(50), safety_way integer, register_way integer, cert_num character varying(50), certifiable integer, err_code integer, end_time character varying(50), secrecy character varying(50), ip_address character varying(50), port integer, password character varying(255), ptz_type integer, custom_ptz_type integer, status bool default false, longitude double precision, custom_longitude double precision, latitude double precision, custom_latitude double precision, stream_id character varying(255), device_id character varying(50) not null, parental character varying(50), has_audio bool default false, create_time character varying(50) not null, update_time character varying(50) not null, sub_count integer, longitude_gcj02 double precision, latitude_gcj02 double precision, longitude_wgs84 double precision, latitude_wgs84 double precision, business_group_id character varying(50), gps_time character varying(50), constraint uk_wvp_device_channel_unique_device_channel unique (device_id, channel_id) ); create table wvp_device_mobile_position ( id serial primary key, device_id character varying(50) not null, channel_id character varying(50) not null, device_name character varying(255), time character varying(50), longitude double precision, latitude double precision, altitude double precision, speed double precision, direction double precision, report_source character varying(50), longitude_gcj02 double precision, latitude_gcj02 double precision, longitude_wgs84 double precision, latitude_wgs84 double precision, create_time character varying(50) ); create table wvp_gb_stream ( gb_stream_id serial primary key, app character varying(255) not null, stream character varying(255) not null, gb_id character varying(50) not null, name character varying(255), longitude double precision, latitude double precision, stream_type character varying(50), media_server_id character varying(50), create_time character varying(50), constraint uk_gb_stream_unique_gb_id unique (gb_id), constraint uk_gb_stream_unique_app_stream unique (app, stream) ); create table wvp_log ( id serial primary key , name character varying(50), type character varying(50), uri character varying(200), address character varying(50), result character varying(50), timing bigint, username character varying(50), create_time character varying(50) ); create table wvp_media_server ( id character varying(255) primary key , ip character varying(50), hook_ip character varying(50), sdp_ip character varying(50), stream_ip character varying(50), http_port integer, http_ssl_port integer, rtmp_port integer, rtmp_ssl_port integer, rtp_proxy_port integer, rtsp_port integer, rtsp_ssl_port integer, auto_config bool default false, secret character varying(50), rtp_enable bool default false, rtp_port_range character varying(50), send_rtp_port_range character varying(50), record_assist_port integer, default_server bool default false, create_time character varying(50), update_time character varying(50), hook_alive_interval integer, record_path character varying(255), record_day integer default 7, constraint uk_media_server_unique_ip_http_port unique (ip, http_port) ); create table wvp_platform ( id serial primary key , enable bool default false, name character varying(255), server_gb_id character varying(50), server_gb_domain character varying(50), server_ip character varying(50), server_port integer, device_gb_id character varying(50), device_ip character varying(50), device_port character varying(50), username character varying(255), password character varying(50), expires character varying(50), keep_timeout character varying(50), transport character varying(50), character_set character varying(50), catalog_id character varying(50), ptz bool default false, rtcp bool default false, status bool default false, start_offline_push bool default false, administrative_division character varying(50), catalog_group integer, create_time character varying(50), update_time character varying(50), as_message_channel bool default false, auto_push_channel bool default false, constraint uk_platform_unique_server_gb_id unique (server_gb_id) ); create table wvp_platform_catalog ( id character varying(50), platform_id character varying(50), name character varying(255), parent_id character varying(50), civil_code character varying(50), business_group_id character varying(50), constraint uk_platform_catalog_id_platform_id unique (id, platform_id) ); create table wvp_platform_gb_channel ( id serial primary key , platform_id character varying(50), catalog_id character varying(50), device_channel_id integer, constraint uk_platform_gb_channel_platform_id_catalog_id_device_channel_id unique (platform_id, catalog_id, device_channel_id) ); create table wvp_platform_gb_stream ( id serial primary key, platform_id character varying(50), catalog_id character varying(50), gb_stream_id integer, constraint uk_platform_gb_stream_platform_id_catalog_id_gb_stream_id unique (platform_id, catalog_id, gb_stream_id) ); create table wvp_stream_proxy ( id serial primary key, type character varying(50), app character varying(255), stream character varying(255), url character varying(255), src_url character varying(255), dst_url character varying(255), timeout_ms integer, ffmpeg_cmd_key character varying(255), rtp_type character varying(50), media_server_id character varying(50), enable_audio bool default false, enable_mp4 bool default false, enable bool default false, status boolean, enable_remove_none_reader bool default false, create_time character varying(50), name character varying(255), update_time character varying(50), stream_key character varying(255), enable_disable_none_reader bool default false, constraint uk_stream_proxy_app_stream unique (app, stream) ); create table wvp_stream_push ( id serial primary key, app character varying(255), stream character varying(255), total_reader_count character varying(50), origin_type integer, origin_type_str character varying(50), create_time character varying(50), alive_second integer, media_server_id character varying(50), server_id character varying(50), push_time character varying(50), status bool default false, update_time character varying(50), push_ing bool default false, self bool default false, constraint uk_stream_push_app_stream unique (app, stream) ); create table wvp_cloud_record ( id serial primary key, app character varying(255), stream character varying(255), call_id character varying(255), start_time int8, end_time int8, media_server_id character varying(50), file_name character varying(255), folder character varying(255), file_path character varying(255), collect bool default false, file_size int8, time_len int8, constraint uk_stream_push_app_stream_path unique (app, stream, file_path) ); create table wvp_user ( id serial primary key, username character varying(255), password character varying(255), role_id integer, create_time character varying(50), update_time character varying(50), push_key character varying(50), constraint uk_user_username unique (username) ); create table wvp_user_role ( id serial primary key, name character varying(50), authority character varying(50), create_time character varying(50), update_time character varying(50) ); create table wvp_resources_tree ( id serial primary key , is_catalog bool default true, device_channel_id integer , gb_stream_id integer, name character varying(255), parentId integer, path character varying(255) ); /*初始数据*/ INSERT INTO wvp_user VALUES (1, 'admin','21232f297a57a5a743894a0e4a801fc3',1,'2021-04-13 14:14:57','2021-04-13 14:14:57','3e80d1762a324d5b0ff636e0bd16f1e3'); INSERT INTO wvp_user_role VALUES (1, 'admin','0','2021-04-13 14:14:57','2021-04-13 14:14:57'); ================================================ FILE: 数据库/2.6.9/更新-mysql-2.6.9.sql ================================================ alter table device change deviceId device_id varchar(50) not null; alter table device change streamMode stream_mode varchar(50) null; alter table device change registerTime register_time varchar(50) null; alter table device change keepaliveTime keepalive_time varchar(50) null; alter table device change createTime create_time varchar(50) not null; alter table device change updateTime update_time varchar(50) not null; alter table device change subscribeCycleForCatalog subscribe_cycle_for_catalog bool default false; alter table device change subscribeCycleForMobilePosition subscribe_cycle_for_mobile_position bool default false; alter table device change mobilePositionSubmissionInterval mobile_position_submission_interval int default 5 not null; alter table device change subscribeCycleForAlarm subscribe_cycle_for_alarm bool default false; alter table device change hostAddress host_address varchar(50) null; alter table device change ssrcCheck ssrc_check bool default false; alter table device change geoCoordSys geo_coord_sys varchar(50) not null; alter table device drop column treeType; alter table device change mediaServerId media_server_id varchar(50) default 'auto' null; alter table device change sdpIp sdp_ip varchar(50) null; alter table device change localIp local_ip varchar(50) null; alter table device change asMessageChannel as_message_channel bool default false; alter table device change keepaliveIntervalTime keepalive_interval_time int null; alter table device change online on_line varchar(50) null; alter table device add COLUMN switch_primary_sub_stream bool default false comment '开启主子码流切换的开关(0-不开启,1-开启)现在已知支持设备为 大华、TP——LINK全系设备'; alter table device_alarm change deviceId device_id varchar(50) not null; alter table device_alarm change channelId channel_id varchar(50) not null; alter table device_alarm change alarmPriority alarm_priority varchar(50) not null; alter table device_alarm change alarmMethod alarm_method varchar(50) null; alter table device_alarm change alarmTime alarm_time varchar(50) not null; alter table device_alarm change alarmDescription alarm_description varchar(255) null; alter table device_alarm change alarmType alarm_type varchar(50) null; alter table device_alarm change createTime create_time varchar(50) null; alter table device_channel change channelId channel_id varchar(50) not null; alter table device_channel change civilCode civil_code varchar(50) null; alter table device_channel change parentId parent_id varchar(50) null; alter table device_channel change safetyWay safety_way int null; alter table device_channel change registerWay register_way int null; alter table device_channel change certNum cert_num varchar(50) null; alter table device_channel change errCode err_code int null; alter table device_channel change endTime end_time varchar(50) null; alter table device_channel change ipAddress ip_address varchar(50) null; alter table device_channel change PTZType ptz_type int null; alter table device_channel change status status bool default false; alter table device_channel change streamId stream_id varchar(255) null; alter table device_channel change deviceId device_id varchar(50) not null; alter table device_channel change hasAudio has_audio bool default false; alter table device_channel change createTime create_time varchar(50) not null; alter table device_channel change updateTime update_time varchar(50) not null; alter table device_channel change subCount sub_count int default 0 null; alter table device_channel change longitudeGcj02 longitude_gcj02 double null; alter table device_channel change latitudeGcj02 latitude_gcj02 double null; alter table device_channel change longitudeWgs84 longitude_wgs84 double null; alter table device_channel change latitudeWgs84 latitude_wgs84 double null; alter table device_channel change businessGroupId business_group_id varchar(50) null; alter table device_channel change gpsTime gps_time varchar(50) null; alter table device_mobile_position change deviceId device_id varchar(50) not null; alter table device_mobile_position change channelId channel_id varchar(50) not null; alter table device_mobile_position change deviceName device_name varchar(255) null; alter table device_mobile_position change reportSource report_source varchar(50) null; alter table device_mobile_position change longitudeGcj02 longitude_gcj02 double null; alter table device_mobile_position change latitudeGcj02 latitude_gcj02 double null; alter table device_mobile_position change longitudeWgs84 longitude_wgs84 double null; alter table device_mobile_position change latitudeWgs84 latitude_wgs84 double null; alter table device_mobile_position change createTime create_time varchar(50) null; alter table gb_stream change gbStreamId gb_stream_id int auto_increment; alter table gb_stream change gbId gb_id varchar(50) not null; alter table gb_stream change streamType stream_type varchar(50) null; alter table gb_stream change mediaServerId media_server_id varchar(50) null; alter table gb_stream change createTime create_time varchar(50) null; alter table log change createTime create_time varchar(50) not null; alter table media_server change hookIp hook_ip varchar(50) not null; alter table media_server add column send_rtp_port_range varchar(50) default null; alter table media_server change sdpIp sdp_ip varchar(50) not null; alter table media_server change streamIp stream_ip varchar(50) not null; alter table media_server change httpPort http_port int not null; alter table media_server change httpSSlPort http_ssl_port int not null; alter table media_server change rtmpPort rtmp_port int not null; alter table media_server change rtmpSSlPort rtmp_ssl_port int not null; alter table media_server change rtpProxyPort rtp_proxy_port int not null; alter table media_server change rtspPort rtsp_port int not null; alter table media_server change rtspSSLPort rtsp_ssl_port int not null; alter table media_server change autoConfig auto_config bool default true; alter table media_server change rtpEnable rtp_enable bool default false; alter table media_server change rtpPortRange rtp_port_range varchar(50) not null; alter table media_server change recordAssistPort record_assist_port int not null; alter table media_server change defaultServer default_server bool default false; alter table media_server change createTime create_time varchar(50) not null; alter table media_server change updateTime update_time varchar(50) not null; alter table media_server change hookAliveInterval hook_alive_interval int not null; alter table parent_platform change serverGBId server_gb_id varchar(50) not null; alter table parent_platform change serverGBDomain server_gb_domain varchar(50) null; alter table parent_platform change serverIP server_ip varchar(50) null; alter table parent_platform change serverPort server_port int null; alter table parent_platform change deviceGBId device_gb_id varchar(50) not null; alter table parent_platform change deviceIp device_ip varchar(50) null; alter table parent_platform change devicePort device_port varchar(50) null; alter table parent_platform change keepTimeout keep_timeout varchar(50) null; alter table parent_platform change characterSet character_set varchar(50) null; alter table parent_platform change catalogId catalog_id varchar(50) not null; alter table parent_platform change startOfflinePush start_offline_push bool default false; alter table parent_platform change administrativeDivision administrative_division varchar(50) not null; alter table parent_platform change catalogGroup catalog_group int default 1 null; alter table parent_platform change createTime create_time varchar(50) null; alter table parent_platform change updateTime update_time varchar(50) null; alter table parent_platform drop column treeType; alter table parent_platform change asMessageChannel as_message_channel bool default false; alter table parent_platform change enable enable bool default false; alter table parent_platform change ptz ptz bool default false; alter table parent_platform change rtcp rtcp bool default false; alter table parent_platform change status status bool default false; alter table parent_platform change status status bool default false; alter table platform_catalog change platformId platform_id varchar(50) not null; alter table platform_catalog change parentId parent_id varchar(50) null; alter table platform_catalog change civilCode civil_code varchar(50) null; alter table platform_catalog change businessGroupId business_group_id varchar(50) null; alter table platform_gb_channel change platformId platform_id varchar(50) not null; alter table platform_gb_channel change catalogId catalog_id varchar(50) not null; alter table platform_gb_channel change deviceChannelId device_channel_id int not null; alter table platform_gb_stream change platformId platform_id varchar(50) not null; alter table platform_gb_stream change catalogId catalog_id varchar(50) not null; alter table platform_gb_stream change gbStreamId gb_stream_id int not null; alter table stream_proxy change mediaServerId media_server_id varchar(50) null; alter table stream_proxy change createTime create_time varchar(50) not null; alter table stream_proxy change updateTime update_time varchar(50) null; alter table stream_proxy change enable_remove_none_reader enable_remove_none_reader bool default false; alter table stream_proxy change enable_disable_none_reader enable_disable_none_reader bool default false; alter table stream_proxy change enable_audio enable_audio bool default false; alter table stream_proxy change enable_mp4 enable_mp4 bool default false; alter table stream_proxy change enable enable bool default false; alter table stream_push change totalReaderCount total_reader_count varchar(50) null; alter table stream_push change originType origin_type int null; alter table stream_push change originTypeStr origin_type_str varchar(50) null; alter table stream_push change createTime create_time varchar(50) null; alter table stream_push change aliveSecond alive_second int null; alter table stream_push change mediaServerId media_server_id varchar(50) null; alter table stream_push change status status bool default false; alter table stream_push change pushTime push_time varchar(50) null; alter table stream_push change updateTime update_time varchar(50) null; alter table stream_push change pushIng push_ing bool default false; alter table stream_push change status status bool default false; alter table stream_push change self self bool default false; alter table stream_push drop column serverId; alter table user change roleId role_id int not null; alter table user change createTime create_time varchar(50) not null; alter table user change updateTime update_time varchar(50) not null; alter table user change pushKey push_key varchar(50) null; alter table user_role change createTime create_time varchar(50) not null; alter table user_role change updateTime update_time varchar(50) not null; rename table device to wvp_device; rename table device_alarm to wvp_device_alarm; rename table device_channel to wvp_device_channel; rename table device_mobile_position to wvp_device_mobile_position; rename table gb_stream to wvp_gb_stream; rename table log to wvp_log; rename table media_server to wvp_media_server; rename table parent_platform to wvp_platform; rename table platform_catalog to wvp_platform_catalog; rename table platform_gb_channel to wvp_platform_gb_channel; rename table platform_gb_stream to wvp_platform_gb_stream; rename table stream_proxy to wvp_stream_proxy; rename table stream_push to wvp_stream_push; rename table user to wvp_user; rename table user_role to wvp_user_role; alter table wvp_device add column broadcast_push_after_ack bool default false; alter table wvp_device_channel add column custom_name varchar(255) null ; alter table wvp_device_channel add column custom_longitude double null ; alter table wvp_device_channel add column custom_latitude double null ; alter table wvp_device_channel add column custom_ptz_type int null ; create table wvp_resources_tree ( id serial primary key , is_catalog bool default true, device_channel_id integer , gb_stream_id integer, name character varying(255), parentId integer, path character varying(255) ); alter table wvp_platform add auto_push_channel bool default false; alter table wvp_stream_proxy add stream_key character varying(255); create table wvp_cloud_record ( id serial primary key, app character varying(255), stream character varying(255), call_id character varying(255), start_time bigint, end_time bigint, media_server_id character varying(50), file_name character varying(255), folder character varying(255), file_path character varying(255), collect bool default false, file_size bigint, time_len bigint, constraint uk_stream_push_app_stream_path unique (app, stream, file_path) ); alter table wvp_media_server add record_path character varying(255); alter table wvp_media_server add record_day integer default 7; alter table wvp_stream_push add server_id character varying(50); ================================================ FILE: 数据库/2.6.9/更新-postgresql-kingbase-2.6.9.sql ================================================ alter table device change deviceId device_id varchar(50) not null; alter table device change streamMode stream_mode varchar(50) null; alter table device change registerTime register_time varchar(50) null; alter table device change keepaliveTime keepalive_time varchar(50) null; alter table device change createTime create_time varchar(50) not null; alter table device change updateTime update_time varchar(50) not null; alter table device change subscribeCycleForCatalog subscribe_cycle_for_catalog bool default false; alter table device change subscribeCycleForMobilePosition subscribe_cycle_for_mobile_position bool default false; alter table device change mobilePositionSubmissionInterval mobile_position_submission_interval int default 5 not null; alter table device change subscribeCycleForAlarm subscribe_cycle_for_alarm bool default false; alter table device change hostAddress host_address varchar(50) null; alter table device change ssrcCheck ssrc_check bool default false; alter table device change geoCoordSys geo_coord_sys varchar(50) not null; alter table device drop column treeType; alter table device change mediaServerId media_server_id varchar(50) default 'auto' null; alter table device change sdpIp sdp_ip varchar(50) null; alter table device change localIp local_ip varchar(50) null; alter table device change asMessageChannel as_message_channel bool default false; alter table device change keepaliveIntervalTime keepalive_interval_time int null; alter table device change online on_line varchar(50) null; alter table device add COLUMN switch_primary_sub_stream bool default false comment '开启主子码流切换的开关(0-不开启,1-开启)现在已知支持设备为 大华、TP——LINK全系设备' alter table device_alarm change deviceId device_id varchar(50) not null; alter table device_alarm change channelId channel_id varchar(50) not null; alter table device_alarm change alarmPriority alarm_priority varchar(50) not null; alter table device_alarm change alarmMethod alarm_method varchar(50) null; alter table device_alarm change alarmTime alarm_time varchar(50) not null; alter table device_alarm change alarmDescription alarm_description varchar(255) null; alter table device_alarm change alarmType alarm_type varchar(50) null; alter table device_alarm change createTime create_time varchar(50) null; alter table device_channel change channelId channel_id varchar(50) not null; alter table device_channel change civilCode civil_code varchar(50) null; alter table device_channel change parentId parent_id varchar(50) null; alter table device_channel change safetyWay safety_way int null; alter table device_channel change registerWay register_way int null; alter table device_channel change certNum cert_num varchar(50) null; alter table device_channel change errCode err_code int null; alter table device_channel change endTime end_time varchar(50) null; alter table device_channel change ipAddress ip_address varchar(50) null; alter table device_channel change PTZType ptz_type int null; alter table device_channel change status status bool default false; alter table device_channel change streamId stream_id varchar(255) null; alter table device_channel change deviceId device_id varchar(50) not null; alter table device_channel change hasAudio has_audio bool default false; alter table device_channel change createTime create_time varchar(50) not null; alter table device_channel change updateTime update_time varchar(50) not null; alter table device_channel change subCount sub_count int default 0 null; alter table device_channel change longitudeGcj02 longitude_gcj02 double null; alter table device_channel change latitudeGcj02 latitude_gcj02 double null; alter table device_channel change longitudeWgs84 longitude_wgs84 double null; alter table device_channel change latitudeWgs84 latitude_wgs84 double null; alter table device_channel change businessGroupId business_group_id varchar(50) null; alter table device_channel change gpsTime gps_time varchar(50) null; alter table device_mobile_position change deviceId device_id varchar(50) not null; alter table device_mobile_position change channelId channel_id varchar(50) not null; alter table device_mobile_position change deviceName device_name varchar(255) null; alter table device_mobile_position change reportSource report_source varchar(50) null; alter table device_mobile_position change longitudeGcj02 longitude_gcj02 double null; alter table device_mobile_position change latitudeGcj02 latitude_gcj02 double null; alter table device_mobile_position change longitudeWgs84 longitude_wgs84 double null; alter table device_mobile_position change latitudeWgs84 latitude_wgs84 double null; alter table device_mobile_position change createTime create_time varchar(50) null; alter table gb_stream change gbStreamId gb_stream_id int auto_increment; alter table gb_stream change gbId gb_id varchar(50) not null; alter table gb_stream change streamType stream_type varchar(50) null; alter table gb_stream change mediaServerId media_server_id varchar(50) null; alter table gb_stream change createTime create_time varchar(50) null; alter table log change createTime create_time varchar(50) not null; alter table media_server change hookIp hook_ip varchar(50) not null; alter table media_server add column send_rtp_port_range varchar(50) default null; alter table media_server change sdpIp sdp_ip varchar(50) not null; alter table media_server change streamIp stream_ip varchar(50) not null; alter table media_server change httpPort http_port int not null; alter table media_server change httpSSlPort http_ssl_port int not null; alter table media_server change rtmpPort rtmp_port int not null; alter table media_server change rtmpSSlPort rtmp_ssl_port int not null; alter table media_server change rtpProxyPort rtp_proxy_port int not null; alter table media_server change rtspPort rtsp_port int not null; alter table media_server change rtspSSLPort rtsp_ssl_port int not null; alter table media_server change autoConfig auto_config bool default true; alter table media_server change rtpEnable rtp_enable bool default false; alter table media_server change rtpPortRange rtp_port_range varchar(50) not null; alter table media_server change recordAssistPort record_assist_port int not null; alter table media_server change defaultServer default_server bool default false; alter table media_server change createTime create_time varchar(50) not null; alter table media_server change updateTime update_time varchar(50) not null; alter table media_server change hookAliveInterval hook_alive_interval int not null; alter table parent_platform change serverGBId server_gb_id varchar(50) not null; alter table parent_platform change serverGBDomain server_gb_domain varchar(50) null; alter table parent_platform change serverIP server_ip varchar(50) null; alter table parent_platform change serverPort server_port int null; alter table parent_platform change deviceGBId device_gb_id varchar(50) not null; alter table parent_platform change deviceIp device_ip varchar(50) null; alter table parent_platform change devicePort device_port varchar(50) null; alter table parent_platform change keepTimeout keep_timeout varchar(50) null; alter table parent_platform change characterSet character_set varchar(50) null; alter table parent_platform change catalogId catalog_id varchar(50) not null; alter table parent_platform change startOfflinePush start_offline_push bool default false; alter table parent_platform change administrativeDivision administrative_division varchar(50) not null; alter table parent_platform change catalogGroup catalog_group int default 1 null; alter table parent_platform change createTime create_time varchar(50) null; alter table parent_platform change updateTime update_time varchar(50) null; alter table parent_platform drop column treeType; alter table parent_platform change asMessageChannel as_message_channel bool default false; alter table parent_platform change enable enable bool default false; alter table parent_platform change ptz ptz bool default false; alter table parent_platform change rtcp rtcp bool default false; alter table parent_platform change status status bool default false; alter table parent_platform change status status bool default false; alter table platform_catalog change platformId platform_id varchar(50) not null; alter table platform_catalog change parentId parent_id varchar(50) null; alter table platform_catalog change civilCode civil_code varchar(50) null; alter table platform_catalog change businessGroupId business_group_id varchar(50) null; alter table platform_gb_channel change platformId platform_id varchar(50) not null; alter table platform_gb_channel change catalogId catalog_id varchar(50) not null; alter table platform_gb_channel change deviceChannelId device_channel_id int not null; alter table platform_gb_stream change platformId platform_id varchar(50) not null; alter table platform_gb_stream change catalogId catalog_id varchar(50) not null; alter table platform_gb_stream change gbStreamId gb_stream_id int not null; alter table stream_proxy change mediaServerId media_server_id varchar(50) null; alter table stream_proxy change createTime create_time varchar(50) not null; alter table stream_proxy change updateTime update_time varchar(50) null; alter table stream_proxy change enable_remove_none_reader enable_remove_none_reader bool default false; alter table stream_proxy change enable_disable_none_reader enable_disable_none_reader bool default false; alter table stream_proxy change enable_audio enable_audio bool default false; alter table stream_proxy change enable_mp4 enable_mp4 bool default false; alter table stream_proxy change enable enable bool default false; alter table stream_push change totalReaderCount total_reader_count varchar(50) null; alter table stream_push change originType origin_type int null; alter table stream_push change originTypeStr origin_type_str varchar(50) null; alter table stream_push change createTime create_time varchar(50) null; alter table stream_push change aliveSecond alive_second int null; alter table stream_push change mediaServerId media_server_id varchar(50) null; alter table stream_push change status status bool default false; alter table stream_push change pushTime push_time varchar(50) null; alter table stream_push change updateTime update_time varchar(50) null; alter table stream_push change pushIng push_ing bool default false; alter table stream_push change status status bool default false; alter table stream_push change self self bool default false; alter table stream_push drop column serverId; alter table user change roleId role_id int not null; alter table user change createTime create_time varchar(50) not null; alter table user change updateTime update_time varchar(50) not null; alter table user change pushKey push_key varchar(50) null; alter table user_role change createTime create_time varchar(50) not null; alter table user_role change updateTime update_time varchar(50) not null; rename table device to wvp_device; rename table device_alarm to wvp_device_alarm; rename table device_channel to wvp_device_channel; rename table device_mobile_position to wvp_device_mobile_position; rename table gb_stream to wvp_gb_stream; rename table log to wvp_log; rename table media_server to wvp_media_server; rename table parent_platform to wvp_platform; rename table platform_catalog to wvp_platform_catalog; rename table platform_gb_channel to wvp_platform_gb_channel; rename table platform_gb_stream to wvp_platform_gb_stream; rename table stream_proxy to wvp_stream_proxy; rename table stream_push to wvp_stream_push; rename table user to wvp_user; rename table user_role to wvp_user_role; alter table wvp_device add column broadcast_push_after_ack bool default false; alter table wvp_device_channel add column custom_name varchar(255) null ; alter table wvp_device_channel add column custom_longitude double null ; alter table wvp_device_channel add column custom_latitude double null ; alter table wvp_device_channel add column custom_ptz_type int null ; create table wvp_resources_tree ( id serial primary key , is_catalog bool default true, device_channel_id integer , gb_stream_id integer, name character varying(255), parentId integer, path character varying(255) ); alter table wvp_platform add auto_push_channel bool default false; alter table wvp_stream_proxy add stream_key character varying(255); create table wvp_cloud_record ( id serial primary key, app character varying(255), stream character varying(255), call_id character varying(255), start_time int8, end_time int8, media_server_id character varying(50), file_name character varying(255), folder character varying(255), file_path character varying(255), collect bool default false, file_size int8, time_len int8, constraint uk_stream_push_app_stream_path unique (app, stream, file_path) ); alter table wvp_media_server add record_path character varying(255); alter table wvp_media_server add record_day integer default 7; alter table wvp_stream_push add server_id character varying(50); ================================================ FILE: 数据库/2.7.0/初始化-mysql-2.7.0.sql ================================================ /*建表*/ create table wvp_device ( id serial primary key , device_id character varying(50) not null , name character varying(255), manufacturer character varying(255), model character varying(255), firmware character varying(255), transport character varying(50), stream_mode character varying(50), on_line bool default false, register_time character varying(50), keepalive_time character varying(50), ip character varying(50), create_time character varying(50), update_time character varying(50), port integer, expires integer, subscribe_cycle_for_catalog integer DEFAULT 0, subscribe_cycle_for_mobile_position integer DEFAULT 0, mobile_position_submission_interval integer DEFAULT 5, subscribe_cycle_for_alarm integer DEFAULT 0, host_address character varying(50), charset character varying(50), ssrc_check bool default false, geo_coord_sys character varying(50), media_server_id character varying(50), custom_name character varying(255), sdp_ip character varying(50), local_ip character varying(50), password character varying(255), as_message_channel bool default false, keepalive_interval_time integer, broadcast_push_after_ack bool default false, constraint uk_device_device unique (device_id) ); create table wvp_device_alarm ( id serial primary key , device_id character varying(50) not null, channel_id character varying(50) not null, alarm_priority character varying(50), alarm_method character varying(50), alarm_time character varying(50), alarm_description character varying(255), longitude double precision, latitude double precision, alarm_type character varying(50), create_time character varying(50) not null ); create table wvp_device_channel ( id serial primary key , channel_id character varying(50) not null, name character varying(255), custom_name character varying(255), manufacture character varying(50), model character varying(50), owner character varying(50), civil_code character varying(50), block character varying(50), address character varying(50), parent_id character varying(50), safety_way integer, register_way integer, cert_num character varying(50), certifiable integer, err_code integer, end_time character varying(50), secrecy character varying(50), ip_address character varying(50), port integer, password character varying(255), ptz_type integer, custom_ptz_type integer, status bool default false, longitude double precision, custom_longitude double precision, latitude double precision, custom_latitude double precision, stream_id character varying(255), device_id character varying(50) not null, parental character varying(50), has_audio bool default false, create_time character varying(50) not null, update_time character varying(50) not null, sub_count integer, longitude_gcj02 double precision, latitude_gcj02 double precision, longitude_wgs84 double precision, latitude_wgs84 double precision, business_group_id character varying(50), gps_time character varying(50), stream_identification character varying(50), constraint uk_wvp_device_channel_unique_device_channel unique (device_id, channel_id) ); create table wvp_device_mobile_position ( id serial primary key, device_id character varying(50) not null, channel_id character varying(50) not null, device_name character varying(255), time character varying(50), longitude double precision, latitude double precision, altitude double precision, speed double precision, direction double precision, report_source character varying(50), longitude_gcj02 double precision, latitude_gcj02 double precision, longitude_wgs84 double precision, latitude_wgs84 double precision, create_time character varying(50) ); create table wvp_gb_stream ( gb_stream_id serial primary key, app character varying(255) not null, stream character varying(255) not null, gb_id character varying(50) not null, name character varying(255), longitude double precision, latitude double precision, stream_type character varying(50), media_server_id character varying(50), create_time character varying(50), constraint uk_gb_stream_unique_gb_id unique (gb_id), constraint uk_gb_stream_unique_app_stream unique (app, stream) ); create table wvp_log ( id serial primary key , name character varying(50), type character varying(50), uri character varying(200), address character varying(50), result character varying(50), timing bigint, username character varying(50), create_time character varying(50) ); create table wvp_media_server ( id character varying(255) primary key , ip character varying(50), hook_ip character varying(50), sdp_ip character varying(50), stream_ip character varying(50), http_port integer, http_ssl_port integer, rtmp_port integer, rtmp_ssl_port integer, rtp_proxy_port integer, rtsp_port integer, rtsp_ssl_port integer, auto_config bool default false, secret character varying(50), rtp_enable bool default false, rtp_port_range character varying(50), send_rtp_port_range character varying(50), record_assist_port integer, default_server bool default false, create_time character varying(50), update_time character varying(50), hook_alive_interval integer, record_path character varying(255), record_day integer default 7, constraint uk_media_server_unique_ip_http_port unique (ip, http_port) ); create table wvp_platform ( id serial primary key , enable bool default false, name character varying(255), server_gb_id character varying(50), server_gb_domain character varying(50), server_ip character varying(50), server_port integer, device_gb_id character varying(50), device_ip character varying(50), device_port character varying(50), username character varying(255), password character varying(50), expires character varying(50), keep_timeout character varying(50), transport character varying(50), character_set character varying(50), catalog_id character varying(50), ptz bool default false, rtcp bool default false, status bool default false, start_offline_push bool default false, administrative_division character varying(50), catalog_group integer, create_time character varying(50), update_time character varying(50), as_message_channel bool default false, auto_push_channel bool default false, send_stream_ip character varying(50), constraint uk_platform_unique_server_gb_id unique (server_gb_id) ); create table wvp_platform_catalog ( id character varying(50), platform_id character varying(50), name character varying(255), parent_id character varying(50), civil_code character varying(50), business_group_id character varying(50), constraint uk_platform_catalog_id_platform_id unique (id, platform_id) ); create table wvp_platform_gb_channel ( id serial primary key , platform_id character varying(50), catalog_id character varying(50), device_channel_id integer, constraint uk_platform_gb_channel_platform_id_catalog_id_device_channel_id unique (platform_id, catalog_id, device_channel_id) ); create table wvp_platform_gb_stream ( id serial primary key, platform_id character varying(50), catalog_id character varying(50), gb_stream_id integer, constraint uk_platform_gb_stream_platform_id_catalog_id_gb_stream_id unique (platform_id, catalog_id, gb_stream_id) ); create table wvp_stream_proxy ( id serial primary key, type character varying(50), app character varying(255), stream character varying(255), url character varying(255), src_url character varying(255), dst_url character varying(255), timeout_ms integer, ffmpeg_cmd_key character varying(255), rtp_type character varying(50), media_server_id character varying(50), enable_audio bool default false, enable_mp4 bool default false, enable bool default false, status boolean, enable_remove_none_reader bool default false, create_time character varying(50), name character varying(255), update_time character varying(50), stream_key character varying(255), enable_disable_none_reader bool default false, constraint uk_stream_proxy_app_stream unique (app, stream) ); create table wvp_stream_push ( id serial primary key, app character varying(255), stream character varying(255), total_reader_count character varying(50), origin_type integer, origin_type_str character varying(50), create_time character varying(50), alive_second integer, media_server_id character varying(50), server_id character varying(50), push_time character varying(50), status bool default false, update_time character varying(50), push_ing bool default false, self bool default false, constraint uk_stream_push_app_stream unique (app, stream) ); create table wvp_cloud_record ( id serial primary key, app character varying(255), stream character varying(255), call_id character varying(255), start_time bigint, end_time bigint, media_server_id character varying(50), file_name character varying(255), folder character varying(255), file_path character varying(255), collect bool default false, file_size bigint, time_len bigint, constraint uk_stream_push_app_stream_path unique (app, stream, file_path) ); create table wvp_user ( id serial primary key, username character varying(255), password character varying(255), role_id integer, create_time character varying(50), update_time character varying(50), push_key character varying(50), constraint uk_user_username unique (username) ); create table wvp_user_role ( id serial primary key, name character varying(50), authority character varying(50), create_time character varying(50), update_time character varying(50) ); create table wvp_resources_tree ( id serial primary key , is_catalog bool default true, device_channel_id integer , gb_stream_id integer, name character varying(255), parentId integer, path character varying(255) ); create table wvp_user_api_key ( id serial primary key , user_id bigint, app character varying(255) , api_key text, expired_at bigint, remark character varying(255), enable bool default true, create_time character varying(50), update_time character varying(50) ); /*初始数据*/ INSERT INTO wvp_user VALUES (1, 'admin','21232f297a57a5a743894a0e4a801fc3',1,'2021-04-13 14:14:57','2021-04-13 14:14:57','3e80d1762a324d5b0ff636e0bd16f1e3'); INSERT INTO wvp_user_role VALUES (1, 'admin','0','2021-04-13 14:14:57','2021-04-13 14:14:57'); ================================================ FILE: 数据库/2.7.0/初始化-postgresql-kingbase-2.7.0.sql ================================================ /*建表*/ create table wvp_device ( id serial primary key , device_id character varying(50) not null , name character varying(255), manufacturer character varying(255), model character varying(255), firmware character varying(255), transport character varying(50), stream_mode character varying(50), on_line bool default false, register_time character varying(50), keepalive_time character varying(50), ip character varying(50), create_time character varying(50), update_time character varying(50), port integer, expires integer, subscribe_cycle_for_catalog integer DEFAULT 0, subscribe_cycle_for_mobile_position integer DEFAULT 0, mobile_position_submission_interval integer DEFAULT 5, subscribe_cycle_for_alarm integer DEFAULT 0, host_address character varying(50), charset character varying(50), ssrc_check bool default false, geo_coord_sys character varying(50), media_server_id character varying(50), custom_name character varying(255), sdp_ip character varying(50), local_ip character varying(50), password character varying(255), as_message_channel bool default false, keepalive_interval_time integer, broadcast_push_after_ack bool default false, constraint uk_device_device unique (device_id) ); create table wvp_device_alarm ( id serial primary key , device_id character varying(50) not null, channel_id character varying(50) not null, alarm_priority character varying(50), alarm_method character varying(50), alarm_time character varying(50), alarm_description character varying(255), longitude double precision, latitude double precision, alarm_type character varying(50), create_time character varying(50) not null ); create table wvp_device_channel ( id serial primary key , channel_id character varying(50) not null, name character varying(255), custom_name character varying(255), manufacture character varying(50), model character varying(50), owner character varying(50), civil_code character varying(50), block character varying(50), address character varying(50), parent_id character varying(50), safety_way integer, register_way integer, cert_num character varying(50), certifiable integer, err_code integer, end_time character varying(50), secrecy character varying(50), ip_address character varying(50), port integer, password character varying(255), ptz_type integer, custom_ptz_type integer, status bool default false, longitude double precision, custom_longitude double precision, latitude double precision, custom_latitude double precision, stream_id character varying(255), device_id character varying(50) not null, parental character varying(50), has_audio bool default false, create_time character varying(50) not null, update_time character varying(50) not null, sub_count integer, longitude_gcj02 double precision, latitude_gcj02 double precision, longitude_wgs84 double precision, latitude_wgs84 double precision, business_group_id character varying(50), gps_time character varying(50), stream_identification character varying(50), constraint uk_wvp_device_channel_unique_device_channel unique (device_id, channel_id) ); create table wvp_device_mobile_position ( id serial primary key, device_id character varying(50) not null, channel_id character varying(50) not null, device_name character varying(255), time character varying(50), longitude double precision, latitude double precision, altitude double precision, speed double precision, direction double precision, report_source character varying(50), longitude_gcj02 double precision, latitude_gcj02 double precision, longitude_wgs84 double precision, latitude_wgs84 double precision, create_time character varying(50) ); create table wvp_gb_stream ( gb_stream_id serial primary key, app character varying(255) not null, stream character varying(255) not null, gb_id character varying(50) not null, name character varying(255), longitude double precision, latitude double precision, stream_type character varying(50), media_server_id character varying(50), create_time character varying(50), constraint uk_gb_stream_unique_gb_id unique (gb_id), constraint uk_gb_stream_unique_app_stream unique (app, stream) ); create table wvp_log ( id serial primary key , name character varying(50), type character varying(50), uri character varying(200), address character varying(50), result character varying(50), timing bigint, username character varying(50), create_time character varying(50) ); create table wvp_media_server ( id character varying(255) primary key , ip character varying(50), hook_ip character varying(50), sdp_ip character varying(50), stream_ip character varying(50), http_port integer, http_ssl_port integer, rtmp_port integer, rtmp_ssl_port integer, rtp_proxy_port integer, rtsp_port integer, rtsp_ssl_port integer, auto_config bool default false, secret character varying(50), rtp_enable bool default false, rtp_port_range character varying(50), send_rtp_port_range character varying(50), record_assist_port integer, default_server bool default false, create_time character varying(50), update_time character varying(50), hook_alive_interval integer, record_path character varying(255), record_day integer default 7, constraint uk_media_server_unique_ip_http_port unique (ip, http_port) ); create table wvp_platform ( id serial primary key , enable bool default false, name character varying(255), server_gb_id character varying(50), server_gb_domain character varying(50), server_ip character varying(50), server_port integer, device_gb_id character varying(50), device_ip character varying(50), device_port character varying(50), username character varying(255), password character varying(50), expires character varying(50), keep_timeout character varying(50), transport character varying(50), character_set character varying(50), catalog_id character varying(50), ptz bool default false, rtcp bool default false, status bool default false, start_offline_push bool default false, administrative_division character varying(50), catalog_group integer, create_time character varying(50), update_time character varying(50), as_message_channel bool default false, auto_push_channel bool default false, send_stream_ip character varying(50), constraint uk_platform_unique_server_gb_id unique (server_gb_id) ); create table wvp_platform_catalog ( id character varying(50), platform_id character varying(50), name character varying(255), parent_id character varying(50), civil_code character varying(50), business_group_id character varying(50), constraint uk_platform_catalog_id_platform_id unique (id, platform_id) ); create table wvp_platform_gb_channel ( id serial primary key , platform_id character varying(50), catalog_id character varying(50), device_channel_id integer, constraint uk_platform_gb_channel_platform_id_catalog_id_device_channel_id unique (platform_id, catalog_id, device_channel_id) ); create table wvp_platform_gb_stream ( id serial primary key, platform_id character varying(50), catalog_id character varying(50), gb_stream_id integer, constraint uk_platform_gb_stream_platform_id_catalog_id_gb_stream_id unique (platform_id, catalog_id, gb_stream_id) ); create table wvp_stream_proxy ( id serial primary key, type character varying(50), app character varying(255), stream character varying(255), url character varying(255), src_url character varying(255), dst_url character varying(255), timeout_ms integer, ffmpeg_cmd_key character varying(255), rtp_type character varying(50), media_server_id character varying(50), enable_audio bool default false, enable_mp4 bool default false, enable bool default false, status boolean, enable_remove_none_reader bool default false, create_time character varying(50), name character varying(255), update_time character varying(50), stream_key character varying(255), enable_disable_none_reader bool default false, constraint uk_stream_proxy_app_stream unique (app, stream) ); create table wvp_stream_push ( id serial primary key, app character varying(255), stream character varying(255), total_reader_count character varying(50), origin_type integer, origin_type_str character varying(50), create_time character varying(50), alive_second integer, media_server_id character varying(50), server_id character varying(50), push_time character varying(50), status bool default false, update_time character varying(50), push_ing bool default false, self bool default false, constraint uk_stream_push_app_stream unique (app, stream) ); create table wvp_cloud_record ( id serial primary key, app character varying(255), stream character varying(255), call_id character varying(255), start_time int8, end_time int8, media_server_id character varying(50), file_name character varying(255), folder character varying(255), file_path character varying(255), collect bool default false, file_size int8, time_len int8, constraint uk_stream_push_app_stream_path unique (app, stream, file_path) ); create table wvp_user ( id serial primary key, username character varying(255), password character varying(255), role_id integer, create_time character varying(50), update_time character varying(50), push_key character varying(50), constraint uk_user_username unique (username) ); create table wvp_user_role ( id serial primary key, name character varying(50), authority character varying(50), create_time character varying(50), update_time character varying(50) ); create table wvp_resources_tree ( id serial primary key , is_catalog bool default true, device_channel_id integer , gb_stream_id integer, name character varying(255), parentId integer, path character varying(255) ); create table wvp_user_api_key ( id serial primary key , user_id bigint, app character varying(255) , api_key text, expired_at bigint, remark character varying(255), enable bool default true, create_time character varying(50), update_time character varying(50) ); /*初始数据*/ INSERT INTO wvp_user VALUES (1, 'admin','21232f297a57a5a743894a0e4a801fc3',1,'2021-04-13 14:14:57','2021-04-13 14:14:57','3e80d1762a324d5b0ff636e0bd16f1e3'); INSERT INTO wvp_user_role VALUES (1, 'admin','0','2021-04-13 14:14:57','2021-04-13 14:14:57'); ================================================ FILE: 数据库/2.7.0/更新-mysql-2.7.0.sql ================================================ alter table wvp_device_channel add stream_identification character varying(50); alter table wvp_device drop switch_primary_sub_stream; # 第一个补丁包 alter table wvp_platform add send_stream_ip character varying(50); alter table wvp_device change on_line on_line bool default false; alter table wvp_device change id id serial primary key; alter table wvp_device change ssrc_check ssrc_check bool default false; ================================================ FILE: 数据库/2.7.0/更新-postgresql-kingbase-2.7.0.sql ================================================ alter table wvp_device_channel add stream_identification character varying(50); alter table wvp_device drop switch_primary_sub_stream; # 第一个补丁包 alter table wvp_platform add send_stream_ip character varying(50); alter table wvp_device change on_line on_line bool default false; alter table wvp_device change id id serial primary key; alter table wvp_device change ssrc_check ssrc_check bool default false; ================================================ FILE: 数据库/2.7.1/初始化-mysql-2.7.1.sql ================================================ /*建表*/ create table wvp_device ( id serial primary key , device_id character varying(50) not null , name character varying(255), manufacturer character varying(255), model character varying(255), firmware character varying(255), transport character varying(50), stream_mode character varying(50), on_line bool default false, register_time character varying(50), keepalive_time character varying(50), ip character varying(50), create_time character varying(50), update_time character varying(50), port integer, expires integer, subscribe_cycle_for_catalog integer DEFAULT 0, subscribe_cycle_for_mobile_position integer DEFAULT 0, mobile_position_submission_interval integer DEFAULT 5, subscribe_cycle_for_alarm integer DEFAULT 0, host_address character varying(50), charset character varying(50), ssrc_check bool default false, geo_coord_sys character varying(50), media_server_id character varying(50), custom_name character varying(255), sdp_ip character varying(50), local_ip character varying(50), password character varying(255), as_message_channel bool default false, keepalive_interval_time integer, broadcast_push_after_ack bool default false, constraint uk_device_device unique (device_id) ); create table wvp_device_alarm ( id serial primary key , device_id character varying(50) not null, channel_id character varying(50) not null, alarm_priority character varying(50), alarm_method character varying(50), alarm_time character varying(50), alarm_description character varying(255), longitude double precision, latitude double precision, alarm_type character varying(50), create_time character varying(50) not null ); create table wvp_device_channel ( id serial primary key , channel_id character varying(50) not null, name character varying(255), custom_name character varying(255), manufacture character varying(50), model character varying(50), owner character varying(50), civil_code character varying(50), block character varying(50), address character varying(50), parent_id character varying(50), safety_way integer, register_way integer, cert_num character varying(50), certifiable integer, err_code integer, end_time character varying(50), secrecy character varying(50), ip_address character varying(50), port integer, password character varying(255), ptz_type integer, custom_ptz_type integer, status bool default false, longitude double precision, custom_longitude double precision, latitude double precision, custom_latitude double precision, stream_id character varying(255), device_id character varying(50) not null, parental character varying(50), has_audio bool default false, create_time character varying(50) not null, update_time character varying(50) not null, sub_count integer, longitude_gcj02 double precision, latitude_gcj02 double precision, longitude_wgs84 double precision, latitude_wgs84 double precision, business_group_id character varying(50), gps_time character varying(50), stream_identification character varying(50), constraint uk_wvp_device_channel_unique_device_channel unique (device_id, channel_id) ); create table wvp_device_mobile_position ( id serial primary key, device_id character varying(50) not null, channel_id character varying(50) not null, device_name character varying(255), time character varying(50), longitude double precision, latitude double precision, altitude double precision, speed double precision, direction double precision, report_source character varying(50), longitude_gcj02 double precision, latitude_gcj02 double precision, longitude_wgs84 double precision, latitude_wgs84 double precision, create_time character varying(50) ); create table wvp_gb_stream ( gb_stream_id serial primary key, app character varying(255) not null, stream character varying(255) not null, gb_id character varying(50) not null, name character varying(255), longitude double precision, latitude double precision, stream_type character varying(50), media_server_id character varying(50), create_time character varying(50), constraint uk_gb_stream_unique_gb_id unique (gb_id), constraint uk_gb_stream_unique_app_stream unique (app, stream) ); create table wvp_log ( id serial primary key , name character varying(50), type character varying(50), uri character varying(200), address character varying(50), result character varying(50), timing bigint, username character varying(50), create_time character varying(50) ); create table wvp_media_server ( id character varying(255) primary key , ip character varying(50), hook_ip character varying(50), sdp_ip character varying(50), stream_ip character varying(50), http_port integer, http_ssl_port integer, rtmp_port integer, rtmp_ssl_port integer, rtp_proxy_port integer, rtsp_port integer, rtsp_ssl_port integer, flv_port integer, flv_ssl_port integer, ws_flv_port integer, ws_flv_ssl_port integer, auto_config bool default false, secret character varying(50), type character varying(50) default 'zlm', rtp_enable bool default false, rtp_port_range character varying(50), send_rtp_port_range character varying(50), record_assist_port integer, default_server bool default false, create_time character varying(50), update_time character varying(50), hook_alive_interval integer, record_path character varying(255), record_day integer default 7, transcode_suffix character varying(255), constraint uk_media_server_unique_ip_http_port unique (ip, http_port) ); create table wvp_platform ( id serial primary key , enable bool default false, name character varying(255), server_gb_id character varying(50), server_gb_domain character varying(50), server_ip character varying(50), server_port integer, device_gb_id character varying(50), device_ip character varying(50), device_port character varying(50), username character varying(255), password character varying(50), expires character varying(50), keep_timeout character varying(50), transport character varying(50), character_set character varying(50), catalog_id character varying(50), ptz bool default false, rtcp bool default false, status bool default false, start_offline_push bool default false, administrative_division character varying(50), catalog_group integer, create_time character varying(50), update_time character varying(50), as_message_channel bool default false, auto_push_channel bool default false, send_stream_ip character varying(50), constraint uk_platform_unique_server_gb_id unique (server_gb_id) ); create table wvp_platform_catalog ( id character varying(50), platform_id character varying(50), name character varying(255), parent_id character varying(50), civil_code character varying(50), business_group_id character varying(50), constraint uk_platform_catalog_id_platform_id unique (id, platform_id) ); create table wvp_platform_gb_channel ( id serial primary key , platform_id character varying(50), catalog_id character varying(50), device_channel_id integer, constraint uk_platform_gb_channel_platform_id_catalog_id_device_channel_id unique (platform_id, catalog_id, device_channel_id) ); create table wvp_platform_gb_stream ( id serial primary key, platform_id character varying(50), catalog_id character varying(50), gb_stream_id integer, constraint uk_platform_gb_stream_platform_id_catalog_id_gb_stream_id unique (platform_id, catalog_id, gb_stream_id) ); create table wvp_stream_proxy ( id serial primary key, type character varying(50), app character varying(255), stream character varying(255), url character varying(255), src_url character varying(255), dst_url character varying(255), timeout_ms integer, ffmpeg_cmd_key character varying(255), rtp_type character varying(50), media_server_id character varying(50), enable_audio bool default false, enable_mp4 bool default false, enable bool default false, status boolean, enable_remove_none_reader bool default false, create_time character varying(50), name character varying(255), update_time character varying(50), stream_key character varying(255), enable_disable_none_reader bool default false, constraint uk_stream_proxy_app_stream unique (app, stream) ); create table wvp_stream_push ( id serial primary key, app character varying(255), stream character varying(255), total_reader_count character varying(50), origin_type integer, origin_type_str character varying(50), create_time character varying(50), alive_second integer, media_server_id character varying(50), server_id character varying(50), push_time character varying(50), status bool default false, update_time character varying(50), push_ing bool default false, self bool default false, constraint uk_stream_push_app_stream unique (app, stream) ); create table wvp_cloud_record ( id serial primary key, app character varying(255), stream character varying(255), call_id character varying(255), start_time bigint, end_time bigint, media_server_id character varying(50), file_name character varying(255), folder character varying(255), file_path character varying(255), collect bool default false, file_size bigint, time_len bigint, constraint uk_stream_push_app_stream_path unique (app, stream, file_path) ); create table wvp_user ( id serial primary key, username character varying(255), password character varying(255), role_id integer, create_time character varying(50), update_time character varying(50), push_key character varying(50), constraint uk_user_username unique (username) ); create table wvp_user_role ( id serial primary key, name character varying(50), authority character varying(50), create_time character varying(50), update_time character varying(50) ); create table wvp_resources_tree ( id serial primary key , is_catalog bool default true, device_channel_id integer , gb_stream_id integer, name character varying(255), parentId integer, path character varying(255) ); create table wvp_user_api_key ( id serial primary key , user_id bigint, app character varying(255) , api_key text, expired_at bigint, remark character varying(255), enable bool default true, create_time character varying(50), update_time character varying(50) ); /*初始数据*/ INSERT INTO wvp_user VALUES (1, 'admin','21232f297a57a5a743894a0e4a801fc3',1,'2021-04-13 14:14:57','2021-04-13 14:14:57','3e80d1762a324d5b0ff636e0bd16f1e3'); INSERT INTO wvp_user_role VALUES (1, 'admin','0','2021-04-13 14:14:57','2021-04-13 14:14:57'); ================================================ FILE: 数据库/2.7.1/初始化-postgresql-kingbase-2.7.1.sql ================================================ /*建表*/ create table wvp_device ( id serial primary key , device_id character varying(50) not null , name character varying(255), manufacturer character varying(255), model character varying(255), firmware character varying(255), transport character varying(50), stream_mode character varying(50), on_line bool default false, register_time character varying(50), keepalive_time character varying(50), ip character varying(50), create_time character varying(50), update_time character varying(50), port integer, expires integer, subscribe_cycle_for_catalog integer DEFAULT 0, subscribe_cycle_for_mobile_position integer DEFAULT 0, mobile_position_submission_interval integer DEFAULT 5, subscribe_cycle_for_alarm integer DEFAULT 0, host_address character varying(50), charset character varying(50), ssrc_check bool default false, geo_coord_sys character varying(50), media_server_id character varying(50), custom_name character varying(255), sdp_ip character varying(50), local_ip character varying(50), password character varying(255), as_message_channel bool default false, keepalive_interval_time integer, broadcast_push_after_ack bool default false, constraint uk_device_device unique (device_id) ); create table wvp_device_alarm ( id serial primary key , device_id character varying(50) not null, channel_id character varying(50) not null, alarm_priority character varying(50), alarm_method character varying(50), alarm_time character varying(50), alarm_description character varying(255), longitude double precision, latitude double precision, alarm_type character varying(50), create_time character varying(50) not null ); create table wvp_device_channel ( id serial primary key , channel_id character varying(50) not null, name character varying(255), custom_name character varying(255), manufacture character varying(50), model character varying(50), owner character varying(50), civil_code character varying(50), block character varying(50), address character varying(50), parent_id character varying(50), safety_way integer, register_way integer, cert_num character varying(50), certifiable integer, err_code integer, end_time character varying(50), secrecy character varying(50), ip_address character varying(50), port integer, password character varying(255), ptz_type integer, custom_ptz_type integer, status bool default false, longitude double precision, custom_longitude double precision, latitude double precision, custom_latitude double precision, stream_id character varying(255), device_id character varying(50) not null, parental character varying(50), has_audio bool default false, create_time character varying(50) not null, update_time character varying(50) not null, sub_count integer, longitude_gcj02 double precision, latitude_gcj02 double precision, longitude_wgs84 double precision, latitude_wgs84 double precision, business_group_id character varying(50), gps_time character varying(50), stream_identification character varying(50), constraint uk_wvp_device_channel_unique_device_channel unique (device_id, channel_id) ); create table wvp_device_mobile_position ( id serial primary key, device_id character varying(50) not null, channel_id character varying(50) not null, device_name character varying(255), time character varying(50), longitude double precision, latitude double precision, altitude double precision, speed double precision, direction double precision, report_source character varying(50), longitude_gcj02 double precision, latitude_gcj02 double precision, longitude_wgs84 double precision, latitude_wgs84 double precision, create_time character varying(50) ); create table wvp_gb_stream ( gb_stream_id serial primary key, app character varying(255) not null, stream character varying(255) not null, gb_id character varying(50) not null, name character varying(255), longitude double precision, latitude double precision, stream_type character varying(50), media_server_id character varying(50), create_time character varying(50), constraint uk_gb_stream_unique_gb_id unique (gb_id), constraint uk_gb_stream_unique_app_stream unique (app, stream) ); create table wvp_log ( id serial primary key , name character varying(50), type character varying(50), uri character varying(200), address character varying(50), result character varying(50), timing bigint, username character varying(50), create_time character varying(50) ); create table wvp_media_server ( id character varying(255) primary key , ip character varying(50), hook_ip character varying(50), sdp_ip character varying(50), stream_ip character varying(50), http_port integer, http_ssl_port integer, rtmp_port integer, rtmp_ssl_port integer, rtp_proxy_port integer, rtsp_port integer, rtsp_ssl_port integer, flv_port integer, flv_ssl_port integer, ws_flv_port integer, ws_flv_ssl_port integer, auto_config bool default false, secret character varying(50), type character varying(50) default 'zlm', rtp_enable bool default false, rtp_port_range character varying(50), send_rtp_port_range character varying(50), record_assist_port integer, default_server bool default false, create_time character varying(50), update_time character varying(50), hook_alive_interval integer, record_path character varying(255), record_day integer default 7, transcode_suffix character varying(255), constraint uk_media_server_unique_ip_http_port unique (ip, http_port) ); create table wvp_platform ( id serial primary key , enable bool default false, name character varying(255), server_gb_id character varying(50), server_gb_domain character varying(50), server_ip character varying(50), server_port integer, device_gb_id character varying(50), device_ip character varying(50), device_port character varying(50), username character varying(255), password character varying(50), expires character varying(50), keep_timeout character varying(50), transport character varying(50), character_set character varying(50), catalog_id character varying(50), ptz bool default false, rtcp bool default false, status bool default false, start_offline_push bool default false, administrative_division character varying(50), catalog_group integer, create_time character varying(50), update_time character varying(50), as_message_channel bool default false, auto_push_channel bool default false, send_stream_ip character varying(50), constraint uk_platform_unique_server_gb_id unique (server_gb_id) ); create table wvp_platform_catalog ( id character varying(50), platform_id character varying(50), name character varying(255), parent_id character varying(50), civil_code character varying(50), business_group_id character varying(50), constraint uk_platform_catalog_id_platform_id unique (id, platform_id) ); create table wvp_platform_gb_channel ( id serial primary key , platform_id character varying(50), catalog_id character varying(50), device_channel_id integer, constraint uk_platform_gb_channel_platform_id_catalog_id_device_channel_id unique (platform_id, catalog_id, device_channel_id) ); create table wvp_platform_gb_stream ( id serial primary key, platform_id character varying(50), catalog_id character varying(50), gb_stream_id integer, constraint uk_platform_gb_stream_platform_id_catalog_id_gb_stream_id unique (platform_id, catalog_id, gb_stream_id) ); create table wvp_stream_proxy ( id serial primary key, type character varying(50), app character varying(255), stream character varying(255), url character varying(255), src_url character varying(255), dst_url character varying(255), timeout_ms integer, ffmpeg_cmd_key character varying(255), rtp_type character varying(50), media_server_id character varying(50), enable_audio bool default false, enable_mp4 bool default false, enable bool default false, status boolean, enable_remove_none_reader bool default false, create_time character varying(50), name character varying(255), update_time character varying(50), stream_key character varying(255), enable_disable_none_reader bool default false, constraint uk_stream_proxy_app_stream unique (app, stream) ); create table wvp_stream_push ( id serial primary key, app character varying(255), stream character varying(255), total_reader_count character varying(50), origin_type integer, origin_type_str character varying(50), create_time character varying(50), alive_second integer, media_server_id character varying(50), server_id character varying(50), push_time character varying(50), status bool default false, update_time character varying(50), push_ing bool default false, self bool default false, constraint uk_stream_push_app_stream unique (app, stream) ); create table wvp_cloud_record ( id serial primary key, app character varying(255), stream character varying(255), call_id character varying(255), start_time int8, end_time int8, media_server_id character varying(50), file_name character varying(255), folder character varying(255), file_path character varying(255), collect bool default false, file_size int8, time_len int8, constraint uk_stream_push_app_stream_path unique (app, stream, file_path) ); create table wvp_user ( id serial primary key, username character varying(255), password character varying(255), role_id integer, create_time character varying(50), update_time character varying(50), push_key character varying(50), constraint uk_user_username unique (username) ); create table wvp_user_role ( id serial primary key, name character varying(50), authority character varying(50), create_time character varying(50), update_time character varying(50) ); create table wvp_resources_tree ( id serial primary key , is_catalog bool default true, device_channel_id integer , gb_stream_id integer, name character varying(255), parentId integer, path character varying(255) ); create table wvp_user_api_key ( id serial primary key , user_id bigint, app character varying(255) , api_key text, expired_at bigint, remark character varying(255), enable bool default true, create_time character varying(50), update_time character varying(50) ); /*初始数据*/ INSERT INTO wvp_user VALUES (1, 'admin','21232f297a57a5a743894a0e4a801fc3',1,'2021-04-13 14:14:57','2021-04-13 14:14:57','3e80d1762a324d5b0ff636e0bd16f1e3'); INSERT INTO wvp_user_role VALUES (1, 'admin','0','2021-04-13 14:14:57','2021-04-13 14:14:57'); ================================================ FILE: 数据库/2.7.1/更新-mysql-2.7.1.sql ================================================ alter table wvp_media_server add transcode_suffix character varying(255); alter table wvp_media_server add type character varying(50) default 'zlm'; alter table wvp_media_server add flv_port integer; alter table wvp_media_server add flv_ssl_port integer; alter table wvp_media_server add ws_flv_port integer; alter table wvp_media_server add ws_flv_ssl_port integer; create table wvp_user_api_key ( id serial primary key , user_id bigint, app character varying(255) , api_key text, expired_at bigint, remark character varying(255), enable bool default true, create_time character varying(50), update_time character varying(50) ); ================================================ FILE: 数据库/2.7.1/更新-postgresql-kingbase-2.7.1.sql ================================================ alter table wvp_media_server add transcode_suffix character varying(255); alter table wvp_media_server add type character varying(50) default 'zlm'; alter table wvp_media_server add flv_port integer; alter table wvp_media_server add flv_ssl_port integer; alter table wvp_media_server add ws_flv_port integer; alter table wvp_media_server add ws_flv_ssl_port integer; create table wvp_user_api_key ( id serial primary key , user_id bigint, app character varying(255) , api_key text, expired_at bigint, remark character varying(255), enable bool default true, create_time character varying(50), update_time character varying(50) ); ================================================ FILE: 数据库/2.7.3/初始化-mysql-2.7.3.sql ================================================ /*建表*/ drop table IF EXISTS wvp_device; create table IF NOT EXISTS wvp_device ( id serial primary key, device_id character varying(50) not null, name character varying(255), manufacturer character varying(255), model character varying(255), firmware character varying(255), transport character varying(50), stream_mode character varying(50), on_line bool default false, register_time character varying(50), keepalive_time character varying(50), ip character varying(50), create_time character varying(50), update_time character varying(50), port integer, expires integer, subscribe_cycle_for_catalog integer DEFAULT 0, subscribe_cycle_for_mobile_position integer DEFAULT 0, mobile_position_submission_interval integer DEFAULT 5, subscribe_cycle_for_alarm integer DEFAULT 0, host_address character varying(50), charset character varying(50), ssrc_check bool default false, geo_coord_sys character varying(50), media_server_id character varying(50) default 'auto', custom_name character varying(255), sdp_ip character varying(50), local_ip character varying(50), password character varying(255), as_message_channel bool default false, heart_beat_interval integer, heart_beat_count integer, position_capability integer, broadcast_push_after_ack bool default false, server_id character varying(50), constraint uk_device_device unique (device_id) ); drop table IF EXISTS wvp_device_alarm; create table IF NOT EXISTS wvp_device_alarm ( id serial primary key, device_id character varying(50) not null, channel_id character varying(50) not null, alarm_priority character varying(50), alarm_method character varying(50), alarm_time character varying(50), alarm_description character varying(255), longitude double precision, latitude double precision, alarm_type character varying(50), create_time character varying(50) not null ); drop table IF EXISTS wvp_device_mobile_position; create table IF NOT EXISTS wvp_device_mobile_position ( id serial primary key, device_id character varying(50) not null, channel_id character varying(50) not null, device_name character varying(255), time character varying(50), longitude double precision, latitude double precision, altitude double precision, speed double precision, direction double precision, report_source character varying(50), create_time character varying(50) ); drop table IF EXISTS wvp_device_channel; create table IF NOT EXISTS wvp_device_channel ( id serial primary key, device_id character varying(50), name character varying(255), manufacturer character varying(50), model character varying(50), owner character varying(50), civil_code character varying(50), block character varying(50), address character varying(50), parental integer, parent_id character varying(50), safety_way integer, register_way integer, cert_num character varying(50), certifiable integer, err_code integer, end_time character varying(50), secrecy integer, ip_address character varying(50), port integer, password character varying(255), status character varying(50), longitude double precision, latitude double precision, ptz_type integer, position_type integer, room_type integer, use_type integer, supply_light_type integer, direction_type integer, resolution character varying(255), business_group_id character varying(255), download_speed character varying(255), svc_space_support_mod integer, svc_time_support_mode integer, create_time character varying(50) not null, update_time character varying(50) not null, sub_count integer, stream_id character varying(255), has_audio bool default false, gps_time character varying(50), stream_identification character varying(50), channel_type int default 0 not null, gb_device_id character varying(50), gb_name character varying(255), gb_manufacturer character varying(255), gb_model character varying(255), gb_owner character varying(255), gb_civil_code character varying(255), gb_block character varying(255), gb_address character varying(255), gb_parental integer, gb_parent_id character varying(255), gb_safety_way integer, gb_register_way integer, gb_cert_num character varying(50), gb_certifiable integer, gb_err_code integer, gb_end_time character varying(50), gb_secrecy integer, gb_ip_address character varying(50), gb_port integer, gb_password character varying(50), gb_status character varying(50), gb_longitude double, gb_latitude double, gb_business_group_id character varying(50), gb_ptz_type integer, gb_position_type integer, gb_room_type integer, gb_use_type integer, gb_supply_light_type integer, gb_direction_type integer, gb_resolution character varying(255), gb_download_speed character varying(255), gb_svc_space_support_mod integer, gb_svc_time_support_mode integer, record_plan_id integer, data_type integer not null, data_device_id integer not null, gps_speed double precision, gps_altitude double precision, gps_direction double precision, index (data_type), index (data_device_id), constraint uk_wvp_unique_channel unique (gb_device_id) ); drop table IF EXISTS wvp_media_server; create table IF NOT EXISTS wvp_media_server ( id character varying(255) primary key, ip character varying(50), hook_ip character varying(50), sdp_ip character varying(50), stream_ip character varying(50), http_port integer, http_ssl_port integer, rtmp_port integer, rtmp_ssl_port integer, rtp_proxy_port integer, rtsp_port integer, rtsp_ssl_port integer, flv_port integer, flv_ssl_port integer, ws_flv_port integer, ws_flv_ssl_port integer, auto_config bool default false, secret character varying(50), type character varying(50) default 'zlm', rtp_enable bool default false, rtp_port_range character varying(50), send_rtp_port_range character varying(50), record_assist_port integer, default_server bool default false, create_time character varying(50), update_time character varying(50), hook_alive_interval integer, record_path character varying(255), record_day integer default 7, transcode_suffix character varying(255), server_id character varying(50), constraint uk_media_server_unique_ip_http_port unique (ip, http_port, server_id) ); drop table IF EXISTS wvp_platform; create table IF NOT EXISTS wvp_platform ( id serial primary key, enable bool default false, name character varying(255), server_gb_id character varying(50), server_gb_domain character varying(50), server_ip character varying(50), server_port integer, device_gb_id character varying(50), device_ip character varying(50), device_port character varying(50), username character varying(255), password character varying(50), expires character varying(50), keep_timeout character varying(50), transport character varying(50), civil_code character varying(50), manufacturer character varying(255), model character varying(255), address character varying(255), character_set character varying(50), ptz bool default false, rtcp bool default false, status bool default false, catalog_group integer, register_way integer, secrecy integer, create_time character varying(50), update_time character varying(50), as_message_channel bool default false, catalog_with_platform integer default 1, catalog_with_group integer default 1, catalog_with_region integer default 1, auto_push_channel bool default true, send_stream_ip character varying(50), server_id character varying(50), constraint uk_platform_unique_server_gb_id unique (server_gb_id) ); drop table IF EXISTS wvp_platform_channel; create table IF NOT EXISTS wvp_platform_channel ( id serial primary key, platform_id integer, device_channel_id integer, custom_device_id character varying(50), custom_name character varying(255), custom_manufacturer character varying(50), custom_model character varying(50), custom_owner character varying(50), custom_civil_code character varying(50), custom_block character varying(50), custom_address character varying(50), custom_parental integer, custom_parent_id character varying(50), custom_safety_way integer, custom_register_way integer, custom_cert_num character varying(50), custom_certifiable integer, custom_err_code integer, custom_end_time character varying(50), custom_secrecy integer, custom_ip_address character varying(50), custom_port integer, custom_password character varying(255), custom_status character varying(50), custom_longitude double precision, custom_latitude double precision, custom_ptz_type integer, custom_position_type integer, custom_room_type integer, custom_use_type integer, custom_supply_light_type integer, custom_direction_type integer, custom_resolution character varying(255), custom_business_group_id character varying(255), custom_download_speed character varying(255), custom_svc_space_support_mod integer, custom_svc_time_support_mode integer, constraint uk_platform_gb_channel_platform_id_catalog_id_device_channel_id unique (platform_id, device_channel_id), constraint uk_platform_gb_channel_device_id unique (custom_device_id) ); drop table IF EXISTS wvp_platform_group; create table IF NOT EXISTS wvp_platform_group ( id serial primary key, platform_id integer, group_id integer, constraint uk_wvp_platform_group_platform_id_group_id unique (platform_id, group_id) ); drop table IF EXISTS wvp_platform_region; create table IF NOT EXISTS wvp_platform_region ( id serial primary key, platform_id integer, region_id integer, constraint uk_wvp_platform_region_platform_id_group_id unique (platform_id, region_id) ); drop table IF EXISTS wvp_stream_proxy; create table IF NOT EXISTS wvp_stream_proxy ( id serial primary key, type character varying(50), app character varying(255), stream character varying(255), src_url character varying(255), timeout integer, ffmpeg_cmd_key character varying(255), rtsp_type character varying(50), media_server_id character varying(50), enable_audio bool default false, enable_mp4 bool default false, pulling bool default false, enable bool default false, enable_remove_none_reader bool default false, create_time character varying(50), name character varying(255), update_time character varying(50), stream_key character varying(255), server_id character varying(50), enable_disable_none_reader bool default false, relates_media_server_id character varying(50), constraint uk_stream_proxy_app_stream unique (app, stream) ); drop table IF EXISTS wvp_stream_push; create table IF NOT EXISTS wvp_stream_push ( id serial primary key, app character varying(255), stream character varying(255), create_time character varying(50), media_server_id character varying(50), server_id character varying(50), push_time character varying(50), status bool default false, update_time character varying(50), pushing bool default false, self bool default false, start_offline_push bool default true, constraint uk_stream_push_app_stream unique (app, stream) ); drop table IF EXISTS wvp_cloud_record; create table IF NOT EXISTS wvp_cloud_record ( id serial primary key, app character varying(255), stream character varying(255), call_id character varying(255), start_time bigint, end_time bigint, media_server_id character varying(50), server_id character varying(50), file_name character varying(255), folder character varying(500), file_path character varying(500), collect bool default false, file_size bigint, time_len double precision ); drop table IF EXISTS wvp_user; create table IF NOT EXISTS wvp_user ( id serial primary key, username character varying(255), password character varying(255), role_id integer, create_time character varying(50), update_time character varying(50), push_key character varying(50), constraint uk_user_username unique (username) ); drop table IF EXISTS wvp_user_role; create table IF NOT EXISTS wvp_user_role ( id serial primary key, name character varying(50), authority character varying(50), create_time character varying(50), update_time character varying(50) ); drop table IF EXISTS wvp_user_api_key; create table IF NOT EXISTS wvp_user_api_key ( id serial primary key, user_id bigint, app character varying(255), api_key text, expired_at bigint, remark character varying(255), enable bool default true, create_time character varying(50), update_time character varying(50) ); /*初始数据*/ INSERT INTO wvp_user VALUES (1, 'admin', '21232f297a57a5a743894a0e4a801fc3', 1, '2021-04-13 14:14:57', '2021-04-13 14:14:57', '3e80d1762a324d5b0ff636e0bd16f1e3'); INSERT INTO wvp_user_role VALUES (1, 'admin', '0', '2021-04-13 14:14:57', '2021-04-13 14:14:57'); drop table IF EXISTS wvp_common_group; create table IF NOT EXISTS wvp_common_group ( id serial primary key, device_id varchar(50) NOT NULL, name varchar(255) NOT NULL, parent_id int, parent_device_id varchar(50) DEFAULT NULL, business_group varchar(50) NOT NULL, create_time varchar(50) NOT NULL, update_time varchar(50) NOT NULL, civil_code varchar(50) default null, constraint uk_common_group_device_platform unique (device_id) ); drop table IF EXISTS wvp_common_region; create table IF NOT EXISTS wvp_common_region ( id serial primary key, device_id varchar(50) NOT NULL, name varchar(255) NOT NULL, parent_id int, parent_device_id varchar(50) DEFAULT NULL, create_time varchar(50) NOT NULL, update_time varchar(50) NOT NULL, constraint uk_common_region_device_id unique (device_id) ); drop table IF EXISTS wvp_record_plan; create table IF NOT EXISTS wvp_record_plan ( id serial primary key, snap bool default false, name varchar(255) NOT NULL, create_time character varying(50), update_time character varying(50) ); drop table IF EXISTS wvp_record_plan_item; create table IF NOT EXISTS wvp_record_plan_item ( id serial primary key, start int, stop int, week_day int, plan_id int, create_time character varying(50), update_time character varying(50) ); ================================================ FILE: 数据库/2.7.3/初始化-postgresql-kingbase-2.7.3.sql ================================================ /*建表*/ drop table IF EXISTS wvp_device; create table IF NOT EXISTS wvp_device ( id serial primary key, device_id character varying(50) not null, name character varying(255), manufacturer character varying(255), model character varying(255), firmware character varying(255), transport character varying(50), stream_mode character varying(50), on_line bool default false, register_time character varying(50), keepalive_time character varying(50), ip character varying(50), create_time character varying(50), update_time character varying(50), port integer, expires integer, subscribe_cycle_for_catalog integer DEFAULT 0, subscribe_cycle_for_mobile_position integer DEFAULT 0, mobile_position_submission_interval integer DEFAULT 5, subscribe_cycle_for_alarm integer DEFAULT 0, host_address character varying(50), charset character varying(50), ssrc_check bool default false, geo_coord_sys character varying(50), media_server_id character varying(50) default 'auto', custom_name character varying(255), sdp_ip character varying(50), local_ip character varying(50), password character varying(255), as_message_channel bool default false, heart_beat_interval integer, heart_beat_count integer, position_capability integer, broadcast_push_after_ack bool default false, server_id character varying(50), constraint uk_device_device unique (device_id) ); drop table IF EXISTS wvp_device_alarm; create table IF NOT EXISTS wvp_device_alarm ( id serial primary key, device_id character varying(50) not null, channel_id character varying(50) not null, alarm_priority character varying(50), alarm_method character varying(50), alarm_time character varying(50), alarm_description character varying(255), longitude double precision, latitude double precision, alarm_type character varying(50), create_time character varying(50) not null ); drop table IF EXISTS wvp_device_mobile_position; create table IF NOT EXISTS wvp_device_mobile_position ( id serial primary key, device_id character varying(50) not null, channel_id character varying(50) not null, device_name character varying(255), time character varying(50), longitude double precision, latitude double precision, altitude double precision, speed double precision, direction double precision, report_source character varying(50), create_time character varying(50) ); drop table IF EXISTS wvp_device_channel; create table IF NOT EXISTS wvp_device_channel ( id serial primary key, device_id character varying(50), name character varying(255), manufacturer character varying(50), model character varying(50), owner character varying(50), civil_code character varying(50), block character varying(50), address character varying(50), parental integer, parent_id character varying(50), safety_way integer, register_way integer, cert_num character varying(50), certifiable integer, err_code integer, end_time character varying(50), secrecy integer, ip_address character varying(50), port integer, password character varying(255), status character varying(50), longitude double precision, latitude double precision, ptz_type integer, position_type integer, room_type integer, use_type integer, supply_light_type integer, direction_type integer, resolution character varying(255), business_group_id character varying(255), download_speed character varying(255), svc_space_support_mod integer, svc_time_support_mode integer, create_time character varying(50) not null, update_time character varying(50) not null, sub_count integer, stream_id character varying(255), has_audio bool default false, gps_time character varying(50), stream_identification character varying(50), channel_type int default 0 not null, gb_device_id character varying(50), gb_name character varying(255), gb_manufacturer character varying(255), gb_model character varying(255), gb_owner character varying(255), gb_civil_code character varying(255), gb_block character varying(255), gb_address character varying(255), gb_parental integer, gb_parent_id character varying(255), gb_safety_way integer, gb_register_way integer, gb_cert_num character varying(50), gb_certifiable integer, gb_err_code integer, gb_end_time character varying(50), gb_secrecy integer, gb_ip_address character varying(50), gb_port integer, gb_password character varying(50), gb_status character varying(50), gb_longitude double precision, gb_latitude double precision, gb_business_group_id character varying(50), gb_ptz_type integer, gb_position_type integer, gb_room_type integer, gb_use_type integer, gb_supply_light_type integer, gb_direction_type integer, gb_resolution character varying(255), gb_download_speed character varying(255), gb_svc_space_support_mod integer, gb_svc_time_support_mode integer, record_plan_id integer, data_type integer not null, data_device_id integer not null, gps_speed double precision, gps_altitude double precision, gps_direction double precision, constraint uk_wvp_unique_channel unique (gb_device_id) ); CREATE INDEX idx_data_type ON wvp_device_channel (data_type); CREATE INDEX idx_data_device_id ON wvp_device_channel (data_device_id); drop table IF EXISTS wvp_media_server; create table IF NOT EXISTS wvp_media_server ( id character varying(255) primary key, ip character varying(50), hook_ip character varying(50), sdp_ip character varying(50), stream_ip character varying(50), http_port integer, http_ssl_port integer, rtmp_port integer, rtmp_ssl_port integer, rtp_proxy_port integer, rtsp_port integer, rtsp_ssl_port integer, flv_port integer, flv_ssl_port integer, ws_flv_port integer, ws_flv_ssl_port integer, auto_config bool default false, secret character varying(50), type character varying(50) default 'zlm', rtp_enable bool default false, rtp_port_range character varying(50), send_rtp_port_range character varying(50), record_assist_port integer, default_server bool default false, create_time character varying(50), update_time character varying(50), hook_alive_interval integer, record_path character varying(255), record_day integer default 7, transcode_suffix character varying(255), server_id character varying(50), constraint uk_media_server_unique_ip_http_port unique (ip, http_port, server_id) ); drop table IF EXISTS wvp_platform; create table IF NOT EXISTS wvp_platform ( id serial primary key, enable bool default false, name character varying(255), server_gb_id character varying(50), server_gb_domain character varying(50), server_ip character varying(50), server_port integer, device_gb_id character varying(50), device_ip character varying(50), device_port character varying(50), username character varying(255), password character varying(50), expires character varying(50), keep_timeout character varying(50), transport character varying(50), civil_code character varying(50), manufacturer character varying(255), model character varying(255), address character varying(255), character_set character varying(50), ptz bool default false, rtcp bool default false, status bool default false, catalog_group integer, register_way integer, secrecy integer, create_time character varying(50), update_time character varying(50), as_message_channel bool default false, catalog_with_platform integer default 1, catalog_with_group integer default 1, catalog_with_region integer default 1, auto_push_channel bool default true, send_stream_ip character varying(50), server_id character varying(50), constraint uk_platform_unique_server_gb_id unique (server_gb_id) ); drop table IF EXISTS wvp_platform_channel; create table IF NOT EXISTS wvp_platform_channel ( id serial primary key, platform_id integer, device_channel_id integer, custom_device_id character varying(50), custom_name character varying(255), custom_manufacturer character varying(50), custom_model character varying(50), custom_owner character varying(50), custom_civil_code character varying(50), custom_block character varying(50), custom_address character varying(50), custom_parental integer, custom_parent_id character varying(50), custom_safety_way integer, custom_register_way integer, custom_cert_num character varying(50), custom_certifiable integer, custom_err_code integer, custom_end_time character varying(50), custom_secrecy integer, custom_ip_address character varying(50), custom_port integer, custom_password character varying(255), custom_status character varying(50), custom_longitude double precision, custom_latitude double precision, custom_ptz_type integer, custom_position_type integer, custom_room_type integer, custom_use_type integer, custom_supply_light_type integer, custom_direction_type integer, custom_resolution character varying(255), custom_business_group_id character varying(255), custom_download_speed character varying(255), custom_svc_space_support_mod integer, custom_svc_time_support_mode integer, constraint uk_platform_gb_channel_platform_id_catalog_id_device_channel_id unique (platform_id, device_channel_id), constraint uk_platform_gb_channel_device_id unique (custom_device_id) ); drop table IF EXISTS wvp_platform_group; create table IF NOT EXISTS wvp_platform_group ( id serial primary key, platform_id integer, group_id integer, constraint uk_wvp_platform_group_platform_id_group_id unique (platform_id, group_id) ); drop table IF EXISTS wvp_platform_region; create table IF NOT EXISTS wvp_platform_region ( id serial primary key, platform_id integer, region_id integer, constraint uk_wvp_platform_region_platform_id_group_id unique (platform_id, region_id) ); drop table IF EXISTS wvp_stream_proxy; create table IF NOT EXISTS wvp_stream_proxy ( id serial primary key, type character varying(50), app character varying(255), stream character varying(255), src_url character varying(255), timeout integer, ffmpeg_cmd_key character varying(255), rtsp_type character varying(50), media_server_id character varying(50), enable_audio bool default false, enable_mp4 bool default false, pulling bool default false, enable bool default false, enable_remove_none_reader bool default false, create_time character varying(50), name character varying(255), update_time character varying(50), stream_key character varying(255), server_id character varying(50), enable_disable_none_reader bool default false, relates_media_server_id character varying(50), constraint uk_stream_proxy_app_stream unique (app, stream) ); drop table IF EXISTS wvp_stream_push; create table IF NOT EXISTS wvp_stream_push ( id serial primary key, app character varying(255), stream character varying(255), create_time character varying(50), media_server_id character varying(50), server_id character varying(50), push_time character varying(50), status bool default false, update_time character varying(50), pushing bool default false, self bool default false, start_offline_push bool default true, constraint uk_stream_push_app_stream unique (app, stream) ); drop table IF EXISTS wvp_cloud_record; create table IF NOT EXISTS wvp_cloud_record ( id serial primary key, app character varying(255), stream character varying(255), call_id character varying(255), start_time int8, end_time int8, media_server_id character varying(50), server_id character varying(50), file_name character varying(255), folder character varying(500), file_path character varying(500), collect bool default false, file_size int8, time_len double precision ); drop table IF EXISTS wvp_user; create table IF NOT EXISTS wvp_user ( id serial primary key, username character varying(255), password character varying(255), role_id integer, create_time character varying(50), update_time character varying(50), push_key character varying(50), constraint uk_user_username unique (username) ); drop table IF EXISTS wvp_user_role; create table IF NOT EXISTS wvp_user_role ( id serial primary key, name character varying(50), authority character varying(50), create_time character varying(50), update_time character varying(50) ); drop table IF EXISTS wvp_user_api_key; create table IF NOT EXISTS wvp_user_api_key ( id serial primary key, user_id int8, app character varying(255), api_key text, expired_at int8, remark character varying(255), enable bool default true, create_time character varying(50), update_time character varying(50) ); /*初始数据*/ INSERT INTO wvp_user VALUES (1, 'admin', '21232f297a57a5a743894a0e4a801fc3', 1, '2021-04-13 14:14:57', '2021-04-13 14:14:57', '3e80d1762a324d5b0ff636e0bd16f1e3'); INSERT INTO wvp_user_role VALUES (1, 'admin', '0', '2021-04-13 14:14:57', '2021-04-13 14:14:57'); drop table IF EXISTS wvp_common_group; create table IF NOT EXISTS wvp_common_group ( id serial primary key, device_id varchar(50) NOT NULL, name varchar(255) NOT NULL, parent_id int, parent_device_id varchar(50) DEFAULT NULL, business_group varchar(50) NOT NULL, create_time varchar(50) NOT NULL, update_time varchar(50) NOT NULL, civil_code varchar(50) default null, constraint uk_common_group_device_platform unique (device_id) ); drop table IF EXISTS wvp_common_region; create table IF NOT EXISTS wvp_common_region ( id serial primary key, device_id varchar(50) NOT NULL, name varchar(255) NOT NULL, parent_id int, parent_device_id varchar(50) DEFAULT NULL, create_time varchar(50) NOT NULL, update_time varchar(50) NOT NULL, constraint uk_common_region_device_id unique (device_id) ); drop table IF EXISTS wvp_record_plan; create table IF NOT EXISTS wvp_record_plan ( id serial primary key, snap bool default false, name varchar(255) NOT NULL, create_time character varying(50), update_time character varying(50) ); drop table IF EXISTS wvp_record_plan_item; create table IF NOT EXISTS wvp_record_plan_item ( id serial primary key, "start" int, stop int, week_day int, plan_id int, create_time character varying(50), update_time character varying(50) ); ================================================ FILE: 数据库/2.7.3/更新-mysql-2.7.1升级到2.7.3.sql ================================================ drop table if exists wvp_resources_tree; drop table if exists wvp_platform_catalog; drop table if exists wvp_platform_gb_stream; drop table if exists wvp_platform_gb_channel; drop table if exists wvp_gb_stream; drop table if exists wvp_log; drop table IF EXISTS wvp_device; drop table IF EXISTS wvp_platform; drop table IF EXISTS wvp_media_server; drop table IF EXISTS wvp_device_mobile_position; drop table IF EXISTS wvp_device_channel; drop table IF EXISTS wvp_stream_proxy; drop table IF EXISTS wvp_stream_push; create table IF NOT EXISTS wvp_device ( id serial primary key, device_id character varying(50) not null, name character varying(255), manufacturer character varying(255), model character varying(255), firmware character varying(255), transport character varying(50), stream_mode character varying(50), on_line bool default false, register_time character varying(50), keepalive_time character varying(50), ip character varying(50), create_time character varying(50), update_time character varying(50), port integer, expires integer, subscribe_cycle_for_catalog integer DEFAULT 0, subscribe_cycle_for_mobile_position integer DEFAULT 0, mobile_position_submission_interval integer DEFAULT 5, subscribe_cycle_for_alarm integer DEFAULT 0, host_address character varying(50), charset character varying(50), ssrc_check bool default false, geo_coord_sys character varying(50), media_server_id character varying(50) default 'auto', custom_name character varying(255), sdp_ip character varying(50), local_ip character varying(50), password character varying(255), as_message_channel bool default false, heart_beat_interval integer, heart_beat_count integer, position_capability integer, broadcast_push_after_ack bool default false, server_id character varying(50), constraint uk_device_device unique (device_id) ); create table if not exists wvp_common_group ( id bigint unsigned auto_increment primary key, device_id varchar(50) not null, name varchar(255) not null, parent_id int null, parent_device_id varchar(50) null, business_group varchar(50) not null, create_time varchar(50) not null, update_time varchar(50) not null, civil_code varchar(50) null, constraint id unique (id), constraint uk_common_group_device_platform unique (device_id) ); create table if not exists wvp_common_region ( id bigint unsigned auto_increment primary key, device_id varchar(50) not null, name varchar(255) not null, parent_id int null, parent_device_id varchar(50) null, create_time varchar(50) not null, update_time varchar(50) not null, constraint id unique (id), constraint uk_common_region_device_id unique (device_id) ); create table IF NOT EXISTS wvp_device_channel ( id serial primary key, device_id character varying(50), name character varying(255), manufacturer character varying(50), model character varying(50), owner character varying(50), civil_code character varying(50), block character varying(50), address character varying(50), parental integer, parent_id character varying(50), safety_way integer, register_way integer, cert_num character varying(50), certifiable integer, err_code integer, end_time character varying(50), secrecy integer, ip_address character varying(50), port integer, password character varying(255), status character varying(50), longitude double precision, latitude double precision, ptz_type integer, position_type integer, room_type integer, use_type integer, supply_light_type integer, direction_type integer, resolution character varying(255), business_group_id character varying(255), download_speed character varying(255), svc_space_support_mod integer, svc_time_support_mode integer, create_time character varying(50) not null, update_time character varying(50) not null, sub_count integer, stream_id character varying(255), has_audio bool default false, gps_time character varying(50), stream_identification character varying(50), channel_type int default 0 not null, gb_device_id character varying(50), gb_name character varying(255), gb_manufacturer character varying(255), gb_model character varying(255), gb_owner character varying(255), gb_civil_code character varying(255), gb_block character varying(255), gb_address character varying(255), gb_parental integer, gb_parent_id character varying(255), gb_safety_way integer, gb_register_way integer, gb_cert_num character varying(50), gb_certifiable integer, gb_err_code integer, gb_end_time character varying(50), gb_secrecy integer, gb_ip_address character varying(50), gb_port integer, gb_password character varying(50), gb_status character varying(50), gb_longitude double, gb_latitude double, gb_business_group_id character varying(50), gb_ptz_type integer, gb_position_type integer, gb_room_type integer, gb_use_type integer, gb_supply_light_type integer, gb_direction_type integer, gb_resolution character varying(255), gb_download_speed character varying(255), gb_svc_space_support_mod integer, gb_svc_time_support_mode integer, record_plan_id integer, data_type integer not null, data_device_id integer not null, gps_speed double precision, gps_altitude double precision, gps_direction double precision, index (data_type), index (data_device_id), constraint uk_wvp_unique_channel unique (gb_device_id) ); create table IF NOT EXISTS wvp_device_mobile_position ( id serial primary key, device_id character varying(50) not null, channel_id character varying(50) not null, device_name character varying(255), time character varying(50), longitude double precision, latitude double precision, altitude double precision, speed double precision, direction double precision, report_source character varying(50), create_time character varying(50) ); create table IF NOT EXISTS wvp_media_server ( id character varying(255) primary key, ip character varying(50), hook_ip character varying(50), sdp_ip character varying(50), stream_ip character varying(50), http_port integer, http_ssl_port integer, rtmp_port integer, rtmp_ssl_port integer, rtp_proxy_port integer, rtsp_port integer, rtsp_ssl_port integer, flv_port integer, flv_ssl_port integer, ws_flv_port integer, ws_flv_ssl_port integer, auto_config bool default false, secret character varying(50), type character varying(50) default 'zlm', rtp_enable bool default false, rtp_port_range character varying(50), send_rtp_port_range character varying(50), record_assist_port integer, default_server bool default false, create_time character varying(50), update_time character varying(50), hook_alive_interval integer, record_path character varying(255), record_day integer default 7, transcode_suffix character varying(255), server_id character varying(50), constraint uk_media_server_unique_ip_http_port unique (ip, http_port, server_id) ); create table IF NOT EXISTS wvp_platform ( id serial primary key, enable bool default false, name character varying(255), server_gb_id character varying(50), server_gb_domain character varying(50), server_ip character varying(50), server_port integer, device_gb_id character varying(50), device_ip character varying(50), device_port character varying(50), username character varying(255), password character varying(50), expires character varying(50), keep_timeout character varying(50), transport character varying(50), civil_code character varying(50), manufacturer character varying(255), model character varying(255), address character varying(255), character_set character varying(50), ptz bool default false, rtcp bool default false, status bool default false, catalog_group integer, register_way integer, secrecy integer, create_time character varying(50), update_time character varying(50), as_message_channel bool default false, catalog_with_platform integer default 1, catalog_with_group integer default 1, catalog_with_region integer default 1, auto_push_channel bool default true, send_stream_ip character varying(50), server_id character varying(50), constraint uk_platform_unique_server_gb_id unique (server_gb_id) ); create table IF NOT EXISTS wvp_stream_proxy ( id serial primary key, type character varying(50), app character varying(255), stream character varying(255), src_url character varying(255), timeout integer, ffmpeg_cmd_key character varying(255), rtsp_type character varying(50), media_server_id character varying(50), enable_audio bool default false, enable_mp4 bool default false, pulling bool default false, enable bool default false, enable_remove_none_reader bool default false, create_time character varying(50), name character varying(255), update_time character varying(50), stream_key character varying(255), server_id character varying(50), enable_disable_none_reader bool default false, relates_media_server_id character varying(50), constraint uk_stream_proxy_app_stream unique (app, stream) ); create table IF NOT EXISTS wvp_stream_push ( id serial primary key, app character varying(255), stream character varying(255), create_time character varying(50), media_server_id character varying(50), server_id character varying(50), push_time character varying(50), status bool default false, update_time character varying(50), pushing bool default false, self bool default false, start_offline_push bool default true, constraint uk_stream_push_app_stream unique (app, stream) ); create table IF NOT EXISTS wvp_platform_channel ( id serial primary key, platform_id integer, device_channel_id integer, custom_device_id character varying(50), custom_name character varying(255), custom_manufacturer character varying(50), custom_model character varying(50), custom_owner character varying(50), custom_civil_code character varying(50), custom_block character varying(50), custom_address character varying(50), custom_parental integer, custom_parent_id character varying(50), custom_safety_way integer, custom_register_way integer, custom_cert_num character varying(50), custom_certifiable integer, custom_err_code integer, custom_end_time character varying(50), custom_secrecy integer, custom_ip_address character varying(50), custom_port integer, custom_password character varying(255), custom_status character varying(50), custom_longitude double precision, custom_latitude double precision, custom_ptz_type integer, custom_position_type integer, custom_room_type integer, custom_use_type integer, custom_supply_light_type integer, custom_direction_type integer, custom_resolution character varying(255), custom_business_group_id character varying(255), custom_download_speed character varying(255), custom_svc_space_support_mod integer, custom_svc_time_support_mode integer, constraint uk_platform_gb_channel_platform_id_catalog_id_device_channel_id unique (platform_id, device_channel_id), constraint uk_platform_gb_channel_device_id unique (custom_device_id) ); create table IF NOT EXISTS wvp_platform_group ( id serial primary key, platform_id integer, group_id integer, constraint uk_wvp_platform_group_platform_id_group_id unique (platform_id, group_id) ); create table IF NOT EXISTS wvp_platform_region ( id serial primary key, platform_id integer, region_id integer, constraint uk_wvp_platform_region_platform_id_group_id unique (platform_id, region_id) ); create table IF NOT EXISTS wvp_record_plan ( id serial primary key, snap bool default false, name varchar(255) NOT NULL, create_time character varying(50), update_time character varying(50) ); create table IF NOT EXISTS wvp_record_plan_item ( id serial primary key, start int, stop int, week_day int, plan_id int, create_time character varying(50), update_time character varying(50) ); DELIMITER // -- 重定义分隔符避免分号冲突 CREATE PROCEDURE `wvp_20250111`() BEGIN DECLARE serverId VARCHAR(32) DEFAULT '你的服务ID'; IF EXISTS (SELECT column_name FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_cloud_record' and INDEX_NAME = 'uk_stream_push_app_stream_path') THEN alter table wvp_cloud_record drop index uk_stream_push_app_stream_path ; END IF; IF EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_cloud_record' and column_name = 'folder') THEN alter table wvp_cloud_record modify folder varchar(500) null; END IF; IF EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_cloud_record' and column_name = 'file_path') THEN alter table wvp_cloud_record modify file_path varchar(500) null; END IF; IF not EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_cloud_record' and column_name = 'server_id') THEN alter table wvp_cloud_record add server_id character varying(50); update wvp_cloud_record set server_id = serverId; END IF; END;// call wvp_20250111(); DROP PROCEDURE wvp_20250111; DELIMITER ; /** * 20250414 */ alter table wvp_cloud_record modify time_len double precision; ================================================ FILE: 数据库/2.7.3/更新-mysql-2.7.3.sql ================================================ /* * 20240528 */ DELIMITER // -- 重定义分隔符避免分号冲突 CREATE PROCEDURE `wvp_20240528`() BEGIN IF NOT EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_media_server' and column_name = 'transcode_suffix') THEN ALTER TABLE wvp_media_server ADD transcode_suffix character varying(255); END IF; IF not EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_media_server' and column_name = 'type') THEN alter table wvp_media_server add type character varying(50) default 'zlm'; END IF; IF not EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_media_server' and column_name = 'flv_port') THEN alter table wvp_media_server add flv_port integer; END IF; IF not EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_media_server' and column_name = 'flv_ssl_port') THEN alter table wvp_media_server add flv_ssl_port integer; END IF; IF not EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_media_server' and column_name = 'ws_flv_port') THEN alter table wvp_media_server add ws_flv_port integer; END IF; IF not EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_media_server' and column_name = 'ws_flv_ssl_port') THEN alter table wvp_media_server add ws_flv_ssl_port integer; END IF; END; // call wvp_20240528(); DROP PROCEDURE wvp_20240528; DELIMITER ; create table IF NOT EXISTS wvp_user_api_key ( id serial primary key , user_id bigint, app character varying(255) , api_key text, expired_at bigint, remark character varying(255), enable bool default true, create_time character varying(50), update_time character varying(50) ); /* * 20241222 */ DELIMITER // -- 重定义分隔符避免分号冲突 CREATE PROCEDURE `wvp_20241222`() BEGIN IF EXISTS (SELECT column_name FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_device_channel' and INDEX_NAME = 'uk_wvp_device_channel_unique_device_channel') THEN alter table wvp_device_channel drop index uk_wvp_device_channel_unique_device_channel; END IF; IF EXISTS (SELECT column_name FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_device_channel' and INDEX_NAME = 'uk_wvp_unique_stream_push_id') THEN alter table wvp_device_channel drop index uk_wvp_unique_stream_push_id; END IF; IF EXISTS (SELECT column_name FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_device_channel' and INDEX_NAME = 'uk_wvp_unique_stream_proxy_id') THEN alter table wvp_device_channel drop index uk_wvp_unique_stream_proxy_id; END IF; IF not EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_device_channel' and column_name = 'data_type') THEN alter table wvp_device_channel add data_type integer not null; END IF; IF not EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_device_channel' and column_name = 'data_device_id') THEN alter table wvp_device_channel add data_device_id integer not null; END IF; IF EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_device_channel' and column_name = 'device_db_id') THEN update wvp_device_channel wdc INNER JOIN (SELECT id, device_db_id from wvp_device_channel where device_db_id is not null ) ct on ct.id = wdc.id set wdc.data_type = 1, wdc.data_device_id = ct.device_db_id where wdc.device_db_id is not null; alter table wvp_device_channel drop device_db_id; END IF; IF EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_device_channel' and column_name = 'stream_push_id') THEN update wvp_device_channel wdc INNER JOIN (SELECT id, stream_push_id from wvp_device_channel where stream_push_id is not null ) ct on ct.id = wdc.id set wdc.data_type = 2, wdc.data_device_id = ct.stream_push_id where wdc.stream_push_id is not null; alter table wvp_device_channel drop stream_push_id; END IF; IF EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_device_channel' and column_name = 'stream_proxy_id') THEN update wvp_device_channel wdc INNER JOIN (SELECT id, stream_proxy_id from wvp_device_channel where stream_proxy_id is not null ) ct on ct.id = wdc.id set wdc.data_type = 3, wdc.data_device_id = ct.stream_proxy_id where wdc.stream_proxy_id is not null; alter table wvp_device_channel drop stream_proxy_id; END IF; END; // call wvp_20241222(); DROP PROCEDURE wvp_20241222; DELIMITER ; /* * 20241231 */ DELIMITER // CREATE PROCEDURE `wvp_20241231`() BEGIN IF not EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_stream_proxy' and column_name = 'relates_media_server_id') THEN alter table wvp_stream_proxy add relates_media_server_id character varying(50); END IF; END; // call wvp_20241231(); DROP PROCEDURE wvp_20241231; DELIMITER ; /* * 20250111 */ DELIMITER // -- 重定义分隔符避免分号冲突 CREATE PROCEDURE `wvp_20250111`() BEGIN IF EXISTS (SELECT column_name FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_cloud_record' and INDEX_NAME = 'uk_stream_push_app_stream_path') THEN alter table wvp_cloud_record drop index uk_stream_push_app_stream_path ; END IF; IF EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_cloud_record' and column_name = 'folder') THEN alter table wvp_cloud_record modify folder varchar(500) null; END IF; IF EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_cloud_record' and column_name = 'file_path') THEN alter table wvp_cloud_record modify file_path varchar(500) null; END IF; END; // call wvp_20250111(); DROP PROCEDURE wvp_20250111; DELIMITER ; /* * 20250211 */ DELIMITER // -- 重定义分隔符避免分号冲突 CREATE PROCEDURE `wvp_20250211`() BEGIN IF EXISTS (SELECT column_name FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_device' and column_name = 'keepalive_interval_time') THEN alter table wvp_device change keepalive_interval_time heart_beat_interval integer after as_message_channel; END IF; IF not EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_device' and column_name = 'heart_beat_count') THEN alter table wvp_device add heart_beat_count integer; END IF; IF not EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_device' and column_name = 'position_capability') THEN alter table wvp_device add position_capability integer; END IF; END; // call wvp_20250211(); DROP PROCEDURE wvp_20250211; DELIMITER ; /** * 20250312 */ DELIMITER // -- 重定义分隔符避免分号冲突 CREATE PROCEDURE `wvp_20250312`() BEGIN DECLARE serverId VARCHAR(32) DEFAULT '你的服务ID'; IF not EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_device' and column_name = 'server_id') THEN alter table wvp_device add server_id character varying(50); update wvp_device set server_id = serverId; END IF; IF not EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_media_server' and column_name = 'server_id') THEN alter table wvp_media_server add server_id character varying(50); update wvp_media_server set server_id = serverId; END IF; IF not EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_stream_proxy' and column_name = 'server_id') THEN alter table wvp_stream_proxy add server_id character varying(50); update wvp_stream_proxy set server_id = serverId; END IF; IF not EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_cloud_record' and column_name = 'server_id') THEN alter table wvp_cloud_record add server_id character varying(50); update wvp_cloud_record set server_id = serverId; END IF; IF not EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_platform' and column_name = 'server_id') THEN alter table wvp_platform add server_id character varying(50); END IF; END; // call wvp_20250312(); DROP PROCEDURE wvp_20250312; DELIMITER ; /* * 20250319 */ DELIMITER // -- 重定义分隔符避免分号冲突 CREATE PROCEDURE `wvp_20250319`() BEGIN IF NOT EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_device_channel' and column_name = 'gps_speed') THEN alter table wvp_device_channel add gps_speed double precision; END IF; IF NOT EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_device_channel' and column_name = 'gps_altitude') THEN alter table wvp_device_channel add gps_altitude double precision; END IF; IF NOT EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_device_channel' and column_name = 'gps_direction') THEN alter table wvp_device_channel add gps_direction double precision; END IF; END; // call wvp_20250319(); DROP PROCEDURE wvp_20250319; DELIMITER ; /* * 20250319 */ update wvp_record_plan_item set start = start * 30, stop = (stop + 1) * 30 /* * 20250402 */ DELIMITER // -- 重定义分隔符避免分号冲突 CREATE PROCEDURE `wvp_20250402`() BEGIN IF NOT EXISTS (SELECT column_name FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_device_channel' and INDEX_NAME = 'data_type') THEN create index data_type on wvp_device_channel (data_type); END IF; IF NOT EXISTS (SELECT column_name FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_device_channel' and INDEX_NAME = 'data_device_id') THEN create index data_device_id on wvp_device_channel (data_device_id); END IF; END; // call wvp_20250402(); DROP PROCEDURE wvp_20250402; DELIMITER ; /** * 20250414 */ alter table wvp_cloud_record modify time_len double precision; ================================================ FILE: 数据库/2.7.3/更新-postgresql-kingbase-2.7.1升级到2.7.3.sql ================================================ drop table if exists wvp_resources_tree; drop table if exists wvp_platform_catalog; drop table if exists wvp_platform_gb_stream; drop table if exists wvp_platform_gb_channel; drop table if exists wvp_gb_stream; drop table if exists wvp_log; drop table IF EXISTS wvp_device; drop table IF EXISTS wvp_platform; drop table IF EXISTS wvp_media_server; drop table IF EXISTS wvp_device_mobile_position; drop table IF EXISTS wvp_device_channel; drop table IF EXISTS wvp_stream_proxy; drop table IF EXISTS wvp_stream_push; create table IF NOT EXISTS wvp_device ( id serial primary key, device_id character varying(50) not null, name character varying(255), manufacturer character varying(255), model character varying(255), firmware character varying(255), transport character varying(50), stream_mode character varying(50), on_line bool default false, register_time character varying(50), keepalive_time character varying(50), ip character varying(50), create_time character varying(50), update_time character varying(50), port integer, expires integer, subscribe_cycle_for_catalog integer DEFAULT 0, subscribe_cycle_for_mobile_position integer DEFAULT 0, mobile_position_submission_interval integer DEFAULT 5, subscribe_cycle_for_alarm integer DEFAULT 0, host_address character varying(50), charset character varying(50), ssrc_check bool default false, geo_coord_sys character varying(50), media_server_id character varying(50) default 'auto', custom_name character varying(255), sdp_ip character varying(50), local_ip character varying(50), password character varying(255), as_message_channel bool default false, heart_beat_interval integer, heart_beat_count integer, position_capability integer, broadcast_push_after_ack bool default false, server_id character varying(50), constraint uk_device_device unique (device_id) ); create table IF NOT EXISTS wvp_device_channel ( id serial primary key, device_id character varying(50), name character varying(255), manufacturer character varying(50), model character varying(50), owner character varying(50), civil_code character varying(50), block character varying(50), address character varying(50), parental integer, parent_id character varying(50), safety_way integer, register_way integer, cert_num character varying(50), certifiable integer, err_code integer, end_time character varying(50), secrecy integer, ip_address character varying(50), port integer, password character varying(255), status character varying(50), longitude double precision, latitude double precision, ptz_type integer, position_type integer, room_type integer, use_type integer, supply_light_type integer, direction_type integer, resolution character varying(255), business_group_id character varying(255), download_speed character varying(255), svc_space_support_mod integer, svc_time_support_mode integer, create_time character varying(50) not null, update_time character varying(50) not null, sub_count integer, stream_id character varying(255), has_audio bool default false, gps_time character varying(50), stream_identification character varying(50), channel_type int default 0 not null, gb_device_id character varying(50), gb_name character varying(255), gb_manufacturer character varying(255), gb_model character varying(255), gb_owner character varying(255), gb_civil_code character varying(255), gb_block character varying(255), gb_address character varying(255), gb_parental integer, gb_parent_id character varying(255), gb_safety_way integer, gb_register_way integer, gb_cert_num character varying(50), gb_certifiable integer, gb_err_code integer, gb_end_time character varying(50), gb_secrecy integer, gb_ip_address character varying(50), gb_port integer, gb_password character varying(50), gb_status character varying(50), gb_longitude double precision, gb_latitude double precision, gb_business_group_id character varying(50), gb_ptz_type integer, gb_position_type integer, gb_room_type integer, gb_use_type integer, gb_supply_light_type integer, gb_direction_type integer, gb_resolution character varying(255), gb_download_speed character varying(255), gb_svc_space_support_mod integer, gb_svc_time_support_mode integer, record_plan_id integer, data_type integer not null, data_device_id integer not null, gps_speed double precision, gps_altitude double precision, gps_direction double precision, constraint uk_wvp_unique_channel unique (gb_device_id) ); create index if not exists data_type on wvp_device_channel (data_type); create index if not exists data_device_id on wvp_device_channel (data_device_id); create table IF NOT EXISTS wvp_device_mobile_position ( id serial primary key, device_id character varying(50) not null, channel_id character varying(50) not null, device_name character varying(255), time character varying(50), longitude double precision, latitude double precision, altitude double precision, speed double precision, direction double precision, report_source character varying(50), create_time character varying(50) ); create table IF NOT EXISTS wvp_media_server ( id character varying(255) primary key, ip character varying(50), hook_ip character varying(50), sdp_ip character varying(50), stream_ip character varying(50), http_port integer, http_ssl_port integer, rtmp_port integer, rtmp_ssl_port integer, rtp_proxy_port integer, rtsp_port integer, rtsp_ssl_port integer, flv_port integer, flv_ssl_port integer, ws_flv_port integer, ws_flv_ssl_port integer, auto_config bool default false, secret character varying(50), type character varying(50) default 'zlm', rtp_enable bool default false, rtp_port_range character varying(50), send_rtp_port_range character varying(50), record_assist_port integer, default_server bool default false, create_time character varying(50), update_time character varying(50), hook_alive_interval integer, record_path character varying(255), record_day integer default 7, transcode_suffix character varying(255), server_id character varying(50), constraint uk_media_server_unique_ip_http_port unique (ip, http_port, server_id) ); create table IF NOT EXISTS wvp_common_group ( id serial primary key, device_id varchar(50) NOT NULL, name varchar(255) NOT NULL, parent_id int, parent_device_id varchar(50) DEFAULT NULL, business_group varchar(50) NOT NULL, create_time varchar(50) NOT NULL, update_time varchar(50) NOT NULL, civil_code varchar(50) default null, constraint uk_common_group_device_platform unique (device_id) ); create table IF NOT EXISTS wvp_common_region ( id serial primary key, device_id varchar(50) NOT NULL, name varchar(255) NOT NULL, parent_id int, parent_device_id varchar(50) DEFAULT NULL, create_time varchar(50) NOT NULL, update_time varchar(50) NOT NULL, constraint uk_common_region_device_id unique (device_id) ); create table IF NOT EXISTS wvp_record_plan ( id serial primary key, snap bool default false, name varchar(255) NOT NULL, create_time character varying(50), update_time character varying(50) ); create table IF NOT EXISTS wvp_record_plan_item ( id serial primary key, "start" int, stop int, week_day int, plan_id int, create_time character varying(50), update_time character varying(50) ); create table IF NOT EXISTS wvp_platform ( id serial primary key, enable bool default false, name character varying(255), server_gb_id character varying(50), server_gb_domain character varying(50), server_ip character varying(50), server_port integer, device_gb_id character varying(50), device_ip character varying(50), device_port character varying(50), username character varying(255), password character varying(50), expires character varying(50), keep_timeout character varying(50), transport character varying(50), civil_code character varying(50), manufacturer character varying(255), model character varying(255), address character varying(255), character_set character varying(50), ptz bool default false, rtcp bool default false, status bool default false, catalog_group integer, register_way integer, secrecy integer, create_time character varying(50), update_time character varying(50), as_message_channel bool default false, catalog_with_platform integer default 1, catalog_with_group integer default 1, catalog_with_region integer default 1, auto_push_channel bool default true, send_stream_ip character varying(50), server_id character varying(50), constraint uk_platform_unique_server_gb_id unique (server_gb_id) ); create table IF NOT EXISTS wvp_platform_channel ( id serial primary key, platform_id integer, device_channel_id integer, custom_device_id character varying(50), custom_name character varying(255), custom_manufacturer character varying(50), custom_model character varying(50), custom_owner character varying(50), custom_civil_code character varying(50), custom_block character varying(50), custom_address character varying(50), custom_parental integer, custom_parent_id character varying(50), custom_safety_way integer, custom_register_way integer, custom_cert_num character varying(50), custom_certifiable integer, custom_err_code integer, custom_end_time character varying(50), custom_secrecy integer, custom_ip_address character varying(50), custom_port integer, custom_password character varying(255), custom_status character varying(50), custom_longitude double precision, custom_latitude double precision, custom_ptz_type integer, custom_position_type integer, custom_room_type integer, custom_use_type integer, custom_supply_light_type integer, custom_direction_type integer, custom_resolution character varying(255), custom_business_group_id character varying(255), custom_download_speed character varying(255), custom_svc_space_support_mod integer, custom_svc_time_support_mode integer, constraint uk_platform_gb_channel_platform_id_catalog_id_device_channel_id unique (platform_id, device_channel_id), constraint uk_platform_gb_channel_device_id unique (custom_device_id) ); create table IF NOT EXISTS wvp_platform_group ( id serial primary key, platform_id integer, group_id integer, constraint uk_wvp_platform_group_platform_id_group_id unique (platform_id, group_id) ); create table IF NOT EXISTS wvp_platform_region ( id serial primary key, platform_id integer, region_id integer, constraint uk_wvp_platform_region_platform_id_group_id unique (platform_id, region_id) ); create table IF NOT EXISTS wvp_stream_proxy ( id serial primary key, type character varying(50), app character varying(255), stream character varying(255), src_url character varying(255), timeout integer, ffmpeg_cmd_key character varying(255), rtsp_type character varying(50), media_server_id character varying(50), enable_audio bool default false, enable_mp4 bool default false, pulling bool default false, enable bool default false, enable_remove_none_reader bool default false, create_time character varying(50), name character varying(255), update_time character varying(50), stream_key character varying(255), server_id character varying(50), enable_disable_none_reader bool default false, relates_media_server_id character varying(50), constraint uk_stream_proxy_app_stream unique (app, stream) ); create table IF NOT EXISTS wvp_stream_push ( id serial primary key, app character varying(255), stream character varying(255), create_time character varying(50), media_server_id character varying(50), server_id character varying(50), push_time character varying(50), status bool default false, update_time character varying(50), pushing bool default false, self bool default false, start_offline_push bool default true, constraint uk_stream_push_app_stream unique (app, stream) ); alter table wvp_cloud_record add column if not exists server_id character varying(50); ALTER TABLE wvp_cloud_record DROP CONSTRAINT IF EXISTS uk_stream_push_app_stream_path; alter table wvp_cloud_record alter folder type varchar(500); alter table wvp_cloud_record alter file_path type varchar(500); update wvp_cloud_record set server_id = '你的服务ID'; /** * 20250414 */ alter table wvp_cloud_record modify time_len double precision; ================================================ FILE: 数据库/2.7.3/更新-postgresql-kingbase-2.7.3.sql ================================================ /* * 20240528 */ ALTER TABLE wvp_media_server ADD COLUMN IF NOT EXISTS transcode_suffix character varying(255); ALTER table wvp_media_server ADD COLUMN IF NOT EXISTS type character varying(50) default 'zlm'; ALTER table wvp_media_server ADD COLUMN IF NOT EXISTS flv_port integer; ALTER table wvp_media_server ADD COLUMN IF NOT EXISTS flv_ssl_port integer; ALTER table wvp_media_server ADD COLUMN IF NOT EXISTS ws_flv_port integer; ALTER table wvp_media_server ADD COLUMN IF NOT EXISTS ws_flv_ssl_port integer; create table IF NOT EXISTS wvp_user_api_key ( id serial primary key , user_id bigint, app character varying(255) , api_key text, expired_at bigint, remark character varying(255), enable bool default true, create_time character varying(50), update_time character varying(50) ); /* * 20241222 */ ALTER TABLE wvp_device_channel drop CONSTRAINT IF EXISTS uk_wvp_device_channel_unique_device_channel; ALTER TABLE wvp_device_channel DROP CONSTRAINT IF EXISTS uk_wvp_unique_stream_push_id; ALTER TABLE wvp_device_channel DROP CONSTRAINT IF EXISTS uk_wvp_unique_stream_proxy_id; ALTER TABLE wvp_device_channel ADD COLUMN IF NOT EXISTS data_type integer not null; ALTER TABLE wvp_device_channel ADD COLUMN IF NOT EXISTS data_device_id integer not null; DO $$ BEGIN IF EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT current_schema()) and table_name = 'wvp_device_channel' and column_name = 'device_db_id') THEN update wvp_device_channel wdc set data_type = 1, data_device_id = (SELECT device_db_id from wvp_device_channel where device_db_id is not null and id = wdc.id ) where device_db_id is not null; END IF; IF EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT current_schema()) and table_name = 'wvp_device_channel' and column_name = 'stream_push_id') THEN update wvp_device_channel wdc set data_type = 2, data_device_id = (SELECT stream_push_id from wvp_device_channel where stream_push_id is not null and id = wdc.id ) where stream_push_id is not null; END IF; IF EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT current_schema()) and table_name = 'wvp_device_channel' and column_name = 'stream_proxy_id') THEN update wvp_device_channel wdc set data_type = 3, data_device_id = (SELECT stream_proxy_id from wvp_device_channel where stream_proxy_id is not null and id = wdc.id ) where stream_proxy_id is not null; END IF; END $$; alter table wvp_device_channel drop column IF EXISTS device_db_id; alter table wvp_device_channel drop column IF EXISTS stream_push_id; alter table wvp_device_channel drop column IF EXISTS stream_proxy_id; /* * 20241231 */ alter table wvp_stream_proxy add column IF NOT EXISTS relates_media_server_id character varying(50); /* * 20250111 */ ALTER TABLE wvp_cloud_record DROP CONSTRAINT IF EXISTS uk_stream_push_app_stream_path; alter table wvp_cloud_record alter folder type varchar(500); alter table wvp_cloud_record alter file_path type varchar(500); /* * 20250211 */ alter table wvp_device rename keepalive_interval_time to heart_beat_interval; alter table wvp_device add column if not exists heart_beat_count integer; alter table wvp_device add column if not exists position_capability integer; /** * 20250312 */ alter table wvp_device add column if not exists server_id character varying(50); alter table wvp_media_server add column if not exists server_id character varying(50); alter table wvp_stream_proxy add column if not exists server_id character varying(50); alter table wvp_cloud_record add column if not exists server_id character varying(50); alter table wvp_platform add column if not exists server_id character varying(50); update wvp_device set server_id = '你的服务ID'; update wvp_media_server set server_id = '你的服务ID'; update wvp_stream_proxy set server_id = '你的服务ID'; update wvp_cloud_record set server_id = '你的服务ID'; /* * 20250319 */ alter table wvp_device_channel add column if not exists gps_speed double precision; alter table wvp_device_channel add column if not exists gps_altitude double precision; alter table wvp_device_channel add column if not exists gps_direction double precision; /* * 20250319 */ update wvp_record_plan_item set start = start * 30, stop = (stop + 1) * 30 /* * 20250402 */ create index if not exists data_type on wvp_device_channel (data_type); create index if not exists data_device_id on wvp_device_channel (data_device_id); /** * 20250414 */ alter table wvp_cloud_record modify time_len double precision; ================================================ FILE: 数据库/2.7.4/初始化-mysql-2.7.4.sql ================================================ /*建表*/ -- 存储国标设备的基础信息及在线状态 drop table IF EXISTS wvp_device; create table IF NOT EXISTS wvp_device ( id serial primary key COMMENT '主键ID', device_id character varying(50) not null COMMENT '国标设备编号', name character varying(255) COMMENT '设备名称', manufacturer character varying(255) COMMENT '设备厂商', model character varying(255) COMMENT '设备型号', firmware character varying(255) COMMENT '固件版本号', transport character varying(50) COMMENT '信令传输协议(TCP/UDP)', stream_mode character varying(50) COMMENT '拉流方式(主动/被动)', on_line bool default false COMMENT '在线状态', register_time character varying(50) COMMENT '注册时间', keepalive_time character varying(50) COMMENT '最近心跳时间', ip character varying(50) COMMENT '设备IP地址', create_time character varying(50) COMMENT '创建时间', update_time character varying(50) COMMENT '更新时间', port integer COMMENT '信令端口', expires integer COMMENT '注册有效期', subscribe_cycle_for_catalog integer DEFAULT 0 COMMENT '目录订阅周期', subscribe_cycle_for_mobile_position integer DEFAULT 0 COMMENT '移动位置订阅周期', mobile_position_submission_interval integer DEFAULT 5 COMMENT '移动位置上报间隔', subscribe_cycle_for_alarm integer DEFAULT 0 COMMENT '报警订阅周期', host_address character varying(50) COMMENT '设备域名/主机地址', charset character varying(50) COMMENT '信令字符集', ssrc_check bool default false COMMENT '是否校验SSRC', geo_coord_sys character varying(50) COMMENT '坐标系类型', media_server_id character varying(50) default 'auto' COMMENT '绑定的流媒体服务ID', custom_name character varying(255) COMMENT '自定义显示名称', sdp_ip character varying(50) COMMENT 'SDP中携带的IP', local_ip character varying(50) COMMENT '本地局域网IP', password character varying(255) COMMENT '设备鉴权密码', as_message_channel bool default false COMMENT '是否作为消息通道', heart_beat_interval integer COMMENT '心跳间隔', heart_beat_count integer COMMENT '心跳失败次数', position_capability integer COMMENT '定位能力标识', broadcast_push_after_ack bool default false COMMENT 'ACK后是否自动推流', server_id character varying(50) COMMENT '所属信令服务器ID', constraint uk_device_device unique (device_id) ); -- 记录各设备上报的报警信息 drop table IF EXISTS wvp_device_alarm; create table IF NOT EXISTS wvp_device_alarm ( id serial primary key COMMENT '主键ID', device_id character varying(50) not null COMMENT '国标设备ID', channel_id character varying(50) not null COMMENT '报警关联的通道ID', alarm_priority character varying(50) COMMENT '报警级别', alarm_method character varying(50) COMMENT '报警方式(视频/语音等)', alarm_time character varying(50) COMMENT '报警发生时间', alarm_description character varying(255) COMMENT '报警描述', longitude double precision COMMENT '报警经度', latitude double precision COMMENT '报警纬度', alarm_type character varying(50) COMMENT '报警类型', create_time character varying(50) not null COMMENT '数据入库时间' ); -- 存储移动位置订阅上报的数据 drop table IF EXISTS wvp_device_mobile_position; create table IF NOT EXISTS wvp_device_mobile_position ( id serial primary key COMMENT '主键ID', device_id character varying(50) not null COMMENT '设备ID', channel_id character varying(50) not null COMMENT '通道ID', device_name character varying(255) COMMENT '设备名称', time character varying(50) COMMENT '上报时间', longitude double precision COMMENT '经度', latitude double precision COMMENT '纬度', altitude double precision COMMENT '海拔', speed double precision COMMENT '速度', direction double precision COMMENT '方向角', report_source character varying(50) COMMENT '上报来源', create_time character varying(50) COMMENT '入库时间' ); -- 保存设备下的通道信息以及扩展属性 drop table IF EXISTS wvp_device_channel; create table IF NOT EXISTS wvp_device_channel ( id serial primary key COMMENT '主键ID', device_id character varying(50) COMMENT '所属设备ID', name character varying(255) COMMENT '通道名称', manufacturer character varying(50) COMMENT '厂商', model character varying(50) COMMENT '型号', owner character varying(50) COMMENT '归属单位', civil_code character varying(50) COMMENT '行政区划代码', block character varying(50) COMMENT '区域/小区编号', address character varying(50) COMMENT '安装地址', parental integer COMMENT '是否有子节点', parent_id character varying(50) COMMENT '父级通道ID', safety_way integer COMMENT '安全防范等级', register_way integer COMMENT '注册方式', cert_num character varying(50) COMMENT '证书编号', certifiable integer COMMENT '是否可认证', err_code integer COMMENT '故障状态码', end_time character varying(50) COMMENT '服务截止时间', secrecy integer COMMENT '保密级别', ip_address character varying(50) COMMENT '设备IP地址', port integer COMMENT '设备端口', password character varying(255) COMMENT '访问密码', status character varying(50) COMMENT '在线状态', longitude double precision COMMENT '经度', latitude double precision COMMENT '纬度', ptz_type integer COMMENT '云台类型', position_type integer COMMENT '点位类型', room_type integer COMMENT '房间类型', use_type integer COMMENT '使用性质', supply_light_type integer COMMENT '补光方式', direction_type integer COMMENT '朝向', resolution character varying(255) COMMENT '分辨率', business_group_id character varying(255) COMMENT '业务分组ID', download_speed character varying(255) COMMENT '下载/码流速率', svc_space_support_mod integer COMMENT '空域SVC能力', svc_time_support_mode integer COMMENT '时域SVC能力', create_time character varying(50) not null COMMENT '创建时间', update_time character varying(50) not null COMMENT '更新时间', sub_count integer COMMENT '子节点数量', stream_id character varying(255) COMMENT '绑定的流ID', has_audio bool default false COMMENT '是否有音频', gps_time character varying(50) COMMENT 'GPS定位时间', stream_identification character varying(50) COMMENT '流标识', channel_type int default 0 not null COMMENT '通道类型', map_level int default 0 COMMENT '地图层级', gb_device_id character varying(50) COMMENT 'GB内的设备ID', gb_name character varying(255) COMMENT 'GB上报的名称', gb_manufacturer character varying(255) COMMENT 'GB厂商', gb_model character varying(255) COMMENT 'GB型号', gb_owner character varying(255) COMMENT 'GB归属', gb_civil_code character varying(255) COMMENT 'GB行政区划', gb_block character varying(255) COMMENT 'GB区域', gb_address character varying(255) COMMENT 'GB地址', gb_parental integer COMMENT 'GB子节点标识', gb_parent_id character varying(255) COMMENT 'GB父通道', gb_safety_way integer COMMENT 'GB安全防范', gb_register_way integer COMMENT 'GB注册方式', gb_cert_num character varying(50) COMMENT 'GB证书编号', gb_certifiable integer COMMENT 'GB认证标志', gb_err_code integer COMMENT 'GB错误码', gb_end_time character varying(50) COMMENT 'GB截止时间', gb_secrecy integer COMMENT 'GB保密级别', gb_ip_address character varying(50) COMMENT 'GB IP', gb_port integer COMMENT 'GB端口', gb_password character varying(50) COMMENT 'GB接入密码', gb_status character varying(50) COMMENT 'GB状态', gb_longitude double COMMENT 'GB经度', gb_latitude double COMMENT 'GB纬度', gb_business_group_id character varying(50) COMMENT 'GB业务分组', gb_ptz_type integer COMMENT 'GB云台类型', gb_position_type integer COMMENT 'GB点位类型', gb_room_type integer COMMENT 'GB房间类型', gb_use_type integer COMMENT 'GB用途', gb_supply_light_type integer COMMENT 'GB补光', gb_direction_type integer COMMENT 'GB朝向', gb_resolution character varying(255) COMMENT 'GB分辨率', gb_download_speed character varying(255) COMMENT 'GB码流速率', gb_svc_space_support_mod integer COMMENT 'GB空域SVC', gb_svc_time_support_mode integer COMMENT 'GB时域SVC', record_plan_id integer COMMENT '绑定的录像计划ID', data_type integer not null COMMENT '数据类型标识', data_device_id integer not null COMMENT '数据来源设备主键', gps_speed double precision COMMENT 'GPS速度', gps_altitude double precision COMMENT 'GPS海拔', gps_direction double precision COMMENT 'GPS方向', enable_broadcast integer default 0 COMMENT '是否支持广播', index (data_type), index (data_device_id), constraint uk_wvp_unique_channel unique (gb_device_id) ); -- 媒体服务器(如 ZLM)节点信息 drop table IF EXISTS wvp_media_server; create table IF NOT EXISTS wvp_media_server ( id character varying(255) primary key COMMENT '媒体服务器ID', ip character varying(50) COMMENT '服务器IP', hook_ip character varying(50) COMMENT 'hook回调IP', sdp_ip character varying(50) COMMENT 'SDP中使用的IP', stream_ip character varying(50) COMMENT '推流使用的IP', http_port integer COMMENT 'HTTP端口', http_ssl_port integer COMMENT 'HTTPS端口', rtmp_port integer COMMENT 'RTMP端口', rtmp_ssl_port integer COMMENT 'RTMPS端口', rtp_proxy_port integer COMMENT 'RTP代理端口', rtsp_port integer COMMENT 'RTSP端口', rtsp_ssl_port integer COMMENT 'RTSPS端口', flv_port integer COMMENT 'FLV端口', flv_ssl_port integer COMMENT 'FLV HTTPS端口', mp4_port integer COMMENT 'MP4点播端口', mp4_ssl_port integer COMMENT 'MP4 HTTPS端口', ws_flv_port integer COMMENT 'WS-FLV端口', ws_flv_ssl_port integer COMMENT 'WS-FLV HTTPS端口', jtt_proxy_port integer COMMENT 'JT/T代理端口', auto_config bool default false COMMENT '是否自动配置', secret character varying(50) COMMENT 'ZLM校验密钥', type character varying(50) default 'zlm' COMMENT '节点类型', rtp_enable bool default false COMMENT '是否开启RTP', rtp_port_range character varying(50) COMMENT 'RTP端口范围', send_rtp_port_range character varying(50) COMMENT '发送RTP端口范围', record_assist_port integer COMMENT '录像辅助端口', default_server bool default false COMMENT '是否默认节点', create_time character varying(50) COMMENT '创建时间', update_time character varying(50) COMMENT '更新时间', hook_alive_interval integer COMMENT 'hook心跳间隔', record_path character varying(255) COMMENT '录像目录', record_day integer default 7 COMMENT '录像保留天数', transcode_suffix character varying(255) COMMENT '转码指令后缀', server_id character varying(50) COMMENT '对应信令服务器ID', constraint uk_media_server_unique_ip_http_port unique (ip, http_port, server_id) ); -- 上级国标平台注册信息 drop table IF EXISTS wvp_platform; create table IF NOT EXISTS wvp_platform ( id serial primary key COMMENT '主键ID', enable bool default false COMMENT '是否启用该平台注册', name character varying(255) COMMENT '平台名称', server_gb_id character varying(50) COMMENT '上级平台国标编码', server_gb_domain character varying(50) COMMENT '上级平台域编码', server_ip character varying(50) COMMENT '上级平台IP', server_port integer COMMENT '上级平台注册端口', device_gb_id character varying(50) COMMENT '本平台向上注册的国标编码', device_ip character varying(50) COMMENT '本平台信令IP', device_port character varying(50) COMMENT '本平台信令端口', username character varying(255) COMMENT '注册用户名', password character varying(50) COMMENT '注册密码', expires character varying(50) COMMENT '注册有效期', keep_timeout character varying(50) COMMENT '心跳超时时间', transport character varying(50) COMMENT '传输协议(UDP/TCP)', civil_code character varying(50) COMMENT '行政区划代码', manufacturer character varying(255) COMMENT '厂商', model character varying(255) COMMENT '型号', address character varying(255) COMMENT '地址', character_set character varying(50) COMMENT '字符集', ptz bool default false COMMENT '是否支持PTZ', rtcp bool default false COMMENT '是否开启RTCP', status bool default false COMMENT '注册状态', catalog_group integer COMMENT '目录分组方式', register_way integer COMMENT '注册方式', secrecy integer COMMENT '保密级别', create_time character varying(50) COMMENT '创建时间', update_time character varying(50) COMMENT '更新时间', as_message_channel bool default false COMMENT '是否作为消息通道', catalog_with_platform integer default 1 COMMENT '是否推送平台目录', catalog_with_group integer default 1 COMMENT '是否推送分组目录', catalog_with_region integer default 1 COMMENT '是否推送区域目录', auto_push_channel bool default true COMMENT '是否自动推送通道', send_stream_ip character varying(50) COMMENT '推流时使用的IP', server_id character varying(50) COMMENT '对应信令服务器ID', constraint uk_platform_unique_server_gb_id unique (server_gb_id) ); -- 国标平台下发的通道映射关系 drop table IF EXISTS wvp_platform_channel; create table IF NOT EXISTS wvp_platform_channel ( id serial primary key COMMENT '主键ID', platform_id integer COMMENT '平台ID', device_channel_id integer COMMENT '本地通道表主键', custom_device_id character varying(50) COMMENT '自定义国标编码', custom_name character varying(255) COMMENT '自定义名称', custom_manufacturer character varying(50) COMMENT '自定义厂商', custom_model character varying(50) COMMENT '自定义型号', custom_owner character varying(50) COMMENT '自定义归属', custom_civil_code character varying(50) COMMENT '自定义行政区划', custom_block character varying(50) COMMENT '自定义区域', custom_address character varying(50) COMMENT '自定义地址', custom_parental integer COMMENT '自定义父/子标识', custom_parent_id character varying(50) COMMENT '自定义父节点', custom_safety_way integer COMMENT '自定义安全防范', custom_register_way integer COMMENT '自定义注册方式', custom_cert_num character varying(50) COMMENT '自定义证书编号', custom_certifiable integer COMMENT '自定义可认证标志', custom_err_code integer COMMENT '自定义错误码', custom_end_time character varying(50) COMMENT '自定义截止时间', custom_secrecy integer COMMENT '自定义保密级别', custom_ip_address character varying(50) COMMENT '自定义IP', custom_port integer COMMENT '自定义端口', custom_password character varying(255) COMMENT '自定义密码', custom_status character varying(50) COMMENT '自定义状态', custom_longitude double precision COMMENT '自定义经度', custom_latitude double precision COMMENT '自定义纬度', custom_ptz_type integer COMMENT '自定义云台类型', custom_position_type integer COMMENT '自定义点位类型', custom_room_type integer COMMENT '自定义房间类型', custom_use_type integer COMMENT '自定义用途', custom_supply_light_type integer COMMENT '自定义补光', custom_direction_type integer COMMENT '自定义朝向', custom_resolution character varying(255) COMMENT '自定义分辨率', custom_business_group_id character varying(255) COMMENT '自定义业务分组', custom_download_speed character varying(255) COMMENT '自定义码流速率', custom_svc_space_support_mod integer COMMENT '自定义空域SVC', custom_svc_time_support_mode integer COMMENT '自定义时域SVC', constraint uk_platform_gb_channel_platform_id_catalog_id_device_channel_id unique (platform_id, device_channel_id), constraint uk_platform_gb_channel_device_id unique (custom_device_id) ); -- 平台与分组(行政区划/组织)关系 drop table IF EXISTS wvp_platform_group; create table IF NOT EXISTS wvp_platform_group ( id serial primary key COMMENT '主键ID', platform_id integer COMMENT '平台ID', group_id integer COMMENT '分组ID', constraint uk_wvp_platform_group_platform_id_group_id unique (platform_id, group_id) ); -- 平台与区域关系 drop table IF EXISTS wvp_platform_region; create table IF NOT EXISTS wvp_platform_region ( id serial primary key COMMENT '主键ID', platform_id integer COMMENT '平台ID', region_id integer COMMENT '区域ID', constraint uk_wvp_platform_region_platform_id_group_id unique (platform_id, region_id) ); -- 拉流代理/转推配置 drop table IF EXISTS wvp_stream_proxy; create table IF NOT EXISTS wvp_stream_proxy ( id serial primary key COMMENT '主键ID', type character varying(50) COMMENT '代理类型(拉流/推流)', app character varying(255) COMMENT '应用名', stream character varying(255) COMMENT '流ID', src_url character varying(255) COMMENT '源地址', timeout integer COMMENT '拉流超时时间', ffmpeg_cmd_key character varying(255) COMMENT 'FFmpeg命令模板键', rtsp_type character varying(50) COMMENT 'RTSP拉流方式', media_server_id character varying(50) COMMENT '指定媒体服务器ID', enable_audio bool default false COMMENT '是否启用音频', enable_mp4 bool default false COMMENT '是否录制MP4', pulling bool default false COMMENT '当前是否在拉流', enable bool default false COMMENT '是否启用该代理', create_time character varying(50) COMMENT '创建时间', name character varying(255) COMMENT '代理名称', update_time character varying(50) COMMENT '更新时间', stream_key character varying(255) COMMENT '唯一流标识', server_id character varying(50) COMMENT '信令服务器ID', enable_disable_none_reader bool default false COMMENT '是否无人观看时自动停流', relates_media_server_id character varying(50) COMMENT '关联的媒体服务器ID', constraint uk_stream_proxy_app_stream unique (app, stream) ); -- 推流会话记录 drop table IF EXISTS wvp_stream_push; create table IF NOT EXISTS wvp_stream_push ( id serial primary key COMMENT '主键ID', app character varying(255) COMMENT '应用名', stream character varying(255) COMMENT '流ID', create_time character varying(50) COMMENT '创建时间', media_server_id character varying(50) COMMENT '推流所在媒体服务器', server_id character varying(50) COMMENT '信令服务器ID', push_time character varying(50) COMMENT '推流开始时间', status bool default false COMMENT '推流状态', update_time character varying(50) COMMENT '更新时间', pushing bool default false COMMENT '是否正在推流', self bool default false COMMENT '是否本地发起', start_offline_push bool default true COMMENT '是否离线后自动重推', constraint uk_stream_push_app_stream unique (app, stream) ); -- 云端录像记录 drop table IF EXISTS wvp_cloud_record; create table IF NOT EXISTS wvp_cloud_record ( id serial primary key COMMENT '主键ID', app character varying(255) COMMENT '应用名', stream character varying(255) COMMENT '流ID', call_id character varying(255) COMMENT '会话ID', start_time bigint COMMENT '录像开始时间', end_time bigint COMMENT '录像结束时间', media_server_id character varying(50) COMMENT '媒体服务器ID', server_id character varying(50) COMMENT '信令服务器ID', file_name character varying(255) COMMENT '文件名', folder character varying(500) COMMENT '目录', file_path character varying(500) COMMENT '完整路径', collect bool default false COMMENT '是否收藏', file_size bigint COMMENT '文件大小', time_len double precision COMMENT '时长' ); -- 平台用户信息 drop table IF EXISTS wvp_user; create table IF NOT EXISTS wvp_user ( id serial primary key COMMENT '主键ID', username character varying(255) COMMENT '用户名', password character varying(255) COMMENT '密码(MD5)', role_id integer COMMENT '角色ID', create_time character varying(50) COMMENT '创建时间', update_time character varying(50) COMMENT '更新时间', push_key character varying(50) COMMENT '推送密钥', constraint uk_user_username unique (username) ); -- 用户角色信息 drop table IF EXISTS wvp_user_role; create table IF NOT EXISTS wvp_user_role ( id serial primary key COMMENT '主键ID', name character varying(50) COMMENT '角色名称', authority character varying(50) COMMENT '权限标识', create_time character varying(50) COMMENT '创建时间', update_time character varying(50) COMMENT '更新时间' ); drop table IF EXISTS wvp_user_api_key; create table IF NOT EXISTS wvp_user_api_key ( id serial primary key COMMENT '主键ID', user_id bigint COMMENT '关联用户ID', app character varying(255) COMMENT '应用标识', api_key text COMMENT 'API Key', expired_at bigint COMMENT '过期时间戳', remark character varying(255) COMMENT '备注', enable bool default true COMMENT '是否启用', create_time character varying(50) COMMENT '创建时间', update_time character varying(50) COMMENT '更新时间' ); /*初始数据*/ -- 初始化管理员账号,账号admin 密码admin(MD5加密后) INSERT INTO wvp_user VALUES (1, 'admin', '21232f297a57a5a743894a0e4a801fc3', 1, '2021-04-13 14:14:57', '2021-04-13 14:14:57', '3e80d1762a324d5b0ff636e0bd16f1e3'); -- 初始化管理员角色 INSERT INTO wvp_user_role VALUES (1, 'admin', '0', '2021-04-13 14:14:57', '2021-04-13 14:14:57'); -- 通用分组表,存储行业或组织结构 drop table IF EXISTS wvp_common_group; create table IF NOT EXISTS wvp_common_group ( id serial primary key COMMENT '主键ID', device_id varchar(50) NOT NULL COMMENT '分组对应的平台或设备ID', name varchar(255) NOT NULL COMMENT '分组名称', parent_id int COMMENT '父级分组ID', parent_device_id varchar(50) DEFAULT NULL COMMENT '父级分组对应的设备ID', business_group varchar(50) NOT NULL COMMENT '业务分组编码', create_time varchar(50) NOT NULL COMMENT '创建时间', update_time varchar(50) NOT NULL COMMENT '更新时间', civil_code varchar(50) default null COMMENT '行政区划代码', alias varchar(255) default null COMMENT '别名', constraint uk_common_group_device_platform unique (device_id) ); -- 通用行政区域表 drop table IF EXISTS wvp_common_region; create table IF NOT EXISTS wvp_common_region ( id serial primary key COMMENT '主键ID', device_id varchar(50) NOT NULL COMMENT '区域对应的平台或设备ID', name varchar(255) NOT NULL COMMENT '区域名称', parent_id int COMMENT '父级区域ID', parent_device_id varchar(50) DEFAULT NULL COMMENT '父级区域的设备ID', create_time varchar(50) NOT NULL COMMENT '创建时间', update_time varchar(50) NOT NULL COMMENT '更新时间', constraint uk_common_region_device_id unique (device_id) ); -- 录像计划基础信息 drop table IF EXISTS wvp_record_plan; create table IF NOT EXISTS wvp_record_plan ( id serial primary key COMMENT '主键ID', snap bool default false COMMENT '是否抓图计划', name varchar(255) NOT NULL COMMENT '计划名称', create_time character varying(50) COMMENT '创建时间', update_time character varying(50) COMMENT '更新时间' ); -- 录像计划条目表 drop table IF EXISTS wvp_record_plan_item; create table IF NOT EXISTS wvp_record_plan_item ( id serial primary key COMMENT '主键ID', start int COMMENT '开始时间(分钟)', stop int COMMENT '结束时间(分钟)', week_day int COMMENT '星期(0-6)', plan_id int COMMENT '所属录像计划ID', create_time character varying(50) COMMENT '创建时间', update_time character varying(50) COMMENT '更新时间' ); -- 交通部 JT/T 1076 终端信息 drop table IF EXISTS wvp_jt_terminal; create table IF NOT EXISTS wvp_jt_terminal ( id serial primary key COMMENT '主键ID', phone_number character varying(50) COMMENT '终端SIM卡号', terminal_id character varying(50) COMMENT '终端设备ID', province_id character varying(50) COMMENT '所在省份ID', province_text character varying(100) COMMENT '所在省份名称', city_id character varying(50) COMMENT '所在城市ID', city_text character varying(100) COMMENT '所在城市名称', maker_id character varying(50) COMMENT '厂商ID', model character varying(50) COMMENT '终端型号', plate_color character varying(50) COMMENT '车牌颜色', plate_no character varying(50) COMMENT '车牌号码', longitude double precision COMMENT '经度', latitude double precision COMMENT '纬度', status bool default false COMMENT '在线状态', register_time character varying(50) default null COMMENT '注册时间', update_time character varying(50) not null COMMENT '更新时间', create_time character varying(50) not null COMMENT '创建时间', geo_coord_sys character varying(50) COMMENT '坐标系', media_server_id character varying(50) default 'auto' COMMENT '媒体服务器ID', sdp_ip character varying(50) COMMENT 'SDP IP', constraint uk_jt_device_id_device_id unique (id, phone_number) ); -- 交通部 JT/T 1076 通道信息 drop table IF EXISTS wvp_jt_channel; create table IF NOT EXISTS wvp_jt_channel ( id serial primary key COMMENT '主键ID', terminal_db_id integer COMMENT '所属终端记录ID', channel_id integer COMMENT '通道号', has_audio bool default false COMMENT '是否有音频', name character varying(255) COMMENT '通道名称', update_time character varying(50) not null COMMENT '更新时间', create_time character varying(50) not null COMMENT '创建时间', constraint uk_jt_channel_id_device_id unique (terminal_db_id, channel_id) ); ================================================ FILE: 数据库/2.7.4/初始化-postgresql-kingbase-2.7.4.sql ================================================ /*建表*/ drop table IF EXISTS wvp_device; create table IF NOT EXISTS wvp_device ( id serial primary key, device_id character varying(50) not null, name character varying(255), manufacturer character varying(255), model character varying(255), firmware character varying(255), transport character varying(50), stream_mode character varying(50), on_line bool default false, register_time character varying(50), keepalive_time character varying(50), ip character varying(50), create_time character varying(50), update_time character varying(50), port integer, expires integer, subscribe_cycle_for_catalog integer DEFAULT 0, subscribe_cycle_for_mobile_position integer DEFAULT 0, mobile_position_submission_interval integer DEFAULT 5, subscribe_cycle_for_alarm integer DEFAULT 0, host_address character varying(50), charset character varying(50), ssrc_check bool default false, geo_coord_sys character varying(50), media_server_id character varying(50) default 'auto', custom_name character varying(255), sdp_ip character varying(50), local_ip character varying(50), password character varying(255), as_message_channel bool default false, heart_beat_interval integer, heart_beat_count integer, position_capability integer, broadcast_push_after_ack bool default false, server_id character varying(50), constraint uk_device_device unique (device_id) ); COMMENT ON TABLE wvp_device IS '存储国标设备的基础信息及在线状态'; COMMENT ON COLUMN wvp_device.id IS '主键ID'; COMMENT ON COLUMN wvp_device.device_id IS '国标设备编号'; COMMENT ON COLUMN wvp_device.name IS '设备名称'; COMMENT ON COLUMN wvp_device.manufacturer IS '设备厂商'; COMMENT ON COLUMN wvp_device.model IS '设备型号'; COMMENT ON COLUMN wvp_device.firmware IS '固件版本号'; COMMENT ON COLUMN wvp_device.transport IS '信令传输协议(TCP/UDP)'; COMMENT ON COLUMN wvp_device.stream_mode IS '拉流方式(主动/被动)'; COMMENT ON COLUMN wvp_device.on_line IS '在线状态'; COMMENT ON COLUMN wvp_device.register_time IS '注册时间'; COMMENT ON COLUMN wvp_device.keepalive_time IS '最近心跳时间'; COMMENT ON COLUMN wvp_device.ip IS '设备IP地址'; COMMENT ON COLUMN wvp_device.create_time IS '创建时间'; COMMENT ON COLUMN wvp_device.update_time IS '更新时间'; COMMENT ON COLUMN wvp_device.port IS '信令端口'; COMMENT ON COLUMN wvp_device.expires IS '注册有效期'; COMMENT ON COLUMN wvp_device.subscribe_cycle_for_catalog IS '目录订阅周期'; COMMENT ON COLUMN wvp_device.subscribe_cycle_for_mobile_position IS '移动位置订阅周期'; COMMENT ON COLUMN wvp_device.mobile_position_submission_interval IS '移动位置上报间隔'; COMMENT ON COLUMN wvp_device.subscribe_cycle_for_alarm IS '报警订阅周期'; COMMENT ON COLUMN wvp_device.host_address IS '设备域名/主机地址'; COMMENT ON COLUMN wvp_device.charset IS '信令字符集'; COMMENT ON COLUMN wvp_device.ssrc_check IS '是否校验SSRC'; COMMENT ON COLUMN wvp_device.geo_coord_sys IS '坐标系类型'; COMMENT ON COLUMN wvp_device.media_server_id IS '绑定的流媒体服务ID'; COMMENT ON COLUMN wvp_device.custom_name IS '自定义显示名称'; COMMENT ON COLUMN wvp_device.sdp_ip IS 'SDP中携带的IP'; COMMENT ON COLUMN wvp_device.local_ip IS '本地局域网IP'; COMMENT ON COLUMN wvp_device.password IS '设备鉴权密码'; COMMENT ON COLUMN wvp_device.as_message_channel IS '是否作为消息通道'; COMMENT ON COLUMN wvp_device.heart_beat_interval IS '心跳间隔'; COMMENT ON COLUMN wvp_device.heart_beat_count IS '心跳失败次数'; COMMENT ON COLUMN wvp_device.position_capability IS '定位能力标识'; COMMENT ON COLUMN wvp_device.broadcast_push_after_ack IS 'ACK后是否自动推流'; COMMENT ON COLUMN wvp_device.server_id IS '所属信令服务器ID'; drop table IF EXISTS wvp_device_alarm; create table IF NOT EXISTS wvp_device_alarm ( id serial primary key, device_id character varying(50) not null, channel_id character varying(50) not null, alarm_priority character varying(50), alarm_method character varying(50), alarm_time character varying(50), alarm_description character varying(255), longitude double precision, latitude double precision, alarm_type character varying(50), create_time character varying(50) not null ); COMMENT ON TABLE wvp_device_alarm IS '记录各设备上报的报警信息'; COMMENT ON COLUMN wvp_device_alarm.id IS '主键ID'; COMMENT ON COLUMN wvp_device_alarm.device_id IS '国标设备ID'; COMMENT ON COLUMN wvp_device_alarm.channel_id IS '报警关联的通道ID'; COMMENT ON COLUMN wvp_device_alarm.alarm_priority IS '报警级别'; COMMENT ON COLUMN wvp_device_alarm.alarm_method IS '报警方式(视频/语音等)'; COMMENT ON COLUMN wvp_device_alarm.alarm_time IS '报警发生时间'; COMMENT ON COLUMN wvp_device_alarm.alarm_description IS '报警描述'; COMMENT ON COLUMN wvp_device_alarm.longitude IS '报警经度'; COMMENT ON COLUMN wvp_device_alarm.latitude IS '报警纬度'; COMMENT ON COLUMN wvp_device_alarm.alarm_type IS '报警类型'; COMMENT ON COLUMN wvp_device_alarm.create_time IS '数据入库时间'; drop table IF EXISTS wvp_device_mobile_position; create table IF NOT EXISTS wvp_device_mobile_position ( id serial primary key, device_id character varying(50) not null, channel_id character varying(50) not null, device_name character varying(255), time character varying(50), longitude double precision, latitude double precision, altitude double precision, speed double precision, direction double precision, report_source character varying(50), create_time character varying(50) ); COMMENT ON TABLE wvp_device_mobile_position IS '存储移动位置订阅上报的数据'; COMMENT ON COLUMN wvp_device_mobile_position.id IS '主键ID'; COMMENT ON COLUMN wvp_device_mobile_position.device_id IS '设备ID'; COMMENT ON COLUMN wvp_device_mobile_position.channel_id IS '通道ID'; COMMENT ON COLUMN wvp_device_mobile_position.device_name IS '设备名称'; COMMENT ON COLUMN wvp_device_mobile_position.time IS '上报时间'; COMMENT ON COLUMN wvp_device_mobile_position.longitude IS '经度'; COMMENT ON COLUMN wvp_device_mobile_position.latitude IS '纬度'; COMMENT ON COLUMN wvp_device_mobile_position.altitude IS '海拔'; COMMENT ON COLUMN wvp_device_mobile_position.speed IS '速度'; COMMENT ON COLUMN wvp_device_mobile_position.direction IS '方向角'; COMMENT ON COLUMN wvp_device_mobile_position.report_source IS '上报来源'; COMMENT ON COLUMN wvp_device_mobile_position.create_time IS '入库时间'; drop table IF EXISTS wvp_device_channel; create table IF NOT EXISTS wvp_device_channel ( id serial primary key, device_id character varying(50), name character varying(255), manufacturer character varying(50), model character varying(50), owner character varying(50), civil_code character varying(50), block character varying(50), address character varying(50), parental integer, parent_id character varying(50), safety_way integer, register_way integer, cert_num character varying(50), certifiable integer, err_code integer, end_time character varying(50), secrecy integer, ip_address character varying(50), port integer, password character varying(255), status character varying(50), longitude double precision, latitude double precision, ptz_type integer, position_type integer, room_type integer, use_type integer, supply_light_type integer, direction_type integer, resolution character varying(255), business_group_id character varying(255), download_speed character varying(255), svc_space_support_mod integer, svc_time_support_mode integer, create_time character varying(50) not null, update_time character varying(50) not null, sub_count integer, stream_id character varying(255), has_audio bool default false, gps_time character varying(50), stream_identification character varying(50), channel_type int default 0 not null, map_level int default 0, gb_device_id character varying(50), gb_name character varying(255), gb_manufacturer character varying(255), gb_model character varying(255), gb_owner character varying(255), gb_civil_code character varying(255), gb_block character varying(255), gb_address character varying(255), gb_parental integer, gb_parent_id character varying(255), gb_safety_way integer, gb_register_way integer, gb_cert_num character varying(50), gb_certifiable integer, gb_err_code integer, gb_end_time character varying(50), gb_secrecy integer, gb_ip_address character varying(50), gb_port integer, gb_password character varying(50), gb_status character varying(50), gb_longitude double precision, gb_latitude double precision, gb_business_group_id character varying(50), gb_ptz_type integer, gb_position_type integer, gb_room_type integer, gb_use_type integer, gb_supply_light_type integer, gb_direction_type integer, gb_resolution character varying(255), gb_download_speed character varying(255), gb_svc_space_support_mod integer, gb_svc_time_support_mode integer, record_plan_id integer, data_type integer not null, data_device_id integer not null, gps_speed double precision, gps_altitude double precision, gps_direction double precision, enable_broadcast integer default 0, constraint uk_wvp_unique_channel unique (gb_device_id) ); COMMENT ON TABLE wvp_device_channel IS '保存设备下的通道信息以及扩展属性'; COMMENT ON COLUMN wvp_device_channel.id IS '主键ID'; COMMENT ON COLUMN wvp_device_channel.device_id IS '所属设备ID'; COMMENT ON COLUMN wvp_device_channel.name IS '通道名称'; COMMENT ON COLUMN wvp_device_channel.manufacturer IS '厂商'; COMMENT ON COLUMN wvp_device_channel.model IS '型号'; COMMENT ON COLUMN wvp_device_channel.owner IS '归属单位'; COMMENT ON COLUMN wvp_device_channel.civil_code IS '行政区划代码'; COMMENT ON COLUMN wvp_device_channel.block IS '区域/小区编号'; COMMENT ON COLUMN wvp_device_channel.address IS '安装地址'; COMMENT ON COLUMN wvp_device_channel.parental IS '是否有子节点'; COMMENT ON COLUMN wvp_device_channel.parent_id IS '父级通道ID'; COMMENT ON COLUMN wvp_device_channel.safety_way IS '安全防范等级'; COMMENT ON COLUMN wvp_device_channel.register_way IS '注册方式'; COMMENT ON COLUMN wvp_device_channel.cert_num IS '证书编号'; COMMENT ON COLUMN wvp_device_channel.certifiable IS '是否可认证'; COMMENT ON COLUMN wvp_device_channel.err_code IS '故障状态码'; COMMENT ON COLUMN wvp_device_channel.end_time IS '服务截止时间'; COMMENT ON COLUMN wvp_device_channel.secrecy IS '保密级别'; COMMENT ON COLUMN wvp_device_channel.ip_address IS '设备IP地址'; COMMENT ON COLUMN wvp_device_channel.port IS '设备端口'; COMMENT ON COLUMN wvp_device_channel.password IS '访问密码'; COMMENT ON COLUMN wvp_device_channel.status IS '在线状态'; COMMENT ON COLUMN wvp_device_channel.longitude IS '经度'; COMMENT ON COLUMN wvp_device_channel.latitude IS '纬度'; COMMENT ON COLUMN wvp_device_channel.ptz_type IS '云台类型'; COMMENT ON COLUMN wvp_device_channel.position_type IS '点位类型'; COMMENT ON COLUMN wvp_device_channel.room_type IS '房间类型'; COMMENT ON COLUMN wvp_device_channel.use_type IS '使用性质'; COMMENT ON COLUMN wvp_device_channel.supply_light_type IS '补光方式'; COMMENT ON COLUMN wvp_device_channel.direction_type IS '朝向'; COMMENT ON COLUMN wvp_device_channel.resolution IS '分辨率'; COMMENT ON COLUMN wvp_device_channel.business_group_id IS '业务分组ID'; COMMENT ON COLUMN wvp_device_channel.download_speed IS '下载/码流速率'; COMMENT ON COLUMN wvp_device_channel.svc_space_support_mod IS '空域SVC能力'; COMMENT ON COLUMN wvp_device_channel.svc_time_support_mode IS '时域SVC能力'; COMMENT ON COLUMN wvp_device_channel.create_time IS '创建时间'; COMMENT ON COLUMN wvp_device_channel.update_time IS '更新时间'; COMMENT ON COLUMN wvp_device_channel.sub_count IS '子节点数量'; COMMENT ON COLUMN wvp_device_channel.stream_id IS '绑定的流ID'; COMMENT ON COLUMN wvp_device_channel.has_audio IS '是否有音频'; COMMENT ON COLUMN wvp_device_channel.gps_time IS 'GPS定位时间'; COMMENT ON COLUMN wvp_device_channel.stream_identification IS '流标识'; COMMENT ON COLUMN wvp_device_channel.channel_type IS '通道类型'; COMMENT ON COLUMN wvp_device_channel.map_level IS '地图层级'; COMMENT ON COLUMN wvp_device_channel.gb_device_id IS 'GB内的设备ID'; COMMENT ON COLUMN wvp_device_channel.gb_name IS 'GB上报的名称'; COMMENT ON COLUMN wvp_device_channel.gb_manufacturer IS 'GB厂商'; COMMENT ON COLUMN wvp_device_channel.gb_model IS 'GB型号'; COMMENT ON COLUMN wvp_device_channel.gb_owner IS 'GB归属'; COMMENT ON COLUMN wvp_device_channel.gb_civil_code IS 'GB行政区划'; COMMENT ON COLUMN wvp_device_channel.gb_block IS 'GB区域'; COMMENT ON COLUMN wvp_device_channel.gb_address IS 'GB地址'; COMMENT ON COLUMN wvp_device_channel.gb_parental IS 'GB子节点标识'; COMMENT ON COLUMN wvp_device_channel.gb_parent_id IS 'GB父通道'; COMMENT ON COLUMN wvp_device_channel.gb_safety_way IS 'GB安全防范'; COMMENT ON COLUMN wvp_device_channel.gb_register_way IS 'GB注册方式'; COMMENT ON COLUMN wvp_device_channel.gb_cert_num IS 'GB证书编号'; COMMENT ON COLUMN wvp_device_channel.gb_certifiable IS 'GB认证标志'; COMMENT ON COLUMN wvp_device_channel.gb_err_code IS 'GB错误码'; COMMENT ON COLUMN wvp_device_channel.gb_end_time IS 'GB截止时间'; COMMENT ON COLUMN wvp_device_channel.gb_secrecy IS 'GB保密级别'; COMMENT ON COLUMN wvp_device_channel.gb_ip_address IS 'GB IP'; COMMENT ON COLUMN wvp_device_channel.gb_port IS 'GB端口'; COMMENT ON COLUMN wvp_device_channel.gb_password IS 'GB接入密码'; COMMENT ON COLUMN wvp_device_channel.gb_status IS 'GB状态'; COMMENT ON COLUMN wvp_device_channel.gb_longitude IS 'GB经度'; COMMENT ON COLUMN wvp_device_channel.gb_latitude IS 'GB纬度'; COMMENT ON COLUMN wvp_device_channel.gb_business_group_id IS 'GB业务分组'; COMMENT ON COLUMN wvp_device_channel.gb_ptz_type IS 'GB云台类型'; COMMENT ON COLUMN wvp_device_channel.gb_position_type IS 'GB点位类型'; COMMENT ON COLUMN wvp_device_channel.gb_room_type IS 'GB房间类型'; COMMENT ON COLUMN wvp_device_channel.gb_use_type IS 'GB用途'; COMMENT ON COLUMN wvp_device_channel.gb_supply_light_type IS 'GB补光'; COMMENT ON COLUMN wvp_device_channel.gb_direction_type IS 'GB朝向'; COMMENT ON COLUMN wvp_device_channel.gb_resolution IS 'GB分辨率'; COMMENT ON COLUMN wvp_device_channel.gb_download_speed IS 'GB码流速率'; COMMENT ON COLUMN wvp_device_channel.gb_svc_space_support_mod IS 'GB空域SVC'; COMMENT ON COLUMN wvp_device_channel.gb_svc_time_support_mode IS 'GB时域SVC'; COMMENT ON COLUMN wvp_device_channel.record_plan_id IS '绑定的录像计划ID'; COMMENT ON COLUMN wvp_device_channel.data_type IS '数据类型标识'; COMMENT ON COLUMN wvp_device_channel.data_device_id IS '数据来源设备主键'; COMMENT ON COLUMN wvp_device_channel.gps_speed IS 'GPS速度'; COMMENT ON COLUMN wvp_device_channel.gps_altitude IS 'GPS海拔'; COMMENT ON COLUMN wvp_device_channel.gps_direction IS 'GPS方向'; COMMENT ON COLUMN wvp_device_channel.enable_broadcast IS '是否支持广播'; CREATE INDEX idx_data_type ON wvp_device_channel (data_type); CREATE INDEX idx_data_device_id ON wvp_device_channel (data_device_id); drop table IF EXISTS wvp_media_server; create table IF NOT EXISTS wvp_media_server ( id character varying(255) primary key, ip character varying(50), hook_ip character varying(50), sdp_ip character varying(50), stream_ip character varying(50), http_port integer, http_ssl_port integer, rtmp_port integer, rtmp_ssl_port integer, rtp_proxy_port integer, rtsp_port integer, rtsp_ssl_port integer, flv_port integer, flv_ssl_port integer, mp4_port integer, mp4_ssl_port integer, ws_flv_port integer, ws_flv_ssl_port integer, jtt_proxy_port integer, auto_config bool default false, secret character varying(50), type character varying(50) default 'zlm', rtp_enable bool default false, rtp_port_range character varying(50), send_rtp_port_range character varying(50), record_assist_port integer, default_server bool default false, create_time character varying(50), update_time character varying(50), hook_alive_interval integer, record_path character varying(255), record_day integer default 7, transcode_suffix character varying(255), server_id character varying(50), constraint uk_media_server_unique_ip_http_port unique (ip, http_port, server_id) ); COMMENT ON TABLE wvp_media_server IS '媒体服务器(如 ZLM)节点信息'; COMMENT ON COLUMN wvp_media_server.id IS '媒体服务器ID'; COMMENT ON COLUMN wvp_media_server.ip IS '服务器IP'; COMMENT ON COLUMN wvp_media_server.hook_ip IS 'hook回调IP'; COMMENT ON COLUMN wvp_media_server.sdp_ip IS 'SDP中使用的IP'; COMMENT ON COLUMN wvp_media_server.stream_ip IS '推流使用的IP'; COMMENT ON COLUMN wvp_media_server.http_port IS 'HTTP端口'; COMMENT ON COLUMN wvp_media_server.http_ssl_port IS 'HTTPS端口'; COMMENT ON COLUMN wvp_media_server.rtmp_port IS 'RTMP端口'; COMMENT ON COLUMN wvp_media_server.rtmp_ssl_port IS 'RTMPS端口'; COMMENT ON COLUMN wvp_media_server.rtp_proxy_port IS 'RTP代理端口'; COMMENT ON COLUMN wvp_media_server.rtsp_port IS 'RTSP端口'; COMMENT ON COLUMN wvp_media_server.rtsp_ssl_port IS 'RTSPS端口'; COMMENT ON COLUMN wvp_media_server.flv_port IS 'FLV端口'; COMMENT ON COLUMN wvp_media_server.flv_ssl_port IS 'FLV HTTPS端口'; COMMENT ON COLUMN wvp_media_server.mp4_port IS 'MP4点播端口'; COMMENT ON COLUMN wvp_media_server.mp4_ssl_port IS 'MP4 HTTPS端口'; COMMENT ON COLUMN wvp_media_server.ws_flv_port IS 'WS-FLV端口'; COMMENT ON COLUMN wvp_media_server.ws_flv_ssl_port IS 'WS-FLV HTTPS端口'; COMMENT ON COLUMN wvp_media_server.jtt_proxy_port IS 'JT/T代理端口'; COMMENT ON COLUMN wvp_media_server.auto_config IS '是否自动配置'; COMMENT ON COLUMN wvp_media_server.secret IS 'ZLM校验密钥'; COMMENT ON COLUMN wvp_media_server.type IS '节点类型'; COMMENT ON COLUMN wvp_media_server.rtp_enable IS '是否开启RTP'; COMMENT ON COLUMN wvp_media_server.rtp_port_range IS 'RTP端口范围'; COMMENT ON COLUMN wvp_media_server.send_rtp_port_range IS '发送RTP端口范围'; COMMENT ON COLUMN wvp_media_server.record_assist_port IS '录像辅助端口'; COMMENT ON COLUMN wvp_media_server.default_server IS '是否默认节点'; COMMENT ON COLUMN wvp_media_server.create_time IS '创建时间'; COMMENT ON COLUMN wvp_media_server.update_time IS '更新时间'; COMMENT ON COLUMN wvp_media_server.hook_alive_interval IS 'hook心跳间隔'; COMMENT ON COLUMN wvp_media_server.record_path IS '录像目录'; COMMENT ON COLUMN wvp_media_server.record_day IS '录像保留天数'; COMMENT ON COLUMN wvp_media_server.transcode_suffix IS '转码指令后缀'; COMMENT ON COLUMN wvp_media_server.server_id IS '对应信令服务器ID'; drop table IF EXISTS wvp_platform; create table IF NOT EXISTS wvp_platform ( id serial primary key, enable bool default false, name character varying(255), server_gb_id character varying(50), server_gb_domain character varying(50), server_ip character varying(50), server_port integer, device_gb_id character varying(50), device_ip character varying(50), device_port character varying(50), username character varying(255), password character varying(50), expires character varying(50), keep_timeout character varying(50), transport character varying(50), civil_code character varying(50), manufacturer character varying(255), model character varying(255), address character varying(255), character_set character varying(50), ptz bool default false, rtcp bool default false, status bool default false, catalog_group integer, register_way integer, secrecy integer, create_time character varying(50), update_time character varying(50), as_message_channel bool default false, catalog_with_platform integer default 1, catalog_with_group integer default 1, catalog_with_region integer default 1, auto_push_channel bool default true, send_stream_ip character varying(50), server_id character varying(50), constraint uk_platform_unique_server_gb_id unique (server_gb_id) ); COMMENT ON TABLE wvp_platform IS '上级国标平台注册信息'; COMMENT ON COLUMN wvp_platform.id IS '主键ID'; COMMENT ON COLUMN wvp_platform.enable IS '是否启用该平台注册'; COMMENT ON COLUMN wvp_platform.name IS '平台名称'; COMMENT ON COLUMN wvp_platform.server_gb_id IS '上级平台国标编码'; COMMENT ON COLUMN wvp_platform.server_gb_domain IS '上级平台域编码'; COMMENT ON COLUMN wvp_platform.server_ip IS '上级平台IP'; COMMENT ON COLUMN wvp_platform.server_port IS '上级平台注册端口'; COMMENT ON COLUMN wvp_platform.device_gb_id IS '本平台向上注册的国标编码'; COMMENT ON COLUMN wvp_platform.device_ip IS '本平台信令IP'; COMMENT ON COLUMN wvp_platform.device_port IS '本平台信令端口'; COMMENT ON COLUMN wvp_platform.username IS '注册用户名'; COMMENT ON COLUMN wvp_platform.password IS '注册密码'; COMMENT ON COLUMN wvp_platform.expires IS '注册有效期'; COMMENT ON COLUMN wvp_platform.keep_timeout IS '心跳超时时间'; COMMENT ON COLUMN wvp_platform.transport IS '传输协议(UDP/TCP)'; COMMENT ON COLUMN wvp_platform.civil_code IS '行政区划代码'; COMMENT ON COLUMN wvp_platform.manufacturer IS '厂商'; COMMENT ON COLUMN wvp_platform.model IS '型号'; COMMENT ON COLUMN wvp_platform.address IS '地址'; COMMENT ON COLUMN wvp_platform.character_set IS '字符集'; COMMENT ON COLUMN wvp_platform.ptz IS '是否支持PTZ'; COMMENT ON COLUMN wvp_platform.rtcp IS '是否开启RTCP'; COMMENT ON COLUMN wvp_platform.status IS '注册状态'; COMMENT ON COLUMN wvp_platform.catalog_group IS '目录分组方式'; COMMENT ON COLUMN wvp_platform.register_way IS '注册方式'; COMMENT ON COLUMN wvp_platform.secrecy IS '保密级别'; COMMENT ON COLUMN wvp_platform.create_time IS '创建时间'; COMMENT ON COLUMN wvp_platform.update_time IS '更新时间'; COMMENT ON COLUMN wvp_platform.as_message_channel IS '是否作为消息通道'; COMMENT ON COLUMN wvp_platform.catalog_with_platform IS '是否推送平台目录'; COMMENT ON COLUMN wvp_platform.catalog_with_group IS '是否推送分组目录'; COMMENT ON COLUMN wvp_platform.catalog_with_region IS '是否推送区域目录'; COMMENT ON COLUMN wvp_platform.auto_push_channel IS '是否自动推送通道'; COMMENT ON COLUMN wvp_platform.send_stream_ip IS '推流时使用的IP'; COMMENT ON COLUMN wvp_platform.server_id IS '对应信令服务器ID'; drop table IF EXISTS wvp_platform_channel; create table IF NOT EXISTS wvp_platform_channel ( id serial primary key, platform_id integer, device_channel_id integer, custom_device_id character varying(50), custom_name character varying(255), custom_manufacturer character varying(50), custom_model character varying(50), custom_owner character varying(50), custom_civil_code character varying(50), custom_block character varying(50), custom_address character varying(50), custom_parental integer, custom_parent_id character varying(50), custom_safety_way integer, custom_register_way integer, custom_cert_num character varying(50), custom_certifiable integer, custom_err_code integer, custom_end_time character varying(50), custom_secrecy integer, custom_ip_address character varying(50), custom_port integer, custom_password character varying(255), custom_status character varying(50), custom_longitude double precision, custom_latitude double precision, custom_ptz_type integer, custom_position_type integer, custom_room_type integer, custom_use_type integer, custom_supply_light_type integer, custom_direction_type integer, custom_resolution character varying(255), custom_business_group_id character varying(255), custom_download_speed character varying(255), custom_svc_space_support_mod integer, custom_svc_time_support_mode integer, constraint uk_platform_gb_channel_platform_id_catalog_id_device_channel_id unique (platform_id, device_channel_id), constraint uk_platform_gb_channel_device_id unique (custom_device_id) ); COMMENT ON TABLE wvp_platform_channel IS '国标平台下发的通道映射关系'; COMMENT ON COLUMN wvp_platform_channel.id IS '主键ID'; COMMENT ON COLUMN wvp_platform_channel.platform_id IS '平台ID'; COMMENT ON COLUMN wvp_platform_channel.device_channel_id IS '本地通道表主键'; COMMENT ON COLUMN wvp_platform_channel.custom_device_id IS '自定义国标编码'; COMMENT ON COLUMN wvp_platform_channel.custom_name IS '自定义名称'; COMMENT ON COLUMN wvp_platform_channel.custom_manufacturer IS '自定义厂商'; COMMENT ON COLUMN wvp_platform_channel.custom_model IS '自定义型号'; COMMENT ON COLUMN wvp_platform_channel.custom_owner IS '自定义归属'; COMMENT ON COLUMN wvp_platform_channel.custom_civil_code IS '自定义行政区划'; COMMENT ON COLUMN wvp_platform_channel.custom_block IS '自定义区域'; COMMENT ON COLUMN wvp_platform_channel.custom_address IS '自定义地址'; COMMENT ON COLUMN wvp_platform_channel.custom_parental IS '自定义父/子标识'; COMMENT ON COLUMN wvp_platform_channel.custom_parent_id IS '自定义父节点'; COMMENT ON COLUMN wvp_platform_channel.custom_safety_way IS '自定义安全防范'; COMMENT ON COLUMN wvp_platform_channel.custom_register_way IS '自定义注册方式'; COMMENT ON COLUMN wvp_platform_channel.custom_cert_num IS '自定义证书编号'; COMMENT ON COLUMN wvp_platform_channel.custom_certifiable IS '自定义可认证标志'; COMMENT ON COLUMN wvp_platform_channel.custom_err_code IS '自定义错误码'; COMMENT ON COLUMN wvp_platform_channel.custom_end_time IS '自定义截止时间'; COMMENT ON COLUMN wvp_platform_channel.custom_secrecy IS '自定义保密级别'; COMMENT ON COLUMN wvp_platform_channel.custom_ip_address IS '自定义IP'; COMMENT ON COLUMN wvp_platform_channel.custom_port IS '自定义端口'; COMMENT ON COLUMN wvp_platform_channel.custom_password IS '自定义密码'; COMMENT ON COLUMN wvp_platform_channel.custom_status IS '自定义状态'; COMMENT ON COLUMN wvp_platform_channel.custom_longitude IS '自定义经度'; COMMENT ON COLUMN wvp_platform_channel.custom_latitude IS '自定义纬度'; COMMENT ON COLUMN wvp_platform_channel.custom_ptz_type IS '自定义云台类型'; COMMENT ON COLUMN wvp_platform_channel.custom_position_type IS '自定义点位类型'; COMMENT ON COLUMN wvp_platform_channel.custom_room_type IS '自定义房间类型'; COMMENT ON COLUMN wvp_platform_channel.custom_use_type IS '自定义用途'; COMMENT ON COLUMN wvp_platform_channel.custom_supply_light_type IS '自定义补光'; COMMENT ON COLUMN wvp_platform_channel.custom_direction_type IS '自定义朝向'; COMMENT ON COLUMN wvp_platform_channel.custom_resolution IS '自定义分辨率'; COMMENT ON COLUMN wvp_platform_channel.custom_business_group_id IS '自定义业务分组'; COMMENT ON COLUMN wvp_platform_channel.custom_download_speed IS '自定义码流速率'; COMMENT ON COLUMN wvp_platform_channel.custom_svc_space_support_mod IS '自定义空域SVC'; COMMENT ON COLUMN wvp_platform_channel.custom_svc_time_support_mode IS '自定义时域SVC'; drop table IF EXISTS wvp_platform_group; create table IF NOT EXISTS wvp_platform_group ( id serial primary key, platform_id integer, group_id integer, constraint uk_wvp_platform_group_platform_id_group_id unique (platform_id, group_id) ); COMMENT ON TABLE wvp_platform_group IS '平台与分组(行政区划/组织)关系'; COMMENT ON COLUMN wvp_platform_group.id IS '主键ID'; COMMENT ON COLUMN wvp_platform_group.platform_id IS '平台ID'; COMMENT ON COLUMN wvp_platform_group.group_id IS '分组ID'; drop table IF EXISTS wvp_platform_region; create table IF NOT EXISTS wvp_platform_region ( id serial primary key, platform_id integer, region_id integer, constraint uk_wvp_platform_region_platform_id_group_id unique (platform_id, region_id) ); COMMENT ON TABLE wvp_platform_region IS '平台与区域关系'; COMMENT ON COLUMN wvp_platform_region.id IS '主键ID'; COMMENT ON COLUMN wvp_platform_region.platform_id IS '平台ID'; COMMENT ON COLUMN wvp_platform_region.region_id IS '区域ID'; drop table IF EXISTS wvp_stream_proxy; create table IF NOT EXISTS wvp_stream_proxy ( id serial primary key, type character varying(50), app character varying(255), stream character varying(255), src_url character varying(255), timeout integer, ffmpeg_cmd_key character varying(255), rtsp_type character varying(50), media_server_id character varying(50), enable_audio bool default false, enable_mp4 bool default false, pulling bool default false, enable bool default false, create_time character varying(50), name character varying(255), update_time character varying(50), stream_key character varying(255), server_id character varying(50), enable_disable_none_reader bool default false, relates_media_server_id character varying(50), constraint uk_stream_proxy_app_stream unique (app, stream) ); COMMENT ON TABLE wvp_stream_proxy IS '拉流代理/转推配置'; COMMENT ON COLUMN wvp_stream_proxy.id IS '主键ID'; COMMENT ON COLUMN wvp_stream_proxy.type IS '代理类型(拉流/推流)'; COMMENT ON COLUMN wvp_stream_proxy.app IS '应用名'; COMMENT ON COLUMN wvp_stream_proxy.stream IS '流ID'; COMMENT ON COLUMN wvp_stream_proxy.src_url IS '源地址'; COMMENT ON COLUMN wvp_stream_proxy.timeout IS '拉流超时时间'; COMMENT ON COLUMN wvp_stream_proxy.ffmpeg_cmd_key IS 'FFmpeg命令模板键'; COMMENT ON COLUMN wvp_stream_proxy.rtsp_type IS 'RTSP拉流方式'; COMMENT ON COLUMN wvp_stream_proxy.media_server_id IS '指定媒体服务器ID'; COMMENT ON COLUMN wvp_stream_proxy.enable_audio IS '是否启用音频'; COMMENT ON COLUMN wvp_stream_proxy.enable_mp4 IS '是否录制MP4'; COMMENT ON COLUMN wvp_stream_proxy.pulling IS '当前是否在拉流'; COMMENT ON COLUMN wvp_stream_proxy.enable IS '是否启用该代理'; COMMENT ON COLUMN wvp_stream_proxy.create_time IS '创建时间'; COMMENT ON COLUMN wvp_stream_proxy.name IS '代理名称'; COMMENT ON COLUMN wvp_stream_proxy.update_time IS '更新时间'; COMMENT ON COLUMN wvp_stream_proxy.stream_key IS '唯一流标识'; COMMENT ON COLUMN wvp_stream_proxy.server_id IS '信令服务器ID'; COMMENT ON COLUMN wvp_stream_proxy.enable_disable_none_reader IS '是否无人观看时自动停流'; COMMENT ON COLUMN wvp_stream_proxy.relates_media_server_id IS '关联的媒体服务器ID'; drop table IF EXISTS wvp_stream_push; create table IF NOT EXISTS wvp_stream_push ( id serial primary key, app character varying(255), stream character varying(255), create_time character varying(50), media_server_id character varying(50), server_id character varying(50), push_time character varying(50), status bool default false, update_time character varying(50), pushing bool default false, self bool default false, start_offline_push bool default true, constraint uk_stream_push_app_stream unique (app, stream) ); COMMENT ON TABLE wvp_stream_push IS '推流会话记录'; COMMENT ON COLUMN wvp_stream_push.id IS '主键ID'; COMMENT ON COLUMN wvp_stream_push.app IS '应用名'; COMMENT ON COLUMN wvp_stream_push.stream IS '流ID'; COMMENT ON COLUMN wvp_stream_push.create_time IS '创建时间'; COMMENT ON COLUMN wvp_stream_push.media_server_id IS '推流所在媒体服务器'; COMMENT ON COLUMN wvp_stream_push.server_id IS '信令服务器ID'; COMMENT ON COLUMN wvp_stream_push.push_time IS '推流开始时间'; COMMENT ON COLUMN wvp_stream_push.status IS '推流状态'; COMMENT ON COLUMN wvp_stream_push.update_time IS '更新时间'; COMMENT ON COLUMN wvp_stream_push.pushing IS '是否正在推流'; COMMENT ON COLUMN wvp_stream_push.self IS '是否本地发起'; COMMENT ON COLUMN wvp_stream_push.start_offline_push IS '是否离线后自动重推'; drop table IF EXISTS wvp_cloud_record; create table IF NOT EXISTS wvp_cloud_record ( id serial primary key, app character varying(255), stream character varying(255), call_id character varying(255), start_time int8, end_time int8, media_server_id character varying(50), server_id character varying(50), file_name character varying(255), folder character varying(500), file_path character varying(500), collect bool default false, file_size int8, time_len double precision ); COMMENT ON TABLE wvp_cloud_record IS '云端录像记录'; COMMENT ON COLUMN wvp_cloud_record.id IS '主键ID'; COMMENT ON COLUMN wvp_cloud_record.app IS '应用名'; COMMENT ON COLUMN wvp_cloud_record.stream IS '流ID'; COMMENT ON COLUMN wvp_cloud_record.call_id IS '会话ID'; COMMENT ON COLUMN wvp_cloud_record.start_time IS '录像开始时间'; COMMENT ON COLUMN wvp_cloud_record.end_time IS '录像结束时间'; COMMENT ON COLUMN wvp_cloud_record.media_server_id IS '媒体服务器ID'; COMMENT ON COLUMN wvp_cloud_record.server_id IS '信令服务器ID'; COMMENT ON COLUMN wvp_cloud_record.file_name IS '文件名'; COMMENT ON COLUMN wvp_cloud_record.folder IS '目录'; COMMENT ON COLUMN wvp_cloud_record.file_path IS '完整路径'; COMMENT ON COLUMN wvp_cloud_record.collect IS '是否收藏'; COMMENT ON COLUMN wvp_cloud_record.file_size IS '文件大小'; COMMENT ON COLUMN wvp_cloud_record.time_len IS '时长'; drop table IF EXISTS wvp_user; create table IF NOT EXISTS wvp_user ( id serial primary key, username character varying(255), password character varying(255), role_id integer, create_time character varying(50), update_time character varying(50), push_key character varying(50), constraint uk_user_username unique (username) ); COMMENT ON TABLE wvp_user IS '平台用户信息'; COMMENT ON COLUMN wvp_user.id IS '主键ID'; COMMENT ON COLUMN wvp_user.username IS '用户名'; COMMENT ON COLUMN wvp_user.password IS '密码(MD5)'; COMMENT ON COLUMN wvp_user.role_id IS '角色ID'; COMMENT ON COLUMN wvp_user.create_time IS '创建时间'; COMMENT ON COLUMN wvp_user.update_time IS '更新时间'; COMMENT ON COLUMN wvp_user.push_key IS '推送密钥'; drop table IF EXISTS wvp_user_role; create table IF NOT EXISTS wvp_user_role ( id serial primary key, name character varying(50), authority character varying(50), create_time character varying(50), update_time character varying(50) ); COMMENT ON TABLE wvp_user_role IS '用户角色信息'; COMMENT ON COLUMN wvp_user_role.id IS '主键ID'; COMMENT ON COLUMN wvp_user_role.name IS '角色名称'; COMMENT ON COLUMN wvp_user_role.authority IS '权限标识'; COMMENT ON COLUMN wvp_user_role.create_time IS '创建时间'; COMMENT ON COLUMN wvp_user_role.update_time IS '更新时间'; drop table IF EXISTS wvp_user_api_key; create table IF NOT EXISTS wvp_user_api_key ( id serial primary key, user_id int8, app character varying(255), api_key text, expired_at int8, remark character varying(255), enable bool default true, create_time character varying(50), update_time character varying(50) ); COMMENT ON COLUMN wvp_user_api_key.id IS '主键ID'; COMMENT ON COLUMN wvp_user_api_key.user_id IS '关联用户ID'; COMMENT ON COLUMN wvp_user_api_key.app IS '应用标识'; COMMENT ON COLUMN wvp_user_api_key.api_key IS 'API Key'; COMMENT ON COLUMN wvp_user_api_key.expired_at IS '过期时间戳'; COMMENT ON COLUMN wvp_user_api_key.remark IS '备注'; COMMENT ON COLUMN wvp_user_api_key.enable IS '是否启用'; COMMENT ON COLUMN wvp_user_api_key.create_time IS '创建时间'; COMMENT ON COLUMN wvp_user_api_key.update_time IS '更新时间'; /*初始数据*/ INSERT INTO wvp_user VALUES (1, 'admin', '21232f297a57a5a743894a0e4a801fc3', 1, '2021-04-13 14:14:57', '2021-04-13 14:14:57', '3e80d1762a324d5b0ff636e0bd16f1e3'); INSERT INTO wvp_user_role VALUES (1, 'admin', '0', '2021-04-13 14:14:57', '2021-04-13 14:14:57'); drop table IF EXISTS wvp_common_group; create table IF NOT EXISTS wvp_common_group ( id serial primary key, device_id varchar(50) NOT NULL, name varchar(255) NOT NULL, parent_id int, parent_device_id varchar(50) DEFAULT NULL, business_group varchar(50) NOT NULL, create_time varchar(50) NOT NULL, update_time varchar(50) NOT NULL, civil_code varchar(50) default null, alias varchar(255) default null, constraint uk_common_group_device_platform unique (device_id) ); COMMENT ON TABLE wvp_common_group IS '通用分组表,存储行业或组织结构'; COMMENT ON COLUMN wvp_common_group.id IS '主键ID'; COMMENT ON COLUMN wvp_common_group.device_id IS '分组对应的平台或设备ID'; COMMENT ON COLUMN wvp_common_group.name IS '分组名称'; COMMENT ON COLUMN wvp_common_group.parent_id IS '父级分组ID'; COMMENT ON COLUMN wvp_common_group.parent_device_id IS '父级分组对应的设备ID'; COMMENT ON COLUMN wvp_common_group.business_group IS '业务分组编码'; COMMENT ON COLUMN wvp_common_group.create_time IS '创建时间'; COMMENT ON COLUMN wvp_common_group.update_time IS '更新时间'; COMMENT ON COLUMN wvp_common_group.civil_code IS '行政区划代码'; COMMENT ON COLUMN wvp_common_group.alias IS '别名'; drop table IF EXISTS wvp_common_region; create table IF NOT EXISTS wvp_common_region ( id serial primary key, device_id varchar(50) NOT NULL, name varchar(255) NOT NULL, parent_id int, parent_device_id varchar(50) DEFAULT NULL, create_time varchar(50) NOT NULL, update_time varchar(50) NOT NULL, constraint uk_common_region_device_id unique (device_id) ); COMMENT ON TABLE wvp_common_region IS '通用行政区域表'; COMMENT ON COLUMN wvp_common_region.id IS '主键ID'; COMMENT ON COLUMN wvp_common_region.device_id IS '区域对应的平台或设备ID'; COMMENT ON COLUMN wvp_common_region.name IS '区域名称'; COMMENT ON COLUMN wvp_common_region.parent_id IS '父级区域ID'; COMMENT ON COLUMN wvp_common_region.parent_device_id IS '父级区域的设备ID'; COMMENT ON COLUMN wvp_common_region.create_time IS '创建时间'; COMMENT ON COLUMN wvp_common_region.update_time IS '更新时间'; drop table IF EXISTS wvp_record_plan; create table IF NOT EXISTS wvp_record_plan ( id serial primary key, snap bool default false, name varchar(255) NOT NULL, create_time character varying(50), update_time character varying(50) ); COMMENT ON TABLE wvp_record_plan IS '录像计划基础信息'; COMMENT ON COLUMN wvp_record_plan.id IS '主键ID'; COMMENT ON COLUMN wvp_record_plan.snap IS '是否抓图计划'; COMMENT ON COLUMN wvp_record_plan.name IS '计划名称'; COMMENT ON COLUMN wvp_record_plan.create_time IS '创建时间'; COMMENT ON COLUMN wvp_record_plan.update_time IS '更新时间'; drop table IF EXISTS wvp_record_plan_item; create table IF NOT EXISTS wvp_record_plan_item ( id serial primary key, "start" int, stop int, week_day int, plan_id int, create_time character varying(50), update_time character varying(50) ); COMMENT ON TABLE wvp_record_plan_item IS '录像计划条目表'; COMMENT ON COLUMN wvp_record_plan_item.id IS '主键ID'; COMMENT ON COLUMN wvp_record_plan_item."start" IS '开始时间(分钟)'; COMMENT ON COLUMN wvp_record_plan_item.stop IS '结束时间(分钟)'; COMMENT ON COLUMN wvp_record_plan_item.week_day IS '星期(0-6)'; COMMENT ON COLUMN wvp_record_plan_item.plan_id IS '所属录像计划ID'; COMMENT ON COLUMN wvp_record_plan_item.create_time IS '创建时间'; COMMENT ON COLUMN wvp_record_plan_item.update_time IS '更新时间'; drop table IF EXISTS wvp_jt_terminal; create table IF NOT EXISTS wvp_jt_terminal ( id serial primary key, phone_number character varying(50), terminal_id character varying(50), province_id character varying(50), province_text character varying(100), city_id character varying(50), city_text character varying(100), maker_id character varying(50), model character varying(50), plate_color character varying(50), plate_no character varying(50), longitude double precision, latitude double precision, status bool default false, register_time character varying(50) default null, update_time character varying(50) not null, create_time character varying(50) not null, geo_coord_sys character varying(50), media_server_id character varying(50) default 'auto', sdp_ip character varying(50), constraint uk_jt_device_id_device_id unique (id, phone_number) ); COMMENT ON TABLE wvp_jt_terminal IS '交通部 JT/T 1076 终端信息'; COMMENT ON COLUMN wvp_jt_terminal.id IS '主键ID'; COMMENT ON COLUMN wvp_jt_terminal.phone_number IS '终端SIM卡号'; COMMENT ON COLUMN wvp_jt_terminal.terminal_id IS '终端设备ID'; COMMENT ON COLUMN wvp_jt_terminal.province_id IS '所在省份ID'; COMMENT ON COLUMN wvp_jt_terminal.province_text IS '所在省份名称'; COMMENT ON COLUMN wvp_jt_terminal.city_id IS '所在城市ID'; COMMENT ON COLUMN wvp_jt_terminal.city_text IS '所在城市名称'; COMMENT ON COLUMN wvp_jt_terminal.maker_id IS '厂商ID'; COMMENT ON COLUMN wvp_jt_terminal.model IS '终端型号'; COMMENT ON COLUMN wvp_jt_terminal.plate_color IS '车牌颜色'; COMMENT ON COLUMN wvp_jt_terminal.plate_no IS '车牌号码'; COMMENT ON COLUMN wvp_jt_terminal.longitude IS '经度'; COMMENT ON COLUMN wvp_jt_terminal.latitude IS '纬度'; COMMENT ON COLUMN wvp_jt_terminal.status IS '在线状态'; COMMENT ON COLUMN wvp_jt_terminal.register_time IS '注册时间'; COMMENT ON COLUMN wvp_jt_terminal.update_time IS '更新时间'; COMMENT ON COLUMN wvp_jt_terminal.create_time IS '创建时间'; COMMENT ON COLUMN wvp_jt_terminal.geo_coord_sys IS '坐标系'; COMMENT ON COLUMN wvp_jt_terminal.media_server_id IS '媒体服务器ID'; COMMENT ON COLUMN wvp_jt_terminal.sdp_ip IS 'SDP IP'; drop table IF EXISTS wvp_jt_channel; create table IF NOT EXISTS wvp_jt_channel ( id serial primary key, terminal_db_id integer, channel_id integer, has_audio bool default false, name character varying(255), update_time character varying(50) not null, create_time character varying(50) not null, constraint uk_jt_channel_id_device_id unique (terminal_db_id, channel_id) ); COMMENT ON TABLE wvp_jt_channel IS '交通部 JT/T 1076 通道信息'; COMMENT ON COLUMN wvp_jt_channel.id IS '主键ID'; COMMENT ON COLUMN wvp_jt_channel.terminal_db_id IS '所属终端记录ID'; COMMENT ON COLUMN wvp_jt_channel.channel_id IS '通道号'; COMMENT ON COLUMN wvp_jt_channel.has_audio IS '是否有音频'; COMMENT ON COLUMN wvp_jt_channel.name IS '通道名称'; COMMENT ON COLUMN wvp_jt_channel.update_time IS '更新时间'; COMMENT ON COLUMN wvp_jt_channel.create_time IS '创建时间'; ================================================ FILE: 数据库/2.7.4/更新-mysql-2.7.4.sql ================================================ drop table IF EXISTS wvp_jt_terminal; create table IF NOT EXISTS wvp_jt_terminal ( id serial primary key, phone_number character varying(50), terminal_id character varying(50), province_id character varying(50), province_text character varying(100), city_id character varying(50), city_text character varying(100), maker_id character varying(50), model character varying(50), plate_color character varying(50), plate_no character varying(50), longitude double precision, latitude double precision, status bool default false, register_time character varying(50) default null, update_time character varying(50) not null, create_time character varying(50) not null, geo_coord_sys character varying(50), media_server_id character varying(50) default 'auto', sdp_ip character varying(50), constraint uk_jt_device_id_device_id unique (id, phone_number) ); drop table IF EXISTS wvp_jt_channel; create table IF NOT EXISTS wvp_jt_channel ( id serial primary key, terminal_db_id integer, channel_id integer, has_audio bool default false, name character varying(255), update_time character varying(50) not null, create_time character varying(50) not null, constraint uk_jt_channel_id_device_id unique (terminal_db_id, channel_id) ); /* * 20250708 */ DELIMITER // -- 重定义分隔符避免分号冲突 CREATE PROCEDURE `wvp_20250708`() BEGIN IF NOT EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_media_server' and column_name = 'jtt_proxy_port') THEN ALTER TABLE wvp_media_server ADD jtt_proxy_port integer; END IF; END; // call wvp_20250708(); DROP PROCEDURE wvp_20250708; DELIMITER ; /* * 20250917 */ DELIMITER // -- 重定义分隔符避免分号冲突 CREATE PROCEDURE `wvp_20250917`() BEGIN IF NOT EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_media_server' and column_name = 'mp4_port') THEN ALTER TABLE wvp_media_server ADD mp4_port integer; END IF; IF NOT EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_media_server' and column_name = 'mp4_ssl_port') THEN ALTER TABLE wvp_media_server ADD mp4_ssl_port integer; END IF; END; // call wvp_20250917(); DROP PROCEDURE wvp_20250917; DELIMITER ; /* * 20250924 */ DELIMITER // -- 重定义分隔符避免分号冲突 CREATE PROCEDURE `wvp_20250924`() BEGIN IF NOT EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_device_channel' and column_name = 'enable_broadcast') THEN ALTER TABLE wvp_device_channel ADD enable_broadcast integer default 0; END IF; IF NOT EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_device_channel' and column_name = 'map_level') THEN ALTER TABLE wvp_device_channel ADD map_level integer default 0; END IF; IF NOT EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_common_group' and column_name = 'alias') THEN ALTER TABLE wvp_common_group ADD alias varchar(255) default null; END IF; END; // call wvp_20250924(); DROP PROCEDURE wvp_20250924; DELIMITER ; /* * 20251027 */ DELIMITER // -- 重定义分隔符避免分号冲突 CREATE PROCEDURE `wvp_20251027`() BEGIN IF EXISTS (SELECT column_name FROM information_schema.columns WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_stream_proxy' and column_name = 'enable_remove_none_reader') THEN ALTER TABLE wvp_stream_proxy DROP enable_remove_none_reader; END IF; END; // call wvp_20251027(); DROP PROCEDURE wvp_20251027; DELIMITER ; ================================================ FILE: 数据库/2.7.4/更新-postgresql-kingbase-2.7.4.sql ================================================ drop table IF EXISTS wvp_jt_terminal; create table IF NOT EXISTS wvp_jt_terminal ( id serial primary key, phone_number character varying(50), terminal_id character varying(50), province_id character varying(50), province_text character varying(100), city_id character varying(50), city_text character varying(100), maker_id character varying(50), model character varying(50), plate_color character varying(50), plate_no character varying(50), longitude double precision, latitude double precision, status bool default false, register_time character varying(50) default null, update_time character varying(50) not null, create_time character varying(50) not null, geo_coord_sys character varying(50), media_server_id character varying(50) default 'auto', sdp_ip character varying(50), constraint uk_jt_device_id_device_id unique (id, phone_number) ); drop table IF EXISTS wvp_jt_channel; create table IF NOT EXISTS wvp_jt_channel ( id serial primary key, terminal_db_id integer, channel_id integer, has_audio bool default false, name character varying(255), update_time character varying(50) not null, create_time character varying(50) not null, constraint uk_jt_channel_id_device_id unique (terminal_db_id, channel_id) ); ALTER table wvp_media_server ADD COLUMN IF NOT EXISTS jtt_proxy_port integer; ALTER table wvp_media_server ADD COLUMN IF NOT EXISTS mp4_port integer; ALTER table wvp_media_server ADD COLUMN IF NOT EXISTS mp4_ssl_port integer; ALTER table wvp_device_channel ADD COLUMN IF NOT EXISTS enable_broadcast integer default 0; ALTER table wvp_device_channel ADD COLUMN IF NOT EXISTS map_level integer default 0; ALTER table wvp_common_group ADD COLUMN IF NOT EXISTS alias varchar(255) default null; ALTER table wvp_stream_proxy DROP COLUMN IF EXISTS enable_remove_none_reader; ================================================ FILE: 数据库/2.7.4-h2/h2-data.sql ================================================ /*初始数据*/ INSERT INTO wvp_user VALUES (1, 'admin', '21232f297a57a5a743894a0e4a801fc3', 1, '2021-04-13 14:14:57', '2021-04-13 14:14:57', '3e80d1762a324d5b0ff636e0bd16f1e3'); INSERT INTO wvp_user_role VALUES (1, 'admin', '0', '2021-04-13 14:14:57', '2021-04-13 14:14:57'); ================================================ FILE: 数据库/2.7.4-h2/h2-schema.sql ================================================ /*建表*/ create table IF NOT EXISTS wvp_device ( id bigint primary key, device_id character varying(50) not null, name character varying(255), manufacturer character varying(255), model character varying(255), firmware character varying(255), transport character varying(50), stream_mode character varying(50), on_line bool default false, register_time character varying(50), keepalive_time character varying(50), ip character varying(50), create_time character varying(50), update_time character varying(50), port integer, expires integer, subscribe_cycle_for_catalog integer DEFAULT 0, subscribe_cycle_for_mobile_position integer DEFAULT 0, mobile_position_submission_interval integer DEFAULT 5, subscribe_cycle_for_alarm integer DEFAULT 0, host_address character varying(50), charset character varying(50), ssrc_check bool default false, geo_coord_sys character varying(50), media_server_id character varying(50) default 'auto', custom_name character varying(255), sdp_ip character varying(50), local_ip character varying(50), password character varying(255), as_message_channel bool default false, heart_beat_interval integer, heart_beat_count integer, position_capability integer, broadcast_push_after_ack bool default false, server_id character varying(50), constraint uk_device_device unique (device_id) ); create table IF NOT EXISTS wvp_device_alarm ( id bigint primary key, device_id character varying(50) not null, channel_id character varying(50) not null, alarm_priority character varying(50), alarm_method character varying(50), alarm_time character varying(50), alarm_description character varying(255), longitude double precision, latitude double precision, alarm_type character varying(50), create_time character varying(50) not null ); create table IF NOT EXISTS wvp_device_mobile_position ( id bigint primary key, device_id character varying(50) not null, channel_id character varying(50) not null, device_name character varying(255), time character varying(50), longitude double precision, latitude double precision, altitude double precision, speed double precision, direction double precision, report_source character varying(50), create_time character varying(50) ); create table IF NOT EXISTS wvp_device_channel ( id bigint primary key, device_id character varying(50), name character varying(255), manufacturer character varying(50), model character varying(50), owner character varying(50), civil_code character varying(50), block character varying(50), address character varying(50), parental integer, parent_id character varying(50), safety_way integer, register_way integer, cert_num character varying(50), certifiable integer, err_code integer, end_time character varying(50), secrecy integer, ip_address character varying(50), port integer, password character varying(255), status character varying(50), longitude double precision, latitude double precision, ptz_type integer, position_type integer, room_type integer, use_type integer, supply_light_type integer, direction_type integer, resolution character varying(255), business_group_id character varying(255), download_speed character varying(255), svc_space_support_mod integer, svc_time_support_mode integer, create_time character varying(50) not null, update_time character varying(50) not null, sub_count integer, stream_id character varying(255), has_audio bool default false, gps_time character varying(50), stream_identification character varying(50), channel_type int default 0 not null, gb_device_id character varying(50), gb_name character varying(255), gb_manufacturer character varying(255), gb_model character varying(255), gb_owner character varying(255), gb_civil_code character varying(255), gb_block character varying(255), gb_address character varying(255), gb_parental integer, gb_parent_id character varying(255), gb_safety_way integer, gb_register_way integer, gb_cert_num character varying(50), gb_certifiable integer, gb_err_code integer, gb_end_time character varying(50), gb_secrecy integer, gb_ip_address character varying(50), gb_port integer, gb_password character varying(50), gb_status character varying(50), gb_longitude double, gb_latitude double, gb_business_group_id character varying(50), gb_ptz_type integer, gb_position_type integer, gb_room_type integer, gb_use_type integer, gb_supply_light_type integer, gb_direction_type integer, gb_resolution character varying(255), gb_download_speed character varying(255), gb_svc_space_support_mod integer, gb_svc_time_support_mode integer, record_plan_id integer, data_type integer not null, data_device_id integer not null, gps_speed double precision, gps_altitude double precision, gps_direction double precision, constraint uk_wvp_unique_channel unique (gb_device_id) ); create table IF NOT EXISTS wvp_media_server ( id character varying(255) primary key, ip character varying(50), hook_ip character varying(50), sdp_ip character varying(50), stream_ip character varying(50), http_port integer, http_ssl_port integer, rtmp_port integer, rtmp_ssl_port integer, rtp_proxy_port integer, rtsp_port integer, rtsp_ssl_port integer, flv_port integer, flv_ssl_port integer, ws_flv_port integer, ws_flv_ssl_port integer, auto_config bool default false, secret character varying(50), type character varying(50) default 'zlm', rtp_enable bool default false, rtp_port_range character varying(50), send_rtp_port_range character varying(50), record_assist_port integer, default_server bool default false, create_time character varying(50), update_time character varying(50), hook_alive_interval integer, record_path character varying(255), record_day integer default 7, transcode_suffix character varying(255), server_id character varying(50), constraint uk_media_server_unique_ip_http_port unique (ip, http_port, server_id) ); create table IF NOT EXISTS wvp_platform ( id bigint primary key, enable bool default false, name character varying(255), server_gb_id character varying(50), server_gb_domain character varying(50), server_ip character varying(50), server_port integer, device_gb_id character varying(50), device_ip character varying(50), device_port character varying(50), username character varying(255), password character varying(50), expires character varying(50), keep_timeout character varying(50), transport character varying(50), civil_code character varying(50), manufacturer character varying(255), model character varying(255), address character varying(255), character_set character varying(50), ptz bool default false, rtcp bool default false, status bool default false, catalog_group integer, register_way integer, secrecy integer, create_time character varying(50), update_time character varying(50), as_message_channel bool default false, catalog_with_platform integer default 1, catalog_with_group integer default 1, catalog_with_region integer default 1, auto_push_channel bool default true, send_stream_ip character varying(50), server_id character varying(50), constraint uk_platform_unique_server_gb_id unique (server_gb_id) ); create table IF NOT EXISTS wvp_platform_channel ( id bigint primary key, platform_id integer, device_channel_id integer, custom_device_id character varying(50), custom_name character varying(255), custom_manufacturer character varying(50), custom_model character varying(50), custom_owner character varying(50), custom_civil_code character varying(50), custom_block character varying(50), custom_address character varying(50), custom_parental integer, custom_parent_id character varying(50), custom_safety_way integer, custom_register_way integer, custom_cert_num character varying(50), custom_certifiable integer, custom_err_code integer, custom_end_time character varying(50), custom_secrecy integer, custom_ip_address character varying(50), custom_port integer, custom_password character varying(255), custom_status character varying(50), custom_longitude double precision, custom_latitude double precision, custom_ptz_type integer, custom_position_type integer, custom_room_type integer, custom_use_type integer, custom_supply_light_type integer, custom_direction_type integer, custom_resolution character varying(255), custom_business_group_id character varying(255), custom_download_speed character varying(255), custom_svc_space_support_mod integer, custom_svc_time_support_mode integer, constraint uk_platform_gb_channel_platform_id_catalog_id_device_channel_id unique (platform_id, device_channel_id), constraint uk_platform_gb_channel_device_id unique (custom_device_id) ); create table IF NOT EXISTS wvp_platform_group ( id bigint primary key, platform_id integer, group_id integer, constraint uk_wvp_platform_group_platform_id_group_id unique (platform_id, group_id) ); create table IF NOT EXISTS wvp_platform_region ( id bigint primary key, platform_id integer, region_id integer, constraint uk_wvp_platform_region_platform_id_group_id unique (platform_id, region_id) ); create table IF NOT EXISTS wvp_stream_proxy ( id bigint primary key, type character varying(50), app character varying(255), stream character varying(255), src_url character varying(255), timeout integer, ffmpeg_cmd_key character varying(255), rtsp_type character varying(50), media_server_id character varying(50), enable_audio bool default false, enable_mp4 bool default false, pulling bool default false, enable bool default false, enable_remove_none_reader bool default false, create_time character varying(50), name character varying(255), update_time character varying(50), stream_key character varying(255), server_id character varying(50), enable_disable_none_reader bool default false, relates_media_server_id character varying(50), constraint uk_stream_proxy_app_stream unique (app, stream) ); create table IF NOT EXISTS wvp_stream_push ( id bigint primary key, app character varying(255), stream character varying(255), create_time character varying(50), media_server_id character varying(50), server_id character varying(50), push_time character varying(50), status bool default false, update_time character varying(50), pushing bool default false, self bool default false, start_offline_push bool default true, constraint uk_stream_push_app_stream unique (app, stream) ); create table IF NOT EXISTS wvp_cloud_record ( id bigint primary key, app character varying(255), stream character varying(255), call_id character varying(255), start_time bigint, end_time bigint, media_server_id character varying(50), server_id character varying(50), file_name character varying(255), folder character varying(500), file_path character varying(500), collect bool default false, file_size bigint, time_len double precision ); create table IF NOT EXISTS wvp_user ( id bigint primary key, username character varying(255), password character varying(255), role_id integer, create_time character varying(50), update_time character varying(50), push_key character varying(50), constraint uk_user_username unique (username) ); create table IF NOT EXISTS wvp_user_role ( id bigint primary key, name character varying(50), authority character varying(50), create_time character varying(50), update_time character varying(50) ); create table IF NOT EXISTS wvp_user_api_key ( id bigint primary key, user_id bigint, app character varying(255), api_key text, expired_at bigint, remark character varying(255), enable bool default true, create_time character varying(50), update_time character varying(50) ); create table IF NOT EXISTS wvp_common_group ( id bigint primary key, device_id varchar(50) NOT NULL, name varchar(255) NOT NULL, parent_id int, parent_device_id varchar(50) DEFAULT NULL, business_group varchar(50) NOT NULL, create_time varchar(50) NOT NULL, update_time varchar(50) NOT NULL, civil_code varchar(50) default null, constraint uk_common_group_device_platform unique (device_id) ); create table IF NOT EXISTS wvp_common_region ( id bigint primary key, device_id varchar(50) NOT NULL, name varchar(255) NOT NULL, parent_id int, parent_device_id varchar(50) DEFAULT NULL, create_time varchar(50) NOT NULL, update_time varchar(50) NOT NULL, constraint uk_common_region_device_id unique (device_id) ); create table IF NOT EXISTS wvp_record_plan ( id bigint primary key, snap bool default false, name varchar(255) NOT NULL, create_time character varying(50), update_time character varying(50) ); create table IF NOT EXISTS wvp_record_plan_item ( id bigint primary key, start int, stop int, week_day int, plan_id int, create_time character varying(50), update_time character varying(50) ); ================================================ FILE: 数据库/old/2.6.6-2.6.7更新.sql ================================================ alter table device add asMessageChannel int default 0; alter table parent_platform add asMessageChannel int default 0; alter table device add mediaServerId varchar(50) default null; ALTER TABLE device ADD COLUMN `switchPrimarySubStream` bit(1) NOT NULL DEFAULT b'0' COMMENT '开启主子码流切换的开关(0-不开启,1-开启)现在已知支持设备为 大华、TP——LINK全系设备' AFTER `keepalive_interval_time` ================================================ FILE: 数据库/old/2.6.8升级2.6.9.sql ================================================ alter table device change deviceId device_id varchar(50) not null; alter table device change streamMode stream_mode varchar(50) null; alter table device change registerTime register_time varchar(50) null; alter table device change keepaliveTime keepalive_time varchar(50) null; alter table device change createTime create_time varchar(50) not null; alter table device change updateTime update_time varchar(50) not null; alter table device change subscribeCycleForCatalog subscribe_cycle_for_catalog bool default false; alter table device change subscribeCycleForMobilePosition subscribe_cycle_for_mobile_position bool default false; alter table device change mobilePositionSubmissionInterval mobile_position_submission_interval int default 5 not null; alter table device change subscribeCycleForAlarm subscribe_cycle_for_alarm bool default false; alter table device change hostAddress host_address varchar(50) null; alter table device change ssrcCheck ssrc_check bool default false; alter table device change geoCoordSys geo_coord_sys varchar(50) not null; alter table device drop column treeType; alter table device change mediaServerId media_server_id varchar(50) default 'auto' null; alter table device change sdpIp sdp_ip varchar(50) null; alter table device change localIp local_ip varchar(50) null; alter table device change asMessageChannel as_message_channel bool default false; alter table device change keepaliveIntervalTime keepalive_interval_time int null; alter table device change online on_line varchar(50) null; alter table device add COLUMN switch_primary_sub_stream bool default false comment '开启主子码流切换的开关(0-不开启,1-开启)现在已知支持设备为 大华、TP——LINK全系设备' alter table device_alarm change deviceId device_id varchar(50) not null; alter table device_alarm change channelId channel_id varchar(50) not null; alter table device_alarm change alarmPriority alarm_priority varchar(50) not null; alter table device_alarm change alarmMethod alarm_method varchar(50) null; alter table device_alarm change alarmTime alarm_time varchar(50) not null; alter table device_alarm change alarmDescription alarm_description varchar(255) null; alter table device_alarm change alarmType alarm_type varchar(50) null; alter table device_alarm change createTime create_time varchar(50) null; alter table device_channel change channelId channel_id varchar(50) not null; alter table device_channel change civilCode civil_code varchar(50) null; alter table device_channel change parentId parent_id varchar(50) null; alter table device_channel change safetyWay safety_way int null; alter table device_channel change registerWay register_way int null; alter table device_channel change certNum cert_num varchar(50) null; alter table device_channel change errCode err_code int null; alter table device_channel change endTime end_time varchar(50) null; alter table device_channel change ipAddress ip_address varchar(50) null; alter table device_channel change PTZType ptz_type int null; alter table device_channel change status status bool default false; alter table device_channel change streamId stream_id varchar(255) null; alter table device_channel change deviceId device_id varchar(50) not null; alter table device_channel change hasAudio has_audio bool default false; alter table device_channel change createTime create_time varchar(50) not null; alter table device_channel change updateTime update_time varchar(50) not null; alter table device_channel change subCount sub_count int default 0 null; alter table device_channel change longitudeGcj02 longitude_gcj02 double null; alter table device_channel change latitudeGcj02 latitude_gcj02 double null; alter table device_channel change longitudeWgs84 longitude_wgs84 double null; alter table device_channel change latitudeWgs84 latitude_wgs84 double null; alter table device_channel change businessGroupId business_group_id varchar(50) null; alter table device_channel change gpsTime gps_time varchar(50) null; alter table device_mobile_position change deviceId device_id varchar(50) not null; alter table device_mobile_position change channelId channel_id varchar(50) not null; alter table device_mobile_position change deviceName device_name varchar(255) null; alter table device_mobile_position change reportSource report_source varchar(50) null; alter table device_mobile_position change longitudeGcj02 longitude_gcj02 double null; alter table device_mobile_position change latitudeGcj02 latitude_gcj02 double null; alter table device_mobile_position change longitudeWgs84 longitude_wgs84 double null; alter table device_mobile_position change latitudeWgs84 latitude_wgs84 double null; alter table device_mobile_position change createTime create_time varchar(50) null; alter table gb_stream change gbStreamId gb_stream_id int auto_increment; alter table gb_stream change gbId gb_id varchar(50) not null; alter table gb_stream change streamType stream_type varchar(50) null; alter table gb_stream change mediaServerId media_server_id varchar(50) null; alter table gb_stream change createTime create_time varchar(50) null; alter table log change createTime create_time varchar(50) not null; alter table media_server change hookIp hook_ip varchar(50) not null; alter table media_server add send_rtp_port_range varchar(50) not null; alter table media_server add column send_rtp_port_range varchar(50) default null; alter table media_server change sdpIp sdp_ip varchar(50) not null; alter table media_server change streamIp stream_ip varchar(50) not null; alter table media_server change httpPort http_port int not null; alter table media_server change httpSSlPort http_ssl_port int not null; alter table media_server change rtmpPort rtmp_port int not null; alter table media_server change rtmpSSlPort rtmp_ssl_port int not null; alter table media_server change rtpProxyPort rtp_proxy_port int not null; alter table media_server change rtspPort rtsp_port int not null; alter table media_server change rtspSSLPort rtsp_ssl_port int not null; alter table media_server change autoConfig auto_config bool default true; alter table media_server change rtpEnable rtp_enable bool default false; alter table media_server change rtpPortRange rtp_port_range varchar(50) not null; alter table media_server change recordAssistPort record_assist_port int not null; alter table media_server change defaultServer default_server bool default false; alter table media_server change createTime create_time varchar(50) not null; alter table media_server change updateTime update_time varchar(50) not null; alter table media_server change hookAliveInterval hook_alive_interval int not null; alter table parent_platform change serverGBId server_gb_id varchar(50) not null; alter table parent_platform change serverGBDomain server_gb_domain varchar(50) null; alter table parent_platform change serverIP server_ip varchar(50) null; alter table parent_platform change serverPort server_port int null; alter table parent_platform change deviceGBId device_gb_id varchar(50) not null; alter table parent_platform change deviceIp device_ip varchar(50) null; alter table parent_platform change devicePort device_port varchar(50) null; alter table parent_platform change keepTimeout keep_timeout varchar(50) null; alter table parent_platform change characterSet character_set varchar(50) null; alter table parent_platform change catalogId catalog_id varchar(50) not null; alter table parent_platform change startOfflinePush start_offline_push bool default false; alter table parent_platform change administrativeDivision administrative_division varchar(50) not null; alter table parent_platform change catalogGroup catalog_group int default 1 null; alter table parent_platform change createTime create_time varchar(50) null; alter table parent_platform change updateTime update_time varchar(50) null; alter table parent_platform drop column treeType; alter table parent_platform change asMessageChannel as_message_channel bool default false; alter table parent_platform change enable enable bool default false; alter table parent_platform change ptz ptz bool default false; alter table parent_platform change rtcp rtcp bool default false; alter table parent_platform change status status bool default false; alter table parent_platform change status status bool default false; alter table platform_catalog change platformId platform_id varchar(50) not null; alter table platform_catalog change parentId parent_id varchar(50) null; alter table platform_catalog change civilCode civil_code varchar(50) null; alter table platform_catalog change businessGroupId business_group_id varchar(50) null; alter table platform_gb_channel change platformId platform_id varchar(50) not null; alter table platform_gb_channel change catalogId catalog_id varchar(50) not null; alter table platform_gb_channel change deviceChannelId device_channel_id int not null; alter table platform_gb_stream change platformId platform_id varchar(50) not null; alter table platform_gb_stream change catalogId catalog_id varchar(50) not null; alter table platform_gb_stream change gbStreamId gb_stream_id int not null; alter table stream_proxy change mediaServerId media_server_id varchar(50) null; alter table stream_proxy change createTime create_time varchar(50) not null; alter table stream_proxy change updateTime update_time varchar(50) null; alter table stream_proxy change enable_remove_none_reader enable_remove_none_reader bool default false; alter table stream_proxy change enable_disable_none_reader enable_disable_none_reader bool default false; alter table stream_proxy change enable_audio enable_audio bool default false; alter table stream_proxy change enable_mp4 enable_mp4 bool default false; alter table stream_proxy change enable enable bool default false; alter table stream_push change totalReaderCount total_reader_count varchar(50) null; alter table stream_push change originType origin_type int null; alter table stream_push change originTypeStr origin_type_str varchar(50) null; alter table stream_push change createTime create_time varchar(50) null; alter table stream_push change aliveSecond alive_second int null; alter table stream_push change mediaServerId media_server_id varchar(50) null; alter table stream_push change status status bool default false; alter table stream_push change pushTime push_time varchar(50) null; alter table stream_push change updateTime update_time varchar(50) null; alter table stream_push change pushIng push_ing bool default false; alter table stream_push change status status bool default false; alter table stream_push change self self bool default false; alter table stream_push drop column serverId; alter table user change roleId role_id int not null; alter table user change createTime create_time varchar(50) not null; alter table user change updateTime update_time varchar(50) not null; alter table user change pushKey push_key varchar(50) null; alter table user_role change createTime create_time varchar(50) not null; alter table user_role change updateTime update_time varchar(50) not null; rename table device to wvp_device; rename table device_alarm to wvp_device_alarm; rename table device_channel to wvp_device_channel; rename table device_mobile_position to wvp_device_mobile_position; rename table gb_stream to wvp_gb_stream; rename table log to wvp_log; rename table media_server to wvp_media_server; rename table parent_platform to wvp_platform; rename table platform_catalog to wvp_platform_catalog; rename table platform_gb_channel to wvp_platform_channel; rename table platform_gb_stream to wvp_platform_gb_stream; rename table stream_proxy to wvp_stream_proxy; rename table stream_push to wvp_stream_push; rename table user to wvp_user; rename table user_role to wvp_user_role; alter table wvp_device add column broadcast_push_after_ack bool default false; alter table wvp_device_channel add column custom_name varchar(255) null ; alter table wvp_device_channel add column custom_longitude double null ; alter table wvp_device_channel add column custom_latitude double null ; alter table wvp_device_channel add column custom_ptz_type int null ; create table wvp_resources_tree ( id serial primary key , is_catalog bool default true, device_channel_id integer , gb_stream_id integer, name character varying(255), parentId integer, path character varying(255) ); ================================================ FILE: 数据库/old/2.6.8补丁更新.sql ================================================ alter table media_server add sendRtpPortRange varchar(50) not null; ================================================ FILE: 数据库/old/clean.sql ================================================ delete from wvp-device; delete from wvp-device_alarm; delete from wvp-device_channel; delete from wvp-device_mobile_position; delete from wvp-gb_stream; delete from wvp-log; delete from wvp-media_server; delete from wvp-parent_platform; delete from wvp-platform_catalog; delete from wvp-platform_gb_channel; delete from wvp-platform_gb_stream; delete from wvp-stream_proxy; delete from wvp-stream_push;