瀏覽代碼

‘视频接入项目提交’

shilin 3 年之前
當前提交
9a5cd2df1d
共有 100 個文件被更改,包括 16756 次插入0 次删除
  1. 8 0
      .idea/.gitignore
  2. 16 0
      .idea/compiler.xml
  3. 7 0
      .idea/encodings.xml
  4. 20 0
      .idea/jarRepositories.xml
  5. 13 0
      .idea/libraries/Maven__cn_hutool_hutool_all_4_6_1.xml
  6. 13 0
      .idea/libraries/Maven__com_alibaba_druid_1_1_21.xml
  7. 13 0
      .idea/libraries/Maven__com_google_protobuf_protobuf_java_3_6_1.xml
  8. 13 0
      .idea/libraries/Maven__com_jfinal_jfinal_4_9_06.xml
  9. 13 0
      .idea/libraries/Maven__com_jfinal_jfinal_undertow_2_4.xml
  10. 13 0
      .idea/libraries/Maven__io_undertow_undertow_core_2_0_33_Final.xml
  11. 13 0
      .idea/libraries/Maven__io_undertow_undertow_servlet_2_0_33_Final.xml
  12. 13 0
      .idea/libraries/Maven__io_undertow_undertow_websockets_jsr_2_0_33_Final.xml
  13. 13 0
      .idea/libraries/Maven__javax_servlet_javax_servlet_api_4_0_1.xml
  14. 13 0
      .idea/libraries/Maven__log4j_log4j_1_2_17.xml
  15. 13 0
      .idea/libraries/Maven__mysql_mysql_connector_java_8_0_18.xml
  16. 13 0
      .idea/libraries/Maven__net_java_dev_jna_jna_5_4_0.xml
  17. 13 0
      .idea/libraries/Maven__net_java_dev_jna_jna_platform_5_4_0.xml
  18. 13 0
      .idea/libraries/Maven__org_jboss_logging_jboss_logging_3_4_0_Final.xml
  19. 13 0
      .idea/libraries/Maven__org_jboss_spec_javax_websocket_jboss_websocket_api_1_1_spec_1_1_4_Final.xml
  20. 13 0
      .idea/libraries/Maven__org_jboss_xnio_xnio_api_3_3_8_Final.xml
  21. 13 0
      .idea/libraries/Maven__org_jboss_xnio_xnio_nio_3_3_8_Final.xml
  22. 13 0
      .idea/libraries/Maven__org_slf4j_slf4j_api_1_7_25.xml
  23. 13 0
      .idea/libraries/Maven__org_slf4j_slf4j_log4j12_1_7_25.xml
  24. 11 0
      .idea/misc.xml
  25. 8 0
      .idea/modules.xml
  26. 6 0
      .idea/vcs.xml
  27. 127 0
      LICENSE
  28. 70 0
      Readme.md
  29. 15 0
      db.sql
  30. 3203 0
      logs/monitor-rtsp-hls.log
  31. 33 0
      monitor-rtsp-hls.iml
  32. 51 0
      package.xml
  33. 137 0
      pom.xml
  34. 93 0
      src/main/java/com/rancedxk/monitor/Config.java
  35. 54 0
      src/main/java/com/rancedxk/monitor/Main.java
  36. 20 0
      src/main/java/com/rancedxk/monitor/controller/ApiController.java
  37. 16 0
      src/main/java/com/rancedxk/monitor/controller/LiveController.java
  38. 98 0
      src/main/java/com/rancedxk/monitor/controller/LiveSocket.java
  39. 54 0
      src/main/java/com/rancedxk/monitor/controller/ProcessController.java
  40. 35 0
      src/main/java/com/rancedxk/monitor/device/Device.java
  41. 28 0
      src/main/java/com/rancedxk/monitor/device/DeviceManager.java
  42. 17 0
      src/main/java/com/rancedxk/monitor/device/provider/IDeviceProvider.java
  43. 63 0
      src/main/java/com/rancedxk/monitor/device/provider/impl/DbProvider.java
  44. 43 0
      src/main/java/com/rancedxk/monitor/device/provider/impl/DefaultProvider.java
  45. 180 0
      src/main/java/com/rancedxk/monitor/task/Task.java
  46. 48 0
      src/main/java/com/rancedxk/monitor/task/TaskManager.java
  47. 41 0
      src/main/java/com/rancedxk/monitor/utils/B.java
  48. 270 0
      src/main/java/com/rancedxk/monitor/utils/C.java
  49. 80 0
      src/main/java/com/rancedxk/monitor/utils/D.java
  50. 86 0
      src/main/java/com/rancedxk/monitor/utils/F.java
  51. 39 0
      src/main/java/com/rancedxk/monitor/utils/H.java
  52. 44 0
      src/main/java/com/rancedxk/monitor/utils/N.java
  53. 88 0
      src/main/java/com/rancedxk/monitor/utils/S.java
  54. 37 0
      src/main/resources/config.properties
  55. 17 0
      src/main/resources/log4j.properties
  56. 32 0
      src/main/resources/monitor.properties
  57. 49 0
      src/main/resources/nginx.template
  58. 108 0
      src/main/webapp/page/live/index.html
  59. 1 0
      src/main/webapp/static/plugins/element/element.min.css
  60. 39 0
      src/main/webapp/static/plugins/element/element.min.js
  61. 二進制
      src/main/webapp/static/plugins/element/fonts/element-icons.ttf
  62. 二進制
      src/main/webapp/static/plugins/element/fonts/element-icons.woff
  63. 2 0
      src/main/webapp/static/plugins/hls/hls.min.js
  64. 9205 0
      src/main/webapp/static/plugins/jquery/jquery.js
  65. 5 0
      src/main/webapp/static/plugins/jquery/jquery.min.js
  66. 381 0
      src/main/webapp/static/plugins/vue/plugins/draggable/vuedraggable.js
  67. 1 0
      src/main/webapp/static/plugins/vue/plugins/draggable/vuedraggable.min.js
  68. 6 0
      src/main/webapp/static/plugins/vue/vue.min.js
  69. 34 0
      start.bat
  70. 15 0
      stop.bat
  71. 37 0
      target/monitor-rtsp-hls/conf/config.properties
  72. 17 0
      target/monitor-rtsp-hls/conf/log4j.properties
  73. 32 0
      target/monitor-rtsp-hls/conf/monitor.properties
  74. 49 0
      target/monitor-rtsp-hls/conf/nginx.template
  75. 二進制
      target/monitor-rtsp-hls/lib/druid-1.1.21.jar
  76. 二進制
      target/monitor-rtsp-hls/lib/hutool-all-4.6.1.jar
  77. 二進制
      target/monitor-rtsp-hls/lib/javax.servlet-api-4.0.1.jar
  78. 二進制
      target/monitor-rtsp-hls/lib/jboss-logging-3.4.0.Final.jar
  79. 二進制
      target/monitor-rtsp-hls/lib/jboss-websocket-api_1.1_spec-1.1.4.Final.jar
  80. 二進制
      target/monitor-rtsp-hls/lib/jfinal-4.9.06.jar
  81. 二進制
      target/monitor-rtsp-hls/lib/jfinal-undertow-2.4.jar
  82. 二進制
      target/monitor-rtsp-hls/lib/jna-5.4.0.jar
  83. 二進制
      target/monitor-rtsp-hls/lib/jna-platform-5.4.0.jar
  84. 二進制
      target/monitor-rtsp-hls/lib/log4j-1.2.17.jar
  85. 二進制
      target/monitor-rtsp-hls/lib/monitor-rtsp-hls-1.0.jar
  86. 二進制
      target/monitor-rtsp-hls/lib/mysql-connector-java-8.0.18.jar
  87. 二進制
      target/monitor-rtsp-hls/lib/protobuf-java-3.6.1.jar
  88. 二進制
      target/monitor-rtsp-hls/lib/slf4j-api-1.7.25.jar
  89. 二進制
      target/monitor-rtsp-hls/lib/slf4j-log4j12-1.7.25.jar
  90. 二進制
      target/monitor-rtsp-hls/lib/undertow-core-2.0.33.Final.jar
  91. 二進制
      target/monitor-rtsp-hls/lib/undertow-servlet-2.0.33.Final.jar
  92. 二進制
      target/monitor-rtsp-hls/lib/undertow-websockets-jsr-2.0.33.Final.jar
  93. 二進制
      target/monitor-rtsp-hls/lib/xnio-api-3.3.8.Final.jar
  94. 二進制
      target/monitor-rtsp-hls/lib/xnio-nio-3.3.8.Final.jar
  95. 1275 0
      target/monitor-rtsp-hls/logs/monitor-rtsp-hls.log
  96. 34 0
      target/monitor-rtsp-hls/start.bat
  97. 15 0
      target/monitor-rtsp-hls/stop.bat
  98. 9 0
      target/monitor-rtsp-hls/third/Readme.md
  99. 二進制
      target/monitor-rtsp-hls/third/ffmpeg/ffmpeg.exe
  100. 0 0
      target/monitor-rtsp-hls/third/hls/SG01_05_TD/index.m3u8

+ 8 - 0
.idea/.gitignore

@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
+# Editor-based HTTP Client requests
+/httpRequests/

+ 16 - 0
.idea/compiler.xml

@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="CompilerConfiguration">
+    <annotationProcessing>
+      <profile name="Maven default annotation processors profile" enabled="true">
+        <sourceOutputDir name="target/generated-sources/annotations" />
+        <sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
+        <outputRelativeToContentRoot value="true" />
+        <module name="monitor-rtsp-hls" />
+      </profile>
+    </annotationProcessing>
+    <bytecodeTargetLevel>
+      <module name="monitor-rtsp-hls" target="1.8" />
+    </bytecodeTargetLevel>
+  </component>
+</project>

+ 7 - 0
.idea/encodings.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="Encoding" native2AsciiForPropertiesFiles="true" defaultCharsetForPropertiesFiles="UTF-8">
+    <file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
+  </component>
+</project>

+ 20 - 0
.idea/jarRepositories.xml

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="RemoteRepositoriesConfiguration">
+    <remote-repository>
+      <option name="id" value="central" />
+      <option name="name" value="Maven Central repository" />
+      <option name="url" value="https://repo1.maven.org/maven2" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="central" />
+      <option name="name" value="Central Repository" />
+      <option name="url" value="http://maven.aliyun.com/nexus/content/groups/public" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="jboss.community" />
+      <option name="name" value="JBoss Community repository" />
+      <option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
+    </remote-repository>
+  </component>
+</project>

+ 13 - 0
.idea/libraries/Maven__cn_hutool_hutool_all_4_6_1.xml

@@ -0,0 +1,13 @@
+<component name="libraryTable">
+  <library name="Maven: cn.hutool:hutool-all:4.6.1">
+    <CLASSES>
+      <root url="jar://$USER_HOME$/.m2/repository/cn/hutool/hutool-all/4.6.1/hutool-all-4.6.1.jar!/" />
+    </CLASSES>
+    <JAVADOC>
+      <root url="jar://$USER_HOME$/.m2/repository/cn/hutool/hutool-all/4.6.1/hutool-all-4.6.1-javadoc.jar!/" />
+    </JAVADOC>
+    <SOURCES>
+      <root url="jar://$USER_HOME$/.m2/repository/cn/hutool/hutool-all/4.6.1/hutool-all-4.6.1-sources.jar!/" />
+    </SOURCES>
+  </library>
+</component>

+ 13 - 0
.idea/libraries/Maven__com_alibaba_druid_1_1_21.xml

@@ -0,0 +1,13 @@
+<component name="libraryTable">
+  <library name="Maven: com.alibaba:druid:1.1.21">
+    <CLASSES>
+      <root url="jar://$USER_HOME$/.m2/repository/com/alibaba/druid/1.1.21/druid-1.1.21.jar!/" />
+    </CLASSES>
+    <JAVADOC>
+      <root url="jar://$USER_HOME$/.m2/repository/com/alibaba/druid/1.1.21/druid-1.1.21-javadoc.jar!/" />
+    </JAVADOC>
+    <SOURCES>
+      <root url="jar://$USER_HOME$/.m2/repository/com/alibaba/druid/1.1.21/druid-1.1.21-sources.jar!/" />
+    </SOURCES>
+  </library>
+</component>

+ 13 - 0
.idea/libraries/Maven__com_google_protobuf_protobuf_java_3_6_1.xml

@@ -0,0 +1,13 @@
+<component name="libraryTable">
+  <library name="Maven: com.google.protobuf:protobuf-java:3.6.1">
+    <CLASSES>
+      <root url="jar://$USER_HOME$/.m2/repository/com/google/protobuf/protobuf-java/3.6.1/protobuf-java-3.6.1.jar!/" />
+    </CLASSES>
+    <JAVADOC>
+      <root url="jar://$USER_HOME$/.m2/repository/com/google/protobuf/protobuf-java/3.6.1/protobuf-java-3.6.1-javadoc.jar!/" />
+    </JAVADOC>
+    <SOURCES>
+      <root url="jar://$USER_HOME$/.m2/repository/com/google/protobuf/protobuf-java/3.6.1/protobuf-java-3.6.1-sources.jar!/" />
+    </SOURCES>
+  </library>
+</component>

+ 13 - 0
.idea/libraries/Maven__com_jfinal_jfinal_4_9_06.xml

@@ -0,0 +1,13 @@
+<component name="libraryTable">
+  <library name="Maven: com.jfinal:jfinal:4.9.06">
+    <CLASSES>
+      <root url="jar://$USER_HOME$/.m2/repository/com/jfinal/jfinal/4.9.06/jfinal-4.9.06.jar!/" />
+    </CLASSES>
+    <JAVADOC>
+      <root url="jar://$USER_HOME$/.m2/repository/com/jfinal/jfinal/4.9.06/jfinal-4.9.06-javadoc.jar!/" />
+    </JAVADOC>
+    <SOURCES>
+      <root url="jar://$USER_HOME$/.m2/repository/com/jfinal/jfinal/4.9.06/jfinal-4.9.06-sources.jar!/" />
+    </SOURCES>
+  </library>
+</component>

+ 13 - 0
.idea/libraries/Maven__com_jfinal_jfinal_undertow_2_4.xml

@@ -0,0 +1,13 @@
+<component name="libraryTable">
+  <library name="Maven: com.jfinal:jfinal-undertow:2.4">
+    <CLASSES>
+      <root url="jar://$USER_HOME$/.m2/repository/com/jfinal/jfinal-undertow/2.4/jfinal-undertow-2.4.jar!/" />
+    </CLASSES>
+    <JAVADOC>
+      <root url="jar://$USER_HOME$/.m2/repository/com/jfinal/jfinal-undertow/2.4/jfinal-undertow-2.4-javadoc.jar!/" />
+    </JAVADOC>
+    <SOURCES>
+      <root url="jar://$USER_HOME$/.m2/repository/com/jfinal/jfinal-undertow/2.4/jfinal-undertow-2.4-sources.jar!/" />
+    </SOURCES>
+  </library>
+</component>

+ 13 - 0
.idea/libraries/Maven__io_undertow_undertow_core_2_0_33_Final.xml

@@ -0,0 +1,13 @@
+<component name="libraryTable">
+  <library name="Maven: io.undertow:undertow-core:2.0.33.Final">
+    <CLASSES>
+      <root url="jar://$USER_HOME$/.m2/repository/io/undertow/undertow-core/2.0.33.Final/undertow-core-2.0.33.Final.jar!/" />
+    </CLASSES>
+    <JAVADOC>
+      <root url="jar://$USER_HOME$/.m2/repository/io/undertow/undertow-core/2.0.33.Final/undertow-core-2.0.33.Final-javadoc.jar!/" />
+    </JAVADOC>
+    <SOURCES>
+      <root url="jar://$USER_HOME$/.m2/repository/io/undertow/undertow-core/2.0.33.Final/undertow-core-2.0.33.Final-sources.jar!/" />
+    </SOURCES>
+  </library>
+</component>

+ 13 - 0
.idea/libraries/Maven__io_undertow_undertow_servlet_2_0_33_Final.xml

@@ -0,0 +1,13 @@
+<component name="libraryTable">
+  <library name="Maven: io.undertow:undertow-servlet:2.0.33.Final">
+    <CLASSES>
+      <root url="jar://$USER_HOME$/.m2/repository/io/undertow/undertow-servlet/2.0.33.Final/undertow-servlet-2.0.33.Final.jar!/" />
+    </CLASSES>
+    <JAVADOC>
+      <root url="jar://$USER_HOME$/.m2/repository/io/undertow/undertow-servlet/2.0.33.Final/undertow-servlet-2.0.33.Final-javadoc.jar!/" />
+    </JAVADOC>
+    <SOURCES>
+      <root url="jar://$USER_HOME$/.m2/repository/io/undertow/undertow-servlet/2.0.33.Final/undertow-servlet-2.0.33.Final-sources.jar!/" />
+    </SOURCES>
+  </library>
+</component>

+ 13 - 0
.idea/libraries/Maven__io_undertow_undertow_websockets_jsr_2_0_33_Final.xml

@@ -0,0 +1,13 @@
+<component name="libraryTable">
+  <library name="Maven: io.undertow:undertow-websockets-jsr:2.0.33.Final">
+    <CLASSES>
+      <root url="jar://$USER_HOME$/.m2/repository/io/undertow/undertow-websockets-jsr/2.0.33.Final/undertow-websockets-jsr-2.0.33.Final.jar!/" />
+    </CLASSES>
+    <JAVADOC>
+      <root url="jar://$USER_HOME$/.m2/repository/io/undertow/undertow-websockets-jsr/2.0.33.Final/undertow-websockets-jsr-2.0.33.Final-javadoc.jar!/" />
+    </JAVADOC>
+    <SOURCES>
+      <root url="jar://$USER_HOME$/.m2/repository/io/undertow/undertow-websockets-jsr/2.0.33.Final/undertow-websockets-jsr-2.0.33.Final-sources.jar!/" />
+    </SOURCES>
+  </library>
+</component>

+ 13 - 0
.idea/libraries/Maven__javax_servlet_javax_servlet_api_4_0_1.xml

@@ -0,0 +1,13 @@
+<component name="libraryTable">
+  <library name="Maven: javax.servlet:javax.servlet-api:4.0.1">
+    <CLASSES>
+      <root url="jar://$USER_HOME$/.m2/repository/javax/servlet/javax.servlet-api/4.0.1/javax.servlet-api-4.0.1.jar!/" />
+    </CLASSES>
+    <JAVADOC>
+      <root url="jar://$USER_HOME$/.m2/repository/javax/servlet/javax.servlet-api/4.0.1/javax.servlet-api-4.0.1-javadoc.jar!/" />
+    </JAVADOC>
+    <SOURCES>
+      <root url="jar://$USER_HOME$/.m2/repository/javax/servlet/javax.servlet-api/4.0.1/javax.servlet-api-4.0.1-sources.jar!/" />
+    </SOURCES>
+  </library>
+</component>

+ 13 - 0
.idea/libraries/Maven__log4j_log4j_1_2_17.xml

@@ -0,0 +1,13 @@
+<component name="libraryTable">
+  <library name="Maven: log4j:log4j:1.2.17">
+    <CLASSES>
+      <root url="jar://$USER_HOME$/.m2/repository/log4j/log4j/1.2.17/log4j-1.2.17.jar!/" />
+    </CLASSES>
+    <JAVADOC>
+      <root url="jar://$USER_HOME$/.m2/repository/log4j/log4j/1.2.17/log4j-1.2.17-javadoc.jar!/" />
+    </JAVADOC>
+    <SOURCES>
+      <root url="jar://$USER_HOME$/.m2/repository/log4j/log4j/1.2.17/log4j-1.2.17-sources.jar!/" />
+    </SOURCES>
+  </library>
+</component>

+ 13 - 0
.idea/libraries/Maven__mysql_mysql_connector_java_8_0_18.xml

@@ -0,0 +1,13 @@
+<component name="libraryTable">
+  <library name="Maven: mysql:mysql-connector-java:8.0.18">
+    <CLASSES>
+      <root url="jar://$USER_HOME$/.m2/repository/mysql/mysql-connector-java/8.0.18/mysql-connector-java-8.0.18.jar!/" />
+    </CLASSES>
+    <JAVADOC>
+      <root url="jar://$USER_HOME$/.m2/repository/mysql/mysql-connector-java/8.0.18/mysql-connector-java-8.0.18-javadoc.jar!/" />
+    </JAVADOC>
+    <SOURCES>
+      <root url="jar://$USER_HOME$/.m2/repository/mysql/mysql-connector-java/8.0.18/mysql-connector-java-8.0.18-sources.jar!/" />
+    </SOURCES>
+  </library>
+</component>

+ 13 - 0
.idea/libraries/Maven__net_java_dev_jna_jna_5_4_0.xml

@@ -0,0 +1,13 @@
+<component name="libraryTable">
+  <library name="Maven: net.java.dev.jna:jna:5.4.0">
+    <CLASSES>
+      <root url="jar://$USER_HOME$/.m2/repository/net/java/dev/jna/jna/5.4.0/jna-5.4.0.jar!/" />
+    </CLASSES>
+    <JAVADOC>
+      <root url="jar://$USER_HOME$/.m2/repository/net/java/dev/jna/jna/5.4.0/jna-5.4.0-javadoc.jar!/" />
+    </JAVADOC>
+    <SOURCES>
+      <root url="jar://$USER_HOME$/.m2/repository/net/java/dev/jna/jna/5.4.0/jna-5.4.0-sources.jar!/" />
+    </SOURCES>
+  </library>
+</component>

+ 13 - 0
.idea/libraries/Maven__net_java_dev_jna_jna_platform_5_4_0.xml

@@ -0,0 +1,13 @@
+<component name="libraryTable">
+  <library name="Maven: net.java.dev.jna:jna-platform:5.4.0">
+    <CLASSES>
+      <root url="jar://$USER_HOME$/.m2/repository/net/java/dev/jna/jna-platform/5.4.0/jna-platform-5.4.0.jar!/" />
+    </CLASSES>
+    <JAVADOC>
+      <root url="jar://$USER_HOME$/.m2/repository/net/java/dev/jna/jna-platform/5.4.0/jna-platform-5.4.0-javadoc.jar!/" />
+    </JAVADOC>
+    <SOURCES>
+      <root url="jar://$USER_HOME$/.m2/repository/net/java/dev/jna/jna-platform/5.4.0/jna-platform-5.4.0-sources.jar!/" />
+    </SOURCES>
+  </library>
+</component>

+ 13 - 0
.idea/libraries/Maven__org_jboss_logging_jboss_logging_3_4_0_Final.xml

@@ -0,0 +1,13 @@
+<component name="libraryTable">
+  <library name="Maven: org.jboss.logging:jboss-logging:3.4.0.Final">
+    <CLASSES>
+      <root url="jar://$USER_HOME$/.m2/repository/org/jboss/logging/jboss-logging/3.4.0.Final/jboss-logging-3.4.0.Final.jar!/" />
+    </CLASSES>
+    <JAVADOC>
+      <root url="jar://$USER_HOME$/.m2/repository/org/jboss/logging/jboss-logging/3.4.0.Final/jboss-logging-3.4.0.Final-javadoc.jar!/" />
+    </JAVADOC>
+    <SOURCES>
+      <root url="jar://$USER_HOME$/.m2/repository/org/jboss/logging/jboss-logging/3.4.0.Final/jboss-logging-3.4.0.Final-sources.jar!/" />
+    </SOURCES>
+  </library>
+</component>

+ 13 - 0
.idea/libraries/Maven__org_jboss_spec_javax_websocket_jboss_websocket_api_1_1_spec_1_1_4_Final.xml

@@ -0,0 +1,13 @@
+<component name="libraryTable">
+  <library name="Maven: org.jboss.spec.javax.websocket:jboss-websocket-api_1.1_spec:1.1.4.Final">
+    <CLASSES>
+      <root url="jar://$USER_HOME$/.m2/repository/org/jboss/spec/javax/websocket/jboss-websocket-api_1.1_spec/1.1.4.Final/jboss-websocket-api_1.1_spec-1.1.4.Final.jar!/" />
+    </CLASSES>
+    <JAVADOC>
+      <root url="jar://$USER_HOME$/.m2/repository/org/jboss/spec/javax/websocket/jboss-websocket-api_1.1_spec/1.1.4.Final/jboss-websocket-api_1.1_spec-1.1.4.Final-javadoc.jar!/" />
+    </JAVADOC>
+    <SOURCES>
+      <root url="jar://$USER_HOME$/.m2/repository/org/jboss/spec/javax/websocket/jboss-websocket-api_1.1_spec/1.1.4.Final/jboss-websocket-api_1.1_spec-1.1.4.Final-sources.jar!/" />
+    </SOURCES>
+  </library>
+</component>

+ 13 - 0
.idea/libraries/Maven__org_jboss_xnio_xnio_api_3_3_8_Final.xml

@@ -0,0 +1,13 @@
+<component name="libraryTable">
+  <library name="Maven: org.jboss.xnio:xnio-api:3.3.8.Final">
+    <CLASSES>
+      <root url="jar://$USER_HOME$/.m2/repository/org/jboss/xnio/xnio-api/3.3.8.Final/xnio-api-3.3.8.Final.jar!/" />
+    </CLASSES>
+    <JAVADOC>
+      <root url="jar://$USER_HOME$/.m2/repository/org/jboss/xnio/xnio-api/3.3.8.Final/xnio-api-3.3.8.Final-javadoc.jar!/" />
+    </JAVADOC>
+    <SOURCES>
+      <root url="jar://$USER_HOME$/.m2/repository/org/jboss/xnio/xnio-api/3.3.8.Final/xnio-api-3.3.8.Final-sources.jar!/" />
+    </SOURCES>
+  </library>
+</component>

+ 13 - 0
.idea/libraries/Maven__org_jboss_xnio_xnio_nio_3_3_8_Final.xml

@@ -0,0 +1,13 @@
+<component name="libraryTable">
+  <library name="Maven: org.jboss.xnio:xnio-nio:3.3.8.Final">
+    <CLASSES>
+      <root url="jar://$USER_HOME$/.m2/repository/org/jboss/xnio/xnio-nio/3.3.8.Final/xnio-nio-3.3.8.Final.jar!/" />
+    </CLASSES>
+    <JAVADOC>
+      <root url="jar://$USER_HOME$/.m2/repository/org/jboss/xnio/xnio-nio/3.3.8.Final/xnio-nio-3.3.8.Final-javadoc.jar!/" />
+    </JAVADOC>
+    <SOURCES>
+      <root url="jar://$USER_HOME$/.m2/repository/org/jboss/xnio/xnio-nio/3.3.8.Final/xnio-nio-3.3.8.Final-sources.jar!/" />
+    </SOURCES>
+  </library>
+</component>

+ 13 - 0
.idea/libraries/Maven__org_slf4j_slf4j_api_1_7_25.xml

@@ -0,0 +1,13 @@
+<component name="libraryTable">
+  <library name="Maven: org.slf4j:slf4j-api:1.7.25">
+    <CLASSES>
+      <root url="jar://$USER_HOME$/.m2/repository/org/slf4j/slf4j-api/1.7.25/slf4j-api-1.7.25.jar!/" />
+    </CLASSES>
+    <JAVADOC>
+      <root url="jar://$USER_HOME$/.m2/repository/org/slf4j/slf4j-api/1.7.25/slf4j-api-1.7.25-javadoc.jar!/" />
+    </JAVADOC>
+    <SOURCES>
+      <root url="jar://$USER_HOME$/.m2/repository/org/slf4j/slf4j-api/1.7.25/slf4j-api-1.7.25-sources.jar!/" />
+    </SOURCES>
+  </library>
+</component>

+ 13 - 0
.idea/libraries/Maven__org_slf4j_slf4j_log4j12_1_7_25.xml

@@ -0,0 +1,13 @@
+<component name="libraryTable">
+  <library name="Maven: org.slf4j:slf4j-log4j12:1.7.25">
+    <CLASSES>
+      <root url="jar://$USER_HOME$/.m2/repository/org/slf4j/slf4j-log4j12/1.7.25/slf4j-log4j12-1.7.25.jar!/" />
+    </CLASSES>
+    <JAVADOC>
+      <root url="jar://$USER_HOME$/.m2/repository/org/slf4j/slf4j-log4j12/1.7.25/slf4j-log4j12-1.7.25-javadoc.jar!/" />
+    </JAVADOC>
+    <SOURCES>
+      <root url="jar://$USER_HOME$/.m2/repository/org/slf4j/slf4j-log4j12/1.7.25/slf4j-log4j12-1.7.25-sources.jar!/" />
+    </SOURCES>
+  </library>
+</component>

+ 11 - 0
.idea/misc.xml

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="MavenProjectsManager">
+    <option name="originalFiles">
+      <list>
+        <option value="$PROJECT_DIR$/pom.xml" />
+      </list>
+    </option>
+  </component>
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="false" project-jdk-name="1.8" project-jdk-type="JavaSDK" />
+</project>

+ 8 - 0
.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/monitor-rtsp-hls.iml" filepath="$PROJECT_DIR$/monitor-rtsp-hls.iml" />
+    </modules>
+  </component>
+</project>

+ 6 - 0
.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="" vcs="Git" />
+  </component>
+</project>

文件差異過大導致無法顯示
+ 127 - 0
LICENSE


+ 70 - 0
Readme.md

@@ -0,0 +1,70 @@
+# monitor-rtsp-hls
+
+## 介绍
+视频监控RTSP转RTMP转HLS解决方案
+
+由于公司业务,需要实现基于WEB访问监控摄像头实时流的预览,经过各种百度,补充了不少相关知识,了解到了很多大神的实现方法,也因为很多过时的帖子,而踩了不少的坑。
+
+## 使用说明
+
+0.  下载源码,解压根目录中的third.zip压缩包(因为要用到nginx和ffmpeg,直接上传又超过了上传限制,所以就压缩起来),解压后项目结构如图:
+![输入图片说明](https://images.gitee.com/uploads/images/2021/0121/114923_dd14c528_107658.png "微信图片_20210121114904.png")
+1.  在项目根目录下执行mvn clean package打包,在target目录下将生成monitor-rtsp-hls-release.zip
+2.  解压monitor-rtsp-hls-release.zip
+3.  根据需要,修改src/main/resources/config.properties配置项,如:服务端口、服务context_path、服务域名
+4.  修改conf/monitor.properties,录入项目需要对接的监控设备RTSP或RTMP地址信息,其中监控设备代码随意命名,只是一个标识,不重复即可
+5.  双击运行start.bat即可开启服务
+6.  本服务提供了一个监控预览页面,在浏览器访问http://192.168.10.10:{服务端口}/{服务context_path}/live即可查看
+7.  对外提供监控HLS预览地址,URL格式为http://{IP}:{服务端口}/{服务context_path}/hls/{监控设备代码}/index.m3u8
+
+## 使用举例
+
+例如现在我要接入一台监控设备,那么接入步骤就是:
+
+0.  下载源码,解压根目录下的third.zip
+1.  编译源码,在target目录下得到monitor-rtsp-hls-release.zip,解压该zip,得到monitor-rtsp-hls文件夹
+2.  修改conf/monitor.properties设备配置文件,添加相应设备配置信息,如图:
+![输入图片说明](https://images.gitee.com/uploads/images/2021/0121/170857_cd88f9f0_107658.png "微信图片_20210121170801.png")
+3.  如果有需要修改服务端口信息,则编辑conf/config.properties中的server.port、server.context_path、server.domain等参数,没有需要不修改即可
+4.  到此配置就完成了,双击根目录start.bat运行,接下来就可以打开http://192.168.10.10:{服务端口}/{服务context_path}/live地址查看配置的设备
+
+## 原理说明
+
+本程序其实逻辑很简单,就是将nginx和ffmpeg整合起来,一方面将rtsp通过ffmpeg转码生成切片,一方面通过nginx将切片代理出去(大神勿喷,我只是一个搬运工)。
+既然是整合,我就在程序中会去控制启动或关闭nginx和ffmpeg,这方面处理的不够好,因为是通过命令窗口启动的服务,所以不可避免的用户可能随手就关掉窗口了,但是其实后台nginx.exe和ffmpeg.exe进程还在运行,所以想了个办法就是增加了一个stop.bat脚本,需要用户手动运行,该脚本的功能就是关闭这两个进程的。
+有更好办法的大神,请不吝赐教!
+
+## 奉上整理的几个厂家(主要是海康、大华和宇视)RTSP地址格式
+
+[分享链接](https://mubu.com/doc/4IvOBWbQq-P)
+
+## 更新记录
+
+####  20210101 增加RTMP流支持
+
+其实也没啥大的调整,正好ffmpeg也支持rtmp,所以只是增加了一个判断处理而已
+
+#### 20210618 增加数据库存储监控设备信息的方式
+
+应一些使用者要求,本次增加了数据库存储监控设备的方式,建表语句db.sql文件在根目录,大家可参考,实现类是com.rancedxk.monitor.device.provider.impl.DbProvider,当然这个实现类仅供参考,大家可自行修改源码并按自己的业务来实现,config.properties中也增加了相应的配置信息供大家参考。
+当然大家也可以实现自己的存储方式,比如基于redis或者其他
+
+#### 20210618 增加设备信息查询接口
+
+新增加了一个简单的接口,用来查询当前所有监控设备信息(其实就是从monitor.properties中获取或从数据库中获取的),接口地址:http://{IP}:{服务端口}/{服务context_path}/api/getAllDevices,返回格式:[{code:"dev1",title:"胡南卫视RTMP流",streamUrl:"rtmp://58.200.131.2:1935/livetv/hunantv"}]
+
+## 感谢
+
+1.  JFinal作者波总开发这么好用的框架,让我爱不释手
+2.  参考网上整理的各主流摄像头的rtsp地址格式
+3.  还有网上大神写的基于javacv将rtsp推至nginx rtmp的帖子,现在找不到了
+
+[![Giteye chart](https://chart.giteye.net/gitee/rancedxk/monitor-rtsp-hls/2EUUFJTE.png)](https://giteye.net/chart/2EUUFJTE)
+
+## 联系方式
+
+![输入图片说明](https://images.gitee.com/uploads/images/2021/0121/121430_5e3cfd8c_107658.jpeg "185443_1bbd3352_107658.jpg")
+
+## 请我吃碗螺丝粉吧
+
+![输入图片说明](https://images.gitee.com/uploads/images/2021/0121/121217_0b892e44_107658.png "未标题-2.png")

+ 15 - 0
db.sql

@@ -0,0 +1,15 @@
+-- ----------------------------
+-- Table structure for t_device
+-- ----------------------------
+DROP TABLE IF EXISTS `t_device`;
+CREATE TABLE `t_device` (
+  `code` varchar(10) NOT NULL,
+  `title` varchar(20) DEFAULT NULL,
+  `streamUrl` varchar(255) DEFAULT NULL,
+  PRIMARY KEY (`code`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='监控设备信息';
+
+-- ----------------------------
+-- Records of t_device
+-- ----------------------------
+INSERT INTO `t_device` VALUES ('dev1', '胡南卫视RTMP流', 'rtmp://58.200.131.2:1935/livetv/hunantv');

文件差異過大導致無法顯示
+ 3203 - 0
logs/monitor-rtsp-hls.log


+ 33 - 0
monitor-rtsp-hls.iml

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">
+    <output url="file://$MODULE_DIR$/target/classes" />
+    <output-test url="file://$MODULE_DIR$/target/test-classes" />
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
+      <excludeFolder url="file://$MODULE_DIR$/target" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="library" name="Maven: com.jfinal:jfinal:4.9.06" level="project" />
+    <orderEntry type="library" name="Maven: com.jfinal:jfinal-undertow:2.4" level="project" />
+    <orderEntry type="library" name="Maven: io.undertow:undertow-core:2.0.33.Final" level="project" />
+    <orderEntry type="library" name="Maven: org.jboss.logging:jboss-logging:3.4.0.Final" level="project" />
+    <orderEntry type="library" name="Maven: org.jboss.xnio:xnio-api:3.3.8.Final" level="project" />
+    <orderEntry type="library" scope="RUNTIME" name="Maven: org.jboss.xnio:xnio-nio:3.3.8.Final" level="project" />
+    <orderEntry type="library" name="Maven: io.undertow:undertow-servlet:2.0.33.Final" level="project" />
+    <orderEntry type="library" name="Maven: io.undertow:undertow-websockets-jsr:2.0.33.Final" level="project" />
+    <orderEntry type="library" name="Maven: org.jboss.spec.javax.websocket:jboss-websocket-api_1.1_spec:1.1.4.Final" level="project" />
+    <orderEntry type="library" scope="RUNTIME" name="Maven: javax.servlet:javax.servlet-api:4.0.1" level="project" />
+    <orderEntry type="library" name="Maven: cn.hutool:hutool-all:4.6.1" level="project" />
+    <orderEntry type="library" name="Maven: org.slf4j:slf4j-api:1.7.25" level="project" />
+    <orderEntry type="library" scope="RUNTIME" name="Maven: org.slf4j:slf4j-log4j12:1.7.25" level="project" />
+    <orderEntry type="library" name="Maven: log4j:log4j:1.2.17" level="project" />
+    <orderEntry type="library" name="Maven: net.java.dev.jna:jna:5.4.0" level="project" />
+    <orderEntry type="library" name="Maven: net.java.dev.jna:jna-platform:5.4.0" level="project" />
+    <orderEntry type="library" name="Maven: com.alibaba:druid:1.1.21" level="project" />
+    <orderEntry type="library" name="Maven: mysql:mysql-connector-java:8.0.18" level="project" />
+    <orderEntry type="library" name="Maven: com.google.protobuf:protobuf-java:3.6.1" level="project" />
+  </component>
+</module>

+ 51 - 0
package.xml

@@ -0,0 +1,51 @@
+<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
+	<id>release</id>
+
+	<formats>
+		<format>zip</format>
+	</formats>
+
+	<includeBaseDirectory>true</includeBaseDirectory>
+	
+	<fileSets>
+		<!-- third 全部 copy 到 third 目录下 -->
+		<fileSet>
+			<directory>${basedir}/third</directory>
+			<outputDirectory>third</outputDirectory>
+		</fileSet>
+		<!-- src/main/resources 全部 copy 到 conf 目录下 -->
+		<fileSet>
+			<directory>${basedir}/src/main/resources</directory>
+			<outputDirectory>conf</outputDirectory>
+		</fileSet>
+		<!-- lib 全部 copy 到 lib 目录下 -->
+		<fileSet>
+			<directory>${basedir}/lib</directory>
+			<outputDirectory>lib</outputDirectory>
+		</fileSet>
+		<!-- src/main/webapp 全部 copy 到 webapp 目录下 -->
+	    <fileSet>
+			<directory>${basedir}/src/main/webapp</directory>
+			<outputDirectory>webapp</outputDirectory>
+	    </fileSet>
+		<!-- 项目根下面的脚本文件 copy 到根目录下 -->
+		<fileSet>
+			<directory>${basedir}</directory>
+			<outputDirectory></outputDirectory>
+			<!-- 脚本文件在 linux 下的权限设为 755,无需 chmod 可直接运行 -->
+			<fileMode>755</fileMode>
+			<includes>
+				<include>*.bat</include>
+			</includes>
+		</fileSet>
+	</fileSets>	
+
+	<!-- 依赖的 jar 包 copy 到 lib 目录下 -->
+	<dependencySets>
+		<dependencySet>
+			<outputDirectory>lib</outputDirectory>			
+		</dependencySet>
+	</dependencySets>
+</assembly>

+ 137 - 0
pom.xml

@@ -0,0 +1,137 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<groupId>com.rancedxk</groupId>
+	<artifactId>monitor-rtsp-hls</artifactId>
+	<packaging>jar</packaging>
+	<version>1.0</version>
+	
+	<properties>
+		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+		<java.version>1.8</java.version>
+	</properties>
+
+	<dependencies>
+		<dependency>
+			<groupId>com.jfinal</groupId>
+			<artifactId>jfinal</artifactId>
+			<version>4.9.06</version>
+		</dependency>
+	 	<dependency>
+		    <groupId>com.jfinal</groupId>
+		    <artifactId>jfinal-undertow</artifactId>
+			<version>2.4</version>
+		</dependency>
+		<dependency>
+		    <groupId>io.undertow</groupId>
+		    <artifactId>undertow-websockets-jsr</artifactId>
+		    <version>2.0.33.Final</version>
+		</dependency>
+		<dependency>
+			<groupId>javax.servlet</groupId>
+		    <artifactId>javax.servlet-api</artifactId>
+		    <!-- 覆盖父项目的版本 -->
+		    <version>4.0.1</version><!--$NO-MVN-MAN-VER$-->
+			<scope>runtime</scope>
+	    </dependency>
+		<dependency>
+			<groupId>cn.hutool</groupId>
+			<artifactId>hutool-all</artifactId>
+			<version>4.6.1</version>
+		</dependency>
+		<!-- 日志工具类logger -->
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>slf4j-api</artifactId>
+			<version>1.7.25</version>
+		</dependency>
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>slf4j-log4j12</artifactId>
+			<scope>runtime</scope>
+			<version>1.7.25</version>
+		</dependency>
+		<dependency>
+			<groupId>log4j</groupId>
+			<artifactId>log4j</artifactId>
+			<version>1.2.17</version>
+		</dependency>
+        <!-- JNA依赖库 -->
+        <dependency>
+            <groupId>net.java.dev.jna</groupId>
+            <artifactId>jna</artifactId>
+            <version>5.4.0</version>
+        </dependency>
+        <dependency>
+            <groupId>net.java.dev.jna</groupId>
+            <artifactId>jna-platform</artifactId>
+            <version>5.4.0</version>
+        </dependency>
+        <!-- 数据库连接池插件 -->
+		<dependency>
+			<groupId>com.alibaba</groupId>
+			<artifactId>druid</artifactId>
+			<version>1.1.21</version>
+		</dependency>
+		<!-- mysql驱动包 -->
+		<dependency>
+			<groupId>mysql</groupId>
+			<artifactId>mysql-connector-java</artifactId>
+			<version>8.0.18</version>
+		</dependency>
+	</dependencies>
+	
+	<build>
+		<plugins>
+			<!-- 插件及编译配置管理 -->
+			<plugin>
+				<artifactId>maven-compiler-plugin</artifactId>
+				<configuration>
+					<source>1.8</source>
+					<target>1.8</target>
+					<encoding>${project.build.sourceEncoding}</encoding>
+				</configuration>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-jar-plugin</artifactId>
+				<version>2.6</version>
+				<configuration>
+					<excludes>
+						<exclude>*.txt</exclude>
+						<exclude>*.xml</exclude>
+						<exclude>*.properties</exclude>
+					</excludes>
+				</configuration>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-assembly-plugin</artifactId>
+				<version>3.1.0</version>
+				<executions>
+					<execution>
+						<id>make-assembly</id>
+						<phase>package</phase>
+						<goals>
+							<goal>single</goal>
+						</goals>
+						<configuration>
+							<!-- 打包生成的文件名 -->
+							<finalName>${project.artifactId}</finalName>
+							<!-- jar 等压缩文件在被打包进入 zip、tar.gz 时是否压缩,设置为 false 可加快打包速度 -->
+							<recompressZippedFiles>false</recompressZippedFiles>
+							<!-- 打包生成的文件是否要追加 release.xml 中定义的 id 值 -->
+							<appendAssemblyId>true</appendAssemblyId>
+							<!-- 指向打包描述文件 package.xml -->
+							<descriptors>
+								<descriptor>package.xml</descriptor>
+							</descriptors>
+							<!-- 打包结果输出的基础目录 -->
+							<outputDirectory>${project.build.directory}/</outputDirectory>
+						</configuration>
+					</execution>
+				</executions>
+			</plugin>
+		</plugins>
+	</build>
+</project>

+ 93 - 0
src/main/java/com/rancedxk/monitor/Config.java

@@ -0,0 +1,93 @@
+package com.rancedxk.monitor;
+
+import com.jfinal.config.Constants;
+import com.jfinal.config.Handlers;
+import com.jfinal.config.Interceptors;
+import com.jfinal.config.JFinalConfig;
+import com.jfinal.config.Plugins;
+import com.jfinal.config.Routes;
+import com.jfinal.kit.PropKit;
+import com.jfinal.template.Engine;
+import com.rancedxk.monitor.controller.ApiController;
+import com.rancedxk.monitor.controller.LiveController;
+import com.rancedxk.monitor.controller.ProcessController;
+import com.rancedxk.monitor.device.DeviceManager;
+import com.rancedxk.monitor.device.provider.IDeviceProvider;
+import com.rancedxk.monitor.utils.F;
+import com.rancedxk.monitor.utils.H;
+import com.rancedxk.monitor.utils.N;
+
+public class Config extends JFinalConfig{
+	public static final String PROJECT_PATH = System.getProperty("user.dir").replaceAll("\\\\", "/");
+
+	@Override
+	public void configConstant(Constants me) {
+		me.setDevMode(true);
+	}
+
+	@Override
+	public void configRoute(Routes me) {
+		me.setBaseViewPath("/page");
+		me.add("/live",LiveController.class);
+		me.add("/process",ProcessController.class);
+		me.add("/api",ApiController.class);
+	}
+
+	@Override
+	public void configEngine(Engine me) {
+	}
+
+	@Override
+	public void configPlugin(Plugins me) {
+	}
+
+	@Override
+	public void configInterceptor(Interceptors me) {
+	}
+
+	@Override
+	public void configHandler(Handlers me) {
+	}
+	
+	@SuppressWarnings("rawtypes")
+	@Override
+	public void afterJFinalStart() {
+		//0.设置监控设备管理器
+		if(PropKit.containsKey("device.provider")){
+			String deviceProviderClass = PropKit.get("device.provider");
+			try {
+				Class clazz = Class.forName(deviceProviderClass);
+				Object instance = clazz.newInstance();
+				if(instance instanceof IDeviceProvider){
+					DeviceManager.setProvider((IDeviceProvider)instance);
+				}
+			} catch (Exception e) {
+				throw new RuntimeException("加载监控设备管理器失败,请检查配置或使用默认加载方式[com.rancedxk.monitor.device.provider.impl.DefaultProvider]");
+			}
+		}
+
+		//1.初始化启动Nginx
+		try {
+			//关闭Nginx
+			N.stop();
+			//同步配置信息
+			N.config();
+			//启动Nginx
+			N.start();
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw new RuntimeException("启动Nginx失败");
+		}
+
+		//2.初始化创建HLS路径
+		H.initHome();
+
+		//3.关闭所有ffmpeg进程
+		try {
+			F.stop();
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw new RuntimeException("关闭ffmpeg进程失败");
+		}
+	}
+}

+ 54 - 0
src/main/java/com/rancedxk/monitor/Main.java

@@ -0,0 +1,54 @@
+package com.rancedxk.monitor;
+
+import java.io.IOException;
+import java.net.DatagramSocket;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import com.rancedxk.monitor.controller.LiveSocket;
+import com.rancedxk.monitor.utils.B;
+import com.jfinal.kit.PropKit;
+import com.jfinal.server.undertow.UndertowConfig;
+import com.jfinal.server.undertow.UndertowServer;
+
+public class Main {
+	public static void main(String[] args) throws IOException {
+		//1.运行后台服务
+		PropKit.use("config.properties");
+		
+		//2.生成随机端口
+		DatagramSocket socket = null;
+		int hlsServerPort = 0;
+		try {
+			socket = new DatagramSocket(0);
+			hlsServerPort = socket.getLocalPort();
+			PropKit.getProp().getProperties().setProperty("hls.server.port", String.valueOf(hlsServerPort));
+		} catch (Exception e) {
+			throw new RuntimeException("获取空闲端口失败");
+		} finally {
+			if(socket!=null&&!socket.isClosed()){
+				socket.close();
+			}
+		}
+		
+		//3.运行WEB控制台
+		UndertowServer
+			.create(new UndertowConfig(Config.class))
+			.setHost("0.0.0.0")
+			.setPort(hlsServerPort)
+			.setContextPath("/hls_server")
+			.configWeb(builder->{
+				builder.addWebSocketEndpoint(LiveSocket.class);
+			})
+			.start();
+		
+		//4.默认1秒后打开状态页
+		new Timer().schedule(new TimerTask() {
+			@Override
+			public void run() {
+				String url = "http://192.168.10.10:" + PropKit.get("server.port") + PropKit.get("server.context_path") + "/live";
+				B.openDefault(url);
+			}
+		}, 1000);
+	}
+}

+ 20 - 0
src/main/java/com/rancedxk/monitor/controller/ApiController.java

@@ -0,0 +1,20 @@
+package com.rancedxk.monitor.controller;
+
+import com.jfinal.core.Controller;
+import com.rancedxk.monitor.device.DeviceManager;
+
+import cn.hutool.json.JSONUtil;
+
+/**
+ * 对外提供接口服务
+ * @author duxikun
+ */
+public class ApiController extends Controller{
+    
+    /**
+     * 返回当前所有设备信息
+     */
+    public void getAllDevices(){
+    	renderText(JSONUtil.toJsonStr(DeviceManager.getAll()));
+    }
+}

+ 16 - 0
src/main/java/com/rancedxk/monitor/controller/LiveController.java

@@ -0,0 +1,16 @@
+package com.rancedxk.monitor.controller;
+
+import com.jfinal.core.Controller;
+import com.jfinal.kit.PropKit;
+
+/**
+ * 跳转live页面
+ * @author duxikun
+ */
+public class LiveController extends Controller{
+	public void index(){
+		setAttr("contextPath", PropKit.get("server.context_path"));
+		setAttr("serverPort", PropKit.get("server.port"));
+		render("index.html");
+	}
+}

+ 98 - 0
src/main/java/com/rancedxk/monitor/controller/LiveSocket.java

@@ -0,0 +1,98 @@
+package com.rancedxk.monitor.controller;
+
+import java.util.Map;
+
+import javax.websocket.OnClose;
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import com.jfinal.kit.Kv;
+import com.rancedxk.monitor.device.DeviceManager;
+
+import cn.hutool.json.JSONUtil;
+
+/**
+ * 此处使用的websocket实现功能,没啥目的,纯粹是为了练习websocket,请无视我糟糕的代码以及日志输出
+ * @author duxikun
+ */
+@ServerEndpoint("/api.ws")
+public class LiveSocket{
+	
+	@OnOpen
+	public void onOpen(Session session){
+		System.out.println("socket open");
+	}
+	
+	@OnMessage
+	public void onMessage(String message, Session session){
+		System.out.println("socket get message:"+message);
+		Params params = JSONUtil.toBean(message, Params.class);
+		if(params.action.equals("loadDevs")){
+			sendSuccess(session, params.getAction(), DeviceManager.getAll());
+		}
+	}
+    
+	@OnError
+	public void onError(Session session, Throwable error){
+		System.out.println("err:");
+		error.printStackTrace();
+	}
+	
+	@OnClose
+	public void onClose(){
+		System.out.println("socket close");
+	}
+	
+	public void sendFail(Session session,String action){
+		session.getAsyncRemote().sendText(JSONUtil.toJsonStr(Kv.by("code", "fail").set("action",action)));
+	}
+	
+	public void sendFail(Session session,String action,String message){
+		session.getAsyncRemote().sendText(JSONUtil.toJsonStr(Kv.by("code", "fail").set("message",message).set("action",action)));
+	}
+	
+	public void sendSuccess(Session session,String action, Object message){
+		session.getAsyncRemote().sendText(JSONUtil.toJsonStr(Kv.by("code", "success").set("action",action).set("object",message)));
+	}
+	
+	public static class Params{
+		private String action;
+		private Map<String,Object> params;
+
+		public String getAction() {
+			return action;
+		}
+		public void setAction(String action) {
+			this.action = action;
+		}
+		public Map<String, Object> getParams() {
+			return params;
+		}
+		public void setParams(Map<String, Object> params) {
+			this.params = params;
+		}
+		public String getStr(String key){
+			Object value = params.get(key);
+			return value==null?null:String.valueOf(params.get(key));
+		}
+		public String getStr(String key,String defaultValue){
+			String value = getStr(key);
+			return value==null?defaultValue:value;
+		}
+		public int getInt(String key){
+			String value = getStr(key);
+			return value==null?null:Integer.valueOf(getStr(key));
+		}
+		public long getLong(String key){
+			String value = getStr(key);
+			return value==null?null:Long.valueOf(getStr(key));
+		}
+		public double getDouble(String key){
+			String value = getStr(key);
+			return value==null?null:Double.valueOf(getStr(key));
+		}
+	}
+}

+ 54 - 0
src/main/java/com/rancedxk/monitor/controller/ProcessController.java

@@ -0,0 +1,54 @@
+package com.rancedxk.monitor.controller;
+
+import org.apache.log4j.Logger;
+
+import com.jfinal.core.Controller;
+import com.jfinal.kit.PropKit;
+import com.jfinal.kit.StrKit;
+import com.rancedxk.monitor.device.DeviceManager;
+import com.rancedxk.monitor.task.TaskManager;
+import com.rancedxk.monitor.utils.H;
+
+import cn.hutool.core.io.FileUtil;
+
+/**
+ * 为页面HLS.js提供m3u8文件响应
+ * @author duxikun
+ */
+public class ProcessController extends Controller{
+    private Logger logger = Logger.getLogger(ProcessController.class);
+    
+    public void index(){
+        String code = getPara(0);
+        //判断设备编码不为空,且在配置文件中存在相关配置
+        if(StrKit.notBlank(code)&&DeviceManager.isContain(code)){
+            //判断是否当前对应设备的转换线程
+            if(TaskManager.isContain(code)){
+                //如果有,则重新激活
+                TaskManager.get(code).active();
+            }else{
+                //如果没有,则新建
+                TaskManager.create(DeviceManager.get(code));
+            }
+            //判断是否生成了转换后的m3u8文件
+            String m3u8Path = H.getPath(code,"index.m3u8");
+            //每次间隔1秒,循环判断m3u8文件是否创建成功
+            long retryTimes = PropKit.getLong("hls.duration")*2/1000;
+            try {
+                while(!FileUtil.exist(m3u8Path)&&(retryTimes--)>0){
+                    Thread.sleep(1000);
+                }
+                //如果最终判断m3u8文件创建成功,则返回
+                if(FileUtil.exist(m3u8Path)){
+                    renderFile(FileUtil.file(m3u8Path));
+                    return;
+                }
+                logger.error("请求设备[" + code + "]:m3u8文件未创建");
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+        //否则返回404
+        renderError(404);
+    }
+}

+ 35 - 0
src/main/java/com/rancedxk/monitor/device/Device.java

@@ -0,0 +1,35 @@
+package com.rancedxk.monitor.device;
+
+public class Device {
+	String code;
+	String title;
+	String streamUrl;
+	
+	public Device() {
+	}
+	
+	public Device(String code,String title,String streamUrl) {
+		this.code = code;
+		this.title = title;
+		this.streamUrl = streamUrl;
+	}
+	
+	public void setCode(String code) {
+		this.code = code;
+	}
+	public String getCode() {
+		return code;
+	}
+	public void setTitle(String title) {
+		this.title = title;
+	}
+	public String getTitle() {
+		return title;
+	}
+	public void setStreamUrl(String streamUrl) {
+		this.streamUrl = streamUrl;
+	}
+	public String getStreamUrl() {
+		return streamUrl;
+	}
+}

+ 28 - 0
src/main/java/com/rancedxk/monitor/device/DeviceManager.java

@@ -0,0 +1,28 @@
+package com.rancedxk.monitor.device;
+
+import java.util.Collection;
+
+import com.rancedxk.monitor.device.provider.IDeviceProvider;
+import com.rancedxk.monitor.task.TaskManager;
+
+public class DeviceManager {
+	static IDeviceProvider provider;
+	
+	public static void setProvider(IDeviceProvider provider) {
+		DeviceManager.provider = provider;
+		//初始化TaskManager
+		TaskManager.init(provider.getAll().size());
+	}
+	
+	public static boolean isContain(String code) {
+		return provider.isContain(code);
+	}
+	
+	public static Device get(String code) {
+		return provider.get(code);
+	}
+	
+	public static Collection<Device> getAll() {
+		return provider.getAll();
+	}
+}

+ 17 - 0
src/main/java/com/rancedxk/monitor/device/provider/IDeviceProvider.java

@@ -0,0 +1,17 @@
+package com.rancedxk.monitor.device.provider;
+
+import java.util.Collection;
+
+import com.rancedxk.monitor.device.Device;
+
+/**
+ * 设备管理接口
+ * @author duxikun
+ */
+public interface IDeviceProvider {
+	public boolean isContain(String code);
+	
+	public Device get(String code);
+	
+	public Collection<Device> getAll();
+}

+ 63 - 0
src/main/java/com/rancedxk/monitor/device/provider/impl/DbProvider.java

@@ -0,0 +1,63 @@
+package com.rancedxk.monitor.device.provider.impl;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import com.jfinal.core.JFinal;
+import com.jfinal.kit.PropKit;
+import com.jfinal.plugin.activerecord.ActiveRecordPlugin;
+import com.jfinal.plugin.activerecord.Db;
+import com.jfinal.plugin.activerecord.DbPro;
+import com.jfinal.plugin.activerecord.Record;
+import com.jfinal.plugin.activerecord.dialect.MysqlDialect;
+import com.jfinal.plugin.druid.DruidPlugin;
+import com.rancedxk.monitor.device.Device;
+import com.rancedxk.monitor.device.provider.IDeviceProvider;
+
+import cn.hutool.core.bean.BeanUtil;
+
+public class DbProvider implements IDeviceProvider{
+	
+	private static final String configName = "dbProvider";
+	
+	static {
+		//配置数据库连接池
+		//配置Druid插件,可以监控查看SQL执行情况
+		DruidPlugin dbPlugin = new DruidPlugin(PropKit.get("device.provider.db.url"), PropKit.get("device.provider.db.username"), PropKit.get("device.provider.db.password"));
+		dbPlugin.start();
+		//配置ActiveRecord插件
+		ActiveRecordPlugin arp = new ActiveRecordPlugin(configName,dbPlugin);
+		arp.setDialect(new MysqlDialect());
+		//是否在控制台显示所使用的SQL原文,用于调试
+		arp.setShowSql(JFinal.me().getConstants().getDevMode());
+		arp.start();
+	}
+	
+	private DbPro db(){
+		return Db.use(configName);
+	}
+
+	@Override
+	public boolean isContain(String code) {
+		return db().queryLong("SELECT COUNT(1) FROM t_device WHERE code=?",code)!=0;
+	}
+
+	@Override
+	public Device get(String code) {
+		Record record = db().findFirst("SELECT * FROM t_device WHERE code=?",code);
+		return BeanUtil.mapToBean(record.getColumns(), Device.class, true);
+	}
+
+	@Override
+	public Collection<Device> getAll() {
+		Collection<Device> deviceList = new ArrayList<Device>();
+		List<Record> recordList = db().find("SELECT * FROM t_device");
+		if(recordList!=null){
+			for(Record item : recordList){
+				deviceList.add(BeanUtil.mapToBean(item.getColumns(), Device.class, true));
+			}
+		}
+		return deviceList;
+	}
+}

+ 43 - 0
src/main/java/com/rancedxk/monitor/device/provider/impl/DefaultProvider.java

@@ -0,0 +1,43 @@
+package com.rancedxk.monitor.device.provider.impl;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.jfinal.kit.Prop;
+import com.rancedxk.monitor.device.Device;
+import com.rancedxk.monitor.device.provider.IDeviceProvider;
+
+public class DefaultProvider implements IDeviceProvider{
+	
+	static Map<String,Device> devs = null;
+	
+	static{
+		//读取监控设备配置信息
+		Prop prop = null;
+		try {
+			prop = new Prop("monitor.properties");
+		} catch (Exception e) {
+			//加载失败时,抛出异常
+			throw new RuntimeException("monitor.properties不存在,无法加载设备信息");
+		}
+		//读取配置信息
+		devs = new HashMap<String, Device>();
+		String codes = prop.get("monitor.codes");
+		for(String code : codes.split(",")){
+			devs.put(code, new Device(code,prop.get(String.format("monitor.%s.title",code)),prop.get(String.format("monitor.%s.stream_url",code))));
+		}
+	}
+	
+	public boolean isContain(String code){
+		return devs.containsKey(code);
+	}
+	
+	public Device get(String code){
+		return devs.get(code);
+	}
+	
+	public Collection<Device> getAll(){
+		return devs.values();
+	}
+}

+ 180 - 0
src/main/java/com/rancedxk/monitor/task/Task.java

@@ -0,0 +1,180 @@
+package com.rancedxk.monitor.task;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.util.regex.Pattern;
+
+import org.apache.log4j.Logger;
+
+import com.jfinal.kit.PropKit;
+import com.jfinal.kit.StrKit;
+import com.rancedxk.monitor.device.Device;
+import com.rancedxk.monitor.utils.F;
+import com.rancedxk.monitor.utils.H;
+import com.rancedxk.monitor.utils.S;
+
+import cn.hutool.core.util.ReUtil;
+
+public class Task implements Runnable{
+    private Logger logger = Logger.getLogger(getClass());
+
+    private boolean isRunning = false;
+    //记录线程开始时间
+    private long startTime;
+    //转换可持续最大时间,达到时间则自动结束
+    private static final long duration = PropKit.getLong("hls.duration");
+    //设备信息
+    private Device dev;
+    //设备HLS目录路径
+    private String dirPath;
+    //M3U8文件路径
+    private String m3u8Path;
+
+    public Task(Device dev) {
+        this.dev = dev;
+        this.dirPath = H.getPath(dev.getCode());
+        this.m3u8Path = H.getPath(dev.getCode(),"index.m3u8");
+    }
+
+    public Device getDev() {
+        return dev;
+    }
+
+    public boolean isRunning() {
+        return isRunning;
+    }
+
+    /**
+     * 设置开始时间为当前时间,以此来延长转换持续时间
+     */
+    public void active() {
+        if(!this.isRunning){
+            //如果当前已经停止,则重新运行
+            this.run();
+        }else{
+            //如果正在运行,则重新设置startTime
+            this.startTime = System.currentTimeMillis();
+        }
+    }
+
+    @Override
+    public void run() {
+        //开始
+        isRunning = true;
+        //创建文件夹
+        createDir();
+        //运行,设置视频源和推流地址和HLS目录
+        convert();
+        //运行结束,从池中移除
+        TaskManager.remove(dev.getCode());
+        //删除文件夹
+        deleteDir();
+        //结束
+        isRunning = false;
+    }
+
+    private void convert() {
+    	String streamUrl = this.dev.getStreamUrl();
+    	if(StrKit.isBlank(streamUrl)){
+    		return;
+    	}
+        //检测流服务是否可访问
+        if(!isConnectable(streamUrl)){
+            return;
+        }
+        try {
+            logger.info(S.f("设备[%s]:%s", this.dev.getCode(), "开始转码"));
+            Process process = F.ffmpeg(streamUrl, this.m3u8Path);
+            InputStream is = process.getErrorStream();
+            BufferedReader br = new BufferedReader(new InputStreamReader(is));
+            String line;
+            this.startTime = System.currentTimeMillis();
+            while(System.currentTimeMillis()-startTime < duration && (line = br.readLine()) != null) {
+            	if(PropKit.getBoolean("ffmpeg.log")){
+            		logger.error(S.f("设备[%s]: %s", this.dev.getCode(), line));
+            	}
+            }
+            //关闭流,释放资源
+            if(process != null){
+                process.destroy();
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            logger.info(S.f("设备[%s]:%s", this.dev.getCode(), "结束转码"));
+		}
+    }
+
+    /**
+     * 检测指定IP+端口是否可连接
+     * @param streamUrl RTSP或RTMP地址
+     * @return true:可连接,false:不可连接
+     */
+    private boolean isConnectable(String streamUrl) {
+        String ip = ReUtil.get(Pattern.compile("(\\d+\\.\\d+\\.\\d+\\.\\d+):(\\d+)"), streamUrl, 1);
+        String port = ReUtil.get(Pattern.compile("(\\d+\\.\\d+\\.\\d+\\.\\d+):(\\d+)"), streamUrl, 2);
+        if(StrKit.isBlank(ip)||StrKit.isBlank(port)){
+            return true;
+        }
+        Socket socket = null;
+        try {
+            if(InetAddress.getByName(ip).isReachable(3000)){
+                socket = new Socket();
+                socket.connect(new InetSocketAddress(ip, Integer.valueOf(port)));
+                return true;
+            }
+        } catch (UnknownHostException e) {
+            e.printStackTrace();
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            if(socket!=null){
+                try {
+                    socket.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        return false;
+    }
+    /**
+     * 创建文件夹
+     * @return true:正确 false:错误
+     */
+    private boolean createDir(){
+        File file = new File(this.dirPath);
+        return file.mkdir();
+    }
+    /**
+     * 删除文件夹
+     * @param dir
+     */
+    private void deleteDir(){
+        deleteDir(this.dirPath);
+    }
+    private void deleteDir(String path){
+        File file = new File(path);
+    	//删除文件夹
+        if(file.isFile()){
+            file.delete();
+        }else{
+            File[] files = file.listFiles();
+            if(files == null){
+                file.delete();
+            }else{
+                for (int i = 0; i < files.length; i++) {
+                    deleteDir(files[i].getAbsolutePath());
+                }
+                file.delete();
+            }
+        }
+    }
+}

+ 48 - 0
src/main/java/com/rancedxk/monitor/task/TaskManager.java

@@ -0,0 +1,48 @@
+package com.rancedxk.monitor.task;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import com.rancedxk.monitor.device.Device;
+
+public class TaskManager {
+    private static ExecutorService taskExecutor = null;
+
+	static Map<String,Task> tasks = new ConcurrentHashMap<String,Task>();
+	
+	public static void init(int maxTaskSize){
+		taskExecutor = Executors.newFixedThreadPool(maxTaskSize);
+	}
+	
+	public static Task create(Device dev){
+		if(taskExecutor!=null){
+			Task task = new Task(dev);
+			tasks.put(dev.getCode(), task);
+			taskExecutor.execute(task);
+			return task;
+		}
+		return null;
+	}
+	
+	public static Map<String,Task> getAll(){
+		return tasks;
+	}
+	
+	public static Task get(String code){
+		return tasks.get(code);
+	}
+	
+	public static boolean isContain(String code){
+		return tasks.containsKey(code);
+	}
+	
+	public static Task remove(String code){
+		Task task = null;
+		if(tasks.containsKey(code)){
+			task = tasks.remove(code);
+		}
+		return task;
+	}
+}

+ 41 - 0
src/main/java/com/rancedxk/monitor/utils/B.java

@@ -0,0 +1,41 @@
+package com.rancedxk.monitor.utils;
+
+import java.awt.Desktop;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+/**
+ * 浏览器相关操作
+ * @author duxikun
+ */
+public class B {
+	
+	/**
+	 * 打开默认浏览器访问页面
+	 */
+	public static void openDefault(String url){
+		//启用系统默认浏览器来打开网址。
+        try {
+            URI uri = new URI(url);
+            Desktop.getDesktop().browse(uri);
+        } catch (URISyntaxException e) {
+            e.printStackTrace();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+	}
+
+	/**
+	 * 打开IE浏览器访问页面
+	 */
+	public static void openIE(String url) {
+		//启用cmd运行IE的方式来打开网址。
+		String str = "cmd /c start iexplore " + url;
+		try {
+			Runtime.getRuntime().exec(str);
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+	}
+}

+ 270 - 0
src/main/java/com/rancedxk/monitor/utils/C.java

@@ -0,0 +1,270 @@
+package com.rancedxk.monitor.utils;
+
+import org.apache.log4j.Logger;
+
+import java.io.*;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * 配置文件读取工具类
+ */
+public class C {
+	private static Logger logger = Logger.getLogger(C.class);
+	//配置文件路径
+	private static String filePath = null;
+	//配置项
+	private static ConfigBuffer params = null;
+
+	public static void u(String filePath){
+		C.filePath = filePath;
+		try {
+			params = ConfigBuffer.load(filePath);
+		} catch (Exception e) {
+			logger.error("读取配置文件失败:",e);
+		}
+	}
+	
+	private static ConfigBuffer getProp() {
+		if (params == null) {
+			throw new IllegalStateException("未加载配置文件");
+		}
+		return params;
+	}
+	
+	/**
+	 * 重新设置配置信息<br/>
+	 * 注:此方法不向配置文件中物理输出,如果需要物理保存,请继续调用saveConfig方法
+	 * @param key 键
+	 * @param value 值
+	 */
+	public static void s(String key, String value){
+		getProp().set(key, value).store();;
+	}
+	
+	/**
+	 * 从配置文件中获取key所对应的值
+	 * @param key 键
+	 * @return value 值
+	 */
+	public static String g(String key){
+		return getProp().get(key);
+	}
+	
+	/**
+	 * 从配置文件中获取key所对应的值
+	 * @param key 键
+	 * @param defaultValue 键不存在时,返回该默认值
+	 * @return value 值
+	 */
+	public static String g(String key, String defaultValue){
+		String value = g(key);
+		return value == null ? defaultValue : value;
+	}
+
+	/**
+	 * 获取指定key对应的值,转换为int型
+	 */
+	public static Integer gi(String key) {
+		return gi(key, null);
+	}
+
+	/**
+	 * 获取指定key对应的值,转换为int型
+	 */
+	public static Integer gi(String key, Integer defaultValue) {
+		String value = g(key);
+		return (value != null) ? Integer.parseInt(value) : defaultValue;
+	}
+
+	/**
+	 * 获取指定key对应的值,转换为long型
+	 */
+	public static Long gl(String key) {
+		return gl(key, null);
+	}
+
+	/**
+	 * 获取指定key对应的值,转换为long型
+	 */
+	public static Long gl(String key, Long defaultValue) {
+		String value = g(key);
+		return (value != null) ? Long.parseLong(value) : defaultValue;
+	}
+	
+	/**
+	 * 获取指定key对应的值,转换为boolean型
+	 */
+	public static Boolean gb(String key) {
+		return gb(key, null);
+	}
+
+	/**
+	 * 获取指定key对应的值,转换为boolean型
+	 */
+	public static Boolean gb(String key, Boolean defaultValue) {
+		String value = g(key);
+		return (value != null) ? Boolean.parseBoolean(value) : defaultValue;
+	}
+	
+	public static boolean c(String key) {
+		return params.containsKey(key);
+	}
+	
+	public static interface ConfigLine{
+	}
+	
+	public static class ConfigBlank implements ConfigLine{
+	}
+	
+	public static class ConfigComment implements ConfigLine{
+		private String comment;
+		public ConfigComment(String comment) {
+			this.comment = comment;
+		}
+		public String get(){
+			return comment;
+		}
+	}
+	
+	public static class ConfigParam implements ConfigLine{
+		private String key;
+		private String value;
+		
+		public ConfigParam(String key, String value) {
+			this.key = key;
+			this.value = value;
+		}
+		
+		public String getKey(){
+			return this.key;
+		}
+		
+		public ConfigLine set(String value){
+			this.value = value;
+			return this;
+		}
+		
+		public String get(){
+			return this.value;
+		}
+		
+		public int getInt(){
+			return Integer.valueOf(this.value);
+		}
+		
+		public long getLong(){
+			return Long.valueOf(this.value);
+		}
+		
+		public boolean isTrue(){
+			return Boolean.valueOf(this.value);
+		}
+		
+		public boolean isFalse(){
+			return !isTrue();
+		}
+	}
+	
+	public static class ConfigBuffer{
+		private String fileName = null;
+		private Map<String,ConfigLine> lines = null;
+		
+		public ConfigBuffer set(String key, String value){
+			if(lines.containsKey(key)){
+				((ConfigParam)lines.get(key)).set(value);
+			}
+			return this;
+		}
+		
+		public String get(String key){
+			if(containsKey(key)){
+				return ((ConfigParam)lines.get(key)).get();
+			}
+			return null;
+		}
+		
+		public boolean containsKey(String key){
+			return lines.containsKey(key);
+		}
+		
+		public static ConfigBuffer load(String fileName) throws Exception {
+			InputStream inputStream = C.class.getClassLoader().getResourceAsStream(fileName);
+			if(inputStream!=null){
+				ConfigBuffer buffer = new ConfigBuffer();
+				buffer.fileName = fileName;
+				buffer.lines = new LinkedHashMap<String,ConfigLine>();
+				BufferedReader reader = null;
+				try {
+					reader = new BufferedReader(new InputStreamReader(inputStream));
+					int lineNum = 0;
+					while (true) {
+						String lineStr = reader.readLine();
+						if(lineStr==null)break;
+						if(lineStr.trim().equals("")){
+							buffer.lines.put(S.f("%02d", ++lineNum), new ConfigBlank());
+						}else if(lineStr.startsWith("#")){
+							buffer.lines.put(S.f("%02d", ++lineNum), new ConfigComment(lineStr.substring(1)));
+						}else{
+							String key = lineStr.split("=")[0].trim();
+							String value = lineStr.split("=")[1].trim();
+							buffer.lines.put(key, new ConfigParam(key,value));
+						}
+					}
+					return buffer;
+				}catch (Exception e){
+					throw new RuntimeException("加载配置文件失败", e);
+				}finally{
+					if(reader!=null){
+						try {
+							reader.close();
+						} catch (IOException e) {
+							logger.error("",e);
+						}
+					}
+				}
+			}else{
+				throw new IllegalArgumentException(S.f("配置文件[%s]不存在",filePath));
+			}
+		}
+		
+		public void store(){
+			String filePath = C.class.getClassLoader().getResource(fileName).getFile();
+			File file = new File(filePath);
+			if(file.exists()){
+				file.delete();
+			}
+			FileWriter writer = null;
+			try {
+				StringBuilder fileContent = new StringBuilder();
+				for(ConfigLine line : lines.values()){
+					if(line instanceof ConfigBlank){
+						fileContent.append("\n");
+					}else if(line instanceof ConfigComment){
+						fileContent.append("#");
+						fileContent.append(((ConfigComment)line).get());
+						fileContent.append("\n");
+					}else if(line instanceof ConfigParam){
+						fileContent.append(((ConfigParam)line).getKey());
+						fileContent.append("=");
+						fileContent.append(((ConfigParam)line).get());
+						fileContent.append("\n");
+					}
+				}
+				writer = new FileWriter(file);
+				writer.write(fileContent.toString());
+				writer.flush();
+			}catch (Exception e){
+				throw new RuntimeException("保存配置文件失败", e);
+			}finally{
+				if(writer!=null){
+					try {
+						writer.close();
+					} catch (IOException e) {
+						logger.error("",e);
+					}
+				}
+			}
+		}
+	}
+}

+ 80 - 0
src/main/java/com/rancedxk/monitor/utils/D.java

@@ -0,0 +1,80 @@
+package com.rancedxk.monitor.utils;
+
+import cn.hutool.core.util.StrUtil;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * 日期时间工具类
+ */
+public class D {
+	
+	/**
+	 * 默认格式:YYYY-MM-dd HH:mm:ss
+	 */
+	public final static String FORMAT_DEF = "YYYY-MM-dd HH:mm:ss";
+	/**
+	 * 格式1:yyyyMMdd
+	 */
+	public final static String FORMAT1 = "yyyyMMdd";
+	/**
+	 * 格式2:yyyy-MM-dd
+	 */
+	public final static String FORMAT2 = "yyyy-MM-dd";
+	/**
+	 * 格式3:yyyyMMddHHmmss
+	 */
+	public final static String FORMAT3 = "yyyyMMddHHmmss";
+	/**
+	 * 格式4:yyMMddHHmmss
+	 */
+	public final static String FORMAT4 = "yyMMddHHmmss";
+	/**
+	 * 格式5:HH:mm:ss
+	 */
+	public final static String FORMAT5 = "HH:mm:ss";
+	
+	/**
+	 * 以默认格式获取当前日期
+	 */
+	public static String g(){
+		return g(FORMAT_DEF);
+	}
+	
+	/**
+	 * 以指定格式获取当前日期,格式为null时使用默认格式
+	 * @param format 日期格式
+	 */
+	public static String g(String format){
+		if(StrUtil.isBlank(format)){
+			format = FORMAT_DEF;
+		}
+		return new SimpleDateFormat(format).format(new Date());
+	}
+	
+	/**
+	 * 以默认格式来格式化指定日期对象
+	 * @param date 日期对象
+	 */
+	public static String f(Date date){
+		if(date==null){
+			return g();
+		}
+		return new SimpleDateFormat(FORMAT_DEF).format(date);
+	}
+	
+	/**
+	 * 使用指定格式来格式化指定日期对象
+	 * @param date 日期对象
+	 * @param format 日期格式
+	 */
+	public static String f(Date date, String format){
+		if(date==null){
+			return g(format);
+		}else if(StrUtil.isBlank(format)){
+			format = FORMAT_DEF;
+		}
+		return new SimpleDateFormat(format).format(date);
+	}
+}

+ 86 - 0
src/main/java/com/rancedxk/monitor/utils/F.java

@@ -0,0 +1,86 @@
+package com.rancedxk.monitor.utils;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Scanner;
+
+import com.rancedxk.monitor.Config;
+import com.jfinal.kit.Kv;
+import com.jfinal.kit.PropKit;
+
+/**
+ * 执行系统命令
+ * @author duxikun
+ */
+public class F {
+	
+	private static String basePath = Config.PROJECT_PATH + "/third/ffmpeg";
+	
+	/**
+	 * 执行命令行并获取进程
+	 * @param streamUrl 流地址
+	 * @param m3u8FileName 输出的m3u8文件名
+	 * @return 返回window处理线程
+	 * @throws IOException
+	 */
+	public synchronized static Process ffmpeg(String streamUrl, String m3u8FileName) throws IOException {
+		String ckey = PropKit.get("ffmpeg.cmd.rtsp");
+		if(streamUrl.startsWith("rtmp://")){
+			ckey = PropKit.get("ffmpeg.cmd.rtmp");
+		}
+        //从配置文件中读取命令模板,并注入相应的动态参数值
+		String cmd = S.t(ckey,Kv.create()
+                                    .set("ffmpeg_path", basePath)
+									.set("monitor_stream_url",streamUrl)
+									.set("hls_m3u8_path", m3u8FileName));
+		//执行命令获取主进程
+		Process process = Runtime.getRuntime().exec(cmd);
+		return process;
+	}
+	
+	/**
+	 * 杀掉所有ffmpeg进程
+	 */
+	public static void stop() throws IOException, NumberFormatException, InterruptedException {
+		//先查询进程号
+		List<String> pids = q("ffmpeg.exe");
+		//循环依次杀掉对应进程
+		if(pids.size()!=0){
+			for(String pid : pids){
+				k(Integer.valueOf(pid));
+			}
+		}
+	}
+	
+	/**
+	 * 查询包含指定关键的进程号
+	 * @param keyword 关键字
+	 * @return 进程号集合
+	 */
+	private static List<String> q(String keyword) throws IOException {
+		Process process = Runtime.getRuntime().exec("tasklist");
+		Scanner in = new Scanner(process.getInputStream());
+		List<String> pids = new ArrayList<String>();
+		while(in.hasNextLine()){
+			String p = in.nextLine();
+			if(p.contains(keyword)){
+				pids.add(p.replaceAll("\\s{1,}",",").split(",")[1]);
+			}
+		}
+		in.close();
+		return pids;
+	}
+	
+	/**
+	 * 杀掉指定进程号的进程
+	 * @param pid 进程号
+	 */
+	private static void k(int pid) throws IOException, InterruptedException {
+		String cmd = "cmd.exe /c taskkill /PID " + pid + " /F /T ";
+		Runtime rt = Runtime.getRuntime();
+		Process killPrcess = rt.exec(cmd);
+		killPrcess.waitFor();
+		killPrcess.destroy();
+	}
+}

+ 39 - 0
src/main/java/com/rancedxk/monitor/utils/H.java

@@ -0,0 +1,39 @@
+package com.rancedxk.monitor.utils;
+
+import java.io.File;
+
+import com.rancedxk.monitor.Config;
+
+import cn.hutool.core.io.FileUtil;
+
+/**
+ * hls相关操作
+ * @author duxikun
+ */
+public class H {
+	
+	private static String basePath = Config.PROJECT_PATH + "/third/hls";
+	
+	/**
+	 * 初始化HLS所有路径
+	 */
+	public static void initHome(){
+		if(!FileUtil.exist(basePath)){
+			FileUtil.mkdir(basePath);
+		}
+	}
+	
+	/**
+	 * 获取基于HLS目录的相对路径
+	 * @param dirs 目录路径
+	 */
+	public static String getPath(String... dirs){
+		StringBuffer sBuffer = new StringBuffer(basePath);
+		for(String dir : dirs){
+			sBuffer.append(File.separator).append(dir);
+		}
+		String temp=sBuffer.toString().replace("/","\\");
+		return temp;
+		//return sBuffer.toString();
+	}
+}

+ 44 - 0
src/main/java/com/rancedxk/monitor/utils/N.java

@@ -0,0 +1,44 @@
+package com.rancedxk.monitor.utils;
+
+import java.io.File;
+import java.io.IOException;
+
+import com.rancedxk.monitor.Config;
+import com.jfinal.core.JFinal;
+import com.jfinal.kit.Kv;
+
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.io.resource.ResourceUtil;
+
+/**
+ * Nginx操作类
+ * @author duxikun
+ */
+public class N {
+	
+	private static String basePath = Config.PROJECT_PATH + "/third/nginx";
+	private static String hlsPath = Config.PROJECT_PATH + "/third/hls";
+	
+	public static void config() {
+		File configFile = new File(basePath + "/conf/nginx.conf");
+		if(configFile.exists()){
+			configFile.delete();
+		}
+		String nginxTemplateStr = ResourceUtil.readUtf8Str("nginx.template");
+		String nginxConfigStr = S.t(nginxTemplateStr, Kv.create()
+														.set("context_path",S.tc("server.context_path"))
+														.set("nginx_port",S.tc("server.port"))
+														.set("nginx_domain",S.tc("server.domain"))
+														.set("hls_server_port",S.tc("hls.server.port"))
+														.set("hls_path",hlsPath));
+		FileUtil.writeString(nginxConfigStr, configFile, JFinal.me().getConstants().getEncoding());
+	}
+	public static void start() throws IOException, InterruptedException {
+		//执行命令获取主进程
+		Runtime.getRuntime().exec("cmd.exe /c nginx.exe",new String[]{},new File(basePath));
+	}
+	public static void stop() throws IOException, InterruptedException {
+		//执行命令获取主进程
+		Runtime.getRuntime().exec("cmd.exe /c nginx.exe -s stop",new String[]{},new File(basePath)).waitFor();
+	}
+}

+ 88 - 0
src/main/java/com/rancedxk/monitor/utils/S.java

@@ -0,0 +1,88 @@
+package com.rancedxk.monitor.utils;
+
+import com.jfinal.kit.Kv;
+import com.jfinal.kit.PropKit;
+import com.jfinal.kit.StrKit;
+import com.jfinal.template.Engine;
+
+/**
+ * 字符串模板格式化输出
+ * @author duxikun
+ */
+public class S {
+	
+	/**
+	 * 按模板格式化字符串
+	 * @param str 模板串,如:"%s"
+	 * @param args 参数集
+	 */
+	public static String f(String str, Object... args) {
+		return String.format(str, args);
+	}
+	
+	/**
+	 * 拼接字符串参数
+	 * @param strs 字符串参数集
+	 */
+	public static String j(String... strs){
+		return j("",strs);
+	}
+	
+	/**
+	 * 拼接字符串参数,以分隔符分隔
+	 * @param divide 分隔符
+	 * @param strs 字符串参数集
+	 */
+	public static String j(String divide, String... strs){
+		if(strs==null||strs.length==0){
+			return "";
+		}
+		StringBuilder sBuilder = new StringBuilder();
+		for(int i=0;i<strs.length;i++){
+			sBuilder.append(strs[i]);
+			if(i<strs.length-1){
+				sBuilder.append(divide);
+			}
+		}
+		return sBuilder.toString();
+	}
+	
+	/**
+	 * 解析模板字符串
+	 * @param templateStr 模板字符串,详见jfinal enjoy模板文档
+	 * @param params 占位参数集
+	 * @return 解析结果
+	 */
+	public static String t(String templateStr, Kv params){
+		if(StrKit.isBlank(templateStr)){
+			return null;
+		}
+		if(params==null){
+			params = Kv.create();
+		}
+		params.set("project_path", System.getProperty("user.dir"));
+		return Engine.use()
+			.getTemplateByString(templateStr)
+			.renderToString(params);
+	}
+	
+	/**
+	 * 解析配置文件中的配置项模板字符串
+	 * @param configKey 配置文件中的配置项KEY
+	 * @param params 占位参数集
+	 * @return 解析结果
+	 */
+	public static String tc(String configKey, Kv params){
+		return t(PropKit.get(configKey),params);
+	}
+	
+	/**
+	 * 解析配置文件中的配置项模板字符串
+	 * @param configKey 配置文件中的配置项KEY
+	 * @param params 占位参数集
+	 * @return 解析结果
+	 */
+	public static String tc(String configKey){
+		return t(PropKit.get(configKey),null);
+	}
+}

+ 37 - 0
src/main/resources/config.properties

@@ -0,0 +1,37 @@
+##\u670d\u52a1\u914d\u7f6e
+#\u670d\u52a1\u7aef\u53e3
+server.port=1935
+#\u670d\u52a1\u8def\u5f84
+server.context_path=/
+#\u670d\u52a1\u7ed1\u5b9a\u57df\u540d
+#server.domain=
+
+##HLS\u914d\u7f6e
+#\u8d85\u8fc7\u8be5\u65f6\u957f\u65e0\u5ba2\u6237\u7aef\u8bbf\u95ee\u65f6\uff0c\u5c06\u4f1a\u81ea\u52a8\u7ed3\u675f\u8f6c\u7801\u8fdb\u7a0b\uff0c\u5355\u4f4d\u6beb\u79d2\uff0c\u5982\u679c\u8bbe\u5907\u591a\u662f\u63d0\u4f9b\u7684\u516c\u7f51IP\uff0c\u90a3\u8be5\u53c2\u6570\u6700\u597d\u914d\u7f6e\u7684\u5927\u4e00\u4e9b
+hls.duration=10000
+
+##\u76d1\u63a7\u8bbe\u5907\u63d0\u4f9b\u5668
+#\u9ed8\u8ba4\u63d0\u4f9b\u5668\uff0c\u8bfb\u53d6\u7684monitor.properties\u914d\u7f6e\u6587\u4ef6
+device.provider = com.rancedxk.monitor.device.provider.impl.DefaultProvider
+#\u6570\u636e\u5e93\u65b9\u5f0f\u63d0\u4f9b\u5668
+#device.provider = com.rancedxk.monitor.device.provider.impl.DbProvider
+#device.provider.db.driver=com.mysql.jdbc.Driver
+#device.provider.db.url=jdbc:mysql://192.168.10.10:3306/demo?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
+#device.provider.db.username=root
+#device.provider.db.password=
+
+##ffmpeg
+#rtsp\u8f6cm3u8\u547d\u4ee4\u6a21\u677f
+#\u53c2\u80031\uff1ahttps://my.oschina.net/ososchina/blog/828100
+#\u53c2\u80032\uff1ahttps://blog.csdn.net/defonds/article/details/9274479
+#\u53c2\u80033\uff1ahttps://www.livelu.com/201907352.html
+#-rtsp_transport tcp: \u5f3a\u5236\u4f7f\u7528TCP\u65b9\u5f0f\u5904\u7406rtsp\u6d41\uff08\u9ed8\u8ba4\u4f7f\u7528\u7684\u662fUDP\uff0c\u5728\u5206\u8fa8\u7387\u8fc7\u5927\u65f6\u4f1a\u51fa\u73b0\u82b1\u5c4f\u7684\u95ee\u9898\uff0c\u53c2\u8003\uff1ahttp://www.mamicode.com/info-detail-1871429.html\uff09
+#-s 800x450: \u8bbe\u7f6e\u8f6c\u6362\u540e\u89c6\u9891\u753b\u9762\u5c3a\u5bf8\uff0c\u53ef\u7528\u6765\u51cf\u5c0f\u753b\u9762\uff0c\u76f8\u5bf9\u7684\u5c31\u52a0\u8f7d\u7684\u5feb\u4e86
+#-hls_time n: \u8bbe\u7f6e\u6bcf\u7247\u7684\u957f\u5ea6\uff0c\u9ed8\u8ba4\u503c\u4e3a2\u3002\u5355\u4f4d\u4e3a\u79d2
+#-hls_list_size n:\u8bbe\u7f6e\u64ad\u653e\u5217\u8868\u4fdd\u5b58\u7684\u6700\u591a\u6761\u76ee\uff0c\u8bbe\u7f6e\u4e3a0\u4f1a\u4fdd\u5b58\u6709\u6240\u7247\u4fe1\u606f\uff0c\u9ed8\u8ba4\u503c\u4e3a5
+#-hls_wrap n:\u8bbe\u7f6e\u591a\u5c11\u7247\u4e4b\u540e\u5f00\u59cb\u8986\u76d6\uff0c\u5982\u679c\u8bbe\u7f6e\u4e3a0\u5219\u4e0d\u4f1a\u8986\u76d6\uff0c\u9ed8\u8ba4\u503c\u4e3a0.\u8fd9\u4e2a\u9009\u9879\u80fd\u591f\u907f\u514d\u5728\u78c1\u76d8\u4e0a\u5b58\u50a8\u8fc7\u591a\u7684\u7247\uff0c\u800c\u4e14\u80fd\u591f\u9650\u5236\u5199\u5165\u78c1\u76d8\u7684\u6700\u591a\u7684\u7247\u7684\u6570\u91cf
+#-hls_start_number n:\u8bbe\u7f6e\u64ad\u653e\u5217\u8868\u4e2dsequence number\u7684\u503c\u4e3anumber\uff0c\u9ed8\u8ba4\u503c\u4e3a0
+ffmpeg.cmd.rtsp=#(ffmpeg_path)/ffmpeg -rtsp_transport tcp -i #(monitor_stream_url) -force_key_frames "expr:gte(t,n_forced*1)" -c:v libx264 -c:a aac -s 800x450 -f hls -hls_list_size 3 -hls_time 5 -hls_wrap 3 -an #(hls_m3u8_path)
+ffmpeg.cmd.rtmp=#(ffmpeg_path)/ffmpeg -i #(monitor_stream_url) -force_key_frames "expr:gte(t,n_forced*1)" -c:v libx264 -c:a aac -s 800x450 -f hls -hls_list_size 3 -hls_time 5 -hls_wrap 3 -an #(hls_m3u8_path)
+#\u662f\u5426\u8f93\u51fa\u8f6c\u7801\u65e5\u5fd7
+ffmpeg.log=true

+ 17 - 0
src/main/resources/log4j.properties

@@ -0,0 +1,17 @@
+log4j.rootLogger=info,console, file
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%n%-d{yyyy-MM-dd HH:mm:ss}%n[%p]-[Thread: %t]-[%C.%M()]: %m%n
+log4j.appender.console=org.apache.log4j.ConsoleAppender
+log4j.appender.console.Target=System.out  
+log4j.appender.console.layout=org.apache.log4j.PatternLayout  
+log4j.appender.console.layout.ConversionPattern=%-d{yyyy-MM-dd HH\:mm\:ss} %c , row at %L : %m%n
+# Output to the File
+log4j.appender.file=org.apache.log4j.DailyRollingFileAppender
+log4j.appender.file.Append=true
+#windows
+log4j.appender.file.File=./logs/monitor-rtsp-hls.log
+#liunx
+log4j.appender.file.DatePattern = '.'yyyy-MM-dd'.log'
+log4j.appender.file.layout=org.apache.log4j.PatternLayout
+log4j.appender.file.layout.ConversionPattern=%n%-d{yyyy-MM-dd HH:mm:ss}%n[%p]-[Thread: %t]-[%C.%M()]: %m%n

+ 32 - 0
src/main/resources/monitor.properties

@@ -0,0 +1,32 @@
+##\u76D1\u63A7\u4FE1\u606F\u914D\u7F6E
+#\u76D1\u63A7\u8BBE\u5907\u7F16\u7801\u96C6
+#rtsp://admin:gdnxfd@123@10.75.17.111:554/mpeg4/ch1/main/av_stream
+#rtsp://admin:admin123@172.22.81.5:554/cam/realmonitor?channel=1&subtype=0
+monitor.codes=SG01_05_TD,SG01_48_TD,SG01_10_TD,SG01_11_TD,SG01_06_TD,SG01_07_TD,SG01_08_TD,SG01_12_TD,SG01_13_TD,SG01_14_TD,SG01_15_TD,SG01_16_TD,SG01_17_TD
+#\u6BCF\u4E2A\u76D1\u63A7\u8BBE\u5907\u7684\u6D41\u5730\u5740
+monitor.SG01_05_TD.title=\u77F3\u677F\u6CC9\u5854\u5E955
+monitor.SG01_05_TD.stream_url=rtsp://admin:admin123@172.22.81.5:554/cam/realmonitor?channel=1&subtype=0
+monitor.SG01_48_TD.title=\u77F3\u677F\u6CC9\u5854\u5E9548
+monitor.SG01_48_TD.stream_url=rtsp://admin:admin@172.22.85.48:554/cam/realmonitor?channel=1&subtype=0
+monitor.SG01_10_TD.title=\u77F3\u677F\u6CC9\u5854\u5E9510
+monitor.SG01_10_TD.stream_url=rtsp://admin:admin@172.22.81.10:554/cam/realmonitor?channel=1&subtype=0
+monitor.SG01_11_TD.title=\u77F3\u677F\u6CC9\u5854\u5E9511
+monitor.SG01_11_TD.stream_url=rtsp://admin:admin@172.22.81.11:554/cam/realmonitor?channel=1&subtype=0
+monitor.SG01_06_TD.title=\u77F3\u677F\u6CC9\u5854\u5E956
+monitor.SG01_06_TD.stream_url=rtsp://admin:admin@172.22.81.6:554/cam/realmonitor?channel=1&subtype=0
+monitor.SG01_07_TD.title=\u77F3\u677F\u6CC9\u5854\u5E957
+monitor.SG01_07_TD.stream_url=rtsp://admin:admin@172.22.81.7:554/cam/realmonitor?channel=1&subtype=0
+monitor.SG01_08_TD.title=\u77F3\u677F\u6CC9\u5854\u5E958
+monitor.SG01_08_TD.stream_url=rtsp://admin:admin@172.22.81.8:554/cam/realmonitor?channel=1&subtype=0
+monitor.SG01_12_TD.title=\u77F3\u677F\u6CC9\u5854\u5E9512
+monitor.SG01_12_TD.stream_url=rtsp://admin:admin@172.22.82.12:554/cam/realmonitor?channel=1&subtype=0
+monitor.SG01_13_TD.title=\u77F3\u677F\u6CC9\u5854\u5E9513
+monitor.SG01_13_TD.stream_url=rtsp://admin:admin@172.22.82.13:554/cam/realmonitor?channel=1&subtype=0
+monitor.SG01_14_TD.title=\u77F3\u677F\u6CC9\u5854\u5E9514
+monitor.SG01_14_TD.stream_url=rtsp://admin:admin@172.22.82.14:554/cam/realmonitor?channel=1&subtype=0
+monitor.SG01_15_TD.title=\u77F3\u677F\u6CC9\u5854\u5E9515
+monitor.SG01_15_TD.stream_url=rtsp://admin:admin@172.22.82.15:554/cam/realmonitor?channel=1&subtype=0
+monitor.SG01_16_TD.title=\u77F3\u677F\u6CC9\u5854\u5E9516
+monitor.SG01_16_TD.stream_url=rtsp://admin:admin@172.22.82.16:554/cam/realmonitor?channel=1&subtype=0
+monitor.SG01_17_TD.title=\u77F3\u677F\u6CC9\u5854\u5E9517
+monitor.SG01_17_TD.stream_url=rtsp://admin:admin@172.22.82.17:554/cam/realmonitor?channel=1&subtype=0

+ 49 - 0
src/main/resources/nginx.template

@@ -0,0 +1,49 @@
+worker_processes  10;
+
+events {
+	worker_connections  1024;
+}
+
+http {
+	#Nginx开启websocket支持
+	map $http_upgrade $connection_upgrade {
+	    default upgrade;
+	    '' close;
+	}
+
+	server{
+		listen	#(nginx_port);
+		server_name #(nginx_domain ?? '192.168.10.10');
+		
+		#监控视频点播服务
+		location ~ #(context_path)hls/(\w+)/index\.m3u8 {
+			proxy_ignore_client_abort on;
+			proxy_pass http://192.168.10.10:#(hls_server_port)/hls_server/process/$1;
+			proxy_redirect off;
+		}
+
+		#监控视频点播服务
+		location #(context_path)hls {
+			types {
+				application/vnd.apple.mpegurl m3u8;
+				video/mp2t ts;
+			}
+			add_header Cache-Control no-cache;
+			#后端配置支持HTTP1.1,必须配
+			proxy_http_version 1.1;
+			proxy_set_header Connection "";
+			#存放hls切片的路径
+			alias '#(hls_path)';
+			autoindex on;
+			expires 1h;
+		}
+		
+		#预览所有监控
+		location #(context_path) {
+			proxy_pass http://192.168.10.10:#(hls_server_port)/hls_server/;
+			proxy_http_version 1.1;
+			proxy_set_header Upgrade $http_upgrade;
+			proxy_set_header Connection $connection_upgrade;
+		}
+	}
+}

+ 108 - 0
src/main/webapp/page/live/index.html

@@ -0,0 +1,108 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Live</title>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"/>
+<!-- 引入样式 -->
+<link rel="stylesheet" href="#(contextPath)static/plugins/element/element.min.css">
+</head>
+<body>
+<div id="app">
+	<el-container style="height: calc(100vh - 16px); border: 1px solid #eee">
+		<el-aside width="200px" style="background-color: rgb(238, 241, 246)">
+			<el-menu @select="onDevChoosed">
+				<el-menu-item v-for="(item,index) in devs" :index="index">
+					<span slot="title">{{item.title}}</span>
+					<i class="el-submenu__icon-arrow el-icon-arrow-right"></i>
+				</el-menu-item>
+			</el-menu>
+		</el-aside>
+		<el-container>
+			<el-main>
+				<video ref="video" controls style="width:100%;height:100%;"></video>
+			</el-main>
+		</el-container>
+	</el-container>
+</div>
+<script type="text/javascript" src="#(contextPath)static/plugins/vue/vue.min.js"></script>
+<script type="text/javascript" src="#(contextPath)static/plugins/element/element.min.js"></script>
+<script type="text/javascript" src="#(contextPath)static/plugins/hls/hls.min.js"></script>
+<script type="text/javascript">
+new Vue({
+	el:"#app",
+	data:{
+		socket:null,
+		isReady:false,
+		devs:[],
+		hls:null,
+		currDevCode:null
+	},
+	methods:{
+		_play:function(){
+			if (Hls.isSupported()) {
+				var self = this;
+				self.$nextTick(function(){
+					var video = self.$refs.video;
+					if(self.hls){
+						self.hls.destroy();
+					}
+					video.volume = 1.0;
+					self.hls = new Hls();
+					self.hls.on(Hls.Events.MANIFEST_PARSED, function () {
+						self.$refs.video.play();
+					});
+					self.hls.loadSource(`http://192.168.10.10:#(serverPort)#(contextPath)hls/${self.currDevCode}/index.m3u8`);
+					self.hls.attachMedia(video);
+				});
+			}
+		},
+		onReady:function(){
+			this.send({action:"loadDevs"})
+		},
+		onDevChoosed:function(index){
+			this.currDevCode = this.devs[index].code;
+			this._play();
+		},
+		onMessage:function(data){
+			var result = eval(data);
+			if(result.code==="fail"){
+				if(result.message){
+					this.$message.error(result.message);
+				}else{
+					this.$message.error("操作失败");
+				}
+			}else if(result.action=="loadDevs"){
+				this.devs = result.object;
+			}
+		},
+		send:function(message){
+			if(!this.isReady){
+				alert("this socket is not ready!");
+				return;
+			}
+			this.socket.send(JSON.stringify(message));
+		}
+	},
+	created:function(){
+		var self = this;
+		if(window.WebSocket){
+			self.socket = new WebSocket('ws://'+window.location.host+'/api.ws');
+			//建立websocket连接
+			self.socket.onopen = function(){
+				self.isReady = self.socket.readyState==1;
+				self.onReady();
+			};
+			self.socket.onmessage = function(event) {
+			    var data = JSON.parse(event.data);
+				self.onMessage(data);
+			};
+			self.socket.onclose = function(event) {
+				self.isReady = false;
+			};
+		}
+	}
+});
+</script>
+</body>
+</html>

文件差異過大導致無法顯示
+ 1 - 0
src/main/webapp/static/plugins/element/element.min.css


文件差異過大導致無法顯示
+ 39 - 0
src/main/webapp/static/plugins/element/element.min.js


二進制
src/main/webapp/static/plugins/element/fonts/element-icons.ttf


二進制
src/main/webapp/static/plugins/element/fonts/element-icons.woff


文件差異過大導致無法顯示
+ 2 - 0
src/main/webapp/static/plugins/hls/hls.min.js


文件差異過大導致無法顯示
+ 9205 - 0
src/main/webapp/static/plugins/jquery/jquery.js


文件差異過大導致無法顯示
+ 5 - 0
src/main/webapp/static/plugins/jquery/jquery.min.js


+ 381 - 0
src/main/webapp/static/plugins/vue/plugins/draggable/vuedraggable.js

@@ -0,0 +1,381 @@
+'use strict';
+var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
+
+function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
+
+(function () {
+  "use strict";
+
+  if (!Array.from) {
+    Array.from = function (object) {
+      return [].slice.call(object);
+    };
+  }
+
+  function buildDraggable(Sortable) {
+    function removeNode(node) {
+      node.parentElement.removeChild(node);
+    }
+
+    function insertNodeAt(fatherNode, node, position) {
+      var refNode = position === 0 ? fatherNode.children[0] : fatherNode.children[position - 1].nextSibling;
+      fatherNode.insertBefore(node, refNode);
+    }
+
+    function computeVmIndex(vnodes, element) {
+      return vnodes.map(function (elt) {
+        return elt.elm;
+      }).indexOf(element);
+    }
+
+    function _computeIndexes(slots, children, isTransition) {
+      if (!slots) {
+        return [];
+      }
+
+      var elmFromNodes = slots.map(function (elt) {
+        return elt.elm;
+      });
+      var rawIndexes = [].concat(_toConsumableArray(children)).map(function (elt) {
+        return elmFromNodes.indexOf(elt);
+      });
+      return isTransition ? rawIndexes.filter(function (ind) {
+        return ind !== -1;
+      }) : rawIndexes;
+    }
+
+    function emit(evtName, evtData) {
+      var _this = this;
+
+      this.$nextTick(function () {
+        return _this.$emit(evtName.toLowerCase(), evtData);
+      });
+    }
+
+    function delegateAndEmit(evtName) {
+      var _this2 = this;
+
+      return function (evtData) {
+        if (_this2.realList !== null) {
+          _this2['onDrag' + evtName](evtData);
+        }
+        emit.call(_this2, evtName, evtData);
+      };
+    }
+
+    var eventsListened = ['Start', 'Add', 'Remove', 'Update', 'End'];
+    var eventsToEmit = ['Choose', 'Sort', 'Filter', 'Clone'];
+    var readonlyProperties = ['Move'].concat(eventsListened, eventsToEmit).map(function (evt) {
+      return 'on' + evt;
+    });
+    var draggingElement = null;
+
+    var props = {
+      options: Object,
+      list: {
+        type: Array,
+        required: false,
+        default: null
+      },
+      value: {
+        type: Array,
+        required: false,
+        default: null
+      },
+      noTransitionOnDrag: {
+        type: Boolean,
+        default: false
+      },
+      clone: {
+        type: Function,
+        default: function _default(original) {
+          return original;
+        }
+      },
+      element: {
+        type: String,
+        default: 'div'
+      },
+      move: {
+        type: Function,
+        default: null
+      }
+    };
+
+    var draggableComponent = {
+      name: 'draggable',
+
+      props: props,
+
+      data: function data() {
+        return {
+          transitionMode: false,
+          componentMode: false
+        };
+      },
+      render: function render(h) {
+        var slots = this.$slots.default;
+        if (slots && slots.length === 1) {
+          var child = slots[0];
+          if (child.componentOptions && child.componentOptions.tag === "transition-group") {
+            this.transitionMode = true;
+          }
+        }
+        var children = slots;
+        var footer = this.$slots.footer;
+
+        if (footer) {
+          children = slots ? [].concat(_toConsumableArray(slots), _toConsumableArray(footer)) : [].concat(_toConsumableArray(footer));
+        }
+        return h(this.element, null, children);
+      },
+      mounted: function mounted() {
+        var _this3 = this;
+
+        this.componentMode = this.element.toLowerCase() !== this.$el.nodeName.toLowerCase();
+        if (this.componentMode && this.transitionMode) {
+          throw new Error('Transition-group inside component is not supported. Please alter element value or remove transition-group. Current element value: ' + this.element);
+        }
+        var optionsAdded = {};
+        eventsListened.forEach(function (elt) {
+          optionsAdded['on' + elt] = delegateAndEmit.call(_this3, elt);
+        });
+
+        eventsToEmit.forEach(function (elt) {
+          optionsAdded['on' + elt] = emit.bind(_this3, elt);
+        });
+
+        var options = _extends({}, this.options, optionsAdded, { onMove: function onMove(evt, originalEvent) {
+            return _this3.onDragMove(evt, originalEvent);
+          } });
+        !('draggable' in options) && (options.draggable = '>*');
+        this._sortable = new Sortable(this.rootContainer, options);
+        this.computeIndexes();
+      },
+      beforeDestroy: function beforeDestroy() {
+        this._sortable.destroy();
+      },
+
+
+      computed: {
+        rootContainer: function rootContainer() {
+          return this.transitionMode ? this.$el.children[0] : this.$el;
+        },
+        isCloning: function isCloning() {
+          return !!this.options && !!this.options.group && this.options.group.pull === 'clone';
+        },
+        realList: function realList() {
+          return !!this.list ? this.list : this.value;
+        }
+      },
+
+      watch: {
+        options: {
+          handler: function handler(newOptionValue) {
+            for (var property in newOptionValue) {
+              if (readonlyProperties.indexOf(property) == -1) {
+                this._sortable.option(property, newOptionValue[property]);
+              }
+            }
+          },
+
+          deep: true
+        },
+
+        realList: function realList() {
+          this.computeIndexes();
+        }
+      },
+
+      methods: {
+        getChildrenNodes: function getChildrenNodes() {
+          if (this.componentMode) {
+            return this.$children[0].$slots.default;
+          }
+          var rawNodes = this.$slots.default;
+          return this.transitionMode ? rawNodes[0].child.$slots.default : rawNodes;
+        },
+        computeIndexes: function computeIndexes() {
+          var _this4 = this;
+
+          this.$nextTick(function () {
+            _this4.visibleIndexes = _computeIndexes(_this4.getChildrenNodes(), _this4.rootContainer.children, _this4.transitionMode);
+          });
+        },
+        getUnderlyingVm: function getUnderlyingVm(htmlElt) {
+          var index = computeVmIndex(this.getChildrenNodes() || [], htmlElt);
+          if (index === -1) {
+            //Edge case during move callback: related element might be
+            //an element different from collection
+            return null;
+          }
+          var element = this.realList[index];
+          return { index: index, element: element };
+        },
+        getUnderlyingPotencialDraggableComponent: function getUnderlyingPotencialDraggableComponent(_ref) {
+          var __vue__ = _ref.__vue__;
+
+          if (!__vue__ || !__vue__.$options || __vue__.$options._componentTag !== "transition-group") {
+            return __vue__;
+          }
+          return __vue__.$parent;
+        },
+        emitChanges: function emitChanges(evt) {
+          var _this5 = this;
+
+          this.$nextTick(function () {
+            _this5.$emit('change', evt);
+          });
+        },
+        alterList: function alterList(onList) {
+          if (!!this.list) {
+            onList(this.list);
+          } else {
+            var newList = [].concat(_toConsumableArray(this.value));
+            onList(newList);
+            this.$emit('input', newList);
+          }
+        },
+        spliceList: function spliceList() {
+          var _arguments = arguments;
+
+          var spliceList = function spliceList(list) {
+            return list.splice.apply(list, _arguments);
+          };
+          this.alterList(spliceList);
+        },
+        updatePosition: function updatePosition(oldIndex, newIndex) {
+          var updatePosition = function updatePosition(list) {
+            return list.splice(newIndex, 0, list.splice(oldIndex, 1)[0]);
+          };
+          this.alterList(updatePosition);
+        },
+        getRelatedContextFromMoveEvent: function getRelatedContextFromMoveEvent(_ref2) {
+          var to = _ref2.to,
+              related = _ref2.related;
+
+          var component = this.getUnderlyingPotencialDraggableComponent(to);
+          if (!component) {
+            return { component: component };
+          }
+          var list = component.realList;
+          var context = { list: list, component: component };
+          if (to !== related && list && component.getUnderlyingVm) {
+            var destination = component.getUnderlyingVm(related);
+            if (destination) {
+              return _extends(destination, context);
+            }
+          }
+
+          return context;
+        },
+        getVmIndex: function getVmIndex(domIndex) {
+          var indexes = this.visibleIndexes;
+          var numberIndexes = indexes.length;
+          return domIndex > numberIndexes - 1 ? numberIndexes : indexes[domIndex];
+        },
+        getComponent: function getComponent() {
+          return this.$slots.default[0].componentInstance;
+        },
+        resetTransitionData: function resetTransitionData(index) {
+          if (!this.noTransitionOnDrag || !this.transitionMode) {
+            return;
+          }
+          var nodes = this.getChildrenNodes();
+          nodes[index].data = null;
+          var transitionContainer = this.getComponent();
+          transitionContainer.children = [];
+          transitionContainer.kept = undefined;
+        },
+        onDragStart: function onDragStart(evt) {
+          this.context = this.getUnderlyingVm(evt.item);
+          evt.item._underlying_vm_ = this.clone(this.context.element);
+          draggingElement = evt.item;
+        },
+        onDragAdd: function onDragAdd(evt) {
+          var element = evt.item._underlying_vm_;
+          if (element === undefined) {
+            return;
+          }
+          removeNode(evt.item);
+          var newIndex = this.getVmIndex(evt.newIndex);
+          this.spliceList(newIndex, 0, element);
+          this.computeIndexes();
+          var added = { element: element, newIndex: newIndex };
+          this.emitChanges({ added: added });
+        },
+        onDragRemove: function onDragRemove(evt) {
+          insertNodeAt(this.rootContainer, evt.item, evt.oldIndex);
+          if (this.isCloning) {
+            removeNode(evt.clone);
+            return;
+          }
+          var oldIndex = this.context.index;
+          this.spliceList(oldIndex, 1);
+          var removed = { element: this.context.element, oldIndex: oldIndex };
+          this.resetTransitionData(oldIndex);
+          this.emitChanges({ removed: removed });
+        },
+        onDragUpdate: function onDragUpdate(evt) {
+          removeNode(evt.item);
+          insertNodeAt(evt.from, evt.item, evt.oldIndex);
+          var oldIndex = this.context.index;
+          var newIndex = this.getVmIndex(evt.newIndex);
+          this.updatePosition(oldIndex, newIndex);
+          var moved = { element: this.context.element, oldIndex: oldIndex, newIndex: newIndex };
+          this.emitChanges({ moved: moved });
+        },
+        computeFutureIndex: function computeFutureIndex(relatedContext, evt) {
+          if (!relatedContext.element) {
+            return 0;
+          }
+          var domChildren = [].concat(_toConsumableArray(evt.to.children)).filter(function (el) {
+            return el.style['display'] !== 'none';
+          });
+          var currentDOMIndex = domChildren.indexOf(evt.related);
+          var currentIndex = relatedContext.component.getVmIndex(currentDOMIndex);
+          var draggedInList = domChildren.indexOf(draggingElement) != -1;
+          return draggedInList || !evt.willInsertAfter ? currentIndex : currentIndex + 1;
+        },
+        onDragMove: function onDragMove(evt, originalEvent) {
+          var onMove = this.move;
+          if (!onMove || !this.realList) {
+            return true;
+          }
+
+          var relatedContext = this.getRelatedContextFromMoveEvent(evt);
+          var draggedContext = this.context;
+          var futureIndex = this.computeFutureIndex(relatedContext, evt);
+          _extends(draggedContext, { futureIndex: futureIndex });
+          _extends(evt, { relatedContext: relatedContext, draggedContext: draggedContext });
+          return onMove(evt, originalEvent);
+        },
+        onDragEnd: function onDragEnd(evt) {
+          this.computeIndexes();
+          draggingElement = null;
+        }
+      }
+    };
+    return draggableComponent;
+  }
+
+  if (typeof exports == "object") {
+    var Sortable = require("sortablejs");
+    module.exports = buildDraggable(Sortable);
+  } else if (typeof define == "function" && define.amd) {
+    define(['sortablejs'], function (Sortable) {
+      return buildDraggable(Sortable);
+    });
+  } else if (window && window.Vue && window.Sortable) {
+    var draggable = buildDraggable(window.Sortable);
+    Vue.component('draggable', draggable);
+  } else {
+    if(typeof window.Vue == "undefined") {
+      throw 'Vue.js not found!';
+    }
+    
+    if(typeof window.Sortable == "undefined") {
+      throw 'Sortable.js not found!';
+    }
+  }
+})();

文件差異過大導致無法顯示
+ 1 - 0
src/main/webapp/static/plugins/vue/plugins/draggable/vuedraggable.min.js


文件差異過大導致無法顯示
+ 6 - 0
src/main/webapp/static/plugins/vue/vue.min.js


+ 34 - 0
start.bat

@@ -0,0 +1,34 @@
+@echo off
+ 
+rem ---------------------------------------------------------------
+rem
+rem 使用说明:
+rem
+rem 1: 该脚本用于别的项目时只需要修改 MAIN_CLASS 即可运行
+rem
+rem 2: JAVA_OPTS 可通过 -D 传入 undertow.port 与 undertow.host 这类参数覆盖
+rem    配置文件中的相同值此外还有 undertow.resourcePath, undertow.ioThreads
+rem    undertow.workerThreads 共五个参数可通过 -D 进行传入
+rem
+rem 3: JAVA_OPTS 可传入标准的 java 命令行参数,例如 -Xms256m -Xmx1024m 这类常用参数
+rem
+rem
+rem ---------------------------------------------------------------
+ 
+setlocal & pushd
+ 
+ 
+rem 启动入口类,该脚本文件用于别的项目时要改这里
+set MAIN_CLASS=com.rancedxk.monitor.Main
+ 
+rem Java 命令行参数,根据需要开启下面的配置,改成自己需要的,注意等号前后不能有空格
+rem set "JAVA_OPTS=-Xms256m -Xmx1024m -Dundertow.port=80 -Dundertow.host=0.0.0.0"
+rem set "JAVA_OPTS=-Dundertow.port=80 -Dundertow.host=0.0.0.0"
+ 
+set APP_BASE_PATH=%~dp0
+set CP=%APP_BASE_PATH%conf;%APP_BASE_PATH%lib\*
+java -Xverify:none %JAVA_OPTS% -cp %CP% %MAIN_CLASS%
+ 
+ 
+endlocal & popd
+pause

+ 15 - 0
stop.bat

@@ -0,0 +1,15 @@
+@echo off
+set url1="%~dp0\third\nginx\nginx.exe"
+set url2="%~dp0\third\ffmpeg\ffmpeg.exe"
+echo 本程序用来手动关闭nginx.exe进程和ffmpeg.exe进程,并不会关闭hls服务,所以一旦关闭nginx.exe后,hls服务将无法正常访问,需要用户手动关闭start.bat窗口!
+set /p var="请选择y/n:"
+if /i "%var%"=="y" (
+	for /f "tokens=1,2" %%a in ('"wmic process where name="nginx.exe" get processid,executablepath| findstr /C:%url1%"') do (
+		taskkill /f /pid %%b
+	)
+	for /f "tokens=1,2" %%a in ('"wmic process where name="ffmpeg.exe" get processid,executablepath| findstr /C:%url2%"') do (
+		taskkill /f /pid %%b
+	)
+)
+
+pause

+ 37 - 0
target/monitor-rtsp-hls/conf/config.properties

@@ -0,0 +1,37 @@
+##\u670d\u52a1\u914d\u7f6e
+#\u670d\u52a1\u7aef\u53e3
+server.port=1935
+#\u670d\u52a1\u8def\u5f84
+server.context_path=/
+#\u670d\u52a1\u7ed1\u5b9a\u57df\u540d
+#server.domain=
+
+##HLS\u914d\u7f6e
+#\u8d85\u8fc7\u8be5\u65f6\u957f\u65e0\u5ba2\u6237\u7aef\u8bbf\u95ee\u65f6\uff0c\u5c06\u4f1a\u81ea\u52a8\u7ed3\u675f\u8f6c\u7801\u8fdb\u7a0b\uff0c\u5355\u4f4d\u6beb\u79d2\uff0c\u5982\u679c\u8bbe\u5907\u591a\u662f\u63d0\u4f9b\u7684\u516c\u7f51IP\uff0c\u90a3\u8be5\u53c2\u6570\u6700\u597d\u914d\u7f6e\u7684\u5927\u4e00\u4e9b
+hls.duration=10000
+
+##\u76d1\u63a7\u8bbe\u5907\u63d0\u4f9b\u5668
+#\u9ed8\u8ba4\u63d0\u4f9b\u5668\uff0c\u8bfb\u53d6\u7684monitor.properties\u914d\u7f6e\u6587\u4ef6
+device.provider = com.rancedxk.monitor.device.provider.impl.DefaultProvider
+#\u6570\u636e\u5e93\u65b9\u5f0f\u63d0\u4f9b\u5668
+#device.provider = com.rancedxk.monitor.device.provider.impl.DbProvider
+#device.provider.db.driver=com.mysql.jdbc.Driver
+#device.provider.db.url=jdbc:mysql://192.168.10.10:3306/demo?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
+#device.provider.db.username=root
+#device.provider.db.password=
+
+##ffmpeg
+#rtsp\u8f6cm3u8\u547d\u4ee4\u6a21\u677f
+#\u53c2\u80031\uff1ahttps://my.oschina.net/ososchina/blog/828100
+#\u53c2\u80032\uff1ahttps://blog.csdn.net/defonds/article/details/9274479
+#\u53c2\u80033\uff1ahttps://www.livelu.com/201907352.html
+#-rtsp_transport tcp: \u5f3a\u5236\u4f7f\u7528TCP\u65b9\u5f0f\u5904\u7406rtsp\u6d41\uff08\u9ed8\u8ba4\u4f7f\u7528\u7684\u662fUDP\uff0c\u5728\u5206\u8fa8\u7387\u8fc7\u5927\u65f6\u4f1a\u51fa\u73b0\u82b1\u5c4f\u7684\u95ee\u9898\uff0c\u53c2\u8003\uff1ahttp://www.mamicode.com/info-detail-1871429.html\uff09
+#-s 800x450: \u8bbe\u7f6e\u8f6c\u6362\u540e\u89c6\u9891\u753b\u9762\u5c3a\u5bf8\uff0c\u53ef\u7528\u6765\u51cf\u5c0f\u753b\u9762\uff0c\u76f8\u5bf9\u7684\u5c31\u52a0\u8f7d\u7684\u5feb\u4e86
+#-hls_time n: \u8bbe\u7f6e\u6bcf\u7247\u7684\u957f\u5ea6\uff0c\u9ed8\u8ba4\u503c\u4e3a2\u3002\u5355\u4f4d\u4e3a\u79d2
+#-hls_list_size n:\u8bbe\u7f6e\u64ad\u653e\u5217\u8868\u4fdd\u5b58\u7684\u6700\u591a\u6761\u76ee\uff0c\u8bbe\u7f6e\u4e3a0\u4f1a\u4fdd\u5b58\u6709\u6240\u7247\u4fe1\u606f\uff0c\u9ed8\u8ba4\u503c\u4e3a5
+#-hls_wrap n:\u8bbe\u7f6e\u591a\u5c11\u7247\u4e4b\u540e\u5f00\u59cb\u8986\u76d6\uff0c\u5982\u679c\u8bbe\u7f6e\u4e3a0\u5219\u4e0d\u4f1a\u8986\u76d6\uff0c\u9ed8\u8ba4\u503c\u4e3a0.\u8fd9\u4e2a\u9009\u9879\u80fd\u591f\u907f\u514d\u5728\u78c1\u76d8\u4e0a\u5b58\u50a8\u8fc7\u591a\u7684\u7247\uff0c\u800c\u4e14\u80fd\u591f\u9650\u5236\u5199\u5165\u78c1\u76d8\u7684\u6700\u591a\u7684\u7247\u7684\u6570\u91cf
+#-hls_start_number n:\u8bbe\u7f6e\u64ad\u653e\u5217\u8868\u4e2dsequence number\u7684\u503c\u4e3anumber\uff0c\u9ed8\u8ba4\u503c\u4e3a0
+ffmpeg.cmd.rtsp=#(ffmpeg_path)/ffmpeg -rtsp_transport tcp -i #(monitor_stream_url) -force_key_frames "expr:gte(t,n_forced*1)" -c:v libx264 -c:a aac -s 800x450 -f hls -hls_list_size 3 -hls_time 5 -hls_wrap 3 -an #(hls_m3u8_path)
+ffmpeg.cmd.rtmp=#(ffmpeg_path)/ffmpeg -i #(monitor_stream_url) -force_key_frames "expr:gte(t,n_forced*1)" -c:v libx264 -c:a aac -s 800x450 -f hls -hls_list_size 3 -hls_time 5 -hls_wrap 3 -an #(hls_m3u8_path)
+#\u662f\u5426\u8f93\u51fa\u8f6c\u7801\u65e5\u5fd7
+ffmpeg.log=true

+ 17 - 0
target/monitor-rtsp-hls/conf/log4j.properties

@@ -0,0 +1,17 @@
+log4j.rootLogger=info,console, file
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%n%-d{yyyy-MM-dd HH:mm:ss}%n[%p]-[Thread: %t]-[%C.%M()]: %m%n
+log4j.appender.console=org.apache.log4j.ConsoleAppender
+log4j.appender.console.Target=System.out  
+log4j.appender.console.layout=org.apache.log4j.PatternLayout  
+log4j.appender.console.layout.ConversionPattern=%-d{yyyy-MM-dd HH\:mm\:ss} %c , row at %L : %m%n
+# Output to the File
+log4j.appender.file=org.apache.log4j.DailyRollingFileAppender
+log4j.appender.file.Append=true
+#windows
+log4j.appender.file.File=./logs/monitor-rtsp-hls.log
+#liunx
+log4j.appender.file.DatePattern = '.'yyyy-MM-dd'.log'
+log4j.appender.file.layout=org.apache.log4j.PatternLayout
+log4j.appender.file.layout.ConversionPattern=%n%-d{yyyy-MM-dd HH:mm:ss}%n[%p]-[Thread: %t]-[%C.%M()]: %m%n

+ 32 - 0
target/monitor-rtsp-hls/conf/monitor.properties

@@ -0,0 +1,32 @@
+##\u76D1\u63A7\u4FE1\u606F\u914D\u7F6E
+#\u76D1\u63A7\u8BBE\u5907\u7F16\u7801\u96C6
+#rtsp://admin:gdnxfd@123@10.75.17.111:554/mpeg4/ch1/main/av_stream
+#rtsp://admin:admin123@172.22.81.5:554/cam/realmonitor?channel=1&subtype=0
+monitor.codes=SG01_05_TD,SG01_48_TD,SG01_10_TD,SG01_11_TD,SG01_06_TD,SG01_07_TD,SG01_08_TD,SG01_12_TD,SG01_13_TD,SG01_14_TD,SG01_15_TD,SG01_16_TD,SG01_17_TD
+#\u6BCF\u4E2A\u76D1\u63A7\u8BBE\u5907\u7684\u6D41\u5730\u5740
+monitor.SG01_05_TD.title=\u77F3\u677F\u6CC9\u5854\u5E955
+monitor.SG01_05_TD.stream_url=rtsp://admin:admin123@172.22.81.5:554/cam/realmonitor?channel=1&subtype=0
+monitor.SG01_48_TD.title=\u77F3\u677F\u6CC9\u5854\u5E9548
+monitor.SG01_48_TD.stream_url=rtsp://admin:admin@172.22.85.48:554/cam/realmonitor?channel=1&subtype=0
+monitor.SG01_10_TD.title=\u77F3\u677F\u6CC9\u5854\u5E9510
+monitor.SG01_10_TD.stream_url=rtsp://admin:admin@172.22.81.10:554/cam/realmonitor?channel=1&subtype=0
+monitor.SG01_11_TD.title=\u77F3\u677F\u6CC9\u5854\u5E9511
+monitor.SG01_11_TD.stream_url=rtsp://admin:admin@172.22.81.11:554/cam/realmonitor?channel=1&subtype=0
+monitor.SG01_06_TD.title=\u77F3\u677F\u6CC9\u5854\u5E956
+monitor.SG01_06_TD.stream_url=rtsp://admin:admin@172.22.81.6:554/cam/realmonitor?channel=1&subtype=0
+monitor.SG01_07_TD.title=\u77F3\u677F\u6CC9\u5854\u5E957
+monitor.SG01_07_TD.stream_url=rtsp://admin:admin@172.22.81.7:554/cam/realmonitor?channel=1&subtype=0
+monitor.SG01_08_TD.title=\u77F3\u677F\u6CC9\u5854\u5E958
+monitor.SG01_08_TD.stream_url=rtsp://admin:admin@172.22.81.8:554/cam/realmonitor?channel=1&subtype=0
+monitor.SG01_12_TD.title=\u77F3\u677F\u6CC9\u5854\u5E9512
+monitor.SG01_12_TD.stream_url=rtsp://admin:admin@172.22.82.12:554/cam/realmonitor?channel=1&subtype=0
+monitor.SG01_13_TD.title=\u77F3\u677F\u6CC9\u5854\u5E9513
+monitor.SG01_13_TD.stream_url=rtsp://admin:admin@172.22.82.13:554/cam/realmonitor?channel=1&subtype=0
+monitor.SG01_14_TD.title=\u77F3\u677F\u6CC9\u5854\u5E9514
+monitor.SG01_14_TD.stream_url=rtsp://admin:admin@172.22.82.14:554/cam/realmonitor?channel=1&subtype=0
+monitor.SG01_15_TD.title=\u77F3\u677F\u6CC9\u5854\u5E9515
+monitor.SG01_15_TD.stream_url=rtsp://admin:admin@172.22.82.15:554/cam/realmonitor?channel=1&subtype=0
+monitor.SG01_16_TD.title=\u77F3\u677F\u6CC9\u5854\u5E9516
+monitor.SG01_16_TD.stream_url=rtsp://admin:admin@172.22.82.16:554/cam/realmonitor?channel=1&subtype=0
+monitor.SG01_17_TD.title=\u77F3\u677F\u6CC9\u5854\u5E9517
+monitor.SG01_17_TD.stream_url=rtsp://admin:admin@172.22.82.17:554/cam/realmonitor?channel=1&subtype=0

+ 49 - 0
target/monitor-rtsp-hls/conf/nginx.template

@@ -0,0 +1,49 @@
+worker_processes  10;
+
+events {
+	worker_connections  1024;
+}
+
+http {
+	#Nginx开启websocket支持
+	map $http_upgrade $connection_upgrade {
+	    default upgrade;
+	    '' close;
+	}
+
+	server{
+		listen	#(nginx_port);
+		server_name #(nginx_domain ?? '192.168.10.10');
+		
+		#监控视频点播服务
+		location ~ #(context_path)hls/(\w+)/index\.m3u8 {
+			proxy_ignore_client_abort on;
+			proxy_pass http://192.168.10.10:#(hls_server_port)/hls_server/process/$1;
+			proxy_redirect off;
+		}
+
+		#监控视频点播服务
+		location #(context_path)hls {
+			types {
+				application/vnd.apple.mpegurl m3u8;
+				video/mp2t ts;
+			}
+			add_header Cache-Control no-cache;
+			#后端配置支持HTTP1.1,必须配
+			proxy_http_version 1.1;
+			proxy_set_header Connection "";
+			#存放hls切片的路径
+			alias '#(hls_path)';
+			autoindex on;
+			expires 1h;
+		}
+		
+		#预览所有监控
+		location #(context_path) {
+			proxy_pass http://192.168.10.10:#(hls_server_port)/hls_server/;
+			proxy_http_version 1.1;
+			proxy_set_header Upgrade $http_upgrade;
+			proxy_set_header Connection $connection_upgrade;
+		}
+	}
+}

二進制
target/monitor-rtsp-hls/lib/druid-1.1.21.jar


二進制
target/monitor-rtsp-hls/lib/hutool-all-4.6.1.jar


二進制
target/monitor-rtsp-hls/lib/javax.servlet-api-4.0.1.jar


二進制
target/monitor-rtsp-hls/lib/jboss-logging-3.4.0.Final.jar


二進制
target/monitor-rtsp-hls/lib/jboss-websocket-api_1.1_spec-1.1.4.Final.jar


二進制
target/monitor-rtsp-hls/lib/jfinal-4.9.06.jar


二進制
target/monitor-rtsp-hls/lib/jfinal-undertow-2.4.jar


二進制
target/monitor-rtsp-hls/lib/jna-5.4.0.jar


二進制
target/monitor-rtsp-hls/lib/jna-platform-5.4.0.jar


二進制
target/monitor-rtsp-hls/lib/log4j-1.2.17.jar


二進制
target/monitor-rtsp-hls/lib/monitor-rtsp-hls-1.0.jar


二進制
target/monitor-rtsp-hls/lib/mysql-connector-java-8.0.18.jar


二進制
target/monitor-rtsp-hls/lib/protobuf-java-3.6.1.jar


二進制
target/monitor-rtsp-hls/lib/slf4j-api-1.7.25.jar


二進制
target/monitor-rtsp-hls/lib/slf4j-log4j12-1.7.25.jar


二進制
target/monitor-rtsp-hls/lib/undertow-core-2.0.33.Final.jar


二進制
target/monitor-rtsp-hls/lib/undertow-servlet-2.0.33.Final.jar


二進制
target/monitor-rtsp-hls/lib/undertow-websockets-jsr-2.0.33.Final.jar


二進制
target/monitor-rtsp-hls/lib/xnio-api-3.3.8.Final.jar


二進制
target/monitor-rtsp-hls/lib/xnio-nio-3.3.8.Final.jar


文件差異過大導致無法顯示
+ 1275 - 0
target/monitor-rtsp-hls/logs/monitor-rtsp-hls.log


+ 34 - 0
target/monitor-rtsp-hls/start.bat

@@ -0,0 +1,34 @@
+@echo off
+ 
+rem ---------------------------------------------------------------
+rem
+rem 使用说明:
+rem
+rem 1: 该脚本用于别的项目时只需要修改 MAIN_CLASS 即可运行
+rem
+rem 2: JAVA_OPTS 可通过 -D 传入 undertow.port 与 undertow.host 这类参数覆盖
+rem    配置文件中的相同值此外还有 undertow.resourcePath, undertow.ioThreads
+rem    undertow.workerThreads 共五个参数可通过 -D 进行传入
+rem
+rem 3: JAVA_OPTS 可传入标准的 java 命令行参数,例如 -Xms256m -Xmx1024m 这类常用参数
+rem
+rem
+rem ---------------------------------------------------------------
+ 
+setlocal & pushd
+ 
+ 
+rem 启动入口类,该脚本文件用于别的项目时要改这里
+set MAIN_CLASS=com.rancedxk.monitor.Main
+ 
+rem Java 命令行参数,根据需要开启下面的配置,改成自己需要的,注意等号前后不能有空格
+rem set "JAVA_OPTS=-Xms256m -Xmx1024m -Dundertow.port=80 -Dundertow.host=0.0.0.0"
+rem set "JAVA_OPTS=-Dundertow.port=80 -Dundertow.host=0.0.0.0"
+ 
+set APP_BASE_PATH=%~dp0
+set CP=%APP_BASE_PATH%conf;%APP_BASE_PATH%lib\*
+java -Xverify:none %JAVA_OPTS% -cp %CP% %MAIN_CLASS%
+ 
+ 
+endlocal & popd
+pause

+ 15 - 0
target/monitor-rtsp-hls/stop.bat

@@ -0,0 +1,15 @@
+@echo off
+set url1="%~dp0\third\nginx\nginx.exe"
+set url2="%~dp0\third\ffmpeg\ffmpeg.exe"
+echo 本程序用来手动关闭nginx.exe进程和ffmpeg.exe进程,并不会关闭hls服务,所以一旦关闭nginx.exe后,hls服务将无法正常访问,需要用户手动关闭start.bat窗口!
+set /p var="请选择y/n:"
+if /i "%var%"=="y" (
+	for /f "tokens=1,2" %%a in ('"wmic process where name="nginx.exe" get processid,executablepath| findstr /C:%url1%"') do (
+		taskkill /f /pid %%b
+	)
+	for /f "tokens=1,2" %%a in ('"wmic process where name="ffmpeg.exe" get processid,executablepath| findstr /C:%url2%"') do (
+		taskkill /f /pid %%b
+	)
+)
+
+pause

+ 9 - 0
target/monitor-rtsp-hls/third/Readme.md

@@ -0,0 +1,9 @@
+本目录中存放所使用到的第三方软件和转码后的hls相关文件(m3u8文件和ts切片文件)
+
+nginx:解析http请求,转发转码后生成的m3u8和ts文件,当前版本1.19.6
+
+ffmpeg:用来执行流媒体转码,当前版本n4.3.1-26-gca55240b8c
+
+hls:用来存放生成的m3u8文件和ts切片文件
+
+注意:软件版本可自行替换升级,但请升级后保持当前目录名和结构不变

二進制
target/monitor-rtsp-hls/third/ffmpeg/ffmpeg.exe


+ 0 - 0
target/monitor-rtsp-hls/third/hls/SG01_05_TD/index.m3u8


部分文件因文件數量過多而無法顯示