node+ffmpeg实现视频切片

218 阅读2分钟

node后端使用express搭建两个接口

一个是获取视频流,一个是上传视频并进行切片

实现的效果: image.png

上传视频接口

import express from "express";
import fs, { existsSync } from "fs";
import multer from "multer";
import { nanoid } from "nanoid";
import ffmpeg from "fluent-ffmpeg";
import path from "path";
import cors from 'cors';

// 创建一个服务器,监听 3300 端口
const server = express();
server.listen(3300);
console.log("Server started.");
server.use(cors()) 
/*
 *  定义一个处理上传表单文件
 */
const copeUpload = multer({
  dest: "uploads-temp/",
  filename: function (req, file, cb) {
    const uniqueSuffix = nanoid();
    cb(null, file.fieldname + "-" + uniqueSuffix);
  },
}).single("video");

// 定义上传的 API 路由,并且使用上面的中间件
server.post("/upload", copeUpload, function (req, res, next) {
  const tempFilePath = path.resolve(req.file.path); // 视频上传后的临时文件位置
  const videoId = nanoid(); // 为视频资源创建唯一 ID
  const storageDirectory = path.resolve("storage", videoId); //为视频创建储存位置
  fs.mkdirSync(storageDirectory);

  ffmpeg(tempFilePath)
    .videoCodec("libx264")
    .audioCodec("aac")
    .addOption("-hls_time", 10)
    .addOption("-hls_segment_type", "mpegts")
    .addOption("-hls_list_size", 0)
    .format("hls")
    .addOption("-max_muxing_queue_size", 1024)
    .output(`${storageDirectory}/video.m3u8`)
    .on("start", function () {
      console.log("开始为视频切片");
    })
    .on("end", function () {
      fs.rmSync(tempFilePath); // 删除上传的临时文件
      console.log("切片完成");
    })
    .on("error", function (err) {
      fs.rmSync(tempFilePath); // 删除上传的临时文件
      console.error("切片失败:", err);
    })
    .run();

  res.json(`http://localhost:3300/play/${videoId}/video.m3u8`); //返回播放地址
});

获取播放接口

server.get("/play/:videoId/:filename", (req, res) => {
  const videoId = req.params["videoId"]; // 从 URL 中获取视频 ID
  const storageDirectory = path.resolve("storage", videoId); // 视频切片和清单的储存位置
  if (!existsSync(storageDirectory)) {
    // 若目标视频记录不存在则返回 404
    res.status(404).send();
  }
  const filename = req.params["filename"]; //请求的文件
  const filepath = path.join(storageDirectory, filename);
  if (!existsSync(filepath)) {
    // 若目标文件不存在则返回 404
    res.status(404).send();
  }
  const data = fs.readFileSync(filepath); // 读取目标文件
  res.send(data);
});

前端使用视频播放器

<label for="m3u8-url">请输入视频的m3u8地址:</label>
    <input type="text" id="m3u8-url" name="m3u8-url" placeholder="例如:https://example.com/video.m3u8" />
<!-- 视频播放器 -->
    <video id="my-video" class="video-js" controls preload="auto" width="640" height="360" data-setup="{}">
      <source src="" type="application/x-mpegURL" />
    </video>

写一个方法进行视频流播放

function playVideo () {
        // 拿到输入框的m3u8地址
        var m3u8Url = document.getElementById("m3u8-url").value;
        console.log("🚀 ~ playVideo ~ m3u8Url:", m3u8Url)
        // 设置视频源为输入的m3u8地址
        var videoPlayer = videojs("my-video");
        videoPlayer.src({
          src: m3u8Url,
          type: "application/x-mpegURL",
        });
        // 播放视频
        videoPlayer.play();
      }

使用的是video.js进行播放

 <script src="https://vjs.zencdn.net/8.10.0/video.min.js"></script>

前端上传视频

<form id="uploadForm" method="POST" enctype="multipart/form-data">
      <input type="file" name="video" accept="video/*" />
      <button type="submit">上传</button>
    </form>
    <div id="response"></div>
    <script>
      // 获取表单元素
      const form = document.getElementById("uploadForm");
      // 监听表单提交事件
      form.addEventListener("submit", function (event) {
        event.preventDefault(); // 阻止默认提交行为
        // 创建 FormData 对象,用于将表单数据发送到服务器
        const formData = new FormData(form);
        // 发送表单数据到服务器
        fetch("http://localhost:3300/upload", {
          method: "POST",
          body: formData,
        })
          .then((response) => response.text()) // 将响应转换为文本格式
          .then((data) => {
            // 将服务器返回的文本数据显示在页面上
            document.getElementById("response").innerText =
              "上传成功,视频地址是:" + data;
          })
          .catch((error) => {
            console.error("请求错误:", error);
          });
      });
    </script>