使用 FFMpeg 和 Nest.js+Next.js 进行视频流化

问题描述 投票:0回答:1

这是我的问题:我有一个视频 src 1080p(在前端)。在前端,我将此视频路线发送到后端:

const req = async()=>{try{const res = await axios.get('/catalog/item',{params:{SeriesName:seriesName}});return {data:res.data};}catch(err){console.log(err);return false;}}const fetchedData = await req();-On the backend i return seriesName.Now i can make a full path,what the video is,and where it is,code:
const videoUrl = 'C:/Users/arMori/Desktop/RedditClone/reddit/public/videos';console.log('IT VideoURL',videoUrl);
const selectedFile = `${videoUrl}/${fetchedData.data.VideoSource}/${seriesName}-1080p.mp4`
console.log(`ITS'S SELECTED FILE: ${selectedFile}`);

好的,我有 1080p 的 src,现在是时候将其发送到后端了:

const response = await axios.post('/videoFormat', {videoUrl:selectedFile})console.log('Это консоль лог путей: ',response.data);const videoPaths = response.data;

后端获取,FFMpqg 制作两种分辨率,720p 和 480p,保存到后端临时存储,然后返回这些视频存储的两条路径

async videoUpload(videoUrl:string){try{const tempDir = C:/Users/arMori/Desktop/RedditClone/reddit_back/src/video/temp;const inputFile = videoUrl;console.log('VIDEOURL: ',videoUrl);
        const outputFiles = [];
        
        await this.createDirectories(tempDir);        
        outputFiles.push(await this.convertVideo(inputFile, '1280x720', '720p.mp4'));
        outputFiles.push(await this.convertVideo(inputFile, '854x480', '480p.mp4'));
        console.log('OUTUPT FILES SERVICE: ',outputFiles);
        
        return outputFiles;

    }catch(err){
        console.error('VideoFormatterService Error: ',err);
        
    }
}

private convertVideo(inputPath:string,resolution:string,outputFileName:string):Promise<string>{
    const temp = `C:/Users/arMori/Desktop/RedditClone/reddit_back/src/video/temp`;
    return new Promise(async(resolve,reject)=>{
        const height = resolution.split('x')[1];
        console.log('HIEGHT: ',height);
        
        const outputDir = `C:/Users/arMori/Desktop/RedditClone/reddit_back/src/video/temp/${height}p`;
        const outputPath = join(outputDir, outputFileName);
        const isExists = await fs.access(outputPath).then(() => true).catch(() => false);
        if(isExists){ 
            console.log(`File already exists: ${outputPath}`);
            return resolve(outputPath)
        };
        ffmpeg(inputPath)
        .size(`${resolution}`)
        .videoCodec('libx264') // Кодек H.264
        .audioCodec('aac') 
        .output(outputPath)
        .on('end',()=>resolve(outputPath))
        .on('error',(err)=>reject(err))
        .run()
            
    })
}

private async createDirectories(temp:string){
    try{
        const dir720p = `${temp}/720p`;
        const dir480p = `${temp}/480p`;
        const dir720pExists = await fs.access(dir720p).then(() => true).catch(() => false);
        const dir480pExists = await fs.access(dir480p).then(() => true).catch(() => false);
        if(dir720pExists && dir480pExists){
            console.log('FILES ALIVE');
            return;
        }
        if (!dir720pExists) {
            await fs.mkdir(dir720p, { recursive: true });
            console.log('Папка 720p создана');
        }
        
        if (!dir480pExists) {
            await fs.mkdir(dir480p, { recursive: true });
            console.log('Папка 480p создана');
        }
    } catch (err) {
        console.error('Ошибка при создании директорий:', err);
    }
}

继续前端代码:

let videoPath;

if (quality === '720p') {
        videoPath = videoPaths[0];
} else if (quality === '480p') {
        videoPath = videoPaths[1];
}

if (!videoPath) {
        console.error('Video path not found!');
        return;
}

// Получаем видео по его пути
console.log('VIDEOPATH LOG: ',videoPath);
    
const videoRes = await axios.get('/videoFormat/getVideo', { 
        params: { path: videoPath } ,
        headers: { Range: 'bytes=0-' },
        responseType: 'blob'
    });
    console.log('Video fetched: ', videoRes);
    const videoBlob = new Blob([videoRes.data], { type: 'video/mp4' });
    const videoURL = URL.createObjectURL(videoBlob);
    return videoURL;
    /* console.log('Видео успешно загружено:', response.data); */
    } catch (error) {
    console.error('Ошибка при загрузке видео:', error);
    }
}

这里我只是选择其中一个路线并发出一个新的 GET 请求(VideoRes),现在在后端的控制器中,我正在尝试进行视频流:

@Public()
    @Get('/getVideo')
    async getVideo(@Query('path') videoPath:string,@Req() req:Request,@Res() res:Response){
        try {
            console.log('PATH ARGUMENT: ',videoPath);
            console.log('VIDEOPATH IN SERVICE: ',videoPath);
        const videoSize = (await fs.stat(videoPath)).size;
        const CHUNK_SIZE = 10 ** 6;
        const range = req.headers['range'] as string | undefined;
        if (!range) {
            return new ForbiddenException('Range не найденно');
        }
        const start = Number(range.replace(/\D/g,""));
        const end = Math.min(start + CHUNK_SIZE,videoSize - 1);

        const contentLength = end - start + 1;
        const videoStream = fsSync.createReadStream(videoPath, { start, end });
        const headers = {
            'Content-Range':`bytes ${start}-${end}/${videoSize}`,
            'Accept-Ranges':'bytes',
            'Content-Length':contentLength,
            'Content-Type':'video/mp4'
        }
        
        res.writeHead(206,headers);

        // Передаем поток в ответ
        videoStream.pipe(res);
        

        // Если возникнет ошибка при стриминге, логируем ошибку
        videoStream.on('error', (error) => {
            console.error('Ошибка при чтении видео:', error);
            res.status(500).send('Ошибка при чтении видео');
        });
        } catch (error) {
            console.error('Ошибка при обработке запросов:', error);
            return res.status(400).json({ message: 'Ошибка при обработке getVideo запросов' });
        }
    }

发送到前端

res.writeHead(206,headers);

在前端,我为视频源创建 blob url 并返回它

const videoBlob = new Blob([videoRes.data], { type: 'video/mp4' });const videoURL = URL.createObjectURL(videoBlob);return videoURL;

并将 src 分配给视频:

useVideo(seriesName,quality).then(src => {
                if (src) {
                    console.log('ITS VIDEOLOGISC GOIDA!');
                    if(!playRef.current) return;
                    
                    const oldURL = playRef.current.src;
                    if (oldURL && oldURL.startsWith('blob:')) {
                        URL.revokeObjectURL(oldURL);
                    }
                    playRef.current.pause();
                    playRef.current.src = '';
                    setQuality(quality);
                    console.log('SRC: ',src);
                    
                    playRef.current.src = src;
                    playRef.current.load();
                    console.log('ITS VIDEOURL GOIDA!');
                    togglePlayPause();
                }
            })
            .catch(err => console.error('Failed to fetch video', err));

但问题是:

Vinland-Saga:1 未捕获(承诺中)NotSupportedError:无法加载,因为找不到支持的源

我也不知道为什么...

我尝试了一切,但我不明白为什么 src 不正确..

next.js video ffmpeg nestjs video-streaming
1个回答
0
投票

我理解我的问题。Ffmpeg 没有足够的时间来完全完成转换。我应该选择其中一种策略: 1)它将存储在服务器上,然后我可以使用它 2)当用户尝试选择其中一种质量时,服务器开始转换并将转换后的部分文件返回给用户。第二个更难实现,并且有一个问题,在转换时可能会发生一些错误。也许我会选择第一个

© www.soinside.com 2019 - 2024. All rights reserved.