mirror of
https://github.com/ajayyy/SponsorBlock.git
synced 2025-05-11 18:36:15 +02:00
Merge pull request #2243 from gosha305/loop-chapters
Added the possibility to loop a chapter
This commit is contained in:
commit
67a16a7b34
6 changed files with 112 additions and 16 deletions
|
@ -1 +1 @@
|
|||
Subproject commit e9efadcf82316b358e3f81cba71b20d7ed1ead86
|
||||
Subproject commit d2cf3595c782b712d104f5893a0a744478c1a872
|
4
public/icons/loop.svg
Normal file
4
public/icons/loop.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg width="24px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="#FFFFFF">
|
||||
<path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-8 4-8 7h2s1-5 6-5c1.66 0 3.14.69 4.22 1.78L14 10h6V4l-2.35 2.35z"/>
|
||||
<path d="M5.85 17.65C7.3 19.1 9.29 20 11.5 20c4.42 0 8-4 8-7h-2s-1 5-6 5c-1.66 0-3.14-.69-4.22-1.78L9.5 14h-6v6z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 335 B |
4
public/icons/looped.svg
Normal file
4
public/icons/looped.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg width="24px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="#80fff6">
|
||||
<path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-8 4-8 7h2s1-5 6-5c1.66 0 3.14.69 4.22 1.78L14 10h6V4l-2.35 2.35z"/>
|
||||
<path d="M5.85 17.65C7.3 19.1 9.29 20 11.5 20c4.42 0 8-4 8-7h-2s-1 5-6 5c-1.66 0-3.14-.69-4.22-1.78L9.5 14h-6v6z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 334 B |
|
@ -76,6 +76,7 @@ let sponsorTimes: SponsorTime[] = [];
|
|||
let existingChaptersImported = false;
|
||||
let importingChaptersWaitingForFocus = false;
|
||||
let importingChaptersWaiting = false;
|
||||
let loopedChapter :SponsorTime = null;
|
||||
// List of open skip notices
|
||||
const skipNotices: SkipNotice[] = [];
|
||||
let upcomingNotice: UpcomingNotice | null = null;
|
||||
|
@ -301,6 +302,20 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo
|
|||
case "copyToClipboard":
|
||||
navigator.clipboard.writeText(request.text);
|
||||
break;
|
||||
case "loopChapter":
|
||||
if (!request.UUID){
|
||||
loopedChapter = null;
|
||||
break;
|
||||
}
|
||||
loopedChapter = {...utils.getSponsorTimeFromUUID(sponsorTimes, request.UUID)};
|
||||
loopedChapter.actionType = ActionType.Skip;
|
||||
loopedChapter.segment = [loopedChapter.segment[1], loopedChapter.segment[0]];
|
||||
break;
|
||||
case "getLoopedChapter":
|
||||
sendResponse({
|
||||
UUID: loopedChapter?.UUID,
|
||||
});
|
||||
break;
|
||||
case "importSegments": {
|
||||
const importedSegments = importTimes(request.data, getVideoDuration());
|
||||
let addedSegments = false;
|
||||
|
@ -397,6 +412,7 @@ function resetValues() {
|
|||
sponsorTimes = [];
|
||||
existingChaptersImported = false;
|
||||
sponsorSkipped = [];
|
||||
loopedChapter = null;
|
||||
lastResponseStatus = 0;
|
||||
shownSegmentFailedToFetchWarning = false;
|
||||
|
||||
|
@ -694,7 +710,7 @@ async function startSponsorSchedule(includeIntersectingSegments = false, current
|
|||
for (const segment of skipInfo.array) {
|
||||
if (shouldAutoSkip(segment) &&
|
||||
segment.segment[0] >= skipTime[0] && segment.segment[1] <= skipTime[1]
|
||||
&& segment.segment[0] === segment.scheduledTime) { // Don't include artifical scheduled segments (end times for mutes)
|
||||
&& segment.segment[0] === segment.scheduledTime) { // Don't include artificial scheduled segments (end times for mutes)
|
||||
skippingSegments.push(segment);
|
||||
}
|
||||
}
|
||||
|
@ -711,7 +727,7 @@ async function startSponsorSchedule(includeIntersectingSegments = false, current
|
|||
forceVideoTime ||= Math.max(getCurrentTime(), getVirtualTime());
|
||||
|
||||
if ((shouldSkip(currentSkip) || sponsorTimesSubmitting?.some((segment) => segment.segment === currentSkip.segment))) {
|
||||
if (forceVideoTime >= skipTime[0] - skipBuffer && forceVideoTime < skipTime[1]) {
|
||||
if (forceVideoTime >= skipTime[0] - skipBuffer && (forceVideoTime < skipTime[1] || skipTime[1] < skipTime[0])) {
|
||||
skipToTime({
|
||||
v: getVideo(),
|
||||
skipTime,
|
||||
|
@ -719,7 +735,7 @@ async function startSponsorSchedule(includeIntersectingSegments = false, current
|
|||
openNotice: skipInfo.openNotice
|
||||
});
|
||||
|
||||
// These are segments that start at the exact same time but need seperate notices
|
||||
// These are segments that start at the exact same time but need separate notices
|
||||
for (const extra of skipInfo.extraIndexes) {
|
||||
const extraSkip = skipInfo.array[extra];
|
||||
if (shouldSkip(extraSkip)) {
|
||||
|
@ -752,7 +768,7 @@ async function startSponsorSchedule(includeIntersectingSegments = false, current
|
|||
}
|
||||
|
||||
// Don't pretend to be earlier than we are, could result in loops
|
||||
if (forcedSkipTime !== null && forceVideoTime > forcedSkipTime) {
|
||||
if (forcedSkipTime !== null && forceVideoTime > forcedSkipTime && skipTime[1] > skipTime[0]) {
|
||||
forcedSkipTime = forceVideoTime;
|
||||
}
|
||||
|
||||
|
@ -867,7 +883,8 @@ function incorrectVideoCheck(videoID?: string, sponsorTime?: SponsorTime): boole
|
|||
const recordedVideoID = videoID || getVideoID();
|
||||
if (currentVideoID !== recordedVideoID || (sponsorTime
|
||||
&& (!sponsorTimes || !sponsorTimes?.some((time) => time.segment[0] === sponsorTime.segment[0] && time.segment[1] === sponsorTime.segment[1]))
|
||||
&& !sponsorTimesSubmitting.some((time) => time.segment[0] === sponsorTime.segment[0] && time.segment[1] === sponsorTime.segment[1]))) {
|
||||
&& !sponsorTimesSubmitting.some((time) => time.segment[0] === sponsorTime.segment[0] && time.segment[1] === sponsorTime.segment[1])
|
||||
&& (!isLoopedChapter(sponsorTime)))) {
|
||||
// Something has really gone wrong
|
||||
console.error("[SponsorBlock] The videoID recorded when trying to skip is different than what it should be.");
|
||||
console.error("[SponsorBlock] VideoID recorded: " + recordedVideoID + ". Actual VideoID: " + currentVideoID);
|
||||
|
@ -1540,7 +1557,7 @@ function getNextSkipIndex(currentTime: number, includeIntersectingSegments: bool
|
|||
array: submittedArray,
|
||||
index: minSponsorTimeIndex,
|
||||
endIndex: endTimeIndex,
|
||||
extraIndexes, // Segments at same time that need seperate notices
|
||||
extraIndexes, // Segments at same time that need separate notices
|
||||
openNotice: true
|
||||
};
|
||||
} else {
|
||||
|
@ -1575,8 +1592,16 @@ function getLatestEndTimeIndex(sponsorTimes: SponsorTime[], index: number, hideH
|
|||
return index;
|
||||
}
|
||||
|
||||
// Default to the normal endTime
|
||||
let latestEndTimeIndex = index;
|
||||
let latestEndTimeIndex = -1;
|
||||
// Default to looped chapter if its end would have been skipped
|
||||
if (loopedChapter
|
||||
&& (loopedChapter.segment[0] > sponsorTimes[index].segment[0]
|
||||
&& loopedChapter.segment[0] <= sponsorTimes[index]?.segment[1])){
|
||||
latestEndTimeIndex = sponsorTimes.length - 1;
|
||||
} else {
|
||||
// or the normal end time otherwise
|
||||
latestEndTimeIndex = index;
|
||||
}
|
||||
|
||||
for (let i = 0; i < sponsorTimes?.length; i++) {
|
||||
const currentSegment = sponsorTimes[i].segment;
|
||||
|
@ -1619,7 +1644,8 @@ function getStartTimes(sponsorTimes: SponsorTime[], includeIntersectingSegments:
|
|||
const shouldIncludeTime = (segment: ScheduledTime ) => (minimum === undefined
|
||||
|| ((includeNonIntersectingSegments && segment.scheduledTime >= minimum)
|
||||
|| (includeIntersectingSegments && segment.scheduledTime < minimum
|
||||
&& segment.segment[1] > minimum && shouldSkip(segment)))) // Only include intersecting skippable segments
|
||||
&& ((segment.segment[1] > minimum && shouldSkip(segment)) // Only include intersecting skippable segments
|
||||
|| isLoopedChapter(segment)))))
|
||||
&& (!hideHiddenSponsors || segment.hidden === SponsorHideType.Visible)
|
||||
&& segment.segment.length === 2
|
||||
&& segment.actionType !== ActionType.Poi
|
||||
|
@ -1641,6 +1667,12 @@ function getStartTimes(sponsorTimes: SponsorTime[], includeIntersectingSegments:
|
|||
}
|
||||
});
|
||||
|
||||
if (loopedChapter){
|
||||
possibleTimes.push({
|
||||
...loopedChapter,
|
||||
scheduledTime: loopedChapter.segment[0]})
|
||||
}
|
||||
|
||||
for (let i = 0; i < possibleTimes.length; i++) {
|
||||
if (shouldIncludeTime(possibleTimes[i])) {
|
||||
scheduledTimes.push(possibleTimes[i].scheduledTime);
|
||||
|
@ -1898,7 +1930,8 @@ function shouldAutoSkip(segment: SponsorTime): boolean {
|
|||
&& (utils.getCategorySelection(segment.category)?.option === CategorySkipOption.AutoSkip ||
|
||||
(Config.config.autoSkipOnMusicVideos && sponsorTimes?.some((s) => s.category === "music_offtopic")
|
||||
&& segment.actionType === ActionType.Skip)
|
||||
|| sponsorTimesSubmitting.some((s) => s.segment === segment.segment));
|
||||
|| sponsorTimesSubmitting.some((s) => s.segment === segment.segment))
|
||||
|| isLoopedChapter(segment);
|
||||
}
|
||||
|
||||
function shouldSkip(segment: SponsorTime): boolean {
|
||||
|
@ -1906,7 +1939,13 @@ function shouldSkip(segment: SponsorTime): boolean {
|
|||
&& segment.source !== SponsorSourceType.YouTube
|
||||
&& utils.getCategorySelection(segment.category)?.option !== CategorySkipOption.ShowOverlay)
|
||||
|| (Config.config.autoSkipOnMusicVideos && sponsorTimes?.some((s) => s.category === "music_offtopic")
|
||||
&& segment.actionType === ActionType.Skip);
|
||||
&& segment.actionType === ActionType.Skip)
|
||||
|| isLoopedChapter(segment);
|
||||
}
|
||||
|
||||
function isLoopedChapter(segment: SponsorTime) :boolean{
|
||||
return !!segment && !!loopedChapter && segment.actionType === ActionType.Skip && segment.segment[1] != undefined
|
||||
&& segment.segment[0] === loopedChapter.segment[0] && segment.segment[1] === loopedChapter.segment[1];
|
||||
}
|
||||
|
||||
/** Creates any missing buttons on the YouTube player if possible. */
|
||||
|
|
|
@ -18,7 +18,8 @@ interface DefaultMessage {
|
|||
| "submitTimes"
|
||||
| "refreshSegments"
|
||||
| "closePopup"
|
||||
| "getLogs";
|
||||
| "getLogs"
|
||||
| "getLoopedChapter";
|
||||
}
|
||||
|
||||
interface BoolValueMessage {
|
||||
|
@ -58,6 +59,11 @@ interface ImportSegmentsMessage {
|
|||
data: string;
|
||||
}
|
||||
|
||||
interface LoopChapterMessage {
|
||||
message: "loopChapter";
|
||||
UUID: SegmentUUID;
|
||||
}
|
||||
|
||||
interface KeyDownMessage {
|
||||
message: "keydown";
|
||||
key: string;
|
||||
|
@ -70,7 +76,7 @@ interface KeyDownMessage {
|
|||
metaKey: boolean;
|
||||
}
|
||||
|
||||
export type Message = BaseMessage & (DefaultMessage | BoolValueMessage | IsInfoFoundMessage | SkipMessage | SubmitVoteMessage | HideSegmentMessage | CopyToClipboardMessage | ImportSegmentsMessage | KeyDownMessage);
|
||||
export type Message = BaseMessage & (DefaultMessage | BoolValueMessage | IsInfoFoundMessage | SkipMessage | SubmitVoteMessage | HideSegmentMessage | CopyToClipboardMessage | ImportSegmentsMessage | KeyDownMessage | LoopChapterMessage);
|
||||
|
||||
export interface IsInfoFoundMessageResponse {
|
||||
found: boolean;
|
||||
|
@ -97,6 +103,10 @@ export interface IsChannelWhitelistedResponse {
|
|||
value: boolean;
|
||||
}
|
||||
|
||||
export interface LoopedChapterResponse {
|
||||
UUID: SegmentUUID;
|
||||
}
|
||||
|
||||
export type MessageResponse =
|
||||
IsInfoFoundMessageResponse
|
||||
| GetVideoIdResponse
|
||||
|
@ -107,7 +117,8 @@ export type MessageResponse =
|
|||
| VoteResponse
|
||||
| ImportSegmentsResponse
|
||||
| RefreshSegmentsResponse
|
||||
| LogResponse;
|
||||
| LogResponse
|
||||
| LoopedChapterResponse;
|
||||
|
||||
export interface VoteResponse {
|
||||
successType: number;
|
||||
|
|
40
src/popup.ts
40
src/popup.ts
|
@ -13,6 +13,7 @@ import {
|
|||
IsChannelWhitelistedResponse,
|
||||
IsInfoFoundMessageResponse,
|
||||
LogResponse,
|
||||
LoopedChapterResponse,
|
||||
Message,
|
||||
MessageResponse,
|
||||
PopupMessage,
|
||||
|
@ -539,7 +540,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
|||
}
|
||||
|
||||
//display the video times from the array at the top, in a different section
|
||||
function displayDownloadedSponsorTimes(sponsorTimes: SponsorTime[], time: number) {
|
||||
async function displayDownloadedSponsorTimes(sponsorTimes: SponsorTime[], time: number) {
|
||||
let currentSegmentTab = segmentTab;
|
||||
if (!sponsorTimes.some((segment) => segment.actionType === ActionType.Chapter && segment.source !== SponsorSourceType.YouTube)) {
|
||||
PageElements.issueReporterTabs.classList.add("hidden");
|
||||
|
@ -554,6 +555,9 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
|||
}
|
||||
}
|
||||
|
||||
const response = await sendTabMessageAsync({message : "getLoopedChapter"}) as LoopedChapterResponse
|
||||
const loopedChapter = response.UUID;
|
||||
|
||||
// Sort list by start time
|
||||
const downloadedTimes = sponsorTimes
|
||||
.filter((segment) => {
|
||||
|
@ -728,6 +732,37 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
|||
})
|
||||
});
|
||||
|
||||
const loopButton = document.createElement("input");
|
||||
const loopButtonIcon = document.createElement("img");
|
||||
loopButtonIcon.src = loopedChapter === UUID ? chrome.runtime.getURL("icons/looped.svg") : chrome.runtime.getURL("icons/loop.svg");
|
||||
loopButtonIcon.className = "loopButtonIcon";
|
||||
loopButton.type = "checkbox";
|
||||
loopButton.checked = loopedChapter === UUID;
|
||||
loopButton.id = "loopChapterButtonContainer" + UUID;
|
||||
loopButton.className = "loopButton hidden";
|
||||
|
||||
loopButton.addEventListener("click", () => {
|
||||
const stopAnimation = AnimationUtils.applyLoadingAnimation(loopButtonIcon, 0.4);
|
||||
stopAnimation();
|
||||
if (loopButton.checked) {
|
||||
document.querySelectorAll(".loopButton").forEach((e :HTMLInputElement) => e.checked = false);
|
||||
document.querySelectorAll(".loopButtonIcon").forEach((e :HTMLImageElement) => e.src = chrome.runtime.getURL("icons/loop.svg"));
|
||||
sendTabMessage({message: "loopChapter", UUID : UUID})
|
||||
loopButton.checked = true;
|
||||
} else {
|
||||
sendTabMessage({message: "loopChapter", UUID : null})
|
||||
loopButton.checked = false;
|
||||
}
|
||||
loopButtonIcon.src = loopButton.checked ? chrome.runtime.getURL("icons/looped.svg") : chrome.runtime.getURL("icons/loop.svg");
|
||||
loopButtonLabel.title = loopButton.checked ? chrome.i18n.getMessage("unloopChapter") : chrome.i18n.getMessage("loopChapter");
|
||||
});
|
||||
const loopButtonLabel = document.createElement("label");
|
||||
loopButtonLabel.setAttribute("for", loopButton.id);
|
||||
loopButtonLabel.className = "voteButton";
|
||||
loopButtonLabel.title = loopedChapter === UUID ? chrome.i18n.getMessage("unloopChapter") : chrome.i18n.getMessage("loopChapter");
|
||||
loopButtonLabel.appendChild(loopButtonIcon);
|
||||
loopButtonLabel.appendChild(loopButton);
|
||||
|
||||
const skipButton = document.createElement("img");
|
||||
skipButton.id = "sponsorTimesSkipButtonContainer" + UUID;
|
||||
skipButton.className = "voteButton";
|
||||
|
@ -743,6 +778,9 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
|||
voteButtonsContainer.appendChild(upvoteButton);
|
||||
voteButtonsContainer.appendChild(downvoteButton);
|
||||
voteButtonsContainer.appendChild(uuidButton);
|
||||
if (downloadedTimes[i].actionType === ActionType.Chapter) {
|
||||
voteButtonsContainer.appendChild(loopButtonLabel);
|
||||
}
|
||||
if (downloadedTimes[i].actionType === ActionType.Skip || downloadedTimes[i].actionType === ActionType.Mute
|
||||
|| downloadedTimes[i].actionType === ActionType.Poi
|
||||
&& [SponsorHideType.Visible, SponsorHideType.Hidden].includes(downloadedTimes[i].hidden)) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue