Improve thumbnail generation and quality selection

Refactors thumbnail queue to generate 127 evenly distributed thumbnails instead of using a layered approach. Adds a new selectLowestQuality function to consistently select the lowest available video quality for thumbnail extraction, replacing the previous selectQuality usage.
This commit is contained in:
Pas 2025-11-09 15:02:27 -07:00
parent c7dcec2560
commit bd491f2d14

View file

@ -3,27 +3,64 @@ import { useCallback, useEffect, useRef } from "react";
import { ThumbnailImage } from "@/stores/player/slices/thumbnails"; import { ThumbnailImage } from "@/stores/player/slices/thumbnails";
import { usePlayerStore } from "@/stores/player/store"; import { usePlayerStore } from "@/stores/player/store";
import { LoadableSource, selectQuality } from "@/stores/player/utils/qualities"; import {
LoadableSource,
SourceQuality,
SourceSliceSource,
} from "@/stores/player/utils/qualities";
import { usePreferencesStore } from "@/stores/preferences"; import { usePreferencesStore } from "@/stores/preferences";
import { processCdnLink } from "@/utils/cdn"; import { processCdnLink } from "@/utils/cdn";
import { isSafari } from "@/utils/detectFeatures"; import { isSafari } from "@/utils/detectFeatures";
function makeQueue(layers: number): number[] { function makeQueue(thumbnails: number): number[] {
const output = [0, 1]; const output = [];
let segmentSize = 0.5; for (let i = 0; i < thumbnails; i += 1) {
let lastSegmentAmount = 0; output.push(i / (thumbnails - 1));
for (let layer = 0; layer < layers; layer += 1) {
const segmentAmount = 1 / segmentSize - 1;
for (let i = 0; i < segmentAmount - lastSegmentAmount; i += 1) {
const offset = i * segmentSize * 2;
output.push(offset + segmentSize);
}
lastSegmentAmount = segmentAmount;
segmentSize /= 2;
} }
return output; return output;
} }
function selectLowestQuality(source: SourceSliceSource): LoadableSource {
if (source.type === "hls") return source;
if (source.type === "file") {
const availableQualities = Object.entries(source.qualities)
.filter((entry) => (entry[1].url.length ?? 0) > 0)
.map((entry) => entry[0]) as SourceQuality[];
// Quality sorting by priority (higher number = higher quality)
const qualityPriority: Record<SourceQuality, number> = {
"360": 10,
"480": 20,
"720": 30,
"4k": 35,
"1080": 40,
unknown: 50, // unknown is typically the largest quality
};
// Find the lowest quality (smallest priority number) that's available
let lowestQuality: SourceQuality | null = null;
let lowestPriority = Infinity;
for (const quality of availableQualities) {
const priority = qualityPriority[quality] ?? 0;
if (priority < lowestPriority) {
lowestPriority = priority;
lowestQuality = quality;
}
}
if (lowestQuality) {
const stream = source.qualities[lowestQuality];
if (stream) {
return stream;
}
}
}
throw new Error("couldn't select lowest quality");
}
class ThumnbnailWorker { class ThumnbnailWorker {
interrupted: boolean; interrupted: boolean;
@ -114,7 +151,7 @@ class ThumnbnailWorker {
if (!vid) return; if (!vid) return;
await this.initVideo(); await this.initVideo();
const queue = makeQueue(6); // 7 layers is 63 thumbnails evenly distributed const queue = makeQueue(127); // 127 thumbnails evenly distributed across the video
for (let i = 0; i < queue.length; i += 1) { for (let i = 0; i < queue.length; i += 1) {
if (this.interrupted) return; if (this.interrupted) return;
await this.takeSnapshot(vid.duration * queue[i]); await this.takeSnapshot(vid.duration * queue[i]);
@ -137,11 +174,7 @@ export function ThumbnailScraper() {
const start = useCallback(() => { const start = useCallback(() => {
let inputStream = null; let inputStream = null;
if (source) if (source) inputStream = selectLowestQuality(source);
inputStream = selectQuality(source, {
automaticQuality: false,
lastChosenQuality: "360",
});
// dont interrupt existing working // dont interrupt existing working
if (workerRef.current) return; if (workerRef.current) return;
// Allow thumbnail generation when video is loaded and has duration // Allow thumbnail generation when video is loaded and has duration
@ -152,7 +185,7 @@ export function ThumbnailScraper() {
addImage, addImage,
}); });
workerRef.current = ins; workerRef.current = ins;
ins.start(inputStream.stream); ins.start(inputStream);
}, [source, addImage, resetImages, hasPlayedOnce, duration]); }, [source, addImage, resetImages, hasPlayedOnce, duration]);
const startRef = useRef(start); const startRef = useRef(start);