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. NOTE: On YouTube, click the main video area to play the video so don't have to click the Pause/Play button twice for the first time. From Dan Moorehead (xeonx1), developer of PowerAccess™ (http://PowerAccessDB.com) (https://twitter.com/PowerAccessDB).

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

// ==UserScript==
// @name         Disable HTML5 Video AutoPlay and Background Play (Pause On Leave Tab)
// @namespace    xeonx1
// @version      1.5
// @description  Prevents HTML5 videos from auto-playing in new tabs on any page (not just YouTube) and pauses videos when leave tab.  NOTE: On YouTube, click the main video area to play the video so don't have to click the Pause/Play button twice for the first time.  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) and
// @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();

})();