Youtube outro skip

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

Verze ze dne 27. 05. 2020. Zobrazit nejnovější verzi.

// ==UserScript==
// @name         Youtube outro skip
// @namespace    http://tampermonkey.net/
// @version      1.3
// @description  Sick of outros on your favourite creators videos? With this script simply set the outro length in seconds, apply and enjoy!
// @author       JustDaile
// @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");}
}

let whenReady = function(selector, callback) {
    if ($(selector).length) {
       return callback($(selector));
    } else {
        setTimeout(function(){
            whenReady(selector, callback);
        }, 100);
    }
};

(function(fn){
    "use strict";
    var l = document.URL;
    console.log(l);
    //
    $(document).ready(function(){
        console.log("document ready");
        fn();
    });
    //
    // detect page change hashchange not working
    // so check every 3 seconds if current URL matches URL we started with.
    // handle appropriately.
    //
    setInterval(function(){
       if (l != document.URL){
           console.log("document url: " + l);
           l = document.URL;
           if (l === "https://www.youtube.com/") {
               console.log("ignoring home");
               destroy();
               return;
           }
           $(document).ready(function(){
               destroy();
               fn();
           });
       }
   }, 3000);
})(function(){
    whenReady(".video-stream", function($stream){
        whenReady("#primary", function($container){
            whenReady("#meta-contents #text.ytd-channel-name,.ytp-ce-channel-title", function($channel){
                let channel = $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");

                $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;
                        $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();
            });
        });
    });
});