用koa2 和 html javascript做了一个视频列表功能

服务器部分

{
  "name": "koa-vite-video",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node app.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@koa/cors": "^5.0.0",
    "ffmpeg": "^0.0.4",
    "fluent-ffmpeg": "^2.1.3",
    "koa": "^2.15.3",
    "koa-bodyparser": "^4.4.1",
    "koa-router": "^12.0.1",
    "koa-static": "^5.0.0",
    "mysql": "^2.18.1",
    "nodemon": "^3.1.4"
  },
  "devDependencies": {
    "vite": "^5.3.5"
  }
}

其中nodejs中使用 fluent-ffmpeg 要和ffmpeg软件一起使用。
主要功能就是在网页中能够看到视频图片的缩略图。
下载地址 https://ffmpeg.org/

const Koa = require("koa");
const Router = require("koa-router");
const cors = require("@koa/cors");
const fs = require("fs");
const path = require("path");
const ffmpeg = require("fluent-ffmpeg");

// Set paths to ffmpeg and ffprobe executables
const ffmpegPath =
  "D:\\Users\\lhl\\Desktop\\ffmpeg-master-latest-win64-gpl\\bin\\ffmpeg.exe";
const ffprobePath =
  "D:\\Users\\lhl\\Desktop\\ffmpeg-master-latest-win64-gpl\\bin\\ffprobe.exe";

ffmpeg.setFfmpegPath(ffmpegPath);
ffmpeg.setFfprobePath(ffprobePath);

const app = new Koa();
const router = new Router();

app.use(cors());

const staticPath = path.join(__dirname, "public");

router.get("/files", async (ctx) => {
  const files = fs.readdirSync(staticPath).map((file) => ({ name: file }));
  ctx.body = files;
});

router.get("/thumbnail/:fileName", async (ctx) => {
  const fileName = ctx.params.fileName;
  const videoPath = path.resolve(staticPath, fileName);
  const thumbnailPath = path.resolve(staticPath, `${fileName}.png`);

  if (!fs.existsSync(videoPath)) {
    ctx.status = 404;
    ctx.body = "Video not found";
    return;
  }

  // If thumbnail doesn't exist, generate it
  if (!fs.existsSync(thumbnailPath)) {
    try {
      await new Promise((resolve, reject) => {
        ffmpeg(videoPath)
          .on("end", resolve)
          .on("error", (err) => {
            console.error(
              `Error generating thumbnail for ${fileName}: ${err.message}`
            );
            reject(err);
          })
          .screenshots({
            timestamps: [1], // Capture at 1 second instead of 0
            filename: `${fileName}.png`,
            folder: staticPath,
            size: "200x150",
          });
      });
    } catch (err) {
      ctx.status = 500;
      ctx.body = "Error generating thumbnail";
      return;
    }
  }

  ctx.type = "image/png";
  ctx.body = fs.createReadStream(thumbnailPath);
});

router.get("/video/:fileName", async (ctx) => {
  const range = ctx.headers.range;
  const fileName = ctx.params.fileName;

  if (!range) {
    ctx.status = 400;
    ctx.body = "Requires Range header";
    return;
  }

  const videoPath = path.resolve(staticPath, fileName);
  if (!fs.existsSync(videoPath)) {
    ctx.status = 404;
    ctx.body = "Video not found";
    return;
  }

  const videoSize = fs.statSync(videoPath).size;
  const CHUNK_SIZE = 1024 * 1024;

  const startMatch = range.match(/bytes=(\d+)-/);
  const start = startMatch ? Number(startMatch[1]) : 0;

  if (start >= videoSize) {
    ctx.status = 416;
    ctx.set("Content-Range", `bytes */${videoSize}`);
    return;
  }

  const end = Math.min(start + CHUNK_SIZE - 1, videoSize - 1);
  const contentLength = end - start + 1;

  const headers = {
    "Content-Range": `bytes ${start}-${end}/${videoSize}`,
    "Accept-Ranges": "bytes",
    "Content-Length": contentLength,
    "Content-Type": "video/mp4",
  };

  ctx.set(headers);
  ctx.status = 206;

  const videoStream = fs.createReadStream(videoPath, { start, end });
  ctx.body = videoStream;
});

app.use(router.routes());
app.use(router.allowedMethods());

app.listen(3000, () => {
  console.log("Server is running on http://localhost:3000");
});

前端部分

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Video Stream</title>
  <style>
    body {
      display: flex;
      justify-content: center;
      align-items: center;
      flex-direction: column;
      height: 100vh;
      margin: 0;
    }

    .grid-container {
      display: grid;
      grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
      gap: 10px;
      padding: 10px;
      width: 100%;
      max-width: 1200px;
      overflow: auto;
    }

    .grid-item {
      display: flex;
      justify-content: center;
      align-items: center;
      border: 1px solid #ddd;
      padding: 10px;
      cursor: pointer;
    }

    .grid-item video {
      width: 100%;
      height: auto;
    }

    .full-screen-video {
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      background: rgba(0, 0, 0, 0.8);
      display: flex;
      justify-content: center;
      align-items: center;
      z-index: 1000;
    }

    .full-screen-video video {
      width: 80%;
      height: auto;
    }

    .full-screen-video .close-btn {
      position: absolute;
      top: 10px;
      right: 10px;
      background: rgba(255, 255, 255, 0.7);
      border: none;
      border-radius: 5px;
      padding: 5px 10px;
      cursor: pointer;
      font-size: 16px;
    }
  </style>
</head>
<body>
  <div class="grid-container" id="fileList"></div>

  <script>
    const fileList = document.getElementById("fileList");
 
    // 获取文件列表并展示
    function loadFiles() {
      fetch("http://localhost:3000/files")
        .then((response) => response.json())
        .then((files) => {
          fileList.innerHTML = "";
          files.forEach((file) => {
            const div = document.createElement("div");
            div.className = "grid-item";
 
            const video = document.createElement("video");
            video.dataset.src = `http://localhost:3000/video/${file.name}`; // 使用 data-src 来存储真实的视频 URL
            video.controls = true;
            video.muted = true;
            video.width = 300; // 控制缩略图大小
 
            // IntersectionObserver 用于懒加载
            const observer = new IntersectionObserver((entries) => {
              entries.forEach((entry) => {
                if (entry.isIntersecting) {
                  video.src = video.dataset.src; // 进入视口时加载视频
                  observer.unobserve(entry.target); // 停止观察
                }
              });
            }, {
              rootMargin: '0px',
              threshold: 0.1
            });
 
            observer.observe(video); // 开始观察视频元素
 
            div.appendChild(video);
 
            div.addEventListener("click", () => openFullScreenVideo(file.name));
            fileList.appendChild(div);
          });
        })
        .catch((error) => console.error("Error loading files:", error));
    }
 
    // 打开全屏视频播放
    function openFullScreenVideo(fileName) {
      const fullScreenContainer = document.createElement("div");
      fullScreenContainer.className = "full-screen-video";
 
      const video = document.createElement("video");
      video.src = `http://localhost:3000/video/${fileName}`;
      video.controls = true;
      video.autoplay = true;
      fullScreenContainer.appendChild(video);
 
      const closeBtn = document.createElement("button");
      closeBtn.textContent = "Close";
      closeBtn.className = "close-btn";
      closeBtn.addEventListener("click", () => {
        document.body.removeChild(fullScreenContainer);
      });
      fullScreenContainer.appendChild(closeBtn);
 
      document.body.appendChild(fullScreenContainer);
    }
 
    // 初始化文件列表
    loadFiles();
  </script>
</body>
</html>

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,下面我来给你简单介绍一下搭建Koa服务器并实现注册功能的步骤。 1. 搭建Koa服务器 首先,你需要安装Node.js和npm包管理工具。然后,通过npm安装koakoa-router模块。 ``` npm install koa koa-router ``` 接着,创建一个koa应用。在项目根目录下创建一个app.js文件,输入以下代码: ``` const Koa = require('koa'); const Router = require('koa-router'); const app = new Koa(); const router = new Router(); router.get('/', async (ctx, next) => { ctx.body = 'Hello World'; }); app.use(router.routes()).use(router.allowedMethods()); app.listen(3000, () => { console.log('Server is running at http://localhost:3000'); }); ``` 上述代码创建了一个koa应用,在浏览器中访问http://localhost:3000,会显示“Hello World”。 2. 实现注册功能 接下来,我们来实现注册功能。首先,我们需要安装koa-bodyparser模块,这个模块可以将post请求的参数解析到ctx.request.body中。 ``` npm install koa-bodyparser ``` 在app.js文件中,引入koa-bodyparser模块,并添加一个post路由: ``` const Koa = require('koa'); const Router = require('koa-router'); const bodyParser = require('koa-bodyparser'); const app = new Koa(); const router = new Router(); router.get('/', async (ctx, next) => { ctx.body = 'Hello World'; }); router.post('/register', async (ctx, next) => { const { username, password } = ctx.request.body; // 这里可以将用户名和密码存储到数据库中 ctx.body = { code: 0, message: '注册成功' }; }); app.use(bodyParser()); app.use(router.routes()).use(router.allowedMethods()); app.listen(3000, () => { console.log('Server is running at http://localhost:3000'); }); ``` 上述代码添加了一个post路由,当访问http://localhost:3000/register时,会将post请求的参数解析到ctx.request.body中,并将用户名和密码存储到数据库中(这里省略了数据库操作),最后返回一个json数据表示注册成功。 好了,到这里为止,我们就成功地搭建了一个Koa服务器并实现了注册功能
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lionliu0519

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值