feat: add notice for subtitle

This commit is contained in:
Gabe
2025-10-12 01:41:53 +08:00
parent 1afe976777
commit 0104cb9f29
3 changed files with 91 additions and 15 deletions

View File

@@ -1598,4 +1598,31 @@ export const I18N = {
en: `Default styles reference:`,
zh_TW: `認樣式參考:`,
},
subtitle_load_succeed: {
zh: `双语字幕加载成功!`,
en: `Bilingual subtitles loaded successfully!`,
zh_TW: `双语字幕加载成功!`,
},
subtitle_load_failed: {
zh: `双语字幕加载失败!`,
en: `Failed to load bilingual subtitles!`,
zh_TW: `双语字幕加载失败!`,
},
try_get_subtitle_data: {
zh: `尝试获取字幕数据,请稍候...`,
en: `Trying to get subtitle data, please wait...`,
zh_TW: `尝试获取字幕数据,请稍候...`,
},
subtitle_data_processing: {
zh: `字幕数据处理中...`,
en: `Subtitle data processing...`,
zh_TW: `字幕数据处理中...`,
},
starting_to_process_subtitle: {
zh: `开始处理字幕数据...`,
en: `Starting to process subtitle data...`,
zh_TW: `开始处理字幕数据...`,
},
};
export const i18n = (lang) => (key) => I18N[key]?.[lang] || "";

View File

@@ -10,6 +10,7 @@ import {
import { sleep } from "../libs/utils.js";
import { createLogoSVG } from "../libs/svg.js";
import { randomBetween } from "../libs/utils.js";
import { i18n } from "../config";
const VIDEO_SELECT = "#container video";
const CONTORLS_SELECT = ".ytp-right-controls";
@@ -25,9 +26,13 @@ class YouTubeCaptionProvider {
#ytControls = null;
#isBusy = false;
#fromLang = "auto";
#notificationEl = null;
#notificationTimeout = null;
#i18n = () => "";
constructor(setting = {}) {
this.#setting = setting;
this.#i18n = i18n(setting.uiLang || "zh");
}
initialize() {
@@ -45,7 +50,6 @@ class YouTubeCaptionProvider {
if (this.#toggleButton) {
this.#toggleButton.style.opacity = "0.5";
}
this.#destroyManager();
this.#doubleClick();
}, 1000);
});
@@ -113,14 +117,20 @@ class YouTubeCaptionProvider {
toggleButton.onclick = () => {
if (this.#isBusy) {
logger.info(`Youtube Provider: It's budy now...`);
return;
this.#showNotification(this.#i18n("subtitle_data_processing"));
}
if (!this.#enabled) {
logger.info(`Youtube Provider: Feature toggled ON.`);
this.#enabled = true;
this.#toggleButton?.replaceChildren(
createLogoSVG({ isSelected: true })
);
this.#startManager();
} else {
logger.info(`Youtube Provider: Feature toggled OFF.`);
this.#enabled = false;
this.#toggleButton?.replaceChildren(createLogoSVG());
this.#destroyManager();
}
};
@@ -245,7 +255,7 @@ class YouTubeCaptionProvider {
logger.info("Youtube Provider is busy...");
return;
}
this.#isBusy = true; // todo: 提示用户等待中
this.#isBusy = true;
try {
const videoId = this.#getVideoId();
@@ -265,6 +275,8 @@ class YouTubeCaptionProvider {
return;
}
this.#showNotification(this.#i18n("starting_to_process_subtitle"));
const captionTracks = await this.#getCaptionTracks(videoId);
const captionTrack = this.#findCaptionTrack(captionTracks);
if (!captionTrack) {
@@ -365,6 +377,7 @@ class YouTubeCaptionProvider {
}
} catch (error) {
logger.warn("Youtube Provider: unknow error", error);
this.#showNotification(this.#i18n("subtitle_load_failed"));
} finally {
this.#isBusy = false;
}
@@ -386,11 +399,9 @@ class YouTubeCaptionProvider {
}
#startManager() {
if (this.#enabled || this.#managerInstance) {
if (this.#managerInstance) {
return;
}
this.#enabled = true;
this.#toggleButton?.replaceChildren(createLogoSVG({ isSelected: true }));
const videoEl = document.querySelector(VIDEO_SELECT);
if (!videoEl) {
@@ -400,8 +411,8 @@ class YouTubeCaptionProvider {
const videoId = this.#getVideoId();
if (!this.#subtitles?.length || this.#videoId !== videoId) {
// todo: 等待并给出用户提示
logger.info("Youtube Provider: No subtitles");
this.#showNotification(this.#i18n("try_get_subtitle_data"));
this.#doubleClick();
return;
}
@@ -416,26 +427,24 @@ class YouTubeCaptionProvider {
});
this.#managerInstance.start();
this.#showNotification(this.#i18n("subtitle_load_succeed"));
const ytCaption = document.querySelector(YT_CAPTION_SELECT);
ytCaption && (ytCaption.style.display = "none");
}
#destroyManager() {
if (!this.#enabled) {
if (!this.#managerInstance) {
return;
}
this.#enabled = false;
this.#toggleButton?.replaceChildren(createLogoSVG());
logger.info("Youtube Provider: Destroying manager...");
this.#managerInstance.destroy();
this.#managerInstance = null;
const ytCaption = document.querySelector(YT_CAPTION_SELECT);
ytCaption && (ytCaption.style.display = "block");
if (this.#managerInstance) {
this.#managerInstance.destroy();
this.#managerInstance = null;
}
}
#formatSubtitles(flatEvents, lang) {
@@ -790,6 +799,45 @@ class YouTubeCaptionProvider {
logger.info("Youtube Provider: All subtitle chunks processed.");
}
#createNotificationElement() {
const notificationEl = document.createElement("div");
notificationEl.className = "kiss-notification";
Object.assign(notificationEl.style, {
position: "absolute",
top: "40%",
left: "50%",
transform: "translateX(-50%)",
background: "rgba(0,0,0,0.7)",
color: "red",
padding: "0.5em 1em",
borderRadius: "4px",
zIndex: "2147483647",
opacity: "0",
transition: "opacity 0.3s ease-in-out",
pointerEvents: "none",
fontSize: "2em",
width: "50%",
textAlign: "center",
});
const videoEl = document.querySelector(VIDEO_SELECT);
const videoContainer = videoEl?.parentElement?.parentElement;
if (videoContainer) {
videoContainer.appendChild(notificationEl);
this.#notificationEl = notificationEl;
}
}
#showNotification(message, duration = 3000) {
if (!this.#notificationEl) this.#createNotificationElement();
this.#notificationEl.textContent = message;
this.#notificationEl.style.opacity = "1";
clearTimeout(this.#notificationTimeout);
this.#notificationTimeout = setTimeout(() => {
this.#notificationEl.style.opacity = "0";
}, duration);
}
}
export const YouTubeInitializer = (() => {

View File

@@ -34,6 +34,7 @@ export function runSubtitle({ href, setting }) {
...subtitleSetting,
apiSetting,
segApiSetting,
uiLang: setting.uiLang,
});
}
} catch (err) {