// ==UserScript==
// @name 启航教育课程控制(自动关闭字幕)
// @namespace http://tampermonkey.net/
// @version 2.1
// @description 低性能消耗+彻底关闭字幕
// @author YGTT
// @match https://www.iqihang.com/ark/record/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
let currentCourseId = extractCourseId(window.location.href);
let monitorInterval = null;
let subtitleObserver = null; // 字幕元素专用监听器
// --------------------------
// 1. 性能优化:更优雅的监控方案
// --------------------------
function initElegantMonitoring() {
// 清理旧监控
if (monitorInterval) clearInterval(monitorInterval);
if (subtitleObserver) subtitleObserver.disconnect();
// 方案1:DOM变化触发(优先使用,性能最优)
const videoContainer = document.querySelector('.video-container, .player-wrapper') || document.body;
// 只监听字幕相关元素的变化
subtitleObserver = new MutationObserver((mutations) => {
const hasSubtitleChange = mutations.some(mutation =>
mutation.target.matches('[class*="subtitle"], [id*="subtitle"], .vjs-text-track-display') ||
Array.from(mutation.addedNodes).some(node =>
node.nodeType === 1 && node.matches('[class*="subtitle"], [id*="subtitle"]')
)
);
if (hasSubtitleChange) {
console.log('【智能监控】检测到字幕元素变化,触发检查');
checkAndFixSettings();
}
});
subtitleObserver.observe(videoContainer, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['class', 'style', 'checked'] // 只监听关键属性
});
// 方案2:保底定时器(间隔延长至3秒,降低频率)
monitorInterval = setInterval(() => {
const currentId = extractCourseId(window.location.href);
if (currentId !== currentCourseId) {
currentCourseId = currentId;
console.log(`【智能监控】课程ID变化为 ${currentId},重新初始化`);
checkAndFixSettings();
} else {
// 仅在没有DOM变化时做保底检查
console.log('【智能监控】保底检查');
checkAndFixSettings();
}
}, 3000);
console.log('【智能监控】启动(DOM变化+低频率保底)');
}
// --------------------------
// 2. 字幕彻底关闭修复
// --------------------------
function findAllSubtitleElements() {
// 扩展所有可能的字幕元素选择器
return [
...document.querySelectorAll('.vjs-text-track-display, .baijia-subtitle, [class*="subtitle"]'),
...document.querySelectorAll('[id*="subtitle"], [data-role="subtitle"], .vjs-subtitles'),
...document.querySelectorAll('.vjs-captions, [data-type="subtitle"], .subtitle-container'),
...document.querySelectorAll('div:has(> [class*="subtitle"])') // 兼容容器元素
];
}
function simulateRealClick(element) {
if (!element) return false;
// 模拟真实点击的所有事件(解决网站可能忽略合成事件的问题)
const clickEvent = new MouseEvent('click', {
bubbles: true,
cancelable: true,
view: window,
button: 0,
detail: 1
});
// 先触发mousedown再触发click
const mouseDownEvent = new MouseEvent('mousedown', { bubbles: true });
element.dispatchEvent(mouseDownEvent);
setTimeout(() => {
element.dispatchEvent(clickEvent);
element.dispatchEvent(new MouseEvent('mouseup', { bubbles: true }));
}, 50);
return true;
}
function forceCloseSubtitle() {
// 步骤1:强制隐藏所有可能的字幕元素(包括动态生成的)
const allSubtitles = findAllSubtitleElements();
allSubtitles.forEach(el => {
el.style.display = 'none !important';
el.style.visibility = 'hidden !important';
el.style.opacity = '0 !important';
el.style.pointerEvents = 'none !important'; // 防止交互
});
// 步骤2:找到并点击关闭字幕按钮(模拟真实点击)
const closeBtn = document.getElementById('closeVtt') ||
findElementWithText('li', '关闭字幕') ||
findElementWithText('button', '关闭字幕');
const openBtn = document.getElementById('vtt') ||
findElementWithText('li', '打开字幕') ||
findElementWithText('button', '打开字幕');
// 先确保打开按钮处于非活跃状态
if (openBtn && openBtn.classList.contains('active')) {
simulateRealClick(openBtn); // 点击打开按钮可能会切换状态
}
// 再点击关闭按钮(双重保险)
if (closeBtn && !closeBtn.classList.contains('active')) {
simulateRealClick(closeBtn);
// 延迟再次点击,确保网站内部状态更新
setTimeout(() => simulateRealClick(closeBtn), 300);
}
// 步骤3:检查并取消所有字幕相关选项
document.querySelectorAll('input[type="checkbox"], input[type="radio"]').forEach(checkbox => {
const labelText = checkbox.parentElement?.textContent || checkbox.ariaLabel || '';
if (labelText.includes('字幕') && checkbox.checked) {
checkbox.checked = false;
checkbox.dispatchEvent(new Event('change', { bubbles: true }));
// 触发input事件,应对特殊处理
checkbox.dispatchEvent(new Event('input', { bubbles: true }));
}
});
return allSubtitles.every(el => getComputedStyle(el).display === 'none');
}
// --------------------------
// 通用工具函数
// --------------------------
function extractCourseId(url) {
const match = url.match(/record\/(\d+)/);
return match ? match[1] : '';
}
function findElementWithText(selector, text) {
const elements = document.querySelectorAll(selector);
return Array.from(elements).find(el =>
el.textContent.trim().includes(text)
);
}
function checkAndFixSettings() {
// 只检查并修复字幕设置
const subtitlesClosed = forceCloseSubtitle();
if (!subtitlesClosed) {
console.log('【修复】字幕仍显示,300ms后再次尝试');
setTimeout(forceCloseSubtitle, 300); // 二次尝试
}
}
// --------------------------
// 初始化
// --------------------------
function init() {
setTimeout(() => {
checkAndFixSettings();
initElegantMonitoring();
}, 1000);
window.addEventListener('beforeunload', () => {
if (monitorInterval) clearInterval(monitorInterval);
if (subtitleObserver) subtitleObserver.disconnect();
});
}
if (document.readyState === 'complete') {
init();
} else {
window.addEventListener('load', init, { once: true });
}
})();