Express + MongoDB 实现 VOD 视频点播

一、安装依赖

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

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

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

javascript 复制代码
// 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

javascript 复制代码
// 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 生成视频缩略图:

javascript 复制代码
// 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 });
  }
});

四、视频列表展示

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

javascript 复制代码
// 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 });
  }
});

五、视频播放

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

javascript 复制代码
// 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 });
  }
});
相关推荐
派可数据BI可视化2 小时前
商业智能BI 浅谈数据孤岛和数据分析的发展
大数据·数据库·数据仓库·信息可视化·数据挖掘·数据分析
yong99902 小时前
C#驱动斑马打印机实现包装自动打印
java·数据库·c#
野犬寒鸦3 小时前
从零起步学习MySQL || 第五章:select语句的执行过程是怎么样的?(结合源码深度解析)
java·服务器·数据库·后端·mysql·adb
QT 小鲜肉3 小时前
【个人成长笔记】Qt 中 SkipEmptyParts 编译错误解决方案及版本兼容性指南
数据库·c++·笔记·qt·学习·学习方法
我是苏苏3 小时前
C#高级:数据库中使用SQL作分组处理4(LAG() 偏移函数)
数据库
wudl55664 小时前
股票300394(天孚通信)2025年4月20日
数据库
IvorySQL4 小时前
PostgreSQL 18 中国贡献者经验分享:开源参与的四点建议
数据库·postgresql·开源
曾凡宇先生4 小时前
openEuler安装jdk,nginx,redis
linux·开发语言·数据库·openeuler
点灯小铭4 小时前
基于单片机的四沟道步进电机玉米补种机设计与实现
数据库·单片机·嵌入式硬件·毕业设计·课程设计·期末大作业
清风6666664 小时前
基于单片机的双机串口通信与数字串存储系统设计
数据库·单片机·mongodb·毕业设计·课程设计·期末大作业