refactor(thumbnails): add index to continue from where left off

- hls moved to ref
- block loading thumbnail if there is no thumbnail at all
This commit is contained in:
frost768 2023-06-24 02:21:48 +03:00
parent 50c2a552ab
commit 1021237191
2 changed files with 39 additions and 42 deletions

View File

@ -99,6 +99,7 @@ export default function ThumbnailAction({
const src = source.source?.thumbnails.find( const src = source.source?.thumbnails.find(
(x) => x.from < hoverTime && x.to > hoverTime (x) => x.from < hoverTime && x.to > hoverTime
)?.imgUrl; )?.imgUrl;
if (!source.source?.thumbnails.length) return null;
return ( return (
<div className="pointer-events-none"> <div className="pointer-events-none">
{!src ? ( {!src ? (

View File

@ -8,52 +8,36 @@ import { updateSource, useSource } from "@/video/state/logic/source";
import { Thumbnail } from "@/video/state/types"; import { Thumbnail } from "@/video/state/types";
async function* generate( async function* generate(
videoUrl: string,
streamType: MWStreamType,
videoRef: RefObject<HTMLVideoElement>, videoRef: RefObject<HTMLVideoElement>,
canvasRef: RefObject<HTMLCanvasElement>, canvasRef: RefObject<HTMLCanvasElement>,
index = 0,
numThumbnails = 20 numThumbnails = 20
): AsyncGenerator<Thumbnail, Thumbnail> { ): AsyncGenerator<Thumbnail, Thumbnail> {
const video = videoRef.current; const video = videoRef.current;
const canvas = canvasRef.current; const canvas = canvasRef.current;
if (!video) return { from: -1, to: -1, imgUrl: "" }; if (!video) return { from: -1, to: -1, imgUrl: "" };
if (!canvas) 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) => { await new Promise((resolve, reject) => {
video.addEventListener("loadedmetadata", resolve); video.addEventListener("loadedmetadata", resolve);
video.addEventListener("error", reject); video.addEventListener("error", reject);
}); });
canvas.height = video.videoHeight * 1; canvas.height = video.videoHeight;
canvas.width = video.videoWidth * 1; canvas.width = video.videoWidth;
let i = 0; const ctx = canvas.getContext("2d");
while (i < numThumbnails) { if (!ctx) return { from: -1, to: -1, imgUrl: "" };
const from = i * video.duration; let i = index;
const to = (i + 1) * video.duration; const limit = numThumbnails - 1;
const step = video.duration / limit;
// Seek to the specified time while (i < limit && !Number.isNaN(video.duration)) {
const from = i * step;
const to = (i + 1) * step;
video.currentTime = from; video.currentTime = from;
console.log(from, to);
console.time("seek loaded");
await new Promise((resolve) => { await new Promise((resolve) => {
video.addEventListener("seeked", 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); 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(); const imgUrl = canvas.toDataURL();
i += 1; i += 1;
yield { yield {
@ -67,48 +51,60 @@ async function* generate(
} }
export default function ThumbnailGeneratorInternal() { export default function ThumbnailGeneratorInternal() {
const videoRef = useRef<HTMLVideoElement>(document.createElement("video"));
const canvasRef = useRef<HTMLCanvasElement>(document.createElement("canvas"));
const descriptor = useVideoPlayerDescriptor(); const descriptor = useVideoPlayerDescriptor();
const source = useSource(descriptor); const source = useSource(descriptor);
const videoRef = useRef<HTMLVideoElement>(document.createElement("video"));
const canvasRef = useRef<HTMLCanvasElement>(document.createElement("canvas"));
const hlsRef = useRef<Hls>(new Hls());
const thumbnails = useRef<Thumbnail[]>([]); const thumbnails = useRef<Thumbnail[]>([]);
const abortController = useRef<AbortController>(new AbortController()); const abortController = useRef<AbortController>(new AbortController());
const generator = useCallback( const generator = useCallback(
async (url: string, type: MWStreamType) => { async (videoUrl: string, streamType: MWStreamType) => {
for await (const thumbnail of generate(url, type, videoRef, canvasRef)) { 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) { 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; break;
} }
if (thumbnail.from === -1) continue;
thumbnails.current = [...thumbnails.current, thumbnail]; thumbnails.current = [...thumbnails.current, thumbnail];
const state = getPlayerState(descriptor); const state = getPlayerState(descriptor);
if (!state.source) return; if (!state.source) return;
console.log("ran");
state.source.thumbnails = thumbnails.current; state.source.thumbnails = thumbnails.current;
console.log(thumbnails.current);
updateSource(descriptor, state); updateSource(descriptor, state);
console.log("ran 2");
} }
}, },
[descriptor] [descriptor]
); );
useEffect(() => { useEffect(() => {
const controller = abortController.current;
const state = getPlayerState(descriptor); const state = getPlayerState(descriptor);
if (!state.source) return; if (!state.source) return;
const { url, type } = state.source; const { url, type } = state.source;
generator(url, type); generator(url, type);
}, [descriptor, generator, source.source?.url]);
useEffect(() => {
const controller = abortController.current;
return () => { return () => {
console.log("abort"); if (!source.source?.url) return;
controller.abort(); controller.abort();
}; };
}, []); }, [descriptor, generator, source.source?.url]);
return null; return null;
} }