// ==UserScript==
// @name Old Seek UI
// @version 0.2
// @description Return the old YouTube video player seek UI, replacing the mobile-style ones they added circa 2021.
// @author Taniko Yamamoto
// @author https://github.com/YukisCoffee/yt-player-classicifier
// @match https://www.youtube.com/*
// @icon https://www.youtube.com/favicon.ico
// @grant none
// @run-at document-start
// @namespace https://greasyfork.org/users/1132181
// ==/UserScript==
(function() {
const ICONSET = {
"back10": "M 18,11 V 7 l -5,5 5,5 v -4 c 3.3,0 6,2.7 6,6 0,3.3 -2.7,6 -6,6 -3.3,0 -6,-2.7 -6,-6 h -2 c 0,4.4 3.6,8 8,8 4.4,0 8,-3.6 8,-8 0,-4.4 -3.6,-8 -8,-8 z M 16.9,22 H 16 V 18.7 L 15,19 v -0.7 l 1.8,-0.6 h .1 V 22 z m 4.3,-1.8 c 0,.3 0,.6 -0.1,.8 l -0.3,.6 c 0,0 -0.3,.3 -0.5,.3 -0.2,0 -0.4,.1 -0.6,.1 -0.2,0 -0.4,0 -0.6,-0.1 -0.2,-0.1 -0.3,-0.2 -0.5,-0.3 -0.2,-0.1 -0.2,-0.3 -0.3,-0.6 -0.1,-0.3 -0.1,-0.5 -0.1,-0.8 v -0.7 c 0,-0.3 0,-0.6 .1,-0.8 l .3,-0.6 c 0,0 .3,-0.3 .5,-0.3 .2,0 .4,-0.1 .6,-0.1 .2,0 .4,0 .6,.1 .2,.1 .3,.2 .5,.3 .2,.1 .2,.3 .3,.6 .1,.3 .1,.5 .1,.8 v .7 z m -0.9,-0.8 v -0.5 c 0,0 -0.1,-0.2 -0.1,-0.3 0,-0.1 -0.1,-0.1 -0.2,-0.2 -0.1,-0.1 -0.2,-0.1 -0.3,-0.1 -0.1,0 -0.2,0 -0.3,.1 l -0.2,.2 c 0,0 -0.1,.2 -0.1,.3 v 2 c 0,0 .1,.2 .1,.3 0,.1 .1,.1 .2,.2 .1,.1 .2,.1 .3,.1 .1,0 .2,0 .3,-0.1 l .2,-0.2 c 0,0 .1,-0.2 .1,-0.3 v -1.5 z",
"forward10": "m 10,19 c 0,4.4 3.6,8 8,8 4.4,0 8,-3.6 8,-8 h -2 c 0,3.3 -2.7,6 -6,6 -3.3,0 -6,-2.7 -6,-6 0,-3.3 2.7,-6 6,-6 v 4 l 5,-5 -5,-5 v 4 c -4.4,0 -8,3.6 -8,8 z m 6.8,3 H 16 V 18.7 L 15,19 v -0.7 l 1.8,-0.6 h .1 V 22 z m 4.3,-1.8 c 0,.3 0,.6 -0.1,.8 l -0.3,.6 c 0,0 -0.3,.3 -0.5,.3 C 20,21.9 19.8,22 19.6,22 19.4,22 19.2,22 19,21.9 18.8,21.8 18.7,21.7 18.5,21.6 18.3,21.5 18.3,21.3 18.2,21 18.1,20.7 18.1,20.5 18.1,20.2 v -0.7 c 0,-0.3 0,-0.6 .1,-0.8 l .3,-0.6 c 0,0 .3,-0.3 .5,-0.3 .2,0 .4,-0.1 .6,-0.1 .2,0 .4,0 .6,.1 .2,.1 .3,.2 .5,.3 .2,.1 .2,.3 .3,.6 .1,.3 .1,.5 .1,.8 v .7 z m -0.8,-0.8 v -0.5 c 0,0 -0.1,-0.2 -0.1,-0.3 0,-0.1 -0.1,-0.1 -0.2,-0.2 -0.1,-0.1 -0.2,-0.1 -0.3,-0.1 -0.1,0 -0.2,0 -0.3,.1 l -0.2,.2 c 0,0 -0.1,.2 -0.1,.3 v 2 c 0,0 .1,.2 .1,.3 0,.1 .1,.1 .2,.2 .1,.1 .2,.1 .3,.1 .1,0 .2,0 .3,-0.1 l .2,-0.2 c 0,0 .1,-0.2 .1,-0.3 v -1.5 z",
"back5": "M 18,11 V 7 l -5,5 5,5 v -4 c 3.3,0 6,2.7 6,6 0,3.3 -2.7,6 -6,6 -3.3,0 -6,-2.7 -6,-6 h -2 c 0,4.4 3.6,8 8,8 4.4,0 8,-3.6 8,-8 0,-4.4 -3.6,-8 -8,-8 z m -1.3,8.9 .2,-2.2 h 2.4 v .7 h -1.7 l -0.1,.9 c 0,0 .1,0 .1,-0.1 0,-0.1 .1,0 .1,-0.1 0,-0.1 .1,0 .2,0 h .2 c .2,0 .4,0 .5,.1 .1,.1 .3,.2 .4,.3 .1,.1 .2,.3 .3,.5 .1,.2 .1,.4 .1,.6 0,.2 0,.4 -0.1,.5 -0.1,.1 -0.1,.3 -0.3,.5 -0.2,.2 -0.3,.2 -0.4,.3 C 18.5,22 18.2,22 18,22 17.8,22 17.6,22 17.5,21.9 17.4,21.8 17.2,21.8 17,21.7 16.8,21.6 16.8,21.5 16.7,21.3 16.6,21.1 16.6,21 16.6,20.8 h .8 c 0,.2 .1,.3 .2,.4 .1,.1 .2,.1 .4,.1 .1,0 .2,0 .3,-0.1 L 18.5,21 c 0,0 .1,-0.2 .1,-0.3 v -0.6 l -0.1,-0.2 -0.2,-0.2 c 0,0 -0.2,-0.1 -0.3,-0.1 h -0.2 c 0,0 -0.1,0 -0.2,.1 -0.1,.1 -0.1,0 -0.1,.1 0,.1 -0.1,.1 -0.1,.1 h -0.7 z",
"forward5": "m 10,19 c 0,4.4 3.6,8 8,8 4.4,0 8,-3.6 8,-8 h -2 c 0,3.3 -2.7,6 -6,6 -3.3,0 -6,-2.7 -6,-6 0,-3.3 2.7,-6 6,-6 v 4 l 5,-5 -5,-5 v 4 c -4.4,0 -8,3.6 -8,8 z m 6.7,.9 .2,-2.2 h 2.4 v .7 h -1.7 l -0.1,.9 c 0,0 .1,0 .1,-0.1 0,-0.1 .1,0 .1,-0.1 0,-0.1 .1,0 .2,0 h .2 c .2,0 .4,0 .5,.1 .1,.1 .3,.2 .4,.3 .1,.1 .2,.3 .3,.5 .1,.2 .1,.4 .1,.6 0,.2 0,.4 -0.1,.5 -0.1,.1 -0.1,.3 -0.3,.5 -0.2,.2 -0.3,.2 -0.5,.3 C 18.3,22 18.1,22 17.9,22 17.7,22 17.5,22 17.4,21.9 17.3,21.8 17.1,21.8 16.9,21.7 16.7,21.6 16.7,21.5 16.6,21.3 16.5,21.1 16.5,21 16.5,20.8 h .8 c 0,.2 .1,.3 .2,.4 .1,.1 .2,.1 .4,.1 .1,0 .2,0 .3,-0.1 L 18.4,21 c 0,0 .1,-0.2 .1,-0.3 v -0.6 l -0.1,-0.2 -0.2,-0.2 c 0,0 -0.2,-0.1 -0.3,-0.1 h -0.2 c 0,0 -0.1,0 -0.2,.1 -0.1,.1 -0.1,0 -0.1,.1 0,.1 -0.1,.1 -0.1,.1 h -0.6 z",
"backchapter": "m 16.436975,17.634454 c -0.573529,0 -1.191177,0.117647 -1.617647,0.441177 v 4.308938 c 0,0.191177 0.214706,0.132123 0.220588,0.132123 0.397059,-0.191176 0.970588,-0.323414 1.397059,-0.323414 0.57353,0 1.191177,0.117646 1.617647,0.441176 0.397059,-0.25 1.117648,-0.441176 1.617647,-0.441176 0.485295,0 0.985294,0.08846 1.397059,0.309053 0.120588,0.06177 0.220589,-0.05623 0.220589,-0.132698 v -4.294002 c -0.438235,-0.329412 -1.067647,-0.441177 -1.617648,-0.441177 -0.573529,0 -1.191176,0.117647 -1.617647,0.441177 -0.42647,-0.32353 -1.044117,-0.441177 -1.617647,-0.441177 z m 3.235294,0.588235 c 0.352942,0 0.705883,0.04411 1.029412,0.147059 v 3.382353 c -0.323529,-0.102941 -0.67647,-0.147059 -1.029412,-0.147059 -0.499999,0 -1.220588,0.191177 -1.617647,0.441177 v -3.382353 c 0.397059,-0.25 1.117648,-0.441177 1.617647,-0.441177 z m -0.674976,1.202322 v 1.303997 l 1.024241,-0.651999 z M 18,7 l -5,5 5,5 v -4 c 3.3,0 6,2.7 6,6 0,3.3 -2.7,6 -6,6 -3.3,0 -6,-2.7 -6,-6 h -2 c 0,4.4 3.6,8 8,8 4.4,0 8,-3.6 8,-8 0,-4.4 -3.6,-8 -8,-8 z",
"forwardchapter": "m 16.436975,17.634454 c -0.573529,0 -1.191177,0.117647 -1.617647,0.441177 v 4.308938 c 0,0.191177 0.214706,0.132123 0.220588,0.132123 0.397059,-0.191176 0.970588,-0.323414 1.397059,-0.323414 0.57353,0 1.191177,0.117646 1.617647,0.441176 0.397059,-0.25 1.117648,-0.441176 1.617647,-0.441176 0.485295,0 0.985294,0.08846 1.397059,0.309053 0.120588,0.06177 0.220589,-0.05623 0.220589,-0.132698 v -4.294002 c -0.438235,-0.329412 -1.067647,-0.441177 -1.617648,-0.441177 -0.573529,0 -1.191176,0.117647 -1.617647,0.441177 -0.42647,-0.32353 -1.044117,-0.441177 -1.617647,-0.441177 z m 3.235294,0.588235 c 0.352942,0 0.705883,0.04411 1.029412,0.147059 v 3.382353 c -0.323529,-0.102941 -0.67647,-0.147059 -1.029412,-0.147059 -0.499999,0 -1.220588,0.191177 -1.617647,0.441177 v -3.382353 c 0.397059,-0.25 1.117648,-0.441177 1.617647,-0.441177 z m -0.674976,1.202322 v 1.303997 l 1.024241,-0.651999 z M 18,7 v 4 c -4.4,0 -8,3.6 -8,8 0,4.4 3.6,8 8,8 4.4,0 8,-3.6 8,-8 h -2 c 0,3.3 -2.7,6 -6,6 -3.3,0 -6,-2.7 -6,-6 0,-3.3 2.7,-6 6,-6 v 4 l 5,-5 z"
};
const DEBUG = false;
// Player API reference
var api;
var bezel;
var animationStartTimer = 0;
var animationShouldEndTimer = 0;
function log(a)
{
if (DEBUG) console.log(a);
}
function getAnimationDuration(elm)
{
var prop = window.getComputedStyle(elm).animationDuration;
switch (true)
{
case "ms" == prop.substr(-2):
return +prop.replace("ms", "");
case "s" == prop.substr(-1):
return +(prop.replace("s", "")) * 1000;
}
}
async function attemptEndAnimateBezel()
{
while (animationShouldEndTimer > Date.now())
{
await new Promise(r => requestAnimationFrame(r));
}
animationStartTimer = 0;
animationShouldEndTimer = 0;
bezel.style.display = "none";
}
function animateBezel()
{
var animationDuration = getAnimationDuration(bezel.querySelector(".ytp-bezel"));
bezel.style.display = "";
if (0 == animationShouldEndTimer)
{
animationStartTimer = Date.now();
animationShouldEndTimer = animationStartTimer;
}
animationShouldEndTimer += animationDuration;
attemptEndAnimateBezel();
}
function waitToAnimate()
{
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, 10);
});
}
function createBezel(direction, duration, text = "", chapter = false)
{
return new Promise(resolve => {
log("Creating bezel");
var bezelElm = api.querySelector(".ytp-bezel");
bezel = bezelElm.parentNode;
bezelElm.removeAttribute("aria-label");
// Get the icon from the iconset
var icon;
if (chapter)
{
icon = ICONSET[direction + "chapter"];
}
else if (ICONSET[direction + duration])
{
icon = ICONSET[direction + duration];
}
else
{
icon = "";
}
var iconElm;
if (iconElm = bezel.querySelector(".ytp-bezel-icon path"))
{
iconElm.setAttribute("d", icon);
}
else
{
bezel.querySelector(".ytp-bezel-icon").insertAdjacentHTML("beforeend",
`<svg height="100%" version="1.1" viewBox="0 0 36 36" width="100%">
<path class="ytp-svg-fill" d="${icon}"></path>
</svg>`
);
}
if ("" === text)
{
bezel.setAttribute("class", "ytp-bezel-text-hide");
}
else
{
bezel.setAttribute("class", "");
bezel.querySelector(".ytp-bezel-text").innerText = text;
}
bezel.style.display = "none";
resolve();
});
}
async function waitForElement(query, timeout = 500)
{
log("Waiting for element " + query + " with timeout in " + timeout + " ms.");
var hasTimedOut = false;
setTimeout(function() {
log("Wait for element " + query + " has timed out.");
hasTimedOut = true;
}, timeout);
while (null == document.querySelector(query) && !hasTimedOut)
{
await new Promise(r => requestAnimationFrame(r));
}
var a;
if (a = document.querySelector(query))
{
return a;
}
else
{
return null;
}
}
function handleSeekGui()
{
log("Handing seek GUI");
var direction = this.dataset.side;
var duration = this.querySelector(".ytp-doubletap-tooltip-label")
.innerText.replace(/[\s|[A-Za-z]]*/g, "")
;
var text = "";
var isChapter = false;
if (this.classList.contains("ytp-chapter-seek"))
{
var textContainer = this.querySelector(".ytp-chapter-seek-text-legacy")
text = textContainer.innerText;
duration = 0;
isChapter = true;
}
createBezel(direction, duration, text, isChapter).then(waitToAnimate).then(animateBezel);
}
async function attemptHookPlayer()
{
log("Attempting to hook player");
var playerApi = await waitForElement(".html5-video-player", 5000);
if (playerApi)
{
log("Player API detected");
api = playerApi;
var doubleTapElm = api.querySelector(".ytp-doubletap-ui-legacy") ?? api.querySelector(".ytp-doubletap-ui") ?? null;
if (doubleTapElm && !api.__oldSeekUi)
{
log("Doubletap detected: installing binding");
(new MutationObserver(handleSeekGui.bind(doubleTapElm)))
.observe(doubleTapElm, {"subtree": true, "childList": true, "characterData": "true"});
api.__oldSeekUi = true;
}
}
}
function insertContinuationEvent()
{
log("Inserting continuation events");
if (window.ytspf && window.ytspf.enabled)
{
log("Inserted spf continuation events");
document.addEventListener("spfdone", attemptHookPlayer);
}
else if (document.querySelector("ytd-app"))
{
log("Inserted kevlar continuation events");
document.addEventListener("yt-page-data-updated", attemptHookPlayer);
}
}
function installInitialStyles()
{
log("Installed initial styles");
document.head.insertAdjacentHTML("beforeend",
`<style>
.ytp-doubletap-ui, .ytp-doubletap-ui-legacy
{
display: none !important;
}
</style>`
);
}
function handleDOMContentLoaded()
{
log("domcontentloaded event fired");
installInitialStyles();
document.removeEventListener("DOMContentLoaded", handleDOMContentLoaded);
}
async function main()
{
log("Old seek ui script loaded");
document.addEventListener("DOMContentLoaded", handleDOMContentLoaded);
// The player needs more time to init
// So wait until a little while after page load to attempt
// hooking the player
window.addEventListener("load", function handleLoad() {
log("load event fired");
attemptHookPlayer();
insertContinuationEvent();
window.removeEventListener("load", handleLoad);
});
}
main();
})();