// ==UserScript== // @name B站/哔哩哔哩/bilibili视频增强脚本(中文字幕+倍速控制+顶部栏控制) // @namespace http://tampermonkey.net/ // @version 2.3 // @description 自动开启中文字幕,提供倍速控制功能(Z/X/C键和1/2/3键),按H键控制顶部栏显示,记忆用户选择 // @author anyphasy // @icon https://play-lh.googleusercontent.com/C1tISqYgtW_ejAmnGzvepbaYt7NJLagPelCZ_lzNv06RJPQgBx1_q3VX67z9wc48EgY=s1024 // @match *://www.bilibili.com/video/* // @match *://www.bilibili.com/list/watchlater* // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // ==/UserScript== (function () { 'use strict'; // 配置参数 const MAX_ATTEMPTS = 10; const INITIAL_DELAY = 800; const RETRY_DELAY = 2000; let attemptCount = 0; let isActive = false; let isEnabled = GM_getValue('bilibili_subtitle_enabled', true); let observer = null; let timeoutId = null; let playbackRates = [2, 1.5, 1.25, 1, 0.75, 0.5]; // 支持的倍速选项 let currentRate = GM_getValue('bilibili_playback_rate', 1); // 当前倍速 let isCustomRate = GM_getValue('bilibili_custom_rate', false); // 是否自定义倍速 let isHeaderHidden = GM_getValue('bilibili_header_hidden', false); // 新增:顶部栏隐藏状态 // 添加Element Plus样式 GM_addStyle(` .bpx-player-dm-notice { position: absolute; top: 10%; /* 将提示位置稍微下移,避免遮挡视频内容 */ left: 50%; transform: translate(-50%, -50%); background-color: rgba(0, 0, 0, 0.8); /* 增加背景不透明度 */ color: white; padding: 12px 20px; /* 增加内边距,让提示更大 */ border-radius: 8px; /* 增加圆角 */ font-size: 18px; /* 增大字体大小 */ font-weight: bold; /* 加粗字体 */ z-index: 9999; pointer-events: none; animation: fadeInOut 2.5s ease-in-out forwards; /* 稍微延长动画时间 */ min-width: 120px; /* 设置最小宽度 */ text-align: center; /* 文字居中 */ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); /* 添加阴影效果 */ } @keyframes fadeInOut { 0% { opacity: 0; } 10% { opacity: 1; } 90% { opacity: 1; } 100% { opacity: 0; } } /* 新增:顶部栏隐藏时的页面调整 */ body.header-hidden { padding-top: 0 !important; } body.header-hidden #biliMainHeader { display: none !important; } `); // 初始化 function init() { /* console.log(`B站增强脚本已加载,字幕状态: ${isEnabled ? '开启' : '关闭'}, 当前倍速: ${currentRate}x, 自定义倍速: ${isCustomRate}`); */ console.log(`B站增强脚本已加载,字幕状态: ${isEnabled ? '开启' : '关闭'}, 当前倍速: ${currentRate}x, 自定义倍速: ${isCustomRate}, 顶部栏状态: ${isHeaderHidden ? '隐藏' : '显示'}`); // 修改这里:直接应用保存的状态(而不是切换) applyHeaderState(); // 清理函数 const cleanup = () => { if (timeoutId) { clearTimeout(timeoutId); timeoutId = null; } if (observer) { observer.disconnect(); observer = null; } document.removeEventListener('keydown', handleKeyPress); }; // 添加页面卸载时的清理 window.addEventListener('beforeunload', cleanup); // 添加键盘监听器 document.addEventListener('keydown', handleKeyPress); console.log('快捷键监听器已添加 (Shift+A:字幕, Z/X/C:倍速, 1/2/3:快速倍速)'); // 设置SPA路由变化监听 setupSPAObserver(); // 立即开始尝试(减少延迟) if (isEnabled) { timeoutId = setTimeout(tryClickChineseSubtitle, INITIAL_DELAY); } // 设置倍速控制 timeoutId = setTimeout(applySavedPlaybackRate, INITIAL_DELAY); } // 设置SPA路由监听 function setupSPAObserver() { let lastUrl = location.href; observer = new MutationObserver(() => { const currentUrl = location.href; if (currentUrl !== lastUrl) { lastUrl = currentUrl; resetState(); if (isEnabled) { timeoutId = setTimeout(tryClickChineseSubtitle, INITIAL_DELAY); } timeoutId = setTimeout(applySavedPlaybackRate, INITIAL_DELAY); } }); observer.observe(document, { subtree: true, childList: true }); } // 重置状态 function resetState() { if (timeoutId) { clearTimeout(timeoutId); timeoutId = null; } attemptCount = 0; isActive = false; } // 快捷键处理 // 修改后的handleKeyPress函数 function handleKeyPress(event) { // 当焦点在输入元素时,不处理快捷键 const activeElement = document.activeElement; const isInputFocused = activeElement && (activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA' || activeElement.isContentEditable); if (isInputFocused) return; // 新增:H键切换顶部栏 if (event.key.toLowerCase() === 'j') { console.log("按下了J键 - 切换顶部栏"); event.preventDefault(); toggleHeader(); return; } // Shift+A 切换字幕 if (event.shiftKey && event.key === 'A') { console.log("按下了Shift+A"); event.preventDefault(); toggleSubtitles(); } // 倍速控制 - 只处理主键盘数字键(event.location === 0) if (!event.ctrlKey && !event.altKey && !event.metaKey && event.location === 0) { switch (event.key.toLowerCase()) { case 'z': console.log("按下了Z键 - 重置倍速"); event.preventDefault(); setPlaybackRate(1); break; case 'x': console.log("按下了X键 - 减小倍速"); event.preventDefault(); decreasePlaybackRate(); break; case 'c': console.log("按下了C键 - 增加倍速"); event.preventDefault(); increasePlaybackRate(); break; case '1': console.log("按下了1键 - 1倍速"); event.preventDefault(); setPlaybackRate(1); break; case '2': console.log("按下了2键 - 2倍速"); event.preventDefault(); setPlaybackRate(2); break; case '3': console.log("按下了3键 - 3倍速"); event.preventDefault(); setCustomPlaybackRate(3); break; } } } // 切换字幕开关 function toggleSubtitles() { isEnabled = !isEnabled; GM_setValue('bilibili_subtitle_enabled', isEnabled); console.log(`字幕功能已${isEnabled ? '开启' : '关闭'}`); resetState(); if (isEnabled) { timeoutId = setTimeout(tryClickChineseSubtitle, INITIAL_DELAY); } else { closeSubtitles(); } } // 尝试点击中文字幕(优化版) function tryClickChineseSubtitle() { timeoutId = null; if (!isEnabled || isActive || attemptCount >= MAX_ATTEMPTS) { return; } attemptCount++; console.log(`尝试点击中文字幕 (第 ${attemptCount} 次)`); // 使用更高效的选择器 let subtitleItems = document.querySelectorAll('.bpx-player-ctrl-subtitle-language-item'); if (subtitleItems.length > 1) { //根据.bpx-player-ctrl-subtitle-language-item-text');里面的文字数量进行排序,少的在前 // 将 NodeList 转换为数组并按照文本长度排序 subtitleItems = Array.from(subtitleItems).sort((a, b) => { const textA = a.querySelector('.bpx-player-ctrl-subtitle-language-item-text')?.textContent?.trim().length || 0; const textB = b.querySelector('.bpx-player-ctrl-subtitle-language-item-text')?.textContent?.trim().length || 0; return textA - textB; }); } let foundChinese = false; for (const item of subtitleItems) { const textElement = item.querySelector('.bpx-player-ctrl-subtitle-language-item-text'); if (textElement && textElement.textContent.trim().includes('中文')) { foundChinese = true; console.log('找到中文字幕按钮'); if (!item.classList.contains('bpx-state-active')) { console.log('中文字幕未激活,执行点击'); textElement.click(); // 使用更快的验证机制 timeoutId = setTimeout(() => verifyActivation(item), 300); } else { console.log('中文字幕已激活'); isActive = true; } break; } } if (!foundChinese && attemptCount < MAX_ATTEMPTS) { console.log('未找到中文字幕按钮,稍后重试'); timeoutId = setTimeout(tryClickChineseSubtitle, RETRY_DELAY); } else if (!foundChinese) { console.log(`已达到最大尝试次数(${MAX_ATTEMPTS}),可能此视频无中文字幕`); } } // 验证激活状态 function verifyActivation(item) { timeoutId = null; if (item.classList.contains('bpx-state-active')) { console.log('中文字幕激活成功'); isActive = true; } else { console.log('中文字幕激活失败'); if (attemptCount < MAX_ATTEMPTS) { timeoutId = setTimeout(tryClickChineseSubtitle, RETRY_DELAY); } } } // 关闭字幕 function closeSubtitles() { const closeButton = document.querySelector('.bpx-player-ctrl-subtitle-close-switch[data-action="close"]'); if (closeButton && !closeButton.classList.contains('bpx-state-active')) { console.log('找到关闭字幕按钮,执行点击'); closeButton.click(); isActive = false; } else { console.log('关闭字幕按钮已激活或未找到'); } } function showRateMessage(message) { // 移除旧的消息 const oldNotice = document.querySelector('.bpx-player-dm-notice'); if (oldNotice) oldNotice.remove(); // 创建新消息元素 const notice = document.createElement('div'); notice.className = 'bpx-player-dm-notice'; notice.textContent = message; // 添加到播放器容器 const playerContainer = document.querySelector('.bpx-player-container') || document.body; playerContainer.appendChild(notice); // 2秒后自动移除 setTimeout(() => { if (notice.parentNode) { notice.parentNode.removeChild(notice); } }, 2000); } // 设置自定义倍速(用于3倍速) function setCustomPlaybackRate(rate) { const videoElement = document.querySelector('.bpx-player-video-wrap video'); if (videoElement) { videoElement.playbackRate = rate; currentRate = rate; isCustomRate = true; GM_setValue('bilibili_playback_rate', currentRate); GM_setValue('bilibili_custom_rate', true); showRateMessage(`倍速已设为 ${rate}x (自定义)`); console.log(`已设置自定义倍速为 ${rate}x`); } else { console.log('未找到视频元素'); showRateMessage('无法设置自定义倍速'); } } // 倍速控制相关函数 function applySavedPlaybackRate() { // 如果是自定义倍速(3x),直接设置视频元素 if (isCustomRate && currentRate > 2) { const videoElement = document.querySelector('.bpx-player-video-wrap video'); if (videoElement) { videoElement.playbackRate = currentRate; console.log(`已恢复自定义倍速为 ${currentRate}x`); showRateMessage(`倍速已恢复为 ${currentRate}x (自定义)`); } else { console.log('未找到视频元素,稍后重试'); if (attemptCount < MAX_ATTEMPTS) { timeoutId = setTimeout(applySavedPlaybackRate, RETRY_DELAY); attemptCount++; } } return; } // 否则使用官方倍速控制 const rateMenu = document.querySelector('.bpx-player-ctrl-playbackrate-menu'); if (!rateMenu) { if (attemptCount < MAX_ATTEMPTS) { timeoutId = setTimeout(applySavedPlaybackRate, RETRY_DELAY); attemptCount++; return; } console.log('未找到倍速菜单'); return; } const currentActive = rateMenu.querySelector('.bpx-state-active'); if (currentActive) { const activeRate = parseFloat(currentActive.dataset.value); if (Math.abs(activeRate - currentRate) > 0.01) { console.log(`当前倍速(${activeRate}x)与保存倍速(${currentRate}x)不一致,正在调整`); setPlaybackRate(currentRate); } else { console.log(`当前倍速(${activeRate}x)与保存倍速一致,无需调整`); } } else { console.log('未找到激活的倍速选项,尝试设置'); setPlaybackRate(currentRate); } } function setPlaybackRate(rate) { // 确保rate是支持的倍速 const supportedRate = playbackRates.find(r => Math.abs(r - rate) < 0.01) || 1; currentRate = supportedRate; isCustomRate = false; GM_setValue('bilibili_playback_rate', currentRate); GM_setValue('bilibili_custom_rate', false); const rateMenu = document.querySelector('.bpx-player-ctrl-playbackrate-menu'); if (rateMenu) { const items = rateMenu.querySelectorAll('.bpx-player-ctrl-playbackrate-menu-item'); let found = false; items.forEach(item => { const itemRate = parseFloat(item.dataset.value); if (Math.abs(itemRate - currentRate) < 0.01) { found = true; if (!item.classList.contains('bpx-state-active')) { item.click(); console.log(`已设置倍速为 ${currentRate}x`); showRateMessage(`倍速已设为 ${currentRate}x`); } } }); if (!found) { console.log(`未找到 ${currentRate}x 的倍速选项`); } } else { console.log('未找到倍速菜单'); if (attemptCount < MAX_ATTEMPTS) { timeoutId = setTimeout(() => setPlaybackRate(rate), RETRY_DELAY); attemptCount++; } } } function increasePlaybackRate() { const currentIndex = playbackRates.findIndex(r => Math.abs(r - currentRate) < 0.01); if (currentIndex > 0) { const newRate = playbackRates[currentIndex - 1]; setPlaybackRate(newRate); } else { showRateMessage('已经是最大倍速 (2x)'); console.log('已经是最大倍速 (2x)'); } } function decreasePlaybackRate() { const currentIndex = playbackRates.findIndex(r => Math.abs(r - currentRate) < 0.01); if (currentIndex < playbackRates.length - 1) { const newRate = playbackRates[currentIndex + 1]; setPlaybackRate(newRate); } else { showRateMessage('已经是最小倍速 (0.5x)'); console.log('已经是最小倍速 (0.5x)'); } } // 新增:应用保存的顶部栏状态(不切换) function applyHeaderState() { const header = document.getElementById('biliMainHeader'); if (header) { if (isHeaderHidden) { document.body.classList.add('header-hidden'); } else { document.body.classList.remove('header-hidden'); } console.log(`已应用保存的顶部栏状态: ${isHeaderHidden ? '隐藏' : '显示'}`); } else { console.log('未找到顶部栏元素'); if (attemptCount < MAX_ATTEMPTS) { timeoutId = setTimeout(applyHeaderState, RETRY_DELAY); attemptCount++; } } } // 修改后的切换函数 function toggleHeader(showMessage = true) { isHeaderHidden = !isHeaderHidden; GM_setValue('bilibili_header_hidden', isHeaderHidden); const header = document.getElementById('biliMainHeader'); if (header) { if (isHeaderHidden) { document.body.classList.add('header-hidden'); } else { document.body.classList.remove('header-hidden'); } if (showMessage) { showRateMessage(`顶部栏已${isHeaderHidden ? '隐藏' : '显示'}`); } console.log(`顶部栏状态已切换: ${isHeaderHidden ? '隐藏' : '显示'}`); } else { console.log('未找到顶部栏元素'); if (attemptCount < MAX_ATTEMPTS) { timeoutId = setTimeout(() => toggleHeader(showMessage), RETRY_DELAY); attemptCount++; } } } // 启动脚本 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();