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
================================================

# 开箱即用的国标28181和部标808+1078协议视频平台
[](https://travis-ci.org/xia-chu/ZLMediaKit)
[](https://github.com/xia-chu/ZLMediaKit/blob/master/LICENSE)
[](https://en.cppreference.com/)
[](https://github.com/xia-chu/ZLMediaKit)
[](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许可协议。保留版权的情况下可以用于商业项目。
- 支持多流媒体节点负载均衡。
# 付费社群
[](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管理页面点击添加

#### 1.1.2 填入wvp-pro上级平台信息


#### 1.1.3 编辑wvp-pro上级设备信息,开启订阅

### 1.2 大华平台
### 1.3 海康平台
### 1.4 liveGBS
#### 1.4.1. wvp-pro管理页面点击添加

#### 1.4.2. 填入liveGBS平台信息


#### 1.4.3. 编辑liveGBS设备信息,开启目录订阅

#### 1.4.4. 编辑liveGBS设备信息,开启GPS订阅

## 2 添加目录与通道
1. 级联平台添加目录信息

2. 为目录添加通道

3. 设置默认流目录
如果需要后续自动生成的流信息都在某一个节点下,可以在对应节点右键设置为默认

================================================
FILE: doc/_content/ability/cascade2.md
================================================
# 国标级联的使用
国标28181不同平台之间支持两种连接方式,平级和上下级,WVP目前支持向上级级联。
## 添加上级平台
在国标级联页面点击“添加”按钮,以推送到上级WVP为例子,参看[接入设备](./_content/ability/device.md)

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:更新;
- 推送平台信息
勾选此项,上级收到的通道信息中会多出一个平台信息的通道.内容在平台的编辑中修改;
- 推送分组信息
勾选此项,如果你共享的通道分配了具体的业务分组以及虚拟组织,那么上级收到的通道中会包括业务分组以及虚拟组织节点信息;
- 推送行政区划
勾选此项,如果你共享的通道分配了具体的行政区划,那么上级收到的通道中会包括行政区划信息;
国标级联列表出现了级联的这个平台;同时状态显示为在线,如果状态为离线那么可能是你的服务信息配置有误或者网络不通。
订阅信息列有三个图标,表示上级开启订阅,从左到右依次是:报警订阅,目录订阅,移动位置订阅。
## 通道共享
点击你要推送的平台的“通道共享”按钮。

1. 添加状态选择"未共享"可以将具体的通道共享给上级;
2. 添加状态选择"已共享"可以看到已经共享的通道,并且支持为这个通道在这个平台设备专门的名称和编号;
3. 点击"按设备添加"可以将某个国标设备下的所有通道共享给上级;
4. 点击"按设备移除"可以将某个国标设备下的所有通道取消共享给上级;
5. 点击"全部添加"可以将所有通道共享给上级;
6. 点击"全部移除"可以将所有通道共享给上级;
## 推送通道
WVP会将所有通道信息按照目录订阅消息通知形式,发送ADD事件给上级.
================================================
FILE: doc/_content/ability/channel.md
================================================
# 通道管理
通道管理为了对已经分配国标编号的通道进行统一的行政区划和业务分组管理,国标中对于组织结构有两种表示方式,一种是按照行政区划,一种是业务分组+虚拟组织的方式.
行政区划结构固定,比如: 北京/市辖区/昌平区, 通道可以挂载道何一级行政区划下. 业务分组比较灵活, 可以按照自己的随意取名,
但是通道只能放在业务分组下的虚拟组织里,不能放在业务分组下.
## 行政区划
左侧树结构为行政区划结构, 通过数据鼠标右键可以操作,包括: 刷新节点,新建节点,编辑节点,删除节点,添加设备(
可以将某个国标设备下的通道全部添加道某一个节点下),移除设备(可以将某个国标设备下的通道全部从这个节点移除)
右侧伪通道列表, 对于非国标接入的设备只有配置了国标编号后才可以在这里进行操作。
选择左侧的节点后,可以点击右侧的“添加通道”, 选择需要的通道添加到改节点下,如果找不到通道, 可以选择“异常挂载通道”,点击清理后重新回来选择。

## 业务分组
左侧树结构为业务分组结构, 通过数据鼠标右键可以操作,包括: 刷新节点,新建节点,编辑节点,删除节点,添加设备(
可以将某个国标设备下的通道全部添加道某一个节点下),移除设备(可以将某个国标设备下的通道全部从这个节点移除)
业务分组下不能挂载设备,所以没有选择该节点的单选框.
右侧为通道列表, 对于非国标接入的设备只有配置了国标编号后才可以在这里进行操作。
选择左侧的节点后,可以点击右侧的“添加通道”, 选择需要的通道添加到改节点下。
如果找不到通道, 可以选择“异常挂载通道”,点击清理后重新回来选择。
注意,根资源组下的那一级为业务分组类型不可以直接挂载设备,需要继续建立节点,后续的节点的都是虚拟组织类型, 就可以挂载通道了。

================================================
FILE: doc/_content/ability/cloud_record.md
================================================
# 云端录像

云端录像是对录制在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服务密码
- 配置信息在如下位置

***
### 1. 大华摄像头

### 2. 大华NVR

### 3. 宇视科技

### 3. 艾科威视摄像头

### 4. 水星摄像头

### 5. 海康摄像头

## 直播推流设备
这里以obs推流为例,很多无人机也是一样的,设置下推流地址就可以接入了
1. 从wvp获取推流地址, 选择节点管理菜单,查看要推流的节点;

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
================================================
# 国标设备
### 更新设备通道
点击列表末尾的“刷新”按钮,可以看到一个圆形进度条,等进度结束提示成功后即可更新完成,如果通道数量有变化你可以看点击左上角的
即可看到通道数量的变化;如果通道数量仍未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
================================================
# 节点管理

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克隆,也可以在项目下载点击下载


从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目录
**编译完成一般是这个样子,中间没有报红的错误信息**

### 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
## 社群
[](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端口占用了,这些都会导致启动是报错,修改配置配置之后都可以解决;
下面我整理的一些常见的错误,大家可以先对号入座的简单排查下。
> **常见错误**

**错误原因:** redis配置错误,可能原因: redis未启动/ip错误/端口错误/网络不通
---

**错误原因:** redis配置错误,可能原因: 密码错误
---

**错误原因:** mysql配置错误,可能原因: mysql未启动/ip错误/端口错误/网络不通
---

**错误原因:** mysql配置错误,可能原因: 用户名/密码错误
---

**错误原因:** SIP配置错误,可能原因: SIP端口被占用
---

**错误原因:** 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`
详细的过滤规则可以自行百度,我可以提供一些常用的给大家参考

**只过滤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`获取网卡信息,如下所示:

```shell
sudo tcpdump -i wlp3s0 -w demo.pcap
```

命令行会停留在这个位置,此时可以进行操作,比如点播,录像回放等,操作完成回到命令行使用`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。
> 类型编码指定了设备或用户的具体类型,其中的前端设备包含公安系统和非公安系统的前端设备,终端用
> 户包含公安系统和非公安系统的终端用户。



## D.2 编码规则 B
> 编码规则 B由中心编码(8位)、行业编码(2位)、序号(4位)和类型编码(2位)四个码段构成,即系
> 统编码 =中心编码 + 行业编码 +序号+类型编码。编码规则 B的详细说明见表 D.2。


## D.3 行业编码对照表
> 行业编码对照表见表 D.3。


================================================
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
================================================

# 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]: <> ())
================================================
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]*?(?:\\1>[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>\\n*|$)|\\n*|$)|\\n*|$)|?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:\\n{2,}|$)|<(?!script|pre|style)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:\\n{2,}|$)|(?!script|pre|style)[a-z][\\w-]*\\s*>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:\\n{2,}|$))",def:/^ {0,3}\[(label)\]: *\n? *([^\s>]+)>?(?:(?: +\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","?(?:tag)(?: +|\\n|/?>)|<(?: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","?(?:tag)(?: +|\\n|/?>)|<(?: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","?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|!--)").replace("tag",Ne._tag).getRegex(),Ne.pedantic=De({},Ne.normal,{html:Ve("^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+?\\1> *(?:\\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:/^ *\[([^\]]+)\]: *([^\s>]+)>?(?: +(["(][^\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:-]*\\s*>|^<[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?"\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+""+o+">\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+""+i+">\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='"+i+""},e.prototype.image=function(e,n,i){if(null===(e=dn(this.options.sanitize,this.options.baseUrl,e)))return 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=''),!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""+e+";"}).join("").concat("︎")+"":'
':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+""+t.tag+">"},!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"},/?[\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":'
"}),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+""+n+">"},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%",'')),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||"",""+('')+''),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