mirror of
https://github.com/movie-web/movie-web.git
synced 2024-12-28 02:51:49 +01:00
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:
parent
50c2a552ab
commit
1021237191
@ -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 ? (
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user