Pu Zhibing
2025-04-10 153b905185b40182978758d409accbb76f678cb1
修改视频直播方式为http-flv
6个文件已修改
320 ■■■■■ 已修改文件
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/CarController.java 126 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/OrderController.java 93 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/ICarService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/CarServiceImpl.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/util/JavaCVStreamUtil.java 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/util/TaskUtil.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/CarController.java
@@ -52,14 +52,23 @@
@RequestMapping("/car")
public class CarController {
    
    @Value("${live.output.path}")
    private String outputPath;
    @Value("${live.hls.output-path}")
    private String hlsOutputPath;
    
    @Value("${live.ip}")
    private String liveIp;
    @Value("${live.hls.ip}")
    private String hlsIp;
    
    @Value("${live.port}")
    private Integer livePort;
    @Value("${live.hls.port}")
    private Integer hlsPort;
    @Value("${live.flv.ip}")
    private String flvIp;
    @Value("${live.flv.rtmp-port}")
    private Integer flvRtmpPort;
    @Value("${live.flv.http-port}")
    private Integer flvHttpPort;
    @Resource
    private ICarService carService;
    @Resource
@@ -122,32 +131,17 @@
        Enterprise enterprise = enterpriseService.getById(car.getEnterpriseId());
        R<UPRealvideoMsgStartupAckVo> msgStartupAckVoR = realVideoMsgClient.startupRealVideo(Integer.valueOf(enterprise.getCode()), car.getVehicleNumber());
        if (200 == msgStartupAckVoR.getCode()) {
            String path = outputPath + "hls\\" + car.getVehicleNumber() + "\\live.m3u8";
            String folderPath = outputPath + "hls\\" + car.getVehicleNumber();
            FileUtil.mkParentDirs(path);
            File file = new File(path);
            if (!file.exists()) {
                try {
                    file.createNewFile();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            UPRealvideoMsgStartupAckVo data = msgStartupAckVoR.getData();
            RealVideoResp resp = new RealVideoResp();
            //执行拉流和推流
            ExecutorService executorService = new ThreadPoolExecutor(1, 1,
                    0L, TimeUnit.MILLISECONDS,
                    new LinkedBlockingQueue<Runnable>());
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    JavaCVStreamUtil.push_hls(data.getUrl(), path, id, folderPath);
                }
            });
            resp.setServerIp(liveIp);
            resp.setServerPort(livePort);
//            live_hls(data.getUrl(), car);
//            resp.setServerIp(hlsIp);
//            resp.setServerPort(hlsPort);
            live_flv(data.getUrl(), car.getId());
            resp.setServerIp(flvIp);
            resp.setServerPort(flvHttpPort);
            return R.ok(resp);
        }
        
@@ -165,7 +159,7 @@
        if (null == car) {
            return R.fail("失败");
        }
        String folderPath = outputPath + "hls\\" + car.getVehicleNumber();
        String folderPath = hlsOutputPath + "hls\\" + car.getVehicleNumber();
        JavaCVStreamUtil.close(id, folderPath);
        return R.ok();
    }
@@ -197,30 +191,14 @@
        if (200 == startupAckVoR.getCode()) {
            UPPlaybackMsgStartupAckVo data = startupAckVoR.getData();
            RealVideoResp resp = new RealVideoResp();
            String path = outputPath + "hls\\" + car.getVehicleNumber() + "\\live.m3u8";
            String folderPath = outputPath + "hls\\" + car.getVehicleNumber();
            FileUtil.mkParentDirs(path);
            File file = new File(path);
            if (!file.exists()) {
                try {
                    file.createNewFile();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            //执行拉流和推流
            ExecutorService executorService = new ThreadPoolExecutor(1, 1,
                    0L, TimeUnit.MILLISECONDS,
                    new LinkedBlockingQueue<Runnable>());
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    JavaCVStreamUtil.push_hls(data.getUrl(), path, req.getId(), folderPath);
                }
            });
            resp.setServerIp(liveIp);
            resp.setServerPort(livePort);
//            live_hls(data.getUrl(), car);
//            resp.setServerIp(hlsIp);
//            resp.setServerPort(hlsPort);
            live_flv(data.getUrl(), car.getId());
            resp.setServerIp(flvIp);
            resp.setServerPort(flvHttpPort);
            return R.ok(resp);
        }
        return R.fail(startupAckVoR.getMsg());
@@ -313,4 +291,46 @@
        }
        return R.ok(list);
    }
    public void live_hls(String input, Car car){
        String path = hlsOutputPath + "hls\\" + car.getVehicleNumber() + "\\live.m3u8";
        String folderPath = hlsOutputPath + "hls\\" + car.getVehicleNumber();
        FileUtil.mkParentDirs(path);
        File file = new File(path);
        if (!file.exists()) {
            try {
                file.createNewFile();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        //执行拉流和推流
        ExecutorService executorService = new ThreadPoolExecutor(1, 1,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>());
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                JavaCVStreamUtil.push_hls(input, path, car.getId(), folderPath);
            }
        });
        carService.taskPlayDetection(car.getId());
    }
    public void live_flv(String input, Integer id){
        String url = "rtmp://" + flvIp + ":" + flvRtmpPort + "/flv/" + id;
        //执行拉流和推流
        ExecutorService executorService = new ThreadPoolExecutor(1, 1,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>());
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                JavaCVStreamUtil.push_flv(input, url, id);
            }
        });
//        carService.taskPlayDetection(id);
    }
}
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/OrderController.java
@@ -75,14 +75,23 @@
    @Resource
    private RedisTemplate redisTemplate;
    
    @Value("${live.output.path}")
    private String outputPath;
    @Value("${live.hls.output-path}")
    private String hlsOutputPath;
    
    @Value("${live.ip}")
    private String liveIp;
    @Value("${live.hls.ip}")
    private String hlsIp;
    
    @Value("${live.port}")
    private Integer livePort;
    @Value("${live.hls.port}")
    private Integer hlsPort;
    @Value("${live.flv.ip}")
    private String flvIp;
    @Value("${live.flv.rtmp-port}")
    private Integer flvRtmpPort;
    @Value("${live.flv.http-port}")
    private Integer flvHttpPort;
    
    
    @GetMapping("/getOrderList")
@@ -187,32 +196,58 @@
        if (200 == startupAckVoR.getCode()) {
            UPPlaybackMsgStartupAckVo data = startupAckVoR.getData();
            RealVideoResp resp = new RealVideoResp();
            String path = outputPath + "hls\\" + car.getVehicleNumber() + "\\live.m3u8";
            String folderPath = outputPath + "hls\\" + car.getVehicleNumber();
            FileUtil.mkParentDirs(path);
            File file = new File(path);
            if (!file.exists()) {
                try {
                    file.createNewFile();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            //执行拉流和推流
            ExecutorService executorService = new ThreadPoolExecutor(1, 1,
                    0L, TimeUnit.MILLISECONDS,
                    new LinkedBlockingQueue<Runnable>());
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    JavaCVStreamUtil.push_hls(data.getUrl(), path, id, folderPath);
                }
            });
            resp.setServerIp(liveIp);
            resp.setServerPort(livePort);
//            live_hls(data.getUrl(), car);
//            resp.setServerIp(hlsIp);
//            resp.setServerPort(hlsPort);
            live_flv(data.getUrl(), car.getId());
            resp.setServerIp(flvIp);
            resp.setServerPort(flvHttpPort);
            return R.ok(resp);
        }
        return R.fail(startupAckVoR.getMsg());
    }
    public void live_hls(String input, Car car){
        String path = hlsOutputPath + "hls\\" + car.getVehicleNumber() + "\\live.m3u8";
        String folderPath = hlsOutputPath + "hls\\" + car.getVehicleNumber();
        FileUtil.mkParentDirs(path);
        File file = new File(path);
        if (!file.exists()) {
            try {
                file.createNewFile();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        //执行拉流和推流
        ExecutorService executorService = new ThreadPoolExecutor(1, 1,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>());
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                JavaCVStreamUtil.push_hls(input, path, car.getId(), folderPath);
            }
        });
        carService.taskPlayDetection(car.getId());
    }
    public void live_flv(String input, Integer id){
        String url = "rtsp://" + flvIp + ":" + flvRtmpPort + "/flv/" + id;
        //执行拉流和推流
        ExecutorService executorService = new ThreadPoolExecutor(1, 1,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>());
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                JavaCVStreamUtil.push_flv(input, url, id);
            }
        });
        carService.taskPlayDetection(id);
    }
}
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/ICarService.java
@@ -37,5 +37,5 @@
     * 检测视频播放,清除没有播放的视频流
     * @return
     */
    void taskPlayDetection();
    void taskPlayDetection(Integer deviceNumber);
}
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/CarServiceImpl.java
@@ -31,6 +31,9 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
 * @author zhibing.pu
@@ -39,8 +42,8 @@
@Service
public class CarServiceImpl extends ServiceImpl<CarMapper, Car> implements ICarService {
    
    @Value("${live.output.path}")
    private String outputPath;
    @Value("${live.hls.output-path}")
    private String hlsOutputPath;
    
    @Resource
    private UPExgMsgRegisterClient upExgMsgRegisterClient;
@@ -162,13 +165,20 @@
     * 检测视频播放,清除没有播放的视频流
     */
    @Override
    public void taskPlayDetection() {
        List<Car> list = this.list();
        for (Car car : list) {
            if(!redisTemplate.hasKey("live:" + car.getId())){
                String folderPath = outputPath + "hls\\" + car.getVehicleNumber();
                JavaCVStreamUtil.close(car.getId(), folderPath);
            }
    public void taskPlayDetection(Integer deviceNumber) {
        Car car = this.getById(deviceNumber);
        try {
            ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
            executorService.scheduleWithFixedDelay(new Runnable() {
                @Override
                public void run() {
                    if(!redisTemplate.hasKey("live:" + car.getId())){
                        String folderPath = hlsOutputPath + "hls\\" + car.getVehicleNumber();
                        JavaCVStreamUtil.close(car.getId(), folderPath);
                    }
                }}, 1, 1, TimeUnit.MINUTES);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/util/JavaCVStreamUtil.java
@@ -2,19 +2,25 @@
import cn.hutool.core.io.FileUtil;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.system.service.ICarService;
import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.ffmpeg.global.avutil;
import org.bytedeco.javacv.*;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
 * 音视频推流拉流工具类
 * 参考资料:https://juejin.cn/post/7311230172237561866
 *
 * @author zhibing.pu
 * @Date 2025/4/8 16:03
 */
@Component
public class JavaCVStreamUtil {
    
    private static Map<Integer, FFmpegFrameGrabber> grabberMap = new ConcurrentHashMap<>();
@@ -25,27 +31,33 @@
    
    private static Map<Integer, Boolean> statusMap = new ConcurrentHashMap<>();
    
    @Resource
    private RedisTemplate redisTemplate;
    @Resource
    private ICarService carService;
    
    
    /**
     * 视频拉流和推流
     */
    public static void push_flv(String inputUrl, String outputUrl, Integer deviceNumber){
    public static void push_flv(String inputUrl, String outputUrl, Integer deviceNumber) {
        OpenCVFrameGrabber grabber = null;
//        FFmpegFrameGrabber grabber = null;
        FFmpegFrameRecorder recorder = null;
        try {
            //关闭上一个没有正确关闭的流
            FFmpegFrameGrabber fFmpegFrameGrabber = grabberMap.get(deviceNumber);
            if(null != fFmpegFrameGrabber){
            if (null != fFmpegFrameGrabber) {
                fFmpegFrameGrabber.close();
            }
            OpenCVFrameGrabber openCVFrameGrabber = grabberMap1.get(deviceNumber);
            if(null != openCVFrameGrabber){
                openCVFrameGrabber.close();;
            if (null != openCVFrameGrabber) {
                openCVFrameGrabber.close();
                ;
            }
            FFmpegFrameRecorder fFmpegFrameRecorder = frameRecorderMap.get(deviceNumber);
            if(null != fFmpegFrameRecorder){
            if (null != fFmpegFrameRecorder) {
                fFmpegFrameRecorder.close();
            }
            
@@ -95,13 +107,13 @@
            while ((frame = grabber.grab()) != null) {
                recorder.record(frame);
                //判断状态为停止,则结束此线程任务
                if(!statusMap.get(deviceNumber)){
                if (!statusMap.get(deviceNumber)) {
                    break;
                }
            }
        } catch (FrameGrabber.Exception | FrameRecorder.Exception e) {
            e.printStackTrace();
        }finally {
        } finally {
            close(deviceNumber, null);
        }
    }
@@ -110,7 +122,7 @@
    /**
     * 视频拉流和推流
     */
    public static void push_hls(String inputUrl, String outputUrl, Integer deviceNumber, String folderPath){
    public static void push_hls(String inputUrl, String outputUrl, Integer deviceNumber, String folderPath) {
        OpenCVFrameGrabber grabber = null;
//        FFmpegFrameGrabber grabber = null;
        FFmpegFrameRecorder recorder = null;
@@ -118,15 +130,16 @@
        try {
            //关闭上一个没有正确关闭的流
            FFmpegFrameGrabber fFmpegFrameGrabber = grabberMap.get(deviceNumber);
            if(null != fFmpegFrameGrabber){
            if (null != fFmpegFrameGrabber) {
                fFmpegFrameGrabber.close();
            }
            OpenCVFrameGrabber openCVFrameGrabber = grabberMap1.get(deviceNumber);
            if(null != openCVFrameGrabber){
                openCVFrameGrabber.close();;
            if (null != openCVFrameGrabber) {
                openCVFrameGrabber.close();
                ;
            }
            FFmpegFrameRecorder fFmpegFrameRecorder = frameRecorderMap.get(deviceNumber);
            if(null != fFmpegFrameRecorder){
            if (null != fFmpegFrameRecorder) {
                fFmpegFrameRecorder.close();
            }
            
@@ -194,13 +207,13 @@
            while ((frame = grabber.grab()) != null) {
                recorder.record(frame);
                //判断状态为停止,则结束此线程任务
                if(!statusMap.get(deviceNumber)){
                if (!statusMap.get(deviceNumber)) {
                    break;
                }
            }
        } catch (FrameGrabber.Exception | FrameRecorder.Exception e) {
            e.printStackTrace();
        }finally {
        } finally {
            close(deviceNumber, folderPath);
        }
    }
@@ -208,12 +221,13 @@
    
    /**
     * 关闭推流和拉流进程
     *
     * @param deviceNumber
     */
    public static void close(Integer deviceNumber, String folderPath){
    public static void close(Integer deviceNumber, String folderPath) {
        //设置状态为停止
        Boolean status = statusMap.get(deviceNumber);
        if(null == status || !status){
        if (null == status || !status) {
            return;
        }
        statusMap.put(deviceNumber, false);
@@ -222,26 +236,25 @@
        FFmpegFrameRecorder fFmpegFrameRecorder = null;
        try {
            fFmpegFrameGrabber = grabberMap.get(deviceNumber);
            if(null != fFmpegFrameGrabber){
                fFmpegFrameGrabber.flush();
            if (null != fFmpegFrameGrabber) {
                fFmpegFrameGrabber.close();
            }
            openCVFrameGrabber = grabberMap1.get(deviceNumber);
            if(null != openCVFrameGrabber){
                openCVFrameGrabber.flush();
            if (null != openCVFrameGrabber) {
                openCVFrameGrabber.close();
            }
            fFmpegFrameRecorder = frameRecorderMap.get(deviceNumber);
            if(null != fFmpegFrameRecorder){
                fFmpegFrameRecorder.flush();
            if (null != fFmpegFrameRecorder) {
                fFmpegFrameRecorder.close();
            }
        }catch (Exception e){
        } catch (Exception e) {
            e.printStackTrace();
        }
        //开始清除视频文件
        if(StringUtils.isNotEmpty(folderPath)){
        if (StringUtils.isNotEmpty(folderPath)) {
            FileUtil.del(folderPath);
        }
    }
}
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/util/TaskUtil.java
@@ -81,14 +81,4 @@
    public void taskUpdateCarStatus() {
        carService.taskUpdateCarStatus();
    }
    /**
     * 视频播放检测
     */
    @Scheduled(fixedRate = 1000 * 300)
    public void taskPlayDetection() {
        carService.taskPlayDetection();
    }
}