Disable HTML5 Video AutoPlay and Background Play (Pause On Leave Tab)

Prevents HTML5 videos from auto-playing in new tabs on any page (not just YouTube) and pauses videos when leave tab. From Dan Moorehead (xeonx1), developer of PowerAccess™ (http://PowerAccessDB.com) (https://twitter.com/PowerAccessDB).

目前為 2017-06-28 提交的版本,檢視 最新版本

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

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

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

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

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

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

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Disable HTML5 Video AutoPlay and Background Play (Pause On Leave Tab)
// @namespace    xeonx1
// @version      1.4
// @description  Prevents HTML5 videos from auto-playing in new tabs on any page (not just YouTube) and pauses videos when leave tab.  From Dan Moorehead (xeonx1), developer of PowerAccess™ (http://PowerAccessDB.com) (https://twitter.com/PowerAccessDB).
// @author       Dan Moorehead (xeonx1), developer of PowerAccess™ (http://PowerAccessDB.com) (https://twitter.com/PowerAccessDB)
// @match        http://*/*
// @match        https://*/*
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    // **** User Preferences ****
    //whether to pause videos when leaving a tab they are playing on
    var pauseOnLeaveTab = true;

     // Number of milliseconds after clicking where a video is allowed to autoplay.
    var allowAutoPlayWithinMillisecondsOfClick = 500;

    //you can add domains (with or without subdomain, must not include http://, etc.) to always allow auto-play
    //pause on leave tab will still function however
    var autoPlayDomains = [
        /*"youtube.com"*/
    ];
    var hasAutoPlayDomains = autoPlayDomains.length > 0;
    // **** End Preferences ***
    /*
         Beyond this point is the logic of the script.
         Do not edit unless you know what you are doing.
      */
    var lastClickTimeMs = 0;

    //determine name of event for switched away from tab, based on the browser
    var tabHiddenPropertyName, tabVisibleChangedEventName;

    if ("undefined" !== typeof document.hidden) {
        tabHiddenPropertyName = "hidden";
        tabVisibleChangedEventName = "visibilitychange";
    } else if ("undefined" !== typeof document.webkitHidden) {
        tabHiddenPropertyName = "webkitHidden";
        tabVisibleChangedEventName = "webkitvisibilitychange";
    }  else if ("undefined" !== typeof document.msHidden) {
        tabHiddenPropertyName = "msHidden";
        tabVisibleChangedEventName = "msvisibilitychange";
    }

    function safeAddHandler(element, event, handler) {
        element.removeEventListener(event, handler);
        element.addEventListener(event, handler);
    }
    function getVideos() {
        //OR: Can also add audio elements
        return document.getElementsByTagName("video");
    }

    function isPlaying(vid) {
        return !!(vid.currentTime > 0 && !vid.paused && !vid.ended && vid.readyState > 2);
    }

    function onTabVisibleChanged() {

        //console.log("Tab visibility changed for Video auto-player disabling user script. Document is hidden status: ", document[tabHiddenPropertyName]);

        var videos = getVideos();

        //if doc is hidden (switched away from that tab), then pause all its videos
        if (document[tabHiddenPropertyName]) {

            //remember had done this
            document.wasPausedOnChangeTab = true;

            //pause all videos, since
            for (var i = 0; i < videos.length; i++) {
                var vid = videos[i];
                
                if (vid.isPlaying) {
                    vid.wasPausedOnChangeTab = true;
                    console.log("Paused video playback because switched away from this tab for video with source: ", vid.currentSrc);
                }

                //always pause just in case isPlaying isn't always reliable
                vid.pause();
            }
        }
        //document is now the active tab
        else { 
            document.wasPausedOnChangeTab = false; //reset state (unless need to use this field or delay this)
            
            //TODO-MAYBE: if want to auto-play once switch back to a tab if had paused before, then uncomment below, after changing from forEach() to for loop
            // getVideos().forEach( function(vid) {
            //     if (vid.wasPausedOnChangeTab == true) {
            //         vid.wasPausedOnChangeTab = false;
            //         vid.play();
            //     }
            // } ); 
        }
    }

    //handle active tab change events for this document/tab
    if (pauseOnLeaveTab) {
        safeAddHandler(document, tabVisibleChangedEventName, onTabVisibleChanged);
    }

    //returns true if auto-play is always allowed, whitelisted, so should do nothing to it
    function isAutoPlayAllowedDomain(s) { // Check if video src is whitelisted.

        if (hasAutoPlayDomains) {
            for (var i = 0; i < autoPlayDomains.length; i++) {
                var reg = new RegExp("https?\:\/\/[a-zA-Z0-9\.\-]*?\.?" + autoPlayDomains[i].replace(/\./, "\.") + "\/", "i");
                if (s.match(reg) !== null) {
                    return true;
                }
            }
        }
        return false;
    }
    //on pause or ended/finished, change playing state back to not-playing, so know to start preventing playback again unless after a click
    function onPaused(e)
    {
        e.target.isPlaying = false;

    }
    function onPlay(e) { // React when a video begins playing

        var msSinceLastClick = Date.now() - lastClickTimeMs;
        var vid = e.target;

        //exit, do nothing if is already playing (but not if undefined/unknown), in case clicked on seekbar, volume, etc. - don't toggle to paused state on each click
        if(vid.isPlaying == true) {
           return;
        }
        vid.isPlaying = true;

        //if haven't clicked recently on video, consider it auto-started, so prevent playback by pausing it (unless whitelisted source domain to always play from)
        if (msSinceLastClick > allowAutoPlayWithinMillisecondsOfClick && !isAutoPlayAllowedDomain(vid.currentSrc)) {

            vid.pause();
            
            //remember video is no longer playing -  just in case, though event handler for pause should also set this
            vid.isPlaying = false;
            
            console.log("Paused video from source: ", vid.currentSrc);
        }
    }
    function addListenersToVideo(vid)
    {
        if (vid.hasAutoPlayHandlers != true) {
            vid.hasAutoPlayHandlers = true;

            safeAddHandler(vid, "play", onPlay);
            //NOTE: Seems playing is needed in addition to play event, but isn't this just supposed to occur whenever play, plus after play once buffering is finished?
            safeAddHandler(vid, "playing", onPlay);

            safeAddHandler(vid, "pause", onPaused);
            safeAddHandler(vid, "ended", onPaused);
        }
    }
    function addListeners() {

        var videos = getVideos();
        //OR: Can get audio elements too

        for (var i = 0; i < videos.length; i++) {
            // Due to the way some sites dynamically add videos, the "playing" event is not always sufficient.
            // Also, in order to handle dynamically added videos, this function may be called on the same elements.
            // Must remove any existing instances of this event listener before adding. Prevent duplicate listeners.
            var vid = videos[i];

            addListenersToVideo(vid);
        }
    }

    //handle click event so can limit auto play until X time after a click
    safeAddHandler(document, "click", function () {
        lastClickTimeMs = Date.now();
    });

    var observer = new MutationObserver(function(mutations) {
        // Listen for elements being added. Add event listeners when video elements are added.
        mutations.forEach(function(mutation) {

            if (mutation.type == "attributes" && mutation.target.tagName == "VIDEO") { //&& mutation.attributeName == "src" 

                videoAdded = true;

                addListenersToVideo(mutation.target);
            }

            if (mutation.addedNodes.length > 0) {

                addListeners();

                //faster to use getElementsByTagName() for rarely added types vs. iterating over all added elements, checking tagName
                // for (var i = 0; i < mutation.addedNodes.length; i++) {
                //     var added = mutation.addedNodes[i];
                //     if (added.nodeType == 1 && added.tagName == "VIDEO") {
                //         videoAdded = true;
                //     }
                // }
            }
        });
    });

    //subscribe to documents events for node added and src attribute changed via MutatorObserver, limiting to only src attribute changes
    observer.observe(document, { attributes: true, childList: true, subtree: true, characterData: false, attributeFilter: ['src'] });

    //don't also need to handle "spfdone" event

    //hookup event handlers for all videos that exist now (will still add to any that are inserted later)
    addListeners();

})();