oss视频切片上传与读取

本文介绍了如何利用ffmpeg进行视频切片上传和读取,以减少视频加载等待时间。通过本地安装ffmpeg和引入java项目ffmpeg依赖两种方式进行视频切割,并提供了相关代码示例。
摘要由CSDN通过智能技术生成

传统视频上传是将视频完整上传至服务器,读取视频时需要加载整个视频,知道将完整视频读取后才会就行播放。而使用视频切片后会将原视频切片,分割为一个.m3u8与若干.ts文件,当对视频读取时,会按照时间节点通过.m3u8中的文件地址读取相应的.ts文件,大大减少了视频加载时的等待时间。
本功能用了两种方式。第一种是通过本地安装ffmpeg软件进行视频切割,第二种是通过应用ffmpeg依赖进行视频切割。

安装ffmpeg方式

安装ffmpeg

https://ffmpeg.org/

工具类完整代码

import com.liuniu.common.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * mp4转换m3u8工具类
 **/
@Slf4j
@Component
public class ConvertM3U8Api {
    // ffmPeg.exe的目录
    private static final String ffmPegPath = "e:\\ffmPeg\\bin\\ffmPeg";

    public static boolean convertOss(String folderUrl, String fileName) {
        if (!checkFile(folderUrl + fileName)) {
            System.out.println("文件不存在!");
            return false;
        }

        //验证文件后缀
        String suffix = StringUtils.substringAfter(fileName, ".");
        String fileFullName = StringUtils.substringBefore(fileName, ".");
        if (!validFileType(suffix)) {
            return false;
        }

        return processM3U8(folderUrl, fileName, fileFullName);
    }

    /**
     * 验证上传文件后缀
     *
     * @param type 类型
     * @return 是否为mp4
     */
    private static boolean validFileType(String type) {
        return "mp4".equals(type);
    }

    /**
     * 验证是否是文件格式
     *
     * @param path 路径
     * @return 是否存在
     */
    private static boolean checkFile(String path) {
        File file = new File(path);
        return file.isFile();
    }

    //

    /**
     * ffmPeg程序转换m3u8
     * ffmPeg -i vue.mp4 -c:v libx264 -hls_time 20 -hls_list_size 0 -c:a aac -strict -2 -f hls vue.m3u8
     * ffmPeg能解析的格式:(asx,asf,mpg,wmv,3gp,mp4,mov,avi,flv等)
     *
     * @param folderUrl    文件路径
     * @param fileName     名称
     * @param fileFullName 全名
     * @return 是否成功
     */
    private static boolean processM3U8(String folderUrl, String fileName, String fileFullName) {
        //这里就写入执行语句就可以了
        List<String> commend = new ArrayList<>();
        commend.add(ffmPegPath);
        commend.add(" -i");
        commend.add(folderUrl + fileName);
        commend.add("-c:v");
        commend.add("libx264");
        commend.add("-hls_time");
        commend.add("5");
        commend.add("-hls_list_size");
        commend.add("0");
        commend.add("-c:a");
        commend.add("aac");
        commend.add("-strict");
        commend.add("-2");
        commend.add("-f");
        commend.add("hls");
        commend.add(folderUrl + fileFullName + ".m3u8");
        try {
            ProcessBuilder builder = new ProcessBuilder();//java
            builder.command(commend);
            Process p = builder.start();
            int i = doWaitFor(p);
            log.info("***i=【{}】***", i);
            p.destroy();
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 监听ffmPeg运行过程
     *
     * @param p 进程
     * @return 直接结果
     */
    public static int doWaitFor(Process p) {
        InputStream in = null;
        InputStream err = null;
        int exitValue = -1; // returned to caller when p is finished
        try {
            log.info("***检测ffmPeg运行***");
            in = p.getInputStream();
            err = p.getErrorStream();
            boolean finished = false; // Set to true when p is finished

            while (!finished) {
                try {
                    while (in.available() > 0) {
                        Character c = (char) in.read();
                        System.out.print(c);
                    }
                    while (err.available() > 0) {
                        Character c = (char) err.read();
                        System.out.print(c);
                    }

                    exitValue = p.exitValue();
                    finished = true;

                } catch (IllegalThreadStateException e) {
                    Thread.sleep(500);
                }
            }
        } catch (Exception e) {
            log.error("doWaitFor();: unexpected exception - "
                    + e.getMessage());
        } finally {
            try {
                if (in != null) {
                    in.close();
                }

            } catch (IOException e) {
                System.out.println(e.getMessage());
            }
            if (err != null) {
                try {
                    err.close();
                } catch (IOException e) {
                    log.error(e.getMessage());
                }
            }
        }
        return exitValue;
    }
}

方法调用完整代码


import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.liuniu.common.core.controller.BaseController;
import com.liuniu.common.core.domain.AjaxResult;
import com.liuniu.common.utils.StringUtils;
import com.liuniu.system.service.ISysConfigService;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileInputStream;
import java.util.UUID;


/**
 * ffmPeg
 *
 * @author
 */
@RestController
@RequestMapping("/api/ffmPeg")
public class FfmPegController extends BaseController {

    @Autowired
    private ISysConfigService configService;

    /**
     * 将表单提交的MultipartFile上传到OSS中
     * 实现思路:
     * 先将文件本地上传
     * 重命名,生成对应文件夹
     * 调用ffmpeg切片转成m3u8
     * 将mp4视频和转换后的m3u8以及ts放在一起,然后遍历文件目录
     * 将文件上传后,删除本地文件夹和文件在将转换后文件上传到oss上
     *
     * @param file 上传的文件
     * @return ResponseResult 结果
     */
    @PostMapping("/m3U8InOSSUpload")
    public AjaxResult testM3U8InOSSUpload(MultipartFile file) {
        //oss域名
        String endpoint = configService.selectConfigByKey("sys.alioss.endpoint");
        //ossId
        String accessKeyId = configService.selectConfigByKey("sys.alioss.keyid");
        //ossKey
        String accessKeySecret = configService.selectConfigByKey("sys.alioss.keysecret");
        String bucketName = configService.selectConfigByKey("sys.alioss.bucket");
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        String url ="";
        try {
            String f = file.getOriginalFilename();
            //获取文件后缀
            String suffix = StringUtils.substringAfter(f, ".");
            String uuid = UUID.randomUUID().toString().replaceAll("-", "");
            //创建文件存储再oss的路径,按日期存储
            String filePath = new DateTime().toString("yyyy/MM/dd");
            filePath = filePath+"/"+uuid;
            //获取文件名后重命名
            String filename =uuid+ StringUtils.substringBefore(f, ".") ;
            /*本地上传*/
            //转储路径D:\mp4\output
            String localPath = "d:\\file\\";
            //完整转储路径
            String folderUrl = localPath + filename;
            //文件名带后缀
            String fileName = filename + "." + suffix;
            //上传oss后路径
            String uploadPath = folderUrl + "/" + fileName;
            File fileFolder = new File(folderUrl);
            if (!fileFolder.exists()) {
                fileFolder.mkdirs();
            }
            File newFile = new File(uploadPath);
            file.transferTo(newFile);
            //mp4转m3u8
            boolean b = ConvertM3U8Api.convertOss(folderUrl + "/", fileName);
            if (!b) {
                return  AjaxResult.error(300, "上传失败!系统转码异常!");
            }
            //访问本地上传文件夹所有文件,依次上传至oss服务器
            File[] files = fileFolder.listFiles();
            if (null == files || files.length == 0) {
                return null;
            }
            boolean flag = true;
            for (int i = 0; i < files.length; i++) {
                if (!files[i].isDirectory()) {
                    //上传
                    String name = files[i].getName();
                    String suf = StringUtils.substringAfter(name, ".");
                    String pre = StringUtils.substringBefore(name, ".");
                    FileInputStream fis = new FileInputStream(files[i]);

                    //获取文件流的文件名
                    //拼接路径和文件名
                    if ("m3u8".equals(suf)) {
                        if (flag && filename.equals(pre)) {
                            //这是封装的上传阿里云oss的方法
                            ossClient.putObject(bucketName,
                                    filePath + "/"  + name,
                                    fis);
                            flag = false;
                        }
                        url = "https://" + bucketName + "." + endpoint + "/" + filePath + "/"  + name;

                    } else if ("ts".equals(suf)) {
                        ossClient.putObject(bucketName,
                                filePath + "/"  + name,
                                fis);
                    }
                    fis.close();
                    if (files[i].exists()) {
                        files[i].delete();
                    }
                }
            }
            //删除文件夹
            fileFolder.delete();
            /*********本地上传(Tomcat配置映射C:/upload/file)*********/

        } catch (Exception e) {
            e.printStackTrace();
            log.error("上传异常");
        }
        return AjaxResult.success(url);
    }
}

若想保留本地文件,可将已下代码注释

if (files[i].exists()) {
    files[i].delete();
}

java引用ffmpeg依赖

pom文件依赖

<dependency>
 <groupId>ws.schild</groupId>
 <artifactId>jave-all-deps</artifactId>
 <version>3.1.1</version>
</dependency>

cmd方式调用ffmpeg


import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ws.schild.jave.process.ProcessKiller;
import ws.schild.jave.process.ProcessWrapper;
import ws.schild.jave.process.ffmpeg.DefaultFFMPEGLocator;
/**
 *
 * @Description:(cmd方式调用ffmpeg)
 */
public class FfmpegCmd {

    private static final Logger LOG = LoggerFactory.getLogger(ProcessWrapper.class);

    private Process ffmpeg = null;


    private ProcessKiller ffmpegKiller = null;

    private InputStream inputStream = null;

    private OutputStream outputStream = null;

    private InputStream errorStream = null;


    public void execute(boolean destroyOnRuntimeShutdown, boolean openIOStreams, String ffmpegCmd) throws IOException {
        DefaultFFMPEGLocator defaultFFMPEGLocator = new DefaultFFMPEGLocator();

        StringBuffer cmd = new StringBuffer(defaultFFMPEGLocator.getExecutablePath());
        cmd.append(" ");
        cmd.append(ffmpegCmd);
        String cmdStr = String.format("ffmpegCmd final is :%s", cmd.toString());
        System.out.println(cmdStr);
        LOG.info(cmdStr);

        Runtime runtime = Runtime.getRuntime();
        try {
            ffmpeg = runtime.exec(cmd.toString());

            if (destroyOnRuntimeShutdown) {
                ffmpegKiller = new ProcessKiller(ffmpeg);
                runtime.addShutdownHook(ffmpegKiller);
            }

            if (openIOStreams) {
                inputStream = ffmpeg.getInputStream();
                outputStream = ffmpeg.getOutputStream();
                errorStream = ffmpeg.getErrorStream();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    public InputStream getInputStream() {
        return inputStream;
    }


    public OutputStream getOutputStream() {
        return outputStream;
    }


    public InputStream getErrorStream() {
        return errorStream;
    }

    public void destroy() {
        if (inputStream != null) {
            try {
                inputStream.close();
            } catch (Throwable t) {
                LOG.warn("Error closing input stream", t);
            }
            inputStream = null;
        }

        if (outputStream != null) {
            try {
                outputStream.close();
            } catch (Throwable t) {
                LOG.warn("Error closing output stream", t);
            }
            outputStream = null;
        }

        if (errorStream != null) {
            try {
                errorStream.close();
            } catch (Throwable t) {
                LOG.warn("Error closing error stream", t);
            }
            errorStream = null;
        }

        if (ffmpeg != null) {
            ffmpeg.destroy();
            ffmpeg = null;
        }

        if (ffmpegKiller != null) {
            Runtime runtime = Runtime.getRuntime();
            runtime.removeShutdownHook(ffmpegKiller);
            ffmpegKiller = null;
        }
    }


    public int getProcessExitCode() {
        try {
            ffmpeg.waitFor();
        } catch (InterruptedException ex) {
            LOG.warn("Interrupted during waiting on process, forced shutdown?", ex);
        }
        return ffmpeg.exitValue();
    }

    public void close() {
        destroy();
    }

}

工具类

import com.liuniu.common.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * mp4转换m3u8工具类
 *
 **/
@Slf4j
@Component
public class ConvertM3U8Api {

    public static boolean convertOss(String folderUrl, String fileName) {
        if (!checkFile(folderUrl + fileName)) {
            System.out.println("文件不存在!");
            return false;
        }

        //验证文件后缀
        String suffix = StringUtils.substringAfter(fileName, ".");
        String fileFullName = StringUtils.substringBefore(fileName, ".");
        if (!validFileType(suffix)) {
            return false;
        }

        return processM3U8(folderUrl, fileName, fileFullName);
    }

    /**
     * 验证上传文件后缀
     *
     * @param type 类型
     * @return 是否为mp4
     */
    private static boolean validFileType(String type) {
        return "mp4".equals(type);
    }

    /**
     * 验证是否是文件格式
     *
     * @param path 路径
     * @return 是否存在
     */
    private static boolean checkFile(String path) {
        File file = new File(path);
        return file.isFile();
    }

    //

    /**
     * ffmPeg程序转换m3u8
     * ffmPeg -i vue.mp4 -c:v libx264 -hls_time 20 -hls_list_size 0 -c:a aac -strict -2 -f hls vue.m3u8
     * ffmPeg能解析的格式:(asx,asf,mpg,wmv,3gp,mp4,mov,avi,flv等)
     *
     * @param folderUrl    文件路径
     * @param fileName     名称
     * @param fileFullName 全名
     * @return 是否成功
     */
    private static boolean processM3U8(String folderUrl, String fileName, String fileFullName) {
        //这里就写入执行语句就可以了
        List<String> commend = new ArrayList<>();
        commend.add(" -i");
        commend.add(folderUrl + fileName);
        commend.add("-c:v");
        commend.add("libx264");
        commend.add("-hls_time");
        commend.add("5");
        commend.add("-hls_list_size");
        commend.add("0");
        commend.add("-c:a");
        commend.add("aac");
        commend.add("-strict");
        commend.add("-2");
        commend.add("-f");
        commend.add("hls");
        commend.add(folderUrl + fileFullName + ".m3u8");
        try {
            String commendStr = String.join(" ",commend);
            Integer codeTmp =  cmdExecut(commendStr);
            log.info("***i=【{}】***", codeTmp);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     *
     * @Description: (执行ffmpeg自定义命令)
     * @param: @param cmdStr
     * @param: @return
     * @return: Integer
     * @throws
     */
    public static Integer cmdExecut(String cmdStr) {
        //code=0表示正常
        Integer code  = null;
        FfmpegCmd ffmpegCmd = new FfmpegCmd();
        /**
         * 错误流
         */
        InputStream errorStream = null;
        try {
            //destroyOnRuntimeShutdown表示是否立即关闭Runtime
            //如果ffmpeg命令需要长时间执行,destroyOnRuntimeShutdown = false

            //openIOStreams表示是不是需要打开输入输出流:
            //	       inputStream = processWrapper.getInputStream();
            //	       outputStream = processWrapper.getOutputStream();
            //	       errorStream = processWrapper.getErrorStream();
            ffmpegCmd.execute(false, true, cmdStr);
            errorStream = ffmpegCmd.getErrorStream();

            //打印过程
            int len = 0;
            while ((len=errorStream.read())!=-1){
                System.out.print((char)len);
            }

            //code=0表示正常
            code = ffmpegCmd.getProcessExitCode();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //关闭资源
            ffmpegCmd.close();
        }
        //返回
        return code;
    }

}

方法调用同上

video视频读取

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link
      href="https://unpkg.com/video.js@7.10.2/dist/video-js.min.css"
      rel="stylesheet"
    />
    <title>视频在线播放</title>
    <style>
      html,
      body {
        margin: 0;
        padding: 0;
      }
      .video-container {
        display: flex; /* 1.设置为弹性盒子 */
        align-items: center; /* 2.让子项盒子纵向 居中排列 */
        justify-content: center; /*  3.让子项盒子横向 居中排列 */
        height: 100vh;
        background: rgb(255, 247, 247);
      }
      .video-player {
        width: 50%;
        height: 50%;
      }
    </style>
  </head>

  <body>
    <div class="video-container">
      <!-- 一定要记得 在这里加上类名 video-js  -->
      <video id="videoPlayer" class="video-js video-player">
        <p class="vjs-no-js">
          To view this video please enable JavaScript, and consider upgrading to
          a web browser that
          <a href="https://videojs.com/html5-video-support/" target="_blank">
            supports HTML5 video
          </a>
        </p>
      </video>
    </div>
    <script src="https://unpkg.com/video.js@7.10.2/dist/video.min.js"></script>
    <script>
      const nowPlayVideoUrl = 'http://1257120875.vod2.myqcloud.com/0ef121cdvodtransgzp1257120875/3055695e5285890780828799271/v.f230.m3u8';
      const options = {
        autoplay: true, // 设置自动播放
        muted: true, // 设置了它为true,才可实现自动播放,同时视频也被静音 (Chrome66及以上版本,禁止音视频的自动播放)
        preload: 'auto', // 预加载
        controls: true, // 显示播放的控件
        playbackRates: [0.5, 1, 1.25, 1.5, 2, 3] // 倍速播放
      };
      let player = null;
      function getVideo(nowPlayVideoUrl) {
        player = videojs('#videoPlayer', options, function onPlayerReady() {
          // In this context, `this` is the player that was created by Video.js.
          videojs.log('Your player is ready!', this);
          //关键代码, 动态设置src,才可实现换台操作
          this.src([
            {
              src: nowPlayVideoUrl,
              type: 'application/x-mpegURL' // 告诉videojs,这是一个hls流
            }
          ]);
          this.play();
        });
      }
      getVideo(nowPlayVideoUrl);
      window.addEventListener('unload', function () {
        player.dispose(); // Removing Players,该方法会重置videojs的内部状态并移除dom
      });
    </script>
  </body>
</html>


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

懒人w

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值