当前位置: 首页 > news >正文

用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 itif (!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 0filename: `${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 来存储真实的视频 URLvideo.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>

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Java设计模式(适配器模式)
  • WPF学习(7)- Control基类+ContentControl类(内容控件)+ButtonBase基类
  • docker搭建frp内网穿透
  • 微信小程序--详情实现日期选择期(年月日)
  • Go 语言中创建方法
  • 设计模式18-中介者模式
  • 速记Java八股文——Redis 篇
  • 人工智能|人工智能教育的发展现状及趋势
  • 乐凡三防平板:以科技之力,推动工作智能化升级
  • Java入门、进阶、强化、扩展、知识体系完善等知识点学习、性能优化、源码分析专栏分享
  • Java stream流支持多字段排序
  • 【力扣】746.使用最小花费爬楼梯
  • Mysql 脚本转换为drawio ER 脚本
  • 使用LaTeX分章节撰写文档
  • LVS集群实现四层负载均衡详解(以nat,dr模式为例)
  • 77. Combinations
  • dva中组件的懒加载
  • java8 Stream Pipelines 浅析
  • JavaScript服务器推送技术之 WebSocket
  • Java读取Properties文件的六种方法
  • Median of Two Sorted Arrays
  • MySQL QA
  • WordPress 获取当前文章下的所有附件/获取指定ID文章的附件(图片、文件、视频)...
  • 欢迎参加第二届中国游戏开发者大会
  • 手机app有了短信验证码还有没必要有图片验证码?
  • 手写双向链表LinkedList的几个常用功能
  • 突破自己的技术思维
  • 掌握面试——弹出框的实现(一道题中包含布局/js设计模式)
  • 终端用户监控:真实用户监控还是模拟监控?
  • python最赚钱的4个方向,你最心动的是哪个?
  • ​如何在iOS手机上查看应用日志
  • # 详解 JS 中的事件循环、宏/微任务、Primise对象、定时器函数,以及其在工作中的应用和注意事项
  • #70结构体案例1(导师,学生,成绩)
  • #在 README.md 中生成项目目录结构
  • (3)nginx 配置(nginx.conf)
  • (BAT向)Java岗常问高频面试汇总:MyBatis 微服务 Spring 分布式 MySQL等(1)
  • (C语言)二分查找 超详细
  • (C语言)字符分类函数
  • (Redis使用系列) Springboot 在redis中使用BloomFilter布隆过滤器机制 六
  • (八)Spring源码解析:Spring MVC
  • (动手学习深度学习)第13章 计算机视觉---微调
  • (附表设计)不是我吹!超级全面的权限系统设计方案面世了
  • (附源码)spring boot网络空间安全实验教学示范中心网站 毕业设计 111454
  • (附源码)计算机毕业设计ssm基于Internet快递柜管理系统
  • (附源码)计算机毕业设计SSM疫情下的学生出入管理系统
  • (十六)一篇文章学会Java的常用API
  • (四)linux文件内容查看
  • (图文详解)小程序AppID申请以及在Hbuilderx中运行
  • (一)springboot2.7.6集成activit5.23.0之集成引擎
  • (转)C#调用WebService 基础
  • (转)Linux下编译安装log4cxx
  • (转)Oracle 9i 数据库设计指引全集(1)
  • ***linux下安装xampp,XAMPP目录结构(阿里云安装xampp)
  • *2 echo、printf、mkdir命令的应用
  • . NET自动找可写目录