fix: subtitle

This commit is contained in:
Gabe
2025-10-09 11:55:02 +08:00
parent aeec5e361c
commit 2d0ea09e06
5 changed files with 54 additions and 91 deletions

View File

@@ -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";

View File

@@ -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) {

View File

@@ -10,7 +10,7 @@ export class BilingualSubtitleManager {
#translationService;
#captionWindowEl = null;
#currentSubtitleIndex = -1;
#preTranslateSeconds = 60;
#preTranslateSeconds = 100;
#setting = {};
/**

View File

@@ -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();

View File

@@ -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
);
});
}