Compare commits
10 Commits
1e13507381
...
be740def8d
Author | SHA1 | Date | |
---|---|---|---|
![]() |
be740def8d | ||
![]() |
60cbbb5c1b | ||
![]() |
bbf75b981e | ||
![]() |
a3398bc1ca | ||
![]() |
7669895db7 | ||
![]() |
f960667ff4 | ||
![]() |
1aba8c1014 | ||
![]() |
f5487ca647 | ||
![]() |
bb297dda31 | ||
![]() |
37443ef64b |
33
.gitignore
vendored
33
.gitignore
vendored
@ -1,33 +0,0 @@
|
|||||||
README.md
|
|
||||||
target/
|
|
||||||
!.mvn/wrapper/maven-wrapper.jar
|
|
||||||
!**/src/main/**/target/
|
|
||||||
!**/src/test/**/target/
|
|
||||||
|
|
||||||
### STS ###
|
|
||||||
.apt_generated
|
|
||||||
.classpath
|
|
||||||
.factorypath
|
|
||||||
.project
|
|
||||||
.settings
|
|
||||||
.springBeans
|
|
||||||
.sts4-cache
|
|
||||||
|
|
||||||
### IntelliJ IDEA ###
|
|
||||||
.idea
|
|
||||||
*.iws
|
|
||||||
*.iml
|
|
||||||
*.ipr
|
|
||||||
|
|
||||||
### NetBeans ###
|
|
||||||
/nbproject/private/
|
|
||||||
/nbbuild/
|
|
||||||
/dist/
|
|
||||||
/nbdist/
|
|
||||||
/.nb-gradle/
|
|
||||||
build/
|
|
||||||
!**/src/main/**/build/
|
|
||||||
!**/src/test/**/build/
|
|
||||||
|
|
||||||
### VS Code ###
|
|
||||||
.vscode/
|
|
91
README.cn.md
Normal file
91
README.cn.md
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
<!-- Easy-FLV: Java RTSP/RTMP to FLV Converter -->
|
||||||
|
# 📺 Easy-FLV: Java 实现的 RTSP/RTMP 到 FLV 转换器
|
||||||
|
|
||||||
|
[](https://github.com/javpower/easy-flv)
|
||||||
|
[](https://github.com/javpower/easy-flv/issues)
|
||||||
|
[](https://opensource.org/licenses/Apache-2.0)
|
||||||
|
[](https://adoptopenjdk.net/)
|
||||||
|
[](https://spring.io/projects/spring-boot)
|
||||||
|
|
||||||
|
## 🌟 关于 Easy-FLV
|
||||||
|
Easy-FLV 是一个用 Java 实现的库,它能够将 RTSP 或 RTMP 视频流转换为 FLV 格式,以便在浏览器中播放。它为实时视频监控、直播和视频流处理提供了一个高效、稳定且易于集成的解决方案。
|
||||||
|
|
||||||
|
### 为什么选择 Easy-FLV?
|
||||||
|
- **高效转换**:快速将视频流转换为 FLV 格式,无需复杂配置。
|
||||||
|
- **易于集成**:作为 Spring Boot Starter 使用,轻松集成到任何 Java 项目。
|
||||||
|
- **现代浏览器支持**:支持所有主流浏览器,无需额外插件。
|
||||||
|
- **实时流处理**:适用于实时视频流的转换,如安防监控和直播。
|
||||||
|
|
||||||
|
## 📄 效果图
|
||||||
|
以下是 Easy-FLV 在行动的效果图:
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
## 🚀 快速开始
|
||||||
|
|
||||||
|
### 添加 Maven 依赖
|
||||||
|
在你的 Spring Boot 项目中,添加以下 Maven 依赖:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.github.javpower</groupId>
|
||||||
|
<artifactId>rtsp-converter-flv-spring-boot-starter</artifactId>
|
||||||
|
<version>1.5.9.1</version>
|
||||||
|
</dependency>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 实现接口
|
||||||
|
创建一个服务类来实现 `IOpenFLVService` 接口,并提供流地址:
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Service
|
||||||
|
public class RtspDataService implements IOpenFLVService {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUrl(Integer channel) {
|
||||||
|
// 根据 channel 获取 RTSP 视频流地址
|
||||||
|
return "rtsp://10.11.9.251:554/openUrl/16HV8mA";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 配置 YAML
|
||||||
|
在 `application.yml` 中配置 Easy-FLV:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
easy:
|
||||||
|
flv:
|
||||||
|
host: http://localhost:8200
|
||||||
|
```
|
||||||
|
|
||||||
|
### 使用接口
|
||||||
|
通过以下接口获取转换后的流地址,并在浏览器中播放:
|
||||||
|
|
||||||
|
- 转换地址:`GET http://ip:port/get/flv/hls/stream_{channel}.flv`
|
||||||
|
- 播放地址:`GET http://ip:port/flv/hls/stream_{channel}.flv`
|
||||||
|
|
||||||
|
### 直接使用
|
||||||
|
如果不使用接口,可以直接编码流地址并转换:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public static void main(String[] args) throws UnsupportedEncodingException {
|
||||||
|
String url = "rtsp://XXXXXXXX";
|
||||||
|
String encodedUrl = java.net.URLEncoder.encode(url, "UTF-8");
|
||||||
|
System.out.println("Encoded Stream URL: " + encodedUrl);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- 转换地址:`GET http://ip:port/get/flv/hls/stream?url=编码后的地址`
|
||||||
|
- 播放地址:`GET http://ip:port/flv/hls/stream?url=编码后的地址`
|
||||||
|
|
||||||
|
## 🛠️ 贡献
|
||||||
|
我们欢迎任何形式的贡献,包括但不限于报告 bug、提交修复、添加新功能、改进文档等。
|
||||||
|
|
||||||
|
## 📄 许可证
|
||||||
|
Easy-FLV 根据 [Apache License 2.0](LICENSE) 发布。
|
||||||
|
|
||||||
|
## 📧 联系
|
||||||
|
- Email: [javpower@163.com](mailto:your-email@example.com)
|
||||||
|
- GitHub: [https://github.com/javpower/easy-flv](https://github.com/javpower/easy-flv)
|
||||||
|
- Gitee: [https://gitee.com/giteeClass/easy-flv](https://gitee.com/giteeClass/easy-flv)
|
59
README.md
59
README.md
@ -1,59 +0,0 @@
|
|||||||
|
|
||||||
# Easy-FLV
|
|
||||||
|
|
||||||
#### 项目介绍
|
|
||||||
rtsp、rtmp流地址转换成flv浏览器播放
|
|
||||||
|
|
||||||
极速开始
|
|
||||||
-------------------------------------
|
|
||||||
以下例子基于Spring Boot
|
|
||||||
|
|
||||||
### 第一步:添加Maven依赖
|
|
||||||
|
|
||||||
直接添加以下maven依赖即可
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<dependency>
|
|
||||||
<groupId>io.github.javpower</groupId>
|
|
||||||
<artifactId>rtsp-converter-flv-spring-boot-starter</artifactId>
|
|
||||||
<version>1.5.9</version>
|
|
||||||
</dependency>
|
|
||||||
```
|
|
||||||
### 第二步:实现interface
|
|
||||||
数据库里面存储要播放的rtsp、rtmp流地址和自定义的通道号 实现类中通过通道号查询出地址<br>
|
|
||||||
```java
|
|
||||||
@Service
|
|
||||||
public class RtspDataService implements IOpenFLVService {
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getUrl(Integer channel) {
|
|
||||||
//todo: 根据自定义的channel获取rtsp视频流地址
|
|
||||||
return "rtsp://10.11.9.251:554/openUrl/16HV8mA";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
### 第三步:配置yml
|
|
||||||
```yml
|
|
||||||
easy.flv.host=http://localhost:8200
|
|
||||||
```
|
|
||||||
### 第四步:实现interface使用
|
|
||||||
|
|
||||||
- 流转换地址:GET http://ip:port/get/flv/hls/stream_{channel}.flv
|
|
||||||
- 浏览器直接播放测试: GET http://ip:port/flv/hls/stream_{channel}.flv
|
|
||||||
|
|
||||||
### 第五步:不想实现interface使用
|
|
||||||
原地址:rtsp://XXXXXXXX <br>
|
|
||||||
```Java
|
|
||||||
public static void main(String[] args) throws UnsupportedEncodingException {
|
|
||||||
|
|
||||||
String url = "rtsp://XXXXXXXX";
|
|
||||||
String encodedUrl = java.net.URLEncoder.encode(url, "UTF-8");
|
|
||||||
System.out.println("编码:" + encodedUrl);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
- 流转换地址:GET http://ip:port/get/flv/hls/stream?url=编码后的地址
|
|
||||||
- 浏览器直接播放测试: GET http://ip:port/flv/hls/stream?url=编码后的地址
|
|
||||||
|
|
||||||

|
|
||||||

|
|
17
pom.xml
17
pom.xml
@ -2,12 +2,12 @@
|
|||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>io.github.javpower</groupId>
|
<groupId>me.zhengjie</groupId>
|
||||||
<artifactId>rtsp-converter-flv-spring-boot-starter</artifactId>
|
<artifactId>eladmin-flv</artifactId>
|
||||||
<version>1.5.9</version>
|
<version>1.1</version>
|
||||||
<name>rtsp-converter-flv-spring-boot-starter</name>
|
<name>视频模块</name>
|
||||||
<description>a tool about easy rtsp-converter-flv</description>
|
<description>视频流转换 rtsp/rtmp 转flv</description>
|
||||||
<url>https://github.com/javpower/easy-flv</url>
|
|
||||||
<licenses>
|
<licenses>
|
||||||
<license>
|
<license>
|
||||||
<name> The Apache Software License, Version 2.0 </name>
|
<name> The Apache Software License, Version 2.0 </name>
|
||||||
@ -38,6 +38,11 @@
|
|||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>me.zhengjie</groupId>
|
||||||
|
<artifactId>eladmin-logging</artifactId>
|
||||||
|
<version>1.1</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter</artifactId>
|
<artifactId>spring-boot-starter</artifactId>
|
||||||
|
@ -2,10 +2,16 @@ package com.gc.easy.flv.controller;
|
|||||||
|
|
||||||
import com.gc.easy.flv.service.IFLVService;
|
import com.gc.easy.flv.service.IFLVService;
|
||||||
import com.gc.easy.flv.service.IOpenFLVService;
|
import com.gc.easy.flv.service.IOpenFLVService;
|
||||||
|
import io.swagger.annotations.Api;
|
||||||
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import me.zhengjie.annotation.Log;
|
||||||
|
import me.zhengjie.annotation.rest.AnonymousGetMapping;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
@ -17,28 +23,25 @@ import java.io.UnsupportedEncodingException;
|
|||||||
*
|
*
|
||||||
* @author gc.x
|
* @author gc.x
|
||||||
*/
|
*/
|
||||||
@RestController
|
@Slf4j
|
||||||
|
@RestController("api/front/flv")
|
||||||
|
@Api(tags = "微信:拉流播放")
|
||||||
public class FLVController {
|
public class FLVController {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private IFLVService service;
|
private IFLVService service;
|
||||||
@Autowired(required = false)
|
|
||||||
private IOpenFLVService openFLVService;
|
|
||||||
|
|
||||||
@GetMapping(value = "/get/flv/hls/stream_{channel}.flv")
|
@Autowired(required = false)
|
||||||
public void open(@PathVariable(value = "channel") Integer channel, HttpServletResponse response,
|
private IOpenFLVService openFLVService;
|
||||||
HttpServletRequest request) {
|
|
||||||
String url = openFLVService.getUrl(channel);
|
@Log("拉流播放")
|
||||||
if(!StringUtils.isEmpty(url)){
|
@ApiOperation(value = "打开视频流")
|
||||||
service.open(channel,url, response, request);
|
// @GetMapping(value = "/get/stream")
|
||||||
}
|
@AnonymousGetMapping(value = "/get/stream")
|
||||||
}
|
public void open(String url, HttpServletResponse response, HttpServletRequest request) throws UnsupportedEncodingException {
|
||||||
@GetMapping(value = "/get/flv/hls/stream")
|
if (!StringUtils.isEmpty(url)) {
|
||||||
public void open1(String url, HttpServletResponse response,
|
String decodedUrl = java.net.URLDecoder.decode(url, "UTF-8");
|
||||||
HttpServletRequest request) throws UnsupportedEncodingException {
|
service.open(decodedUrl, response, request, openFLVService);
|
||||||
if(!StringUtils.isEmpty(url)){
|
}
|
||||||
String decodedUrl = java.net.URLDecoder.decode(url, "UTF-8");
|
}
|
||||||
service.open(decodedUrl, response, request);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,39 +1,30 @@
|
|||||||
package com.gc.easy.flv.controller;
|
//package com.gc.easy.flv.controller;
|
||||||
|
//
|
||||||
import com.gc.easy.flv.config.FlvConfig;
|
//import com.gc.easy.flv.config.FlvConfig;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
//import me.zhengjie.annotation.rest.AnonymousGetMapping;
|
||||||
import org.springframework.stereotype.Controller;
|
//import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.ui.Model;
|
//import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
//import org.springframework.ui.Model;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
//
|
||||||
|
//import java.io.UnsupportedEncodingException;
|
||||||
import java.io.UnsupportedEncodingException;
|
//
|
||||||
|
///**
|
||||||
/**
|
// * FLV流转换
|
||||||
* FLV流转换
|
// *
|
||||||
*
|
// * @author gc.x
|
||||||
* @author gc.x
|
// */
|
||||||
*/
|
//@Controller
|
||||||
@Controller
|
//public class FLVPlayController {
|
||||||
public class FLVPlayController {
|
// @Autowired
|
||||||
@Autowired
|
// private FlvConfig flvConfig;
|
||||||
private FlvConfig flvConfig;
|
//
|
||||||
|
// @AnonymousGetMapping(value = "/flv/hls/stream")
|
||||||
@GetMapping(value = "/flv/hls/stream_{channel}.flv")
|
// public String getAppHtml1(String url, Model model) throws UnsupportedEncodingException {
|
||||||
public String getAppHtml(@PathVariable(value = "channel") Integer channel, Model model) {
|
// String decodedUrl = java.net.URLDecoder.decode(url, "UTF-8");
|
||||||
String videoPath=flvConfig.getHost()+"/get/flv/hls/stream_"+channel+".flv";
|
// String videoPath="/api/front/flv/get/stream?url="+decodedUrl;
|
||||||
model.addAttribute("videoPath", videoPath);
|
// model.addAttribute("videoPath", videoPath);
|
||||||
model.addAttribute("wight", flvConfig.getWight());
|
// model.addAttribute("wight", flvConfig.getWight());
|
||||||
model.addAttribute("height", flvConfig.getHeight());
|
// model.addAttribute("height", flvConfig.getHeight());
|
||||||
return "video";
|
// return "video";
|
||||||
}
|
// }
|
||||||
@GetMapping(value = "/flv/hls/stream")
|
//}
|
||||||
public String getAppHtml1(String url, Model model) throws UnsupportedEncodingException {
|
|
||||||
String decodedUrl = java.net.URLDecoder.decode(url, "UTF-8");
|
|
||||||
String videoPath=flvConfig.getHost()+"/get/flv/hls/stream?url="+decodedUrl;
|
|
||||||
model.addAttribute("videoPath", videoPath);
|
|
||||||
model.addAttribute("wight", flvConfig.getWight());
|
|
||||||
model.addAttribute("height", flvConfig.getHeight());
|
|
||||||
return "video";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package com.gc.easy.flv.factories;
|
package com.gc.easy.flv.factories;
|
||||||
|
|
||||||
import com.alibaba.fastjson.util.IOUtils;
|
import com.alibaba.fastjson.util.IOUtils;
|
||||||
|
import com.gc.easy.flv.service.IOpenFLVService;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.bytedeco.ffmpeg.avcodec.AVPacket;
|
import org.bytedeco.ffmpeg.avcodec.AVPacket;
|
||||||
import org.bytedeco.ffmpeg.global.avcodec;
|
import org.bytedeco.ffmpeg.global.avcodec;
|
||||||
@ -23,206 +24,209 @@ import java.util.Map;
|
|||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class ConverterFactories extends Thread implements Converter {
|
public class ConverterFactories extends Thread implements Converter {
|
||||||
public volatile boolean runing = true;
|
public volatile boolean runing = true;
|
||||||
/**
|
/**
|
||||||
* 读流器
|
* 读流器
|
||||||
*/
|
*/
|
||||||
private FFmpegFrameGrabber grabber;
|
private FFmpegFrameGrabber grabber;
|
||||||
/**
|
/**
|
||||||
* 转码器
|
* 转码器
|
||||||
*/
|
*/
|
||||||
private FFmpegFrameRecorder recorder;
|
private FFmpegFrameRecorder recorder;
|
||||||
/**
|
/**
|
||||||
* 转FLV格式的头信息<br/>
|
* 转FLV格式的头信息<br/>
|
||||||
* 如果有第二个客户端播放首先要返回头信息
|
* 如果有第二个客户端播放首先要返回头信息
|
||||||
*/
|
*/
|
||||||
private byte[] headers;
|
private byte[] headers;
|
||||||
/**
|
/**
|
||||||
* 保存转换好的流
|
* 保存转换好的流
|
||||||
*/
|
*/
|
||||||
private ByteArrayOutputStream stream;
|
private ByteArrayOutputStream stream;
|
||||||
/**
|
/**
|
||||||
* 流地址,h264,aac
|
* 流地址,h264,aac
|
||||||
*/
|
*/
|
||||||
private String url;
|
private String url;
|
||||||
/**
|
/**
|
||||||
* 流输出
|
* 流输出
|
||||||
*/
|
*/
|
||||||
private List<AsyncContext> outEntitys;
|
private List<AsyncContext> outEntitys;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* key用于表示这个转换器
|
* key用于表示这个转换器
|
||||||
*/
|
*/
|
||||||
private String key;
|
private String key;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 转换队列
|
* 转换队列
|
||||||
*/
|
*/
|
||||||
private Map<String, Converter> factories;
|
private Map<String, Converter> factories;
|
||||||
|
|
||||||
|
private IOpenFLVService openFLVService;
|
||||||
|
|
||||||
|
|
||||||
public ConverterFactories(String url, String key, Map<String, Converter> factories, List<AsyncContext> outEntitys) {
|
public ConverterFactories(String url, String key, Map<String, Converter> factories, List<AsyncContext> outEntitys, IOpenFLVService openFLVService) {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this.factories = factories;
|
this.factories = factories;
|
||||||
this.outEntitys = outEntitys;
|
this.outEntitys = outEntitys;
|
||||||
}
|
this.openFLVService = openFLVService;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
boolean isCloseGrabberAndResponse = true;
|
boolean isCloseGrabberAndResponse = true;
|
||||||
try {
|
try {
|
||||||
grabber = new FFmpegFrameGrabber(url);
|
grabber = new FFmpegFrameGrabber(url);
|
||||||
if ("rtsp".equals(url.substring(0, 4))) {
|
if ("rtsp".equals(url.substring(0, 4))) {
|
||||||
grabber.setOption("rtsp_transport", "tcp");
|
grabber.setOption("rtsp_transport", "tcp");
|
||||||
grabber.setOption("stimeout", "5000000");
|
grabber.setOption("timeout", "5000000");
|
||||||
}
|
}
|
||||||
grabber.start();
|
grabber.start();
|
||||||
if (avcodec.AV_CODEC_ID_H264 == grabber.getVideoCodec()
|
if (avcodec.AV_CODEC_ID_H264 == grabber.getVideoCodec()
|
||||||
&& (grabber.getAudioChannels() == 0 || avcodec.AV_CODEC_ID_AAC == grabber.getAudioCodec())) {
|
&& (grabber.getAudioChannels() == 0 || avcodec.AV_CODEC_ID_AAC == grabber.getAudioCodec()) && (openFLVService == null || !openFLVService.openPreview())) {
|
||||||
log.info("this url:{} converterFactories start", url);
|
log.info("this url:{} converterFactories start", url);
|
||||||
// 来源视频H264格式,音频AAC格式
|
// 来源视频H264格式,音频AAC格式
|
||||||
// 无须转码,更低的资源消耗,更低的延迟
|
// 无须转码,更低的资源消耗,更低的延迟
|
||||||
stream = new ByteArrayOutputStream();
|
stream = new ByteArrayOutputStream();
|
||||||
recorder = new FFmpegFrameRecorder(stream, grabber.getImageWidth(), grabber.getImageHeight(),
|
recorder = new FFmpegFrameRecorder(stream, grabber.getImageWidth(), grabber.getImageHeight(),
|
||||||
grabber.getAudioChannels());
|
grabber.getAudioChannels());
|
||||||
recorder.setInterleaved(true);
|
recorder.setInterleaved(true);
|
||||||
recorder.setVideoOption("preset", "ultrafast");
|
recorder.setVideoOption("preset", "ultrafast");
|
||||||
recorder.setVideoOption("tune", "zerolatency");
|
recorder.setVideoOption("tune", "zerolatency");
|
||||||
recorder.setVideoOption("crf", "25");
|
recorder.setVideoOption("crf", "25");
|
||||||
recorder.setFrameRate(grabber.getFrameRate());
|
recorder.setFrameRate(grabber.getFrameRate());
|
||||||
recorder.setSampleRate(grabber.getSampleRate());
|
recorder.setSampleRate(grabber.getSampleRate());
|
||||||
if (grabber.getAudioChannels() > 0) {
|
if (grabber.getAudioChannels() > 0) {
|
||||||
recorder.setAudioChannels(grabber.getAudioChannels());
|
recorder.setAudioChannels(grabber.getAudioChannels());
|
||||||
recorder.setAudioBitrate(grabber.getAudioBitrate());
|
recorder.setAudioBitrate(grabber.getAudioBitrate());
|
||||||
recorder.setAudioCodec(grabber.getAudioCodec());
|
recorder.setAudioCodec(grabber.getAudioCodec());
|
||||||
}
|
}
|
||||||
recorder.setFormat("flv");
|
recorder.setFormat("flv");
|
||||||
recorder.setVideoBitrate(grabber.getVideoBitrate());
|
recorder.setVideoBitrate(grabber.getVideoBitrate());
|
||||||
recorder.setVideoCodec(grabber.getVideoCodec());
|
recorder.setVideoCodec(grabber.getVideoCodec());
|
||||||
recorder.start(grabber.getFormatContext());
|
recorder.start(grabber.getFormatContext());
|
||||||
if (headers == null) {
|
if (headers == null) {
|
||||||
headers = stream.toByteArray();
|
headers = stream.toByteArray();
|
||||||
stream.reset();
|
stream.reset();
|
||||||
writeResponse(headers);
|
writeResponse(headers);
|
||||||
}
|
}
|
||||||
int nullNumber = 0;
|
int nullNumber = 0;
|
||||||
while (runing) {
|
while (runing) {
|
||||||
AVPacket k = grabber.grabPacket();
|
AVPacket k = grabber.grabPacket();
|
||||||
if (k != null) {
|
if (k != null) {
|
||||||
try {
|
try {
|
||||||
recorder.recordPacket(k);
|
recorder.recordPacket(k);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
}
|
}
|
||||||
if (stream.size() > 0) {
|
if (stream.size() > 0) {
|
||||||
byte[] b = stream.toByteArray();
|
byte[] b = stream.toByteArray();
|
||||||
stream.reset();
|
stream.reset();
|
||||||
writeResponse(b);
|
writeResponse(b);
|
||||||
if (outEntitys.isEmpty()) {
|
if (outEntitys.isEmpty()) {
|
||||||
log.info("没有输出退出");
|
log.info("没有输出退出");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
avcodec.av_packet_unref(k);
|
avcodec.av_packet_unref(k);
|
||||||
} else {
|
} else {
|
||||||
nullNumber++;
|
nullNumber++;
|
||||||
if (nullNumber > 200) {
|
if (nullNumber > 200) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Thread.sleep(5);
|
Thread.sleep(5);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
isCloseGrabberAndResponse = false;
|
isCloseGrabberAndResponse = false;
|
||||||
// 需要转码为视频H264格式,音频AAC格式
|
// 需要转码为视频H264格式,音频AAC格式
|
||||||
ConverterTranFactories c = new ConverterTranFactories(url, key, factories, outEntitys, grabber);
|
ConverterTranFactories c = new ConverterTranFactories(url, key, factories, outEntitys, grabber, openFLVService);
|
||||||
factories.put(key, c);
|
factories.put(key, c);
|
||||||
c.start();
|
c.start();
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error(e.getMessage(), e);
|
log.error(e.getMessage(), e);
|
||||||
} finally {
|
} finally {
|
||||||
closeConverter(isCloseGrabberAndResponse);
|
closeConverter(isCloseGrabberAndResponse);
|
||||||
completeResponse(isCloseGrabberAndResponse);
|
completeResponse(isCloseGrabberAndResponse);
|
||||||
log.info("this url:{} converterFactories exit", url);
|
log.info("this url:{} converterFactories exit", url);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 输出FLV视频流
|
* 输出FLV视频流
|
||||||
*
|
*
|
||||||
* @param b
|
* @param b
|
||||||
*/
|
*/
|
||||||
public void writeResponse(byte[] b) {
|
public void writeResponse(byte[] b) {
|
||||||
Iterator<AsyncContext> it = outEntitys.iterator();
|
Iterator<AsyncContext> it = outEntitys.iterator();
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
AsyncContext o = it.next();
|
AsyncContext o = it.next();
|
||||||
try {
|
try {
|
||||||
o.getResponse().getOutputStream().write(b);
|
o.getResponse().getOutputStream().write(b);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.info("移除一个输出");
|
log.info("移除一个输出");
|
||||||
it.remove();
|
it.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 退出转换
|
* 退出转换
|
||||||
*/
|
*/
|
||||||
public void closeConverter(boolean isCloseGrabberAndResponse) {
|
public void closeConverter(boolean isCloseGrabberAndResponse) {
|
||||||
if (isCloseGrabberAndResponse) {
|
if (isCloseGrabberAndResponse) {
|
||||||
IOUtils.close(grabber);
|
IOUtils.close(grabber);
|
||||||
factories.remove(this.key);
|
factories.remove(this.key);
|
||||||
}
|
}
|
||||||
IOUtils.close(recorder);
|
IOUtils.close(recorder);
|
||||||
IOUtils.close(stream);
|
IOUtils.close(stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 关闭异步响应
|
* 关闭异步响应
|
||||||
*
|
*
|
||||||
* @param isCloseGrabberAndResponse
|
* @param isCloseGrabberAndResponse
|
||||||
*/
|
*/
|
||||||
public void completeResponse(boolean isCloseGrabberAndResponse) {
|
public void completeResponse(boolean isCloseGrabberAndResponse) {
|
||||||
if (isCloseGrabberAndResponse) {
|
if (isCloseGrabberAndResponse) {
|
||||||
Iterator<AsyncContext> it = outEntitys.iterator();
|
Iterator<AsyncContext> it = outEntitys.iterator();
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
AsyncContext o = it.next();
|
AsyncContext o = it.next();
|
||||||
o.complete();
|
o.complete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getKey() {
|
public String getKey() {
|
||||||
return this.key;
|
return this.key;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getUrl() {
|
public String getUrl() {
|
||||||
return this.url;
|
return this.url;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addOutputStreamEntity(String key, AsyncContext entity) throws IOException {
|
public void addOutputStreamEntity(String key, AsyncContext entity) throws IOException {
|
||||||
if (headers == null) {
|
if (headers == null) {
|
||||||
outEntitys.add(entity);
|
outEntitys.add(entity);
|
||||||
} else {
|
} else {
|
||||||
entity.getResponse().getOutputStream().write(headers);
|
entity.getResponse().getOutputStream().write(headers);
|
||||||
entity.getResponse().getOutputStream().flush();
|
entity.getResponse().getOutputStream().flush();
|
||||||
outEntitys.add(entity);
|
outEntitys.add(entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void exit() {
|
public void exit() {
|
||||||
this.runing = false;
|
this.runing = false;
|
||||||
try {
|
try {
|
||||||
this.join();
|
this.join();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error(e.getMessage(), e);
|
log.error(e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
package com.gc.easy.flv.factories;
|
package com.gc.easy.flv.factories;
|
||||||
|
|
||||||
import com.alibaba.fastjson.util.IOUtils;
|
import com.alibaba.fastjson.util.IOUtils;
|
||||||
|
import com.gc.easy.flv.service.IOpenFLVService;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.bytedeco.ffmpeg.global.avcodec;
|
import org.bytedeco.ffmpeg.global.avcodec;
|
||||||
import org.bytedeco.javacv.FFmpegFrameGrabber;
|
import org.bytedeco.javacv.FFmpegFrameGrabber;
|
||||||
import org.bytedeco.javacv.FFmpegFrameRecorder;
|
import org.bytedeco.javacv.FFmpegFrameRecorder;
|
||||||
import org.bytedeco.javacv.Frame;
|
import org.bytedeco.javacv.Frame;
|
||||||
|
import org.bytedeco.javacv.OpenCVFrameConverter;
|
||||||
|
import org.bytedeco.opencv.opencv_core.IplImage;
|
||||||
|
|
||||||
import javax.servlet.AsyncContext;
|
import javax.servlet.AsyncContext;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
@ -59,13 +62,18 @@ public class ConverterTranFactories extends Thread implements Converter {
|
|||||||
*/
|
*/
|
||||||
private Map<String, Converter> factories;
|
private Map<String, Converter> factories;
|
||||||
|
|
||||||
|
private OpenCVFrameConverter.ToIplImage converter;
|
||||||
|
private IOpenFLVService openFLVService;
|
||||||
|
|
||||||
|
|
||||||
public ConverterTranFactories(String url, String key, Map<String, Converter> factories,
|
public ConverterTranFactories(String url, String key, Map<String, Converter> factories,
|
||||||
List<AsyncContext> outEntitys, FFmpegFrameGrabber grabber) {
|
List<AsyncContext> outEntitys, FFmpegFrameGrabber grabber, IOpenFLVService openFLVService) {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this.factories = factories;
|
this.factories = factories;
|
||||||
this.outEntitys = outEntitys;
|
this.outEntitys = outEntitys;
|
||||||
this.grabber = grabber;
|
this.grabber = grabber;
|
||||||
|
this.openFLVService=openFLVService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -82,6 +90,8 @@ public class ConverterTranFactories extends Thread implements Converter {
|
|||||||
stream = new ByteArrayOutputStream();
|
stream = new ByteArrayOutputStream();
|
||||||
recorder = new FFmpegFrameRecorder(stream, grabber.getImageWidth(), grabber.getImageHeight(),
|
recorder = new FFmpegFrameRecorder(stream, grabber.getImageWidth(), grabber.getImageHeight(),
|
||||||
grabber.getAudioChannels());
|
grabber.getAudioChannels());
|
||||||
|
converter = new OpenCVFrameConverter.ToIplImage();
|
||||||
|
|
||||||
recorder.setInterleaved(true);
|
recorder.setInterleaved(true);
|
||||||
recorder.setVideoOption("preset", "ultrafast");
|
recorder.setVideoOption("preset", "ultrafast");
|
||||||
recorder.setVideoOption("tune", "zerolatency");
|
recorder.setVideoOption("tune", "zerolatency");
|
||||||
@ -107,6 +117,14 @@ public class ConverterTranFactories extends Thread implements Converter {
|
|||||||
while (runing) {
|
while (runing) {
|
||||||
// 抓取一帧
|
// 抓取一帧
|
||||||
Frame f = grabber.grab();
|
Frame f = grabber.grab();
|
||||||
|
if(openFLVService!=null&&openFLVService.openPreview()){
|
||||||
|
//预处理
|
||||||
|
IplImage iplImage = converter.convert(f);//抓取一帧视频并将其转换为图像,至于用这个图像用来做什么?加水印,人脸识别等等自行添加
|
||||||
|
if (iplImage != null) {
|
||||||
|
openFLVService.preview(iplImage);
|
||||||
|
f = converter.convert(iplImage);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (f != null) {
|
if (f != null) {
|
||||||
try {
|
try {
|
||||||
// 转码
|
// 转码
|
||||||
|
@ -11,7 +11,7 @@ public interface IFLVService {
|
|||||||
* @param url
|
* @param url
|
||||||
* @param response
|
* @param response
|
||||||
*/
|
*/
|
||||||
public void open(Integer channel,String url, HttpServletResponse response, HttpServletRequest request);
|
public void open(Integer channel,String url, HttpServletResponse response, HttpServletRequest request,IOpenFLVService openFLVService);
|
||||||
public void open(String url, HttpServletResponse response, HttpServletRequest request);
|
public void open(String url, HttpServletResponse response, HttpServletRequest request,IOpenFLVService openFLVService);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package com.gc.easy.flv.service;
|
package com.gc.easy.flv.service;
|
||||||
|
|
||||||
|
import org.bytedeco.opencv.opencv_core.IplImage;
|
||||||
|
|
||||||
public interface IOpenFLVService {
|
public interface IOpenFLVService {
|
||||||
|
|
||||||
|
|
||||||
@ -8,5 +10,17 @@ public interface IOpenFLVService {
|
|||||||
* @param channel
|
* @param channel
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public String getUrl(Integer channel);
|
String getUrl(Integer channel);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 抓取一帧视频并将其转换为图像预处理
|
||||||
|
* @param iplImage
|
||||||
|
*/
|
||||||
|
void preview(IplImage iplImage);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开启预处理
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
boolean openPreview();
|
||||||
}
|
}
|
@ -4,6 +4,7 @@ package com.gc.easy.flv.service.impl;
|
|||||||
import com.gc.easy.flv.factories.Converter;
|
import com.gc.easy.flv.factories.Converter;
|
||||||
import com.gc.easy.flv.factories.ConverterFactories;
|
import com.gc.easy.flv.factories.ConverterFactories;
|
||||||
import com.gc.easy.flv.service.IFLVService;
|
import com.gc.easy.flv.service.IFLVService;
|
||||||
|
import com.gc.easy.flv.service.IOpenFLVService;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@ -26,61 +27,63 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
@Service
|
@Service
|
||||||
public class FLVService implements IFLVService {
|
public class FLVService implements IFLVService {
|
||||||
|
|
||||||
private ConcurrentHashMap<String, Converter> converters = new ConcurrentHashMap<>();
|
private ConcurrentHashMap<String, Converter> converters = new ConcurrentHashMap<>();
|
||||||
@Override
|
|
||||||
public void open(String url, HttpServletResponse response, HttpServletRequest request) {
|
|
||||||
open(null,url,response,request);
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void open(Integer channel,String url, HttpServletResponse response, HttpServletRequest request) {
|
|
||||||
String key = md5(url);
|
|
||||||
AsyncContext async = request.startAsync();
|
|
||||||
async.setTimeout(0);
|
|
||||||
if (converters.containsKey(key)) {
|
|
||||||
Converter c = converters.get(key);
|
|
||||||
try {
|
|
||||||
c.addOutputStreamEntity(key, async);
|
|
||||||
} catch (IOException e) {
|
|
||||||
log.error(e.getMessage(), e);
|
|
||||||
throw new IllegalArgumentException(e.getMessage());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
List<AsyncContext> outs = new ArrayList<>();
|
|
||||||
outs.add(async);
|
|
||||||
ConverterFactories c = new ConverterFactories(url, key, converters, outs);
|
|
||||||
c.start();
|
|
||||||
converters.put(key, c);
|
|
||||||
}
|
|
||||||
response.setContentType("video/x-flv");
|
|
||||||
response.setHeader("Connection", "keep-alive");
|
|
||||||
response.setStatus(HttpServletResponse.SC_OK);
|
|
||||||
try {
|
|
||||||
response.flushBuffer();
|
|
||||||
} catch (IOException e) {
|
|
||||||
log.error(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String md5(String plainText) {
|
@Override
|
||||||
StringBuilder buf = null;
|
public void open(String url, HttpServletResponse response, HttpServletRequest request, IOpenFLVService openFLVService) {
|
||||||
try {
|
open(null, url, response, request, openFLVService);
|
||||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
}
|
||||||
md.update(plainText.getBytes());
|
|
||||||
byte b[] = md.digest();
|
@Override
|
||||||
int i;
|
public void open(Integer channel, String url, HttpServletResponse response, HttpServletRequest request, IOpenFLVService openFLVService) {
|
||||||
buf = new StringBuilder("");
|
String key = md5(url);
|
||||||
for (int offset = 0; offset < b.length; offset++) {
|
AsyncContext async = request.startAsync();
|
||||||
i = b[offset];
|
async.setTimeout(0);
|
||||||
if (i < 0)
|
if (converters.containsKey(key)) {
|
||||||
i += 256;
|
Converter c = converters.get(key);
|
||||||
if (i < 16)
|
try {
|
||||||
buf.append("0");
|
c.addOutputStreamEntity(key, async);
|
||||||
buf.append(Integer.toHexString(i));
|
} catch (IOException e) {
|
||||||
}
|
log.error(e.getMessage(), e);
|
||||||
} catch (NoSuchAlgorithmException e) {
|
throw new IllegalArgumentException(e.getMessage());
|
||||||
log.error(e.getMessage(), e);
|
}
|
||||||
}
|
} else {
|
||||||
return buf.toString();
|
List<AsyncContext> outs = new ArrayList<>();
|
||||||
}
|
outs.add(async);
|
||||||
|
ConverterFactories c = new ConverterFactories(url, key, converters, outs, openFLVService);
|
||||||
|
c.start();
|
||||||
|
converters.put(key, c);
|
||||||
|
}
|
||||||
|
response.setContentType("video/x-flv");
|
||||||
|
response.setHeader("Connection", "keep-alive");
|
||||||
|
response.setStatus(HttpServletResponse.SC_OK);
|
||||||
|
try {
|
||||||
|
response.flushBuffer();
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String md5(String plainText) {
|
||||||
|
StringBuilder buf = null;
|
||||||
|
try {
|
||||||
|
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||||
|
md.update(plainText.getBytes());
|
||||||
|
byte b[] = md.digest();
|
||||||
|
int i;
|
||||||
|
buf = new StringBuilder("");
|
||||||
|
for (int offset = 0; offset < b.length; offset++) {
|
||||||
|
i = b[offset];
|
||||||
|
if (i < 0)
|
||||||
|
i += 256;
|
||||||
|
if (i < 16)
|
||||||
|
buf.append("0");
|
||||||
|
buf.append(Integer.toHexString(i));
|
||||||
|
}
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
package com.gc.easy.flv.service.impl;
|
||||||
|
|
||||||
|
|
||||||
|
import com.gc.easy.flv.service.IOpenFLVService;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.bytedeco.opencv.opencv_core.IplImage;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FLV流转换
|
||||||
|
*
|
||||||
|
* @author gc.x
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
public class OpenFLVService implements IOpenFLVService {
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUrl(Integer channel) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void preview(IplImage iplImage) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean openPreview() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
73
src/main/java/com/gc/easy/flv/util/FlvUtil.java
Normal file
73
src/main/java/com/gc/easy/flv/util/FlvUtil.java
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
package com.gc.easy.flv.util;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import org.bytedeco.javacv.FFmpegFrameGrabber;
|
||||||
|
import org.bytedeco.javacv.Frame;
|
||||||
|
import org.bytedeco.javacv.FrameGrabber;
|
||||||
|
import org.bytedeco.javacv.Java2DFrameConverter;
|
||||||
|
import org.springframework.core.io.ByteArrayResource;
|
||||||
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class FlvUtil {
|
||||||
|
|
||||||
|
private static BufferedImage getBufferedImageByFrame(String filePath) throws IOException {
|
||||||
|
FFmpegFrameGrabber grabber = FFmpegFrameGrabber.createDefault(filePath);
|
||||||
|
return getBufferedImageByFrame(grabber);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BufferedImage getBufferedImageByFrame(FFmpegFrameGrabber grabber)
|
||||||
|
throws FrameGrabber.Exception {
|
||||||
|
grabber.start();
|
||||||
|
Frame frame;
|
||||||
|
frame = grabber.grabImage();
|
||||||
|
Java2DFrameConverter converter = new Java2DFrameConverter();
|
||||||
|
BufferedImage buffer = converter.getBufferedImage(frame);
|
||||||
|
grabber.stop();
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] getFlvImg(String path) throws Exception {
|
||||||
|
return bufferedImageToByteArray(getBufferedImageByFrame(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将BufferedImage转换为byte[]
|
||||||
|
*
|
||||||
|
* @param image
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static byte[] bufferedImageToByteArray(BufferedImage image) throws IOException {
|
||||||
|
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||||
|
ImageIO.write(image, "jpg", os);
|
||||||
|
return os.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传文件
|
||||||
|
*
|
||||||
|
* @param data 文件数据
|
||||||
|
* @param url 上传地址
|
||||||
|
* @param fileName 文件名称
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static JSONObject postFile(byte[] data, String url, String fileName) {
|
||||||
|
RestTemplate restTemplate = new RestTemplate();
|
||||||
|
MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<>();
|
||||||
|
ByteArrayResource contentsAsResource = new ByteArrayResource(data) {
|
||||||
|
@Override
|
||||||
|
public String getFilename() {
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
paramMap.add("file", contentsAsResource);
|
||||||
|
return restTemplate.postForObject(url, paramMap, JSONObject.class);
|
||||||
|
}
|
||||||
|
}
|
@ -5,33 +5,33 @@
|
|||||||
<title>FLV Video Player</title>
|
<title>FLV Video Player</title>
|
||||||
|
|
||||||
<!-- 引入flv.js库 -->
|
<!-- 引入flv.js库 -->
|
||||||
<script src="https://cdn.jsdelivr.net/npm/flv.js@1.5.0/dist/flv.min.js"></script>
|
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/flv.js/1.5.0/flv.min.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
|
||||||
<video id="videoPlayer" th:style="'width:' + ${width} + 'px; height:' + ${height} + 'px;'" controls></video>
|
<video id="videoPlayer" th:style="'width:500px; height:500px;'" controls></video>
|
||||||
|
|
||||||
<script th:inline="javascript">
|
<script th:inline="javascript">
|
||||||
(function() {
|
(function () {
|
||||||
var videoPath = /*[[${videoPath}]]*/ '';
|
var videoPath = /*[[${videoPath}]]*/ '';
|
||||||
|
|
||||||
if (flvjs.isSupported()) {
|
if (flvjs.isSupported()) {
|
||||||
var videoElement = document.getElementById('videoPlayer');
|
var videoElement = document.getElementById('videoPlayer');
|
||||||
var flvPlayer = flvjs.createPlayer({
|
var flvPlayer = flvjs.createPlayer({
|
||||||
type: 'flv',
|
type: 'flv',
|
||||||
url: videoPath
|
url: 'http://localhost:8000' + videoPath
|
||||||
});
|
});
|
||||||
|
|
||||||
flvPlayer.attachMediaElement(videoElement);
|
flvPlayer.attachMediaElement(videoElement);
|
||||||
flvPlayer.load();
|
flvPlayer.load();
|
||||||
|
|
||||||
// 播放视频
|
// 播放视频
|
||||||
videoElement.play();
|
videoElement.play();
|
||||||
} else {
|
} else {
|
||||||
console.error('FLV not supported');
|
console.error('FLV not supported');
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
BIN
target/classes/com/gc/easy/flv/config/FlvConfig.class
Normal file
BIN
target/classes/com/gc/easy/flv/config/FlvConfig.class
Normal file
Binary file not shown.
BIN
target/classes/com/gc/easy/flv/controller/FLVController.class
Normal file
BIN
target/classes/com/gc/easy/flv/controller/FLVController.class
Normal file
Binary file not shown.
Binary file not shown.
BIN
target/classes/com/gc/easy/flv/factories/Converter.class
Normal file
BIN
target/classes/com/gc/easy/flv/factories/Converter.class
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
target/classes/com/gc/easy/flv/factories/state/OutputImage.class
Normal file
BIN
target/classes/com/gc/easy/flv/factories/state/OutputImage.class
Normal file
Binary file not shown.
BIN
target/classes/com/gc/easy/flv/service/IFLVService.class
Normal file
BIN
target/classes/com/gc/easy/flv/service/IFLVService.class
Normal file
Binary file not shown.
BIN
target/classes/com/gc/easy/flv/service/IOpenFLVService.class
Normal file
BIN
target/classes/com/gc/easy/flv/service/IOpenFLVService.class
Normal file
Binary file not shown.
BIN
target/classes/com/gc/easy/flv/service/impl/FLVService.class
Normal file
BIN
target/classes/com/gc/easy/flv/service/impl/FLVService.class
Normal file
Binary file not shown.
BIN
target/classes/com/gc/easy/flv/service/impl/OpenFLVService.class
Normal file
BIN
target/classes/com/gc/easy/flv/service/impl/OpenFLVService.class
Normal file
Binary file not shown.
BIN
target/classes/com/gc/easy/flv/util/FlvUtil$1.class
Normal file
BIN
target/classes/com/gc/easy/flv/util/FlvUtil$1.class
Normal file
Binary file not shown.
BIN
target/classes/com/gc/easy/flv/util/FlvUtil.class
Normal file
BIN
target/classes/com/gc/easy/flv/util/FlvUtil.class
Normal file
Binary file not shown.
37
target/classes/templates/video.html
Normal file
37
target/classes/templates/video.html
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>FLV Video Player</title>
|
||||||
|
|
||||||
|
<!-- 引入flv.js库 -->
|
||||||
|
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/flv.js/1.5.0/flv.min.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
|
||||||
|
<video id="videoPlayer" th:style="'width:500px; height:500px;'" controls></video>
|
||||||
|
|
||||||
|
<script th:inline="javascript">
|
||||||
|
(function () {
|
||||||
|
var videoPath = /*[[${videoPath}]]*/ '';
|
||||||
|
|
||||||
|
if (flvjs.isSupported()) {
|
||||||
|
var videoElement = document.getElementById('videoPlayer');
|
||||||
|
var flvPlayer = flvjs.createPlayer({
|
||||||
|
type: 'flv',
|
||||||
|
url: 'http://localhost:8000' + videoPath
|
||||||
|
});
|
||||||
|
|
||||||
|
flvPlayer.attachMediaElement(videoElement);
|
||||||
|
flvPlayer.load();
|
||||||
|
|
||||||
|
// 播放视频
|
||||||
|
videoElement.play();
|
||||||
|
} else {
|
||||||
|
console.error('FLV not supported');
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue
Block a user