Play Youtube playlist in reverse order

Adds button for loading the previous video in a YT playlist

目前為 2020-06-25 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Play Youtube playlist in reverse order
// @namespace    https://github.com/Dragosarus/Userscripts/
// @version      2.0
// @description  Adds button for loading the previous video in a YT playlist
// @author       Dragosarus
// @match        www.youtube.com/watch?*list*
// @grant        none
// @require      http://code.jquery.com/jquery-latest.js
// ==/UserScript==

// Cookies (current session):
// pytplir_playPrevious - saves the button state between loads

(function() {
    'use strict';

    // Your code here...
    $(document).ready(function() {
        var activeColor = "rgb(64,166,255)";
        var inactiveColor = "rgb(144,144,144)";
        var circleColor = "rgb(144,144,144)";
        var ttBGColor = "rgb(100,100,100)";
        var ttTextColor = "rgb(237,240,243)";

        var player;
        var arrow_up;
        var arrow_down;
        var btn_svg;
        var btn_div;
        var playPrevious;
        var redirectFlag = false;
        // remove the need to refresh the page for the script to work properly
        $("html")[0].addEventListener("yt-navigate-finish",init);
        $("html")[0].addEventListener("yt-navigate-start",removeButton);

        init();

        function init() {
            playPrevious = getCookie("pytplir_playPrevious");
            if (playPrevious === "") { // cookie has not been set yet
                playPrevious = false; // inital state
                setCookie("pytplir_playPrevious",playPrevious);
            }

            // create button
            btn_div = document.createElement("div");
            var bg_circle = document.createElementNS("http://www.w3.org/2000/svg","circle");
            var bg_circle_anim = document.createElementNS("http://www.w3.org/2000/svg","animate");
            arrow_up = document.createElementNS("http://www.w3.org/2000/svg","polygon");
            arrow_down = document.createElementNS("http://www.w3.org/2000/svg","polygon");
            btn_svg = document.createElementNS("http://www.w3.org/2000/svg","svg");
            var tt_svg = document.createElementNS("http://www.w3.org/2000/svg","svg");
            var tt_svg_fadein = document.createElementNS("http://www.w3.org/2000/svg","animate");
            var tt_svg_fadeout = document.createElementNS("http://www.w3.org/2000/svg","animate");
            var tt_rect = document.createElementNS("http://www.w3.org/2000/svg","rect");
            var tt_text = document.createElementNS("http://www.w3.org/2000/svg","text");

            bg_circle_anim.setAttribute("attributeName","fill-opacity");
            bg_circle_anim.setAttribute("values","0;0.1;0.2;0.1;0.0");
            bg_circle_anim.setAttribute("dur","0.3s");
            bg_circle_anim.setAttribute("restart","always");
            bg_circle_anim.setAttribute("repeatCount","1");
            bg_circle_anim.setAttribute("begin","pytplir_btn.click");
            bg_circle_anim.setAttribute("end","pytplir_btn.click");
            bg_circle.setAttribute("cx","20");
            bg_circle.setAttribute("cy","20");
            bg_circle.setAttribute("r","20");
            bg_circle.setAttribute("fill",circleColor);
            bg_circle.setAttribute("fill-opacity","0");
            bg_circle.appendChild(bg_circle_anim);
            arrow_up.setAttribute("points","17,19 17,17 13,17 20,11 27,17 23,17 23,19");
            arrow_down.setAttribute("points","17,21 17,23 13,23 20,29 27,23 23,23 23,21");

            btn_svg.setAttribute("viewbox","0 0 40 40");
            btn_svg.setAttribute("xmlns","http://www.w3.org/2000/svg");
            btn_svg.setAttribute("width","40");
            btn_svg.setAttribute("height","40");
            btn_svg.setAttribute("style","cursor: pointer; margin-left: 8px;");
            btn_svg.setAttribute("id","pytplir_btn");
            btn_svg.addEventListener("click",onButtonClick);
            btn_svg.appendChild(bg_circle);
            btn_svg.appendChild(arrow_up);
            btn_svg.appendChild(arrow_down);

            tt_rect.setAttribute("x","0");
            tt_rect.setAttribute("y","0");
            tt_rect.setAttribute("rx","2");
            tt_rect.setAttribute("ry","2");
            tt_rect.setAttribute("width","110");
            tt_rect.setAttribute("height","34");
            tt_rect.setAttribute("fill",ttBGColor);
            tt_rect.setAttribute("fill-opacity","0.9");

            tt_text.setAttribute("x","8");
            tt_text.setAttribute("y","22");
            tt_text.setAttribute("font-family","Roboto, Noto, sans-serif");
            tt_text.setAttribute("font-size","13px");
            tt_text.setAttribute("fill",ttTextColor);
            tt_text.setAttribute("style","user-select:none;");
            tt_text.innerHTML = "Autoplay order";

            tt_svg_fadein.setAttribute("attributeType","CSS");
            tt_svg_fadein.setAttribute("attributeName","opacity");
            tt_svg_fadein.setAttribute("values","0;1");
            tt_svg_fadein.setAttribute("dur","0.1s");
            tt_svg_fadein.setAttribute("restart","always");
            tt_svg_fadein.setAttribute("repeatCount","1");
            tt_svg_fadein.setAttribute("begin","pytplir_btn.mouseenter");
            tt_svg_fadein.setAttribute("end","pytplir_btn.mouseleave");
            tt_svg_fadein.setAttribute("fill","freeze");
            tt_svg_fadeout.setAttribute("attributeType","CSS");
            tt_svg_fadeout.setAttribute("attributeName","opacity");
            tt_svg_fadeout.setAttribute("values","1;0");
            tt_svg_fadeout.setAttribute("dur","0.1s");
            tt_svg_fadeout.setAttribute("restart","always");
            tt_svg_fadeout.setAttribute("repeatCount","1");
            tt_svg_fadeout.setAttribute("begin","pytplir_btn.mouseleave");
            tt_svg_fadeout.setAttribute("end","pytplir_btn.mouseenter");
            tt_svg_fadeout.setAttribute("fill","freeze");
            tt_svg.setAttribute("viewbox","0 0 100 34");
            tt_svg.setAttribute("xmlns","http://www.w3.org/2000/svg");
            tt_svg.setAttribute("width","100");
            tt_svg.setAttribute("height","34");
            var tt_svg_offset = "position:relative; top:50px; left:-80px; z-index:100; opacity:0.0;"
            tt_svg.setAttribute("style","padding-left: 10px; fill:" + ttBGColor + "; " + tt_svg_offset);
            tt_svg.setAttribute("id","pytplir_tt");
            tt_svg.appendChild(tt_rect);
            tt_svg.appendChild(tt_text);
            tt_svg.appendChild(tt_svg_fadein);
            tt_svg.appendChild(tt_svg_fadeout);

            btn_div.setAttribute("id","pytplir_div");
            btn_div.appendChild(btn_svg);
            btn_div.appendChild(tt_svg);

            setTimeout(addButton, 500);
            setTimeout(start, 500);
        }

        function onButtonClick() { // toggle
            playPrevious = !playPrevious;
            setCookie("pytplir_playPrevious",playPrevious);
            updateButtonState();
        }

        function addButton() {
            withQuery(".ytd-playlist-panel-renderer > #top-level-buttons", function(res) {
                if (!document.getElementById("pytplir_btn")) {
                    res[0].appendChild(btn_div);
                    updateButtonState();
                }
            });
        }

        function updateButtonState() {
            if (playPrevious) { // play previous video
                arrow_up.setAttribute("style","fill:"+activeColor);
                arrow_down.setAttribute("style","fill:"+inactiveColor);
            } else { // play next video
                arrow_up.setAttribute("style","fill:"+inactiveColor);
                arrow_down.setAttribute("style","fill:"+activeColor);
            }
        }

        function removeButton() {
            btn_svg.parentNode.removeChild(btn_svg);
        }

        function start() {
            withQuery(".html5-main-video", function(res) {
                player = res[0];
                player.addEventListener("timeupdate",checkTime);
            });
        }

        function withQuery(query, onSuccess) {
            var res = $(query);
            if (res) {
                onSuccess(res);
                return res;
            } else { // not loaded yet => retry
                setTimeout(function(){withQuery(query);}, 100);
            }
        }

        function checkTime() {
            var timeLeft = player.duration - player.currentTime;
            var shuffle = strToBool($("[aria-label='Shuffle playlist']")[0].attributes["aria-pressed"].nodeValue);
            if (playPrevious && timeLeft < 1.3 && !redirectFlag && !player.hasAttribute("loop") && !shuffle) {
                redirectFlag = true;
                redirect();
                setTimeout(function() {redirectFlag = false;}, 1000);
            }
        }

        function redirect() {
            var previousURL = getPreviousURL();
            if (previousURL) {
                document.location.href = previousURL;
            } else { // probably at start of playlist
                player.pause(); // prevent next video from loading
            }
        }

        function getPreviousURL(){
            var query = document.querySelectorAll("#playlist-items");
            var index;
            var previousURL;
            for (const a of Array.from(query).entries()) { // a = {index, element}
                if (a[1].textContent.includes("▶")) { // Current video's position in playlist
                    index = a[0]; // index in query
                    if (index == 0) {break;} // start of list
                    previousURL = query[index-1].children[0].href;
                    return previousURL;
                }
            }
        }

        function strToBool(str) {
            return str.toLowerCase() == "true" ? true : false;
        }

        // adapted from https://www.w3schools.com/js/js_cookies.asp
        function setCookie(cname, cvalue) {
            document.cookie = cname + "=" + cvalue + ";sameSite=lax;path=www.youtube.com/watch";
        }

        function getCookie(cname) {
            var name = cname + "=";
            var decodedCookie = decodeURIComponent(document.cookie);
            var ca = decodedCookie.split(';');
            for(var i = 0; i <ca.length; i++) {
                var c = ca[i];
                while (c.charAt(0) == ' ') {
                c = c.substring(1);
                }
                if (c.indexOf(name) == 0) {
                    var x = c.substring(name.length, c.length);
                    return strToBool(x);
                }
            }
            return "";
        }
    });
})();

QingJ © 2025

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