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 });
  }
});
相关推荐
r i c k1 小时前
数据库系统学习笔记
数据库·笔记·学习
野犬寒鸦1 小时前
从零起步学习JVM || 第一章:类加载器与双亲委派机制模型详解
java·jvm·数据库·后端·学习
IvorySQL2 小时前
PostgreSQL 分区表的 ALTER TABLE 语句执行机制解析
数据库·postgresql·开源
·云扬·2 小时前
MySQL 8.0 Redo Log 归档与禁用实战指南
android·数据库·mysql
IT邦德2 小时前
Oracle 26ai DataGuard 搭建(RAC到单机)
数据库·oracle
惊讶的猫2 小时前
redis分片集群
数据库·redis·缓存·分片集群·海量数据存储·高并发写
不爱缺氧i3 小时前
完全卸载MariaDB
数据库·mariadb
纤纡.3 小时前
Linux中SQL 从基础到进阶:五大分类详解与表结构操作(ALTER/DROP)全攻略
linux·数据库·sql
jiunian_cn3 小时前
【Redis】渐进式遍历
数据库·redis·缓存
橙露3 小时前
Spring Boot 核心原理:自动配置机制与自定义 Starter 开发
java·数据库·spring boot