Compare commits
No commits in common. "be740def8df459f37724c4f74ffed73badf83714" and "1e13507381fa154c3404b6cc7b421cc0d6e42be8" have entirely different histories.
be740def8d
...
1e13507381
33
.gitignore
vendored
Normal file
33
.gitignore
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
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
91
README.cn.md
@ -1,91 +0,0 @@
|
|||||||
<!-- 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
Normal file
59
README.md
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
|
||||||
|
# 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>me.zhengjie</groupId>
|
<groupId>io.github.javpower</groupId>
|
||||||
<artifactId>eladmin-flv</artifactId>
|
<artifactId>rtsp-converter-flv-spring-boot-starter</artifactId>
|
||||||
<version>1.1</version>
|
<version>1.5.9</version>
|
||||||
<name>视频模块</name>
|
<name>rtsp-converter-flv-spring-boot-starter</name>
|
||||||
<description>视频流转换 rtsp/rtmp 转flv</description>
|
<description>a tool about easy rtsp-converter-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,11 +38,6 @@
|
|||||||
</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,16 +2,10 @@ 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;
|
||||||
@ -20,28 +14,31 @@ import java.io.UnsupportedEncodingException;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* FLV流转换
|
* FLV流转换
|
||||||
*
|
*
|
||||||
* @author gc.x
|
* @author gc.x
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@RestController
|
||||||
@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;
|
||||||
|
|
||||||
@Autowired(required = false)
|
@GetMapping(value = "/get/flv/hls/stream_{channel}.flv")
|
||||||
private IOpenFLVService openFLVService;
|
public void open(@PathVariable(value = "channel") Integer channel, HttpServletResponse response,
|
||||||
|
HttpServletRequest request) {
|
||||||
@Log("拉流播放")
|
String url = openFLVService.getUrl(channel);
|
||||||
@ApiOperation(value = "打开视频流")
|
if(!StringUtils.isEmpty(url)){
|
||||||
// @GetMapping(value = "/get/stream")
|
service.open(channel,url, response, request);
|
||||||
@AnonymousGetMapping(value = "/get/stream")
|
}
|
||||||
public void open(String url, HttpServletResponse response, HttpServletRequest request) throws UnsupportedEncodingException {
|
}
|
||||||
if (!StringUtils.isEmpty(url)) {
|
@GetMapping(value = "/get/flv/hls/stream")
|
||||||
String decodedUrl = java.net.URLDecoder.decode(url, "UTF-8");
|
public void open1(String url, HttpServletResponse response,
|
||||||
service.open(decodedUrl, response, request, openFLVService);
|
HttpServletRequest request) throws UnsupportedEncodingException {
|
||||||
}
|
if(!StringUtils.isEmpty(url)){
|
||||||
}
|
String decodedUrl = java.net.URLDecoder.decode(url, "UTF-8");
|
||||||
|
service.open(decodedUrl, response, request);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,30 +1,39 @@
|
|||||||
//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 me.zhengjie.annotation.rest.AnonymousGetMapping;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
//import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.stereotype.Controller;
|
||||||
//import org.springframework.stereotype.Controller;
|
import org.springframework.ui.Model;
|
||||||
//import org.springframework.ui.Model;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
//
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
//import java.io.UnsupportedEncodingException;
|
|
||||||
//
|
import java.io.UnsupportedEncodingException;
|
||||||
///**
|
|
||||||
// * FLV流转换
|
/**
|
||||||
// *
|
* FLV流转换
|
||||||
// * @author gc.x
|
*
|
||||||
// */
|
* @author gc.x
|
||||||
//@Controller
|
*/
|
||||||
//public class FLVPlayController {
|
@Controller
|
||||||
// @Autowired
|
public class FLVPlayController {
|
||||||
// private FlvConfig flvConfig;
|
@Autowired
|
||||||
//
|
private FlvConfig flvConfig;
|
||||||
// @AnonymousGetMapping(value = "/flv/hls/stream")
|
|
||||||
// public String getAppHtml1(String url, Model model) throws UnsupportedEncodingException {
|
@GetMapping(value = "/flv/hls/stream_{channel}.flv")
|
||||||
// String decodedUrl = java.net.URLDecoder.decode(url, "UTF-8");
|
public String getAppHtml(@PathVariable(value = "channel") Integer channel, Model model) {
|
||||||
// String videoPath="/api/front/flv/get/stream?url="+decodedUrl;
|
String videoPath=flvConfig.getHost()+"/get/flv/hls/stream_"+channel+".flv";
|
||||||
// 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,7 +1,6 @@
|
|||||||
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;
|
||||||
@ -24,209 +23,206 @@ 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, IOpenFLVService openFLVService) {
|
public ConverterFactories(String url, String key, Map<String, Converter> factories, List<AsyncContext> outEntitys) {
|
||||||
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("timeout", "5000000");
|
grabber.setOption("stimeout", "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()) && (openFLVService == null || !openFLVService.openPreview())) {
|
&& (grabber.getAudioChannels() == 0 || avcodec.AV_CODEC_ID_AAC == grabber.getAudioCodec())) {
|
||||||
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, openFLVService);
|
ConverterTranFactories c = new ConverterTranFactories(url, key, factories, outEntitys, grabber);
|
||||||
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,14 +1,11 @@
|
|||||||
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;
|
||||||
@ -62,18 +59,13 @@ 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, IOpenFLVService openFLVService) {
|
List<AsyncContext> outEntitys, FFmpegFrameGrabber grabber) {
|
||||||
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
|
||||||
@ -90,8 +82,6 @@ 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");
|
||||||
@ -117,14 +107,6 @@ 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,IOpenFLVService openFLVService);
|
public void open(Integer channel,String url, HttpServletResponse response, HttpServletRequest request);
|
||||||
public void open(String url, HttpServletResponse response, HttpServletRequest request,IOpenFLVService openFLVService);
|
public void open(String url, HttpServletResponse response, HttpServletRequest request);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
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 {
|
||||||
|
|
||||||
|
|
||||||
@ -10,17 +8,5 @@ public interface IOpenFLVService {
|
|||||||
* @param channel
|
* @param channel
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
String getUrl(Integer channel);
|
public String getUrl(Integer channel);
|
||||||
|
|
||||||
/**
|
|
||||||
* 抓取一帧视频并将其转换为图像预处理
|
|
||||||
* @param iplImage
|
|
||||||
*/
|
|
||||||
void preview(IplImage iplImage);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 开启预处理
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
boolean openPreview();
|
|
||||||
}
|
}
|
@ -4,7 +4,6 @@ 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;
|
||||||
|
|
||||||
@ -20,70 +19,68 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* FLV流转换
|
* FLV流转换
|
||||||
*
|
*
|
||||||
* @author gc.x
|
* @author gc.x
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
public String md5(String plainText) {
|
||||||
public void open(String url, HttpServletResponse response, HttpServletRequest request, IOpenFLVService openFLVService) {
|
StringBuilder buf = null;
|
||||||
open(null, url, response, request, openFLVService);
|
try {
|
||||||
}
|
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||||
|
md.update(plainText.getBytes());
|
||||||
@Override
|
byte b[] = md.digest();
|
||||||
public void open(Integer channel, String url, HttpServletResponse response, HttpServletRequest request, IOpenFLVService openFLVService) {
|
int i;
|
||||||
String key = md5(url);
|
buf = new StringBuilder("");
|
||||||
AsyncContext async = request.startAsync();
|
for (int offset = 0; offset < b.length; offset++) {
|
||||||
async.setTimeout(0);
|
i = b[offset];
|
||||||
if (converters.containsKey(key)) {
|
if (i < 0)
|
||||||
Converter c = converters.get(key);
|
i += 256;
|
||||||
try {
|
if (i < 16)
|
||||||
c.addOutputStreamEntity(key, async);
|
buf.append("0");
|
||||||
} catch (IOException e) {
|
buf.append(Integer.toHexString(i));
|
||||||
log.error(e.getMessage(), e);
|
}
|
||||||
throw new IllegalArgumentException(e.getMessage());
|
} catch (NoSuchAlgorithmException e) {
|
||||||
}
|
log.error(e.getMessage(), e);
|
||||||
} else {
|
}
|
||||||
List<AsyncContext> outs = new ArrayList<>();
|
return buf.toString();
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,33 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,73 +0,0 @@
|
|||||||
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 type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/flv.js/1.5.0/flv.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/flv.js@1.5.0/dist/flv.min.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
|
||||||
<video id="videoPlayer" th:style="'width:500px; height:500px;'" controls></video>
|
<video id="videoPlayer" th:style="'width:' + ${width} + 'px; height:' + ${height} + 'px;'" 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: 'http://localhost:8000' + videoPath
|
url: 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>
|
||||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,37 +0,0 @@
|
|||||||
<!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