本帖最后由 l9zp6 于 2025-7-4 13:53 编辑 2025.7.4 13:44 更新V1.4 因为除了用B站学习以外,可能还会浏览一些其他视频,但其他视频并不需要跳转片头片尾。 所以,迭代这个版本,增加功能如下: 1、对指定BV号视频跳过片头片尾 2、可自定义每个视频的跳过时间 如果没有这两个需求,可以继续使用V1.2的版本。 PS:两个版本均兼容B站空降助手。 |
// ==UserScript==
// [url=home.php?mod=space&uid=170990]@name[/url] B站指定视频片头片尾跳过
// [url=home.php?mod=space&uid=467642]@namespace[/url] [url=http://tampermonkey.net/]http://tampermonkey.net/[/url]
// [url=home.php?mod=space&uid=1248337]@version[/url] 1.4
// @description 仅对指定BV号视频跳过片头片尾,可自定义每个视频的跳过时间
// [url=home.php?mod=space&uid=686208]@AuThor[/url] l9zp6
// [url=home.php?mod=space&uid=195849]@match[/url] [url=https://www.bilibili.com/video/]https://www.bilibili.com/video/[/url]*
// @match [url=https://www.bilibili.com/bangumi/play/]https://www.bilibili.com/bangumi/play/[/url]*
// [url=home.php?mod=space&uid=609072]@grant[/url] GM_getValue
// @grant GM_setValue
// @grant GM_addStyle
// ==/UserScript==
(function() {
'use strict';
// ========================== 配置区域 ==========================
// 默认跳过时间(当未指定视频的自定义时间时使用)
const DEFAULT_INTRO_SKIP_TIME = 0; // 默认不跳过片头
const DEFAULT_OUTRO_SKIP_TIME = 0; // 默认不跳过片尾
// 指定视频的自定义跳过时间(支持多个BV号)
// 格式: 'BV号': { intro: 片头时间, outro: 片尾时间 }
const CUSTOM_SKIP_TIMES = {
'BV14yzCYgE9Y': { intro: 10, outro: 5 }, // 示例1:自定义此BV号的跳过时间
'BV14yzCYgE9S': { intro: 5, outro: 3 }, // 示例2:添加更多BV号
// 可继续添加更多BV号配置...
};
// =============================================================
// 存储状态信息
let lastVideoKey = ''; // 视频唯一标识
let lastUrl = ''; // 上次的完整URL
let isWaitingForNextVideo = false; // 是否等待下一个视频
let lastProcessTime = 0; // 上次处理时间
let skipCooldown = false; // 跳过冷却标志
let forceProcess = false; // 强制处理标志
// 添加样式
GM_addStyle(`
.skip-hint {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgba(0, 0, 0, 0.7);
color: white;
padding: 10px 20px;
border-radius: 5px;
font-size: 18px;
z-index: 10000;
opacity: 0;
transition: opacity 0.3s;
}
.skip-hint.show {
opacity: 1;
}
`);
// 创建提示元素
function createHintElement() {
const hint = document.createElement('div');
hint.className = 'skip-hint';
document.body.appendChild(hint);
return hint;
}
// 获取播放器元素
function getPlayer() {
return document.querySelector('bwp-video, video');
}
// 获取播放器容器
function getPlayerContainer() {
return document.querySelector('#bilibili-player, .bpx-player-container');
}
// 显示提示
function showHint(text) {
let hint = document.querySelector('.skip-hint');
if (!hint) {
hint = createHintElement();
}
hint.textContent = text;
hint.classList.add('show');
// 获取播放器容器
const playerContainer = getPlayerContainer();
// 检查是否全屏
const isFullscreen = document.fullscreenElement || document.webkitFullscreenElement;
if (playerContainer && !isFullscreen) {
// 非全屏状态,将提示添加到播放器容器内
playerContainer.appendChild(hint);
// 重置位置样式
hint.style.top = '50%';
hint.style.left = '50%';
hint.style.transform = 'translate(-50%, -50%)';
} else {
// 全屏状态,提示保持在body内
document.body.appendChild(hint);
}
setTimeout(() => {
hint.classList.remove('show');
}, 2000); // 提示显示2秒
}
// 获取当前视频的BV号
function getCurrentBVId() {
const pathMatch = location.pathname.match(/\/video\/(BV\w+)/);
return pathMatch ? pathMatch[1] : '';
}
// 生成视频唯一标识(只考虑BV号和p参数)
function generateVideoKey() {
const bvid = getCurrentBVId();
if (!bvid) return '';
const pMatch = location.search.match(/[?&]p=(\d+)/);
const pValue = pMatch ? `_p${pMatch[1]}` : '';
return `${bvid}${pValue}`;
}
// 获取当前视频的跳过时间配置
function getSkipTimes() {
const bvid = getCurrentBVId();
// 如果是指定视频,使用自定义时间,否则使用默认时间
if (CUSTOM_SKIP_TIMES[bvid]) {
console.log(`使用自定义跳过时间 for ${bvid}:`, CUSTOM_SKIP_TIMES[bvid]);
return CUSTOM_SKIP_TIMES[bvid];
}
return {
intro: DEFAULT_INTRO_SKIP_TIME,
outro: DEFAULT_OUTRO_SKIP_TIME
};
}
// 检查视频是否在指定列表中
function isVideoIncluded() {
const bvid = getCurrentBVId();
return !!CUSTOM_SKIP_TIMES[bvid];
}
// 获取视频标题
function getVideoTitle() {
const titleElement = document.querySelector('h1.title, .media-info-title');
return titleElement ? titleElement.textContent.trim() : '';
}
// 检查是否是新视频
function isNewVideo() {
const currentVideoKey = generateVideoKey();
const currentUrl = location.href;
let isNew = currentVideoKey !== lastVideoKey;
// 如果URL相同但需要强制处理
if (!isNew && currentUrl === lastUrl && forceProcess) {
console.log('强制处理相同URL的视频');
isNew = true;
forceProcess = false;
}
if (isNew) {
console.log('检测到新视频:', currentVideoKey);
lastVideoKey = currentVideoKey;
lastUrl = currentUrl;
} else if (currentUrl !== lastUrl) {
console.log('URL参数变化但视频未变:', currentUrl);
lastUrl = currentUrl;
}
return isNew;
}
// 获取并更新记忆进度
function processMemoryProgress(player) {
// 如果不是指定视频,直接返回
if (!isVideoIncluded()) return;
const videoKey = generateVideoKey();
if (!videoKey) return;
const memoryTime = GM_getValue(`bilibili_memory_${videoKey}`, 0);
const { intro } = getSkipTimes();
// 如果记忆进度在片头时间之后,不做处理
if (memoryTime > intro) {
console.log(`检测到记忆进度:${memoryTime}秒,超过片头时间${intro}秒,不跳过`);
return;
}
// 如果是新视频且进度为0,跳转到片头跳过时间
if (player.currentTime <= intro) {
console.log(`跳转到片头跳过时间:${intro}秒`);
player.currentTime = intro;
showHint(`已跳过${intro}秒片头`);
}
}
// 检查片尾并跳过
function checkAndSkipOutro(player, duration) {
// 如果不是指定视频,直接返回
if (!isVideoIncluded()) return;
// 防止无限循环跳转
if (skipCooldown) return;
const { outro } = getSkipTimes();
const currentTime = player.currentTime;
// 检查是否在片尾区域
if (duration - currentTime <= outro) {
// 防止在短时间内重复跳转
const now = Date.now();
if (now - lastProcessTime < 2000) {
console.log('检测到重复跳转尝试,跳过此次操作');
return;
}
lastProcessTime = now;
// 设置冷却期
skipCooldown = true;
setTimeout(() => {
skipCooldown = false;
}, 2000);
// 跳转到离片尾0.5秒处,确保能触发下一集
player.currentTime = duration - 0.5;
console.log(`已跳过${outro}秒片尾,跳转到${duration - 0.5}秒`);
showHint(`已跳过${outro}秒片尾`);
// 标记为等待下一个视频,并设置强制处理
isWaitingForNextVideo = true;
forceProcess = true;
}
}
// 检查是否应该处理新视频
function shouldProcessNewVideo(player) {
if (!player) return false;
// 如果正在等待下一个视频且播放器已加载新视频
if (isWaitingForNextVideo && player.currentTime < 5) {
console.log('检测到自动连播的新视频');
isWaitingForNextVideo = false;
return true;
}
// 检查是否是新视频
return isNewVideo();
}
// 主处理函数
function processVideo() {
const player = getPlayer();
if (!player) return;
// 检查是否应该处理新视频
if (shouldProcessNewVideo(player)) {
console.log('检测到需要处理的新视频:', getVideoTitle());
// 处理记忆进度
processMemoryProgress(player);
}
// 保存进度
const videoKey = generateVideoKey();
if (videoKey) {
GM_setValue(`bilibili_memory_${videoKey}`, player.currentTime);
}
// 检查片尾
if (player.duration && player.duration > 0) {
checkAndSkipOutro(player, player.duration);
}
}
// 初始化
function init() {
console.log('B站片头片尾跳过脚本已加载');
// 监听播放器状态变化
const player = getPlayer();
if (player) {
player.addEventListener('loadedmetadata', () => {
console.log('播放器加载了新的元数据');
processVideo();
});
player.addEventListener('timeupdate', () => {
// 播放器时间更新时检查
processVideo();
});
}
// 监听URL变化
new MutationObserver(() => {
console.log('检测到URL变化');
setTimeout(() => {
processVideo();
}, 300);
}).observe(document, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['href']
});
// 定时检查
setInterval(() => {
const player = getPlayer();
if (player && !player.paused) {
processVideo();
}
}, 1000);
// 初始处理
setTimeout(processVideo, 1000);
}
// 等待页面加载完成
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();
说明
本脚本是专门为了学习而编写的脚本,也只有在学习时使用该脚本才有可能有相应的效果,严禁将此脚本内容用于任何商业或非法用途,对于因违反此说明而产生的任何法律后果,用户需自行承担全部责任。
更多推荐内容
- python批量插入图片到word文档里并且实现分栏排版 8 小时前
- DS生成的代码-考场监考人员抽签系统 8 小时前
- 实时系统监控面板 8 小时前

文章采用: 《署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)》许可协议授权。
版权声明:本站资源来自互联网收集,仅供用于学习和交流,请勿用于商业用途。如有侵权、不妥之处,请联系客服并出示版权证明以便删除!
一个简单网页预约会议室系统
« 上一篇
07-04
自写自用的抖音下载工具,自己用了很久!拿出来给大家
下一篇 »
07-04
发表评论