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 });
  }
});
相关推荐
想要入门的程序猿37 分钟前
Qt写入excel
数据库·qt·excel
Q_970956391 小时前
java+vue+SpringBoo校园失物招领网站(程序+数据库+报告+部署教程+答辩指导)
java·数据库·vue.js
Wyc724091 小时前
Maven
java·数据库·maven
程序猿小D1 小时前
[附源码+数据库+毕业论文]基于Spring+MyBatis+MySQL+Maven+jsp实现的电影小说网站管理系统,推荐!
java·数据库·mysql·spring·毕业设计·ssm框架·电影小说网站
羊小猪~~1 小时前
数据库学习笔记(十七)--触发器的使用
数据库·人工智能·后端·sql·深度学习·mysql·考研
背太阳的牧羊人2 小时前
Neo4j 的向量搜索(Neo4jVector)和常见的向量数据库(比如 Milvus、Qdrant)之间的区别与联系
数据库·neo4j·milvus
liulun2 小时前
在浏览器中使用SQLite(官方sqlite3.wasm)
数据库·sqlite·wasm
IT项目管理3 小时前
达梦数据库DMHS介绍及安装部署
linux·数据库
你都会上树?3 小时前
MySQL MVCC 详解
数据库·mysql
大春儿的试验田4 小时前
高并发收藏功能设计:Redis异步同步与定时补偿机制详解
java·数据库·redis·学习·缓存