Compare commits

..

No commits in common. "0c6869f6b2a9bd76c0ac54068f75c1ccdccdfa9f" and "bb297dda318709a8a3a40589bde59761e3d96d96" have entirely different histories.

30 changed files with 387 additions and 447 deletions

33
.gitignore vendored Normal file
View 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/

View File

@ -1,6 +1,11 @@
<!-- Easy-FLV: Java RTSP/RTMP to FLV Converter -->
# 📺 Easy-FLV: Java 实现的 RTSP/RTMP 到 FLV 转换器
[![GitHub stars](https://img.shields.io/github/stars/javpower/easy-flv.svg)](https://github.com/javpower/easy-flv)
[![GitHub issues](https://img.shields.io/github/issues/javpower/easy-flv.svg)](https://github.com/javpower/easy-flv/issues)
[![Apache License 2.0](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[![Java Version](https://img.shields.io/badge/java-1.8+-orange.svg)](https://adoptopenjdk.net/)
[![Spring Boot](https://img.shields.io/badge/spring--boot-2.7.+-blue.svg)](https://spring.io/projects/spring-boot)
## 🌟 关于 Easy-FLV
Easy-FLV 是一个用 Java 实现的库,它能够将 RTSP 或 RTMP 视频流转换为 FLV 格式,以便在浏览器中播放。它为实时视频监控、直播和视频流处理提供了一个高效、稳定且易于集成的解决方案。
@ -11,6 +16,11 @@ Easy-FLV 是一个用 Java 实现的库,它能够将 RTSP 或 RTMP 视频流
- **现代浏览器支持**:支持所有主流浏览器,无需额外插件。
- **实时流处理**:适用于实时视频流的转换,如安防监控和直播。
## 📄 效果图
以下是 Easy-FLV 在行动的效果图:
![img_1.png](img_1.png)
![img.png](img.png)
## 🚀 快速开始
@ -19,9 +29,9 @@ Easy-FLV 是一个用 Java 实现的库,它能够将 RTSP 或 RTMP 视频流
```xml
<dependency>
<groupId>me.zhengjie</groupId>
<artifactId>eladmin-flv</artifactId>
<version>1.1</version>
<groupId>io.github.javpower</groupId>
<artifactId>rtsp-converter-flv-spring-boot-starter</artifactId>
<version>1.5.9.1</version>
</dependency>
```
@ -35,7 +45,7 @@ public class RtspDataService implements IOpenFLVService {
@Override
public String getUrl(Integer channel) {
// 根据 channel 获取 RTSP 视频流地址
return "rtsp://xx.xx.xx.xx/openUrl/16HV8mA";
return "rtsp://10.11.9.251:554/openUrl/16HV8mA";
}
}
```
@ -73,4 +83,9 @@ public static void main(String[] args) throws UnsupportedEncodingException {
我们欢迎任何形式的贡献,包括但不限于报告 bug、提交修复、添加新功能、改进文档等。
## 📄 许可证
Easy-FLV 根据 [Apache License 2.0](LICENSE) 发布。
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)

BIN
img.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 998 KiB

BIN
img_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

17
pom.xml
View File

@ -2,12 +2,12 @@
<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">
<modelVersion>4.0.0</modelVersion>
<groupId>me.zhengjie</groupId>
<artifactId>eladmin-flv</artifactId>
<version>1.1</version>
<name>视频模块</name>
<description>视频流转换 rtsp/rtmp 转flv</description>
<groupId>io.github.javpower</groupId>
<artifactId>rtsp-converter-flv-spring-boot-starter</artifactId>
<version>1.5.9.1</version>
<name>rtsp-converter-flv-spring-boot-starter</name>
<description>a tool about easy rtsp-converter-flv</description>
<url>https://github.com/javpower/easy-flv</url>
<licenses>
<license>
<name> The Apache Software License, Version 2.0 </name>
@ -38,11 +38,6 @@
</properties>
<dependencies>
<dependency>
<groupId>me.zhengjie</groupId>
<artifactId>eladmin-logging</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>

View File

@ -2,16 +2,10 @@ package com.gc.easy.flv.controller;
import com.gc.easy.flv.service.IFLVService;
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.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
@ -20,28 +14,31 @@ import java.io.UnsupportedEncodingException;
/**
* FLV流转换
*
*
* @author gc.x
*/
@Slf4j
@RestController("api/front/flv")
@Api(tags = "微信:拉流播放")
@RestController
public class FLVController {
@Autowired
private IFLVService service;
@Autowired
private IFLVService service;
@Autowired(required = false)
private IOpenFLVService openFLVService;
@Autowired(required = false)
private IOpenFLVService openFLVService;
@Log("拉流播放")
@ApiOperation(value = "打开视频流")
// @GetMapping(value = "/get/stream")
@AnonymousGetMapping(value = "/get/stream")
public void open(String url, HttpServletResponse response, HttpServletRequest request) throws UnsupportedEncodingException {
if (!StringUtils.isEmpty(url)) {
String decodedUrl = java.net.URLDecoder.decode(url, "UTF-8");
service.open(decodedUrl, response, request, openFLVService);
}
}
@GetMapping(value = "/get/flv/hls/stream_{channel}.flv")
public void open(@PathVariable(value = "channel") Integer channel, HttpServletResponse response,
HttpServletRequest request) {
String url = openFLVService.getUrl(channel);
if(!StringUtils.isEmpty(url)){
service.open(channel,url, response, request);
}
}
@GetMapping(value = "/get/flv/hls/stream")
public void open1(String url, HttpServletResponse response,
HttpServletRequest request) throws UnsupportedEncodingException {
if(!StringUtils.isEmpty(url)){
String decodedUrl = java.net.URLDecoder.decode(url, "UTF-8");
service.open(decodedUrl, response, request);
}
}
}

View File

@ -1,30 +1,39 @@
//package com.gc.easy.flv.controller;
//
//import com.gc.easy.flv.config.FlvConfig;
//import me.zhengjie.annotation.rest.AnonymousGetMapping;
//import org.springframework.beans.factory.annotation.Autowired;
//import org.springframework.stereotype.Controller;
//import org.springframework.ui.Model;
//
//import java.io.UnsupportedEncodingException;
//
///**
// * FLV流转换
// *
// * @author gc.x
// */
//@Controller
//public class FLVPlayController {
// @Autowired
// private FlvConfig flvConfig;
//
// @AnonymousGetMapping(value = "/flv/hls/stream")
// public String getAppHtml1(String url, Model model) throws UnsupportedEncodingException {
// String decodedUrl = java.net.URLDecoder.decode(url, "UTF-8");
// String videoPath="/api/front/flv/get/stream?url="+decodedUrl;
// model.addAttribute("videoPath", videoPath);
// model.addAttribute("wight", flvConfig.getWight());
// model.addAttribute("height", flvConfig.getHeight());
// return "video";
// }
//}
package com.gc.easy.flv.controller;
import com.gc.easy.flv.config.FlvConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import java.io.UnsupportedEncodingException;
/**
* FLV流转换
*
* @author gc.x
*/
@Controller
public class FLVPlayController {
@Autowired
private FlvConfig flvConfig;
@GetMapping(value = "/flv/hls/stream_{channel}.flv")
public String getAppHtml(@PathVariable(value = "channel") Integer channel, Model model) {
String videoPath=flvConfig.getHost()+"/get/flv/hls/stream_"+channel+".flv";
model.addAttribute("videoPath", videoPath);
model.addAttribute("wight", flvConfig.getWight());
model.addAttribute("height", flvConfig.getHeight());
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";
}
}

View File

@ -1,7 +1,6 @@
package com.gc.easy.flv.factories;
import com.alibaba.fastjson.util.IOUtils;
import com.gc.easy.flv.service.IOpenFLVService;
import lombok.extern.slf4j.Slf4j;
import org.bytedeco.ffmpeg.avcodec.AVPacket;
import org.bytedeco.ffmpeg.global.avcodec;
@ -24,209 +23,206 @@ import java.util.Map;
*/
@Slf4j
public class ConverterFactories extends Thread implements Converter {
public volatile boolean runing = true;
/**
* 读流器
*/
private FFmpegFrameGrabber grabber;
/**
* 转码器
*/
private FFmpegFrameRecorder recorder;
/**
* 转FLV格式的头信息<br/>
* 如果有第二个客户端播放首先要返回头信息
*/
private byte[] headers;
/**
* 保存转换好的流
*/
private ByteArrayOutputStream stream;
/**
* 流地址h264,aac
*/
private String url;
/**
* 流输出
*/
private List<AsyncContext> outEntitys;
public volatile boolean runing = true;
/**
* 读流器
*/
private FFmpegFrameGrabber grabber;
/**
* 转码器
*/
private FFmpegFrameRecorder recorder;
/**
* 转FLV格式的头信息<br/>
* 如果有第二个客户端播放首先要返回头信息
*/
private byte[] headers;
/**
* 保存转换好的流
*/
private ByteArrayOutputStream stream;
/**
* 流地址h264,aac
*/
private String url;
/**
* 流输出
*/
private List<AsyncContext> outEntitys;
/**
* key用于表示这个转换器
*/
private String key;
/**
* key用于表示这个转换器
*/
private String key;
/**
* 转换队列
*/
private Map<String, Converter> factories;
private IOpenFLVService openFLVService;
/**
* 转换队列
*/
private Map<String, Converter> factories;
public ConverterFactories(String url, String key, Map<String, Converter> factories, List<AsyncContext> outEntitys, IOpenFLVService openFLVService) {
this.url = url;
this.key = key;
this.factories = factories;
this.outEntitys = outEntitys;
this.openFLVService = openFLVService;
}
public ConverterFactories(String url, String key, Map<String, Converter> factories, List<AsyncContext> outEntitys) {
this.url = url;
this.key = key;
this.factories = factories;
this.outEntitys = outEntitys;
}
@Override
public void run() {
boolean isCloseGrabberAndResponse = true;
try {
grabber = new FFmpegFrameGrabber(url);
if ("rtsp".equals(url.substring(0, 4))) {
grabber.setOption("rtsp_transport", "tcp");
grabber.setOption("timeout", "5000000");
}
grabber.start();
if (avcodec.AV_CODEC_ID_H264 == grabber.getVideoCodec()
&& (grabber.getAudioChannels() == 0 || avcodec.AV_CODEC_ID_AAC == grabber.getAudioCodec()) && (openFLVService == null || !openFLVService.openPreview())) {
log.info("this url:{} converterFactories start", url);
// 来源视频H264格式,音频AAC格式
// 无须转码更低的资源消耗更低的延迟
stream = new ByteArrayOutputStream();
recorder = new FFmpegFrameRecorder(stream, grabber.getImageWidth(), grabber.getImageHeight(),
grabber.getAudioChannels());
recorder.setInterleaved(true);
recorder.setVideoOption("preset", "ultrafast");
recorder.setVideoOption("tune", "zerolatency");
recorder.setVideoOption("crf", "25");
recorder.setFrameRate(grabber.getFrameRate());
recorder.setSampleRate(grabber.getSampleRate());
if (grabber.getAudioChannels() > 0) {
recorder.setAudioChannels(grabber.getAudioChannels());
recorder.setAudioBitrate(grabber.getAudioBitrate());
recorder.setAudioCodec(grabber.getAudioCodec());
}
recorder.setFormat("flv");
recorder.setVideoBitrate(grabber.getVideoBitrate());
recorder.setVideoCodec(grabber.getVideoCodec());
recorder.start(grabber.getFormatContext());
if (headers == null) {
headers = stream.toByteArray();
stream.reset();
writeResponse(headers);
}
int nullNumber = 0;
while (runing) {
AVPacket k = grabber.grabPacket();
if (k != null) {
try {
recorder.recordPacket(k);
} catch (Exception e) {
}
if (stream.size() > 0) {
byte[] b = stream.toByteArray();
stream.reset();
writeResponse(b);
if (outEntitys.isEmpty()) {
log.info("没有输出退出");
break;
}
}
avcodec.av_packet_unref(k);
} else {
nullNumber++;
if (nullNumber > 200) {
break;
}
}
Thread.sleep(5);
}
} else {
isCloseGrabberAndResponse = false;
// 需要转码为视频H264格式,音频AAC格式
ConverterTranFactories c = new ConverterTranFactories(url, key, factories, outEntitys, grabber, openFLVService);
factories.put(key, c);
c.start();
}
} catch (Exception e) {
log.error(e.getMessage(), e);
} finally {
closeConverter(isCloseGrabberAndResponse);
completeResponse(isCloseGrabberAndResponse);
log.info("this url:{} converterFactories exit", url);
@Override
public void run() {
boolean isCloseGrabberAndResponse = true;
try {
grabber = new FFmpegFrameGrabber(url);
if ("rtsp".equals(url.substring(0, 4))) {
grabber.setOption("rtsp_transport", "tcp");
grabber.setOption("stimeout", "5000000");
}
grabber.start();
if (avcodec.AV_CODEC_ID_H264 == grabber.getVideoCodec()
&& (grabber.getAudioChannels() == 0 || avcodec.AV_CODEC_ID_AAC == grabber.getAudioCodec())) {
log.info("this url:{} converterFactories start", url);
// 来源视频H264格式,音频AAC格式
// 无须转码更低的资源消耗更低的延迟
stream = new ByteArrayOutputStream();
recorder = new FFmpegFrameRecorder(stream, grabber.getImageWidth(), grabber.getImageHeight(),
grabber.getAudioChannels());
recorder.setInterleaved(true);
recorder.setVideoOption("preset", "ultrafast");
recorder.setVideoOption("tune", "zerolatency");
recorder.setVideoOption("crf", "25");
recorder.setFrameRate(grabber.getFrameRate());
recorder.setSampleRate(grabber.getSampleRate());
if (grabber.getAudioChannels() > 0) {
recorder.setAudioChannels(grabber.getAudioChannels());
recorder.setAudioBitrate(grabber.getAudioBitrate());
recorder.setAudioCodec(grabber.getAudioCodec());
}
recorder.setFormat("flv");
recorder.setVideoBitrate(grabber.getVideoBitrate());
recorder.setVideoCodec(grabber.getVideoCodec());
recorder.start(grabber.getFormatContext());
if (headers == null) {
headers = stream.toByteArray();
stream.reset();
writeResponse(headers);
}
int nullNumber = 0;
while (runing) {
AVPacket k = grabber.grabPacket();
if (k != null) {
try {
recorder.recordPacket(k);
} catch (Exception e) {
}
if (stream.size() > 0) {
byte[] b = stream.toByteArray();
stream.reset();
writeResponse(b);
if (outEntitys.isEmpty()) {
log.info("没有输出退出");
break;
}
}
avcodec.av_packet_unref(k);
} else {
nullNumber++;
if (nullNumber > 200) {
break;
}
}
Thread.sleep(5);
}
} else {
isCloseGrabberAndResponse = false;
// 需要转码为视频H264格式,音频AAC格式
ConverterTranFactories c = new ConverterTranFactories(url, key, factories, outEntitys, grabber);
factories.put(key, c);
c.start();
}
} catch (Exception e) {
log.error(e.getMessage(), e);
} finally {
closeConverter(isCloseGrabberAndResponse);
completeResponse(isCloseGrabberAndResponse);
log.info("this url:{} converterFactories exit", url);
}
}
}
}
/**
* 输出FLV视频流
*
* @param b
*/
public void writeResponse(byte[] b) {
Iterator<AsyncContext> it = outEntitys.iterator();
while (it.hasNext()) {
AsyncContext o = it.next();
try {
o.getResponse().getOutputStream().write(b);
} catch (Exception e) {
log.info("移除一个输出");
it.remove();
}
}
}
/**
* 输出FLV视频流
*
* @param b
*/
public void writeResponse(byte[] b) {
Iterator<AsyncContext> it = outEntitys.iterator();
while (it.hasNext()) {
AsyncContext o = it.next();
try {
o.getResponse().getOutputStream().write(b);
} catch (Exception e) {
log.info("移除一个输出");
it.remove();
}
}
}
/**
* 退出转换
*/
public void closeConverter(boolean isCloseGrabberAndResponse) {
if (isCloseGrabberAndResponse) {
IOUtils.close(grabber);
factories.remove(this.key);
}
IOUtils.close(recorder);
IOUtils.close(stream);
}
/**
* 退出转换
*/
public void closeConverter(boolean isCloseGrabberAndResponse) {
if (isCloseGrabberAndResponse) {
IOUtils.close(grabber);
factories.remove(this.key);
}
IOUtils.close(recorder);
IOUtils.close(stream);
}
/**
* 关闭异步响应
*
* @param isCloseGrabberAndResponse
*/
public void completeResponse(boolean isCloseGrabberAndResponse) {
if (isCloseGrabberAndResponse) {
Iterator<AsyncContext> it = outEntitys.iterator();
while (it.hasNext()) {
AsyncContext o = it.next();
o.complete();
}
}
}
/**
* 关闭异步响应
*
* @param isCloseGrabberAndResponse
*/
public void completeResponse(boolean isCloseGrabberAndResponse) {
if (isCloseGrabberAndResponse) {
Iterator<AsyncContext> it = outEntitys.iterator();
while (it.hasNext()) {
AsyncContext o = it.next();
o.complete();
}
}
}
@Override
public String getKey() {
return this.key;
}
@Override
public String getKey() {
return this.key;
}
@Override
public String getUrl() {
return this.url;
}
@Override
public String getUrl() {
return this.url;
}
@Override
public void addOutputStreamEntity(String key, AsyncContext entity) throws IOException {
if (headers == null) {
outEntitys.add(entity);
} else {
entity.getResponse().getOutputStream().write(headers);
entity.getResponse().getOutputStream().flush();
outEntitys.add(entity);
}
}
@Override
public void addOutputStreamEntity(String key, AsyncContext entity) throws IOException {
if (headers == null) {
outEntitys.add(entity);
} else {
entity.getResponse().getOutputStream().write(headers);
entity.getResponse().getOutputStream().flush();
outEntitys.add(entity);
}
}
@Override
public void exit() {
this.runing = false;
try {
this.join();
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
@Override
public void exit() {
this.runing = false;
try {
this.join();
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}

View File

@ -1,14 +1,11 @@
package com.gc.easy.flv.factories;
import com.alibaba.fastjson.util.IOUtils;
import com.gc.easy.flv.service.IOpenFLVService;
import lombok.extern.slf4j.Slf4j;
import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.FFmpegFrameRecorder;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.OpenCVFrameConverter;
import org.bytedeco.opencv.opencv_core.IplImage;
import javax.servlet.AsyncContext;
import java.io.ByteArrayOutputStream;
@ -62,18 +59,13 @@ public class ConverterTranFactories extends Thread implements Converter {
*/
private Map<String, Converter> factories;
private OpenCVFrameConverter.ToIplImage converter;
private IOpenFLVService openFLVService;
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.key = key;
this.factories = factories;
this.outEntitys = outEntitys;
this.grabber = grabber;
this.openFLVService=openFLVService;
}
@Override
@ -90,8 +82,6 @@ public class ConverterTranFactories extends Thread implements Converter {
stream = new ByteArrayOutputStream();
recorder = new FFmpegFrameRecorder(stream, grabber.getImageWidth(), grabber.getImageHeight(),
grabber.getAudioChannels());
converter = new OpenCVFrameConverter.ToIplImage();
recorder.setInterleaved(true);
recorder.setVideoOption("preset", "ultrafast");
recorder.setVideoOption("tune", "zerolatency");
@ -117,14 +107,6 @@ public class ConverterTranFactories extends Thread implements Converter {
while (runing) {
// 抓取一帧
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) {
try {
// 转码

View File

@ -11,7 +11,7 @@ public interface IFLVService {
* @param url
* @param response
*/
public void open(Integer channel,String url, HttpServletResponse response, HttpServletRequest request,IOpenFLVService openFLVService);
public void open(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);
}

View File

@ -1,7 +1,5 @@
package com.gc.easy.flv.service;
import org.bytedeco.opencv.opencv_core.IplImage;
public interface IOpenFLVService {
@ -10,17 +8,5 @@ public interface IOpenFLVService {
* @param channel
* @return
*/
String getUrl(Integer channel);
/**
* 抓取一帧视频并将其转换为图像预处理
* @param iplImage
*/
void preview(IplImage iplImage);
/**
* 开启预处理
* @return
*/
boolean openPreview();
public String getUrl(Integer channel);
}

View File

@ -4,7 +4,6 @@ package com.gc.easy.flv.service.impl;
import com.gc.easy.flv.factories.Converter;
import com.gc.easy.flv.factories.ConverterFactories;
import com.gc.easy.flv.service.IFLVService;
import com.gc.easy.flv.service.IOpenFLVService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@ -20,70 +19,68 @@ import java.util.concurrent.ConcurrentHashMap;
/**
* FLV流转换
*
*
* @author gc.x
*/
@Slf4j
@Service
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 void open(String url, HttpServletResponse response, HttpServletRequest request, IOpenFLVService openFLVService) {
open(null, url, response, request, openFLVService);
}
@Override
public void open(Integer channel, String url, HttpServletResponse response, HttpServletRequest request, IOpenFLVService openFLVService) {
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, 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();
}
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();
}
}

View File

@ -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;
}
}

View File

@ -5,33 +5,33 @@
<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>
<script src="https://cdn.jsdelivr.net/npm/flv.js@1.5.0/dist/flv.min.js"></script>
</head>
<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">
(function () {
var videoPath = /*[[${videoPath}]]*/ '';
(function() {
var videoPath = /*[[${videoPath}]]*/ '';
if (flvjs.isSupported()) {
var videoElement = document.getElementById('videoPlayer');
var flvPlayer = flvjs.createPlayer({
type: 'flv',
url: 'http://localhost:8000' + videoPath
});
if (flvjs.isSupported()) {
var videoElement = document.getElementById('videoPlayer');
var flvPlayer = flvjs.createPlayer({
type: 'flv',
url: videoPath
});
flvPlayer.attachMediaElement(videoElement);
flvPlayer.load();
flvPlayer.attachMediaElement(videoElement);
flvPlayer.load();
// 播放视频
videoElement.play();
} else {
console.error('FLV not supported');
}
})();
</script>
// 播放视频
videoElement.play();
} else {
console.error('FLV not supported');
}
})();
</script>
</body>
</html>

View File

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