diff --git a/src/video/components/actions/ThumbnailAction.tsx b/src/video/components/actions/ThumbnailAction.tsx
index e2538057..cbb72374 100644
--- a/src/video/components/actions/ThumbnailAction.tsx
+++ b/src/video/components/actions/ThumbnailAction.tsx
@@ -99,6 +99,7 @@ export default function ThumbnailAction({
const src = source.source?.thumbnails.find(
(x) => x.from < hoverTime && x.to > hoverTime
)?.imgUrl;
+ if (!source.source?.thumbnails.length) return null;
return (
{!src ? (
diff --git a/src/video/components/internal/ThumbnailGeneratorInternal.tsx b/src/video/components/internal/ThumbnailGeneratorInternal.tsx
index 69fc33e7..993b1d5a 100644
--- a/src/video/components/internal/ThumbnailGeneratorInternal.tsx
+++ b/src/video/components/internal/ThumbnailGeneratorInternal.tsx
@@ -8,52 +8,36 @@ import { updateSource, useSource } from "@/video/state/logic/source";
import { Thumbnail } from "@/video/state/types";
async function* generate(
- videoUrl: string,
- streamType: MWStreamType,
videoRef: RefObject
,
canvasRef: RefObject,
+ index = 0,
numThumbnails = 20
): AsyncGenerator {
const video = videoRef.current;
const canvas = canvasRef.current;
if (!video) return { from: -1, to: -1, imgUrl: "" };
if (!canvas) return { from: -1, to: -1, imgUrl: "" };
- console.log("extracting started", streamType.toString());
- if (streamType === MWStreamType.HLS) {
- const hls = new Hls();
- console.log("new hls instance");
-
- hls.attachMedia(video);
- hls.loadSource(videoUrl);
- }
await new Promise((resolve, reject) => {
video.addEventListener("loadedmetadata", resolve);
video.addEventListener("error", reject);
});
- canvas.height = video.videoHeight * 1;
- canvas.width = video.videoWidth * 1;
- let i = 0;
- while (i < numThumbnails) {
- const from = i * video.duration;
- const to = (i + 1) * video.duration;
-
- // Seek to the specified time
+ canvas.height = video.videoHeight;
+ canvas.width = video.videoWidth;
+ const ctx = canvas.getContext("2d");
+ if (!ctx) return { from: -1, to: -1, imgUrl: "" };
+ let i = index;
+ const limit = numThumbnails - 1;
+ const step = video.duration / limit;
+ while (i < limit && !Number.isNaN(video.duration)) {
+ const from = i * step;
+ const to = (i + 1) * step;
video.currentTime = from;
- console.log(from, to);
- console.time("seek loaded");
await new Promise((resolve) => {
video.addEventListener("seeked", resolve);
});
- console.timeEnd("seek loaded");
- console.log("loaded", video.currentTime, streamType.toString());
- const ctx = canvas.getContext("2d");
- if (!ctx) return { from: -1, to: -1, imgUrl: "" };
- // Draw the video frame on the canvas
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
-
- // Convert the canvas to a data URL and add it to the list of thumbnails
const imgUrl = canvas.toDataURL();
i += 1;
yield {
@@ -67,48 +51,60 @@ async function* generate(
}
export default function ThumbnailGeneratorInternal() {
- const videoRef = useRef(document.createElement("video"));
- const canvasRef = useRef(document.createElement("canvas"));
const descriptor = useVideoPlayerDescriptor();
const source = useSource(descriptor);
+
+ const videoRef = useRef(document.createElement("video"));
+ const canvasRef = useRef(document.createElement("canvas"));
+ const hlsRef = useRef(new Hls());
const thumbnails = useRef([]);
const abortController = useRef(new AbortController());
+
const generator = useCallback(
- async (url: string, type: MWStreamType) => {
- for await (const thumbnail of generate(url, type, videoRef, canvasRef)) {
+ async (videoUrl: string, streamType: MWStreamType) => {
+ const prevIndex = thumbnails.current.length;
+ const video = videoRef.current;
+ if (streamType === MWStreamType.HLS) {
+ hlsRef.current.attachMedia(video);
+ hlsRef.current.loadSource(videoUrl);
+ } else {
+ video.crossOrigin = "anonymous";
+ video.src = videoUrl;
+ }
+
+ for await (const thumbnail of generate(videoRef, canvasRef, prevIndex)) {
if (abortController.current.signal.aborted) {
- console.log("broke out of loop", type.toString());
+ if (streamType === MWStreamType.HLS) hlsRef.current.detachMedia();
+ abortController.current = new AbortController();
+ const state = getPlayerState(descriptor);
+ if (!state.source) return;
+ const { url, type } = state.source;
+ generator(url, type);
break;
}
+ if (thumbnail.from === -1) continue;
thumbnails.current = [...thumbnails.current, thumbnail];
const state = getPlayerState(descriptor);
if (!state.source) return;
- console.log("ran");
state.source.thumbnails = thumbnails.current;
- console.log(thumbnails.current);
-
updateSource(descriptor, state);
- console.log("ran 2");
}
},
[descriptor]
);
useEffect(() => {
+ const controller = abortController.current;
const state = getPlayerState(descriptor);
if (!state.source) return;
const { url, type } = state.source;
generator(url, type);
- }, [descriptor, generator, source.source?.url]);
-
- useEffect(() => {
- const controller = abortController.current;
return () => {
- console.log("abort");
+ if (!source.source?.url) return;
controller.abort();
};
- }, []);
+ }, [descriptor, generator, source.source?.url]);
return null;
}