Node.js视频裁剪后Fetch触发无限循环问题的解决需求
解决视频裁剪的无限循环问题
问题根源
- 前端
previewReels函数内重复绑定change事件,每次文件选择都会新增监听器,导致同一操作触发多次处理 - 无状态控制:视频加载元数据时,未标记是否正在处理,同一视频会重复发起裁剪请求
- 后端临时文件命名冲突,且FFmpeg输出路径错误,可能导致文件覆盖或读取错误
前端修复方案
核心修改点
- 将事件监听器移至函数外部,避免重复绑定
- 添加
isProcessing状态变量,防止同一视频重复触发请求 - 清理预览容器,避免重复生成预览项
- 正确遍历选中的所有视频,而非始终取第一个文件
修改后的前端代码
// 初始化处理状态标记 let isProcessing = false; function previewReels(event) { if (isProcessing) return; // 正在处理时直接返回,避免重复请求 isProcessing = true; const videos = document.getElementById('videos').files; const previewItems = document.querySelector('.preview-items'); const buttons = document.querySelector('.buttons'); // 清空现有预览内容,避免重复添加 previewItems.innerHTML = ''; buttons.style.display = 'none'; // 遍历所有选中的视频 for (let i = 0; i < videos.length; i++) { const videoFile = videos[i]; const previewItem = document.createElement('div'); previewItem.className = 'preview-item'; previewItems.appendChild(previewItem); const formData = new FormData(); formData.append('Video', videoFile); const videoElement = document.createElement('video'); videoElement.src = URL.createObjectURL(videoFile); videoElement.addEventListener('loadedmetadata', () => { const videoDuration = videoElement.duration; console.log('Video Duration (mins):', videoDuration / 60); if (videoDuration > 600) { videoElement.style.display = "none"; const loader = document.createElement('img'); loader.src = 'icons/internet.gif'; loader.classList.add('loader'); previewItems.style.backgroundColor = "white"; previewItem.appendChild(loader); fetch('http://localhost:8888/trimVideo', { method: 'POST', body: formData, }) .then((response) => { if (!response.ok) { throw new Error('Error fetching trimmed video data.'); } return response.blob(); }) .then((videoBlob) => { previewItem.removeChild(loader); videoElement.style.display = "block"; buttons.style.display = "block"; const trimmedVideoURL = URL.createObjectURL(videoBlob); videoElement.src = trimmedVideoURL; videoElement.controls = true; previewItem.appendChild(videoElement); previewItems.style.backgroundColor = "#333"; }) .catch((error) => { console.error(error); previewItem.removeChild(loader); previewItems.style.backgroundColor = "#333"; buttons.style.display = "block"; }) .finally(() => { // 处理完成后重置状态 isProcessing = false; }); } else { videoElement.controls = true; buttons.style.display = "block"; previewItems.style.backgroundColor = "#333"; previewItem.appendChild(videoElement); isProcessing = false; } }); } } // 仅绑定一次事件监听器 document.getElementById('videos').addEventListener('change', previewReels);
后端修复方案
核心修改点
- 使用唯一临时文件名,避免同名文件冲突
- 修正FFmpeg输出路径,确保裁剪后的视频正确保存
- 完善临时文件清理逻辑,即使出错也删除临时文件
- 优化跨域头部设置(可选,用CORS中间件更规范)
修改后的后端代码
const fs = require('fs'); const ffmpeg = require('fluent-ffmpeg'); const { v4: uuidv4 } = require('uuid'); // 需要安装uuid:npm install uuid app.post("/trimVideo", (req, res) => { console.log("Received a POST request to /trimVideo"); // 用CORS中间件替代手动设置头部更规范,这里保留原逻辑 res.header("Access-Control-Allow-Origin", "*"); res.header( "Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept" ); if (!req.files || !req.files.Video) { console.log("No video file uploaded"); return res.status(400).send("No video file uploaded."); } const videoFile = req.files.Video; console.log("Received video file:", videoFile.name); if (!videoFile.mimetype.startsWith("video/")) { return res.status(400).send("Invalid video file format."); } // 生成唯一临时文件名,避免冲突 const tempInputPath = `${uuidv4()}_${videoFile.name}`; const tempOutputPath = `${uuidv4()}_trimmed.ismv`; try { // 写入临时输入文件 fs.writeFileSync(tempInputPath, videoFile.data); console.log("Trimming video..."); ffmpeg() .input(tempInputPath) .inputFormat(videoFile.name.split(".").pop().toLowerCase()) .outputOptions("-t 600") // 裁剪为10分钟 .outputFormat("ismv") .on("end", () => { console.log("Video trimming completed."); // 读取裁剪后的视频 const trimmedVideoBuffer = fs.readFileSync(tempOutputPath); // 清理临时文件 fs.unlinkSync(tempInputPath); fs.unlinkSync(tempOutputPath); res.setHeader("Content-Type", "video/ismv"); res.setHeader("Content-Length", trimmedVideoBuffer.length); res.send(trimmedVideoBuffer); }) .on("error", (err, stdout, stderr) => { console.error("Error trimming video:", err); console.error("FFmpeg stdout:", stdout); console.error("FFmpeg stderr:", stderr); // 出错时也要清理临时文件 if (fs.existsSync(tempInputPath)) fs.unlinkSync(tempInputPath); if (fs.existsSync(tempOutputPath)) fs.unlinkSync(tempOutputPath); res.status(500).send(`Error trimming the video: ${err.message}`); }) .on("progress", (progress) => { console.log("Trimming progress:", progress); }) .save(tempOutputPath); // 输出到唯一临时文件 } catch (err) { console.error("Error processing video:", err); // 捕获写入文件时的错误,清理临时文件 if (fs.existsSync(tempInputPath)) fs.unlinkSync(tempInputPath); res.status(500).send(`Error processing video: ${err.message}`); } });
内容的提问来源于stack exchange,提问作者Offeyicial
相关产品推荐
相关产品推荐

