139 lines
3.8 KiB
TypeScript
139 lines
3.8 KiB
TypeScript
/**
|
||
* 从视频文件中提取第一帧并返回为File对象
|
||
* @param videoFile 视频文件
|
||
* @returns 包含视频第一帧的File对象
|
||
*/
|
||
export const extractVideoFirstFrame = (videoFile: File): Promise<File> => {
|
||
return new Promise((resolve, reject) => {
|
||
const videoUrl = URL.createObjectURL(videoFile);
|
||
const video = document.createElement('video');
|
||
video.src = videoUrl;
|
||
video.crossOrigin = 'anonymous';
|
||
video.muted = true;
|
||
video.preload = 'metadata';
|
||
|
||
video.onloadeddata = () => {
|
||
try {
|
||
// 设置视频时间到第一帧
|
||
video.currentTime = 0.1;
|
||
} catch (e) {
|
||
URL.revokeObjectURL(videoUrl);
|
||
reject(e);
|
||
}
|
||
};
|
||
|
||
video.onseeked = () => {
|
||
try {
|
||
const canvas = document.createElement('canvas');
|
||
canvas.width = video.videoWidth;
|
||
canvas.height = video.videoHeight;
|
||
const ctx = canvas.getContext('2d');
|
||
|
||
if (!ctx) {
|
||
throw new Error('无法获取canvas上下文');
|
||
}
|
||
|
||
// 绘制视频帧到canvas
|
||
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
||
|
||
// 将canvas转换为DataURL
|
||
const dataUrl = canvas.toDataURL('image/jpeg');
|
||
|
||
// 将DataURL转换为Blob
|
||
const byteString = atob(dataUrl.split(',')[1]);
|
||
const mimeString = dataUrl.split(',')[0].split(':')[1].split(';')[0];
|
||
const ab = new ArrayBuffer(byteString.length);
|
||
const ia = new Uint8Array(ab);
|
||
for (let i = 0; i < byteString.length; i++) {
|
||
ia[i] = byteString.charCodeAt(i);
|
||
}
|
||
const blob = new Blob([ab], { type: mimeString });
|
||
|
||
// 创建File对象
|
||
const frameFile = new File(
|
||
[blob],
|
||
`${videoFile.name.replace(/\.[^/.]+$/, '')}_frame.jpg`,
|
||
{ type: 'image/jpeg' }
|
||
);
|
||
|
||
// 清理URL对象
|
||
URL.revokeObjectURL(videoUrl);
|
||
resolve(frameFile);
|
||
} catch (e) {
|
||
URL.revokeObjectURL(videoUrl);
|
||
reject(e);
|
||
}
|
||
};
|
||
|
||
video.onerror = () => {
|
||
URL.revokeObjectURL(videoUrl);
|
||
reject(new Error('视频加载失败'));
|
||
};
|
||
});
|
||
};
|
||
|
||
// 获取视频时长
|
||
export const getVideoDuration = (file: File): Promise<number> => {
|
||
return new Promise((resolve) => {
|
||
const video = document.createElement('video');
|
||
video.preload = 'metadata';
|
||
|
||
video.onloadedmetadata = () => {
|
||
URL.revokeObjectURL(video.src);
|
||
resolve(video.duration);
|
||
};
|
||
|
||
video.onerror = () => {
|
||
URL.revokeObjectURL(video.src);
|
||
resolve(0); // Return 0 if we can't get the duration
|
||
};
|
||
|
||
video.src = URL.createObjectURL(file);
|
||
});
|
||
};
|
||
|
||
// 根据 mp4 的url来获取视频时长
|
||
/**
|
||
* 根据视频URL获取视频时长
|
||
* @param videoUrl 视频的URL
|
||
* @returns 返回一个Promise,解析为视频时长(秒)
|
||
*/
|
||
export const getVideoDurationFromUrl = async (videoUrl: string): Promise<number> => {
|
||
return await new Promise((resolve, reject) => {
|
||
// 创建临时的video元素
|
||
const video = document.createElement('video');
|
||
|
||
// 设置为只加载元数据,不加载整个视频
|
||
video.preload = 'metadata';
|
||
|
||
// 处理加载成功
|
||
video.onloadedmetadata = () => {
|
||
// 释放URL对象
|
||
URL.revokeObjectURL(video.src);
|
||
// 返回视频时长(秒)
|
||
resolve(video.duration);
|
||
};
|
||
|
||
// 处理加载错误
|
||
video.onerror = () => {
|
||
URL.revokeObjectURL(video.src);
|
||
reject(new Error('无法加载视频'));
|
||
};
|
||
|
||
// 处理网络错误
|
||
video.onabort = () => {
|
||
URL.revokeObjectURL(video.src);
|
||
reject(new Error('视频加载被中止'));
|
||
};
|
||
|
||
// 设置视频源
|
||
video.src = videoUrl;
|
||
|
||
// 添加跨域属性(如果需要)
|
||
video.setAttribute('crossOrigin', 'anonymous');
|
||
|
||
// 开始加载元数据
|
||
video.load();
|
||
});
|
||
};
|