这是我的问题:我有一个视频 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 不正确..
我现在明白了我的问题。
FFmpeg 没有足够的时间来完全完成转换。
解决方案:
我应该选择其中一种策略:
它会立即存储在服务器上,然后我就可以使用它了
当用户尝试选择其中一种质量时,服务器开始转换,并将转换后的文件部分返回给用户。
第二步比较难实现,并且存在一个问题,转换时可能会出现一些错误。也许我会选择第一个。