├── .gitignore ├── LICENSE ├── README.cn.md ├── README.md ├── img.png ├── img_1.png ├── pom.xml └── src └── main ├── java └── com │ └── gc │ └── easy │ └── flv │ ├── config │ └── FlvConfig.java │ ├── controller │ ├── FLVController.java │ └── FLVPlayController.java │ ├── factories │ ├── Converter.java │ ├── ConverterFactories.java │ ├── ConverterTranFactories.java │ └── state │ │ ├── ConverterState.java │ │ └── OutputImage.java │ ├── service │ ├── IFLVService.java │ ├── IOpenFLVService.java │ └── impl │ │ └── FLVService.java │ └── util │ └── FlvUtil.java └── resources └── templates └── video.html /.gitignore: -------------------------------------------------------------------------------- 1 | README.cn.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.cn.md: -------------------------------------------------------------------------------- 1 | 2 | # 📺 Easy-FLV: Java 实现的 RTSP/RTMP 到 FLV 转换器 3 | 4 | [![GitHub stars](https://img.shields.io/github/stars/javpower/easy-flv.svg)](https://github.com/javpower/easy-flv) 5 | [![GitHub issues](https://img.shields.io/github/issues/javpower/easy-flv.svg)](https://github.com/javpower/easy-flv/issues) 6 | [![Apache License 2.0](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 7 | [![Java Version](https://img.shields.io/badge/java-1.8+-orange.svg)](https://adoptopenjdk.net/) 8 | [![Spring Boot](https://img.shields.io/badge/spring--boot-2.7.+-blue.svg)](https://spring.io/projects/spring-boot) 9 | 10 | ## 🌟 关于 Easy-FLV 11 | Easy-FLV 是一个用 Java 实现的库,它能够将 RTSP 或 RTMP 视频流转换为 FLV 格式,以便在浏览器中播放。它为实时视频监控、直播和视频流处理提供了一个高效、稳定且易于集成的解决方案。 12 | 13 | ### 为什么选择 Easy-FLV? 14 | - **高效转换**:快速将视频流转换为 FLV 格式,无需复杂配置。 15 | - **易于集成**:作为 Spring Boot Starter 使用,轻松集成到任何 Java 项目。 16 | - **现代浏览器支持**:支持所有主流浏览器,无需额外插件。 17 | - **实时流处理**:适用于实时视频流的转换,如安防监控和直播。 18 | 19 | ## 📄 效果图 20 | 以下是 Easy-FLV 在行动的效果图: 21 | 22 | ![img_1.png](img_1.png) 23 | ![img.png](img.png) 24 | 25 | ## 🚀 快速开始 26 | 27 | ### 添加 Maven 依赖 28 | 在你的 Spring Boot 项目中,添加以下 Maven 依赖: 29 | 30 | ```xml 31 | 32 | io.github.javpower 33 | rtsp-converter-flv-spring-boot-starter 34 | 1.5.9.1 35 | 36 | ``` 37 | 38 | ### 实现接口 39 | 创建一个服务类来实现 `IOpenFLVService` 接口,并提供流地址: 40 | 41 | ```java 42 | @Service 43 | public class RtspDataService implements IOpenFLVService { 44 | 45 | @Override 46 | public String getUrl(Integer channel) { 47 | // 根据 channel 获取 RTSP 视频流地址 48 | return "rtsp://10.11.9.251:554/openUrl/16HV8mA"; 49 | } 50 | } 51 | ``` 52 | 53 | ### 配置 YAML 54 | 在 `application.yml` 中配置 Easy-FLV: 55 | 56 | ```yaml 57 | easy: 58 | flv: 59 | host: http://localhost:8200 60 | ``` 61 | 62 | ### 使用接口 63 | 通过以下接口获取转换后的流地址,并在浏览器中播放: 64 | 65 | - 转换地址:`GET http://ip:port/get/flv/hls/stream_{channel}.flv` 66 | - 播放地址:`GET http://ip:port/flv/hls/stream_{channel}.flv` 67 | 68 | ### 直接使用 69 | 如果不使用接口,可以直接编码流地址并转换: 70 | 71 | ```java 72 | public static void main(String[] args) throws UnsupportedEncodingException { 73 | String url = "rtsp://XXXXXXXX"; 74 | String encodedUrl = java.net.URLEncoder.encode(url, "UTF-8"); 75 | System.out.println("Encoded Stream URL: " + encodedUrl); 76 | } 77 | ``` 78 | 79 | - 转换地址:`GET http://ip:port/get/flv/hls/stream?url=编码后的地址` 80 | - 播放地址:`GET http://ip:port/flv/hls/stream?url=编码后的地址` 81 | 82 | ## 🛠️ 贡献 83 | 我们欢迎任何形式的贡献,包括但不限于报告 bug、提交修复、添加新功能、改进文档等。 84 | 85 | ## 📄 许可证 86 | Easy-FLV 根据 [Apache License 2.0](LICENSE) 发布。 87 | 88 | ## 📧 联系 89 | - Email: [javpower@163.com](mailto:your-email@example.com) 90 | - GitHub: [https://github.com/javpower/easy-flv](https://github.com/javpower/easy-flv) 91 | - Gitee: [https://gitee.com/giteeClass/easy-flv](https://gitee.com/giteeClass/easy-flv) 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # 📺 Easy-FLV: Java RTSP/RTMP to FLV Converter 3 | 4 | [![GitHub stars](https://img.shields.io/github/stars/javpower/easy-flv.svg)](https://github.com/javpower/easy-flv) 5 | [![GitHub issues](https://img.shields.io/github/issues/javpower/easy-flv.svg)](https://github.com/javpower/easy-flv/issues) 6 | [![Apache License 2.0](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 7 | [![Java Version](https://img.shields.io/badge/java-1.8+-orange.svg)](https://adoptopenjdk.net/) 8 | [![Spring Boot](https://img.shields.io/badge/spring--boot-2.7.+-blue.svg)](https://spring.io/projects/spring-boot) 9 | 10 | ## 🌟 About Easy-FLV 11 | Easy-FLV is a Java library that converts RTSP or RTMP video streams into FLV format for playback in web browsers. It provides an efficient, stable, and easily integrable solution for real-time video monitoring, live streaming, and video stream processing. 12 | 13 | ### Why Choose Easy-FLV? 14 | - **Efficient Conversion**: Quickly converts video streams to FLV format with no complex configuration required. 15 | - **Easy Integration**: Used as a Spring Boot Starter, it can be easily integrated into any Java project. 16 | - **Modern Browser Support**: Supports all major modern browsers without the need for additional plugins. 17 | - **Real-time Stream Processing**: Suitable for the conversion of real-time video streams, such as security monitoring and live broadcasting. 18 | 19 | ## 📄 Screenshots 20 | Below are screenshots of Easy-FLV in action: 21 | 22 | ![img_1.png](img_1.png) 23 | ![img.png](img.png) 24 | 25 | ## 🚀 Quick Start 26 | 27 | ### Add Maven Dependency 28 | Include the following Maven dependency in your Spring Boot project: 29 | 30 | ```xml 31 | 32 | io.github.javpower 33 | rtsp-converter-flv-spring-boot-starter 34 | 1.5.9.1 35 | 36 | ``` 37 | 38 | ### Implement Interface 39 | Create a service class that implements the `IOpenFLVService` interface to provide the stream address: 40 | 41 | ```java 42 | @Service 43 | public class RtspDataService implements IOpenFLVService { 44 | 45 | @Override 46 | public String getUrl(Integer channel) { 47 | // Retrieve the RTSP stream address based on the channel 48 | return "rtsp://10.11.9.251:554/openUrl/16HV8mA"; 49 | } 50 | } 51 | ``` 52 | 53 | ### Configure YAML 54 | Configure Easy-FLV in your `application.yml`: 55 | 56 | ```yaml 57 | easy: 58 | flv: 59 | host: http://localhost:8200 60 | ``` 61 | 62 | ### Use Interface 63 | To get the converted stream address and play it in a browser: 64 | 65 | - Conversion URL: `GET http://ip:port/get/flv/hls/stream_{channel}.flv` 66 | - Direct Browser Playback: `GET http://ip:port/flv/hls/stream_{channel}.flv` 67 | 68 | ### Direct Usage 69 | If you prefer not to implement an interface, you can directly encode the stream address and convert it: 70 | 71 | ```java 72 | public static void main(String[] args) throws UnsupportedEncodingException { 73 | String url = "rtsp://XXXXXXXX"; 74 | String encodedUrl = java.net.URLEncoder.encode(url, "UTF-8"); 75 | System.out.println("Encoded Stream URL: " + encodedUrl); 76 | } 77 | ``` 78 | 79 | - Conversion URL: `GET http://ip:port/get/flv/hls/stream?url=EncodedAddress` 80 | - Direct Browser Playback: `GET http://ip:port/flv/hls/stream?url=EncodedAddress` 81 | 82 | ## 🛠️ Contribution 83 | Contributions of any kind are welcome, including but not limited to reporting bugs, submitting fixes, adding new features, and improving documentation. 84 | 85 | ## 📄 License 86 | Easy-FLV is released under the [Apache License 2.0](LICENSE). 87 | 88 | ## 📧 Contact 89 | - Email: [javpower@163.com](mailto:javpower@163.com) 90 | - GitHub: [https://github.com/javpower/easy-flv](https://github.com/javpower/easy-flv) 91 | - Gitee: [https://gitee.com/giteeClass/easy-flv](https://gitee.com/giteeClass/easy-flv) 92 | -------------------------------------------------------------------------------- /img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javpower/easy-flv/bbf75b981e62faafe2e20821413b7ae29d832298/img.png -------------------------------------------------------------------------------- /img_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javpower/easy-flv/bbf75b981e62faafe2e20821413b7ae29d832298/img_1.png -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | io.github.javpower 6 | rtsp-converter-flv-spring-boot-starter 7 | 1.5.9.1 8 | rtsp-converter-flv-spring-boot-starter 9 | a tool about easy rtsp-converter-flv 10 | https://github.com/javpower/easy-flv 11 | 12 | 13 | The Apache Software License, Version 2.0 14 | http://www.apache.org/licenses/LICENSE-2.0.txt 15 | repo 16 | 17 | 18 | 19 | https://github.com/javpower/easy-flv 20 | scm:git@github.com/javpower/easy-flv.git 21 | scm:git@github.com/javpower/easy-flv.git 22 | 23 | 24 | 25 | gc.x 26 | javpower@163.com 27 | https://github.com/javpower 28 | +8 29 | 30 | 31 | 32 | 0.0.1-SNAPSHOT 33 | UTF-8 34 | UTF-8 35 | 1.8 36 | 2.7.13 37 | 2.3.2 38 | 39 | 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-starter 44 | 45 | 46 | org.springframework.boot 47 | spring-boot-configuration-processor 48 | true 49 | 50 | 51 | org.springframework.boot 52 | spring-boot-starter-web 53 | 54 | 55 | org.springframework.boot 56 | spring-boot-starter-log4j2 57 | 58 | 59 | com.fasterxml.jackson.core 60 | jackson-databind 61 | 62 | 63 | com.lmax 64 | disruptor 65 | 3.4.2 66 | 67 | 68 | com.alibaba 69 | fastjson 70 | 1.2.31 71 | 72 | 73 | 74 | org.projectlombok 75 | lombok 76 | 77 | 78 | 79 | org.bytedeco 80 | javacv-platform 81 | 1.5.9 82 | 83 | 84 | 85 | 86 | org.bytedeco 87 | opencv-platform-gpu 88 | 4.7.0-1.5.9 89 | 90 | 91 | 92 | 93 | org.bytedeco 94 | ffmpeg-platform-gpl 95 | 6.0-1.5.9 96 | 97 | 98 | 99 | 100 | org.springframework.boot 101 | spring-boot-starter-thymeleaf 102 | 103 | 104 | 105 | net.dreamlu 106 | mica-auto 107 | ${mica-auto.vaersion} 108 | provided 109 | 110 | 111 | 112 | 113 | 114 | 115 | org.springframework.boot 116 | spring-boot-dependencies 117 | ${spring-boot.version} 118 | pom 119 | import 120 | 121 | 122 | 123 | 124 | 125 | 126 | ossrh 127 | https://s01.oss.sonatype.org/content/repositories/snapshots 128 | 129 | 130 | ossrh 131 | https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ 132 | 133 | 134 | 135 | 136 | 137 | 138 | org.apache.maven.plugins 139 | maven-compiler-plugin 140 | 3.8.1 141 | 142 | 1.8 143 | 1.8 144 | UTF-8 145 | 146 | 147 | 148 | org.springframework.boot 149 | spring-boot-maven-plugin 150 | ${spring-boot.version} 151 | 152 | com.gc.easy.EasyHttpApplication 153 | true 154 | 155 | 156 | 157 | org.sonatype.plugins 158 | nexus-staging-maven-plugin 159 | 1.6.7 160 | true 161 | 162 | ossrh 163 | https://s01.oss.sonatype.org/ 164 | true 165 | 166 | 167 | 168 | org.apache.maven.plugins 169 | maven-source-plugin 170 | 2.2.1 171 | 172 | 173 | attach-sources 174 | 175 | jar-no-fork 176 | 177 | 178 | 179 | 180 | 181 | org.apache.maven.plugins 182 | maven-javadoc-plugin 183 | 2.9.1 184 | 185 | private 186 | true 187 | UTF-8 188 | UTF-8 189 | UTF-8 190 | -Xdoclint:none 191 | 192 | 193 | 194 | package 195 | 196 | jar 197 | 198 | 199 | 200 | 201 | 202 | org.apache.maven.plugins 203 | maven-gpg-plugin 204 | 1.5 205 | 206 | 207 | sign-artifacts 208 | verify 209 | 210 | sign 211 | 212 | 213 | 214 | 215 | 216 | org.apache.maven.plugins 217 | maven-deploy-plugin 218 | 2.8.2 219 | 220 | 221 | 222 | 223 | 224 | 225 | -------------------------------------------------------------------------------- /src/main/java/com/gc/easy/flv/config/FlvConfig.java: -------------------------------------------------------------------------------- 1 | package com.gc.easy.flv.config; 2 | 3 | import lombok.Data; 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | import org.springframework.stereotype.Component; 6 | 7 | @ConfigurationProperties(prefix = "easy.flv") 8 | @Data 9 | @Component 10 | public class FlvConfig { 11 | private String host; 12 | private Integer wight=1920; 13 | private Integer height=1080; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/gc/easy/flv/controller/FLVController.java: -------------------------------------------------------------------------------- 1 | package com.gc.easy.flv.controller; 2 | 3 | import com.gc.easy.flv.service.IFLVService; 4 | import com.gc.easy.flv.service.IOpenFLVService; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.util.StringUtils; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.PathVariable; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | import javax.servlet.http.HttpServletRequest; 12 | import javax.servlet.http.HttpServletResponse; 13 | import java.io.UnsupportedEncodingException; 14 | 15 | /** 16 | * FLV流转换 17 | * 18 | * @author gc.x 19 | */ 20 | @RestController 21 | public class FLVController { 22 | 23 | @Autowired 24 | private IFLVService service; 25 | @Autowired(required = false) 26 | private IOpenFLVService openFLVService; 27 | 28 | @GetMapping(value = "/get/flv/hls/stream_{channel}.flv") 29 | public void open(@PathVariable(value = "channel") Integer channel, HttpServletResponse response, 30 | HttpServletRequest request) { 31 | String url = openFLVService.getUrl(channel); 32 | if(!StringUtils.isEmpty(url)){ 33 | service.open(channel,url, response, request,openFLVService); 34 | } 35 | } 36 | @GetMapping(value = "/get/flv/hls/stream") 37 | public void open1(String url, HttpServletResponse response, 38 | HttpServletRequest request) throws UnsupportedEncodingException { 39 | if(!StringUtils.isEmpty(url)){ 40 | String decodedUrl = java.net.URLDecoder.decode(url, "UTF-8"); 41 | service.open(decodedUrl, response, request,openFLVService); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/gc/easy/flv/controller/FLVPlayController.java: -------------------------------------------------------------------------------- 1 | package com.gc.easy.flv.controller; 2 | 3 | import com.gc.easy.flv.config.FlvConfig; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.stereotype.Controller; 6 | import org.springframework.ui.Model; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.PathVariable; 9 | 10 | import java.io.UnsupportedEncodingException; 11 | 12 | /** 13 | * FLV流转换 14 | * 15 | * @author gc.x 16 | */ 17 | @Controller 18 | public class FLVPlayController { 19 | @Autowired 20 | private FlvConfig flvConfig; 21 | 22 | @GetMapping(value = "/flv/hls/stream_{channel}.flv") 23 | public String getAppHtml(@PathVariable(value = "channel") Integer channel, Model model) { 24 | String videoPath=flvConfig.getHost()+"/get/flv/hls/stream_"+channel+".flv"; 25 | model.addAttribute("videoPath", videoPath); 26 | model.addAttribute("wight", flvConfig.getWight()); 27 | model.addAttribute("height", flvConfig.getHeight()); 28 | return "video"; 29 | } 30 | @GetMapping(value = "/flv/hls/stream") 31 | public String getAppHtml1(String url, Model model) throws UnsupportedEncodingException { 32 | String decodedUrl = java.net.URLDecoder.decode(url, "UTF-8"); 33 | String videoPath=flvConfig.getHost()+"/get/flv/hls/stream?url="+decodedUrl; 34 | model.addAttribute("videoPath", videoPath); 35 | model.addAttribute("wight", flvConfig.getWight()); 36 | model.addAttribute("height", flvConfig.getHeight()); 37 | return "video"; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/gc/easy/flv/factories/Converter.java: -------------------------------------------------------------------------------- 1 | package com.gc.easy.flv.factories; 2 | 3 | import javax.servlet.AsyncContext; 4 | import java.io.IOException; 5 | 6 | public interface Converter { 7 | 8 | /** 9 | * 获取该转换的key 10 | */ 11 | public String getKey(); 12 | 13 | /** 14 | * 获取该转换的url 15 | * 16 | * @return 17 | */ 18 | public String getUrl(); 19 | 20 | /** 21 | * 添加一个流输出 22 | * 23 | * @param entity 24 | */ 25 | public void addOutputStreamEntity(String key, AsyncContext entity) throws IOException; 26 | 27 | /** 28 | * 退出转换 29 | */ 30 | public void exit(); 31 | 32 | /** 33 | * 启动 34 | */ 35 | public void start(); 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/gc/easy/flv/factories/ConverterFactories.java: -------------------------------------------------------------------------------- 1 | package com.gc.easy.flv.factories; 2 | 3 | import com.alibaba.fastjson.util.IOUtils; 4 | import com.gc.easy.flv.service.IOpenFLVService; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.bytedeco.ffmpeg.avcodec.AVPacket; 7 | import org.bytedeco.ffmpeg.global.avcodec; 8 | import org.bytedeco.javacv.FFmpegFrameGrabber; 9 | import org.bytedeco.javacv.FFmpegFrameRecorder; 10 | 11 | import javax.servlet.AsyncContext; 12 | import java.io.ByteArrayOutputStream; 13 | import java.io.IOException; 14 | import java.util.Iterator; 15 | import java.util.List; 16 | import java.util.Map; 17 | 18 | /** 19 | * javacv转包装
20 | * 无须转码,更低的资源消耗,更低的延迟
21 | * 确保流来源视频H264格式,音频AAC格式 22 | * 23 | * @author gc.x 24 | */ 25 | @Slf4j 26 | public class ConverterFactories extends Thread implements Converter { 27 | public volatile boolean runing = true; 28 | /** 29 | * 读流器 30 | */ 31 | private FFmpegFrameGrabber grabber; 32 | /** 33 | * 转码器 34 | */ 35 | private FFmpegFrameRecorder recorder; 36 | /** 37 | * 转FLV格式的头信息
38 | * 如果有第二个客户端播放首先要返回头信息 39 | */ 40 | private byte[] headers; 41 | /** 42 | * 保存转换好的流 43 | */ 44 | private ByteArrayOutputStream stream; 45 | /** 46 | * 流地址,h264,aac 47 | */ 48 | private String url; 49 | /** 50 | * 流输出 51 | */ 52 | private List outEntitys; 53 | 54 | /** 55 | * key用于表示这个转换器 56 | */ 57 | private String key; 58 | 59 | /** 60 | * 转换队列 61 | */ 62 | private Map factories; 63 | 64 | private IOpenFLVService openFLVService; 65 | 66 | 67 | public ConverterFactories(String url, String key, Map factories, List outEntitys, IOpenFLVService openFLVService) { 68 | this.url = url; 69 | this.key = key; 70 | this.factories = factories; 71 | this.outEntitys = outEntitys; 72 | this.openFLVService=openFLVService; 73 | } 74 | 75 | @Override 76 | public void run() { 77 | boolean isCloseGrabberAndResponse = true; 78 | try { 79 | grabber = new FFmpegFrameGrabber(url); 80 | if ("rtsp".equals(url.substring(0, 4))) { 81 | grabber.setOption("rtsp_transport", "tcp"); 82 | grabber.setOption("timeout", "5000000"); 83 | } 84 | grabber.start(); 85 | if (avcodec.AV_CODEC_ID_H264 == grabber.getVideoCodec() 86 | && (grabber.getAudioChannels() == 0 || avcodec.AV_CODEC_ID_AAC == grabber.getAudioCodec())&&(openFLVService==null||!openFLVService.openPreview())) { 87 | log.info("this url:{} converterFactories start", url); 88 | // 来源视频H264格式,音频AAC格式 89 | // 无须转码,更低的资源消耗,更低的延迟 90 | stream = new ByteArrayOutputStream(); 91 | recorder = new FFmpegFrameRecorder(stream, grabber.getImageWidth(), grabber.getImageHeight(), 92 | grabber.getAudioChannels()); 93 | recorder.setInterleaved(true); 94 | recorder.setVideoOption("preset", "ultrafast"); 95 | recorder.setVideoOption("tune", "zerolatency"); 96 | recorder.setVideoOption("crf", "25"); 97 | recorder.setFrameRate(grabber.getFrameRate()); 98 | recorder.setSampleRate(grabber.getSampleRate()); 99 | if (grabber.getAudioChannels() > 0) { 100 | recorder.setAudioChannels(grabber.getAudioChannels()); 101 | recorder.setAudioBitrate(grabber.getAudioBitrate()); 102 | recorder.setAudioCodec(grabber.getAudioCodec()); 103 | } 104 | recorder.setFormat("flv"); 105 | recorder.setVideoBitrate(grabber.getVideoBitrate()); 106 | recorder.setVideoCodec(grabber.getVideoCodec()); 107 | recorder.start(grabber.getFormatContext()); 108 | if (headers == null) { 109 | headers = stream.toByteArray(); 110 | stream.reset(); 111 | writeResponse(headers); 112 | } 113 | int nullNumber = 0; 114 | while (runing) { 115 | AVPacket k = grabber.grabPacket(); 116 | if (k != null) { 117 | try { 118 | recorder.recordPacket(k); 119 | } catch (Exception e) { 120 | } 121 | if (stream.size() > 0) { 122 | byte[] b = stream.toByteArray(); 123 | stream.reset(); 124 | writeResponse(b); 125 | if (outEntitys.isEmpty()) { 126 | log.info("没有输出退出"); 127 | break; 128 | } 129 | } 130 | avcodec.av_packet_unref(k); 131 | } else { 132 | nullNumber++; 133 | if (nullNumber > 200) { 134 | break; 135 | } 136 | } 137 | Thread.sleep(5); 138 | } 139 | } else { 140 | isCloseGrabberAndResponse = false; 141 | // 需要转码为视频H264格式,音频AAC格式 142 | ConverterTranFactories c = new ConverterTranFactories(url, key, factories, outEntitys, grabber,openFLVService); 143 | factories.put(key, c); 144 | c.start(); 145 | } 146 | } catch (Exception e) { 147 | log.error(e.getMessage(), e); 148 | } finally { 149 | closeConverter(isCloseGrabberAndResponse); 150 | completeResponse(isCloseGrabberAndResponse); 151 | log.info("this url:{} converterFactories exit", url); 152 | 153 | } 154 | } 155 | 156 | /** 157 | * 输出FLV视频流 158 | * 159 | * @param b 160 | */ 161 | public void writeResponse(byte[] b) { 162 | Iterator it = outEntitys.iterator(); 163 | while (it.hasNext()) { 164 | AsyncContext o = it.next(); 165 | try { 166 | o.getResponse().getOutputStream().write(b); 167 | } catch (Exception e) { 168 | log.info("移除一个输出"); 169 | it.remove(); 170 | } 171 | } 172 | } 173 | 174 | /** 175 | * 退出转换 176 | */ 177 | public void closeConverter(boolean isCloseGrabberAndResponse) { 178 | if (isCloseGrabberAndResponse) { 179 | IOUtils.close(grabber); 180 | factories.remove(this.key); 181 | } 182 | IOUtils.close(recorder); 183 | IOUtils.close(stream); 184 | } 185 | 186 | /** 187 | * 关闭异步响应 188 | * 189 | * @param isCloseGrabberAndResponse 190 | */ 191 | public void completeResponse(boolean isCloseGrabberAndResponse) { 192 | if (isCloseGrabberAndResponse) { 193 | Iterator it = outEntitys.iterator(); 194 | while (it.hasNext()) { 195 | AsyncContext o = it.next(); 196 | o.complete(); 197 | } 198 | } 199 | } 200 | 201 | @Override 202 | public String getKey() { 203 | return this.key; 204 | } 205 | 206 | @Override 207 | public String getUrl() { 208 | return this.url; 209 | } 210 | 211 | @Override 212 | public void addOutputStreamEntity(String key, AsyncContext entity) throws IOException { 213 | if (headers == null) { 214 | outEntitys.add(entity); 215 | } else { 216 | entity.getResponse().getOutputStream().write(headers); 217 | entity.getResponse().getOutputStream().flush(); 218 | outEntitys.add(entity); 219 | } 220 | } 221 | 222 | @Override 223 | public void exit() { 224 | this.runing = false; 225 | try { 226 | this.join(); 227 | } catch (Exception e) { 228 | log.error(e.getMessage(), e); 229 | } 230 | } 231 | 232 | } 233 | -------------------------------------------------------------------------------- /src/main/java/com/gc/easy/flv/factories/ConverterTranFactories.java: -------------------------------------------------------------------------------- 1 | package com.gc.easy.flv.factories; 2 | 3 | import com.alibaba.fastjson.util.IOUtils; 4 | import com.gc.easy.flv.service.IOpenFLVService; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.bytedeco.ffmpeg.global.avcodec; 7 | import org.bytedeco.javacv.FFmpegFrameGrabber; 8 | import org.bytedeco.javacv.FFmpegFrameRecorder; 9 | import org.bytedeco.javacv.Frame; 10 | import org.bytedeco.javacv.OpenCVFrameConverter; 11 | import org.bytedeco.opencv.opencv_core.IplImage; 12 | 13 | import javax.servlet.AsyncContext; 14 | import java.io.ByteArrayOutputStream; 15 | import java.io.IOException; 16 | import java.util.Iterator; 17 | import java.util.List; 18 | import java.util.Map; 19 | 20 | /** 21 | * javacv转码
22 | * 流来源不是视频H264格式,音频AAC格式 转码为视频H264格式,音频AAC格式 23 | * 24 | * @author gc.x 25 | */ 26 | @Slf4j 27 | public class ConverterTranFactories extends Thread implements Converter { 28 | public volatile boolean runing = true; 29 | /** 30 | * 读流器 31 | */ 32 | private FFmpegFrameGrabber grabber; 33 | /** 34 | * 转码器 35 | */ 36 | private FFmpegFrameRecorder recorder; 37 | /** 38 | * 转FLV格式的头信息
39 | * 如果有第二个客户端播放首先要返回头信息 40 | */ 41 | private byte[] headers; 42 | /** 43 | * 保存转换好的流 44 | */ 45 | private ByteArrayOutputStream stream; 46 | /** 47 | * 流地址,h264,aac 48 | */ 49 | private String url; 50 | /** 51 | * 流输出 52 | */ 53 | private List outEntitys; 54 | 55 | /** 56 | * key用于表示这个转换器 57 | */ 58 | private String key; 59 | 60 | /** 61 | * 转换队列 62 | */ 63 | private Map factories; 64 | 65 | private OpenCVFrameConverter.ToIplImage converter; 66 | private IOpenFLVService openFLVService; 67 | 68 | 69 | public ConverterTranFactories(String url, String key, Map factories, 70 | List outEntitys, FFmpegFrameGrabber grabber, IOpenFLVService openFLVService) { 71 | this.url = url; 72 | this.key = key; 73 | this.factories = factories; 74 | this.outEntitys = outEntitys; 75 | this.grabber = grabber; 76 | this.openFLVService=openFLVService; 77 | } 78 | 79 | @Override 80 | public void run() { 81 | try { 82 | log.info("this url:{} converterTranFactories start", url); 83 | grabber.setFrameRate(25); 84 | if (grabber.getImageWidth() > 1920) { 85 | grabber.setImageWidth(1920); 86 | } 87 | if (grabber.getImageHeight() > 1080) { 88 | grabber.setImageHeight(1080); 89 | } 90 | stream = new ByteArrayOutputStream(); 91 | recorder = new FFmpegFrameRecorder(stream, grabber.getImageWidth(), grabber.getImageHeight(), 92 | grabber.getAudioChannels()); 93 | converter = new OpenCVFrameConverter.ToIplImage(); 94 | 95 | recorder.setInterleaved(true); 96 | recorder.setVideoOption("preset", "ultrafast"); 97 | recorder.setVideoOption("tune", "zerolatency"); 98 | recorder.setVideoOption("crf", "25"); 99 | recorder.setGopSize(50); 100 | recorder.setFrameRate(25); 101 | recorder.setSampleRate(grabber.getSampleRate()); 102 | if (grabber.getAudioChannels() > 0) { 103 | recorder.setAudioChannels(grabber.getAudioChannels()); 104 | recorder.setAudioBitrate(grabber.getAudioBitrate()); 105 | recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC); 106 | } 107 | recorder.setFormat("flv"); 108 | recorder.setVideoBitrate(grabber.getVideoBitrate()); 109 | recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264); 110 | recorder.start(); 111 | if (headers == null) { 112 | headers = stream.toByteArray(); 113 | stream.reset(); 114 | writeResponse(headers); 115 | } 116 | int nullNumber = 0; 117 | while (runing) { 118 | // 抓取一帧 119 | Frame f = grabber.grab(); 120 | if(openFLVService!=null&&openFLVService.openPreview()){ 121 | //预处理 122 | IplImage iplImage = converter.convert(f);//抓取一帧视频并将其转换为图像,至于用这个图像用来做什么?加水印,人脸识别等等自行添加 123 | if (iplImage != null) { 124 | openFLVService.preview(iplImage); 125 | f = converter.convert(iplImage); 126 | } 127 | } 128 | if (f != null) { 129 | try { 130 | // 转码 131 | recorder.record(f); 132 | } catch (Exception e) { 133 | } 134 | if (stream.size() > 0) { 135 | byte[] b = stream.toByteArray(); 136 | stream.reset(); 137 | writeResponse(b); 138 | if (outEntitys.isEmpty()) { 139 | log.info("没有输出退出"); 140 | break; 141 | } 142 | } 143 | } else { 144 | nullNumber++; 145 | if (nullNumber > 200) { 146 | break; 147 | } 148 | } 149 | Thread.sleep(5); 150 | } 151 | } catch (Exception e) { 152 | log.error(e.getMessage(), e); 153 | } finally { 154 | closeConverter(); 155 | completeResponse(); 156 | log.info("this url:{} converterTranFactories exit", url); 157 | factories.remove(this.key); 158 | } 159 | } 160 | 161 | /** 162 | * 输出FLV视频流 163 | * 164 | * @param b 165 | */ 166 | public void writeResponse(byte[] b) { 167 | Iterator it = outEntitys.iterator(); 168 | while (it.hasNext()) { 169 | AsyncContext o = it.next(); 170 | try { 171 | o.getResponse().getOutputStream().write(b); 172 | } catch (Exception e) { 173 | log.info("移除一个输出"); 174 | it.remove(); 175 | } 176 | } 177 | } 178 | 179 | /** 180 | * 退出转换 181 | */ 182 | public void closeConverter() { 183 | IOUtils.close(grabber); 184 | IOUtils.close(recorder); 185 | IOUtils.close(stream); 186 | } 187 | 188 | /** 189 | * 关闭异步响应 190 | */ 191 | public void completeResponse() { 192 | Iterator it = outEntitys.iterator(); 193 | while (it.hasNext()) { 194 | AsyncContext o = it.next(); 195 | o.complete(); 196 | } 197 | } 198 | 199 | @Override 200 | public String getKey() { 201 | return this.key; 202 | } 203 | 204 | @Override 205 | public String getUrl() { 206 | return this.url; 207 | } 208 | 209 | @Override 210 | public void addOutputStreamEntity(String key, AsyncContext entity) throws IOException { 211 | if (headers == null) { 212 | outEntitys.add(entity); 213 | } else { 214 | entity.getResponse().getOutputStream().write(headers); 215 | entity.getResponse().getOutputStream().flush(); 216 | outEntitys.add(entity); 217 | } 218 | } 219 | 220 | @Override 221 | public void exit() { 222 | this.runing = false; 223 | try { 224 | this.join(); 225 | } catch (Exception e) { 226 | log.error(e.getMessage(), e); 227 | } 228 | } 229 | 230 | } 231 | -------------------------------------------------------------------------------- /src/main/java/com/gc/easy/flv/factories/state/ConverterState.java: -------------------------------------------------------------------------------- 1 | package com.gc.easy.flv.factories.state; 2 | 3 | /** 4 | * 转换器状态(初始化、打开、关闭、错误、运行) 5 | * 6 | * @author gc.x 7 | */ 8 | public enum ConverterState { 9 | INITIAL, OPEN, CLOSE, ERROR, RUN 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/gc/easy/flv/factories/state/OutputImage.java: -------------------------------------------------------------------------------- 1 | package com.gc.easy.flv.factories.state; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.util.Date; 9 | 10 | @Data 11 | @AllArgsConstructor 12 | @NoArgsConstructor 13 | @Builder 14 | public class OutputImage { 15 | //图片 16 | private byte[] image; 17 | 18 | private Date recordTime; 19 | 20 | private Integer channel; 21 | 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/gc/easy/flv/service/IFLVService.java: -------------------------------------------------------------------------------- 1 | package com.gc.easy.flv.service; 2 | 3 | import javax.servlet.http.HttpServletRequest; 4 | import javax.servlet.http.HttpServletResponse; 5 | 6 | public interface IFLVService { 7 | 8 | /** 9 | * 打开一个流地址 10 | * 11 | * @param url 12 | * @param response 13 | */ 14 | public void open(Integer channel,String url, HttpServletResponse response, HttpServletRequest request,IOpenFLVService openFLVService); 15 | public void open(String url, HttpServletResponse response, HttpServletRequest request,IOpenFLVService openFLVService); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/gc/easy/flv/service/IOpenFLVService.java: -------------------------------------------------------------------------------- 1 | package com.gc.easy.flv.service; 2 | 3 | import org.bytedeco.opencv.opencv_core.IplImage; 4 | 5 | public interface IOpenFLVService { 6 | 7 | 8 | /** 9 | * 通过通道号获取url 10 | * @param channel 11 | * @return 12 | */ 13 | String getUrl(Integer channel); 14 | 15 | /** 16 | * 抓取一帧视频并将其转换为图像预处理 17 | * @param iplImage 18 | */ 19 | void preview(IplImage iplImage); 20 | 21 | /** 22 | * 开启预处理 23 | * @return 24 | */ 25 | boolean openPreview(); 26 | } -------------------------------------------------------------------------------- /src/main/java/com/gc/easy/flv/service/impl/FLVService.java: -------------------------------------------------------------------------------- 1 | package com.gc.easy.flv.service.impl; 2 | 3 | 4 | import com.gc.easy.flv.factories.Converter; 5 | import com.gc.easy.flv.factories.ConverterFactories; 6 | import com.gc.easy.flv.service.IFLVService; 7 | import com.gc.easy.flv.service.IOpenFLVService; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.stereotype.Service; 10 | 11 | import javax.servlet.AsyncContext; 12 | import javax.servlet.http.HttpServletRequest; 13 | import javax.servlet.http.HttpServletResponse; 14 | import java.io.IOException; 15 | import java.security.MessageDigest; 16 | import java.security.NoSuchAlgorithmException; 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | import java.util.concurrent.ConcurrentHashMap; 20 | 21 | /** 22 | * FLV流转换 23 | * 24 | * @author gc.x 25 | */ 26 | @Slf4j 27 | @Service 28 | public class FLVService implements IFLVService { 29 | 30 | private ConcurrentHashMap converters = new ConcurrentHashMap<>(); 31 | @Override 32 | public void open(String url, HttpServletResponse response, HttpServletRequest request,IOpenFLVService openFLVService) { 33 | open(null,url,response,request,openFLVService); 34 | } 35 | @Override 36 | public void open(Integer channel, String url, HttpServletResponse response, HttpServletRequest request, IOpenFLVService openFLVService) { 37 | String key = md5(url); 38 | AsyncContext async = request.startAsync(); 39 | async.setTimeout(0); 40 | if (converters.containsKey(key)) { 41 | Converter c = converters.get(key); 42 | try { 43 | c.addOutputStreamEntity(key, async); 44 | } catch (IOException e) { 45 | log.error(e.getMessage(), e); 46 | throw new IllegalArgumentException(e.getMessage()); 47 | } 48 | } else { 49 | List outs = new ArrayList<>(); 50 | outs.add(async); 51 | ConverterFactories c = new ConverterFactories(url, key, converters, outs,openFLVService); 52 | c.start(); 53 | converters.put(key, c); 54 | } 55 | response.setContentType("video/x-flv"); 56 | response.setHeader("Connection", "keep-alive"); 57 | response.setStatus(HttpServletResponse.SC_OK); 58 | try { 59 | response.flushBuffer(); 60 | } catch (IOException e) { 61 | log.error(e.getMessage(), e); 62 | } 63 | } 64 | 65 | public String md5(String plainText) { 66 | StringBuilder buf = null; 67 | try { 68 | MessageDigest md = MessageDigest.getInstance("MD5"); 69 | md.update(plainText.getBytes()); 70 | byte b[] = md.digest(); 71 | int i; 72 | buf = new StringBuilder(""); 73 | for (int offset = 0; offset < b.length; offset++) { 74 | i = b[offset]; 75 | if (i < 0) 76 | i += 256; 77 | if (i < 16) 78 | buf.append("0"); 79 | buf.append(Integer.toHexString(i)); 80 | } 81 | } catch (NoSuchAlgorithmException e) { 82 | log.error(e.getMessage(), e); 83 | } 84 | return buf.toString(); 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/com/gc/easy/flv/util/FlvUtil.java: -------------------------------------------------------------------------------- 1 | package com.gc.easy.flv.util; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import org.bytedeco.javacv.FFmpegFrameGrabber; 5 | import org.bytedeco.javacv.Frame; 6 | import org.bytedeco.javacv.FrameGrabber; 7 | import org.bytedeco.javacv.Java2DFrameConverter; 8 | import org.springframework.core.io.ByteArrayResource; 9 | import org.springframework.util.LinkedMultiValueMap; 10 | import org.springframework.util.MultiValueMap; 11 | import org.springframework.web.client.RestTemplate; 12 | 13 | import javax.imageio.ImageIO; 14 | import java.awt.image.BufferedImage; 15 | import java.io.ByteArrayOutputStream; 16 | import java.io.IOException; 17 | 18 | public class FlvUtil { 19 | 20 | private static BufferedImage getBufferedImageByFrame(String filePath) throws IOException { 21 | FFmpegFrameGrabber grabber = FFmpegFrameGrabber.createDefault(filePath); 22 | return getBufferedImageByFrame(grabber); 23 | } 24 | 25 | private static BufferedImage getBufferedImageByFrame(FFmpegFrameGrabber grabber) 26 | throws FrameGrabber.Exception { 27 | grabber.start(); 28 | Frame frame; 29 | frame = grabber.grabImage(); 30 | Java2DFrameConverter converter = new Java2DFrameConverter(); 31 | BufferedImage buffer = converter.getBufferedImage(frame); 32 | grabber.stop(); 33 | return buffer; 34 | } 35 | 36 | public static byte[] getFlvImg(String path) throws Exception { 37 | return bufferedImageToByteArray(getBufferedImageByFrame(path)); 38 | } 39 | 40 | 41 | /** 42 | * 将BufferedImage转换为byte[] 43 | * 44 | * @param image 45 | * @return 46 | */ 47 | public static byte[] bufferedImageToByteArray(BufferedImage image) throws IOException { 48 | ByteArrayOutputStream os = new ByteArrayOutputStream(); 49 | ImageIO.write(image, "jpg", os); 50 | return os.toByteArray(); 51 | } 52 | 53 | /** 54 | * 上传文件 55 | * 56 | * @param data 文件数据 57 | * @param url 上传地址 58 | * @param fileName 文件名称 59 | * @return 60 | */ 61 | public static JSONObject postFile(byte[] data, String url, String fileName) { 62 | RestTemplate restTemplate = new RestTemplate(); 63 | MultiValueMap paramMap = new LinkedMultiValueMap<>(); 64 | ByteArrayResource contentsAsResource = new ByteArrayResource(data) { 65 | @Override 66 | public String getFilename() { 67 | return fileName; 68 | } 69 | }; 70 | paramMap.add("file", contentsAsResource); 71 | return restTemplate.postForObject(url, paramMap, JSONObject.class); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/resources/templates/video.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | FLV Video Player 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 36 | 37 | 38 | --------------------------------------------------------------------------------