当前位置: 首页 > article >正文

Express + MongoDB 实现 VOD 视频点播

一、安装依赖

npm install express mongoose multer ffmpeg-static fluent-ffmpeg
  • express:用于构建 Web 服务器。
  • mongoose:用于与 MongoDB 进行交互。
  • multer:用于处理文件上传。
  • ffmpeg-static:提供 FFmpeg 的静态二进制文件。
  • fluent-ffmpeg:用于视频处理。

二、数据库连接与模型定义

创建 models 目录并在其中创建 Video.js 文件

// models/Video.js
const mongoose = require("mongoose");
const videoSchema = new mongoose.Schema({
  title: String,
  description: String,
  filePath: String,
  thumbnailPath: String,
});
module.exports = mongoose.model("Video", videoSchema);

在项目根目录创建 app.js 文件并连接到 MongoDB

// app.js
const express = require("express");
const mongoose = require("mongoose");
const app = express();
mongoose.connect("mongodb://localhost:27017/vod_db", {
  useNewUrlParser: true,
  useUnifiedTopology: true,
});
const db = mongoose.connection;
db.on("error", console.error.bind(console, "MongoDB 连接错误:"));
db.once("open", () => {
  console.log("MongoDB 连接成功");
});
const Video = require("./models/Video");
// 其他代码...

三、视频上传功能

使用 multer 处理视频上传,并使用 fluent-ffmpeg 生成视频缩略图:

// app.js
const multer = require("multer");
const ffmpegPath = require("ffmpeg-static");
const ffmpeg = require("fluent-ffmpeg");
const path = require("path");
const fs = require("fs");
// 配置 multer
const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    const uploadDir = "uploads/videos";
    if (!fs.existsSync(uploadDir)) {
      fs.mkdirSync(uploadDir, { recursive: true });
    }
    cb(null, uploadDir);
  },
  filename: function (req, file, cb) {
    cb(null, Date.now() + path.extname(file.originalname));
  },
});
const upload = multer({ storage: storage });
// 处理视频上传
app.post("/upload", upload.single("video"), async (req, res) => {
  try {
    const { title, description } = req.body;
    const filePath = req.file.path;
    const thumbnailPath = `uploads/thumbnails/${Date.now()}.jpg`;
    // 生成缩略图
    await new Promise((resolve, reject) => {
      ffmpeg()
        .setFfmpegPath(ffmpegPath)
        .input(filePath)
        .on("end", resolve)
        .on("error", reject)
        .screenshots({
          count: 1,
          folder: "uploads/thumbnails",
          size: "320x240",
          filename: `${Date.now()}.jpg`,
        });
    });
    // 保存视频信息到数据库
    const newVideo = new Video({
      title,
      description,
      filePath,
      thumbnailPath,
    });
    await newVideo.save();
    res.status(200).json({ message: "视频上传成功", video: newVideo });
  } catch (error) {
    res.status(500).json({ message: "视频上传失败", error: error.message });
  }
});

四、视频列表展示

创建一个路由来获取视频列表

// app.js
app.get("/videos", async (req, res) => {
  try {
    const videos = await Video.find();
    res.status(200).json(videos);
  } catch (error) {
    res.status(500).json({ message: "获取视频列表失败", error: error.message });
  }
});

五、视频播放

创建一个路由来流式传输视频文件

// app.js
app.get("/videos/:id", async (req, res) => {
  try {
    const video = await Video.findById(req.params.id);
    if (!video) {
      return res.status(404).json({ message: "视频未找到" });
    }
    const filePath = video.filePath;
    const stat = fs.statSync(filePath);
    const fileSize = stat.size;
    const range = req.headers.range;
    if (range) {
      const parts = range.replace(/bytes=/, "").split("-");
      const start = parseInt(parts[0], 10);
      const end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1;

      const chunksize = end - start + 1;
      const file = fs.createReadStream(filePath, { start, end });
      const head = {
        "Content-Range": `bytes ${start}-${end}/${fileSize}`,
        "Accept-Ranges": "bytes",
        "Content-Length": chunksize,
        "Content-Type": "video/mp4",
      };
      res.writeHead(206, head);
      file.pipe(res);
    } else {
      const head = {
        "Content-Length": fileSize,
        "Content-Type": "video/mp4",
      };
      res.writeHead(200, head);
      fs.createReadStream(filePath).pipe(res);
    }
  } catch (error) {
    res.status(500).json({ message: "视频播放失败", error: error.message });
  }
});

http://www.kler.cn/a/567008.html

相关文章:

  • QT:Echart-折线图
  • JeeWMS cgReportController.do 多个参数SQL注入漏洞(CVE-2024-57760)
  • Jeecg-Boot 开放接口开发实战:在 Jeecg-Boot 的jeecg-system-biz中添加一个controller 实现免鉴权数据接口
  • AcWing 农夫约翰的奶酪块
  • DeepSeek引爆AI浪潮:B站如何成为科技普惠的“新课堂”?
  • Linux Mem -- 关于AArch64 MTE功能的疑问
  • 大数据与金融科技:革新金融行业的动力引擎
  • CSS Selectors
  • unity学习56:旧版legacy和新版TMP文本输入框 InputField学习
  • STM32G431RBT6——(1)芯片命名规则
  • 每天一个Flutter开发小项目 (8) : 掌握Flutter网络请求 - 构建每日名言应用
  • Kafka重复消费问题和解决方式
  • Redis大key
  • 基于JAVA+Spring+mysql_快递管理系统源码+设计文档
  • C++20 Lambda表达式新特性:包扩展与初始化捕获的强强联合
  • WatchDog 看门狗
  • 22-接雨水
  • 什么是蓝绿发布?
  • vulfocus靶场漏洞学习——wordpress 垂直越权 (CVE=2021-21389)
  • DeepSeek 1.5B蒸馏模型的J6部署(Llama方式)