Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
940 changes: 509 additions & 431 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"ts-node": "^10.9.1",
"typescript": "4.9",
"web-ext": "^8.2.0",
"webpack": "^5.94.0",
"webpack": "^5.105.0",
"webpack-cli": "^4.10.0",
"webpack-merge": "^5.8.0"
},
Expand Down
6 changes: 5 additions & 1 deletion public/options/options.css
Original file line number Diff line number Diff line change
Expand Up @@ -889,10 +889,14 @@ svg {
position: absolute;
}

#configurationName {
min-width: 200px;
}

.configurationInfo > *:not(:last-child) {
margin-bottom: 10px;
}

.configurationInfo .option-text-box {
width: 100%;
}
}
4 changes: 2 additions & 2 deletions src/dataFetching.ts
Original file line number Diff line number Diff line change
Expand Up @@ -396,8 +396,8 @@ async function fetchBrandingFromThumbnailCache(videoID: VideoID, time?: number,
}

setupPreRenderedThumbnail(videoID, timestamp,
(request.responseBinary instanceof Blob) ?
request.responseBinary : new Blob([new Uint8Array(request.responseBinary).buffer]));
URL.createObjectURL((request.responseBinary instanceof Blob) ?
request.responseBinary : new Blob([new Uint8Array(request.responseBinary).buffer])));
delete activeThumbnailCacheRequests[videoID];

return {
Expand Down
3 changes: 2 additions & 1 deletion src/js-components/previewBar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -950,13 +950,14 @@ class PreviewBar {
chapterButton.disabled = false;
}

const chapterTitle = chaptersContainer.querySelector(".ytp-chapter-title-content") as HTMLDivElement;
const chapterTitle = chaptersContainer.querySelector(".ytp-chapter-title-content:not(.sponsorChapterText)") as HTMLDivElement;
chapterTitle.style.display = "none";

const chapterCustomText = (chapterTitle.parentElement.querySelector(".sponsorChapterText") || (() => {
const elem = document.createElement("div");
chapterTitle.parentElement.insertBefore(elem, chapterTitle);
elem.classList.add("sponsorChapterText");
elem.classList.add("ytp-chapter-title-content");
if (document.location.host === "tv.youtube.com") {
elem.style.lineHeight = "initial";
}
Expand Down
7 changes: 4 additions & 3 deletions src/submission/ThumbnailComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ export const ThumbnailComponent = (props: ThumbnailComponentProps) => {
renderThumbnail(props.videoID, canvasWidth, canvasHeight, false, props.time!, false, true).then((rendered) => {
waitFor(() => canvasRef?.current).then(async () => {
if (rendered && !cancelled) {
const imageBitmap = await createImageBitmap(rendered.blob);
const blob = await new Response(rendered.blobUrl).blob();
const imageBitmap = await createImageBitmap(blob);

drawCenteredToCanvas(canvasRef.current!, canvasRef.current!.width, canvasRef.current!.height,
imageBitmap.width, imageBitmap.height, imageBitmap);
Expand Down Expand Up @@ -191,7 +192,7 @@ async function renderCurrentFrame(props: ThumbnailComponentProps,
if (cacheThumbnail) {
canvasRef.current!.toBlob((blob) => {
if (blob) {
setupPreRenderedThumbnail(props.videoID, props.time!, blob, canvasRef.current!.width, canvasRef.current!.height);
setupPreRenderedThumbnail(props.videoID, props.time!, URL.createObjectURL(blob), canvasRef.current!.width, canvasRef.current!.height);
} else {
logError(`Failed to cache thumbnail for ${props.videoID} at ${props.time}`);
}
Expand Down Expand Up @@ -231,4 +232,4 @@ function calculateCanvasWidth(larger: boolean): number {
} else {
return fallback;
}
}
}
12 changes: 9 additions & 3 deletions src/thumbnails/thumbnailDataCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ interface ThumbnailVideoBase {
}

export type RenderedThumbnailVideo = ThumbnailVideoBase & {
blob: Blob;
blobUrl: string;
rendered: true;
fromThumbnailCache: boolean;
}
Expand Down Expand Up @@ -59,12 +59,18 @@ export const thumbnailDataCache = new DataCache<VideoID, ThumbnailData>(() => ({
},
failures: [],
thumbnailCachesFailed: new Set()
}));
}), (e) => {
for (const video of e.video) {
if (video.rendered) {
URL.revokeObjectURL(video.blobUrl);
}
}
}, 1000);

export interface ChannelData {
avatarUrl: string | null;
}

export const channelInfoCache = new DataCache<ChannelID, ChannelData>(() => ({
avatarUrl: null
}));
}));
90 changes: 28 additions & 62 deletions src/thumbnails/thumbnailRenderer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { fetchChannelnfo, isCustomThumbnailResult } from "./thumbnailData";
import { VideoID } from "../../maze-utils/src/video";
import { getNumberOfThumbnailCacheRequests, getThumbnailUrl, getVideoThumbnailIncludingUnsubmitted, isActiveThumbnailCacheRequest, isFetchingFromThumbnailCache, queueThumbnailCacheRequest, waitForThumbnailCache } from "../dataFetching";
import { getThumbnailUrl, getVideoThumbnailIncludingUnsubmitted, isActiveThumbnailCacheRequest, queueThumbnailCacheRequest, waitForThumbnailCache } from "../dataFetching";
import { log, logError } from "../utils/logger";
import { BrandingLocation, ShowCustomBrandingInfo, getActualShowCustomBranding, shouldShowCasual } from "../videoBranding/videoBranding";
import { isFirefoxOrSafari, timeoutPomise, waitFor } from "../../maze-utils/src";
Expand All @@ -9,7 +9,7 @@ import { getThumbnailFallbackAutoGeneratedOption, getThumbnailFallbackOption, sh
import { countThumbnailReplacement } from "../config/stats";
import { ThumbnailCacheOption } from "../config/config";
import { getThumbnailImageSelectors, getThumbnailSelectors } from "../../maze-utils/src/thumbnail-selectors";
import { onMobile } from "../../maze-utils/src/pageInfo";
import { isOnV3Extension, onMobile } from "../../maze-utils/src/pageInfo";
import { MobileFix, addNodeToListenFor } from "../utils/titleBar";
import { resetMediaSessionThumbnail, setMediaSessionThumbnail } from "../videoBranding/mediaSessionHandler";
import { isSafari } from "../../maze-utils/src/config";
Expand Down Expand Up @@ -75,11 +75,10 @@ export class ThumbnailNotInCacheError extends Error {
}

export async function renderThumbnail(videoID: VideoID, width: number,
height: number, saveVideo: boolean, timestamp: number, onlyFromThumbnailCache = false, ignoreTimeout = false): Promise<RenderedThumbnailVideo | null> {
height: number, saveVideo: boolean, timestamp: number, dontLocalRender = false, ignoreTimeout = false): Promise<RenderedThumbnailVideo | null> {

const startTime = performance.now();

if (onlyFromThumbnailCache) {
if (dontLocalRender) {
await waitForThumbnailCache(videoID);
}

Expand All @@ -88,7 +87,7 @@ export async function renderThumbnail(videoID: VideoID, width: number,
return bestVideoData.renderedThumbnail;
}

if (onlyFromThumbnailCache) {
if (dontLocalRender) {
throw new ThumbnailNotInCacheError(videoID);
}

Expand Down Expand Up @@ -189,7 +188,7 @@ export async function renderThumbnail(videoID: VideoID, width: number,
}

const videoInfo: RenderedThumbnailVideo = {
blob: blobResult,
blobUrl: URL.createObjectURL(blobResult),
video: saveVideo ? video : null,
width: video.videoWidth,
height: video.videoHeight,
Expand Down Expand Up @@ -369,8 +368,6 @@ function renderToBlob(surface: HTMLVideoElement | HTMLCanvasElement): Promise<Bl

/**
* Returns a canvas that will be drawn to once the thumbnail is ready.
*
* Starts with lower resolution and replaces it with higher resolution when ready.
*/
export async function createThumbnailImageElement(existingElement: HTMLImageElement | null, videoID: VideoID, width: number,
height: number, brandingLocation: BrandingLocation, forcedTimestamp: number | null,
Expand All @@ -381,7 +378,6 @@ 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);
Expand All @@ -400,7 +396,6 @@ export async function createThumbnailImageElement(existingElement: HTMLImageElem
const thumbnail = await thumbnailPromise;
if (thumbnail && isCustomThumbnailResult(thumbnail)) {
timestamp = thumbnail.timestamp;
isRandomTime = thumbnail.isRandomTime;
} else if (!thumbnail
&& [ThumbnailFallbackOption.AutoGenerated, ThumbnailFallbackOption.RandomTime].includes(await getThumbnailFallbackOption(videoID))) {
failure();
Expand All @@ -414,36 +409,6 @@ export async function createThumbnailImageElement(existingElement: HTMLImageElem
}
}

let waitingForFetchTries = 0;
while (isFetchingFromThumbnailCache(videoID, timestamp)) {
// Wait for the thumbnail to be fetched from the cache before trying local generation
try {
const promises = [
waitForThumbnailCache(videoID),
timeoutPomise(Config.config!.startLocalRenderTimeout).catch(() => ({}))
];

// If we know about it, we don't want to return early without using the timeout
if (shouldReplaceThumbnailsFastCheck(videoID) === null) {
promises.push(shouldReplaceThumbnails(videoID));
}

await Promise.race(promises);

if (isFetchingFromThumbnailCache(videoID, timestamp)
&& getNumberOfThumbnailCacheRequests() > 3 && waitingForFetchTries < 5
&& shouldReplaceThumbnailsFastCheck(videoID) !== false) {
waitingForFetchTries++;
log(videoID, "Lots of thumbnail cache requests in progress, waiting a little longer");
} else {
break;
}
} catch (e) {
// Go on and do a local render
break;
}
}

if (!await stillValid()) {
return null;
}
Expand All @@ -458,27 +423,17 @@ export async function createThumbnailImageElement(existingElement: HTMLImageElem
return;
}

const url = URL.createObjectURL(canvasInfo.blob);
ready(image, url, timestamp);
ready(image, canvasInfo.blobUrl, timestamp);
}

const activeRender = activeRenders[videoID] ?? renderThumbnail(videoID, width, height, saveVideo, timestamp, isRandomTime).finally(() => {
const activeRender = activeRenders[videoID] ?? renderThumbnail(videoID, width, height, saveVideo, timestamp, true).finally(() => {
delete activeRenders[videoID];
nextInRenderQueue();
});
activeRenders[videoID] = activeRender;

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();
});
}
activeRender.then(result).catch(() => {
failure();
});

return image;
Expand Down Expand Up @@ -733,7 +688,11 @@ export async function replaceThumbnail(element: HTMLElement, videoID: VideoID, b
image.style.display = "none";
image.classList.remove("cb-visible");
thumbnail.classList.add("style-scope");
thumbnail.classList.add("ytd-img-shadow");
if (!isOnV3Extension()) {
thumbnail.classList.add("ytd-img-shadow");
} else {
thumbnail.style.height = String(parseInt(image.getAttribute("width") ?? "0") * 9 / 16) + "px";
}

if (image.classList.contains("amsterdam-playlist-thumbnail")) {
// Playlist header on mobile
Expand All @@ -759,7 +718,9 @@ export async function replaceThumbnail(element: HTMLElement, videoID: VideoID, b
thumbnail.classList.add("ytp-tooltip-bg");
}

thumbnail.style.height = "100%";
if (!isOnV3Extension()) {
thumbnail.style.height = "100%";
}

if ([BrandingLocation.EmbedSuggestions, BrandingLocation.UpNextPreview].includes(brandingLocation)) {
thumbnail.style.width = image.style.width;
Expand Down Expand Up @@ -788,7 +749,11 @@ export async function replaceThumbnail(element: HTMLElement, videoID: VideoID, b
image.style.removeProperty("display");
image.classList.add("cb-visible");
} else {
image.parentElement?.appendChild?.(thumbnail);
if (!isOnV3Extension()) {
image.parentElement?.appendChild?.(thumbnail);
} else {
image.parentElement?.prepend?.(thumbnail);
}
}

// 2024 Oct UI needs to replacement setup
Expand Down Expand Up @@ -893,7 +858,8 @@ function resetToShowOriginalThumbnail(image: HTMLImageElement, brandingLocation:
|| brandingLocation === BrandingLocation.EndRecommendations
|| !!image.closest("ytd-grid-playlist-renderer")
|| isLiveCover(image)
|| image.parentElement?.classList.contains("ytp-cued-thumbnail-overlay")) {
|| image.parentElement?.classList.contains("ytp-cued-thumbnail-overlay")
|| isOnV3Extension()) {
hideCanvas(image);
}

Expand Down Expand Up @@ -986,7 +952,7 @@ function isLiveCover(image: HTMLElement) {
return !!image.parentElement?.querySelector(".cbLiveCover");
}

export function setupPreRenderedThumbnail(videoID: VideoID, timestamp: number, blob: Blob, width = 1280, height = 720, notifyStopRender = true) {
export function setupPreRenderedThumbnail(videoID: VideoID, timestamp: number, blobUrl: string, width = 1280, height = 720, notifyStopRender = true) {
const videoCache = thumbnailDataCache.setupCache(videoID);
const videoObject: RenderedThumbnailVideo = {
video: null,
Expand All @@ -995,7 +961,7 @@ export function setupPreRenderedThumbnail(videoID: VideoID, timestamp: number, b
rendered: true,
onReady: [],
timestamp,
blob,
blobUrl,
fromThumbnailCache: true
}
videoCache.video.push(videoObject);
Expand All @@ -1007,7 +973,7 @@ export function setupPreRenderedThumbnail(videoID: VideoID, timestamp: number, b
const unrendered = videoCache.video.filter(v => v.timestamp === timestamp && !v.rendered);
if (unrendered.length > 0) {
for (const video of unrendered) {
(video as RenderedThumbnailVideo).blob = blob;
(video as RenderedThumbnailVideo).blobUrl = blobUrl;
video.rendered = true;

for (const callback of video.onReady) {
Expand Down
1 change: 1 addition & 0 deletions src/titles/titleFormatterData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@ export const allowlistedWords = new Set([
"DSKY",
"DRO",
"MCSR",
"ELO",
]);

// Can be switched to a trie structure if it grows
Expand Down
Loading
Loading