大文件分片案例html + nodejs + 视频上传案例

上传页面.html

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>流媒体视频播放</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { padding: 30px; font-family: "Microsoft Yahei", Arial, sans-serif; }
    .input-group { margin: 20px 0; display: flex; gap: 10px; align-items: center; }
    input { flex: 1; padding: 10px; border: 1px solid #ccc; border-radius: 4px; font-size: 14px; }
    button { padding: 10px 20px; background: #2196F3; color: #fff; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; }
    video { width: 100%; max-height: 70vh; background: #000; border-radius: 4px; }
    .log { margin-top: 20px; padding: 10px; background: #f5f5f5; border-radius: 4px; font-size: 12px; color: #666; }
  </style>
</head>
<body>
  <h1>流媒体视频播放</h1>
  <div class="input-group">
    <input type="text" id="videoName" value="微信视频2026-04-09_102453_303.mp4">
    <button onclick="playVideo()">播放视频</button>
  </div>
  <video id="videoPlayer" controls></video>
  <div class="log" id="log"></div>

  <script>
    const logEl = document.getElementById('log');
    const videoPlayer = document.getElementById('videoPlayer');

    // 日志工具
    function log(msg) {
      logEl.innerHTML += `<p>${new Date().toLocaleTimeString()} - ${msg}</p>`;
      console.log(msg);
    }

    async function playVideo() {
      const videoName = document.getElementById('videoName').value.trim();

      if (!videoName) {
        alert('请输入视频文件名!');
        return;
      }

      // 🔴 核心修复:编码中文文件名,解决乱码404
      const encodedName = encodeURIComponent(videoName);
      const videoUrl = `http://localhost:3000/video/${encodedName}`;

      log(`请求视频地址:${videoUrl}`);

      // 先验证接口是否正常
      try {
        const res = await fetch(videoUrl, { method: 'HEAD' });
        if (!res.ok) {
          log(`接口请求失败,状态码:${res.status}`);
          alert(`播放失败!接口返回${res.status},请检查后端服务和文件名`);
          return;
        }
        log(`接口验证成功,状态码:${res.status}`);
      } catch (err) {
        log(`请求错误:${err.message}`);
        alert('网络错误,请检查后端服务是否开启');
        return;
      }

      // 给播放器赋值地址
      videoPlayer.src = videoUrl;

      // 监听加载成功
      videoPlayer.oncanplay = () => {
        log('视频加载成功,开始播放');
        videoPlayer.play();
      };

      // 监听加载错误
      videoPlayer.onerror = (err) => {
        log(`播放错误:${err.message}`);
        alert('播放失败!请检查:\n1. 后端服务是否开启\n2. 文件名是否正确\n3. 视频是否上传成功');
      };
    }
  </script>
</body>
</html>

播放页面.html

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>流媒体视频播放</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { padding: 30px; font-family: "Microsoft Yahei", Arial, sans-serif; }
    .input-group { margin: 20px 0; display: flex; gap: 10px; align-items: center; }
    input { flex: 1; padding: 10px; border: 1px solid #ccc; border-radius: 4px; font-size: 14px; }
    button { padding: 10px 20px; background: #2196F3; color: #fff; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; }
    video { width: 100%; max-height: 70vh; background: #000; border-radius: 4px; }
    .log { margin-top: 20px; padding: 10px; background: #f5f5f5; border-radius: 4px; font-size: 12px; color: #666; }
  </style>
</head>
<body>
  <h1>流媒体视频播放</h1>
  <div class="input-group">
    <input type="text" id="videoName" value="微信视频2026-04-09_102453_303.mp4">
    <button onclick="playVideo()">播放视频</button>
  </div>
  <video id="videoPlayer" controls></video>
  <div class="log" id="log"></div>

  <script>
    const logEl = document.getElementById('log');
    const videoPlayer = document.getElementById('videoPlayer');

    // 日志工具
    function log(msg) {
      logEl.innerHTML += `<p>${new Date().toLocaleTimeString()} - ${msg}</p>`;
      console.log(msg);
    }

    async function playVideo() {
      const videoName = document.getElementById('videoName').value.trim();

      if (!videoName) {
        alert('请输入视频文件名!');
        return;
      }

      // 🔴 核心修复:编码中文文件名,解决乱码404
      const encodedName = encodeURIComponent(videoName);
      const videoUrl = `http://localhost:3000/video/${encodedName}`;

      log(`请求视频地址:${videoUrl}`);

      // 先验证接口是否正常
      try {
        const res = await fetch(videoUrl, { method: 'HEAD' });
        if (!res.ok) {
          log(`接口请求失败,状态码:${res.status}`);
          alert(`播放失败!接口返回${res.status},请检查后端服务和文件名`);
          return;
        }
        log(`接口验证成功,状态码:${res.status}`);
      } catch (err) {
        log(`请求错误:${err.message}`);
        alert('网络错误,请检查后端服务是否开启');
        return;
      }

      // 给播放器赋值地址
      videoPlayer.src = videoUrl;

      // 监听加载成功
      videoPlayer.oncanplay = () => {
        log('视频加载成功,开始播放');
        videoPlayer.play();
      };

      // 监听加载错误
      videoPlayer.onerror = (err) => {
        log(`播放错误:${err.message}`);
        alert('播放失败!请检查:\n1. 后端服务是否开启\n2. 文件名是否正确\n3. 视频是否上传成功');
      };
    }
  </script>
</body>
</html>

package.json

javascript 复制代码
{
  "name": "liumeit",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": "",
  "dependencies": {
    "cors": "^2.8.6",
    "express": "^5.2.1",
    "multer": "^2.1.1"
  }
}

server.js

javascript 复制代码
const express = require('express');
const cors = require('cors');
const fs = require('fs');
const path = require('path');
const multer = require('multer');

const app = express();
app.use(cors());
app.use(express.json());

// 目录
const UPLOAD_DIR = path.join(__dirname, 'uploads');
const CHUNK_DIR = path.join(__dirname, 'chunks');
if (!fs.existsSync(UPLOAD_DIR)) fs.mkdirSync(UPLOAD_DIR);
if (!fs.existsSync(CHUNK_DIR)) fs.mkdirSync(CHUNK_DIR);

// 最简单的存储,不会出错
const upload = multer({ dest: CHUNK_DIR });

// 上传分片
app.post('/upload/chunk', upload.single('chunk'), (req, res) => {
  const { filename, index } = req.body;

  // 🔴 关键:直接改名!彻底解决 multer 解析问题
  const tempPath = req.file.path;
  const targetPath = path.join(CHUNK_DIR, `${filename}-${index}`);
  fs.renameSync(tempPath, targetPath);

  console.log(`✅ 分片 ${index} 保存成功:${filename}-${index}`);
  res.send('ok');
});

// 合并分片
app.post('/upload/merge', (req, res) => {
  const { filename, totalChunks } = req.body;
  const final = fs.createWriteStream(path.join(UPLOAD_DIR, filename));

  for (let i = 0; i < totalChunks; i++) {
    const chunkPath = path.join(CHUNK_DIR, `${filename}-${i}`);
    
    if (!fs.existsSync(chunkPath)) {
      console.log(`❌ 分片不存在:${chunkPath}`);
      return res.status(500).send('缺失分片');
    }

    final.write(fs.readFileSync(chunkPath));
    fs.unlinkSync(chunkPath);
  }

  final.end();
  console.log('✅ 合并完成!');
  res.send('ok');
});

// 3. 视频播放接口(终极修复版,路径100%正确)
app.get('/video/:filename', (req, res) => {
  try {
    const filename = req.params.filename;
    // 🔴 核心修复:用 path.join 绝对路径,彻底解决路径错误
    const filePath = path.join(__dirname, 'uploads', filename);

    console.log(`🔍 播放请求:${filename},完整路径:${filePath}`);
    console.log(`✅ 文件是否存在:${fs.existsSync(filePath)}`);

    if (!fs.existsSync(filePath)) {
      return res.status(404).json({ error: '视频不存在', path: filePath });
    }

    const stat = fs.statSync(filePath);
    const fileSize = stat.size;
    const range = req.headers.range;

    // 统一设置CORS头
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.setHeader('Accept-Ranges', 'bytes');
    res.setHeader('Content-Type', 'video/mp4');

    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 readStream = fs.createReadStream(filePath, { start, end });
      res.setHeader('Content-Range', `bytes ${start}-${end}/${fileSize}`);
      res.setHeader('Content-Length', chunkSize);
      res.writeHead(206);
      readStream.pipe(res);
    } else {
      res.setHeader('Content-Length', fileSize);
      res.writeHead(200);
      fs.createReadStream(filePath).pipe(res);
    }
  } catch (err) {
    console.error('❌ 播放接口错误:', err);
    res.status(500).json({ error: err.message });
  }
});

app.listen(3000, () => {
  console.log('🚀 服务启动:http://localhost:3000');
});
相关推荐
kyriewen1 小时前
代码写成一锅粥?3个设计模式让你的项目“起死回生”
前端·javascript·设计模式
不会敲代码11 小时前
从零搭建 AI 日记助手:用 Milvus 向量数据库实现语义搜索
javascript·openai
Dxy12393102162 小时前
SVG画的曲线如何高亮显示
html
threelab2 小时前
Three.js UV 图像变换效果 | 三维可视化 / AI 提示词
javascript·人工智能·uv
竹林8184 小时前
用Viem替代ethers.js:从一次签名失败到完整迁移的实战记录
前端·javascript
不可能的是5 小时前
Claude Code 子 Agent 机制全解:怎么跑起来、怎么被管理、怎么互不干扰
javascript
HSunR6 小时前
dify 搭建ai作业批改流
开发语言·前端·javascript
代码不加糖6 小时前
2026 跨境电商独立站实战:从 0 到 1 搭建高转化 SaaS 商城(附源码)
开发语言·前端·javascript
用户617517157018 小时前
关于普通函数和箭头函数的this
javascript
RPGMZ8 小时前
RPGMakerMZ 地图存档点制作 标题继续游戏直接读取存档
开发语言·javascript·游戏·游戏引擎·rpgmz·rpgmakermz