YouTube: Stop Automatic Video Playback

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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()
})