哔哩哔哩番剧助手 | Bilibili Bangumi Assistant
// ==UserScript==
// @name 哔哩哔哩番剧助手 | Bilibili Bangumi Assistant
// @namespace http://tampermonkey.net/
// @version 3.3
// @description 自动跳过片头片尾,支持自定义快进秒数和播放速度记忆功能,使观看体验更加流畅。
// @description-en Automatically skip opening and ending sequences, support customizable fast-forward seconds and playback speed memory for a smoother viewing experience.
// @author MarySue
// @match *://*.bilibili.com/bangumi/play/ep*
// @grant none
// @run-at document-end
// @license GNU GPLv3
// ==/UserScript==
(function() {
'use strict';
// 用户自定义块开始 -----------------------------------------------
const userSettings = {
settings: [
{
pattern: '^https?://(www\\.)?bilibili\\.com/bangumi/play/ep\\d+', // 匹配带有可变ep编号的URL
titlePattern: /柯南/, // 包含"柯南"
introDuration: 99, // 片头时长 (秒)
outroDuration: 0, // 片尾时长 (秒)
forwardSeconds: 80, // 快进秒数
forwardKey: 'KeyK', // 快进快捷键
playbackSpeedKeys: {
slowDown: 'KeyZ', // 慢速热键
reset: 'KeyX', // 重置速度热键
speedUp: 'KeyC' // 快速热键
},
playbackSpeedSteps: {
decreaseStep: 0.1, // 每次按下慢速热键减少的速度
increaseStep: 0.1 // 每次按下快速热键增加的速度
}
}
],
defaultSetting: {
introDuration: 0,
outroDuration: 0,
forwardSeconds: 0,
forwardKey: 'Space',
playbackSpeedKeys: {
slowDown: null, // 默认无慢速热键
reset: null, // 默认无重置速度热键
speedUp: null // 默认无快速热键
},
playbackSpeedSteps: {
decreaseStep: 0.1,
increaseStep: 0.1
}
}
};
// 用户自定义块结束 --------------------------------------------
// 获取当前页面对应的设置
function getSettingsForPage(userSettings) {
let pageUrl = window.location.href;
let pageTitle = document.title; // 获取页面标题
for (let setting of userSettings.settings) {
if (new RegExp(setting.pattern).test(pageUrl) && new RegExp(setting.titlePattern).test(pageTitle)) {
return setting;
}
}
return userSettings.defaultSetting;
}
// 使用 MutationObserver 动态检测视频元素加载
function waitForVideoElement(callback) {
const observer = new MutationObserver(() => {
const video = document.querySelector('video');
if (video) {
callback(video);
observer.disconnect(); // 找到视频后停止观察
}
});
observer.observe(document.body, { childList: true, subtree: true });
}
// 应用跳过逻辑到视频元素上
function applySkipLogic(videoElement, settings) {
let originalDuration = videoElement.duration;
let userSeeking = false;
videoElement.addEventListener('loadedmetadata', () => {
originalDuration = videoElement.duration;
});
videoElement.addEventListener('seeking', () => {
userSeeking = true;
});
videoElement.addEventListener('seeked', () => {
userSeeking = false;
});
videoElement.addEventListener('timeupdate', () => {
if (!userSeeking && videoElement.currentTime < settings.introDuration) {
videoElement.currentTime = settings.introDuration;
} else if (!userSeeking && originalDuration - videoElement.currentTime < settings.outroDuration) {
videoElement.currentTime = originalDuration - settings.outroDuration;
}
});
}
// 监听键盘事件以实现快进
function setupKeyboardShortcut(settings) {
document.addEventListener('keydown', event => {
if (event.code === settings.forwardKey) {
const video = document.querySelector('video');
if (video) {
video.currentTime += settings.forwardSeconds;
}
}
});
}
// 处理播放速度控制的函数
function setupPlaybackSpeedControl(videoElement, settings) {
let lastPlaybackRate = parseFloat(localStorage.getItem('lastPlaybackRate')) || 1.0;
let hasReset = false; // 跟踪是否已经恢复过默认速度
let previousPlaybackRate = lastPlaybackRate;
videoElement.playbackRate = lastPlaybackRate;
function changePlaybackSpeed(event) {
if (event.code === settings.playbackSpeedKeys.slowDown) {
lastPlaybackRate -= settings.playbackSpeedSteps.decreaseStep;
lastPlaybackRate = parseFloat(lastPlaybackRate.toFixed(2));
if (lastPlaybackRate < 0.1) { lastPlaybackRate = 0.1; }
videoElement.playbackRate = lastPlaybackRate;
localStorage.setItem('lastPlaybackRate', lastPlaybackRate);
hasReset = false; // 更新播放速度时重置标志
} else if (event.code === settings.playbackSpeedKeys.speedUp) {
lastPlaybackRate += settings.playbackSpeedSteps.increaseStep;
lastPlaybackRate = parseFloat(lastPlaybackRate.toFixed(2));
videoElement.playbackRate = lastPlaybackRate;
localStorage.setItem('lastPlaybackRate', lastPlaybackRate);
hasReset = false; // 更新播放速度时重置标志
} else if (event.code === settings.playbackSpeedKeys.reset) {
if (!hasReset) {
// 第一次按下恢复默认速度并保存当前速度
previousPlaybackRate = lastPlaybackRate;
videoElement.playbackRate = 1.0;
lastPlaybackRate = 1.0;
localStorage.setItem('lastPlaybackRate', lastPlaybackRate);
hasReset = true;
} else {
// 再次按下恢复之前的播放速度
videoElement.playbackRate = previousPlaybackRate;
lastPlaybackRate = previousPlaybackRate;
localStorage.setItem('lastPlaybackRate', lastPlaybackRate);
hasReset = false;
}
}
}
document.addEventListener('keydown', changePlaybackSpeed);
}
// 初始化
let settingsForPage = getSettingsForPage(userSettings);
// 确保脚本在页面完全加载后执行,并等待视频元素加载完成
waitForVideoElement((video) => {
if (settingsForPage !== userSettings.defaultSetting) { // 只有当URL和title都匹配时才应用逻辑
applySkipLogic(video, settingsForPage);
setupKeyboardShortcut(settingsForPage);
setupPlaybackSpeedControl(video, settingsForPage);
}
});
// 监听 video 元素的播放状态以恢复播放速度
function restorePlaybackSpeedOnNewVideo() {
const observer = new MutationObserver(() => {
const video = document.querySelector('video');
if (video) {
let lastPlaybackRate = parseFloat(localStorage.getItem('lastPlaybackRate')) || 1.0;
video.playbackRate = lastPlaybackRate;
}
});
observer.observe(document.body, { childList: true, subtree: true });
}
restorePlaybackSpeedOnNewVideo(); // 在页面加载时启动恢复播放速度的监听
})();
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.