Youtube outro skip

Sick of outros on your favourite creators videos? With this script simply set the outro length in seconds, apply and enjoy!

目前为 2020-09-23 提交的版本。查看 最新版本

// ==UserScript==
// @name         Youtube outro skip
// @namespace    http://tampermonkey.net/
// @version      1.5
// @description  Sick of outros on your favourite creators videos? With this script simply set the outro length in seconds, apply and enjoy!
// @author       Daile Alimo
// @match        https://www.youtube.com/watch?*
// @grant        GM_setValue
// @grant        GM_getValue
// @require      http://code.jquery.com/jquery-latest.js
// ==/UserScript==

let destroy = function(){
    //
    // make sure we have no events binded, in the case fn() was called by interval on URL change
    // this will ensure that we can create clean controls for the current playlist without accidentally
    // having events persisting in the background.
    //
    if ($(".video-stream").unbind()) {console.log("unbinding .video-stream");}
    if ($("#set-outro").unbind()){console.log("unbinding #set-outro");}
    if ($(".outro-controls").remove()){console.log("removed controls");}
}

// whenReady - keep checking the DOM until these given selectors are found
// invokes a callback function on complete that contains an object containing the
// JQuery element(s) for the given selectors accessable with aliases if given.
const whenReady = ({selectors = [], aliases = [], callback = (selectors = {})}) => {
    let ready = {};
    let found = 0;
    for(let i in selectors){
        let $sel = $(selectors[i]);
        if ($sel.length) {
            let index = aliases[i] ? aliases[i]: i;
            console.log("added selector: " + index);
            ready[index] = $sel;
            found++;
        }
    }
    console.log(found + " out of given " + selectors.length + " selector(s) found");
    if (found === selectors.length) {
        return callback(ready);
    }
    setTimeout(function(){
        console.log("waiting for " + (selectors.length - found) + " selector(s)");
        whenReady({
            selectors: selectors,
            aliases: aliases,
            callback: callback
        });
    }, 500);
};

(function(fn){
    "use strict";
    //
    $(document).ready(function(){
        fn();
    });
    //
    // detect page change hashchange not working
    // so check every 3 seconds if current URL matches URL we started with.
    // handle appropriately.
    //
    var l = document.URL;
    setInterval(function(){
        if (l != document.URL){
            l = document.URL;
            destroy();
            if (l === "https://www.youtube.com/") {
                // ignore home, no controls here
                return;
            }
            fn();
        }
    }, 3000);
})(function(){
    whenReady({
        selectors: [".video-stream", "#primary", ".ytp-settings-menu .ytp-panel-menu", "#meta-contents #text.ytd-channel-name,.ytp-ce-channel-title"],
        aliases: ["stream", "container", "settings", "channel"],
        callback: (selectors) => {
            //
            let channel = selectors.channel.first().text();
            console.log("loaded channel: " + channel);
            //
            let urlString = document.URL + '&';
            //
            let isPlaylist = urlString.includes("list")
            let loadedChannel = true;
            let targetId = channel.split(" ").join("_");
            if (isPlaylist && !GM_getValue(targetId)) {
                console.log("using playlist outro settings");
                loadedOutroSetInSeconds = GM_getValue(urlString.match(/[\?\&]list=([^\&\#]+)[\&\#]/i)[1]);
                loadedChannel = false;
            }
            //
            var loadedOutroSetInSeconds = GM_getValue(targetId);
            console.log("outro set: " + (loadedOutroSetInSeconds || 0));
            //
            // basic outro controls design prepended to the primary container.
            // if property length doesn't exist we can assume controls don't either.
            // prepend controls to #primary container
            //
            let target = isPlaylist? "playlist" : channel;
            console.log("target set " + (loadedChannel? "channel": "playlist") + " = " + targetId);
            console.log("writing outro controls");

            selectors.container.prepend(
                $("<div class='outro-controls'>").append(
                    $("<h3>Youtube Skip Outro Controller V1.1</h3>")
                ).append(
                    $("<input type='number' id='outro-length' placeholder='using id: " + targetId + "'/>")
                ).append(
                    $("<button id='set-outro'>apply</button>")
                ).append(
                    $("<div>" + target + " outro set: <span id='outro-set'>0</span> seconds</div>").css({
                        "padding": "2px",
                    })
                ).css({
                    "margin": "2px",
                    "textAlign": "right",
                })
            );
            //
            let bindToStream = function(){
                // hook video timeupdate, wait for outro and hit next button when time reached
                //
                if(loadedOutroSetInSeconds){
                    var skipOutroSeconds = parseInt(loadedOutroSetInSeconds) | 0;
                    selectors.stream.unbind("timeupdate").on("timeupdate", function(e){
                        var currentTime = this.currentTime;
                        var duration = this.duration;
                        if(currentTime >= duration - skipOutroSeconds){
                            $(".ytp-next-button")[0].click();
                        }
                    });
                    $("#outro-set").text(loadedOutroSetInSeconds);
                }
            }
            //
            // handle apply outro in seconds
            //
            $("#set-outro").on("click", function(e){
                var seconds = $("#outro-length").val().toString();
                if(seconds && seconds != "" && parseInt(seconds) != NaN){
                    GM_setValue(targetId, seconds);
                    loadedOutroSetInSeconds = seconds;
                    bindToStream();
                }
            });
            bindToStream();
        },
    });
});

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址