fix: subtitle
This commit is contained in:
@@ -26,5 +26,5 @@ export const MSG_BUILTINAI_DETECT = "builtinai_detect";
|
|||||||
export const MSG_BUILTINAI_TRANSLATE = "builtinai_translte";
|
export const MSG_BUILTINAI_TRANSLATE = "builtinai_translte";
|
||||||
|
|
||||||
export const MSG_XHR_DATA_YOUTUBE = "KISS_XHR_DATA_YOUTUBE";
|
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_FETCH = "KISS_GLOBAL_VAR_FETCH";
|
||||||
export const MSG_GLOBAL_VAR_BACK = "KISS_GLOBAL_VAR_BACK";
|
// export const MSG_GLOBAL_VAR_BACK = "KISS_GLOBAL_VAR_BACK";
|
||||||
|
|||||||
@@ -1,34 +1,5 @@
|
|||||||
import {
|
import { MSG_XHR_DATA_YOUTUBE } from "./config";
|
||||||
MSG_XHR_DATA_YOUTUBE,
|
|
||||||
MSG_GLOBAL_VAR_FETCH,
|
|
||||||
MSG_GLOBAL_VAR_BACK,
|
|
||||||
} 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 () {
|
(function () {
|
||||||
const originalOpen = XMLHttpRequest.prototype.open;
|
const originalOpen = XMLHttpRequest.prototype.open;
|
||||||
XMLHttpRequest.prototype.open = function (...args) {
|
XMLHttpRequest.prototype.open = function (...args) {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export class BilingualSubtitleManager {
|
|||||||
#translationService;
|
#translationService;
|
||||||
#captionWindowEl = null;
|
#captionWindowEl = null;
|
||||||
#currentSubtitleIndex = -1;
|
#currentSubtitleIndex = -1;
|
||||||
#preTranslateSeconds = 60;
|
#preTranslateSeconds = 100;
|
||||||
#setting = {};
|
#setting = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { logger } from "../libs/log.js";
|
import { logger } from "../libs/log.js";
|
||||||
import { apiTranslate } from "../apis/index.js";
|
import { apiTranslate } from "../apis/index.js";
|
||||||
import { BilingualSubtitleManager } from "./BilingualSubtitleManager.js";
|
import { BilingualSubtitleManager } from "./BilingualSubtitleManager.js";
|
||||||
import { getGlobalVariable } from "./globalVariable.js";
|
|
||||||
import { MSG_XHR_DATA_YOUTUBE, APP_NAME } from "../config";
|
import { MSG_XHR_DATA_YOUTUBE, APP_NAME } from "../config";
|
||||||
import { truncateWords, sleep } from "../libs/utils.js";
|
import { truncateWords, sleep } from "../libs/utils.js";
|
||||||
import { createLogoSvg } from "../libs/svg.js";
|
import { createLogoSvg } from "../libs/svg.js";
|
||||||
@@ -32,6 +31,15 @@ class YouTubeCaptionProvider {
|
|||||||
this.#handleInterceptedRequest(url, response);
|
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());
|
this.#waitForElement(CONTORLS_SELECT, () => this.#injectToggleButton());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,13 +112,9 @@ class YouTubeCaptionProvider {
|
|||||||
};
|
};
|
||||||
this.#toggleButton = toggleButton;
|
this.#toggleButton = toggleButton;
|
||||||
this.#ytControls.before(kissControls);
|
this.#ytControls.before(kissControls);
|
||||||
|
|
||||||
this.#doubleClick();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#findCaptionTrack(ytPlayer) {
|
#findCaptionTrack(captionTracks) {
|
||||||
const captionTracks =
|
|
||||||
ytPlayer?.captions?.playerCaptionsTracklistRenderer?.captionTracks || [];
|
|
||||||
let captionTrack = captionTracks.find((item) =>
|
let captionTrack = captionTracks.find((item) =>
|
||||||
item.vssId?.startsWith(".en")
|
item.vssId?.startsWith(".en")
|
||||||
);
|
);
|
||||||
@@ -122,12 +126,27 @@ class YouTubeCaptionProvider {
|
|||||||
return captionTrack;
|
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) {
|
async #getSubtitleEvents(captionTrack, potUrl, responseText) {
|
||||||
if (potUrl.searchParams.get("lang") === captionTrack.languageCode) {
|
if (potUrl.searchParams.get("lang") === captionTrack.languageCode) {
|
||||||
try {
|
try {
|
||||||
return JSON.parse(responseText);
|
return JSON.parse(responseText);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error("parse responseText", err);
|
logger.error("Youtube Provider: parse responseText", err);
|
||||||
return null;
|
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) {
|
async #handleInterceptedRequest(url, responseText) {
|
||||||
try {
|
try {
|
||||||
if (!responseText) {
|
if (!responseText) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ytPlayer = await getGlobalVariable("ytInitialPlayerResponse");
|
const videoId = this.#getVideoId();
|
||||||
const captionTrack = this.#findCaptionTrack(ytPlayer);
|
if (!videoId) {
|
||||||
if (!captionTrack) {
|
logger.info("Youtube Provider: can't get doc videoId");
|
||||||
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.");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,6 +198,19 @@ class YouTubeCaptionProvider {
|
|||||||
return;
|
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(
|
const subtitleEvents = await this.#getSubtitleEvents(
|
||||||
captionTrack,
|
captionTrack,
|
||||||
potUrl,
|
potUrl,
|
||||||
@@ -200,7 +229,7 @@ class YouTubeCaptionProvider {
|
|||||||
|
|
||||||
this.#onCaptionsReady(videoId, subtitles);
|
this.#onCaptionsReady(videoId, subtitles);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("Youtube Provider: unknow error", error);
|
logger.warn("Youtube Provider: unknow error", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,7 +260,8 @@ class YouTubeCaptionProvider {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.#subtitles?.length === 0) {
|
const videoId = this.#getVideoId();
|
||||||
|
if (!this.#subtitles?.length || this.#videoId !== videoId) {
|
||||||
// todo: 等待并给出用户提示
|
// todo: 等待并给出用户提示
|
||||||
logger.info("Youtube Provider: No subtitles");
|
logger.info("Youtube Provider: No subtitles");
|
||||||
this.#doubleClick();
|
this.#doubleClick();
|
||||||
|
|||||||
@@ -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
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user