You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node.js视频裁剪后Fetch触发无限循环问题的解决需求

解决视频裁剪的无限循环问题

问题根源

  1. 前端previewReels函数内重复绑定change事件,每次文件选择都会新增监听器,导致同一操作触发多次处理
  2. 无状态控制:视频加载元数据时,未标记是否正在处理,同一视频会重复发起裁剪请求
  3. 后端临时文件命名冲突,且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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 12:03:13