学习必备:B站全自动跳过固定片头片尾 V1.4 (支持指定视频跳过)

作者头像
首页 编程语言区 正文
本帖最后由 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();
    }
})();

说明
本脚本是专门为了学习而编写的脚本,也只有在学习时使用该脚本才有可能有相应的效果,严禁将此脚本内容用于任何商业或非法用途,对于因违反此说明而产生的任何法律后果,用户需自行承担全部责任。

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

发表评论

  • 泡泡
  • 阿呆
  • 阿鲁
  • 蛆音娘
    没有更多评论了

个人信息

HI ! 请登录
开通会员,享受下载全站资源特权。
百度一下

随便看看

大家都在看

标签TAG