B站视频进度条

这个脚本可以显示哔哩哔哩合集视频的进度条

< Feedback on B站视频进度条

Review: OK - script works, but has bugs

§
Posted: 2025-03-15

现在这个脚本失效了剩个圈,我把代码拿去找AI帮忙改了能用了,我看看评论这里能不能放出来
懒得新建一个脚本了仓库了,代码可能也有些问题我不太懂,就放评论里了,有需要的复制去用吧。
// ==UserScript==
// @name B站视频进度条
// @namespace http://tampermonkey.net/
// @version 6.0.5
// @description 这个脚本可以显示哔哩哔哩合集视频的进度条
// @author FocusOn1
// @match https://www.bilibili.com/video/*
// @match https://www.bilibili.com/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=greasyfork.org
// @grant none
// @license MIT
// ==/UserScript==

(function() {
'use strict';

// 默认颜色配置
let config = {
backgroundColor: '#f6f8fa',
fontColor: '#24292e',
progressBarColor: 'yellow',
progressBarBackgroundColor: '#ddd',
progressFontColor: '#24292e',
opacity: 1
};

// 创建透明度选择条
const opacitySlider = document.createElement('div');
opacitySlider.id = 'opacity-slider';
opacitySlider.style.display = 'none';
opacitySlider.style.position = 'fixed';
opacitySlider.style.backgroundColor = '#fff';
opacitySlider.style.padding = '10px';
opacitySlider.style.border = '1px solid #ccc';
opacitySlider.style.zIndex = '10000000000';
opacitySlider.innerHTML = `
透明度:


保存
重置

`;
document.body.appendChild(opacitySlider);

document.getElementById('save-opacity').addEventListener('click', () => {
config.opacity = document.getElementById('opacity').value;
opacitySlider.style.display = 'none';
applyColors();
});

document.getElementById('reset-opacity').addEventListener('click', () => {
config.opacity = 1;
document.getElementById('opacity').value = config.opacity;
opacitySlider.style.display = 'none';
applyColors();
});

// 创建颜色选择弹窗
const colorPicker = document.createElement('div');
colorPicker.id = 'color-picker';
colorPicker.style.display = 'none';
colorPicker.style.position = 'fixed';
colorPicker.style.top = '50%';
colorPicker.style.left = '50%';
colorPicker.style.transform = 'translate(-50%, -50%)';
colorPicker.style.backgroundColor = '#fff';
colorPicker.style.padding = '20px';
colorPicker.style.border = '1px solid #ccc';
colorPicker.style.zIndex = '10000000000';
colorPicker.innerHTML = `
背景颜色:


进度条颜色:


进度条背景颜色:


进度字体颜色:


全部数据字体颜色:



保存
重置

`;
document.body.appendChild(colorPicker);

document.getElementById('save-colors').addEventListener('click', () => {
config.backgroundColor = document.getElementById('background-color').value;
config.fontColor = document.getElementById('font-color').value;
config.progressBarColor = document.getElementById('progress-bar-color').value;
config.progressBarBackgroundColor = document.getElementById('progress-bar-background-color').value;
config.progressFontColor = document.getElementById('progress-font-color').value;
colorPicker.style.display = 'none';
applyColors();
});

document.getElementById('reset-colors').addEventListener('click', () => {
config = {
backgroundColor: '#f6f8fa',
fontColor: '#24292e',
progressBarColor: 'yellow',
progressBarBackgroundColor: '#ddd',
progressFontColor: '#24292e'
};
document.getElementById('background-color').value = config.backgroundColor;
document.getElementById('font-color').value = config.fontColor;
document.getElementById('progress-bar-color').value = config.progressBarColor;
document.getElementById('progress-bar-background-color').value = config.progressBarBackgroundColor;
document.getElementById('progress-font-color').value = config.progressFontColor;
colorPicker.style.display = 'none';
applyColors();
});

function applyColors() {
timeDisplay.style.backgroundColor = config.backgroundColor;
timeDisplay.style.color = config.fontColor;
timeDisplay.style.opacity = config.opacity;
updateProgressBar(percentageWatched);
}

// 创建时间显示元素
const timeDisplay = document.createElement('div');
timeDisplay.id = 'time-display';
timeDisplay.style.position = 'fixed';
timeDisplay.style.left = '5px';
timeDisplay.style.bottom = '10px';
timeDisplay.style.backgroundColor = config.backgroundColor;
timeDisplay.style.color = config.fontColor;
timeDisplay.style.padding = '5px';
timeDisplay.style.borderRadius = '50px';
timeDisplay.style.boxShadow = '0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24)';
timeDisplay.style.fontFamily = '-apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"';
timeDisplay.style.zIndex = '9999999999';
timeDisplay.style.width = '80px';
timeDisplay.style.height = '80px';
timeDisplay.style.cursor = 'move';
timeDisplay.style.opacity = config.opacity;
document.body.appendChild(timeDisplay);

const progressBarContainer = document.createElement('div');
progressBarContainer.id = 'progress-bar-container';
progressBarContainer.style.width = '100%';
progressBarContainer.style.height = '100%';
progressBarContainer.style.position = 'relative';
timeDisplay.appendChild(progressBarContainer);

const progressBarCanvas = document.createElement('canvas');
progressBarCanvas.id = 'progress-bar-canvas';
progressBarCanvas.width = progressBarContainer.clientWidth;
progressBarCanvas.height = progressBarContainer.clientHeight;
progressBarCanvas.style.position = 'absolute';
progressBarCanvas.style.top = '0';
progressBarCanvas.style.left = '0';
progressBarContainer.appendChild(progressBarCanvas);

const ctx = progressBarCanvas.getContext('2d');

const dataContainer = document.createElement('div');
dataContainer.id = 'data-container';
dataContainer.style.position = 'absolute';
dataContainer.style.width = '100%';
dataContainer.style.height = '100%';
dataContainer.style.display = 'none';
dataContainer.style.textAlign = 'center';
dataContainer.style.fontSize = '12px';
dataContainer.style.display = 'flex';
dataContainer.style.flexDirection = 'column';
dataContainer.style.justifyContent = 'center';
dataContainer.style.alignItems = 'center';
progressBarContainer.appendChild(dataContainer);

let percentageWatched = 0;

function formatDuration(seconds) {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const remainingSeconds = Math.floor(seconds % 60);
return `${hours}:${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;
}

function parseDuration(durationText) {
const timeParts = durationText.trim().split(':').map(Number);
let seconds = 0;
if (timeParts.length === 3) {
const [hours, minutes, secs] = timeParts;
seconds = hours * 3600 + minutes * 60 + secs;
} else if (timeParts.length === 2) {
const [minutes, secs] = timeParts;
seconds = minutes * 60 + secs;
}
return seconds;
}

function calculateDurations(durationsInSeconds, currentVideoIndex, currentVideoProgressInSeconds) {
console.log('Durations in seconds:', durationsInSeconds);
console.log('Current video index:', currentVideoIndex);
console.log('Current video progress:', currentVideoProgressInSeconds);
const totalDurationInSeconds = durationsInSeconds.reduce((total, duration) => total + duration, 0);
const watchedDurationInSeconds = durationsInSeconds.slice(0, currentVideoIndex).reduce((total, duration) => total + duration, 0);
const totalWatchedDurationInSeconds = watchedDurationInSeconds + currentVideoProgressInSeconds;
const remainingDurationInSeconds = totalDurationInSeconds - totalWatchedDurationInSeconds;
percentageWatched = (totalDurationInSeconds > 0) ? (totalWatchedDurationInSeconds / totalDurationInSeconds) * 100 : 0;
console.log('Total duration:', totalDurationInSeconds);
console.log('Watched duration:', totalWatchedDurationInSeconds);
console.log('Percentage watched:', percentageWatched);
return {
totalDurationInSeconds,
totalWatchedDurationInSeconds,
remainingDurationInSeconds,
percentageWatched
};
}

function updateCollectionDurations() {
// 从URL获取当前集数
const urlParams = new URLSearchParams(window.location.search);
const currentVideoIndex = parseInt(urlParams.get('p') || '1', 10) - 1;
console.log('Current video index from URL:', currentVideoIndex);

// 查找合集列表中的时长
const videoItems = document.querySelectorAll('.simple-base-item.video-pod__item.normal');
if (videoItems.length === 0) {
console.log('No video items found with selector: .simple-base-item.video-pod__item.normal');
return false;
}
const durationsInSeconds = Array.from(videoItems).map(item => {
const durationElement = item.querySelector('.stat-item.duration');
const durationText = durationElement ? durationElement.textContent.trim() : '0:00';
console.log('Found duration element:', durationText);
return parseDuration(durationText);
});

// 获取当前播放进度
const currentTimeElement = document.querySelector('.bpx-player-ctrl-time-current');
const durationElement = document.querySelector('.bpx-player-ctrl-time-duration');
if (!currentTimeElement || !durationElement) {
console.log('No time elements found with selectors: .bpx-player-ctrl-time-current, .bpx-player-ctrl-time-duration');
return false;
}
const currentVideoProgressInSeconds = parseDuration(currentTimeElement.textContent.trim());
const totalVideoDurationInSeconds = parseDuration(durationElement.textContent.trim());
console.log('Current video progress:', currentVideoProgressInSeconds);
console.log('Total video duration:', totalVideoDurationInSeconds);

const { totalDurationInSeconds, totalWatchedDurationInSeconds, remainingDurationInSeconds, percentageWatched } = calculateDurations(durationsInSeconds, currentVideoIndex, currentVideoProgressInSeconds);
updateProgressBar(percentageWatched);
updateDataContainer(totalDurationInSeconds, totalWatchedDurationInSeconds, remainingDurationInSeconds);
return true;
}

function updateListDurations() {
const listBox = document.querySelector('.list-box') || document.querySelector('.video-list');
if (!listBox) {
console.log('No list box found');
return false;
}
const videoItems = listBox.querySelectorAll('li');
if (videoItems.length === 0) return false;
const durationsInSeconds = Array.from(videoItems).map(item => {
const durationElement = item.querySelector('.duration') || item.querySelector('.time');
return durationElement ? parseDuration(durationElement.textContent) : 0;
});
const currentVideoItem = listBox.querySelector('.on') || listBox.querySelector('.active');
if (!currentVideoItem) return false;
const currentVideoIndex = Array.from(videoItems).indexOf(currentVideoItem);
const currentTimeElement = document.querySelector('.bpx-player-ctrl-time-current');
if (!currentTimeElement) {
console.log('No current time element found');
return false;
}
const currentVideoProgressInSeconds = parseDuration(currentTimeElement.textContent.trim());
const { totalDurationInSeconds, totalWatchedDurationInSeconds, remainingDurationInSeconds, percentageWatched } = calculateDurations(durationsInSeconds, currentVideoIndex, currentVideoProgressInSeconds);
updateProgressBar(percentageWatched);
updateDataContainer(totalDurationInSeconds, totalWatchedDurationInSeconds, remainingDurationInSeconds);
return true;
}

function updateProgressBar(percentage) {
const radius = 35;
const centerX = progressBarCanvas.width / 2;
const centerY = progressBarCanvas.height / 2;
const strokeWidth = 5;

ctx.clearRect(0, 0, progressBarCanvas.width, progressBarCanvas.height);
ctx.beginPath();
ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI);
ctx.strokeStyle = config.progressBarBackgroundColor;
ctx.lineWidth = strokeWidth;
ctx.stroke();

ctx.beginPath();
const endAngle = (percentage / 100) * 2 * Math.PI - Math.PI / 2;
ctx.arc(centerX, centerY, radius, -Math.PI / 2, endAngle > -Math.PI / 2 ? endAngle : -Math.PI / 2);
ctx.strokeStyle = config.progressBarColor;
ctx.lineWidth = strokeWidth;
ctx.stroke();

if (percentage >= 0) {
ctx.font = '14px Arial';
ctx.fillStyle = config.progressFontColor;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(`${percentage.toFixed(2)}%`, centerX, centerY);
console.log('Drawing percentage:', percentage.toFixed(2) + '%');
} else {
console.log('Invalid percentage:', percentage);
}
}

function updateDataContainer(totalDurationInSeconds, totalWatchedDurationInSeconds, remainingDurationInSeconds) {
dataContainer.innerHTML = `

总时长: ${formatDuration(totalDurationInSeconds)}

已观看: ${formatDuration(totalWatchedDurationInSeconds)}

剩余: ${formatDuration(remainingDurationInSeconds)}

`;
}

function updateDurations() {
if (!updateCollectionDurations()) {
updateListDurations();
}
}

function setupVideoListener() {
const videoPlayer = document.querySelector('video') || document.querySelector('bwp-video');
if (videoPlayer) {
videoPlayer.addEventListener('timeupdate', updateDurations);
console.log('Video listener set up');
} else {
console.log('No video player to listen to, using time label as fallback');
const currentTimeElement = document.querySelector('.bpx-player-ctrl-time-current');
if (currentTimeElement) {
const observer = new MutationObserver(() => updateDurations());
observer.observe(currentTimeElement, { childList: true, subtree: true, characterData: true });
}
}
}

progressBarContainer.addEventListener('mouseenter', () => {
progressBarCanvas.style.display = 'none';
dataContainer.style.display = 'flex';
});

progressBarContainer.addEventListener('mouseleave', () => {
progressBarCanvas.style.display = 'block';
dataContainer.style.display = 'none';
});

// 使用轮询确保页面加载完成
function init() {
let attempts = 0;
const maxAttempts = 20;
const interval = setInterval(() => {
attempts++;
console.log('Attempting to initialize, attempt:', attempts);
if (updateDurations() || attempts >= maxAttempts) {
clearInterval(interval);
setupVideoListener();
console.log('Initialization complete, attempts:', attempts);
}
}, 1000);
}

// 拖动功能
let isDragging = false;
let offsetX, offsetY;
let isMouseDown = false;
let hasMoved = false;

function disableTextSelection() {
document.body.style.userSelect = 'none';
document.body.style.webkitUserSelect = 'none';
}

function enableTextSelection() {
document.body.style.userSelect = '';
document.body.style.webkitUserSelect = '';
}

timeDisplay.addEventListener('mousedown', (e) => {
isMouseDown = true;
isDragging = true;
offsetX = e.clientX - timeDisplay.offsetLeft;
offsetY = e.clientY - timeDisplay.offsetTop;
disableTextSelection();
hasMoved = false;
colorPicker.style.display = 'none';
});

document.addEventListener('mousemove', (e) => {
if (isMouseDown) {
isDragging = true;
hasMoved = true;
timeDisplay.style.left = `${e.clientX - offsetX}px`;
timeDisplay.style.top = `${e.clientY - offsetY}px`;
colorPicker.style.display = 'none';
opacitySlider.style.display = 'none';
}
});

document.addEventListener('mouseup', () => {
if (isDragging) {
isDragging = false;
enableTextSelection();
}
isMouseDown = false;
});

timeDisplay.addEventListener('click', (e) => {
if (!hasMoved) {
colorPicker.style.display = 'block';
}
});

timeDisplay.addEventListener('contextmenu', (e) => {
e.preventDefault();
if (!isDragging && !hasMoved) {
opacitySlider.style.display = 'block';
opacitySlider.style.top = `${timeDisplay.offsetTop + timeDisplay.offsetHeight - 25}px`;
opacitySlider.style.left = `${timeDisplay.offsetLeft + 65}px`;
}
});

// 初始化
applyColors();
init();
})();

Post reply

Sign in to post a reply.