강의 메타데이터 + 영상 파일을 선택하고, 영상 길이(durationSec
)을 자동으로 읽어서 DTO에 포함해 **/api/courses/{courseId}/lectures/with-video
**로 업로드합니다.
import React, { useState } from "react";
/** 영상 길이 읽기 */
function readVideoDuration(file: File): Promise<number> {
return new Promise((resolve, reject) => {
const url = URL.createObjectURL(file);
const v = document.createElement("video");
v.preload = "metadata";
v.onloadedmetadata = () => {
if (!isFinite(v.duration) || v.duration <= 0) {
reject(new Error("영상 길이를 읽을 수 없습니다."));
URL.revokeObjectURL(url);
return;
}
const seconds = Math.round(v.duration);
URL.revokeObjectURL(url);
resolve(seconds);
};
v.onerror = () => {
URL.revokeObjectURL(url);
reject(new Error("영상 메타데이터 로딩 실패"));
};
v.src = url;
});
}
export default function LectureUploader() {
const [title, setTitle] = useState("");
const [desc, setDesc] = useState("");
const [orderIndex, setOrderIndex] = useState(0);
const [isPublic, setIsPublic] = useState(true);
const [file, setFile] = useState<File | null>(null);
const [loading, setLoading] = useState(false);
/** 업로드 실행 */
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!file) {
alert("영상을 선택하세요.");
return;
}
try {
setLoading(true);
// 1) 영상 길이 읽기
const durationSec = await readVideoDuration(file);
const sizeBytes = file.size;
// 2) DTO 생성
const dto = {
title,
description: desc,
orderIndex,
isPublic,
durationSec,
sizeBytes,
};
// 3) FormData 구성
const form = new FormData();
form.append("data", JSON.stringify(dto));
form.append("video", file);
// 4) API 호출
const courseId = 1; // 👉 테스트할 courseId 값 넣기
const res = await fetch(
`https://api.dongcheolcoding.life/api/courses/${courseId}/lectures/with-video`,
{
method: "POST",
body: form,
credentials: "include", // JWT 쿠키 인증이라면 필요
}
);
if (!res.ok) {
const text = await res.text();
throw new Error(`업로드 실패 (${res.status}): ${text}`);
}
const json = await res.json();
console.log("✅ 업로드 성공:", json);
alert("강의가 등록되었습니다.");
} catch (err: any) {
console.error(err);
alert(err.message ?? "업로드 실패");
} finally {
setLoading(false);
}
};
return (
<form onSubmit={handleSubmit} style={{ maxWidth: 500, margin: "2rem auto" }}>
<h2>강의 업로드</h2>
<div>
<label>제목</label>
<inputtype="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
required
/>
</div>
<div>
<label>설명</label>
<textareavalue={desc}
onChange={(e) => setDesc(e.target.value)}
rows={3}
/>
</div>
<div>
<label>순서</label>
<inputtype="number"
value={orderIndex}
onChange={(e) => setOrderIndex(Number(e.target.value))}
/>
</div>
<div>
<label>
공개
<inputtype="checkbox"
checked={isPublic}
onChange={(e) => setIsPublic(e.target.checked)}
/>
</label>
</div>
<div>
<label>영상 파일</label>
<inputtype="file"
accept="video/*"
onChange={(e) => setFile(e.target.files?.[0] ?? null)}
/>
</div>
<button type="submit" disabled={loading}>
{loading ? "업로드 중..." : "업로드"}
</button>
</form>
);
}
readVideoDuration
으로 길이 읽음.durationSec
, sizeBytes
포함해서 JSON 직렬화 → FormData의 data
에 넣음.video
파트에 실제 파일 추가./lectures/with-video
API로 전송.LectureDetailDto
) 확인 가능.