Youtube Auto-Pause Bypass

Prevents Youtube from auto-pausing playlist videos

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Youtube Auto-Pause Bypass
// @namespace    http://tampermonkey.net/
// @version      3.3
// @description  Prevents Youtube from auto-pausing playlist videos
// @author       Nestradama
// @match        *://*.youtube.com/*
// @match        *://youtube.com/*
// @run-at       document-end
// @grant GM_setValue
// @grant GM_getValue
// @license      GNU GPLv3
// ==/UserScript==

(function () {
    console.log("UserScript Auto Pause Bypass - LOADED")
    console.log("Thank you for using my script! Do not hesitate to reach me if you have any issues with it (nestradama on Discord)")
    'use strict';

    let isEnabled = GM_getValue('isEnabled', true);
    let lastManualIntent = 0; 
    let isPiPActive = false;

    function injectToggleButton() {
        const btn = document.createElement('button');
        btn.id = 'afk-bypass-toggle';
        btn.textContent = "Auto-Pause Bypass: ON";
        Object.assign(btn.style, {
            margin:       '0 8px',
            padding:      '6px 12px',
            background:   '#212121',
            color:        '#fff',
            border:       '#ba0000 2px solid',
            borderRadius: '6px',
            cursor:       'pointer',
            fontFamily:   'sans-serif',
            fontSize:     '1.2rem',
            alignSelf:    'center',
        });

        btn.addEventListener('click', () => {
            isEnabled = !isEnabled;
            GM_setValue('isEnabled', isEnabled);
            btn.textContent = isEnabled ? "Auto-Pause Bypass: ON" : "Auto-Pause Bypass: OFF";
            btn.style.background = isEnabled ? '#212121' : '#d50000';
            console.log(`AFK Bypass ${isEnabled ? 'enabled' : 'disabled'}`);
        });

        const isMusic = location.hostname === 'music.youtube.com';

        // Poll > navbar element in DOM
        const tryInject = setInterval(() => {
            const target = isMusic
                ? document.querySelector('[slot="nav-bar"] #right-content')
                : document.querySelector('#start');

            if (target) {
                if (isMusic) {
                    target.insertBefore(btn, target.children[1] ?? null);
                } else {
                    target.appendChild(btn);
                }
                clearInterval(tryInject);

                btn.textContent = isEnabled ? "Auto-Pause Bypass: ON" : "Auto-Pause Bypass: OFF";
                btn.style.background = isEnabled ? '#212121' : '#d50000';

                btn.textContent = "Auto-Pause Bypass: OFF";
                const offWidth = btn.offsetWidth;
                btn.textContent = "Auto-Pause Bypass: ON";
                const onWidth = btn.offsetWidth;
                btn.style.minWidth = Math.max(offWidth, onWidth) + 'px';

                btn.textContent = isEnabled ? "Auto-Pause Bypass: ON" : "Auto-Pause Bypass: OFF";
            }
        }, 300);
    }

    if (document.body) {
        injectToggleButton();
    } else {
        document.addEventListener('DOMContentLoaded', injectToggleButton);
    }


    const MANUAL_KEYS = new Set([' ', 'k', 'K', 'MediaPlayPause', 'MediaStop',
                                  'MediaTrackPrevious', 'MediaTrackNext']);

    document.addEventListener('keydown', e => {
        if (MANUAL_KEYS.has(e.key)) lastManualIntent = Date.now();
    }, true);

    document.addEventListener('click', e => {
        if (e.target?.closest?.('#movie_player, ytd-player, ytd-miniplayer, ytmusic-player, ytmusic-player-bar'))
            lastManualIntent = Date.now();
    }, true);

    // Intercept Media Session API
    try {
        navigator.mediaSession.setActionHandler('pause', () => {
            lastManualIntent = Date.now();
            document.querySelectorAll('video').forEach(v => v.pause());
        });
        navigator.mediaSession.setActionHandler('stop', () => {
            lastManualIntent = Date.now();
            document.querySelectorAll('video').forEach(v => v.pause());
        });
    } catch (_) {}

    function watchPiP(video) {
        if (video.__afkPiPWatching) return;
        video.__afkPiPWatching = true;

        video.addEventListener('enterpictureinpicture', () => {
            isPiPActive = true;
            console.log('AFK Bypass: PiP entered — pauses treated as manual');
        });
        video.addEventListener('leavepictureinpicture', () => {
            isPiPActive = false;
            lastManualIntent = Date.now();
            console.log('AFK Bypass: PiP exited');
        });
        video.addEventListener('pause', () => {
            if (isPiPActive) lastManualIntent = Date.now();
        });
    }

    function attachPiPWatchers() {
        document.querySelectorAll('video').forEach(watchPiP);
    }
    attachPiPWatchers();
    new MutationObserver(attachPiPWatchers)
        .observe(document.documentElement, { childList: true, subtree: true });


    // Returns true if the pause is likely from user
    const isManual = () => isPiPActive || Date.now() - lastManualIntent < 1500;

    function resume() {
        if (!isEnabled) return;
        document.querySelectorAll('video').forEach(v => {
            if (v.paused && !v.ended) v.play().catch(() => {});
        });
        console.log("Resumed");
    }

    function closeAfkDialog() {
        if (!isEnabled) return;
        document.querySelectorAll('tp-yt-paper-dialog').forEach(d => {
            if (d.hasAttribute('opened') || d.opened) {
                const hasConfirm =
                    d.querySelector('yt-confirm-dialog-renderer') ||
                    d.shadowRoot?.querySelector('yt-confirm-dialog-renderer');
                if (hasConfirm && d.close) d.close();
            }
        });

        //close via Polymer
        document.querySelectorAll('ytd-popup-container').forEach(el => {
            const inst = el.polymerController || el.inst || el;
            if (inst?.handleClosePopupAction_) {
                try { inst.handleClosePopupAction_('yt-confirm-dialog-renderer'); } catch (_) {}
            }
        });
    }

    document.addEventListener('yt-popup-opened', function (e) {
        try {
            const target = e.composedPath?.()[0] ?? e.target;
            if (target?.tagName?.toLowerCase() === 'yt-confirm-dialog-renderer') {
                closeAfkDialog();
                resume();
            }
        } catch (_) {}
    }, true);

    document.addEventListener('iron-overlay-opened', function (e) {
        try {
            const dialog = e.target;
            if (!dialog) return;
            const hasConfirm =
                dialog.querySelector?.('yt-confirm-dialog-renderer') ||
                dialog.shadowRoot?.querySelector('yt-confirm-dialog-renderer');
            if (hasConfirm) {
                closeAfkDialog();
                resume();
            }
        } catch (_) {}
    }, true);

    function attachPlayerObserver(player) {
        if (player.__afkWatching) return;
        player.__afkWatching = true;

        new MutationObserver(mutations => {
            for (const m of mutations) {
                if (m.attributeName !== 'class') continue;
                const justPaused =
                    !m.oldValue?.includes('paused-mode') &&
                    player.classList.contains('paused-mode');

                if (justPaused && !isManual()) {
                    resume();
                }
            }
        }).observe(player, {
            attributes: true,
            attributeOldValue: true,
            attributeFilter: ['class'],
        });
    }

    function tryAttach() {
        const player = document.querySelector('#movie_player');
        if (player) attachPlayerObserver(player);

        const musicPlayer = document.querySelector('ytmusic-player');
        if (musicPlayer) attachPlayerObserver(musicPlayer);
    }

    tryAttach();

    document.addEventListener('yt-navigate-finish', () => {
        setTimeout(tryAttach, 800); 
    });

    new MutationObserver(tryAttach)
        .observe(document.documentElement, { childList: true, subtree: true });

})();