|
|
|
@ -8,52 +8,36 @@ import { updateSource, useSource } from "@/video/state/logic/source";
@@ -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<HTMLVideoElement>, |
|
|
|
|
canvasRef: RefObject<HTMLCanvasElement>, |
|
|
|
|
index = 0, |
|
|
|
|
numThumbnails = 20 |
|
|
|
|
): AsyncGenerator<Thumbnail, Thumbnail> { |
|
|
|
|
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(
@@ -67,48 +51,60 @@ async function* generate(
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export default function ThumbnailGeneratorInternal() { |
|
|
|
|
const videoRef = useRef<HTMLVideoElement>(document.createElement("video")); |
|
|
|
|
const canvasRef = useRef<HTMLCanvasElement>(document.createElement("canvas")); |
|
|
|
|
const descriptor = useVideoPlayerDescriptor(); |
|
|
|
|
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 abortController = useRef<AbortController>(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; |
|
|
|
|
} |
|
|
|
|