From 4e9293ae0fd7e9c242c4e8dd783ce7073da1a758 Mon Sep 17 00:00:00 2001 From: Gabe Date: Mon, 13 Oct 2025 14:15:14 +0800 Subject: [PATCH] fix: stubtitle --- src/config/setting.js | 2 -- src/subtitle/BilingualSubtitleManager.js | 32 +++++++++++++++---- src/subtitle/YouTubeCaptionProvider.js | 40 ++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 8 deletions(-) diff --git a/src/config/setting.js b/src/config/setting.js index 726a768..47b5ea2 100644 --- a/src/config/setting.js +++ b/src/config/setting.js @@ -101,8 +101,6 @@ background-color: rgba(0, 0, 0, 0.5); color: white; line-height: 1.3; text-shadow: 1px 1px 2px black; -opacity: 0; -transition: opacity 0.3s ease-in-out; display: inline-block`; const SUBTITLE_ORIGIN_STYLE = `font-size: clamp(1.5rem, 3cqw, 3rem);`; diff --git a/src/subtitle/BilingualSubtitleManager.js b/src/subtitle/BilingualSubtitleManager.js index 1113f0c..ed2f5cd 100644 --- a/src/subtitle/BilingualSubtitleManager.js +++ b/src/subtitle/BilingualSubtitleManager.js @@ -10,9 +10,11 @@ export class BilingualSubtitleManager { #formattedSubtitles = []; #translationService; #captionWindowEl = null; + #paperEl = null; #currentSubtitleIndex = -1; #preTranslateSeconds = 100; #setting = {}; + #isAdPlaying = false; /** * @param {object} options @@ -56,6 +58,14 @@ export class BilingualSubtitleManager { this.#formattedSubtitles = []; } + /** + * 更新广告播放状态。 + */ + setIsAdPlaying(isPlaying) { + this.#isAdPlaying = isPlaying; + this.onTimeUpdate(); + } + /** * 创建并配置用于显示字幕的 DOM 元素。 */ @@ -68,6 +78,7 @@ export class BilingualSubtitleManager { height: "100%", left: "0", top: "0", + pointerEvents: "none", }); const paper = document.createElement("div"); @@ -81,16 +92,20 @@ export class BilingualSubtitleManager { textAlign: "center", containerType: "inline-size", zIndex: "2147483647", + pointerEvents: "auto", + display: "none", }); + this.#paperEl = paper; this.#captionWindowEl = document.createElement("div"); this.#captionWindowEl.className = `kiss-caption-window`; this.#captionWindowEl.style.cssText = this.#setting.windowStyle; this.#captionWindowEl.style.pointerEvents = "auto"; this.#captionWindowEl.style.cursor = "grab"; + this.#captionWindowEl.style.opacity = "1"; - paper.appendChild(this.#captionWindowEl); - container.appendChild(paper); + this.#paperEl.appendChild(this.#captionWindowEl); + container.appendChild(this.#paperEl); const videoContainer = this.#videoEl.parentElement?.parentElement; if (!videoContainer) { @@ -101,7 +116,7 @@ export class BilingualSubtitleManager { videoContainer.style.position = "relative"; videoContainer.appendChild(container); - this.#enableDragging(paper, container, this.#captionWindowEl); + this.#enableDragging(this.#paperEl, container, this.#captionWindowEl); } /** @@ -230,7 +245,12 @@ export class BilingualSubtitleManager { * @param {object | null} subtitle - 字幕对象,或 null 用于清空。 */ #updateCaptionDisplay(subtitle) { - if (!this.#captionWindowEl) return; + if (!this.#paperEl || !this.#captionWindowEl) return; + + if (this.#isAdPlaying) { + this.#paperEl.style.display = "none"; + return; + } if (subtitle) { const p1 = document.createElement("p"); @@ -247,9 +267,9 @@ export class BilingualSubtitleManager { this.#captionWindowEl.replaceChildren(p2); } - this.#captionWindowEl.style.opacity = "1"; + this.#paperEl.style.display = "block"; } else { - this.#captionWindowEl.style.opacity = "0"; + this.#paperEl.style.display = "none"; } } diff --git a/src/subtitle/YouTubeCaptionProvider.js b/src/subtitle/YouTubeCaptionProvider.js index 27cb10a..56e2441 100644 --- a/src/subtitle/YouTubeCaptionProvider.js +++ b/src/subtitle/YouTubeCaptionProvider.js @@ -15,6 +15,7 @@ import { i18n } from "../config"; const VIDEO_SELECT = "#container video"; const CONTORLS_SELECT = ".ytp-right-controls"; const YT_CAPTION_SELECT = "#ytp-caption-window-container"; +const YT_AD_SELECT = ".video-ads"; class YouTubeCaptionProvider { #setting = {}; @@ -45,6 +46,7 @@ class YouTubeCaptionProvider { } } }); + window.addEventListener("yt-navigate-finish", () => { setTimeout(() => { if (this.#toggleButton) { @@ -54,9 +56,46 @@ class YouTubeCaptionProvider { this.#doubleClick(); }, 1000); }); + this.#waitForElement(CONTORLS_SELECT, (ytControls) => this.#injectToggleButton(ytControls) ); + + this.#waitForElement(YT_AD_SELECT, (adContainer) => { + this.#moAds(adContainer); + }); + } + + #moAds(adContainer) { + const adSlector = ".ytp-ad-player-overlay-layout"; + const observer = new MutationObserver((mutations) => { + for (const mutation of mutations) { + if (mutation.type === "childList") { + mutation.addedNodes.forEach((node) => { + if (node.nodeType === 1 && node.matches(adSlector)) { + logger.debug("Youtube Provider: AD start playing!", node); + // todo: 顺带把广告快速跳过 + if (this.#managerInstance) { + this.#managerInstance.setIsAdPlaying(true); + } + } + }); + mutation.removedNodes.forEach((node) => { + if (node.nodeType === 1 && node.matches(adSlector)) { + logger.debug("Youtube Provider: Ad ends!"); + if (this.#managerInstance) { + this.#managerInstance.setIsAdPlaying(false); + } + } + }); + } + } + }); + + observer.observe(adContainer, { + childList: true, + subtree: true, + }); } #waitForElement(selector, callback) { @@ -517,6 +556,7 @@ class YouTubeCaptionProvider { let subtitles = this.#processSubtitles({ flatEvents }); const isPoor = this.#isQualityPoor(subtitles); + logger.debug("Youtube Provider: isQualityPoor", { isPoor, subtitles }); if (isPoor) { subtitles = this.#processSubtitles({ flatEvents, usePause: true }); }