// ==UserScript==
// @name 视频控制增强
// @namespace http://tampermonkey.net/
// @version 2.1
// @description 视频播放增强:1. 长按左键临时加速 2. B站自动开启AI字幕 3. 支持更多视频播放器
// @author Alonewinds
// @match *://*/*
// @exclude *://*/iframe/*
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @run-at document-start
// @license MIT
// @downloadURL none
// ==/UserScript==
(function() {
'use strict';
// 在脚本最开始添加一个检查
// 对于 B 站,只在主页面和播放器 iframe 中执行
// 对于其他网站,允许在所有环境中执行
if (window.location.hostname.includes('bilibili.com') &&
window.self !== window.top &&
window.location.hostname !== 'player.bilibili.com') {
return;
}
// 默认配置
const config = {
speedRate: GM_getValue('speedRate', 2.0),
minPressTime: 200,
subtitleEnabled: GM_getValue('subtitleEnabled', false),
// 保持通用选择器,添加B站和YouTube选择器
selectors: {
'www.bilibili.com': '.bpx-player-video-area',
'www.youtube.com': '.html5-video-player', // 添加 YouTube 选择器
'default': '.video-controls, .progress-bar, [role="slider"]'
}
};
// 状态变量
let pressStartTime = 0;
let originalSpeed = 1.0;
let isPressed = false;
let activeVideo = null;
let isLongPress = false;
let preventNextClick = false;
// B站字幕相关变量
let subtitleEnabled = false;
let subtitleAttempts = 0;
let subtitleRetryTimer = null;
let manuallyDisabled = false;
let currentVideoId = '';
let clickHandler = null;
// 在全局作用域注册菜单(只注册一次)
GM_registerMenuCommand('设置倍速值', () => {
// 检查是否在主页面或B站播放器
if (window.self !== window.top && window.location.hostname !== 'player.bilibili.com') return;
const newSpeed = prompt('请输入新的倍速值(建议范围:1.1-4):', config.speedRate);
if (newSpeed && !isNaN(newSpeed)) {
config.speedRate = parseFloat(newSpeed);
GM_setValue('speedRate', config.speedRate);
const indicator = document.querySelector('.speed-indicator');
if (indicator) {
indicator.innerHTML = `当前加速 ${config.speedRate}x ▶▶`;
}
}
});
// 仅在B站页面显示字幕菜单(只注册一次)
if (window.location.hostname === 'www.bilibili.com') {
GM_registerMenuCommand('B站字幕: ' + (config.subtitleEnabled ? '已开启' : '已关闭'), () => {
// 检查是否在主页面或B站播放器
if (window.self !== window.top && window.location.hostname !== 'player.bilibili.com') return;
config.subtitleEnabled = !config.subtitleEnabled;
GM_setValue('subtitleEnabled', config.subtitleEnabled);
alert('B站字幕' + (config.subtitleEnabled ? '开启,刷新页面生效' : '关闭,刷新页面生效'));
});
}
// ================ 倍速控制功能 ================
function findVideoElement(element) {
if (!element) return null;
if (element instanceof HTMLVideoElement) {
return element;
}
const domain = window.location.hostname;
// B站和YouTube使用区域限制
if (domain === 'www.bilibili.com') {
const playerArea = document.querySelector('.bpx-player-video-area');
if (!playerArea?.contains(element)) return null;
} else if (domain === 'www.youtube.com') {
const ytPlayer = element.closest('.html5-video-player');
if (!ytPlayer?.contains(element)) return null;
const video = ytPlayer.querySelector('video');
if (video) return video;
}
const controlSelector = config.selectors.default;
if (element.closest(controlSelector)) {
return null;
}
const container = element.closest('*:has(video)');
const video = container?.querySelector('video');
return video && window.getComputedStyle(video).display !== 'none' ? video : null;
}
function setYouTubeSpeed(video, speed) {
if (window.location.hostname === 'www.youtube.com') {
const player = video.closest('.html5-video-player');
if (player) {
try {
// 清理之前的监控器
if (player._speedInterval) {
clearInterval(player._speedInterval);
player._speedInterval = null;
}
// 设置速度
video.playbackRate = speed;
if (speed !== 1.0) { // 只在加速时监控
// 增加检查间隔到 100ms
player._speedInterval = setInterval(() => {
if (video.playbackRate !== speed) {
video.playbackRate = speed;
}
}, 100); // 从 50ms 改为 100ms
// 添加超时清理
setTimeout(() => {
if (player._speedInterval) {
clearInterval(player._speedInterval);
player._speedInterval = null;
}
}, 5000); // 5秒后自动清理
}
video.dispatchEvent(new Event('ratechange'));
} catch (e) {
console.error('设置 YouTube 播放速度失败:', e);
}
}
} else {
video.playbackRate = speed;
}
}
// ================ B站字幕功能 ================
function getVideoId() {
const match = location.pathname.match(/\/video\/(.*?)\//);
return match ? match[1] : '';
}
function enableSubtitle() {
if (!config.subtitleEnabled) return;
if (window.location.hostname !== 'www.bilibili.com') return;
if (subtitleAttempts >= 5 || manuallyDisabled) return;
subtitleAttempts++;
const button = document.querySelector("div.bpx-player-ctrl-btn.bpx-player-ctrl-subtitle > div.bpx-player-ctrl-btn-icon > span");
if (!button) {
subtitleRetryTimer = setTimeout(enableSubtitle, 1000);
return;
}
const parent = button.closest('.bpx-player-ctrl-subtitle');
if (!parent) return;
if (clickHandler) {
parent.removeEventListener('click', clickHandler);
}
clickHandler = (event) => {
requestAnimationFrame(() => {
if (!parent.classList.contains('bpx-player-ctrl-subtitle-on')) {
manuallyDisabled = true;
console.log('用户已手动关闭字幕,本视频将不再自动开启');
}
});
};
parent.addEventListener('click', clickHandler);
if (!parent.classList.contains('bpx-player-ctrl-subtitle-on') && !manuallyDisabled) {
button.click();
subtitleEnabled = true;
}
}
// ================ 事件处理 ================
function handleMouseDown(e) {
if (e.button !== 0) return;
const domain = window.location.hostname;
let video = null;
let playerArea = null;
// B站和YouTube使用严格区域限制
if (domain === 'www.bilibili.com' || domain === 'www.youtube.com') {
const selector = config.selectors[domain];
playerArea = document.querySelector(selector);
if (!playerArea?.contains(e.target)) return;
video = findVideoElement(e.target);
} else {
// 其他网站直接查找视频,不做区域限制
video = findVideoElement(e.target);
if (video) {
playerArea = video.closest('*:has(video)') || video.parentElement;
}
}
if (!video) return;
if (video.paused) {
hideSpeedIndicator();
return;
}
pressStartTime = Date.now();
activeVideo = video;
originalSpeed = video.playbackRate;
isPressed = true;
isLongPress = false;
preventNextClick = false;
const currentSpeedRate = GM_getValue('speedRate', config.speedRate);
let indicator = document.querySelector('.speed-indicator');
if (!indicator) {
indicator = document.createElement('div');
indicator.className = 'speed-indicator';
playerArea.appendChild(indicator);
}
// 使用统一的加速提示
indicator.innerHTML = `当前加速 ${currentSpeedRate}x ▶▶`;
indicator.style.display = 'block';
}
function handleMouseUp(e) {
if (!isPressed || !activeVideo) return;
const pressDuration = Date.now() - pressStartTime;
if (pressDuration >= config.minPressTime) {
preventNextClick = true;
setYouTubeSpeed(activeVideo, originalSpeed);
hideSpeedIndicator();
}
isPressed = false;
isLongPress = false;
activeVideo = null;
}
function handlePressDetection() {
if (!isPressed || !activeVideo) return;
const pressDuration = Date.now() - pressStartTime;
if (pressDuration >= config.minPressTime) {
if (activeVideo.playbackRate !== config.speedRate) {
setYouTubeSpeed(activeVideo, config.speedRate);
}
isLongPress = true;
}
}
function handleClick(e) {
if (preventNextClick) {
e.stopPropagation();
preventNextClick = false;
return;
}
}
function onVideoLoad() {
if (window.location.hostname !== 'www.bilibili.com') return;
const video = document.querySelector('video');
if (!video) {
setTimeout(onVideoLoad, 500);
return;
}
function reset() {
const newVideoId = getVideoId();
if (newVideoId !== currentVideoId) {
manuallyDisabled = false;
currentVideoId = newVideoId;
if (clickHandler) {
const oldButton = document.querySelector('.bpx-player-ctrl-subtitle');
if (oldButton) {
oldButton.removeEventListener('click', clickHandler);
}
clickHandler = null;
}
}
subtitleEnabled = false;
subtitleAttempts = 0;
if (subtitleRetryTimer) {
clearTimeout(subtitleRetryTimer);
subtitleRetryTimer = null;
}
if (!manuallyDisabled) {
setTimeout(enableSubtitle, 500);
}
}
video.addEventListener('loadeddata', reset);
video.addEventListener('play', () => {
if (!subtitleEnabled && !manuallyDisabled) {
reset();
}
});
reset();
}
// ================ 初始化 ================
function initializeEvents() {
addSpeedIndicatorStyle();
document.addEventListener('mousedown', handleMouseDown, true);
document.addEventListener('mouseup', handleMouseUp, true);
document.addEventListener('click', handleClick, true);
document.addEventListener('mouseleave', handleMouseUp, true);
document.addEventListener('pause', (e) => {
if (e.target instanceof HTMLVideoElement) {
hideSpeedIndicator();
}
}, true);
function checkPress() {
handlePressDetection();
requestAnimationFrame(checkPress);
}
checkPress();
if (window.location.hostname === 'www.bilibili.com') {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', onVideoLoad);
} else {
onVideoLoad();
}
}
}
function addSpeedIndicatorStyle() {
const style = document.createElement('style');
style.textContent = `
.speed-indicator {
position: absolute;
top: 15%;
left: 50%;
transform: translateX(-50%);
background: rgba(0, 0, 0, 0.7);
color: white;
padding: 5px 10px;
border-radius: 4px;
z-index: 999999;
display: none;
font-size: 14px;
}
.speed-arrow {
color: #00a1d6;
margin-left: 2px;
}`;
document.head.appendChild(style);
}
function hideSpeedIndicator() {
const indicator = document.querySelector('.speed-indicator');
if (indicator) {
indicator.style.display = 'none';
}
}
initializeEvents();
})();