diff --git a/README.md b/README.md
index 9591f5eb..65cc8990 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-
+
Logo by @munadikieh
@@ -72,7 +72,7 @@ You must have [Node.js 16](https://nodejs.org/) and npm installed.
- You can also run `npm run build` (for Chrome) or `npm run build:firefox` (for Firefox) to generate a production build.
-4. The built extension is now in `dist/`. You can load it in Chrome as an [unpacked extension](https://developer.chrome.com/docs/extensions/mv3/getstarted/#manifest) or in Firefox as a [temporary extension](https://developer.mozilla.org/en-US/docs/Tools/about:debugging#loading_a_temporary_extension).
+4. The built extension is now in `dist/`. You can load this folder directly in Chrome as an [unpacked extension](https://developer.chrome.com/docs/extensions/mv3/getstarted/#manifest), or convert it to a zip file to load it as a [temporary extension](https://developer.mozilla.org/en-US/docs/Tools/about:debugging#loading_a_temporary_extension) in Firefox.
### Developing with a clean profile and hot reloading
diff --git a/ci/invidiouslist.json b/ci/invidiouslist.json
index 4638f7c3..6617abf6 100644
--- a/ci/invidiouslist.json
+++ b/ci/invidiouslist.json
@@ -1 +1 @@
-["yewtu.be","invidious.snopyta.org","vid.puffyan.us","invidious.kavin.rocks","invidio.xamh.de","inv.riverside.rocks","invidious-us.kavin.rocks","invidious.osi.kr","tube.cthd.icu","yt.artemislena.eu","youtube.076.ne.jp","invidious.namazso.eu"]
\ No newline at end of file
+["yewtu.be","vid.puffyan.us","invidious.snopyta.org","invidious.kavin.rocks","invidio.xamh.de","inv.riverside.rocks","invidious-us.kavin.rocks","invidious.osi.kr","tube.cthd.icu","invidious.flokinet.to","yt.artemislena.eu","youtube.076.ne.jp","invidious.namazso.eu"]
\ No newline at end of file
diff --git a/manifest/manifest.json b/manifest/manifest.json
index 3ec101fa..4c78d64f 100644
--- a/manifest/manifest.json
+++ b/manifest/manifest.json
@@ -1,7 +1,7 @@
{
"name": "__MSG_fullName__",
"short_name": "SponsorBlock",
- "version": "4.1.6",
+ "version": "4.2.1",
"default_locale": "en",
"description": "__MSG_Description__",
"homepage_url": "https://sponsor.ajay.app",
diff --git a/package-lock.json b/package-lock.json
index a6d80792..419c2e2f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4256,24 +4256,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/copy-webpack-plugin/node_modules/schema-utils": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
- "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
- "dev": true,
- "dependencies": {
- "@types/json-schema": "^7.0.8",
- "ajv": "^6.12.5",
- "ajv-keywords": "^3.5.2"
- },
- "engines": {
- "node": ">= 10.13.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/webpack"
- }
- },
"node_modules/copy-webpack-plugin/node_modules/slash": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz",
@@ -9990,8 +9972,9 @@
}
},
"node_modules/minimist": {
- "version": "1.2.5",
- "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
+ "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
"dev": true
},
"node_modules/mkdirp": {
@@ -10007,9 +9990,9 @@
}
},
"node_modules/moment": {
- "version": "2.29.1",
- "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
- "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==",
+ "version": "2.29.2",
+ "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.2.tgz",
+ "integrity": "sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg==",
"dev": true,
"optional": true,
"engines": {
@@ -17279,16 +17262,6 @@
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true
},
- "schema-utils": {
- "version": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
- "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
- "dev": true,
- "requires": {
- "@types/json-schema": "^7.0.8",
- "ajv": "^6.12.5",
- "ajv-keywords": "^3.5.2"
- }
- },
"slash": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz",
@@ -21575,8 +21548,9 @@
}
},
"minimist": {
- "version": "1.2.5",
- "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
+ "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
"dev": true
},
"mkdirp": {
@@ -21589,9 +21563,9 @@
}
},
"moment": {
- "version": "2.29.1",
- "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
- "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==",
+ "version": "2.29.2",
+ "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.2.tgz",
+ "integrity": "sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg==",
"dev": true,
"optional": true
},
diff --git a/public/_locales/cs/messages.json b/public/_locales/cs/messages.json
index e616cf6d..bb65ddc4 100644
--- a/public/_locales/cs/messages.json
+++ b/public/_locales/cs/messages.json
@@ -586,13 +586,13 @@
"message": "Titulky nebo když se objeví konečné karty YouTube. Není pro závěry s informacemi."
},
"category_preview": {
- "message": "Náhled/shrnutí"
+ "message": "Náhled / shrnutí"
},
"category_preview_description": {
"message": "Rychlé shrnutí předchozích epizod nebo náhled toho, co se objeví v aktuálním videu. Myšleno pro upravené sloučené klipy, ne pro mluvená shrnutí."
},
"category_filler": {
- "message": "Výplň/vtipy"
+ "message": "Výplň / vtipy"
},
"category_filler_description": {
"message": "Výplňové scény přidané jen jako přídavek nebo humor, které nejsou vyžadovány pro pochopení hlavního obsahu videa. Toto by nemělo zahrnovat segmenty poskytující kontext nebo podrobnosti na pozadí."
diff --git a/public/_locales/it/messages.json b/public/_locales/it/messages.json
index 8fa21d58..0e253383 100644
--- a/public/_locales/it/messages.json
+++ b/public/_locales/it/messages.json
@@ -562,7 +562,7 @@
"description": "Short description for this category"
},
"category_interaction": {
- "message": "Promemoria di Interazione (Sottoscrizione)"
+ "message": "Promemoria d'Interazione (Iscrizione)"
},
"category_interaction_description": {
"message": "Quando nel punto centrale del contenuto è presente un breve promemoria per aggiunta di mi piace, iscrizione o seguito. Se dovesse risultare esteso o riguardare qualcosa di specifico, potrebbe essere un'autopromozione."
diff --git a/public/_locales/pt_PT/messages.json b/public/_locales/pt_PT/messages.json
index 012946e8..ac1d4db3 100644
--- a/public/_locales/pt_PT/messages.json
+++ b/public/_locales/pt_PT/messages.json
@@ -118,6 +118,34 @@
"submitCheck": {
"message": "Tem a certeza que pretende submeter?"
},
+ "whitelistChannel": {
+ "message": "Meter canal na Lista Branca"
+ },
+ "removeFromWhitelist": {
+ "message": "Remover canal da Lista Branca"
+ },
+ "voteOnTime": {
+ "message": "Votar em um segmento"
+ },
+ "Submissions": {
+ "message": "Submissões"
+ },
+ "savedPeopleFrom": {
+ "message": "Salvaste pessoas de "
+ },
+ "viewLeaderboard": {
+ "message": "Tabela de Classificação"
+ },
+ "recordTimesDescription": {
+ "message": "Enviar"
+ },
+ "submissionEditHint": {
+ "message": "A edição da seção aparecerá depois que você clicar em enviar",
+ "description": "Appears in the popup to inform them that editing has been moved to the video player."
+ },
+ "popupHint": {
+ "message": "Dica: Você pode configurar atalhos de teclado para enviar nas opções"
+ },
"clearTimesButton": {
"message": "Limpar Intervalos"
},
@@ -127,9 +155,15 @@
"publicStats": {
"message": "Isto é usado na página pública de estatísticas que mostra o quanto já contríbuíu. Veje-a"
},
+ "Username": {
+ "message": "Nome de Utilizador"
+ },
"setUsername": {
"message": "Criar nomde de utilizador"
},
+ "copyPublicID": {
+ "message": "Copiar UserID Publico"
+ },
"discordAdvert": {
"message": "Junte-se ao discord oficial para sugerir dicas e sugestões!"
},
@@ -148,18 +182,48 @@
"hideButtonsDescription": {
"message": "Isto esconde os botões que aparecem no player do Youtube para submeter patrocínios. Entendemos que possa ser\n incómodo a algumas pessoas. Em vez de usar esses botões pode usar os do popup. Para esconder a mensagem que aparece, \n ususe o botão na mesma que diz \"Don't show this again\". Pode sempre reactivar estas definições novamente."
},
+ "showSkipButton": {
+ "message": "Mantenha o Botão Saltar para Destaque no Player"
+ },
"showInfoButton": {
"message": "Mostrar botão de Informações no player do Youtube"
},
"hideInfoButton": {
"message": "Esconder botão de Informações no player do Youtube"
},
+ "autoHideInfoButton": {
+ "message": "Ocultar automaticamente o Botão de Informação"
+ },
"hideDeleteButton": {
"message": "Esconder botão de Apagar no player do Youtube"
},
"showDeleteButton": {
"message": "Mostrar botão de Apagar no player do Youtube"
},
+ "enableViewTracking": {
+ "message": "Ativar Rastreamento de Contagem de Saltos"
+ },
+ "whatViewTracking": {
+ "message": "Esse recurso rastreia quais segmentos você pulou para permitir que os usuários saibam o quanto seu envio ajudou outras pessoas e é usado como métrica junto com votos positivos para garantir que o spam não entre no banco de dados. A extensão envia uma mensagem ao servidor cada vez que você pular um segmento. Espero que a maioria das pessoas não altere essa configuração para que os números de visualização sejam precisos. :)"
+ },
+ "enableViewTrackingInPrivate": {
+ "message": "Ativar o Rastreamento de Contagem de Saltos nas Guias Privadas/Anônimas"
+ },
+ "enableTrackDownvotes": {
+ "message": "Guardar segmentos de votos negativos"
+ },
+ "whatTrackDownvotes": {
+ "message": "Quaisquer segmentos que você votar negativo permanecerão ocultos mesmo após a atualização"
+ },
+ "trackDownvotesWarning": {
+ "message": "Aviso: Ao desabilitar isso excluirá todos os votos negativos armazenados anteriormente"
+ },
+ "enableQueryByHashPrefix": {
+ "message": "Consulta por Prefixo de Hash"
+ },
+ "whatQueryByHashPrefix": {
+ "message": "Em vez de solicitar segmentos do servidor usando o ID do Vídeo, são enviados os primeiros 4 caracteres do hash do ID do Vídeo. Este servidor enviará de volta dados para todos os vídeos com hashes semelhantes."
+ },
"showNotice": {
"message": "Mostrar notificação outra vez"
},
diff --git a/public/content.css b/public/content.css
index 64e0c56e..3864947f 100644
--- a/public/content.css
+++ b/public/content.css
@@ -391,6 +391,10 @@ div:hover > .sponsorBlockChapterBar {
filter: brightness(80%);
}
+.segmentSummary {
+ outline: none !important;
+}
+
.submitButton {
background-color:#ec1c1c;
-moz-border-radius:28px;
@@ -666,6 +670,7 @@ input::-webkit-inner-spin-button {
line-height: 1.5em;
color: white;
font-size: 12px;
+ z-index: 1000;
}
.sponsorBlockTooltip a {
@@ -725,4 +730,10 @@ input::-webkit-inner-spin-button {
.sponsorBlockCategoryPill:hover .categoryPillClose {
display: inherit;
+}
+
+/* tweak for mobile duration */
+#sponsorBlockDurationAfterSkips.ytm-time-display {
+ padding-left: 4px;
+ margin: 0px;
}
\ No newline at end of file
diff --git a/public/popup.css b/public/popup.css
index a02430ca..b51d3ed5 100644
--- a/public/popup.css
+++ b/public/popup.css
@@ -7,14 +7,26 @@
}
/*
- * IDs on container element (when inserted in page), element,
- * element and main container
+ * Container when popup displayed in-page
*/
#sponsorBlockPopupContainer {
+ position: relative;
margin-bottom: 16px;
}
+/*
+ * Disable fixed popup width when displayed in-page
+ */
+
+#sponsorBlockPopupContainer #sponsorBlockPopupBody {
+ width: auto;
+}
+
+/*
+ * Main containers
+ */
+
#sponsorBlockPopupHTML {
color-scheme: dark;
}
@@ -41,6 +53,25 @@
transition: none !important;
}
+/*
+ * Close popup button when displayed in-page
+ */
+
+.sbCloseButton {
+ background: transparent;
+ border: 0;
+ padding: 8px;
+ cursor: pointer;
+ position: absolute;
+ top: 5px;
+ right: 5px;
+ opacity: 0.5;
+}
+
+.sbCloseButton:hover {
+ opacity: 1;
+}
+
/*
* Header logo
*/
@@ -82,6 +113,10 @@
* wrapper around each segment
*/
+.votingButtons {
+ font-family: Arial, Helvetica, sans-serif;
+}
+
.votingButtons[open] {
padding-bottom: 5px;
}
diff --git a/src/components/SubmissionNoticeComponent.tsx b/src/components/SubmissionNoticeComponent.tsx
index 08b60dd5..13955157 100644
--- a/src/components/SubmissionNoticeComponent.tsx
+++ b/src/components/SubmissionNoticeComponent.tsx
@@ -102,7 +102,7 @@ class SubmissionNoticeComponent extends React.Component window.open("https://wiki.sponsor.ajay.app/index.php/Guidelines")}>
+ onClick={() => window.open("https://wiki.sponsor.ajay.app/w/Guidelines")}>
{chrome.i18n.getMessage(Config.config.submissionCountSinceCategories > 3 ? "guidelines" : "readTheGuidelines")}
diff --git a/src/content.ts b/src/content.ts
index e7047120..807e462b 100644
--- a/src/content.ts
+++ b/src/content.ts
@@ -35,12 +35,19 @@ let sponsorVideoID: VideoID = null;
const skipNotices: SkipNotice[] = [];
let activeSkipKeybindElement: ToggleSkippable = null;
-// JSON video info
+// JSON video info
let videoInfo: VideoInfo = null;
// The channel this video is about
let channelIDInfo: ChannelIDInfo;
// Locked Categories in this tab, like: ["sponsor","intro","outro"]
let lockedCategories: Category[] = [];
+// Used to calculate a more precise "virtual" video time
+let lastKnownVideoTime: { videoTime: number, preciseTime: number } = {
+ videoTime: null,
+ preciseTime: null
+};
+// It resumes with a slightly later time on chromium
+let lastTimeFromWaitingEvent = null;
// Skips are scheduled to ensure precision.
// Skips are rescheduled every seeking event.
@@ -301,7 +308,7 @@ async function videoIDChange(id) {
// If enabled, it will check if this video is private or unlisted and double check with the user if the sponsors should be looked up
if (Config.config.checkForUnlistedVideos) {
- const shouldContinue = confirm("SponsorBlock: You have the setting 'Ignore Unlisted/Private Videos' enabled."
+ const shouldContinue = confirm("SponsorBlock: You have the setting 'Ignore Unlisted/Private Videos' enabled."
+ " Due to a change in how segment fetching works, this setting is not needed anymore as it cannot leak your video ID to the server."
+ " It instead sends just the first 4 characters of a longer hash of the videoID to the server, and filters through a subset of the database."
+ " More info about this implementation can be found here: https://github.com/ajayyy/SponsorBlockServer/issues/25"
@@ -327,13 +334,13 @@ async function videoIDChange(id) {
let controlsContainer = null;
utils.wait(() => {
- controlsContainer = document.getElementById("player-control-container")
+ controlsContainer = document.getElementById("player-control-container")
return controlsContainer !== null
}).then(() => {
- observer.observe(document.getElementById("player-control-container"), {
- attributes: true,
- childList: true,
- subtree: true
+ observer.observe(document.getElementById("player-control-container"), {
+ attributes: true,
+ childList: true,
+ subtree: true
});
}).catch();
} else {
@@ -455,7 +462,17 @@ function startSponsorSchedule(includeIntersectingSegments = false, currentTime?:
}
if (!video) return;
- if (currentTime === undefined || currentTime === null) currentTime = video.currentTime;
+ if (currentTime === undefined || currentTime === null) {
+ const virtualTime = lastTimeFromWaitingEvent ?? (lastKnownVideoTime.videoTime ?
+ (performance.now() - lastKnownVideoTime.preciseTime) / 1000 + lastKnownVideoTime.videoTime : null);
+ if ((lastTimeFromWaitingEvent || !utils.isFirefox())
+ && !isSafari() && virtualTime && Math.abs(virtualTime - video.currentTime) < 0.6){
+ currentTime = virtualTime;
+ } else {
+ currentTime = video.currentTime;
+ }
+ }
+ lastTimeFromWaitingEvent = null;
previewBar?.updateChapterText(sponsorTimes, sponsorTimesSubmitting, currentTime);
@@ -509,9 +526,9 @@ function startSponsorSchedule(includeIntersectingSegments = false, currentTime?:
if ((shouldSkip(currentSkip) || sponsorTimesSubmitting?.some((segment) => segment.segment === currentSkip.segment))) {
if (forceVideoTime >= skipTime[0] && forceVideoTime < skipTime[1]) {
skipToTime({
- v: video,
- skipTime,
- skippingSegments,
+ v: video,
+ skipTime,
+ skippingSegments,
openNotice: skipInfo.openNotice
});
@@ -531,24 +548,30 @@ function startSponsorSchedule(includeIntersectingSegments = false, currentTime?:
startSponsorSchedule(forcedIncludeIntersectingSegments, forcedSkipTime, forcedIncludeNonIntersectingSegments);
};
- if (timeUntilSponsor <= 0) {
- skippingFunction();
+ if (timeUntilSponsor < 0.003) {
+ skippingFunction(currentTime);
} else {
const delayTime = timeUntilSponsor * 1000 * (1 / video.playbackRate);
- if (delayTime < 300 && utils.isFirefox() && !isSafari()) {
+ if (delayTime < 300) {
// For Firefox, use interval instead of timeout near the end to combat imprecise video time
const startIntervalTime = performance.now();
- const startVideoTime = video.currentTime;
+ const startVideoTime = Math.max(currentTime, video.currentTime);
currentSkipInterval = setInterval(() => {
const intervalDuration = performance.now() - startIntervalTime;
if (intervalDuration >= delayTime || video.currentTime >= skipTime[0]) {
clearInterval(currentSkipInterval);
- skippingFunction(Math.max(video.currentTime, startVideoTime + intervalDuration / 1000));
+ if (!utils.isFirefox() && !video.muted) {
+ // Workaround for more accurate skipping on Chromium
+ video.muted = true;
+ video.muted = false;
+ }
+
+ skippingFunction(Math.max(video.currentTime, startVideoTime + video.playbackRate * intervalDuration / 1000));
}
- }, 5);
+ }, 1);
} else {
// Schedule for right before to be more precise than normal timeout
- currentSkipSchedule = setTimeout(skippingFunction, Math.max(0, delayTime - 30));
+ currentSkipSchedule = setTimeout(skippingFunction, Math.max(0, delayTime - 100));
}
}
}
@@ -563,8 +586,8 @@ function inMuteSegment(currentTime: number): boolean {
*/
function incorrectVideoCheck(videoID?: string, sponsorTime?: SponsorTime): boolean {
const currentVideoID = getYouTubeVideoID(document);
- if (currentVideoID !== (videoID || sponsorVideoID) || (sponsorTime
- && (!sponsorTimes || !sponsorTimes?.some((time) => time.segment === sponsorTime.segment))
+ if (currentVideoID !== (videoID || sponsorVideoID) || (sponsorTime
+ && (!sponsorTimes || !sponsorTimes?.some((time) => time.segment === sponsorTime.segment))
&& !sponsorTimesSubmitting.some((time) => time.segment === sponsorTime.segment))) {
// Something has really gone wrong
console.error("[SponsorBlock] The videoID recorded when trying to skip is different than what it should be.");
@@ -582,13 +605,13 @@ function incorrectVideoCheck(videoID?: string, sponsorTime?: SponsorTime): boole
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
+ videoMutationObserver.observe(videoContainer, {
+ attributes: true,
+ childList: true,
+ subtree: true
});
}
@@ -630,32 +653,36 @@ function setupVideoListeners() {
if (!firstEvent && video.currentTime === 0) return;
firstEvent = false;
+ updateVirtualTime();
+
if (switchingVideos) {
switchingVideos = false;
// If already segments loaded before video, retry to skip starting segments
if (sponsorTimes) startSkipScheduleCheckingForStartSponsors();
}
-
+
// 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', () => {
+ updateVirtualTime();
+
// Make sure it doesn't get double called with the play event
if (Math.abs(lastCheckVideoTime - video.currentTime) > 0.3
|| (lastCheckVideoTime !== video.currentTime && Date.now() - lastCheckTime > 2000)) {
lastCheckTime = Date.now();
lastCheckVideoTime = video.currentTime;
-
+
startSponsorSchedule();
}
});
@@ -664,7 +691,10 @@ function setupVideoListeners() {
// Reset lastCheckVideoTime
lastCheckTime = Date.now();
lastCheckVideoTime = video.currentTime;
-
+
+ updateVirtualTime();
+ lastTimeFromWaitingEvent = null;
+
startSponsorSchedule();
} else {
previewBar?.updateChapterText(sponsorTimes, sponsorTimesSubmitting, video.currentTime);
@@ -673,26 +703,41 @@ function setupVideoListeners() {
video.addEventListener('ratechange', () => startSponsorSchedule());
// Used by videospeed extension (https://github.com/igrigorik/videospeed/pull/740)
video.addEventListener('videoSpeed_ratechange', () => startSponsorSchedule());
- video.addEventListener('pause', () => {
+ const paused = () => {
// Reset lastCheckVideoTime
lastCheckVideoTime = -1;
lastCheckTime = 0;
-
+
+ lastKnownVideoTime = {
+ videoTime: null,
+ preciseTime: null
+ }
+ lastTimeFromWaitingEvent = video.currentTime;
+
cancelSponsorSchedule();
- });
-
+ };
+ video.addEventListener('pause', paused);
+ video.addEventListener('waiting', paused);
+
startSponsorSchedule();
}
}
+function updateVirtualTime() {
+ lastKnownVideoTime = {
+ videoTime: video.currentTime,
+ preciseTime: performance.now()
+ };
+}
+
function setupSkipButtonControlBar() {
if (!skipButtonControlBar) {
skipButtonControlBar = new SkipButtonControlBar({
skip: (segment) => skipToTime({
- v: video,
- skipTime: segment.segment,
- skippingSegments: [segment],
- openNotice: true,
+ v: video,
+ skipTime: segment.segment,
+ skippingSegments: [segment],
+ openNotice: true,
forceAutoSkip: true
}),
onMobileYouTube
@@ -731,7 +776,7 @@ async function sponsorsLookup(id: string, keepOldSubmissions = true) {
const hashPrefix = (await utils.getHash(id, 1)).slice(0, 4) as VideoID & HashedValue;
const response = await utils.asyncRequestToServer('GET', "/api/skipSegments/" + hashPrefix, {
categories,
- actionTypes: getEnabledActionTypes(),
+ actionTypes: getEnabledActionTypes(),
userAgent: `${chrome.runtime.id}`,
...extraRequestData
});
@@ -744,7 +789,7 @@ async function sponsorsLookup(id: string, keepOldSubmissions = true) {
...segment,
source: SponsorSourceType.Server
}));
- if (!recievedSegments || !recievedSegments.length) {
+ if (!recievedSegments || !recievedSegments.length) {
// return if no video found
retryFetch();
return;
@@ -813,7 +858,7 @@ async function sponsorsLookup(id: string, keepOldSubmissions = true) {
} else if (response?.status === 404) {
retryFetch();
}
-
+
lookupVipInformation(id);
}
@@ -903,8 +948,8 @@ function retryFetch(): void {
}
/**
- * Only should be used when it is okay to skip a sponsor when in the middle of it
- *
+ * Only should be used when it is okay to skip a sponsor when in the middle of it
+ *
* Ex. When segments are first loaded
*/
function startSkipScheduleCheckingForStartSponsors() {
@@ -915,7 +960,7 @@ function startSkipScheduleCheckingForStartSponsors() {
let found = false;
let startingSegment: SponsorTime = null;
for (const time of sponsorTimes) {
- if (time.segment[0] <= video.currentTime && time.segment[0] > startingSegmentTime && time.segment[1] > video.currentTime
+ if (time.segment[0] <= video.currentTime && time.segment[0] > startingSegmentTime && time.segment[1] > video.currentTime
&& time.actionType !== ActionType.Poi) {
startingSegmentTime = time.segment[0];
startingSegment = time;
@@ -925,7 +970,7 @@ function startSkipScheduleCheckingForStartSponsors() {
}
if (!found) {
for (const time of sponsorTimesSubmitting) {
- if (time.segment[0] <= video.currentTime && time.segment[0] > startingSegmentTime && time.segment[1] > video.currentTime
+ if (time.segment[0] <= video.currentTime && time.segment[0] > startingSegmentTime && time.segment[1] > video.currentTime
&& time.actionType !== ActionType.Poi) {
startingSegmentTime = time.segment[0];
startingSegment = time;
@@ -944,8 +989,8 @@ function startSkipScheduleCheckingForStartSponsors() {
if (skipOption !== CategorySkipOption.ShowOverlay) {
skipToTime({
v: video,
- skipTime: time.segment,
- skippingSegments: [time],
+ skipTime: time.segment,
+ skippingSegments: [time],
openNotice: true,
unskipTime: video.currentTime
});
@@ -968,7 +1013,7 @@ function startSkipScheduleCheckingForStartSponsors() {
/**
* Get the video info for the current tab from YouTube
- *
+ *
* TODO: Replace
*/
async function getVideoInfo(): Promise {
@@ -990,10 +1035,10 @@ function getYouTubeVideoID(document: Document): string | boolean {
const url = document.URL;
// clips should never skip, going from clip to full video has no indications.
if (url.includes("youtube.com/clip/")) return false;
+ // skip to document and don't hide if on /embed/
+ if (url.includes("/embed/") && url.includes("youtube.com")) return getYouTubeVideoIDFromDocument(document, false);
// skip to URL if matches youtube watch or invidious or matches youtube pattern
if ((!url.includes("youtube.com")) || url.includes("/watch") || url.includes("/shorts/") || url.includes("playlist")) return getYouTubeVideoIDFromURL(url);
- // skip to document and don't hide if on /embed/
- if (url.includes("/embed/")) return getYouTubeVideoIDFromDocument(document, false);
// skip to document if matches pattern
if (url.includes("/channel/") || url.includes("/user/") || url.includes("/c/")) return getYouTubeVideoIDFromDocument(document);
// not sure, try URL then document
@@ -1016,9 +1061,9 @@ function getYouTubeVideoIDFromURL(url: string): string | boolean {
//Attempt to parse url
let urlObject: URL = null;
- try {
+ try {
urlObject = new URL(url);
- } catch (e) {
+ } catch (e) {
console.error("[SB] Unable to parse URL: " + url);
return false;
}
@@ -1117,11 +1162,12 @@ function updatePreviewBar(): void {
async function whitelistCheck() {
const whitelistedChannels = Config.config.whitelistedChannels;
- 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
- ?? document.querySelector("a.slim-owner-icon-and-title")?.getAttribute("href")?.replace(/\/.+\//, ""); // Mobile YouTube
+ const getChannelID = () =>
+ (document.querySelector("a.ytd-video-owner-renderer") // YouTube
+ ?? document.querySelector("a.ytp-title-channel-logo") // YouTube Embed
+ ?? document.querySelector(".channel-profile #channel-name")?.parentElement.parentElement // Invidious
+ ?? document.querySelector("a.slim-owner-icon-and-title")) // Mobile YouTube
+ ?.getAttribute("href")?.match(/\/channel\/(UC[a-zA-Z0-9_-]{22})/)[1];
try {
await utils.wait(() => !!getChannelID(), 6000, 20);
@@ -1140,7 +1186,7 @@ async function whitelistCheck() {
}
//see if this is a whitelisted channel
- if (whitelistedChannels != undefined &&
+ if (whitelistedChannels != undefined &&
channelIDInfo.status === ChannelIDStatus.Found && whitelistedChannels.includes(channelIDInfo.id)) {
channelWhitelisted = true;
}
@@ -1152,24 +1198,24 @@ async function whitelistCheck() {
/**
* Returns info about the next upcoming sponsor skip
*/
-function getNextSkipIndex(currentTime: number, includeIntersectingSegments: boolean, includeNonIntersectingSegments: boolean):
+function getNextSkipIndex(currentTime: number, includeIntersectingSegments: boolean, includeNonIntersectingSegments: boolean):
{array: ScheduledTime[], index: number, endIndex: number, openNotice: boolean} {
- const { includedTimes: submittedArray, scheduledTimes: sponsorStartTimes } =
+ const { includedTimes: submittedArray, scheduledTimes: sponsorStartTimes } =
getStartTimes(sponsorTimes, includeIntersectingSegments, includeNonIntersectingSegments);
const { scheduledTimes: sponsorStartTimesAfterCurrentTime } = getStartTimes(sponsorTimes, includeIntersectingSegments, includeNonIntersectingSegments, currentTime, true);
const minSponsorTimeIndex = sponsorStartTimes.indexOf(Math.min(...sponsorStartTimesAfterCurrentTime));
const endTimeIndex = getLatestEndTimeIndex(submittedArray, minSponsorTimeIndex);
- const { includedTimes: unsubmittedArray, scheduledTimes: unsubmittedSponsorStartTimes } =
+ const { includedTimes: unsubmittedArray, scheduledTimes: unsubmittedSponsorStartTimes } =
getStartTimes(sponsorTimesSubmitting, includeIntersectingSegments, includeNonIntersectingSegments);
const { scheduledTimes: unsubmittedSponsorStartTimesAfterCurrentTime } = getStartTimes(sponsorTimesSubmitting, includeIntersectingSegments, includeNonIntersectingSegments, currentTime, false);
const minUnsubmittedSponsorTimeIndex = unsubmittedSponsorStartTimes.indexOf(Math.min(...unsubmittedSponsorStartTimesAfterCurrentTime));
const previewEndTimeIndex = getLatestEndTimeIndex(unsubmittedArray, minUnsubmittedSponsorTimeIndex);
- if ((minUnsubmittedSponsorTimeIndex === -1 && minSponsorTimeIndex !== -1) ||
+ if ((minUnsubmittedSponsorTimeIndex === -1 && minSponsorTimeIndex !== -1) ||
sponsorStartTimes[minSponsorTimeIndex] < unsubmittedSponsorStartTimes[minUnsubmittedSponsorTimeIndex]) {
return {
array: submittedArray,
@@ -1189,20 +1235,20 @@ function getNextSkipIndex(currentTime: number, includeIntersectingSegments: bool
/**
* This returns index if the skip option is not AutoSkip
- *
+ *
* Finds the last endTime that occurs in a segment that the given
* segment skips into that is part of an AutoSkip category.
- *
- * Used to find where a segment should truely skip to if there are intersecting submissions due to
+ *
+ * Used to find where a segment should truely skip to if there are intersecting submissions due to
* them having different categories.
- *
- * @param sponsorTimes
+ *
+ * @param sponsorTimes
* @param index Index of the given sponsor
- * @param hideHiddenSponsors
+ * @param hideHiddenSponsors
*/
function getLatestEndTimeIndex(sponsorTimes: SponsorTime[], index: number, hideHiddenSponsors = true): number {
// Only combine segments for AutoSkip
- if (index == -1 ||
+ if (index == -1 ||
!shouldAutoSkip(sponsorTimes[index])
|| sponsorTimes[index].actionType !== ActionType.Skip) {
return index;
@@ -1215,7 +1261,7 @@ function getLatestEndTimeIndex(sponsorTimes: SponsorTime[], index: number, hideH
const currentSegment = sponsorTimes[i].segment;
const latestEndTime = sponsorTimes[latestEndTimeIndex].segment[1];
- if (currentSegment[0] <= latestEndTime && currentSegment[1] > latestEndTime
+ if (currentSegment[0] <= latestEndTime && currentSegment[1] > latestEndTime
&& (!hideHiddenSponsors || sponsorTimes[i].hidden === SponsorHideType.Visible)
&& shouldAutoSkip(sponsorTimes[i])
&& sponsorTimes[i].actionType === ActionType.Skip) {
@@ -1235,11 +1281,11 @@ function getLatestEndTimeIndex(sponsorTimes: SponsorTime[], index: number, hideH
/**
* Gets just the start times from a sponsor times array.
* Optionally specify a minimum
- *
- * @param sponsorTimes
+ *
+ * @param sponsorTimes
* @param minimum
* @param hideHiddenSponsors
- * @param includeIntersectingSegments If true, it will include segments that start before
+ * @param includeIntersectingSegments If true, it will include segments that start before
* the current time, but end after
*/
function getStartTimes(sponsorTimes: SponsorTime[], includeIntersectingSegments: boolean, includeNonIntersectingSegments: boolean,
@@ -1274,7 +1320,7 @@ function getStartTimes(sponsorTimes: SponsorTime[], includeIntersectingSegments:
scheduledTimes.push(possibleTimes[i].scheduledTime);
includedTimes.push(possibleTimes[i]);
- }
+ }
}
return { includedTimes, scheduledTimes };
@@ -1282,8 +1328,8 @@ function getStartTimes(sponsorTimes: SponsorTime[], includeIntersectingSegments:
/**
* Skip to exact time in a video and autoskips
- *
- * @param time
+ *
+ * @param time
*/
function previewTime(time: number, unpause = true) {
video.currentTime = time;
@@ -1308,7 +1354,7 @@ function sendTelemetryAndCount(skippingSegments: SponsorTime[], secondsSkipped:
Config.config.skipCount = Config.config.skipCount + 1;
counted = true;
}
-
+
if (fullSkip) utils.asyncRequestToServer("POST", "/api/viewedVideoSponsorTime?UUID=" + segment.UUID);
}
}
@@ -1321,7 +1367,7 @@ function skipToTime({v, skipTime, skippingSegments, openNotice, forceAutoSkip, u
// There will only be one submission if it is manual skip
const autoSkip: boolean = forceAutoSkip || shouldAutoSkip(skippingSegments[0]);
- if ((autoSkip || sponsorTimesSubmitting.some((time) => time.segment === skippingSegments[0].segment))
+ if ((autoSkip || sponsorTimesSubmitting.some((time) => time.segment === skippingSegments[0].segment))
&& v.currentTime !== skipTime[1]) {
switch(skippingSegments[0].actionType) {
case ActionType.Poi:
@@ -1362,8 +1408,8 @@ function skipToTime({v, skipTime, skippingSegments, openNotice, forceAutoSkip, u
})
}
- if (!autoSkip
- && skippingSegments.length === 1
+ if (!autoSkip
+ && skippingSegments.length === 1
&& skippingSegments[0].actionType === ActionType.Poi) {
skipButtonControlBar.enable(skippingSegments[0]);
if (onMobileYouTube || Config.config.skipKeybind == null) skipButtonControlBar.setShowKeybindHint(false);
@@ -1396,7 +1442,7 @@ function unskipSponsorTime(segment: SponsorTime, unskipTime: number = null) {
//add a tiny bit of time to make sure it is not skipped again
video.currentTime = unskipTime ?? segment.segment[0] + 0.001;
}
-
+
}
function reskipSponsorTime(segment: SponsorTime) {
@@ -1407,7 +1453,7 @@ function reskipSponsorTime(segment: SponsorTime) {
const skippedTime = Math.max(segment.segment[1] - video.currentTime, 0);
const segmentDuration = segment.segment[1] - segment.segment[0];
const fullSkip = skippedTime / segmentDuration > manualSkipPercentCount;
-
+
video.currentTime = segment.segment[1];
sendTelemetryAndCount([segment], skippedTime, fullSkip);
startSponsorSchedule(true, segment.segment[1], false);
@@ -1459,8 +1505,8 @@ function shouldAutoSkip(segment: SponsorTime): boolean {
}
function shouldSkip(segment: SponsorTime): boolean {
- return (segment.actionType !== ActionType.Full
- && utils.getCategorySelection(segment.category)?.option !== CategorySkipOption.ShowOverlay)
+ return (segment.actionType !== ActionType.Full
+ && utils.getCategorySelection(segment.category)?.option !== CategorySkipOption.ShowOverlay)
|| (Config.config.autoSkipOnMusicVideos && sponsorTimes?.some((s) => s.category === "music_offtopic"));
}
@@ -1478,10 +1524,10 @@ async function createButtons(): Promise {
createButton("info", "openPopup", openInfoMenu, "PlayerInfoIconSponsorBlocker.svg");
const controlsContainer = getControls();
- if (Config.config.autoHideInfoButton && !onInvidious && controlsContainer
+ if (Config.config.autoHideInfoButton && !onInvidious && controlsContainer
&& playerButtons["info"]?.button && !controlsWithEventListeners.includes(controlsContainer)) {
controlsWithEventListeners.push(controlsContainer);
-
+
AnimationUtils.setupAutoHideAnimation(playerButtons["info"].button, controlsContainer);
}
}
@@ -1576,7 +1622,7 @@ function startOrEndTimingNewSegment() {
const existingSegment = getIncompleteSegment();
const existingTime = existingSegment.segment[0];
const currentTime = roundedTime;
-
+
// Swap timestamps if the user put the segment end before the start
existingSegment.segment = [Math.min(existingTime, currentTime), Math.max(existingTime, currentTime)];
}
@@ -1680,17 +1726,19 @@ function openInfoMenu() {
popup.innerHTML = htmlData;
//close button
- const closeButton = document.createElement("div");
- closeButton.innerText = chrome.i18n.getMessage("closePopup");
- closeButton.classList.add("smallLink");
- closeButton.setAttribute("align", "center");
+ const closeButton = document.createElement("button");
+ const closeButtonIcon = document.createElement("img");
+ closeButtonIcon.src = chrome.extension.getURL("icons/close.png");
+ closeButtonIcon.width = 15;
+ closeButtonIcon.height = 15;
+ closeButton.appendChild(closeButtonIcon);
+ closeButton.setAttribute("title", chrome.i18n.getMessage("closePopup"));
+ closeButton.classList.add("sbCloseButton");
closeButton.addEventListener("click", closeInfoMenu);
- // Theme based color
- closeButton.style.color = "var(--yt-spec-text-primary)";
//add the close button
popup.prepend(closeButton);
-
+
const parentNodes = document.querySelectorAll("#secondary");
let parentNode = null;
for (let i = 0; i < parentNodes.length; i++) {
@@ -1801,7 +1849,7 @@ async function vote(type: number, UUID: SegmentUUID, category?: Category, skipNo
} else {
skipNotice.setNoticeInfoMessage.bind(skipNotice)(GenericUtils.getErrorMessage(response.statusCode, response.responseText))
}
-
+
skipNotice.resetVoteButtonInfo.bind(skipNotice)();
}
}
@@ -1827,7 +1875,7 @@ async function voteAsync(type: number, UUID: SegmentUUID, category?: Category):
// Count this as a skip
Config.config.minutesSaved = Config.config.minutesSaved + factor * (sponsorTimes[sponsorIndex].segment[1] - sponsorTimes[sponsorIndex].segment[0]) / 60;
-
+
Config.config.skipCount = Config.config.skipCount + factor;
}
@@ -1889,8 +1937,8 @@ function submitSponsorTimes() {
submissionNotice.close();
submissionNotice = null;
return;
- }
-
+ }
+
if (sponsorTimesSubmitting !== undefined && sponsorTimesSubmitting.length > 0) {
submissionNotice = new SubmissionNotice(skipNoticeContentContainer, sendSubmitMessage);
}
@@ -1926,9 +1974,9 @@ async function sendSubmitMessage() {
for (let i = 0; i < sponsorTimesSubmitting.length; i++) {
const duration = sponsorTimesSubmitting[i].segment[1] - sponsorTimesSubmitting[i].segment[0];
if (duration > 0 && duration < Config.config.minDuration) {
- const confirmShort = chrome.i18n.getMessage("shortCheck") + "\n\n" +
+ const confirmShort = chrome.i18n.getMessage("shortCheck") + "\n\n" +
getSegmentsMessage(sponsorTimesSubmitting);
-
+
if(!confirm(confirmShort)) return;
}
}
@@ -2033,10 +2081,10 @@ function hotkeyListener(e: KeyboardEvent): void {
|| document.activeElement?.id?.toLowerCase()?.includes("editable")) return;
const key: Keybind = {
- key: e.key,
- code: e.code,
- alt: e.altKey,
- ctrl: e.ctrlKey,
+ key: e.key,
+ code: e.code,
+ alt: e.altKey,
+ ctrl: e.ctrlKey,
shift: e.shiftKey
};
@@ -2097,7 +2145,7 @@ function sendRequestToCustomServer(type, fullAddress, callback) {
xmlhttp.onreadystatechange = function () {
callback(xmlhttp, false);
};
-
+
xmlhttp.onerror = function() {
callback(xmlhttp, true);
};
@@ -2120,14 +2168,15 @@ function updateAdFlag(): void {
}
function showTimeWithoutSkips(skippedDuration: number): void {
- if (onMobileYouTube || onInvidious) return;
+ if (onInvidious) return;
if (isNaN(skippedDuration) || skippedDuration < 0) {
skippedDuration = 0;
}
// YouTube player time display
- const display = document.querySelector(".ytp-time-display.notranslate");
+ const displayClass = onMobileYouTube ? "ytm-time-display" : "ytp-time-display.notranslate"
+ const display = document.querySelector(`.${displayClass}`);
if (!display) return;
const durationID = "sponsorBlockDurationAfterSkips";
@@ -2137,19 +2186,19 @@ function showTimeWithoutSkips(skippedDuration: number): void {
if (duration === null) {
duration = document.createElement('span');
duration.id = durationID;
- duration.classList.add("ytp-time-duration");
+ duration.classList.add(displayClass);
display.appendChild(duration);
}
- const durationAfterSkips = GenericUtils.getFormattedTime(video?.duration - skippedDuration)
+ const durationAfterSkips = GenericUtils.getFormattedTime(video?.duration - skippedDuration);
duration.innerText = (durationAfterSkips == null || skippedDuration <= 0) ? "" : " (" + durationAfterSkips + ")";
}
function checkForPreloadedSegment() {
if (loadedPreloadedSegment) return;
-
+
loadedPreloadedSegment = true;
const hashParams = getHashParams();
@@ -2177,4 +2226,4 @@ function checkForPreloadedSegment() {
Config.config.unsubmittedSegments[sponsorVideoID] = sponsorTimesSubmitting;
Config.forceSyncUpdate("unsubmittedSegments");
}
-}
\ No newline at end of file
+}
diff --git a/src/popup.ts b/src/popup.ts
index c0c13eca..e7394c79 100644
--- a/src/popup.ts
+++ b/src/popup.ts
@@ -190,7 +190,7 @@ async function runThePopup(messageListener?: MessageListener): Promise {
PageElements.sponsorTimesViewsDisplayEndWord.innerText = chrome.i18n.getMessage("Segment");
}
PageElements.sponsorTimesViewsDisplay.innerText = viewCount.toLocaleString();
- PageElements.sponsorTimesViewsContainer.style.display = "unset";
+ PageElements.sponsorTimesViewsContainer.style.display = "block";
}
showDonateWidget(viewCount);
@@ -222,7 +222,7 @@ async function runThePopup(messageListener?: MessageListener): Promise {
}
PageElements.sponsorTimesSkipsDoneDisplay.innerText = Config.config.skipCount.toLocaleString();
- PageElements.sponsorTimesSkipsDoneContainer.style.display = "unset";
+ PageElements.sponsorTimesSkipsDoneContainer.style.display = "block";
}
//get the amount of time this user has saved.
diff --git a/src/utils.ts b/src/utils.ts
index 8e59b95d..930ca2b1 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -354,13 +354,15 @@ export default class Utils {
findReferenceNode(): HTMLElement {
const selectors = [
+ "#player-container-id", // Mobile YouTube
"#movie_player",
"#c4-player", // Channel Trailer
"#player-container", // Preview on hover
"#main-panel.ytmusic-player-page", // YouTube music
"#player-container .video-js", // Invidious
".main-video-section > .video-container" // Cloudtube
- ]
+ ];
+
let referenceNode = findValidElementFromSelector(selectors)
if (referenceNode == null) {
//for embeds