// ==UserScript== // @name 视频时间计算器 // @namespace http://tampermonkey.net/ // @version 1.7 // @description 在所有视频右上角显示加速后的时间信息 // @author You // @match *://*/* // @grant none // ==/UserScript== (function() { 'use strict'; // 配置选项 const CONFIG = { fontSize: '10px', // 减小字体大小 fontFamily: 'Arial, sans-serif', backgroundColor: 'rgba(0, 0, 0, 0.7)', textColor: 'white', borderRadius: '3px', // 减小圆角 padding: '3px 6px', // 减小内边距 zIndex: 9999, position: 'absolute', top: '8px', // 调整位置 right: '40px', // 向左移动(原来是8px,现在是40px) left: 'auto' // 确保left为auto }; // 创建时间显示元素 function createTimeDisplay() { const timeDisplay = document.createElement('div'); timeDisplay.id = 'video-time-display'; timeDisplay.style.cssText = ` font-size: ${CONFIG.fontSize}; font-family: ${CONFIG.fontFamily}; background-color: ${CONFIG.backgroundColor}; color: ${CONFIG.textColor}; border-radius: ${CONFIG.borderRadius}; padding: ${CONFIG.padding}; z-index: ${CONFIG.zIndex}; position: ${CONFIG.position}; top: ${CONFIG.top}; right: ${CONFIG.right}; left: ${CONFIG.left}; pointer-events: none; font-family: Roboto, Arial, sans-serif; display: flex; gap: 5px; // 减小间距 align-items: center; `; return timeDisplay; } // 格式化时间为 MM:SS 格式 function formatTime(seconds) { // 检查是否为无效时间 if (isNaN(seconds) || seconds === Infinity || seconds === -Infinity) { return '00:00'; } const mins = Math.floor(Math.abs(seconds) / 60); const secs = Math.floor(Math.abs(seconds) % 60); const sign = seconds < 0 ? '-' : ''; return `${sign}${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; } // 检查是否为直播视频 function isLiveVideo(video) { // 检查视频是否为直播 // 直播视频通常duration为Infinity if (video.duration === Infinity) { return true; } // 检查是否有其他直播标识 if (video.duration && video.duration > 1000000) { // 很大的duration值通常是直播 return true; } return false; } // 获取页面中的视频元素(特别针对YouTube优化) function getVideoElement() { // YouTube特定选择器 const youtubeSelectors = [ '#movie_player video', '.html5-video-container video', '.ytp-large-play-button video', 'video' ]; for (const selector of youtubeSelectors) { const videos = document.querySelectorAll(selector); for (const video of videos) { if (video && video.readyState > 0 && (video.clientHeight > 0 || video.clientWidth > 0)) { return video; } } } // 通用选择器 const videos = Array.from(document.querySelectorAll('video')); // 优先选择正在播放且可见的视频 for (let video of videos) { if (video.offsetParent !== null && video.readyState > 0 && video.clientHeight > 0 && video.clientWidth > 0 && !video.paused) { return video; } } // 如果没有正在播放的视频,选择第一个可见的视频 for (let video of videos) { if (video.offsetParent !== null && video.readyState > 0 && video.clientHeight > 0 && video.clientWidth > 0) { return video; } } // 如果没有可见的视频,选择第一个加载的视频 for (let video of videos) { if (video.readyState > 0) { return video; } } return videos[0] || null; } // 获取视频容器(特别针对YouTube优化) function getVideoContainer(video) { // 针对YouTube的特殊处理 if (window.location.hostname.includes('youtube.com')) { const youtubePlayer = video.closest('#movie_player'); if (youtubePlayer) { if (getComputedStyle(youtubePlayer).position === 'static') { youtubePlayer.style.position = 'relative'; } return youtubePlayer; } const html5VideoContainer = video.closest('.html5-video-container'); if (html5VideoContainer) { if (getComputedStyle(html5VideoContainer).position === 'static') { html5VideoContainer.style.position = 'relative'; } return html5VideoContainer; } } // 通用处理 const parent = video.parentElement; if (parent && getComputedStyle(parent).position === 'static') { parent.style.position = 'relative'; } return parent || document.body; } // 更新时间显示 function updateTimeDisplay() { const video = getVideoElement(); if (!video || video.readyState === 0) { // 如果没有找到视频,移除时间显示元素 const timeDisplay = document.getElementById('video-time-display'); if (timeDisplay) { timeDisplay.remove(); } return; } // 检查是否为直播视频,如果是则不显示任何内容 if (isLiveVideo(video)) { const timeDisplay = document.getElementById('video-time-display'); if (timeDisplay) { timeDisplay.remove(); } return; } let timeDisplay = document.getElementById('video-time-display'); // 检查时间显示元素是否在正确的容器中 const videoContainer = getVideoContainer(video); const currentContainer = timeDisplay ? timeDisplay.parentElement : null; if (!timeDisplay || currentContainer !== videoContainer) { if (timeDisplay) { timeDisplay.remove(); } timeDisplay = createTimeDisplay(); videoContainer.appendChild(timeDisplay); } // 获取播放速度 const playbackRate = video.playbackRate; // 计算加速后的时间 const originalDuration = video.duration; const originalCurrentTime = video.currentTime; // 检查数值是否有效 if (isNaN(originalDuration) || isNaN(originalCurrentTime) || originalDuration <= 0 || playbackRate <= 0) { timeDisplay.innerHTML = ` 加载中... `; return; } // 加速后的时间计算 const adjustedDuration = originalDuration / playbackRate; const adjustedCurrentTime = originalCurrentTime / playbackRate; const adjustedRemainingTime = adjustedDuration - adjustedCurrentTime; // 格式化时间 const formattedTotalTime = formatTime(adjustedDuration); const formattedRemainingTime = formatTime(adjustedRemainingTime); const formattedCurrentTime = formatTime(adjustedCurrentTime); // 更新显示内容(剩余时间在前) timeDisplay.innerHTML = ` 剩: ${formattedRemainingTime} 总: ${formattedTotalTime} `; } // 监听播放速度变化 function watchPlaybackRate(video) { video.addEventListener('ratechange', updateTimeDisplay); // 使用属性观察器监听播放速度变化 const observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { if (mutation.type === 'attributes' && (mutation.attributeName === 'playbackrate' || mutation.attributeName === 'defaultplaybackrate')) { updateTimeDisplay(); } }); }); observer.observe(video, { attributes: true }); } // 初始化函数 function initialize() { const video = getVideoElement(); if (!video) return; // 监听播放速度变化 watchPlaybackRate(video); // 每200ms更新一次时间显示(减少性能影响) setInterval(updateTimeDisplay, 200); // 初始更新 updateTimeDisplay(); } // 页面加载完成后执行 function initWhenReady() { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initialize); } else { // 对于YouTube,可能需要等待视频加载 const video = getVideoElement(); if (video && video.readyState > 0) { initialize(); } else { // 如果视频还没加载,稍后重试 setTimeout(initWhenReady, 1000); } } } // 监听动态添加的视频元素 const observer = new MutationObserver(function(mutations) { let shouldUpdate = false; for (const mutation of mutations) { if (mutation.type === 'childList') { for (const node of mutation.addedNodes) { if (node.nodeType === 1) { // Element node if (node.tagName === 'VIDEO' || (node.querySelector && node.querySelector('video'))) { shouldUpdate = true; break; } } } } } if (shouldUpdate) { setTimeout(initialize, 500); // 延迟执行以确保视频已加载 } }); observer.observe(document.body, { childList: true, subtree: true }); // 启动初始化 initWhenReady(); })();