From 2ea05acb5b014b165a852ff4cca73d0a3d89e3fa Mon Sep 17 00:00:00 2001 From: Ajay Date: Mon, 8 Jan 2024 11:30:45 -0500 Subject: [PATCH] Fallback to autogenerated for random thumbnails instead of local render Should prevent playback issues on unpopular videos Also fix falling back on certain errors --- src/dataFetching.ts | 15 ++++++++---- src/thumbnails/thumbnailData.ts | 3 +++ src/thumbnails/thumbnailRenderer.ts | 36 +++++++++++++++++++++++------ 3 files changed, 42 insertions(+), 12 deletions(-) diff --git a/src/dataFetching.ts b/src/dataFetching.ts index c5fc5a8..ec8ebec 100644 --- a/src/dataFetching.ts +++ b/src/dataFetching.ts @@ -1,5 +1,5 @@ import { VideoID, getVideoID } from "../maze-utils/src/video"; -import { ThumbnailResult, ThumbnailSubmission, fetchVideoMetadata } from "./thumbnails/thumbnailData"; +import { ThumbnailSubmission, ThumbnailWithRandomTimeResult, fetchVideoMetadata } from "./thumbnails/thumbnailData"; import { TitleResult, TitleSubmission } from "./titles/titleData"; import { FetchResponse, sendRealRequestToCustomServer } from "../maze-utils/src/background-request-proxy"; import { BrandingLocation, BrandingResult, updateBrandingForVideo } from "./videoBranding/videoBranding"; @@ -36,14 +36,15 @@ const activeRequests: Record | const activeThumbnailCacheRequests: Record = {}; export async function getVideoThumbnailIncludingUnsubmitted(videoID: VideoID, brandingLocation?: BrandingLocation, - returnRandomTime = true): Promise { + returnRandomTime = true): Promise { const unsubmitted = Config.local!.unsubmitted[videoID]?.thumbnails?.find(t => t.selected); if (unsubmitted) { return { ...unsubmitted, votes: 0, locked: false, - UUID: generateUserID() as BrandingUUID + UUID: generateUserID() as BrandingUUID, + isRandomTime: false }; } @@ -59,7 +60,8 @@ export async function getVideoThumbnailIncludingUnsubmitted(videoID: VideoID, br votes: 0, locked: false, timestamp: timestamp, - original: false + original: false, + isRandomTime: true }; } else { return null; @@ -68,7 +70,10 @@ export async function getVideoThumbnailIncludingUnsubmitted(videoID: VideoID, br return null; } } else { - return result; + return { + ...result, + isRandomTime: false + }; } } diff --git a/src/thumbnails/thumbnailData.ts b/src/thumbnails/thumbnailData.ts index 9010d61..294f7df 100644 --- a/src/thumbnails/thumbnailData.ts +++ b/src/thumbnails/thumbnailData.ts @@ -23,6 +23,9 @@ export type OriginalThumbnailSubmission = { export type OriginalThumbnailResult = PartialThumbnailResult & OriginalThumbnailSubmission; export type ThumbnailResult = CustomThumbnailResult | OriginalThumbnailResult; +export type ThumbnailWithRandomTimeResult = ThumbnailResult & { + isRandomTime: boolean; +}; export type ThumbnailSubmission = CustomThumbnailSubmission | OriginalThumbnailSubmission; export interface Format { diff --git a/src/thumbnails/thumbnailRenderer.ts b/src/thumbnails/thumbnailRenderer.ts index c21c91a..94ae43b 100644 --- a/src/thumbnails/thumbnailRenderer.ts +++ b/src/thumbnails/thumbnailRenderer.ts @@ -57,15 +57,29 @@ function addStopRenderingCallback(videoID: VideoID, callback: (error?: string) = thumbnailRendererControls[videoID].push(callback); } +export class ThumbnailNotInCacheError extends Error { + constructor(videoID: VideoID) { + super(`Thumbnail not found in cache for ${videoID}`); + } +} + export async function renderThumbnail(videoID: VideoID, width: number, - height: number, saveVideo: boolean, timestamp: number): Promise { + height: number, saveVideo: boolean, timestamp: number, onlyFromThumbnailCache = false): Promise { const startTime = performance.now(); + if (onlyFromThumbnailCache) { + await waitForThumbnailCache(videoID); + } + let bestVideoData = await findBestVideo(videoID, width, height, timestamp); if (bestVideoData.renderedThumbnail) { return bestVideoData.renderedThumbnail; } + + if (onlyFromThumbnailCache) { + throw new ThumbnailNotInCacheError(videoID); + } await Promise.race([ waitForSpotInRenderQueue(videoID), @@ -344,6 +358,7 @@ export async function createThumbnailImageElement(existingElement: HTMLImageElem image.style.display = "none"; let timestamp = forcedTimestamp as number; + let isRandomTime = false; if (timestamp === null) { try { const thumbnailPromise = getVideoThumbnailIncludingUnsubmitted(videoID, brandingLocation); @@ -362,6 +377,7 @@ export async function createThumbnailImageElement(existingElement: HTMLImageElem const thumbnail = await thumbnailPromise; if (thumbnail && !thumbnail.original) { timestamp = thumbnail.timestamp; + isRandomTime = thumbnail.isRandomTime; } else if (await getThumbnailFallbackOption(videoID) === ThumbnailFallbackOption.AutoGenerated) { return image; } else { @@ -424,14 +440,20 @@ export async function createThumbnailImageElement(existingElement: HTMLImageElem ready(image, url, timestamp); } - const activeRender = activeRenders[videoID] ?? renderThumbnail(videoID, width, height, saveVideo, timestamp); + const activeRender = activeRenders[videoID] ?? renderThumbnail(videoID, width, height, saveVideo, timestamp, isRandomTime); activeRenders[videoID] = activeRender; - activeRender.then(result).catch(() => { - // Try again with lower resolution - renderThumbnail(videoID, 0, 0, saveVideo, timestamp).then(result).catch(() => { - logError(`Failed to render thumbnail for ${videoID}`); - }); + activeRender.then(result).catch((e) => { + if (e instanceof ThumbnailNotInCacheError) { + failure(); + } else { + // Try again with lower resolution + renderThumbnail(videoID, 0, 0, saveVideo, timestamp, isRandomTime).then(result).catch((e) => { + log(`Failed to render thumbnail for ${videoID} due to ${e}`); + + failure(); + }); + } }); return image;