From 2d0ea09e0614be3d510b132586785cc9d94d746e Mon Sep 17 00:00:00 2001 From: Gabe Date: Thu, 9 Oct 2025 11:55:02 +0800 Subject: [PATCH] fix: subtitle --- src/config/msg.js | 4 +- src/injector.js | 31 +---------- src/subtitle/BilingualSubtitleManager.js | 2 +- src/subtitle/YouTubeCaptionProvider.js | 70 +++++++++++++++++------- src/subtitle/globalVariable.js | 38 ------------- 5 files changed, 54 insertions(+), 91 deletions(-) delete mode 100644 src/subtitle/globalVariable.js diff --git a/src/config/msg.js b/src/config/msg.js index 605c91e..d82f447 100644 --- a/src/config/msg.js +++ b/src/config/msg.js @@ -26,5 +26,5 @@ export const MSG_BUILTINAI_DETECT = "builtinai_detect"; export const MSG_BUILTINAI_TRANSLATE = "builtinai_translte"; export const MSG_XHR_DATA_YOUTUBE = "KISS_XHR_DATA_YOUTUBE"; -export const MSG_GLOBAL_VAR_FETCH = "KISS_GLOBAL_VAR_FETCH"; -export const MSG_GLOBAL_VAR_BACK = "KISS_GLOBAL_VAR_BACK"; +// export const MSG_GLOBAL_VAR_FETCH = "KISS_GLOBAL_VAR_FETCH"; +// export const MSG_GLOBAL_VAR_BACK = "KISS_GLOBAL_VAR_BACK"; diff --git a/src/injector.js b/src/injector.js index 1d25e44..1963d72 100644 --- a/src/injector.js +++ b/src/injector.js @@ -1,34 +1,5 @@ -import { - MSG_XHR_DATA_YOUTUBE, - MSG_GLOBAL_VAR_FETCH, - MSG_GLOBAL_VAR_BACK, -} from "./config"; +import { MSG_XHR_DATA_YOUTUBE } from "./config"; -// 响应window全局对象查询 -(function () { - window.addEventListener("message", (event) => { - if ( - event.source === window && - event.data && - event.data.type === MSG_GLOBAL_VAR_FETCH - ) { - const { varName, requestId } = event.data; - if (varName) { - const value = window[varName]; - window.postMessage( - { - type: MSG_GLOBAL_VAR_BACK, - payload: value, - requestId: requestId, - }, - window.location.origin - ); - } - } - }); -})(); - -// 拦截字幕数据 (function () { const originalOpen = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function (...args) { diff --git a/src/subtitle/BilingualSubtitleManager.js b/src/subtitle/BilingualSubtitleManager.js index f8a6ef2..3b33394 100644 --- a/src/subtitle/BilingualSubtitleManager.js +++ b/src/subtitle/BilingualSubtitleManager.js @@ -10,7 +10,7 @@ export class BilingualSubtitleManager { #translationService; #captionWindowEl = null; #currentSubtitleIndex = -1; - #preTranslateSeconds = 60; + #preTranslateSeconds = 100; #setting = {}; /** diff --git a/src/subtitle/YouTubeCaptionProvider.js b/src/subtitle/YouTubeCaptionProvider.js index fb2b9fa..f45ec91 100644 --- a/src/subtitle/YouTubeCaptionProvider.js +++ b/src/subtitle/YouTubeCaptionProvider.js @@ -1,7 +1,6 @@ import { logger } from "../libs/log.js"; import { apiTranslate } from "../apis/index.js"; import { BilingualSubtitleManager } from "./BilingualSubtitleManager.js"; -import { getGlobalVariable } from "./globalVariable.js"; import { MSG_XHR_DATA_YOUTUBE, APP_NAME } from "../config"; import { truncateWords, sleep } from "../libs/utils.js"; import { createLogoSvg } from "../libs/svg.js"; @@ -32,6 +31,15 @@ class YouTubeCaptionProvider { this.#handleInterceptedRequest(url, response); } }); + document.body.addEventListener("yt-navigate-finish", () => { + setTimeout(() => { + if (this.#toggleButton) { + this.#toggleButton.style.opacity = "0.5"; + } + this.#destroyManager(); + this.#doubleClick(); + }, 1000); + }); this.#waitForElement(CONTORLS_SELECT, () => this.#injectToggleButton()); } @@ -104,13 +112,9 @@ class YouTubeCaptionProvider { }; this.#toggleButton = toggleButton; this.#ytControls.before(kissControls); - - this.#doubleClick(); } - #findCaptionTrack(ytPlayer) { - const captionTracks = - ytPlayer?.captions?.playerCaptionsTracklistRenderer?.captionTracks || []; + #findCaptionTrack(captionTracks) { let captionTrack = captionTracks.find((item) => item.vssId?.startsWith(".en") ); @@ -122,12 +126,27 @@ class YouTubeCaptionProvider { return captionTrack; } + async #getCaptionTracks(videoId) { + try { + const url = `https://www.youtube.com/watch?v=${videoId}`; + const html = await fetch(url).then((r) => r.text()); + const match = html.match(/ytInitialPlayerResponse\s*=\s*(\{.*?\});/s); + if (!match) return []; + const data = JSON.parse(match[1]); + return ( + data.captions?.playerCaptionsTracklistRenderer?.captionTracks || [] + ); + } catch (err) { + logger.info("Youtube Provider: get captionTracks", err); + } + } + async #getSubtitleEvents(captionTrack, potUrl, responseText) { if (potUrl.searchParams.get("lang") === captionTrack.languageCode) { try { return JSON.parse(responseText); } catch (err) { - logger.error("parse responseText", err); + logger.error("Youtube Provider: parse responseText", err); return null; } } @@ -157,23 +176,20 @@ class YouTubeCaptionProvider { } } + #getVideoId() { + const docUrl = new URL(document.location.href); + return docUrl.searchParams.get("v"); + } + async #handleInterceptedRequest(url, responseText) { try { if (!responseText) { return; } - const ytPlayer = await getGlobalVariable("ytInitialPlayerResponse"); - const captionTrack = this.#findCaptionTrack(ytPlayer); - if (!captionTrack) { - logger.warn("Youtube Provider: CaptionTrack not found."); - return; - } - - const potUrl = new URL(url); - const { videoId } = ytPlayer.videoDetails || {}; - if (videoId !== potUrl.searchParams.get("v")) { - logger.info("Youtube Provider: skip other timedtext."); + const videoId = this.#getVideoId(); + if (!videoId) { + logger.info("Youtube Provider: can't get doc videoId"); return; } @@ -182,6 +198,19 @@ class YouTubeCaptionProvider { return; } + const potUrl = new URL(url); + if (videoId !== potUrl.searchParams.get("v")) { + logger.info("Youtube Provider: skip other timedtext."); + return; + } + + const captionTracks = await this.#getCaptionTracks(videoId); + const captionTrack = this.#findCaptionTrack(captionTracks); + if (!captionTrack) { + logger.info("Youtube Provider: CaptionTrack not found."); + return; + } + const subtitleEvents = await this.#getSubtitleEvents( captionTrack, potUrl, @@ -200,7 +229,7 @@ class YouTubeCaptionProvider { this.#onCaptionsReady(videoId, subtitles); } catch (error) { - logger.error("Youtube Provider: unknow error", error); + logger.warn("Youtube Provider: unknow error", error); } } @@ -231,7 +260,8 @@ class YouTubeCaptionProvider { return; } - if (this.#subtitles?.length === 0) { + const videoId = this.#getVideoId(); + if (!this.#subtitles?.length || this.#videoId !== videoId) { // todo: 等待并给出用户提示 logger.info("Youtube Provider: No subtitles"); this.#doubleClick(); diff --git a/src/subtitle/globalVariable.js b/src/subtitle/globalVariable.js deleted file mode 100644 index 5748104..0000000 --- a/src/subtitle/globalVariable.js +++ /dev/null @@ -1,38 +0,0 @@ -import { genEventName } from "../libs/utils"; -import { MSG_GLOBAL_VAR_BACK, MSG_GLOBAL_VAR_FETCH } from "../config"; - -export function getGlobalVariable(varName, timeout = 10000) { - return new Promise((resolve, reject) => { - const requestId = genEventName(); - let timeoutId = null; - - const responseHandler = (event) => { - if ( - event.source === window && - event.data && - event.data.type === MSG_GLOBAL_VAR_BACK && - event.data.requestId === requestId - ) { - clearTimeout(timeoutId); - window.removeEventListener("message", responseHandler); - resolve(event.data.payload); - } - }; - - window.addEventListener("message", responseHandler); - - timeoutId = setTimeout(() => { - window.removeEventListener("message", responseHandler); - reject(new Error(`Read "${varName}" timeout: ${timeout}ms`)); - }, timeout); - - window.postMessage( - { - type: MSG_GLOBAL_VAR_FETCH, - varName: varName, - requestId: requestId, - }, - window.location.origin - ); - }); -}