Fix hover previews on load and channel trailers

This commit is contained in:
Ajay
2022-07-25 02:40:48 -04:00
parent 1a6e6279c8
commit a9186a35e5
2 changed files with 91 additions and 54 deletions

View File

@@ -60,6 +60,7 @@ let sponsorSkipped: boolean[] = [];
let video: HTMLVideoElement; let video: HTMLVideoElement;
let videoMuted = false; // Has it been attempted to be muted let videoMuted = false; // Has it been attempted to be muted
let videoMutationObserver: MutationObserver = null; let videoMutationObserver: MutationObserver = null;
let waitingForNewVideo = false;
// List of videos that have had event listeners added to them // List of videos that have had event listeners added to them
const videosWithEventListeners: HTMLVideoElement[] = []; const videosWithEventListeners: HTMLVideoElement[] = [];
const controlsWithEventListeners: HTMLElement[] = [] const controlsWithEventListeners: HTMLElement[] = []
@@ -97,10 +98,8 @@ const playerButtons: Record<string, {button: HTMLButtonElement, image: HTMLImage
// Direct Links after the config is loaded // Direct Links after the config is loaded
utils.wait(() => Config.config !== null, 1000, 1).then(() => videoIDChange(getYouTubeVideoID(document))); utils.wait(() => Config.config !== null, 1000, 1).then(() => videoIDChange(getYouTubeVideoID(document)));
// wait for hover preview to appear, and refresh attachments if ever found // wait for hover preview to appear, and refresh attachments if ever found
window.addEventListener("DOMContentLoaded", () => { utils.waitForElement(".ytp-inline-preview-ui").then(() => refreshVideoAttachments())
utils.waitForElement(".ytp-inline-preview-ui").then(() => refreshVideoAttachments()) utils.waitForElement("[data-sessionlink='feature=player-title']").then(() => videoIDChange(getYouTubeVideoID(document)))
utils.waitForElement("[data-sessionlink='feature=player-title']").then(() => videoIDChange(getYouTubeVideoID(document)))
});
addPageListeners(); addPageListeners();
addHotkeyListener(); addHotkeyListener();
@@ -316,7 +315,7 @@ function resetValues() {
categoryPill?.setVisibility(false); categoryPill?.setVisibility(false);
} }
async function videoIDChange(id) { async function videoIDChange(id): Promise<void> {
//if the id has not changed return unless the video element has changed //if the id has not changed return unless the video element has changed
if (sponsorVideoID === id && (isVisible(video) || !video)) return; if (sponsorVideoID === id && (isVisible(video) || !video)) return;
@@ -500,6 +499,13 @@ function startSponsorSchedule(includeIntersectingSegments = false, currentTime?:
return; return;
} }
// ensure we are on the correct video
const newVideoID = getYouTubeVideoID(document);
if (newVideoID !== sponsorVideoID) {
videoIDChange(newVideoID);
return;
}
logDebug(`Considering to start skipping: ${!video}, ${video?.paused}`); logDebug(`Considering to start skipping: ${!video}, ${video?.paused}`);
if (!video || video.paused) return; if (!video || video.paused) return;
@@ -681,11 +687,14 @@ function setupVideoMutationListener() {
}); });
} }
function refreshVideoAttachments() { async function refreshVideoAttachments(): Promise<void> {
const newVideo = findValidElement(document.querySelectorAll('video')) as HTMLVideoElement; if (waitingForNewVideo) return;
if (newVideo && newVideo !== video) {
video = newVideo;
waitingForNewVideo = true;
const newVideo = await utils.waitForElement("video", true) as HTMLVideoElement;
waitingForNewVideo = false;
video = newVideo;
if (!videosWithEventListeners.includes(video)) { if (!videosWithEventListeners.includes(video)) {
videosWithEventListeners.push(video); videosWithEventListeners.push(video);
@@ -694,14 +703,14 @@ function refreshVideoAttachments() {
setupCategoryPill(); setupCategoryPill();
} }
// Create a new bar in the new video element
if (previewBar && !utils.findReferenceNode()?.contains(previewBar.container)) { if (previewBar && !utils.findReferenceNode()?.contains(previewBar.container)) {
previewBar.remove(); previewBar.remove();
previewBar = null; previewBar = null;
createPreviewBar(); createPreviewBar();
} }
}
videoIDChange(getYouTubeVideoID(document));
} }
function setupVideoListeners() { function setupVideoListeners() {
@@ -1057,23 +1066,24 @@ function getYouTubeVideoID(document: Document, url?: string): string | boolean {
// clips should never skip, going from clip to full video has no indications. // clips should never skip, going from clip to full video has no indications.
if (url.includes("youtube.com/clip/")) return false; if (url.includes("youtube.com/clip/")) return false;
// skip to document and don't hide if on /embed/ // skip to document and don't hide if on /embed/
if (url.includes("/embed/") && url.includes("youtube.com")) return getYouTubeVideoIDFromDocument(document, false); if (url.includes("/embed/") && url.includes("youtube.com")) return getYouTubeVideoIDFromDocument(false);
// skip to URL if matches youtube watch or invidious or matches youtube pattern // skip to URL if matches youtube watch or invidious or matches youtube pattern
if ((!url.includes("youtube.com")) || url.includes("/watch") || url.includes("/shorts/") || url.includes("playlist")) return getYouTubeVideoIDFromURL(url); if ((!url.includes("youtube.com")) || url.includes("/watch") || url.includes("/shorts/") || url.includes("playlist")) return getYouTubeVideoIDFromURL(url);
// skip to document if matches pattern // skip to document if matches pattern
if (url.includes("/channel/") || url.includes("/user/") || url.includes("/c/")) return getYouTubeVideoIDFromDocument(document); if (url.includes("/channel/") || url.includes("/user/") || url.includes("/c/")) return getYouTubeVideoIDFromDocument();
// not sure, try URL then document // not sure, try URL then document
return getYouTubeVideoIDFromURL(url) || getYouTubeVideoIDFromDocument(document, false); return getYouTubeVideoIDFromURL(url) || getYouTubeVideoIDFromDocument(false);
} }
function getYouTubeVideoIDFromDocument(document: Document, hideIcon = true): string | boolean { function getYouTubeVideoIDFromDocument(hideIcon = true): string | boolean {
// get ID from document (channel trailer / embedded playlist) // get ID from document (channel trailer / embedded playlist)
const videoURL = document.querySelector("[data-sessionlink='feature=player-title']")?.getAttribute("href"); const element = video?.parentElement?.parentElement?.querySelector("[data-sessionlink='feature=player-title']");
const videoURL = element?.getAttribute("href");
if (videoURL) { if (videoURL) {
onInvidious = hideIcon; onInvidious = hideIcon;
return getYouTubeVideoIDFromURL(videoURL); return getYouTubeVideoIDFromURL(videoURL);
} else { } else {
return false return false;
} }
} }

View File

@@ -2,7 +2,7 @@ import Config, { VideoDownvotes } from "./config";
import { CategorySelection, SponsorTime, FetchResponse, BackgroundScriptContainer, Registration, HashedValue, VideoID, SponsorHideType } from "./types"; import { CategorySelection, SponsorTime, FetchResponse, BackgroundScriptContainer, Registration, HashedValue, VideoID, SponsorHideType } from "./types";
import * as CompileConfig from "../config.json"; import * as CompileConfig from "../config.json";
import { findValidElementFromSelector } from "./utils/pageUtils"; import { findValidElement, findValidElementFromSelector } from "./utils/pageUtils";
import { GenericUtils } from "./utils/genericUtils"; import { GenericUtils } from "./utils/genericUtils";
export default class Utils { export default class Utils {
@@ -22,8 +22,9 @@ export default class Utils {
]; ];
/* Used for waitForElement */ /* Used for waitForElement */
waitingMutationObserver:MutationObserver = null; creatingWaitingMutationObserver = false;
waitingElements: { selector: string, callback: (element: Element) => void }[] = []; waitingMutationObserver: MutationObserver = null;
waitingElements: { selector: string, visibleCheck: boolean, callback: (element: Element) => void }[] = [];
constructor(backgroundScriptContainer: BackgroundScriptContainer = null) { constructor(backgroundScriptContainer: BackgroundScriptContainer = null) {
this.backgroundScriptContainer = backgroundScriptContainer; this.backgroundScriptContainer = backgroundScriptContainer;
@@ -34,18 +35,40 @@ export default class Utils {
} }
/* Uses a mutation observer to wait asynchronously */ /* Uses a mutation observer to wait asynchronously */
async waitForElement(selector: string): Promise<Element> { async waitForElement(selector: string, visibleCheck = false): Promise<Element> {
return await new Promise((resolve) => { return await new Promise((resolve) => {
const initialElement = this.getElement(selector, visibleCheck);
if (initialElement) {
resolve(initialElement);
return;
}
this.waitingElements.push({ this.waitingElements.push({
selector, selector,
visibleCheck,
callback: resolve callback: resolve
}); });
if (!this.creatingWaitingMutationObserver) {
this.creatingWaitingMutationObserver = true;
if (document.body) {
this.setupWaitingMutationListener();
} else {
window.addEventListener("DOMContentLoaded", () => {
this.setupWaitingMutationListener();
});
}
}
});
}
private setupWaitingMutationListener(): void {
if (!this.waitingMutationObserver) { if (!this.waitingMutationObserver) {
this.waitingMutationObserver = new MutationObserver(() => { this.waitingMutationObserver = new MutationObserver(() => {
const foundSelectors = []; const foundSelectors = [];
for (const { selector, callback } of this.waitingElements) { for (const { selector, visibleCheck, callback } of this.waitingElements) {
const element = document.querySelector(selector); const element = this.getElement(selector, visibleCheck);
if (element) { if (element) {
callback(element); callback(element);
foundSelectors.push(selector); foundSelectors.push(selector);
@@ -57,6 +80,7 @@ export default class Utils {
if (this.waitingElements.length === 0) { if (this.waitingElements.length === 0) {
this.waitingMutationObserver.disconnect(); this.waitingMutationObserver.disconnect();
this.waitingMutationObserver = null; this.waitingMutationObserver = null;
this.creatingWaitingMutationObserver = false;
} }
}); });
@@ -65,7 +89,10 @@ export default class Utils {
subtree: true subtree: true
}); });
} }
}); }
private getElement(selector: string, visibleCheck: boolean) {
return visibleCheck ? findValidElement(document.querySelectorAll(selector)) : document.querySelector(selector);
} }
containsPermission(permissions: chrome.permissions.Permissions): Promise<boolean> { containsPermission(permissions: chrome.permissions.Permissions): Promise<boolean> {