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 });
  }
});
相关推荐
王小小鸭7 分钟前
【Oracle APEX开发小技巧12】
数据库·oracle
噼里啪啦啦.12 分钟前
Spring事务和事务传播机制
数据库·sql·spring
搬码红绿灯19 分钟前
MySQL主从复制深度解析:原理、架构与实战部署指南
数据库·mysql·架构
呼拉拉呼拉20 分钟前
Redis高可用架构
数据库·redis·架构·高可用架构
却尘20 分钟前
当全世界都在用 Rust 重写一切时,Prisma 却选择了反方向
前端·数据库·orm
藥瓿锻30 分钟前
2024 CKA题库+详尽解析| 15、备份还原Etcd
linux·运维·数据库·docker·容器·kubernetes·cka
远方160943 分钟前
16-Oracle 23 ai-JSON-Relational Duality-知识准备
数据库·oracle·json
Wooden-Flute1 小时前
七、数据库的完整性
数据库·oracle
珹洺1 小时前
数据库系统概论(十七)超详细讲解数据库规范化与五大范式(从函数依赖到多值依赖,再到五大范式,附带例题,表格,知识图谱对比带你一步步掌握)
java·数据库·sql·安全·oracle
TDengine (老段)1 小时前
TDengine 开发指南——无模式写入
大数据·数据库·物联网·时序数据库·iot·tdengine·涛思数据