mirror of
https://github.com/p-stream/p-stream.git
synced 2026-04-21 12:12:17 +00:00
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:
parent
c7dcec2560
commit
bd491f2d14
1 changed files with 53 additions and 20 deletions
|
|
@ -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);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue