首页 > 其他分享 >Spring Boot - ffmpeg 获得 m3u8 列表和 ts 文件,前端请求视频流进行播放

Spring Boot - ffmpeg 获得 m3u8 列表和 ts 文件,前端请求视频流进行播放

时间:2023-10-31 23:36:56浏览次数:35  
标签:return ffmpeg m3u8 Spring filename file new String

安装 ffmpeg

FFmpeg 下载地址:GitHub releases。请下载:ffmpeg-master-latest-win64-gpl-shared.zip 压缩包。

解压到你系统盘任意位置(前提是你以后找得到这玩意儿在哪)。

接下来就是配置其环境变量,所有的环境变量都是配置它的启动文件的路径到你系统的 Path,基本上都是(也有例外的?)。如 FFmpeg,就是复制其解压路径下的 bin 文件夹,到 Path 路径中。

VideoToM3u8AndTSUtil

file:[VideoToM3u8AndTSUtil.java]
/**
 * @description:
 * @package: com.example.m3u8
 * @author: zheng
 * @date: 2023/10/31
 */
public class VideoToM3u8AndTSUtil {

    public static String getFilenameWithoutSuffix(String filename) {
        int lastDotIndex = filename.lastIndexOf(".");
        if (lastDotIndex > 0) {
            return filename.substring(0, lastDotIndex);
        } else {
            return null;
        }
    }

    public static boolean convert(String srcPathname, String destPathname) {
        try {
            ProcessBuilder processBuilder = new ProcessBuilder("ffmpeg", "-i", srcPathname, "-c:v", "libx264", "-hls_time", "60",
                    "-hls_list_size", "0", "-c:a", "aac", "-strict", "-2", "-f", "hls", destPathname);
            processBuilder.redirectErrorStream(true);

            Process process = processBuilder.start();
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }

            int exitCode = process.waitFor();
            System.out.println("FFmpeg process exited with code: " + exitCode);
            return true;
        } catch (IOException | InterruptedException e) {
            e.fillInStackTrace();
            return false;
        }
    }

    public static boolean write(InputStream inputStream, String filepath, String filename) throws IOException {
        File file = new File(filepath, filename);

        if (!file.getParentFile().exists() && !file.getParentFile().mkdirs()) {
            return false;
        }

        OutputStream outputStream = new FileOutputStream(file);
        byte[] buffer = new byte[1024];
        int bytesRead;
        while ((bytesRead = inputStream.read(buffer)) != -1) {
            outputStream.write(buffer, 0, bytesRead);
        }
        outputStream.close();
        inputStream.close();
        return true;
    }

}

covert 函数简单说明

需要传递两个参数,srcPathname 和 destPathname,即读取的原视频的目录和存放 m3u8 文件的目录。转换完成之后返回一个布尔值进行判断是否转换成功。

war:[start]

需要注意的是,destPathname 的目录必须要存在,如,你存放 m3u8 的文件目录是 E:\Videos\m3u8s,那么该目录就必须提前存在。

war:[end]

VideoController

需要三个接口,上传视频、获取 m3u8 文件、获取 ts 文件。我这里就没有写 Service 类,而是直接写在接口里面的。

file:[VideoController.java]
/**
 * @description:
 * @package: com.example.m3u8
 * @author: zheng
 * @date: 2023/10/28
 */
@RestController
@RequestMapping("/video")
public class VideoController {

    @PostMapping("/upload")
    public ResponseEntity<String> upload(MultipartFile file) {
        if (file == null) {
            return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
        }

        if (file.isEmpty()) {
            return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
        }

        try {

            boolean written = VideoToM3u8AndTSUtil.write(file.getInputStream(), "E:/Type Files/Videos/Captures/videos/", file.getOriginalFilename());
            if (!written) {
                return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
            }

            String srcPathname = "E:/Type Files/Videos/Captures/videos/" + file.getOriginalFilename();
            String filename = VideoToM3u8AndTSUtil.getFilenameWithoutSuffix(Objects.requireNonNull(file.getOriginalFilename()));
            String destPathname = "E:/Type Files/Videos/Captures/m3u8s/" + filename + ".m3u8";

            boolean converted = VideoToM3u8AndTSUtil.convert(srcPathname, destPathname);
            if (!converted) {
                return ResponseEntity.notFound().build();
            }

            return ResponseEntity.ok("http://localhost:8080/video/m3u8?filepath=E:/Type Files/Videos/Captures/m3u8s&filename=" + filename + ".m3u8");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @GetMapping("/m3u8")
    public ResponseEntity<byte[]> getM3U8Content(@RequestParam String filepath, @RequestParam String filename) {
        try {
            File file = new File(filepath, filename);

            if (file.exists()) {
                // 读取M3U8文件内容
                FileInputStream fileInputStream = new FileInputStream(file);
                byte[] data = new byte[(int) file.length()];
                fileInputStream.read(data);
                fileInputStream.close();

                // 设置响应头为M3U8类型
                return ResponseEntity.ok()
                        .contentType(MediaType.valueOf("application/vnd.apple.mpegurl"))
                        .body(data);
            } else {
                return ResponseEntity.notFound().build();
            }
        } catch (IOException e) {
            e.fillInStackTrace();
            return ResponseEntity.notFound().build();
        }
    }

    @GetMapping("/{filename}")
    public ResponseEntity<byte[]> getTSContent(@PathVariable String filename) {
        try {
            File file = new File("E:/Type Files/Videos/Captures/m3u8s/", filename);

            if (file.exists()) {
                // 读取TS文件内容
                FileInputStream fileInputStream = new FileInputStream(file);
                byte[] data = new byte[(int) file.length()];
                fileInputStream.read(data);
                fileInputStream.close();

                // 设置响应头为TS文件类型
                return ResponseEntity.ok()
                        .contentType(MediaType.valueOf("video/mp2t"))
                        .body(data);
            } else {
                return ResponseEntity.notFound().build();
            }
        } catch (IOException e) {
            e.fillInStackTrace();
            return null;
        }
    }
}

上传视频

  1. 将客户端上传过来的视频存储到本地磁盘。获取已存储的视频目录地址。
  2. 调用 convert 转换视频为 m3u8 文件和视频的切片文件(ts 文件)。
  3. 返回一个视频路径,对接下面的接口,当浏览器请求这个接口时就会返回 m3u8 文件给客户端。

获取 m3u8

  1. 从请求中获取 filename 和 filepath,即 m3u8 存储的目录和 m3u8 的文件名。
  2. 读取文件二进制返回给客户端。

获取 ts

  1. 从请求中获取 filename,也就是前端请求 ts 的文件名称。
  2. 从我们转换完成的目录中获取 ts 文件。
  3. 读取文件二进制返回给客户端。

前端

file:[VideoPlayer.html]
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script src="https://cdn.jsdelivr.net/npm/hls.js@1"></script>
    <title>VideoPlayer</title>
  </head>
  <body>
    <video style="width: 800px; height: 500;" id="video" controls></video>
    <script>
      const video = document.getElementById("video");
      const videoSrc =
        "http://localhost:8080/video/m3u8?filepath=E:/Type Files/Videos/Captures/m3u8s&filename=视频.m3u8";

      if (video.canPlayType("application/vnd.apple.mpegurl")) {
        video.src = videoSrc;
      } else if (Hls.isSupported()) {
        const hls = new Hls();
        hls.loadSource(videoSrc);
        hls.attachMedia(video);
      }
    </script>
  </body>
</html>

源码

title:(m3u8 视频上传和播放源码) link:(https://gitee.com/Himmelbleu/java-learning) cover:(https://www.infocode.com.cn/blog/wp-content/uploads/2021/10/f8fba7a2f3c35d3d7c16892b38ba4785.jpg)

标签:return,ffmpeg,m3u8,Spring,filename,file,new,String
From: https://www.cnblogs.com/Himmelbleu/p/17799371.html

相关文章

  • Exception in thread "main" java.lang.NoSuchMethodError: org.springframework.util
    我的项目是springboot架构,项目启动报错如下Exceptioninthread"main"java.lang.NoSuchMethodError:org.springframework.util.Assert.isInstanceOf(Ljava/lang/Class;Ljava/lang/Object;Ljava/util/function/Supplier;)V atorg.springframework.boot.logging.logback.Logb......
  • spring ApplicationContext版本的快速入门
     代码示例:publicinterfaceUserDao{}-------------------------------------------------------------publicclassUserDaoImplimplementsUserDao{}----------------------------------------------------------publicinterfaceUserService{}---------------------......
  • spring BeanFactory版本的快速入门
       代码示例:  publicstaticvoidmain(String[]args){//创建一个工厂对象DefaultListableBeanFactorybeanFactory=newDefaultListableBeanFactory();//创建一个读取器(读xml文件)XmlBeanDefinitionReaderreader=newXmlBeanDefinitionRead......
  • SpringMVC是什么?
    SpringMVC使用MVC架构模式的思想,将Web应用进行职责解构,把一个复杂的Web应用划分成模型(Model)、控制器(Contorller)以及视图(View)三层,有效地简化了Web应用的开发,降低了出错风险,同时也方便了开发人员之间的分工配合。SpringMVC各层的职责如下:Model:负责对请求进行处理,并将......
  • SpringMVC
    1.SpringMVC:特点1.1:基于MVC架构1.2:容易理解,上手快,使用简单1.3:方便与Spring整合1.4:SpringMVC强化注解的使用,控制层(Controller)@Controller2.第一个SpringMVC注解的程序的创建和使用注解式开发:在代码中通过类与方法的注解,完成处理2.1:创建项目,添加jar2.2:配置注册中央控制器(中央调度......
  • 微服务SpringCloud父工程pom依赖
    <!--设置为pom,管理依赖--><packaging>pom</packaging><properties><java.version>1.8</java.version><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8<......
  • spring发送邮件笔记
    文章目录引入依赖配置代码附件url地址为空会不会报错接收方邮件地址错误会不会报错引入依赖推荐用spring集成依赖,不用一个包一个包找了。<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-mail</artifactId></dependency>配......
  • 【面试专题】Spring篇①
    目录1.你知道Spring框架中有哪些重要的模块吗?2.谈谈你对IOC的认识。3.谈谈你对AOP的认识。4.在实际写代码时,有没有用到过AOP?用过的话是在什么地方或者什么场景下?5.Spring中的事务是如何实现的6.Transaction在哪些情况下可能会失效?7.说说你对Spring中的Bean的理解。8.......
  • java实现文件夹上传功能实例代码(SpringBoot框架)
    前言有时我们后台管理等服务可能会有这样一个简单需求,就是根据文件夹将整个文件夹下的所有资源都上传到我们的服务器上,本人也是搜索了大量资料,最终以最简单便捷的方式实现该功能,具体操作步骤如下一、前端如何设置上传组件并将资源上传到后台服务这里的项目框架为若依VUE版本......
  • SpringBoot中,为什么不直接使用一个Service写功能,而是Service接口+ServiceImpl实现类?
    当项目比较简单的时候,需求明确,变更不频繁或者几乎不怎么修改的时候,用第一种就好了当项目比较复杂,需求变更多的时候,用第二种比较好service层=service接口+serviceImpl实现类这种方式好处:1、解耦合2、便于扩展例如:publicinterfaceHumanService{StringgetName();}@Serv......