| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262 |
- import React, { useRef, useState } from "react";
- import { Button, Card, message, Progress, Space, Tag } from "antd";
- import TcVod from "vod-js-sdk-v6";
- import { VodAPI } from "./api/vod.ts";
- interface UploadTask {
- file: File;
- progress: number;
- status: "pending" | "uploading" | "success" | "error" | "canceled";
- fileId?: string;
- videoUrl?: string;
- coverUrl?: string;
- cancel?: () => void;
- }
- export const VodUploadPage = () => {
- const videoInputRef = useRef<HTMLInputElement>(null);
- const coverInputRef = useRef<HTMLInputElement>(null);
- const [uploadTasks, setUploadTasks] = useState<UploadTask[]>([]);
- const [selectedVideo, setSelectedVideo] = useState<File | null>(null);
- const [selectedCover, setSelectedCover] = useState<File | null>(null);
- // 获取上传签名
- const getSignature = async () => {
- try {
- return await VodAPI.getSignature();
- } catch (error) {
- message.error("获取上传签名失败");
- throw error;
- }
- };
- // 初始化VOD SDK
- const tcVod = new TcVod({
- getSignature: getSignature,
- });
- // 处理视频文件选择
- const handleVideoSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
- if (e.target.files && e.target.files[0]) {
- setSelectedVideo(e.target.files[0]);
- }
- };
- // 处理封面文件选择
- const handleCoverSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
- if (e.target.files && e.target.files[0]) {
- setSelectedCover(e.target.files[0]);
- }
- };
- // 开始上传
- const startUpload = async () => {
- if (!selectedVideo) {
- message.warning("请先选择视频文件");
- return;
- }
- const newTask: UploadTask = {
- file: selectedVideo,
- progress: 0,
- status: "pending",
- };
- setUploadTasks((prev) => [...prev, newTask]);
- try {
- const uploader = tcVod.upload({
- mediaFile: selectedVideo,
- coverFile: selectedCover || undefined,
- });
- const updatedTask: UploadTask = {
- ...newTask,
- status: "uploading",
- cancel: () => {
- uploader.cancel();
- setUploadTasks((prev) =>
- prev.map((task) =>
- task.file === newTask.file
- ? { ...task, status: "canceled" }
- : task
- )
- );
- },
- };
- setUploadTasks((prev) =>
- prev.map((task) => task.file === newTask.file ? updatedTask : task)
- );
- // 监听上传进度
- uploader.on("media_progress", (info) => {
- setUploadTasks((prev) =>
- prev.map((task) =>
- task.file === newTask.file
- ? { ...task, progress: info.percent * 100 }
- : task
- )
- );
- });
- // 监听上传完成
- uploader.on("media_upload", (info) => {
- setUploadTasks((prev) =>
- prev.map((task) =>
- task.file === newTask.file ? { ...task, status: "success" } : task
- )
- );
- });
- // 执行上传
- const result = await uploader.done();
- setUploadTasks((prev) =>
- prev.map((task) =>
- task.file === newTask.file
- ? {
- ...task,
- fileId: result.fileId,
- videoUrl: result.video.url,
- coverUrl: result.cover?.url,
- status: "success",
- }
- : task
- )
- );
- message.success("视频上传成功");
- } catch (error) {
- setUploadTasks((prev) =>
- prev.map((task) =>
- task.file === newTask.file ? { ...task, status: "error" } : task
- )
- );
- message.error("视频上传失败");
- } finally {
- setSelectedVideo(null);
- setSelectedCover(null);
- if (videoInputRef.current) videoInputRef.current.value = "";
- if (coverInputRef.current) coverInputRef.current.value = "";
- }
- };
- // 渲染上传状态标签
- const renderStatusTag = (status: UploadTask["status"]) => {
- switch (status) {
- case "pending":
- return <Tag color="default">等待上传</Tag>;
- case "uploading":
- return <Tag color="processing">上传中</Tag>;
- case "success":
- return <Tag color="success">上传成功</Tag>;
- case "error":
- return <Tag color="error">上传失败</Tag>;
- case "canceled":
- return <Tag color="warning">已取消</Tag>;
- default:
- return <Tag color="default">未知状态</Tag>;
- }
- };
- return (
- <div>
- <Card title="视频上传" className="mb-4">
- <Space direction="vertical" style={{ width: "100%" }}>
- <div>
- <input
- type="file"
- ref={videoInputRef}
- onChange={handleVideoSelect}
- accept="video/*"
- style={{ display: "none" }}
- />
- <Button
- onClick={() => videoInputRef.current?.click()}
- type="primary"
- >
- {selectedVideo ? selectedVideo.name : "选择视频文件"}
- </Button>
- <input
- type="file"
- ref={coverInputRef}
- onChange={handleCoverSelect}
- accept="image/*"
- style={{ display: "none", marginLeft: 16 }}
- />
- <Button
- onClick={() => coverInputRef.current?.click()}
- style={{ marginLeft: 16 }}
- >
- {selectedCover ? selectedCover.name : "选择封面(可选)"}
- </Button>
- <Button
- type="primary"
- onClick={startUpload}
- disabled={!selectedVideo}
- style={{ marginLeft: 16 }}
- >
- 开始上传
- </Button>
- </div>
- <div style={{ marginTop: 24 }}>
- {uploadTasks.map((task, index) => (
- <div key={index} style={{ marginBottom: 16 }}>
- <div style={{ marginBottom: 8 }}>
- <span style={{ marginRight: 8 }}>{task.file.name}</span>
- {renderStatusTag(task.status)}
- {task.status === "uploading" && task.cancel && (
- <Button
- type="link"
- danger
- onClick={task.cancel}
- style={{ marginLeft: 8 }}
- >
- 取消上传
- </Button>
- )}
- </div>
- {task.status === "uploading" && (
- <Progress percent={task.progress} status="active" />
- )}
- {task.fileId && (
- <div style={{ marginTop: 8 }}>
- <div>File ID: {task.fileId}</div>
- {task.videoUrl && (
- <div>
- 视频地址:{" "}
- <a
- href={task.videoUrl}
- target="_blank"
- rel="noreferrer"
- >
- {task.videoUrl}
- </a>
- </div>
- )}
- {task.coverUrl && (
- <div>
- 封面地址:{" "}
- <a
- href={task.coverUrl}
- target="_blank"
- rel="noreferrer"
- >
- {task.coverUrl}
- </a>
- </div>
- )}
- </div>
- )}
- </div>
- ))}
- </div>
- </Space>
- </Card>
- </div>
- );
- };
|