mirror of
https://github.com/ajayyy/SponsorBlock.git
synced 2025-12-10 13:37:04 +03:00
Only create one chapter bar, support videos with no segments and preview segments
This commit is contained in:
@@ -33,7 +33,9 @@ class PreviewBar {
|
|||||||
videoDuration = 0;
|
videoDuration = 0;
|
||||||
|
|
||||||
// For chapter bar
|
// For chapter bar
|
||||||
hoveredSection?: HTMLElement;
|
hoveredSection: HTMLElement;
|
||||||
|
customChaptersBar: HTMLElement;
|
||||||
|
chaptersBarSegments: PreviewBarSegment[];
|
||||||
|
|
||||||
constructor(parent: HTMLElement, onMobileYouTube: boolean, onInvidious: boolean) {
|
constructor(parent: HTMLElement, onMobileYouTube: boolean, onInvidious: boolean) {
|
||||||
this.container = document.createElement('ul');
|
this.container = document.createElement('ul');
|
||||||
@@ -44,6 +46,7 @@ class PreviewBar {
|
|||||||
this.onInvidious = onInvidious;
|
this.onInvidious = onInvidious;
|
||||||
|
|
||||||
this.createElement(parent);
|
this.createElement(parent);
|
||||||
|
this.createChapterMutationObserver();
|
||||||
|
|
||||||
this.setupHoverText();
|
this.setupHoverText();
|
||||||
}
|
}
|
||||||
@@ -218,7 +221,93 @@ class PreviewBar {
|
|||||||
|
|
||||||
const progressBar = document.querySelector('.ytp-progress-bar');
|
const progressBar = document.querySelector('.ytp-progress-bar');
|
||||||
const chapterBar = document.querySelector(".ytp-chapters-container:not(.sponsorBlockChapterBar)") as HTMLElement;
|
const chapterBar = document.querySelector(".ytp-chapters-container:not(.sponsorBlockChapterBar)") as HTMLElement;
|
||||||
if (!progressBar || !chapterBar || segments?.length <= 0) return;
|
if (!progressBar || !chapterBar) return;
|
||||||
|
|
||||||
|
if (segments === this.chaptersBarSegments) return;
|
||||||
|
this.customChaptersBar?.remove();
|
||||||
|
|
||||||
|
if (segments?.length <= 0) {
|
||||||
|
chapterBar.style.removeProperty("display");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create it from cloning
|
||||||
|
const newChapterBar = chapterBar.cloneNode(true) as HTMLElement;
|
||||||
|
newChapterBar.classList.add("sponsorBlockChapterBar");
|
||||||
|
newChapterBar.style.removeProperty("display");
|
||||||
|
const originalSection = newChapterBar.querySelector(".ytp-chapter-hover-container");
|
||||||
|
|
||||||
|
this.customChaptersBar = newChapterBar;
|
||||||
|
this.chaptersBarSegments = segments;
|
||||||
|
|
||||||
|
// Merge overlapping chapters
|
||||||
|
const mergedSegments = segments.filter((segment) => getCategoryActionType(segment.category) !== CategoryActionType.POI
|
||||||
|
&& segment.segment.length === 2)
|
||||||
|
.reduce((acc, curr) => {
|
||||||
|
if (acc.length === 0 || curr.segment[0] > acc[acc.length - 1].segment[1]) {
|
||||||
|
acc.push(curr);
|
||||||
|
} else {
|
||||||
|
acc[acc.length - 1].segment[1] = Math.max(acc[acc.length - 1].segment[1], curr.segment[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, [] as PreviewBarSegment[]);
|
||||||
|
|
||||||
|
// Modify it to have sections for each segment
|
||||||
|
for (let i = 0; i < mergedSegments.length; i++) {
|
||||||
|
const segment = mergedSegments[i];
|
||||||
|
if (i === 0 && segment.segment[0] > 0) {
|
||||||
|
const newBlankSection = originalSection.cloneNode(true) as HTMLElement;
|
||||||
|
const blankDuration = segment.segment[0];
|
||||||
|
|
||||||
|
this.setupChapterSection(newBlankSection, blankDuration);
|
||||||
|
newChapterBar.appendChild(newBlankSection);
|
||||||
|
}
|
||||||
|
|
||||||
|
const duration = segment.segment[1] - segment.segment[0];
|
||||||
|
const newSection = originalSection.cloneNode(true) as HTMLElement;
|
||||||
|
|
||||||
|
this.setupChapterSection(newSection, duration);
|
||||||
|
newChapterBar.appendChild(newSection);
|
||||||
|
|
||||||
|
if (segment.segment[1] < this.videoDuration) {
|
||||||
|
const nextSegment = mergedSegments[i + 1];
|
||||||
|
const newBlankSection = originalSection.cloneNode(true) as HTMLElement;
|
||||||
|
const nextTime = nextSegment ? nextSegment.segment[0] : this.videoDuration;
|
||||||
|
const blankDuration = nextTime - segment.segment[1];
|
||||||
|
|
||||||
|
this.setupChapterSection(newBlankSection, blankDuration);
|
||||||
|
newChapterBar.appendChild(newBlankSection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide old bar
|
||||||
|
chapterBar.style.display = "none";
|
||||||
|
|
||||||
|
originalSection.remove();
|
||||||
|
if (this.container?.parentElement === progressBar) {
|
||||||
|
progressBar.insertBefore(newChapterBar, this.container.nextSibling);
|
||||||
|
} else {
|
||||||
|
progressBar.prepend(newChapterBar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupChapterSection(section: HTMLElement, duration: number): void {
|
||||||
|
section.style.marginRight = "2px";
|
||||||
|
section.style.width = `calc(${this.timeToPercentage(duration)} - 2px)`;
|
||||||
|
section.setAttribute("decimal-width", String(this.timeToDecimal(duration)));
|
||||||
|
|
||||||
|
section.addEventListener("mouseenter", () => {
|
||||||
|
this.hoveredSection?.classList.remove("ytp-exp-chapter-hover-effect");
|
||||||
|
section.classList.add("ytp-exp-chapter-hover-effect");
|
||||||
|
this.hoveredSection = section;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private createChapterMutationObserver(): void {
|
||||||
|
const progressBar = document.querySelector('.ytp-progress-bar');
|
||||||
|
const chapterBar = document.querySelector(".ytp-chapters-container:not(.sponsorBlockChapterBar)") as HTMLElement;
|
||||||
|
if (!progressBar || !chapterBar) return;
|
||||||
|
|
||||||
const observer = new MutationObserver((mutations) => {
|
const observer = new MutationObserver((mutations) => {
|
||||||
const changes: Record<string, MutationRecord> = {};
|
const changes: Record<string, MutationRecord> = {};
|
||||||
@@ -230,13 +319,23 @@ class PreviewBar {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.updateChapterMutation(changes, progressBar);
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.observe(chapterBar, {
|
||||||
|
subtree: true,
|
||||||
|
attributes: true,
|
||||||
|
attributeFilter: ["style", "class"],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateChapterMutation(changes: Record<string, MutationRecord>, progressBar: HTMLElement): void {
|
||||||
// Go through each newly generated chapter bar and update the width based on changes array
|
// Go through each newly generated chapter bar and update the width based on changes array
|
||||||
const generatedChapterBar = document.querySelector(".sponsorBlockChapterBar");
|
if (this.customChaptersBar) {
|
||||||
if (generatedChapterBar) {
|
|
||||||
// Width reached so far in decimal percent
|
// Width reached so far in decimal percent
|
||||||
let cursor = 0;
|
let cursor = 0;
|
||||||
|
|
||||||
const sections = generatedChapterBar.querySelectorAll(".ytp-chapter-hover-container") as NodeListOf<HTMLElement>;
|
const sections = this.customChaptersBar.querySelectorAll(".ytp-chapter-hover-container") as NodeListOf<HTMLElement>;
|
||||||
for (const section of sections) {
|
for (const section of sections) {
|
||||||
const sectionWidthDecimal = parseFloat(section.getAttribute("decimal-width"));
|
const sectionWidthDecimal = parseFloat(section.getAttribute("decimal-width"));
|
||||||
|
|
||||||
@@ -267,80 +366,6 @@ class PreviewBar {
|
|||||||
cursor += sectionWidthDecimal;
|
cursor += sectionWidthDecimal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
observer.observe(chapterBar, {
|
|
||||||
subtree: true,
|
|
||||||
attributes: true,
|
|
||||||
attributeFilter: ["style", "class"],
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create it from cloning
|
|
||||||
const newChapterBar = chapterBar.cloneNode(true) as HTMLElement;
|
|
||||||
newChapterBar.classList.add("sponsorBlockChapterBar");
|
|
||||||
const originalSectionClone = newChapterBar.querySelector(".ytp-chapter-hover-container");
|
|
||||||
|
|
||||||
// Merge overlapping chapters
|
|
||||||
const mergedSegments = segments.filter((segment) => getCategoryActionType(segment.category) !== CategoryActionType.POI)
|
|
||||||
.reduce((acc, curr) => {
|
|
||||||
if (acc.length === 0 || curr.segment[0] > acc[acc.length - 1].segment[1]) {
|
|
||||||
acc.push(curr);
|
|
||||||
} else {
|
|
||||||
acc[acc.length - 1].segment[1] = Math.max(acc[acc.length - 1].segment[1], curr.segment[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, [] as PreviewBarSegment[]);
|
|
||||||
|
|
||||||
// Modify it to have sections for each segment
|
|
||||||
for (let i = 0; i < mergedSegments.length; i++) {
|
|
||||||
const segment = mergedSegments[i];
|
|
||||||
if (i === 0 && segment.segment[0] > 0) {
|
|
||||||
const newBlankSection = originalSectionClone.cloneNode(true) as HTMLElement;
|
|
||||||
const blankDuration = segment.segment[0];
|
|
||||||
|
|
||||||
this.setupChapterSection(newBlankSection, blankDuration);
|
|
||||||
newChapterBar.appendChild(newBlankSection);
|
|
||||||
}
|
|
||||||
|
|
||||||
const duration = segment.segment[1] - segment.segment[0];
|
|
||||||
const newSection = originalSectionClone.cloneNode(true) as HTMLElement;
|
|
||||||
|
|
||||||
this.setupChapterSection(newSection, duration);
|
|
||||||
newChapterBar.appendChild(newSection);
|
|
||||||
|
|
||||||
if (segment.segment[1] < this.videoDuration) {
|
|
||||||
const nextSegment = mergedSegments[i + 1];
|
|
||||||
const newBlankSection = originalSectionClone.cloneNode(true) as HTMLElement;
|
|
||||||
const nextTime = nextSegment ? nextSegment.segment[0] : this.videoDuration;
|
|
||||||
const blankDuration = nextTime - segment.segment[1];
|
|
||||||
|
|
||||||
this.setupChapterSection(newBlankSection, blankDuration);
|
|
||||||
newChapterBar.appendChild(newBlankSection);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
originalSectionClone.remove();
|
|
||||||
if (this.container?.parentElement === progressBar) {
|
|
||||||
progressBar.insertBefore(newChapterBar, this.container.nextSibling);
|
|
||||||
} else {
|
|
||||||
progressBar.prepend(newChapterBar);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hide old bar
|
|
||||||
chapterBar.style.display = "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
private setupChapterSection(section: HTMLElement, duration: number): void {
|
|
||||||
section.style.marginRight = "2px";
|
|
||||||
section.style.width = `calc(${this.timeToPercentage(duration)} - 2px)`;
|
|
||||||
section.setAttribute("decimal-width", String(this.timeToDecimal(duration)));
|
|
||||||
|
|
||||||
section.addEventListener("mouseenter", () => {
|
|
||||||
this.hoveredSection?.classList.remove("ytp-exp-chapter-hover-effect");
|
|
||||||
section.classList.add("ytp-exp-chapter-hover-effect");
|
|
||||||
this.hoveredSection = section;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateChapterText(segments: SponsorTime[], currentTime: number): void {
|
updateChapterText(segments: SponsorTime[], currentTime: number): void {
|
||||||
|
|||||||
Reference in New Issue
Block a user