// ==UserScript== // @name 视频左上显示剩余时长 // @author He // @version 1.1 // @description 在视频播放时显示剩余时间和进度条, 包括已加载但未播放的部分 // @match *://*/* // @exclude *://*live*/* // @exclude *://www.huya.com/* // @exclude *://www.douyu.com/* // @namespace https://greasyfork.org/users/808960 // @downloadURL none // ==/UserScript== (function() { 'use strict'; function setupVideoTimeDisplay(video) { const container = document.createElement('div'); container.style.cssText = ` position: absolute; left: 10px; top: 10px; z-index: 1000; `; const timeDisplay = document.createElement('div'); timeDisplay.id = 'timeDisplay'; timeDisplay.style.cssText = ` color: #C8DCC8; background: rgba(0, 0, 0, 0.5); padding: 1px; font-family: 'DS-Digital', sans-serif; font-size: 22px; text-align: center; border-radius: 5px; /* Add this line for rounded corners */ `; const progressBar = document.createElement('div'); progressBar.id = 'progressBar'; progressBar.style.cssText = ` width: 100px; height: 2px; background: rgba(255, 255, 255, 0.3); /* Semi-transparent white background */ margin-top: 0px; overflow: hidden; position: relative; `; const bufferedBar = document.createElement('div'); bufferedBar.id = 'bufferedBar'; bufferedBar.style.cssText = ` width: 0%; height: 100%; background: #FF6A00; position: absolute; top: 0; left: 0; `; const progressBarInner = document.createElement('div'); progressBarInner.id = 'progressBarInner'; progressBarInner.style.cssText = ` width: 0%; height: 100%; background: skyblue; position: absolute; top: 0; left: 0; `; progressBar.appendChild(bufferedBar); progressBar.appendChild(progressBarInner); container.appendChild(timeDisplay); container.appendChild(progressBar); // If video has a parent with relative positioning, append to that for better overlay let parent = video.parentElement; while (parent && getComputedStyle(parent).position !== 'relative') { parent = parent.parentElement; } if (parent) { parent.appendChild(container); } else { document.body.appendChild(container); } function updateTimeAndProgress() { const remainingTime = video.duration - video.currentTime; if (isFinite(remainingTime)) { const minutes = Math.floor(remainingTime / 60); const seconds = Math.floor(remainingTime % 60); timeDisplay.textContent = `${minutes}:${seconds < 10 ? '0' + seconds : seconds}`; // Update played progress bar const progress = (video.currentTime / video.duration) * 100; progressBarInner.style.width = `${progress}%`; // Update buffered progress bar let buffered = 0; for (let i = 0; i < video.buffered.length; i++) { if (video.buffered.start(video.buffered.length - 1 - i) < video.currentTime && video.buffered.end(video.buffered.length - 1 - i) > video.currentTime) { buffered = video.buffered.end(video.buffered.length - 1 - i) / video.duration * 100; break; } } bufferedBar.style.width = `${buffered}%`; } } video.addEventListener('timeupdate', updateTimeAndProgress); video.addEventListener('loadedmetadata', updateTimeAndProgress); video.addEventListener('progress', updateTimeAndProgress); // For buffer updates } // Observes DOM changes for video elements const observer = new MutationObserver((mutationsList) => { for(let mutation of mutationsList) { if (mutation.type === 'childList') { mutation.addedNodes.forEach(node => { if (node.nodeType === Node.ELEMENT_NODE && node.tagName.toLowerCase() === 'video') { setupVideoTimeDisplay(node); } }); } } }); observer.observe(document.body, { childList: true, subtree: true }); // Check for existing videos on load document.querySelectorAll('video').forEach(setupVideoTimeDisplay); })();