YouTube: Stop Automatic Video Playback

Stop automatic video playback everywhere. Works on first page load & after navigating.

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name            YouTube: Stop Automatic Video Playback
// @namespace       org.sidneys.userscripts
// @homepage        https://gist.githubusercontent.com/sidneys/02a9025ae1f23aefe1f4ea02e78b0ac8/raw/
// @version         4.7.3
// @description     Stop automatic video playback everywhere. Works on first page load & after navigating.
// @author          sidneys
// @icon            https://www.youtube.com/favicon.ico
// @noframes
// @match           http*://www.youtube.com/*
// @require         https://greasyfork.org/scripts/38888-greasemonkey-color-log/code/Greasemonkey%20%7C%20Color%20Log.js
// @run-at          document-start
// ==/UserScript==


/**
 * ESLint
 * @global
 */
/* global Debug */
Debug = false


/**
 * Applicable URL paths
 * @default
 * @constant
 */
const urlPathList = [
    '/channel',
    '/watch'
]


/**
 * YouTube API Player States
 * @constant
 * @enum
 */
const PLAYERSTATE = {
    '-1': 'UNSTARTED',
    0: 'ENDED',
    1: 'PLAYING',
    2: 'PAUSED',
    3: 'BUFFERING',
    5: 'CUED'
}


/**
 * Generate a method name for an event name using the DOM convention ("on" + Event Name)
 * @param {String} eventName - Event name (e.g. 'Click')
 * @returns {String} - Method name (e.g. 'onclick')
 */
let getHandlerMethodNameForEventName = eventName => `on${eventName.toLowerCase()}`

/**
 * Lookup the first <video> Element
 * @returns {Element} - <video> Element
 */
let getVideoElement = () => document.querySelector('video')

/**
 * Lookup YouTube Video Player through the DOM
 * @returns {Object} - YouTube Video Player
 */
let getYoutubePlayer = () => {
    console.debug('getYoutubePlayer')

    // Lookup Player element
    const playerElement = document.querySelector('ytd-player')

    // Return the property containing the Player API
    return playerElement && playerElement.player_
}

/**
 * Stop playback on YouTube via the Player API
 * @param {Object} youtubePlayer - YouTube Video Player API
 */
let stopYoutubePlayerPlayback = (youtubePlayer) => {
    console.debug('stopYoutubePlayerPlayback')

    // Get YouTube Video element
    const videoElement = getVideoElement()

    // Playback event types to watch
    const eventTypeList = [ 'play', 'playing', 'timeupdate' ]

    // Iterate playback event types
    eventTypeList.forEach((eventType, eventTypeIndex) => {

        // Playback "Stopper" method, each playback event
        let eventHandler = () => {
            console.debug(`videoElement#${eventType}`)

            // Remove all "Stopper" event handlers by deleting <video>#onplay, <video>#onplaying, <video>#ontimeupdate
            eventTypeList.forEach((eventType) => {
                const handlerMethodName = getHandlerMethodNameForEventName(eventType)

                delete videoElement[handlerMethodName]
                videoElement[handlerMethodName] = null

                // DEBUG
                console.debug('videoElement', 'removing event handler method:', handlerMethodName)
            })

            // Lookup YouTube Player state
            const playerState = youtubePlayer.getPlayerState()

            // Stop video (if it is not already paused)
            if (youtubePlayer.getPlayerState() !== 2) {
                youtubePlayer.pauseVideo()

                // Status
                console.info('Stopped automatic video playback', 'during the', PLAYERSTATE[playerState], 'phase')

                // DEBUG
                console.debug('stopYoutubePlayerPlayback', 'eventType:', eventType, 'playerState:', `${playerState} (${PLAYERSTATE[playerState]})`)
            }
        }

        // Add event handler to video element
        const handlerMethodName = getHandlerMethodNameForEventName(eventType)
        videoElement[handlerMethodName] = eventHandler
    })
}


/**
 * Init
 */
let init = () => {
    console.info('init')

    // Verify URL path
    if (!urlPathList.some(urlPath => window.location.pathname.startsWith(urlPath))) { return }

    // Initiate lookup loop
    let requestId
    let lookup = () => {
        // Lookup YouTube Player
        const youtubePlayer = getYoutubePlayer()

        // Is 1. the Player API available,  2. the Player ready?
        if (!youtubePlayer || (youtubePlayer && !youtubePlayer.isReady())) {
            // DEBUG
            console.debug('❌ YouTube Player API unavailable or Player not ready yet.')

            // Skip loop
            requestId = window.requestAnimationFrame(lookup)

            return
        }

        // Stop Playback
        stopYoutubePlayerPlayback(youtubePlayer)

        // DEBUG
        console.debug('✅ YouTube Player API available and Player ready.')

        // End loop
        window.cancelAnimationFrame(requestId)
    }

    // Initiate loop
    requestId = window.requestAnimationFrame(lookup)
}


/**
 * Handle in-page navigation (modern YouTube)
 * @listens window:Event#yt-navigate-finish
 */
window.addEventListener('yt-navigate-finish', () => {
    console.debug('window#yt-navigate-finish')

    init()
})