Greasy Fork is available in English.

YouTube mix next/previous buttons

Adds next/previous buttons to seek between tracks in a YouTube mix without having to scroll down.

// ==UserScript==
// @name         YouTube mix next/previous buttons
// @namespace    https://zachsaucier.com/
// @version      0.6
// @description  Adds next/previous buttons to seek between tracks in a YouTube mix without having to scroll down.
// @author       Zach Saucier
// @include      /^https://www.youtube.com/watch
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    function goToNextTrack() {
        var currTimeString = document.querySelector(".ytp-time-current").innerText,
            currTime = parseTimeString(currTimeString);

        for(var i = 0; i < timeLinks.length; i++) {
            var linkTimeString = timeLinks[i].innerText,
                linkTime = parseTimeString(linkTimeString);

            if(isLaterTime(currTime, linkTime)) {
                timeLinks[i].click();
                return;
            }
        }

        timeLinks[0].click();
    }

    function goToPreviousTrack() {
        var currTimeString = document.querySelector(".ytp-time-current").innerText,
            currTime = parseTimeString(currTimeString);

        for(var i = timeLinks.length - 1; i >= 0; i--) {
            var linkTimeString = timeLinks[i].innerText,
                linkTime = parseTimeString(linkTimeString);

            if(isLaterTime(linkTime, currTime)) {
                timeLinks[i].click();
                return;
            }
        }

        timeLinks[timeLinks.length - 1].click();
    }

    function parseTimeString(timeString) {
        var timeArr = timeString.match(/\d+/g);

        var h = 0,
            m = 0,
            s = 0;

        if(timeArr.length === 3) {
            h = parseInt(timeArr[0]);
            m = parseInt(timeArr[1]);
            s = parseInt(timeArr[2]);
        } else if(timeArr.length == 2) {
            m = parseInt(timeArr[0]);
            s = parseInt(timeArr[1]);
        }

        return [h, m, s];
    }

    function isLaterTime(firstTime, secondTime) {
        if(firstTime[0] < secondTime[0]
           || (firstTime[0] === secondTime[0] && firstTime[1] < secondTime[1])
           || (firstTime[0] === secondTime[0] && firstTime[1] === secondTime[1] && firstTime[2] < secondTime[2])
          )
            return true;

        else
            return false;
    }
    
    var description, 
        timeLinks;
    
    var url = window.location.href;
    var videoid = url.match(/(?:https?:\/{2})?(?:w{3}\.)?youtu(?:be)?\.(?:com|be)(?:\/watch\?v=|\/)([^\s&]+)/);
    
    function checkReady() {
        description = document.getElementById("description");
        
        if(description) { 
            timeLinks = description.querySelectorAll("a[href *= '/watch?v=" + videoid[1] + "'");

            // If true, it's (probably) a mix with tracklisting provided
            if(timeLinks.length > 0) {
                // Create the buttons
                var nextButton = document.createElement("button"),
                    prevButton = document.createElement("button"),
                    br = document.createElement("br");

                nextButton.innerText = "Next track";
                prevButton.innerText = "Previous track";
                nextButton.onclick = goToNextTrack;
                prevButton.onclick = goToPreviousTrack;

                nextButton.style = "cursor: pointer; background-color:#444244; color: white; padding: 4px 12px; border: none; margin-right: 5px; margin-bottom: 5px;";
                prevButton.style = "cursor: pointer; background-color:#444244; color: white; padding: 4px 12px; border: none; margin-right: 5px; margin-bottom: 5px;";

                nextButton.title = "Hotkey: u";
                prevButton.title = "Hotkey: i";

                description.insertBefore(br, description.childNodes[0]);
                description.insertBefore(nextButton, description.childNodes[0]);
                description.insertBefore(prevButton, description.childNodes[0]);

                // Create the key listeners
                document.addEventListener("keyup", function(e) {
                    if(e.keyCode === 80
                    && e.srcElement.tagName !== "INPUT"
                    && e.srcElement.tagName !== "TEXTAREA") { // p pressed
                        goToNextTrack();
                    } else if(e.keyCode === 79
                    && e.srcElement.tagName !== "INPUT"
                    && e.srcElement.tagName !== "TEXTAREA") { // o pressed
                        goToPreviousTrack();
                    }
                });
            }
        }
        else {
            setTimeout(checkReady, 100);
        }
    }
    setTimeout(checkReady, 100);

})();