Merge pull request #2187 from tech234a/master

Implement support for YouTube TV (tv.youtube.com)
This commit is contained in:
Ajay Ramachandran
2025-03-06 03:16:36 -05:00
committed by GitHub
13 changed files with 172 additions and 28 deletions

View File

@@ -31,6 +31,15 @@
transition: transform .1s cubic-bezier(0,0,0.2,1); 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 */ /* May 2024 hover preview */
.YtPlayerProgressBarProgressBar #previewbar { .YtPlayerProgressBarProgressBar #previewbar {
transform: none; transform: none;
@@ -67,6 +76,11 @@ div:hover > #previewbar.sbNotInvidious {
min-width: 1px; min-width: 1px;
} }
.previewbar-yttv {
height: 10px;
top: 14px;
}
.previewbar.requiredSegment { .previewbar.requiredSegment {
transform: scaleY(3); transform: scaleY(3);
} }
@@ -184,6 +198,16 @@ div:hover > .sponsorBlockChapterBar {
padding-right: 3.6px; padding-right: 3.6px;
} }
.sbButtonYTTV {
padding-left: 5px !important;
}
/* YTTV only */
.ytu-player-controls > .skipButtonControlBarContainer > div {
padding-left: 5px;
align-content: center;
}
.autoHiding { .autoHiding {
overflow: visible !important; overflow: visible !important;
} }

View File

@@ -44,7 +44,7 @@ class ChapterVoteComponent extends React.Component<ChapterVoteProps, ChapterVote
<> <>
{/* Upvote Button */} {/* Upvote Button */}
<button id={"sponsorTimesDownvoteButtonsContainerUpvoteChapter"} <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" draggable="false"
title={chrome.i18n.getMessage("upvoteButtonInfo")} title={chrome.i18n.getMessage("upvoteButtonInfo")}
onClick={(e) => this.vote(e, 1)}> onClick={(e) => this.vote(e, 1)}>
@@ -55,7 +55,7 @@ class ChapterVoteComponent extends React.Component<ChapterVoteProps, ChapterVote
{/* Downvote Button */} {/* Downvote Button */}
<button id={"sponsorTimesDownvoteButtonsContainerDownvoteChapter"} <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" draggable="false"
title={chrome.i18n.getMessage("reportButtonInfo")} title={chrome.i18n.getMessage("reportButtonInfo")}
onClick={(e) => { onClick={(e) => {

View File

@@ -35,7 +35,7 @@ import { ChapterVote } from "./render/ChapterVote";
import { openWarningDialog } from "./utils/warnings"; import { openWarningDialog } from "./utils/warnings";
import { isFirefoxOrSafari, waitFor } from "../maze-utils/src"; import { isFirefoxOrSafari, waitFor } from "../maze-utils/src";
import { getErrorMessage, getFormattedTime } from "../maze-utils/src/formating"; 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 { Keybind, StorageChangesObject, isSafari, keybindEquals, keybindToString } from "../maze-utils/src/config";
import { findValidElement } from "../maze-utils/src/dom" import { findValidElement } from "../maze-utils/src/dom"
import { getHash, HashedValue } from "../maze-utils/src/hash"; import { getHash, HashedValue } from "../maze-utils/src/hash";
@@ -240,7 +240,8 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo
break; break;
case "getChannelID": case "getChannelID":
sendResponse({ sendResponse({
channelID: getChannelIDInfo().id channelID: getChannelIDInfo().id,
isYTTV: (document.location.host === "tv.youtube.com")
}); });
break; break;
@@ -553,6 +554,10 @@ function getPreviewBarAttachElement(): HTMLElement | null {
}, { }, {
// For Vorapis v3 // For Vorapis v3
selector: ".ytp-progress-bar-container > .html5-progress-bar > .ytp-progress-list" selector: ".ytp-progress-bar-container > .html5-progress-bar > .ytp-progress-list"
}, {
// For YTTV
selector: ".yssi-slider > div.ytu-ss-timeline-container",
isVisibleCheck: false
} }
]; ];
@@ -578,7 +583,7 @@ function createPreviewBar(): void {
if (el) { if (el) {
const chapterVote = new ChapterVote(voteAsync); 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(); updatePreviewBar();
} }
@@ -1147,7 +1152,8 @@ function setupSkipButtonControlBar() {
forceAutoSkip: true forceAutoSkip: true
}), }),
selectSegment, selectSegment,
onMobileYouTube: isOnMobileYouTube() onMobileYouTube: isOnMobileYouTube(),
onYTTV: isOnYTTV(),
}); });
} }
@@ -1850,6 +1856,9 @@ function createButton(baseID: string, title: string, callback: () => void, image
newButton.id = baseID + "Button"; newButton.id = baseID + "Button";
newButton.classList.add("playerButton"); newButton.classList.add("playerButton");
newButton.classList.add("ytp-button"); newButton.classList.add("ytp-button");
if (isOnYTTV()) {
newButton.setAttribute("style", "width: 40px; height: 40px");
}
newButton.setAttribute("title", chrome.i18n.getMessage(title)); newButton.setAttribute("title", chrome.i18n.getMessage(title));
newButton.addEventListener("click", () => { newButton.addEventListener("click", () => {
callback(); callback();
@@ -1924,7 +1933,7 @@ async function updateVisibilityOfPlayerControlsButton(): Promise<void> {
updateEditButtonsOnPlayer(); updateEditButtonsOnPlayer();
// Don't show the info button on embeds // 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) { || document.getElementById("sponsorBlockPopupContainer") != null) {
playerButtons.info.button.style.display = "none"; playerButtons.info.button.style.display = "none";
} else { } else {
@@ -1991,6 +2000,11 @@ function getRealCurrentTime(): number {
} }
function startOrEndTimingNewSegment() { function startOrEndTimingNewSegment() {
if (isOnYTTV() && getIsLivePremiere()) {
alert(chrome.i18n.getMessage("yttvLiveContentWarning"));
return;
}
verifyCurrentTime(); verifyCurrentTime();
const roundedTime = Math.round((getRealCurrentTime() + Number.EPSILON) * 1000) / 1000; const roundedTime = Math.round((getRealCurrentTime() + Number.EPSILON) * 1000) / 1000;
if (!isSegmentCreationInProgress()) { if (!isSegmentCreationInProgress()) {
@@ -2694,6 +2708,7 @@ function showTimeWithoutSkips(skippedDuration: number): void {
// YouTube player time display // YouTube player time display
const selector = const selector =
isOnInvidious() ? ".vjs-duration" : isOnInvidious() ? ".vjs-duration" :
isOnYTTV() ? ".ypl-full-controls .ypmcs-control .time-info-bar" :
isOnMobileYouTube() ? ".ytwPlayerTimeDisplayContent" : isOnMobileYouTube() ? ".ytwPlayerTimeDisplayContent" :
".ytp-time-display.notranslate .ytp-time-wrapper"; ".ytp-time-display.notranslate .ytp-time-wrapper";
const display = document.querySelector(selector); const display = document.querySelector(selector);

View File

@@ -15,6 +15,7 @@ import { findValidElement } from "../../maze-utils/src/dom";
import { addCleanupListener } from "../../maze-utils/src/cleanup"; import { addCleanupListener } from "../../maze-utils/src/cleanup";
import { hasAutogeneratedChapters, isVisible } from "../utils/pageUtils"; import { hasAutogeneratedChapters, isVisible } from "../utils/pageUtils";
import { isVorapisInstalled } from "../utils/compatibility"; import { isVorapisInstalled } from "../utils/compatibility";
import { isOnYTTV } from "../../maze-utils/src/video";
const TOOLTIP_VISIBLE_CLASS = 'sponsorCategoryTooltipVisible'; const TOOLTIP_VISIBLE_CLASS = 'sponsorCategoryTooltipVisible';
const MIN_CHAPTER_SIZE = 0.003; const MIN_CHAPTER_SIZE = 0.003;
@@ -41,6 +42,12 @@ class PreviewBar {
categoryTooltip?: HTMLDivElement; categoryTooltip?: HTMLDivElement;
categoryTooltipContainer?: HTMLElement; categoryTooltipContainer?: HTMLElement;
chapterTooltip?: HTMLDivElement; chapterTooltip?: HTMLDivElement;
// ScrubTooltips for YTTV only
categoryScrubTooltip?: HTMLDivElement;
categoryScrubTooltipContainer?: HTMLElement;
chapterScrubTooltip?: HTMLDivElement;
lastSmallestSegment: Record<string, { lastSmallestSegment: Record<string, {
index: number; index: number;
segment: PreviewBarSegment; segment: PreviewBarSegment;
@@ -49,6 +56,7 @@ class PreviewBar {
parent: HTMLElement; parent: HTMLElement;
onMobileYouTube: boolean; onMobileYouTube: boolean;
onInvidious: boolean; onInvidious: boolean;
onYTTV: boolean;
progressBar: HTMLElement; progressBar: HTMLElement;
segments: PreviewBarSegment[] = []; segments: PreviewBarSegment[] = [];
@@ -70,14 +78,19 @@ class PreviewBar {
unfilteredChapterGroups: ChapterGroup[]; unfilteredChapterGroups: ChapterGroup[];
chapterGroups: 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; if (test) return;
this.container = document.createElement('ul'); this.container = document.createElement('ul');
this.container.id = 'previewbar'; this.container.id = 'previewbar';
if (onYTTV) {
this.container.classList.add("sponsorblock-yttv-container");
}
this.parent = parent; this.parent = parent;
this.onMobileYouTube = onMobileYouTube; this.onMobileYouTube = onMobileYouTube;
this.onInvidious = onInvidious; this.onInvidious = onInvidious;
this.onYTTV = onYTTV;
this.chapterVote = chapterVote; this.chapterVote = chapterVote;
this.updateExistingChapters = updateExistingChapters; this.updateExistingChapters = updateExistingChapters;
@@ -97,26 +110,49 @@ class PreviewBar {
// Create label placeholder // Create label placeholder
this.categoryTooltip = document.createElement("div"); 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 = document.createElement("div");
this.chapterTooltip.className = "ytp-tooltip-title sponsorCategoryTooltip"; if (isOnYTTV()) {
this.chapterTooltip.className = "sponsorCategoryTooltip";
} else {
this.chapterTooltip.className = "ytp-tooltip-title sponsorCategoryTooltip";
}
// global chaper tooltip or duration tooltip if (isOnYTTV()) {
// YT, Vorapis, unknown this.categoryScrubTooltip = document.createElement("div");
const tooltipTextWrapper = document.querySelector(".ytp-tooltip-text-wrapper, .ytp-progress-tooltip-text-container") ?? document.querySelector("#progress-bar-container.ytk-player > #hover-time-info"); this.categoryScrubTooltip.className = "sponsorCategoryTooltip";
const originalTooltip = tooltipTextWrapper.querySelector(".ytp-tooltip-title:not(.sponsorCategoryTooltip), .ytp-progress-tooltip-text:not(.sponsorCategoryTooltip)") as HTMLElement; this.chapterScrubTooltip = document.createElement("div");
this.chapterScrubTooltip.className = "sponsorCategoryTooltip";
}
// 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; if (!tooltipTextWrapper || !tooltipTextWrapper.parentElement) return;
// Grab the tooltip from the text wrapper as the tooltip doesn't have its classes on init // Grab the tooltip from the text wrapper as the tooltip doesn't have its classes on init
this.categoryTooltipContainer = tooltipTextWrapper.parentElement; this.categoryTooltipContainer = tooltipTextWrapper.parentElement;
// YT, Vorapis // YT, Vorapis, YTTV
const titleTooltip = tooltipTextWrapper.querySelector(".ytp-tooltip-title, .ytp-progress-tooltip-text") as HTMLElement; const titleTooltip = tooltipTextWrapper.querySelector(".ytp-tooltip-title, .ytp-progress-tooltip-text, .current-time") as HTMLElement;
if (!this.categoryTooltipContainer || !titleTooltip) return; if (!this.categoryTooltipContainer || !titleTooltip) return;
tooltipTextWrapper.insertBefore(this.categoryTooltip, titleTooltip.nextSibling); tooltipTextWrapper.insertBefore(this.categoryTooltip, titleTooltip.nextSibling);
tooltipTextWrapper.insertBefore(this.chapterTooltip, 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; if (!seekBar) return;
let mouseOnSeekBar = false; let mouseOnSeekBar = false;
@@ -163,6 +199,12 @@ class PreviewBar {
this.categoryTooltipContainer.classList.remove(TOOLTIP_VISIBLE_CLASS); this.categoryTooltipContainer.classList.remove(TOOLTIP_VISIBLE_CLASS);
originalTooltip.style.removeProperty("display"); 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 { } else {
this.categoryTooltipContainer.classList.add(TOOLTIP_VISIBLE_CLASS); this.categoryTooltipContainer.classList.add(TOOLTIP_VISIBLE_CLASS);
if (mainSegment !== null && secondarySegment !== null) { if (mainSegment !== null && secondarySegment !== null) {
@@ -174,6 +216,10 @@ class PreviewBar {
this.setTooltipTitle(mainSegment, this.categoryTooltip); this.setTooltipTitle(mainSegment, this.categoryTooltip);
this.setTooltipTitle(secondarySegment, this.chapterTooltip); this.setTooltipTitle(secondarySegment, this.chapterTooltip);
if (this.onYTTV) {
this.setTooltipTitle(mainSegment, this.categoryScrubTooltip);
this.setTooltipTitle(secondarySegment, this.chapterScrubTooltip);
}
if (isVorapisInstalled()) { if (isVorapisInstalled()) {
const tooltipParent = tooltipTextWrapper.parentElement!; const tooltipParent = tooltipTextWrapper.parentElement!;
@@ -226,7 +272,12 @@ class PreviewBar {
} }
// On the seek bar // 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 { clear(): void {
@@ -362,6 +413,10 @@ class PreviewBar {
bar.style.marginRight = `${this.chapterMargin}px`; bar.style.marginRight = `${this.chapterMargin}px`;
} }
if (this.onYTTV) {
bar.classList.add("previewbar-yttv");
}
return bar; return bar;
} }
@@ -868,8 +923,10 @@ class PreviewBar {
})[0]; })[0];
const chapterButton = this.getChapterButton(chaptersContainer); const chapterButton = this.getChapterButton(chaptersContainer);
chapterButton.classList.remove("ytp-chapter-container-disabled"); if (chapterButton) {
chapterButton.disabled = false; chapterButton.classList.remove("ytp-chapter-container-disabled");
chapterButton.disabled = false;
}
const chapterTitle = chaptersContainer.querySelector(".ytp-chapter-title-content") as HTMLDivElement; const chapterTitle = chaptersContainer.querySelector(".ytp-chapter-title-content") as HTMLDivElement;
chapterTitle.style.display = "none"; chapterTitle.style.display = "none";
@@ -878,6 +935,9 @@ class PreviewBar {
const elem = document.createElement("div"); const elem = document.createElement("div");
chapterTitle.parentElement.insertBefore(elem, chapterTitle); chapterTitle.parentElement.insertBefore(elem, chapterTitle);
elem.classList.add("sponsorChapterText"); elem.classList.add("sponsorChapterText");
if (document.location.host === "tv.youtube.com") {
elem.style.lineHeight = "initial";
}
return elem; return elem;
})()) as HTMLDivElement; })()) as HTMLDivElement;
chapterCustomText.innerText = chosenSegment.description || shortCategoryName(chosenSegment.category); chapterCustomText.innerText = chosenSegment.description || shortCategoryName(chosenSegment.category);
@@ -890,7 +950,15 @@ class PreviewBar {
if (chosenSegment.source === SponsorSourceType.Server) { if (chosenSegment.source === SponsorSourceType.Server) {
const chapterVoteContainer = this.chapterVote.getContainer(); 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"); const oldVoteContainers = document.querySelectorAll("#chapterVote");
if (oldVoteContainers.length > 0) { if (oldVoteContainers.length > 0) {
oldVoteContainers.forEach((oldVoteContainer) => oldVoteContainer.remove()); oldVoteContainers.forEach((oldVoteContainer) => oldVoteContainer.remove());
@@ -929,6 +997,18 @@ class PreviewBar {
} }
private getChaptersContainer(): HTMLElement { 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; return document.querySelector(".ytp-chapter-container") as HTMLElement;
} }

View File

@@ -9,6 +9,7 @@ export interface SkipButtonControlBarProps {
skip: (segment: SponsorTime) => void; skip: (segment: SponsorTime) => void;
selectSegment: (UUID: SegmentUUID) => void; selectSegment: (UUID: SegmentUUID) => void;
onMobileYouTube: boolean; onMobileYouTube: boolean;
onYTTV: boolean;
} }
export class SkipButtonControlBar { export class SkipButtonControlBar {
@@ -21,6 +22,7 @@ export class SkipButtonControlBar {
showKeybindHint = true; showKeybindHint = true;
onMobileYouTube: boolean; onMobileYouTube: boolean;
onYTTV: boolean;
enabled = false; enabled = false;
@@ -40,6 +42,7 @@ export class SkipButtonControlBar {
constructor(props: SkipButtonControlBarProps) { constructor(props: SkipButtonControlBarProps) {
this.skip = props.skip; this.skip = props.skip;
this.onMobileYouTube = props.onMobileYouTube; this.onMobileYouTube = props.onMobileYouTube;
this.onYTTV = props.onYTTV;
this.container = document.createElement("div"); this.container = document.createElement("div");
this.container.classList.add("skipButtonControlBarContainer"); this.container.classList.add("skipButtonControlBarContainer");
@@ -50,6 +53,10 @@ export class SkipButtonControlBar {
this.skipIcon.src = chrome.runtime.getURL("icons/skipIcon.svg"); this.skipIcon.src = chrome.runtime.getURL("icons/skipIcon.svg");
this.skipIcon.classList.add("ytp-button"); this.skipIcon.classList.add("ytp-button");
this.skipIcon.id = "sbSkipIconControlBarImage"; this.skipIcon.id = "sbSkipIconControlBarImage";
if (this.onYTTV) {
this.skipIcon.style.width = "24px";
this.skipIcon.style.height = "24px";
}
this.textContainer = document.createElement("div"); this.textContainer = document.createElement("div");
@@ -84,7 +91,7 @@ export class SkipButtonControlBar {
this.chapterText = document.querySelector(".ytp-chapter-container"); this.chapterText = document.querySelector(".ytp-chapter-container");
if (mountingContainer && !mountingContainer.contains(this.container)) { if (mountingContainer && !mountingContainer.contains(this.container)) {
if (this.onMobileYouTube) { if (this.onMobileYouTube || this.onYTTV) {
mountingContainer.appendChild(this.container); mountingContainer.appendChild(this.container);
} else { } else {
mountingContainer.insertBefore(this.container, this.chapterText); mountingContainer.insertBefore(this.container, this.chapterText);
@@ -101,8 +108,10 @@ export class SkipButtonControlBar {
} }
private getMountingContainer(): HTMLElement { private getMountingContainer(): HTMLElement {
if (!this.onMobileYouTube) { if (!this.onMobileYouTube && !this.onYTTV) {
return document.querySelector(".ytp-left-controls"); return document.querySelector(".ytp-left-controls");
} else if (this.onYTTV) {
return document.querySelector(".ypcs-control-buttons-left");
} else { } else {
return document.getElementById("player-container-id"); return document.getElementById("player-container-id");
} }

View File

@@ -86,6 +86,7 @@ interface GetVideoIdResponse {
export interface GetChannelIDResponse { export interface GetChannelIDResponse {
channelID: string; channelID: string;
isYTTV: boolean;
} }
export interface SponsorStartResponse { export interface SponsorStartResponse {

View File

@@ -930,7 +930,11 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
//get the channel url //get the channel url
const response = await sendTabMessageAsync({ message: 'getChannelID' }) as GetChannelIDResponse; const response = await sendTabMessageAsync({ message: 'getChannelID' }) as GetChannelIDResponse;
if (!response.channelID) { if (!response.channelID) {
alert(chrome.i18n.getMessage("channelDataNotFound") + " https://github.com/ajayyy/SponsorBlock/issues/753"); if (response.isYTTV) {
alert(chrome.i18n.getMessage("yttvNoChannelWhitelist"));
} else {
alert(chrome.i18n.getMessage("channelDataNotFound") + " https://github.com/ajayyy/SponsorBlock/issues/753");
}
return; return;
} }

View File

@@ -20,6 +20,10 @@ export class ChapterVote {
this.container.id = "chapterVote"; this.container.id = "chapterVote";
this.container.style.height = "100%"; this.container.style.height = "100%";
if (document.location.host === "tv.youtube.com") {
this.container.style.lineHeight = "initial";
}
this.root = createRoot(this.container); this.root = createRoot(this.container);
this.root.render(<ChapterVoteComponent ref={this.ref} vote={vote} />); this.root.render(<ChapterVoteComponent ref={this.ref} vote={vote} />);
} }

View File

@@ -249,6 +249,7 @@ export default class Utils {
".main-video-section > .video-container", // Cloudtube ".main-video-section > .video-container", // Cloudtube
".shaka-video-container", // Piped ".shaka-video-container", // Piped
"#player-container.ytk-player", // YT Kids "#player-container.ytk-player", // YT Kids
"#id-tv-container" // YTTV
]; ];
let referenceNode = findValidElementFromSelector(selectors) let referenceNode = findValidElementFromSelector(selectors)

View File

@@ -13,7 +13,9 @@ export function getControls(): HTMLElement {
// Piped shaka player // Piped shaka player
".shaka-bottom-controls", ".shaka-bottom-controls",
// Vorapis v3 // Vorapis v3
".html5-player-chrome" ".html5-player-chrome",
// tv.youtube.com
".ypcs-control-buttons-right"
]; ];
for (const controlsSelector of controlsSelectors) { for (const controlsSelector of controlsSelectors) {

View File

@@ -1,9 +1,13 @@
/**
* @jest-environment jsdom
*/
import PreviewBar, { PreviewBarSegment } from "../src/js-components/previewBar"; import PreviewBar, { PreviewBarSegment } from "../src/js-components/previewBar";
describe("createChapterRenderGroups", () => { describe("createChapterRenderGroups", () => {
let previewBar: PreviewBar; let previewBar: PreviewBar;
beforeEach(() => { beforeEach(() => {
previewBar = new PreviewBar(null, null, null, null, null, true); previewBar = new PreviewBar(null, null, null, null, null, null, true);
}) })
it("Two unrelated times", () => { it("Two unrelated times", () => {