Skip to content

Commit

Permalink
Merge pull request #2187 from tech234a/master
Browse files Browse the repository at this point in the history
Implement support for YouTube TV (tv.youtube.com)
  • Loading branch information
ajayyy authored Mar 6, 2025
2 parents b0984e9 + 9ab235c commit 5540728
Show file tree
Hide file tree
Showing 13 changed files with 172 additions and 28 deletions.
2 changes: 1 addition & 1 deletion maze-utils
2 changes: 1 addition & 1 deletion public/_locales
Submodule _locales updated 1 files
+8 −0 en/messages.json
24 changes: 24 additions & 0 deletions public/content.css
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@
transition: transform .1s cubic-bezier(0,0,0.2,1);
}

/* Prevent bar from covering highlights on YTTV */
#previewbar.sponsorblock-yttv-container {
z-index: unset;
}

ytu-time-bar.ytu-storyboard {
text-align: center;
}

/* May 2024 hover preview */
.YtPlayerProgressBarProgressBar #previewbar {
transform: none;
Expand Down Expand Up @@ -67,6 +76,11 @@ div:hover > #previewbar.sbNotInvidious {
min-width: 1px;
}

.previewbar-yttv {
height: 10px;
top: 14px;
}

.previewbar.requiredSegment {
transform: scaleY(3);
}
Expand Down Expand Up @@ -184,6 +198,16 @@ div:hover > .sponsorBlockChapterBar {
padding-right: 3.6px;
}

.sbButtonYTTV {
padding-left: 5px !important;
}

/* YTTV only */
.ytu-player-controls > .skipButtonControlBarContainer > div {
padding-left: 5px;
align-content: center;
}

.autoHiding {
overflow: visible !important;
}
Expand Down
4 changes: 2 additions & 2 deletions src/components/ChapterVoteComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class ChapterVoteComponent extends React.Component<ChapterVoteProps, ChapterVote
<>
{/* Upvote Button */}
<button id={"sponsorTimesDownvoteButtonsContainerUpvoteChapter"}
className={"playerButton sbPlayerUpvote ytp-button " + (!this.state.show ? "sbhidden" : "")}
className={"playerButton sbPlayerUpvote ytp-button " + (!this.state.show ? "sbhidden " : " ") + (document.location.host === "tv.youtube.com" ? "sbButtonYTTV" : "")}
draggable="false"
title={chrome.i18n.getMessage("upvoteButtonInfo")}
onClick={(e) => this.vote(e, 1)}>
Expand All @@ -55,7 +55,7 @@ class ChapterVoteComponent extends React.Component<ChapterVoteProps, ChapterVote

{/* Downvote Button */}
<button id={"sponsorTimesDownvoteButtonsContainerDownvoteChapter"}
className={"playerButton sbPlayerDownvote ytp-button " + (!this.state.show ? "sbhidden" : "")}
className={"playerButton sbPlayerDownvote ytp-button " + (!this.state.show ? "sbhidden " : " ") + (document.location.host === "tv.youtube.com" ? "sbButtonYTTV" : "")}
draggable="false"
title={chrome.i18n.getMessage("reportButtonInfo")}
onClick={(e) => {
Expand Down
25 changes: 20 additions & 5 deletions src/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import { ChapterVote } from "./render/ChapterVote";
import { openWarningDialog } from "./utils/warnings";
import { isFirefoxOrSafari, waitFor } from "../maze-utils/src";
import { getErrorMessage, getFormattedTime } from "../maze-utils/src/formating";
import { getChannelIDInfo, getVideo, getIsAdPlaying, getIsLivePremiere, setIsAdPlaying, checkVideoIDChange, getVideoID, getYouTubeVideoID, setupVideoModule, checkIfNewVideoID, isOnInvidious, isOnMobileYouTube, getLastNonInlineVideoID, triggerVideoIDChange, triggerVideoElementChange, getIsInline, getCurrentTime, setCurrentTime, getVideoDuration, verifyCurrentTime, waitForVideo } from "../maze-utils/src/video";
import { getChannelIDInfo, getVideo, getIsAdPlaying, getIsLivePremiere, setIsAdPlaying, checkVideoIDChange, getVideoID, getYouTubeVideoID, setupVideoModule, checkIfNewVideoID, isOnInvidious, isOnMobileYouTube, isOnYTTV, getLastNonInlineVideoID, triggerVideoIDChange, triggerVideoElementChange, getIsInline, getCurrentTime, setCurrentTime, getVideoDuration, verifyCurrentTime, waitForVideo } from "../maze-utils/src/video";
import { Keybind, StorageChangesObject, isSafari, keybindEquals, keybindToString } from "../maze-utils/src/config";
import { findValidElement } from "../maze-utils/src/dom"
import { getHash, HashedValue } from "../maze-utils/src/hash";
Expand Down Expand Up @@ -240,7 +240,8 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo
break;
case "getChannelID":
sendResponse({
channelID: getChannelIDInfo().id
channelID: getChannelIDInfo().id,
isYTTV: (document.location.host === "tv.youtube.com")
});

break;
Expand Down Expand Up @@ -553,6 +554,10 @@ function getPreviewBarAttachElement(): HTMLElement | null {
}, {
// For Vorapis v3
selector: ".ytp-progress-bar-container > .html5-progress-bar > .ytp-progress-list"
}, {
// For YTTV
selector: ".yssi-slider > div.ytu-ss-timeline-container",
isVisibleCheck: false
}
];

Expand All @@ -578,7 +583,7 @@ function createPreviewBar(): void {

if (el) {
const chapterVote = new ChapterVote(voteAsync);
previewBar = new PreviewBar(el, isOnMobileYouTube(), isOnInvidious(), chapterVote, () => importExistingChapters(true));
previewBar = new PreviewBar(el, isOnMobileYouTube(), isOnInvidious(), isOnYTTV(), chapterVote, () => importExistingChapters(true));

updatePreviewBar();
}
Expand Down Expand Up @@ -1147,7 +1152,8 @@ function setupSkipButtonControlBar() {
forceAutoSkip: true
}),
selectSegment,
onMobileYouTube: isOnMobileYouTube()
onMobileYouTube: isOnMobileYouTube(),
onYTTV: isOnYTTV(),
});
}

Expand Down Expand Up @@ -1850,6 +1856,9 @@ function createButton(baseID: string, title: string, callback: () => void, image
newButton.id = baseID + "Button";
newButton.classList.add("playerButton");
newButton.classList.add("ytp-button");
if (isOnYTTV()) {
newButton.setAttribute("style", "width: 40px; height: 40px");
}
newButton.setAttribute("title", chrome.i18n.getMessage(title));
newButton.addEventListener("click", () => {
callback();
Expand Down Expand Up @@ -1924,7 +1933,7 @@ async function updateVisibilityOfPlayerControlsButton(): Promise<void> {
updateEditButtonsOnPlayer();

// Don't show the info button on embeds
if (Config.config.hideInfoButtonPlayerControls || document.URL.includes("/embed/") || isOnInvidious()
if (Config.config.hideInfoButtonPlayerControls || document.URL.includes("/embed/") || isOnInvidious() || isOnYTTV()
|| document.getElementById("sponsorBlockPopupContainer") != null) {
playerButtons.info.button.style.display = "none";
} else {
Expand Down Expand Up @@ -1991,6 +2000,11 @@ function getRealCurrentTime(): number {
}

function startOrEndTimingNewSegment() {
if (isOnYTTV() && getIsLivePremiere()) {
alert(chrome.i18n.getMessage("yttvLiveContentWarning"));
return;
}

verifyCurrentTime();
const roundedTime = Math.round((getRealCurrentTime() + Number.EPSILON) * 1000) / 1000;
if (!isSegmentCreationInProgress()) {
Expand Down Expand Up @@ -2694,6 +2708,7 @@ function showTimeWithoutSkips(skippedDuration: number): void {
// YouTube player time display
const selector =
isOnInvidious() ? ".vjs-duration" :
isOnYTTV() ? ".ypl-full-controls .ypmcs-control .time-info-bar" :
isOnMobileYouTube() ? ".ytwPlayerTimeDisplayContent" :
".ytp-time-display.notranslate .ytp-time-wrapper";
const display = document.querySelector(selector);
Expand Down
108 changes: 94 additions & 14 deletions src/js-components/previewBar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { findValidElement } from "../../maze-utils/src/dom";
import { addCleanupListener } from "../../maze-utils/src/cleanup";
import { hasAutogeneratedChapters, isVisible } from "../utils/pageUtils";
import { isVorapisInstalled } from "../utils/compatibility";
import { isOnYTTV } from "../../maze-utils/src/video";

const TOOLTIP_VISIBLE_CLASS = 'sponsorCategoryTooltipVisible';
const MIN_CHAPTER_SIZE = 0.003;
Expand All @@ -41,6 +42,12 @@ class PreviewBar {
categoryTooltip?: HTMLDivElement;
categoryTooltipContainer?: HTMLElement;
chapterTooltip?: HTMLDivElement;

// ScrubTooltips for YTTV only
categoryScrubTooltip?: HTMLDivElement;
categoryScrubTooltipContainer?: HTMLElement;
chapterScrubTooltip?: HTMLDivElement;

lastSmallestSegment: Record<string, {
index: number;
segment: PreviewBarSegment;
Expand All @@ -49,6 +56,7 @@ class PreviewBar {
parent: HTMLElement;
onMobileYouTube: boolean;
onInvidious: boolean;
onYTTV: boolean;
progressBar: HTMLElement;

segments: PreviewBarSegment[] = [];
Expand All @@ -70,14 +78,19 @@ class PreviewBar {
unfilteredChapterGroups: ChapterGroup[];
chapterGroups: ChapterGroup[];

constructor(parent: HTMLElement, onMobileYouTube: boolean, onInvidious: boolean, chapterVote: ChapterVote, updateExistingChapters: () => void, test=false) {
constructor(parent: HTMLElement, onMobileYouTube: boolean, onInvidious: boolean, onYTTV: boolean, chapterVote: ChapterVote, updateExistingChapters: () => void, test=false) {
if (test) return;
this.container = document.createElement('ul');
this.container.id = 'previewbar';

if (onYTTV) {
this.container.classList.add("sponsorblock-yttv-container");
}

this.parent = parent;
this.onMobileYouTube = onMobileYouTube;
this.onInvidious = onInvidious;
this.onYTTV = onYTTV;
this.chapterVote = chapterVote;
this.updateExistingChapters = updateExistingChapters;

Expand All @@ -97,26 +110,49 @@ class PreviewBar {

// Create label placeholder
this.categoryTooltip = document.createElement("div");
this.categoryTooltip.className = "ytp-tooltip-title sponsorCategoryTooltip";
if (isOnYTTV()) {
this.categoryTooltip.className = "sponsorCategoryTooltip";
} else {
this.categoryTooltip.className = "ytp-tooltip-title sponsorCategoryTooltip";
}
this.chapterTooltip = document.createElement("div");
this.chapterTooltip.className = "ytp-tooltip-title sponsorCategoryTooltip";
if (isOnYTTV()) {
this.chapterTooltip.className = "sponsorCategoryTooltip";
} else {
this.chapterTooltip.className = "ytp-tooltip-title sponsorCategoryTooltip";
}

if (isOnYTTV()) {
this.categoryScrubTooltip = document.createElement("div");
this.categoryScrubTooltip.className = "sponsorCategoryTooltip";
this.chapterScrubTooltip = document.createElement("div");
this.chapterScrubTooltip.className = "sponsorCategoryTooltip";
}

// global chaper tooltip or duration tooltip
// YT, Vorapis, unknown
const tooltipTextWrapper = document.querySelector(".ytp-tooltip-text-wrapper, .ytp-progress-tooltip-text-container") ?? document.querySelector("#progress-bar-container.ytk-player > #hover-time-info");
const originalTooltip = tooltipTextWrapper.querySelector(".ytp-tooltip-title:not(.sponsorCategoryTooltip), .ytp-progress-tooltip-text:not(.sponsorCategoryTooltip)") as HTMLElement;
// global chapter tooltip or duration tooltip
// YT, Vorapis, unknown, YTTV
const tooltipTextWrapper = document.querySelector(".ytp-tooltip-text-wrapper, .ytp-progress-tooltip-text-container, .yssi-slider .ys-seek-details .time-info-bar") ?? document.querySelector("#progress-bar-container.ytk-player > #hover-time-info");
const originalTooltip = tooltipTextWrapper.querySelector(".ytp-tooltip-title:not(.sponsorCategoryTooltip), .ytp-progress-tooltip-text:not(.sponsorCategoryTooltip), .current-time:not(.sponsorCategoryTooltip)") as HTMLElement;
if (!tooltipTextWrapper || !tooltipTextWrapper.parentElement) return;

// Grab the tooltip from the text wrapper as the tooltip doesn't have its classes on init
this.categoryTooltipContainer = tooltipTextWrapper.parentElement;
// YT, Vorapis
const titleTooltip = tooltipTextWrapper.querySelector(".ytp-tooltip-title, .ytp-progress-tooltip-text") as HTMLElement;
// YT, Vorapis, YTTV
const titleTooltip = tooltipTextWrapper.querySelector(".ytp-tooltip-title, .ytp-progress-tooltip-text, .current-time") as HTMLElement;
if (!this.categoryTooltipContainer || !titleTooltip) return;

tooltipTextWrapper.insertBefore(this.categoryTooltip, titleTooltip.nextSibling);
tooltipTextWrapper.insertBefore(this.chapterTooltip, titleTooltip.nextSibling);

const seekBar = document.querySelector(".ytp-progress-bar-container");
if (isOnYTTV()) {
const scrubTooltipTextWrapper = document.querySelector(".yssi-slider .ysl-filmstrip-lens .time-info-bar")
if (!this.categoryTooltipContainer) return;

scrubTooltipTextWrapper.appendChild(this.categoryScrubTooltip);
scrubTooltipTextWrapper.appendChild(this.chapterScrubTooltip);
}

const seekBar = (document.querySelector(".ytp-progress-bar-container, .ypcs-scrub-slider-slot.ytu-player-controls"));
if (!seekBar) return;

let mouseOnSeekBar = false;
Expand Down Expand Up @@ -163,6 +199,12 @@ class PreviewBar {
this.categoryTooltipContainer.classList.remove(TOOLTIP_VISIBLE_CLASS);
originalTooltip.style.removeProperty("display");
}
if (this.onYTTV) {
this.setTooltipTitle(mainSegment, this.categoryTooltip);
this.setTooltipTitle(secondarySegment, this.chapterTooltip);
this.setTooltipTitle(mainSegment, this.categoryScrubTooltip);
this.setTooltipTitle(secondarySegment, this.chapterScrubTooltip);
}
} else {
this.categoryTooltipContainer.classList.add(TOOLTIP_VISIBLE_CLASS);
if (mainSegment !== null && secondarySegment !== null) {
Expand All @@ -174,6 +216,10 @@ class PreviewBar {

this.setTooltipTitle(mainSegment, this.categoryTooltip);
this.setTooltipTitle(secondarySegment, this.chapterTooltip);
if (this.onYTTV) {
this.setTooltipTitle(mainSegment, this.categoryScrubTooltip);
this.setTooltipTitle(secondarySegment, this.chapterScrubTooltip);
}

if (isVorapisInstalled()) {
const tooltipParent = tooltipTextWrapper.parentElement!;
Expand Down Expand Up @@ -226,7 +272,12 @@ class PreviewBar {
}

// On the seek bar
this.parent.prepend(this.container);
if (this.onYTTV) {
// order of sibling elements matters on YTTV
this.parent.insertBefore(this.container, this.parent.firstChild.nextSibling.nextSibling);
} else {
this.parent.prepend(this.container);
}
}

clear(): void {
Expand Down Expand Up @@ -362,6 +413,10 @@ class PreviewBar {
bar.style.marginRight = `${this.chapterMargin}px`;
}

if (this.onYTTV) {
bar.classList.add("previewbar-yttv");
}

return bar;
}

Expand Down Expand Up @@ -868,8 +923,10 @@ class PreviewBar {
})[0];

const chapterButton = this.getChapterButton(chaptersContainer);
chapterButton.classList.remove("ytp-chapter-container-disabled");
chapterButton.disabled = false;
if (chapterButton) {
chapterButton.classList.remove("ytp-chapter-container-disabled");
chapterButton.disabled = false;
}

const chapterTitle = chaptersContainer.querySelector(".ytp-chapter-title-content") as HTMLDivElement;
chapterTitle.style.display = "none";
Expand All @@ -878,6 +935,9 @@ class PreviewBar {
const elem = document.createElement("div");
chapterTitle.parentElement.insertBefore(elem, chapterTitle);
elem.classList.add("sponsorChapterText");
if (document.location.host === "tv.youtube.com") {
elem.style.lineHeight = "initial";
}
return elem;
})()) as HTMLDivElement;
chapterCustomText.innerText = chosenSegment.description || shortCategoryName(chosenSegment.category);
Expand All @@ -890,7 +950,15 @@ class PreviewBar {

if (chosenSegment.source === SponsorSourceType.Server) {
const chapterVoteContainer = this.chapterVote.getContainer();
if (!chapterButton.contains(chapterVoteContainer)) {
if (document.location.host === "tv.youtube.com") {
if (!chaptersContainer.contains(chapterVoteContainer)) {
const oldVoteContainers = document.querySelectorAll("#chapterVote");
if (oldVoteContainers.length > 0) {
oldVoteContainers.forEach((oldVoteContainer) => oldVoteContainer.remove());
}
chaptersContainer.appendChild(chapterVoteContainer);
}
} else if (!chapterButton.contains(chapterVoteContainer)) {
const oldVoteContainers = document.querySelectorAll("#chapterVote");
if (oldVoteContainers.length > 0) {
oldVoteContainers.forEach((oldVoteContainer) => oldVoteContainer.remove());
Expand Down Expand Up @@ -929,6 +997,18 @@ class PreviewBar {
}

private getChaptersContainer(): HTMLElement {
if (document.location.host === "tv.youtube.com") {
if (!document.querySelector(".ytp-chapter-container")) {
const dest = document.querySelector(".ypcs-control-buttons-left");
if (!dest) return null;
const sbChapterContainer = document.createElement("div");
sbChapterContainer.className = "ytp-chapter-container";
const sbChapterTitleContent = document.createElement("div");
sbChapterTitleContent.className = "ytp-chapter-title-content";
sbChapterContainer.appendChild(sbChapterTitleContent);
dest.appendChild(sbChapterContainer);
}
}
return document.querySelector(".ytp-chapter-container") as HTMLElement;
}

Expand Down
Loading

0 comments on commit 5540728

Please sign in to comment.