|
|
|
@ -0,0 +1,306 @@
|
|
|
|
|
import React, { useRef, useState } from 'react';
|
|
|
|
|
import { Button, message, Modal, Progress, Upload } from 'antd';
|
|
|
|
|
import { UploadOutlined } from '@ant-design/icons';
|
|
|
|
|
import { createUploadVideoUsingPost, getVideoAuthByVideoIdUsingGet } from '@/services/pop-b2b2c/pbcVodController';
|
|
|
|
|
import type { UploadFile, UploadProps } from 'antd/es/upload/interface';
|
|
|
|
|
import AliyunUpload from 'aliyun-upload-vod';
|
|
|
|
|
import AliPlayer from '../AliPlayer';
|
|
|
|
|
|
|
|
|
|
interface AliVideoUploadProps {
|
|
|
|
|
value?: UploadFile[];
|
|
|
|
|
onChange?: (fileList: UploadFile[]) => void;
|
|
|
|
|
onSuccess?: (data: any) => void;
|
|
|
|
|
disabled?: boolean;
|
|
|
|
|
maxSize?: number; // 最大文件大小,单位MB,默认500MB
|
|
|
|
|
maxCount?: number; // 最大上传个数
|
|
|
|
|
accept?: string; // 接受的文件类型,默认video/*
|
|
|
|
|
multiple?: boolean; // 是否支持多文件上传,默认false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const AliVideoUpload: React.FC<AliVideoUploadProps> = ({
|
|
|
|
|
value = [],
|
|
|
|
|
onChange,
|
|
|
|
|
onSuccess,
|
|
|
|
|
disabled = false,
|
|
|
|
|
maxSize = 500,
|
|
|
|
|
maxCount = 9,
|
|
|
|
|
accept = 'video/*',
|
|
|
|
|
multiple = false,
|
|
|
|
|
}) => {
|
|
|
|
|
const [uploading, setUploading] = useState(false);
|
|
|
|
|
const [uploadProgress, setUploadProgress] = useState(0);
|
|
|
|
|
const uploadRef = useRef<any>(null);
|
|
|
|
|
const vodUploadRef = useRef<any>(null);
|
|
|
|
|
|
|
|
|
|
// 获取上传凭证
|
|
|
|
|
const getUploadAuth = async (fileName: string) => {
|
|
|
|
|
try {
|
|
|
|
|
const response = await createUploadVideoUsingPost({
|
|
|
|
|
title: fileName,
|
|
|
|
|
fileName: fileName,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (response.retcode && response.data) {
|
|
|
|
|
return response.data;
|
|
|
|
|
} else {
|
|
|
|
|
throw new Error(response.retmsg || '获取上传凭证失败');
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('获取上传凭证失败:', error);
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 客户端上传到阿里云VOD
|
|
|
|
|
const uploadToAliyunVOD = async (file: File) => {
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
try {
|
|
|
|
|
// 创建VOD上传客户端
|
|
|
|
|
const uploader = new AliyunUpload.Vod({
|
|
|
|
|
// 阿里账号ID,必须有值
|
|
|
|
|
userId: '1303984639806000', // 这里需要根据实际情况配置
|
|
|
|
|
// 上传到视频点播的地域,默认值为'cn-shanghai'
|
|
|
|
|
region: 'cn-shanghai',
|
|
|
|
|
// 分片大小默认1 MB,不能小于100 KB
|
|
|
|
|
partSize: 1048576,
|
|
|
|
|
// 并行上传分片个数,默认5
|
|
|
|
|
parallel: 5,
|
|
|
|
|
// 网络原因失败时,重新上传次数,默认为3
|
|
|
|
|
retryCount: 3,
|
|
|
|
|
// 网络原因失败时,重新上传间隔时间,默认为2秒
|
|
|
|
|
retryDuration: 2,
|
|
|
|
|
// 添加文件成功
|
|
|
|
|
addFileSuccess: () => {
|
|
|
|
|
if (uploader !== null) {
|
|
|
|
|
uploader.startUpload();
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
// 开始上传
|
|
|
|
|
onUploadstarted: async (uploadInfo: any) => {
|
|
|
|
|
try {
|
|
|
|
|
// 获取上传凭证
|
|
|
|
|
const uploadAuthData = await getUploadAuth(file.name);
|
|
|
|
|
console.log('上传凭证:', uploadAuthData);
|
|
|
|
|
|
|
|
|
|
// 解析上传凭证数据 - 根据实际返回格式处理
|
|
|
|
|
let uploadAuth: string;
|
|
|
|
|
let uploadAddress: string;
|
|
|
|
|
let videoId: string;
|
|
|
|
|
|
|
|
|
|
if (typeof uploadAuthData === 'string') {
|
|
|
|
|
// 如果返回的是字符串,尝试解析JSON
|
|
|
|
|
try {
|
|
|
|
|
const parsed = JSON.parse(uploadAuthData);
|
|
|
|
|
uploadAuth = parsed.uploadAuth || uploadAuthData;
|
|
|
|
|
uploadAddress = parsed.uploadAddress || '';
|
|
|
|
|
videoId = parsed.videoId || '';
|
|
|
|
|
} catch {
|
|
|
|
|
// 如果解析失败,直接使用字符串
|
|
|
|
|
uploadAuth = uploadAuthData;
|
|
|
|
|
uploadAddress = '';
|
|
|
|
|
videoId = '';
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// 如果返回的是对象
|
|
|
|
|
uploadAuth = (uploadAuthData as any).uploadAuth || uploadAuthData;
|
|
|
|
|
uploadAddress = (uploadAuthData as any).UploadAddress || '';
|
|
|
|
|
videoId = (uploadAuthData as any).videoId || '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 设置上传凭证和地址
|
|
|
|
|
uploader.setUploadAuthAndAddress(uploadInfo, uploadAuth, uploadAddress, videoId);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('获取上传凭证失败:', error);
|
|
|
|
|
reject(error);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
// 文件上传成功
|
|
|
|
|
onUploadSucceed: (uploadInfo: any) => {
|
|
|
|
|
console.log('上传成功:', uploadInfo);
|
|
|
|
|
getVideoAuthByVideoIdUsingGet({ id: uploadInfo.videoId }).then(res => {
|
|
|
|
|
if (res.retcode) {
|
|
|
|
|
resolve({
|
|
|
|
|
videoId: uploadInfo.videoId,
|
|
|
|
|
playAuth: res.data?.playAuth, // 需要单独获取播放凭证
|
|
|
|
|
duration: res.data?.videoMeta?.duration,
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
message.error(res.retmsg);
|
|
|
|
|
reject(new Error(res.retmsg));
|
|
|
|
|
}
|
|
|
|
|
}).catch(error => {
|
|
|
|
|
message.error(error);
|
|
|
|
|
reject(new Error(error));
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
// 文件上传失败
|
|
|
|
|
onUploadFailed: (uploadInfo: any, code: string, message: string) => {
|
|
|
|
|
console.error('上传失败:', uploadInfo, code, message);
|
|
|
|
|
reject(new Error(message || '上传失败'));
|
|
|
|
|
},
|
|
|
|
|
// 文件上传进度,单位:字节
|
|
|
|
|
onUploadProgress: (uploadInfo: any, totalSize: number, loadedPercent: number) => {
|
|
|
|
|
const progress = Math.ceil(loadedPercent * 100);
|
|
|
|
|
setUploadProgress(progress);
|
|
|
|
|
},
|
|
|
|
|
// 上传凭证或STS token超时
|
|
|
|
|
onUploadTokenExpired: () => {
|
|
|
|
|
message.error('文件上传token超时');
|
|
|
|
|
reject(new Error('上传凭证超时'));
|
|
|
|
|
},
|
|
|
|
|
// 全部文件上传结束
|
|
|
|
|
onUploadEnd: () => {
|
|
|
|
|
console.log("onUploadEnd: uploaded all the files");
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 保存上传器引用
|
|
|
|
|
vodUploadRef.current = uploader;
|
|
|
|
|
|
|
|
|
|
// 添加文件到上传队列
|
|
|
|
|
const userData = '{"Vod":{}}';
|
|
|
|
|
uploader.addFile(file, undefined, undefined, undefined, userData);
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
reject(error);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleUpload = async (file: File) => {
|
|
|
|
|
if (!file) return;
|
|
|
|
|
|
|
|
|
|
setUploading(true);
|
|
|
|
|
setUploadProgress(0);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// 上传到阿里云VOD
|
|
|
|
|
const result = await uploadToAliyunVOD(file);
|
|
|
|
|
|
|
|
|
|
// 更新文件列表
|
|
|
|
|
const newFile: UploadFile = {
|
|
|
|
|
uid: (file as any).uid || Date.now().toString(),
|
|
|
|
|
name: file.name,
|
|
|
|
|
status: 'done',
|
|
|
|
|
url: '',
|
|
|
|
|
response: {
|
|
|
|
|
retcode: 1,
|
|
|
|
|
data: result,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const newFileList = [...value, newFile];
|
|
|
|
|
onChange?.(newFileList);
|
|
|
|
|
onSuccess?.(result);
|
|
|
|
|
|
|
|
|
|
message.success('视频上传成功');
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('上传失败:', error);
|
|
|
|
|
message.error(error instanceof Error ? error.message : '上传失败');
|
|
|
|
|
|
|
|
|
|
// 更新文件状态为错误
|
|
|
|
|
const errorFile: UploadFile = {
|
|
|
|
|
uid: (file as any).uid || Date.now().toString(),
|
|
|
|
|
name: file.name,
|
|
|
|
|
status: 'error',
|
|
|
|
|
error: error instanceof Error ? error : new Error('上传失败'),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const newFileList = [...value, errorFile];
|
|
|
|
|
onChange?.(newFileList);
|
|
|
|
|
} finally {
|
|
|
|
|
setUploading(false);
|
|
|
|
|
setUploadProgress(0);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const uploadProps: UploadProps = {
|
|
|
|
|
name: 'file',
|
|
|
|
|
accept: accept,
|
|
|
|
|
maxCount: maxCount,
|
|
|
|
|
multiple: multiple,
|
|
|
|
|
disabled: disabled || uploading,
|
|
|
|
|
beforeUpload: (file) => {
|
|
|
|
|
// 检查文件数量限制
|
|
|
|
|
if (value.length >= maxCount) {
|
|
|
|
|
message.error(`最多只能上传${maxCount}个文件!`);
|
|
|
|
|
return Upload.LIST_IGNORE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 检查文件大小 (限制为maxSize MB)
|
|
|
|
|
const isLtMaxSize = file.size / 1024 / 1024 < maxSize;
|
|
|
|
|
if (!isLtMaxSize) {
|
|
|
|
|
message.error(`视频文件大小不能超过${maxSize}MB!`);
|
|
|
|
|
return Upload.LIST_IGNORE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 检查文件类型
|
|
|
|
|
const isVideo = file.type.startsWith('video/');
|
|
|
|
|
if (!isVideo) {
|
|
|
|
|
message.error('只能上传视频文件!');
|
|
|
|
|
return Upload.LIST_IGNORE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 手动处理上传
|
|
|
|
|
handleUpload(file);
|
|
|
|
|
return Upload.LIST_IGNORE;
|
|
|
|
|
},
|
|
|
|
|
fileList: value,
|
|
|
|
|
onRemove: (file) => {
|
|
|
|
|
const newFileList = value.filter(item => item.uid !== file.uid);
|
|
|
|
|
onChange?.(newFileList);
|
|
|
|
|
},
|
|
|
|
|
onPreview: (file) => {
|
|
|
|
|
let videoId = ""
|
|
|
|
|
if (file.uid === '-1' && file.url) {
|
|
|
|
|
videoId = file.url
|
|
|
|
|
} else if (file.response?.data?.videoId) {
|
|
|
|
|
videoId = file.response?.data?.videoId
|
|
|
|
|
}
|
|
|
|
|
getVideoAuthByVideoIdUsingGet({ id: videoId }).then(res => {
|
|
|
|
|
if (res.retcode && res.data?.playAuth) {
|
|
|
|
|
setVideoId(videoId)
|
|
|
|
|
setPlayAuth(res.data?.playAuth)
|
|
|
|
|
handleShowVideo(true)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const [showVideo, handleShowVideo] = useState<boolean>(false);
|
|
|
|
|
const [playAuth, setPlayAuth] = useState<string>('')
|
|
|
|
|
const [videoId, setVideoId] = useState<string>('')
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div>
|
|
|
|
|
<Upload {...uploadProps} ref={uploadRef}>
|
|
|
|
|
<Button
|
|
|
|
|
icon={<UploadOutlined />}
|
|
|
|
|
disabled={disabled || uploading}
|
|
|
|
|
>
|
|
|
|
|
{uploading ? '上传中...' : '选择视频文件'}
|
|
|
|
|
</Button>
|
|
|
|
|
</Upload>
|
|
|
|
|
|
|
|
|
|
{uploading && (
|
|
|
|
|
<div style={{ marginTop: 8 }}>
|
|
|
|
|
<Progress percent={uploadProgress} status="active" />
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<div style={{ marginTop: 8, fontSize: '12px', color: '#666' }}>
|
|
|
|
|
支持格式:MP4、AVI、MOV等视频格式,文件大小不超过{maxSize}MB
|
|
|
|
|
</div>
|
|
|
|
|
<Modal
|
|
|
|
|
title={'预览视频'}
|
|
|
|
|
open={showVideo}
|
|
|
|
|
onCancel={() => handleShowVideo(false)}
|
|
|
|
|
width={800}
|
|
|
|
|
>
|
|
|
|
|
<AliPlayer playAuth={playAuth} vid={videoId} />
|
|
|
|
|
</Modal>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default AliVideoUpload;
|