您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
统计bilibili合集总时长与观看百分比,支持倍速计算
// ==UserScript== // @name bilibili统计观看时长 // @version 0.0.2 // @description 统计bilibili合集总时长与观看百分比,支持倍速计算 // @author stone // @match https://www.bilibili.com/video/* // @icon https://www.bilibili.com/favicon.ico // @grant none // @namespace [email protected] // @license MIT // ==/UserScript== (function () { "use strict"; setTimeout(function () { // 添加倍速输入框 function addSpeedInput() { const header = document.querySelector(".video-pod .header-top"); if (!header) return; const speedInput = document.createElement("input"); speedInput.type = "number"; speedInput.value = "1.0"; speedInput.min = "0.1"; speedInput.max = "16"; speedInput.style.cssText = ` width: 20px; height: 20px; margin-left: 5px; font-size: 12px; border: 1px solid #e3e5e7; border-radius: 2px; padding: 0 4px; color: #18191c; background: #f1f2f3; outline: none; /* 隐藏步进控制器 */ -webkit-appearance: none; -moz-appearance: textfield; `; speedInput.id = "speed-input"; // 隐藏步进控制器的额外样式 const style = document.createElement("style"); style.textContent = ` input[type="number"]::-webkit-inner-spin-button, input[type="number"]::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; } input[type="number"] { -moz-appearance: textfield; } `; document.head.appendChild(style); const speedLabel = document.createElement("span"); speedLabel.textContent = "倍速:"; speedLabel.style.cssText = ` font-size: 12px; color: #61666d; margin-left: 10px; `; header.appendChild(speedLabel); header.appendChild(speedInput); // 添加失去焦点事件 speedInput.addEventListener("blur", () => count()); // 监听视频播放速度变化 const video = document.querySelector("video"); if (video) { video.addEventListener("ratechange", function () { speedInput.value = video.playbackRate; count(); }); // 初始化时同步视频速度 speedInput.value = video.playbackRate; } } let lis = document.querySelectorAll(".video-pod__list .video-pod__item"); let timer = null; if (lis.length > 0) { addSpeedInput(); count(); } function count() { clearTimeout(timer); timer = setTimeout(function () { let totalSec = 0; let watchedSec = 0; let flag = true; // 获取当前倍速 const speedInput = document.getElementById("speed-input"); const speed = speedInput ? parseFloat(speedInput.value) : 1.0; for (let i = 0; i < lis.length; i++) { let TimeStr = lis[i].querySelector(".stat-item.duration").innerHTML; const TimeArr = TimeStr.split(":"); // 计算总秒数 if (TimeArr.length === 3) { totalSec += Number(TimeArr[0]) * 3600; totalSec += Number(TimeArr[1]) * 60; totalSec += Number(TimeArr[2]); } else if (TimeArr.length === 2) { totalSec += Number(TimeArr[0]) * 60; totalSec += Number(TimeArr[1]); } else if (TimeArr.length === 1) { totalSec += Number(TimeArr[0]); } if (flag) { watchedSec = totalSec; } if ( lis[i].className.indexOf("active") != -1 || lis[i].querySelector(".active") != null ) { flag = false; } } // 考虑倍速计算实际时长 const actualTotalHours = (totalSec / 3600).toFixed(1); const actualWatchedHours = (watchedSec / 3600).toFixed(1); const adjustedTotalHours = (totalSec / (3600 * speed)).toFixed(1); const adjustedWatchedHours = (watchedSec / (3600 * speed)).toFixed(1); const rate = ((watchedSec / totalSec) * 100).toFixed(2); // 获取当前视频序号和总集数 const currentNum = document .querySelector(".video-pod__header .amt") .innerText.match(/\d+/g); const current = currentNum ? currentNum[0] : ""; const total = currentNum ? currentNum[1] : ""; // 隐藏原有的集数显示 const amtElement = document.querySelector(".video-pod__header .amt"); if (amtElement) { amtElement.style.display = "none"; } // 更新显示,加入集数信息 const title = document.querySelector(".video-pod .header-top .title"); title.style.cssText = ` font-size: 13px; color: #18191c; display: inline-block; margin-right: 10px; `; title.innerHTML = `(${current}/${total}) ${adjustedWatchedHours}/${adjustedTotalHours}h (${rate}%)`; // 更新进度条 updateProgressBar(rate); }, 500); } function updateProgressBar(rate) { const bar = document.querySelector(".header-top"); let oldProgress = bar.querySelector(".progressBar"); if (oldProgress) { bar.removeChild(oldProgress); } const progress = document.createElement("div"); progress.className = "progressBar"; progress.style.cssText = ` background-color: #00aeec; width: ${bar.offsetWidth * (rate / 100)}px; height: 4px; position: absolute; bottom: -4px; left: 0; z-index: 999; border-radius: 2px; `; // 添加背景条 const bgBar = document.createElement("div"); bgBar.style.cssText = ` background-color: #e3e5e7; width: 100%; height: 4px; position: absolute; bottom: -4px; left: 0; border-radius: 2px; `; bar.style.position = "relative"; bar.appendChild(bgBar); bar.appendChild(progress); } // 监听视频切换 const targetNode = document.querySelector(".video-pod__list"); if (targetNode) { const observer = new MutationObserver(() => { debounce(count, 200)(); }); observer.observe(targetNode, { childList: true, attributes: true, subtree: true, }); } }, 2500); })(); function debounce(fn, delay = 500) { let timer = null; return function (...args) { clearTimeout(timer); timer = setTimeout(() => { fn.apply(null, args); timer = null; }, delay); }; }