mirror of
https://github.com/ajayyy/SponsorBlock.git
synced 2025-12-12 14:37:23 +03:00
Merge branch 'master' of https://github.com/ajayyy/SponsorBlock into improvements
This commit is contained in:
176
src/content.ts
176
src/content.ts
@@ -33,15 +33,15 @@ let channelIDInfo: ChannelIDInfo;
|
||||
// Skips are rescheduled every seeking event.
|
||||
// Skips are canceled every seeking event
|
||||
let currentSkipSchedule: NodeJS.Timeout = null;
|
||||
let seekListenerSetUp = false
|
||||
|
||||
/** Has the sponsor been skipped */
|
||||
let sponsorSkipped: boolean[] = [];
|
||||
|
||||
//the video
|
||||
let video: HTMLVideoElement;
|
||||
let videoMutationObserver: MutationObserver = null;
|
||||
// List of videos that have had event listeners added to them
|
||||
const videoRootsWithEventListeners: HTMLDivElement[] = [];
|
||||
const videosWithEventListeners: HTMLVideoElement[] = [];
|
||||
|
||||
let onInvidious;
|
||||
let onMobileYouTube;
|
||||
@@ -49,9 +49,6 @@ let onMobileYouTube;
|
||||
//the video id of the last preview bar update
|
||||
let lastPreviewBarUpdate;
|
||||
|
||||
//whether the duration listener listening for the duration changes of the video has been setup yet
|
||||
let durationListenerSetUp = false;
|
||||
|
||||
// Is the video currently being switched
|
||||
let switchingVideos = null;
|
||||
|
||||
@@ -77,6 +74,7 @@ const playerButtons: Record<string, {button: HTMLButtonElement, image: HTMLImage
|
||||
|
||||
// Direct Links after the config is loaded
|
||||
utils.wait(() => Config.config !== null, 1000, 1).then(() => videoIDChange(getYouTubeVideoID(document.URL)));
|
||||
addHotkeyListener();
|
||||
|
||||
//the amount of times the sponsor lookup has retried
|
||||
//this only happens if there is an error
|
||||
@@ -475,48 +473,60 @@ function incorrectVideoCheck(videoID?: string, sponsorTime?: SponsorTime): boole
|
||||
}
|
||||
}
|
||||
|
||||
async function sponsorsLookup(id: string) {
|
||||
video = document.querySelector('video') // Youtube video player
|
||||
//there is no video here
|
||||
if (video == null) {
|
||||
setTimeout(() => sponsorsLookup(id), 100);
|
||||
return;
|
||||
function setupVideoMutationListener() {
|
||||
const videoContainer = document.querySelector(".html5-video-container");
|
||||
if (!videoContainer || videoMutationObserver !== null || onInvidious) return;
|
||||
|
||||
videoMutationObserver = new MutationObserver(refreshVideoAttachments);
|
||||
|
||||
videoMutationObserver.observe(videoContainer, {
|
||||
attributes: true,
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
}
|
||||
|
||||
function refreshVideoAttachments() {
|
||||
const newVideo = document.querySelector('video');
|
||||
if (newVideo && newVideo !== video) {
|
||||
video = newVideo;
|
||||
|
||||
if (!videosWithEventListeners.includes(video)) {
|
||||
videosWithEventListeners.push(video);
|
||||
|
||||
setupVideoListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addHotkeyListener();
|
||||
function setupVideoListeners() {
|
||||
//wait until it is loaded
|
||||
video.addEventListener('durationchange', durationChangeListener);
|
||||
|
||||
if (!durationListenerSetUp) {
|
||||
durationListenerSetUp = true;
|
||||
|
||||
//wait until it is loaded
|
||||
video.addEventListener('durationchange', durationChangeListener);
|
||||
}
|
||||
|
||||
if (!seekListenerSetUp && !Config.config.disableSkipping) {
|
||||
seekListenerSetUp = true;
|
||||
if (!Config.config.disableSkipping) {
|
||||
switchingVideos = false;
|
||||
|
||||
video.addEventListener('play', () => {
|
||||
switchingVideos = false;
|
||||
|
||||
|
||||
// If it is not the first event, then the only way to get to 0 is if there is a seek event
|
||||
// This check makes sure that changing the video resolution doesn't cause the extension to think it
|
||||
// gone back to the begining
|
||||
if (!firstEvent && video.currentTime === 0) return;
|
||||
firstEvent = false;
|
||||
|
||||
|
||||
// Check if an ad is playing
|
||||
updateAdFlag();
|
||||
|
||||
|
||||
// Make sure it doesn't get double called with the playing event
|
||||
if (Math.abs(lastCheckVideoTime - video.currentTime) > 0.3
|
||||
|| (lastCheckVideoTime !== video.currentTime && Date.now() - lastCheckTime > 2000)) {
|
||||
lastCheckTime = Date.now();
|
||||
lastCheckVideoTime = video.currentTime;
|
||||
|
||||
|
||||
startSponsorSchedule();
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
video.addEventListener('playing', () => {
|
||||
// Make sure it doesn't get double called with the play event
|
||||
@@ -524,7 +534,7 @@ async function sponsorsLookup(id: string) {
|
||||
|| (lastCheckVideoTime !== video.currentTime && Date.now() - lastCheckTime > 2000)) {
|
||||
lastCheckTime = Date.now();
|
||||
lastCheckVideoTime = video.currentTime;
|
||||
|
||||
|
||||
startSponsorSchedule();
|
||||
}
|
||||
});
|
||||
@@ -533,7 +543,7 @@ async function sponsorsLookup(id: string) {
|
||||
// Reset lastCheckVideoTime
|
||||
lastCheckTime = Date.now();
|
||||
lastCheckVideoTime = video.currentTime;
|
||||
|
||||
|
||||
startSponsorSchedule();
|
||||
}
|
||||
});
|
||||
@@ -544,12 +554,23 @@ async function sponsorsLookup(id: string) {
|
||||
// Reset lastCheckVideoTime
|
||||
lastCheckVideoTime = -1;
|
||||
lastCheckTime = 0;
|
||||
|
||||
|
||||
cancelSponsorSchedule();
|
||||
});
|
||||
|
||||
|
||||
startSponsorSchedule();
|
||||
}
|
||||
}
|
||||
|
||||
async function sponsorsLookup(id: string) {
|
||||
if (!video) refreshVideoAttachments();
|
||||
//there is still no video here
|
||||
if (!video) {
|
||||
setTimeout(() => sponsorsLookup(id), 100);
|
||||
return;
|
||||
}
|
||||
|
||||
setupVideoMutationListener();
|
||||
|
||||
//check database for sponsor times
|
||||
//made true once a setTimeout has been created to try again after a server error
|
||||
@@ -566,22 +587,12 @@ async function sponsorsLookup(id: string) {
|
||||
categories
|
||||
}).then(async (response: FetchResponse) => {
|
||||
if (response?.ok) {
|
||||
let result = JSON.parse(response.responseText);
|
||||
result = result.filter((video) => video.videoID === id);
|
||||
if (result.length > 0) {
|
||||
result = result[0].segments;
|
||||
if (result.length === 0) { // return if no segments found
|
||||
retryFetch(id);
|
||||
return;
|
||||
}
|
||||
} else { // return if no video found
|
||||
retryFetch(id);
|
||||
return;
|
||||
}
|
||||
|
||||
const recievedSegments: SponsorTime[] = result;
|
||||
if (!recievedSegments.length) {
|
||||
console.error("[SponsorBlock] Server returned malformed response: " + JSON.stringify(recievedSegments));
|
||||
const recievedSegments: SponsorTime[] = JSON.parse(response.responseText)
|
||||
?.filter((video) => video.videoID === id)
|
||||
?.map((video) => video.segments)[0];
|
||||
if (!recievedSegments || !recievedSegments.length) {
|
||||
// return if no video found
|
||||
retryFetch();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -597,6 +608,7 @@ async function sponsorsLookup(id: string) {
|
||||
}
|
||||
}
|
||||
|
||||
const oldSegments = sponsorTimes || [];
|
||||
sponsorTimes = recievedSegments;
|
||||
|
||||
// Hide all submissions smaller than the minimum duration
|
||||
@@ -608,6 +620,15 @@ async function sponsorsLookup(id: string) {
|
||||
}
|
||||
}
|
||||
|
||||
for (const segment of oldSegments) {
|
||||
const otherSegment = sponsorTimes.find((other) => segment.UUID === other.UUID);
|
||||
if (otherSegment) {
|
||||
// If they downvoted it, or changed the category, keep it
|
||||
otherSegment.hidden = segment.hidden;
|
||||
otherSegment.category = segment.category;
|
||||
}
|
||||
}
|
||||
|
||||
startSkipScheduleCheckingForStartSponsors();
|
||||
|
||||
// Reset skip save
|
||||
@@ -623,20 +644,24 @@ async function sponsorsLookup(id: string) {
|
||||
|
||||
sponsorLookupRetries = 0;
|
||||
} else if (response?.status === 404) {
|
||||
retryFetch(id);
|
||||
retryFetch();
|
||||
} else if (sponsorLookupRetries < 15 && !recheckStarted) {
|
||||
recheckStarted = true;
|
||||
|
||||
//TODO lower when server becomes better (back to 1 second)
|
||||
//some error occurred, try again in a second
|
||||
setTimeout(() => sponsorsLookup(id), 5000 + Math.random() * 15000 + 5000 * sponsorLookupRetries);
|
||||
setTimeout(() => {
|
||||
if (sponsorVideoID && sponsorTimes?.length === 0) {
|
||||
sponsorsLookup(sponsorVideoID);
|
||||
}
|
||||
}, 5000 + Math.random() * 15000 + 5000 * sponsorLookupRetries);
|
||||
|
||||
sponsorLookupRetries++;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function retryFetch(id: string): void {
|
||||
function retryFetch(): void {
|
||||
if (!Config.config.refetchWhenNotFound) return;
|
||||
|
||||
sponsorDataFound = false;
|
||||
@@ -647,7 +672,11 @@ function retryFetch(id: string): void {
|
||||
|
||||
//if less than 3 days old
|
||||
if (Date.now() - new Date(dateUploaded).getTime() < 259200000) {
|
||||
setTimeout(() => sponsorsLookup(id), 30000 + Math.random() * 90000);
|
||||
setTimeout(() => {
|
||||
if (sponsorVideoID && sponsorTimes?.length === 0) {
|
||||
sponsorsLookup(sponsorVideoID);
|
||||
}
|
||||
}, 10000 + Math.random() * 30000);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -692,7 +721,7 @@ function startSkipScheduleCheckingForStartSponsors() {
|
||||
* Get the video info for the current tab from YouTube
|
||||
*/
|
||||
async function getVideoInfo(): Promise<void> {
|
||||
const result = await utils.asyncRequestToCustomServer("GET", "https://www.youtube.com/get_video_info?video_id=" + sponsorVideoID);
|
||||
const result = await utils.asyncRequestToCustomServer("GET", "https://www.youtube.com/get_video_info?video_id=" + sponsorVideoID + "&html5=1");
|
||||
|
||||
if (result.ok) {
|
||||
const decodedData = decodeURIComponent(result.responseText).match(/player_response=([^&]*)/)[1];
|
||||
@@ -706,7 +735,7 @@ async function getVideoInfo(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
function getYouTubeVideoID(url: string) {
|
||||
function getYouTubeVideoID(url: string): string | boolean {
|
||||
// For YouTube TV support
|
||||
if(url.startsWith("https://www.youtube.com/tv#/")) url = url.replace("#", "");
|
||||
|
||||
@@ -753,7 +782,7 @@ function getYouTubeVideoID(url: string) {
|
||||
*/
|
||||
function updatePreviewBarPositionMobile(parent: HTMLElement) {
|
||||
if (document.getElementById("previewbar") === null) {
|
||||
previewBar.updatePosition(parent);
|
||||
previewBar.createElement(parent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -805,16 +834,19 @@ function updatePreviewBar(): void {
|
||||
async function whitelistCheck() {
|
||||
const whitelistedChannels = Config.config.whitelistedChannels;
|
||||
|
||||
const channelID = document.querySelector(".ytd-channel-name a")?.getAttribute("href")?.replace(/\/.+\//, "") // YouTube
|
||||
const getChannelID = () => videoInfo?.videoDetails?.channelId
|
||||
?? document.querySelector(".ytd-channel-name a")?.getAttribute("href")?.replace(/\/.+\//, "") // YouTube
|
||||
?? document.querySelector(".ytp-title-channel-logo")?.getAttribute("href")?.replace(/https:\/.+\//, "") // YouTube Embed
|
||||
?? document.querySelector("a > .channel-profile")?.parentElement?.getAttribute("href")?.replace(/\/.+\//, ""); // Invidious
|
||||
|
||||
if (channelID) {
|
||||
try {
|
||||
await utils.wait(() => !!getChannelID(), 6000, 20);
|
||||
|
||||
channelIDInfo = {
|
||||
status: ChannelIDStatus.Found,
|
||||
id: channelID
|
||||
id: getChannelID()
|
||||
}
|
||||
} else {
|
||||
} catch (e) {
|
||||
channelIDInfo = {
|
||||
status: ChannelIDStatus.Failed,
|
||||
id: null
|
||||
@@ -824,7 +856,7 @@ async function whitelistCheck() {
|
||||
}
|
||||
|
||||
//see if this is a whitelisted channel
|
||||
if (whitelistedChannels != undefined && whitelistedChannels.includes(channelID)) {
|
||||
if (whitelistedChannels != undefined && whitelistedChannels.includes(getChannelID())) {
|
||||
channelWhitelisted = true;
|
||||
}
|
||||
|
||||
@@ -956,8 +988,8 @@ function previewTime(time: number, unpause = true) {
|
||||
|
||||
//send telemetry and count skip
|
||||
function sendTelemetryAndCount(skippingSegments: SponsorTime[], secondsSkipped: number, fullSkip: boolean) {
|
||||
if (!Config.config.trackViewCount) return;
|
||||
|
||||
if (!Config.config.trackViewCount || (!Config.config.trackViewCountInPrivate && chrome.extension.inIncognitoContext)) return;
|
||||
|
||||
let counted = false;
|
||||
for (const segment of skippingSegments) {
|
||||
const index = sponsorTimes.indexOf(segment);
|
||||
@@ -1091,7 +1123,7 @@ async function createButtons(): Promise<void> {
|
||||
/** Creates any missing buttons on the player and updates their visiblity. */
|
||||
async function updateVisibilityOfPlayerControlsButton(): Promise<void> {
|
||||
// Not on a proper video yet
|
||||
if (!sponsorVideoID) return;
|
||||
if (!sponsorVideoID || onMobileYouTube) return;
|
||||
|
||||
await createButtons();
|
||||
|
||||
@@ -1108,7 +1140,7 @@ async function updateVisibilityOfPlayerControlsButton(): Promise<void> {
|
||||
/** Updates the visibility of buttons on the player related to creating segments. */
|
||||
function updateEditButtonsOnPlayer(): void {
|
||||
// Don't try to update the buttons if we aren't on a YouTube video page
|
||||
if (!sponsorVideoID) return;
|
||||
if (!sponsorVideoID || onMobileYouTube) return;
|
||||
|
||||
const buttonsEnabled = !Config.config.hideVideoPlayerControls && !onInvidious;
|
||||
|
||||
@@ -1183,6 +1215,9 @@ function startOrEndTimingNewSegment() {
|
||||
// Save the newly created segment
|
||||
Config.config.segmentTimes.set(sponsorVideoID, sponsorTimesSubmitting);
|
||||
|
||||
// Make sure they know if someone has already submitted something it while they were watching
|
||||
sponsorsLookup(sponsorVideoID);
|
||||
|
||||
updateEditButtonsOnPlayer();
|
||||
updateSponsorTimesSubmitting(false);
|
||||
}
|
||||
@@ -1531,21 +1566,14 @@ function getSegmentsMessage(sponsorTimes: SponsorTime[]): string {
|
||||
return sponsorTimesMessage;
|
||||
}
|
||||
|
||||
function addHotkeyListener(): boolean {
|
||||
let videoRoot = document.getElementById("movie_player") as HTMLDivElement;
|
||||
if (onInvidious) videoRoot = (document.getElementById("player-container") ?? document.getElementById("player")) as HTMLDivElement;
|
||||
if (video.baseURI.startsWith("https://www.youtube.com/tv#/")) videoRoot = document.querySelector("ytlr-watch-page") as HTMLDivElement;
|
||||
|
||||
if (videoRoot && !videoRootsWithEventListeners.includes(videoRoot)) {
|
||||
videoRoot.addEventListener("keydown", hotkeyListener);
|
||||
videoRootsWithEventListeners.push(videoRoot);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
function addHotkeyListener(): void {
|
||||
document.addEventListener("keydown", hotkeyListener);
|
||||
}
|
||||
|
||||
function hotkeyListener(e: KeyboardEvent): void {
|
||||
if (["textarea", "input"].includes(document.activeElement?.tagName?.toLowerCase())
|
||||
|| document.activeElement?.id?.toLowerCase()?.includes("editable")) return;
|
||||
|
||||
const key = e.key;
|
||||
|
||||
const skipKey = Config.config.skipKeybind;
|
||||
|
||||
Reference in New Issue
Block a user