mirror of
https://github.com/ajayyy/SponsorBlock.git
synced 2024-11-10 09:07:45 +01:00
Merge pull request #1116 from mchangrh/embeddedVideos
Support embedded videos
This commit is contained in:
commit
2db35a624a
3 changed files with 98 additions and 18 deletions
|
@ -17,7 +17,7 @@ import { getCategoryActionType } from "./utils/categoryUtils";
|
|||
import { SkipButtonControlBar } from "./js-components/skipButtonControlBar";
|
||||
import { Tooltip } from "./render/Tooltip";
|
||||
import { getStartTimeFromUrl } from "./utils/urlParser";
|
||||
import { getControls } from "./utils/pageUtils";
|
||||
import { findValidElement, getControls, isVisible } from "./utils/pageUtils";
|
||||
import { CategoryPill } from "./render/CategoryPill";
|
||||
import { AnimationUtils } from "./utils/animationUtils";
|
||||
import { GenericUtils } from "./utils/genericUtils";
|
||||
|
@ -91,7 +91,8 @@ let controls: HTMLElement | null = null;
|
|||
const playerButtons: Record<string, {button: HTMLButtonElement, image: HTMLImageElement, setupListener: boolean}> = {};
|
||||
|
||||
// Direct Links after the config is loaded
|
||||
utils.wait(() => Config.config !== null, 1000, 1).then(() => videoIDChange(getYouTubeVideoID(document.URL)));
|
||||
utils.wait(() => Config.config !== null, 1000, 1).then(() => videoIDChange(getYouTubeVideoID(document)));
|
||||
addPageListeners();
|
||||
addHotkeyListener();
|
||||
|
||||
//the amount of times the sponsor lookup has retried
|
||||
|
@ -142,7 +143,7 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo
|
|||
//messages from popup script
|
||||
switch(request.message){
|
||||
case "update":
|
||||
videoIDChange(getYouTubeVideoID(document.URL));
|
||||
videoIDChange(getYouTubeVideoID(document));
|
||||
break;
|
||||
case "sponsorStart":
|
||||
startOrEndTimingNewSegment()
|
||||
|
@ -272,8 +273,8 @@ function resetValues() {
|
|||
}
|
||||
|
||||
async function videoIDChange(id) {
|
||||
//if the id has not changed return
|
||||
if (sponsorVideoID === id) return;
|
||||
//if the id has not changed return unless the video element has changed
|
||||
if (sponsorVideoID === id && isVisible(video)) return;
|
||||
|
||||
//set the global videoID
|
||||
sponsorVideoID = id;
|
||||
|
@ -383,7 +384,7 @@ function createPreviewBar(): void {
|
|||
];
|
||||
|
||||
for (const selector of progressElementSelectors) {
|
||||
const el = document.querySelector<HTMLElement>(selector);
|
||||
const el = findValidElement(document.querySelectorAll(selector));
|
||||
|
||||
if (el) {
|
||||
previewBar = new PreviewBar(el, onMobileYouTube, onInvidious);
|
||||
|
@ -404,6 +405,16 @@ function durationChangeListener(): void {
|
|||
updatePreviewBar();
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered once the video is ready.
|
||||
* This is mainly to attach to embedded players who don't have a video element visible.
|
||||
*/
|
||||
function videoOnReadyListener(): void {
|
||||
createPreviewBar();
|
||||
updatePreviewBar();
|
||||
createButtons();
|
||||
}
|
||||
|
||||
function cancelSponsorSchedule(): void {
|
||||
if (currentSkipSchedule !== null) {
|
||||
clearTimeout(currentSkipSchedule);
|
||||
|
@ -515,7 +526,7 @@ function inMuteSegment(currentTime: number): boolean {
|
|||
* This makes sure the videoID is still correct and if the sponsorTime is included
|
||||
*/
|
||||
function incorrectVideoCheck(videoID?: string, sponsorTime?: SponsorTime): boolean {
|
||||
const currentVideoID = getYouTubeVideoID(document.URL);
|
||||
const currentVideoID = getYouTubeVideoID(document);
|
||||
if (currentVideoID !== (videoID || sponsorVideoID) || (sponsorTime
|
||||
&& (!sponsorTimes || !sponsorTimes?.some((time) => time.segment === sponsorTime.segment))
|
||||
&& !sponsorTimesSubmitting.some((time) => time.segment === sponsorTime.segment))) {
|
||||
|
@ -546,7 +557,7 @@ function setupVideoMutationListener() {
|
|||
}
|
||||
|
||||
function refreshVideoAttachments() {
|
||||
const newVideo = document.querySelector('video');
|
||||
const newVideo = findValidElement(document.querySelectorAll('video')) as HTMLVideoElement;
|
||||
if (newVideo && newVideo !== video) {
|
||||
video = newVideo;
|
||||
|
||||
|
@ -557,11 +568,20 @@ function refreshVideoAttachments() {
|
|||
setupSkipButtonControlBar();
|
||||
setupCategoryPill();
|
||||
}
|
||||
|
||||
// Create a new bar in the new video element
|
||||
if (previewBar && !utils.findReferenceNode()?.contains(previewBar.container)) {
|
||||
previewBar.remove();
|
||||
previewBar = null;
|
||||
|
||||
createPreviewBar();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setupVideoListeners() {
|
||||
//wait until it is loaded
|
||||
video.addEventListener('loadstart', videoOnReadyListener)
|
||||
video.addEventListener('durationchange', durationChangeListener);
|
||||
|
||||
if (!Config.config.disableSkipping) {
|
||||
|
@ -653,7 +673,7 @@ function setupCategoryPill() {
|
|||
}
|
||||
|
||||
async function sponsorsLookup(id: string, keepOldSubmissions = true) {
|
||||
if (!video) refreshVideoAttachments();
|
||||
if (!video || !isVisible(video)) refreshVideoAttachments();
|
||||
//there is still no video here
|
||||
if (!video) {
|
||||
setTimeout(() => sponsorsLookup(id), 100);
|
||||
|
@ -924,8 +944,30 @@ async function getVideoInfo(): Promise<void> {
|
|||
}
|
||||
}
|
||||
|
||||
function getYouTubeVideoID(url: string): string | boolean {
|
||||
// For YouTube TV support
|
||||
function getYouTubeVideoID(document: Document): string | boolean {
|
||||
const url = document.URL;
|
||||
// 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);
|
||||
// skip to document and don't hide if on /embed/
|
||||
if (url.includes("/embed/")) return getYouTubeVideoIDFromDocument(document, false);
|
||||
// skip to document if matches pattern
|
||||
if (url.includes("/channel/") || url.includes("/user/") || url.includes("/c/")) return getYouTubeVideoIDFromDocument(document);
|
||||
// not sure, try URL then document
|
||||
return getYouTubeVideoIDFromURL(url) || getYouTubeVideoIDFromDocument(document);
|
||||
}
|
||||
|
||||
function getYouTubeVideoIDFromDocument(document: Document, hideIcon = true): string | boolean {
|
||||
// get ID from document (channel trailer / embedded playlist)
|
||||
const videoURL = document.querySelector("[data-sessionlink='feature=player-title']")?.getAttribute("href");
|
||||
if (videoURL) {
|
||||
onInvidious = hideIcon;
|
||||
return getYouTubeVideoIDFromURL(videoURL);
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function getYouTubeVideoIDFromURL(url: string): string | boolean {
|
||||
if(url.startsWith("https://www.youtube.com/tv#/")) url = url.replace("#", "");
|
||||
|
||||
//Attempt to parse url
|
||||
|
@ -945,7 +987,7 @@ function getYouTubeVideoID(url: string): string | boolean {
|
|||
} else if (!["m.youtube.com", "www.youtube.com", "www.youtube-nocookie.com", "music.youtube.com"].includes(urlObject.host)) {
|
||||
if (!Config.config) {
|
||||
// Call this later, in case this is an Invidious tab
|
||||
utils.wait(() => Config.config !== null).then(() => videoIDChange(getYouTubeVideoID(url)));
|
||||
utils.wait(() => Config.config !== null).then(() => videoIDChange(getYouTubeVideoIDFromURL(url)));
|
||||
}
|
||||
|
||||
return false
|
||||
|
@ -963,7 +1005,7 @@ function getYouTubeVideoID(url: string): string | boolean {
|
|||
console.error("[SB] Video ID not valid for " + url);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1862,6 +1904,16 @@ function getSegmentsMessage(sponsorTimes: SponsorTime[]): string {
|
|||
return sponsorTimesMessage;
|
||||
}
|
||||
|
||||
function addPageListeners(): void {
|
||||
const refreshListners = () => {
|
||||
if (!isVisible(video)) {
|
||||
refreshVideoAttachments();
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("yt-navigate-finish", refreshListners);
|
||||
}
|
||||
|
||||
function addHotkeyListener(): void {
|
||||
document.addEventListener("keydown", hotkeyListener);
|
||||
}
|
||||
|
|
15
src/utils.ts
15
src/utils.ts
|
@ -2,6 +2,7 @@ import Config from "./config";
|
|||
import { CategorySelection, SponsorTime, FetchResponse, BackgroundScriptContainer, Registration } from "./types";
|
||||
|
||||
import * as CompileConfig from "../config.json";
|
||||
import { findValidElementFromSelector } from "./utils/pageUtils";
|
||||
import { GenericUtils } from "./utils/genericUtils";
|
||||
|
||||
export default class Utils {
|
||||
|
@ -329,11 +330,15 @@ export default class Utils {
|
|||
}
|
||||
|
||||
findReferenceNode(): HTMLElement {
|
||||
let referenceNode = document.getElementById("player-container-id")
|
||||
?? document.getElementById("movie_player")
|
||||
?? document.querySelector("#main-panel.ytmusic-player-page") // YouTube music
|
||||
?? document.querySelector("#player-container .video-js") // Invidious
|
||||
?? document.querySelector(".main-video-section > .video-container"); // Cloudtube
|
||||
const selectors = [
|
||||
"#player-container-id",
|
||||
"#movie_player",
|
||||
"#c4-player", // Channel Trailer
|
||||
"#main-panel.ytmusic-player-page", // YouTube music
|
||||
"#player-container .video-js", // Invidious
|
||||
".main-video-section > .video-container" // Cloudtube
|
||||
]
|
||||
let referenceNode = findValidElementFromSelector(selectors)
|
||||
if (referenceNode == null) {
|
||||
//for embeds
|
||||
const player = document.getElementById("player");
|
||||
|
|
|
@ -17,4 +17,27 @@ export function getControls(): HTMLElement | false {
|
|||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isVisible(element: HTMLElement): boolean {
|
||||
return element && element.offsetWidth > 0 && element.offsetHeight > 0;
|
||||
}
|
||||
|
||||
export function findValidElementFromSelector(selectors: string[]): HTMLElement {
|
||||
return findValidElementFromGenerator(selectors, (selector) => document.querySelector(selector));
|
||||
}
|
||||
|
||||
export function findValidElement(elements: HTMLElement[] | NodeListOf<HTMLElement>): HTMLElement {
|
||||
return findValidElementFromGenerator(elements);
|
||||
}
|
||||
|
||||
function findValidElementFromGenerator<T>(objects: T[] | NodeListOf<HTMLElement>, generator?: (obj: T) => HTMLElement): HTMLElement {
|
||||
for (const obj of objects) {
|
||||
const element = generator ? generator(obj as T) : obj as HTMLElement;
|
||||
if (element && isVisible(element)) {
|
||||
return element;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
Loading…
Reference in a new issue