YouTube Shorts Autoplay

YouTube Shorts autosplays next video after a video finished

// ==UserScript==
// @name         YouTube Shorts Autoplay
// @version      1.5
// @description  YouTube Shorts autosplays next video after a video finished
// @author       pshot
// @match        *://www.youtube.com/*
// @run-at       document-ready
// @license      MIT
// @namespace    https://greasyfork.org/de/users/1283999-pshot
// ==/UserScript==

(function() {
    'use strict';

    const video_tag = '#shorts-player video'; //"#shorts-player > div.html5-video-container > video";
    const button_nav_down_tag = '#navigation-button-down .yt-spec-button-shape-next';//".yt-spec-button-shape-next";
    const button_upvote_tag = '.ytd-shorts[is-active=""] .action-container';
    const progress_bar_tag = '#progress-bar-line';

    waitForTag(video_tag, (e) => {
        console.log("found shorts video");

        setTimeout(() => {
            setup_auto_player();
            add_keybinds();
        }, 1000);
    });

    function setup_auto_player(){
        const vid = get_video();
        console.log("setup shorts video");
        if(vid){
            //skip_ads(vid);
            setup_video(vid);
            on_video_end(vid);
        }
    }

    function skip_ads(vid){
        // Skip Ads - no vote button => Ad
        const upvote = document.querySelector(button_upvote_tag);
        if(upvote == null){
            setTimeout(() =>{
                next_video(vid);
                document.querySelector(button_nav_down_tag).click();
            }, 500);
            return;
        }
    }

    function on_video_end(vid){
        const on_ended = function(){
            next_video(vid);
            document.querySelector(button_nav_down_tag).click();
            vid.removeEventListener('ended', on_ended);
        }
        vid.addEventListener('ended', on_ended);
    }

    function next_video(vid){
        setTimeout(setup_auto_player, 200);
    }

    function setup_video(vid){
        vid.removeAttribute("loop");

        onAttributeChange(vid, (e, mutation) => {
            if(mutation.attributeName == "loop"){
                vid.removeAttribute("loop");
            }
        }, false)

        // document.querySelector(progress_bar_tag).style = "display: block !important";
    }

    function get_video(){
        if (document.querySelector(video_tag) != null) {
            return document.querySelector(video_tag);
        }
        return null
    }

    function add_keybinds(vid){
        addEventListener("keydown", key_binds)
    }

    function key_binds(e){
        const vid = get_video();
        switch (e.key.toUpperCase()) {
            case "ARROWLEFT":
            case "A":
                vid.currentTime=vid.currentTime-2;
                break;
            case "ARROWRIGHT":
            case "D":
                vid.currentTime=vid.currentTime+2;
                break;
            case "ARROWUP":
            case "ARROWDOWN":
            case "W":
            case "S":
                //console.log("4 video skipped" + vid.src);
                next_video(vid);
                break;

            default:
                break;
        }
    }

    function waitForTag(selector, callback = () => {}, remove = true ){
        if(selector){
            console.log("wait for short on");
            (new MutationObserver((changes, observer) => {
                if(document.querySelector(selector)) {
                    if(remove){
                        observer.disconnect();
                    }
                    callback(document.querySelector(selector));
                }
            })).observe(document, {childList: true, subtree: true});
        }
    }

    function onAttributeChange(element, callback = () => {}, one_time = true){
        const observer = new MutationObserver(function(mutations) {
            mutations.forEach(function(mutation) {
                if(mutation.type === "attributes") {
                    if(one_time){
                        observer.disconnect();
                    }
                    const remove = callback(element, mutation);
                    if(remove){
                        observer.disconnect();
                    }
                }
            });
        });

        observer.observe(element, {
            attributes: true //configure it to listen to attribute changes
        });

        return observer;
    }
})();