스트리밍되는 동영상의 썸네일을 생성할 때 프레임이 준비되지 않으면 무한히 대기하던 문제 수정

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
staticandClaude Opus 5.5 committed 2026-10-12 01:00:23 +09:00
1 parent 3e52fa7a7c
commit 06fdfcc447
1 file changed
+35 -6
+35 -6
View File
@@ -60,24 +60,53 @@ export const captureVideoThumbnail = (video: HTMLVideoElement) => {
});
};
const VIDEO_THUMBNAIL_TIMEOUT_MS = 30_000;
const generateVideoThumbnail = (videoUrl: string, time = 0) => {
return new Promise<Blob>((resolve, reject) => {
const video = document.createElement("video");
let isCaptured = false;
const cleanup = () => {
clearTimeout(timeout);
video.onloadedmetadata = video.onloadeddata = video.onseeked = video.onerror = null;
video.removeAttribute("src");
video.load(); // Stops loading the video
};
const fail = (reason: unknown) => {
cleanup();
reject(reason);
};
const captureFrame = () => {
if (isCaptured || video.readyState < HTMLMediaElement.HAVE_CURRENT_DATA) return;
isCaptured = true;
captureVideoThumbnail(video).then(resolve, reject).finally(cleanup);
};
// Streamed videos may stall without any error
const timeout = setTimeout(
() => fail(new Error("Timed out while loading the video")),
VIDEO_THUMBNAIL_TIMEOUT_MS,
);
video.onloadedmetadata = () => {
if (video.videoWidth === 0 || video.videoHeight === 0) {
return reject();
return fail(new Error("Invalid video size"));
}
const callbackId = video.requestVideoFrameCallback(() => {
captureVideoThumbnail(video).then(resolve).catch(reject);
video.cancelVideoFrameCallback(callbackId);
});
// Frame callbacks aren't guaranteed for videos not in the document, so also wait for seeking
video.requestVideoFrameCallback(captureFrame);
video.onseeked = captureFrame;
if (time === 0) {
video.onloadeddata = captureFrame;
}
video.currentTime = Math.min(time, video.duration);
};
video.onerror = reject;
video.onerror = () => fail(video.error);
video.muted = true;
video.playsInline = true;
video.preload = "auto";
video.src = videoUrl;
});
};