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:`, en: `Default styles reference:`,
zh_TW: `認樣式參考:`, 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 { sleep } from "../libs/utils.js";
import { createLogoSVG } from "../libs/svg.js"; import { createLogoSVG } from "../libs/svg.js";
import { randomBetween } from "../libs/utils.js"; import { randomBetween } from "../libs/utils.js";
import { i18n } from "../config";
const VIDEO_SELECT = "#container video"; const VIDEO_SELECT = "#container video";
const CONTORLS_SELECT = ".ytp-right-controls"; const CONTORLS_SELECT = ".ytp-right-controls";
@@ -25,9 +26,13 @@ class YouTubeCaptionProvider {
#ytControls = null; #ytControls = null;
#isBusy = false; #isBusy = false;
#fromLang = "auto"; #fromLang = "auto";
#notificationEl = null;
#notificationTimeout = null;
#i18n = () => "";
constructor(setting = {}) { constructor(setting = {}) {
this.#setting = setting; this.#setting = setting;
this.#i18n = i18n(setting.uiLang || "zh");
} }
initialize() { initialize() {
@@ -45,7 +50,6 @@ class YouTubeCaptionProvider {
if (this.#toggleButton) { if (this.#toggleButton) {
this.#toggleButton.style.opacity = "0.5"; this.#toggleButton.style.opacity = "0.5";
} }
this.#destroyManager();
this.#doubleClick(); this.#doubleClick();
}, 1000); }, 1000);
}); });
@@ -113,14 +117,20 @@ class YouTubeCaptionProvider {
toggleButton.onclick = () => { toggleButton.onclick = () => {
if (this.#isBusy) { if (this.#isBusy) {
logger.info(`Youtube Provider: It's budy now...`); logger.info(`Youtube Provider: It's budy now...`);
return; this.#showNotification(this.#i18n("subtitle_data_processing"));
} }
if (!this.#enabled) { if (!this.#enabled) {
logger.info(`Youtube Provider: Feature toggled ON.`); logger.info(`Youtube Provider: Feature toggled ON.`);
this.#enabled = true;
this.#toggleButton?.replaceChildren(
createLogoSVG({ isSelected: true })
);
this.#startManager(); this.#startManager();
} else { } else {
logger.info(`Youtube Provider: Feature toggled OFF.`); logger.info(`Youtube Provider: Feature toggled OFF.`);
this.#enabled = false;
this.#toggleButton?.replaceChildren(createLogoSVG());
this.#destroyManager(); this.#destroyManager();
} }
}; };
@@ -245,7 +255,7 @@ class YouTubeCaptionProvider {
logger.info("Youtube Provider is busy..."); logger.info("Youtube Provider is busy...");
return; return;
} }
this.#isBusy = true; // todo: 提示用户等待中 this.#isBusy = true;
try { try {
const videoId = this.#getVideoId(); const videoId = this.#getVideoId();
@@ -265,6 +275,8 @@ class YouTubeCaptionProvider {
return; return;
} }
this.#showNotification(this.#i18n("starting_to_process_subtitle"));
const captionTracks = await this.#getCaptionTracks(videoId); const captionTracks = await this.#getCaptionTracks(videoId);
const captionTrack = this.#findCaptionTrack(captionTracks); const captionTrack = this.#findCaptionTrack(captionTracks);
if (!captionTrack) { if (!captionTrack) {
@@ -365,6 +377,7 @@ class YouTubeCaptionProvider {
} }
} catch (error) { } catch (error) {
logger.warn("Youtube Provider: unknow error", error); logger.warn("Youtube Provider: unknow error", error);
this.#showNotification(this.#i18n("subtitle_load_failed"));
} finally { } finally {
this.#isBusy = false; this.#isBusy = false;
} }
@@ -386,11 +399,9 @@ class YouTubeCaptionProvider {
} }
#startManager() { #startManager() {
if (this.#enabled || this.#managerInstance) { if (this.#managerInstance) {
return; return;
} }
this.#enabled = true;
this.#toggleButton?.replaceChildren(createLogoSVG({ isSelected: true }));
const videoEl = document.querySelector(VIDEO_SELECT); const videoEl = document.querySelector(VIDEO_SELECT);
if (!videoEl) { if (!videoEl) {
@@ -400,8 +411,8 @@ class YouTubeCaptionProvider {
const videoId = this.#getVideoId(); const videoId = this.#getVideoId();
if (!this.#subtitles?.length || this.#videoId !== videoId) { if (!this.#subtitles?.length || this.#videoId !== videoId) {
// todo: 等待并给出用户提示
logger.info("Youtube Provider: No subtitles"); logger.info("Youtube Provider: No subtitles");
this.#showNotification(this.#i18n("try_get_subtitle_data"));
this.#doubleClick(); this.#doubleClick();
return; return;
} }
@@ -416,26 +427,24 @@ class YouTubeCaptionProvider {
}); });
this.#managerInstance.start(); this.#managerInstance.start();
this.#showNotification(this.#i18n("subtitle_load_succeed"));
const ytCaption = document.querySelector(YT_CAPTION_SELECT); const ytCaption = document.querySelector(YT_CAPTION_SELECT);
ytCaption && (ytCaption.style.display = "none"); ytCaption && (ytCaption.style.display = "none");
} }
#destroyManager() { #destroyManager() {
if (!this.#enabled) { if (!this.#managerInstance) {
return; return;
} }
this.#enabled = false;
this.#toggleButton?.replaceChildren(createLogoSVG());
logger.info("Youtube Provider: Destroying manager..."); logger.info("Youtube Provider: Destroying manager...");
this.#managerInstance.destroy();
this.#managerInstance = null;
const ytCaption = document.querySelector(YT_CAPTION_SELECT); const ytCaption = document.querySelector(YT_CAPTION_SELECT);
ytCaption && (ytCaption.style.display = "block"); ytCaption && (ytCaption.style.display = "block");
if (this.#managerInstance) {
this.#managerInstance.destroy();
this.#managerInstance = null;
}
} }
#formatSubtitles(flatEvents, lang) { #formatSubtitles(flatEvents, lang) {
@@ -790,6 +799,45 @@ class YouTubeCaptionProvider {
logger.info("Youtube Provider: All subtitle chunks processed."); 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 = (() => { export const YouTubeInitializer = (() => {

View File

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