计算B站视频列表中到指定集数的剩余时长
当前为
// ==UserScript==
// @name B站视频时长计算器
// @namespace http://tampermonkey.net/
// @version 0.6
// @description 计算B站视频列表中到指定集数的剩余时长
// @author Your name
// @match https://www.bilibili.com/video/*
// @license MIT
// @grant none
// ==/UserScript==
(function() {
'use strict';
// 将时长从MM:SS转换为秒
function parseDuration(duration) {
const [minutes, seconds] = duration.split(':').map(Number);
return minutes * 60 + seconds;
}
// 将总秒数转换为HH:MM:SS格式
function formatDuration(totalSeconds) {
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = totalSeconds % 60;
return `${hours}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
}
// 获取当前播放的集数
function getCurrentEpisode() {
const urlParams = new URLSearchParams(window.location.search);
const p = urlParams.get('p');
return p ? parseInt(p) : 1;
}
// 主要计算逻辑
function calculateTotalDuration(currentPage) {
const videoItems = document.querySelectorAll('.duration');
let totalSeconds = 0;
let totalEpisodes = videoItems.length;
videoItems.forEach((item, index) => {
const pageNum = index + 1;
if (pageNum >= currentPage) {
const duration = item.textContent.trim();
totalSeconds += parseDuration(duration);
}
});
return {
duration: formatDuration(totalSeconds),
totalEpisodes: totalEpisodes
};
}
// 创建控件
function createControls() {
const container = document.createElement('div');
container.style.cssText = `
display: inline-flex;
gap: 10px;
align-items: center;
margin-left: 20px;
vertical-align: middle;
`;
// 创建计算按钮(左)
const button = document.createElement('button');
button.textContent = '计算剩余时长';
button.style.cssText = `
padding: 5px 10px;
background: #00A1D6;
color: white;
border: none;
border-radius: 3px;
cursor: pointer;
font-size: 14px;
order: 1;
`;
// 创建输入框(中)
const input = document.createElement('input');
input.type = 'number';
input.placeholder = '目标集数';
input.style.cssText = `
width: 80px;
padding: 5px;
border: 1px solid #ccc;
border-radius: 3px;
font-size: 14px;
text-align: center;
order: 2;
`;
// 创建结果显示框(右)
const resultBox = document.createElement('div');
resultBox.style.cssText = `
display: none;
padding: 5px 10px;
background: rgba(0, 0, 0, 0.7);
color: white;
border-radius: 3px;
font-size: 14px;
line-height: 1.5;
white-space: nowrap;
vertical-align: middle;
order: 3;
`;
container.appendChild(button);
container.appendChild(input);
container.appendChild(resultBox);
const titleContainer = document.querySelector('.video-info-title-inner');
if (titleContainer) {
titleContainer.appendChild(container);
}
return { button, input, resultBox };
}
// 更新显示
function updateDisplay(resultBox, input) {
const currentPage = parseInt(input.value) || getCurrentEpisode();
const result = calculateTotalDuration(currentPage);
resultBox.textContent = `从第${currentPage}集到最后还有: ${result.duration}`;
resultBox.style.display = 'block';
}
// 初始化
function init() {
// 等待页面加载完成
setTimeout(() => {
const { button, input, resultBox } = createControls();
// 设置初始值并显示
input.value = getCurrentEpisode();
updateDisplay(resultBox, input);
// 点击按钮更新
button.addEventListener('click', () => {
updateDisplay(resultBox, input);
});
// 回车更新
input.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
button.click();
}
});
// URL变化监听
let lastUrl = location.href;
new MutationObserver(() => {
const url = location.href;
if (url !== lastUrl) {
lastUrl = url;
setTimeout(() => {
input.value = getCurrentEpisode();
updateDisplay(resultBox, input);
}, 1000);
}
}).observe(document, { subtree: true, childList: true });
// 视频切换监听
const videoList = document.querySelector('.video-episode-card');
if (videoList) {
new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.target.classList.contains('video-episode-card__info-playing')) {
input.value = getCurrentEpisode();
updateDisplay(resultBox, input);
}
});
}).observe(videoList.parentElement, {
subtree: true,
attributes: true,
attributeFilter: ['class']
});
}
}, 2000);
}
// 启动
window.addEventListener('load', init);
})();