Skip to content

Instantly share code, notes, and snippets.

@CHFR-wide
Last active October 29, 2024 14:55
Show Gist options
  • Save CHFR-wide/c47d240e46112333145510cc73607429 to your computer and use it in GitHub Desktop.
Save CHFR-wide/c47d240e46112333145510cc73607429 to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name Youtube seek undo
// @namespace https://gist.github.com/CHFR-wide/
// @version 0.5
// @description Adds undo and redo when watching a video
// @author CHFR
// @match *://www.youtube.com/*
// @grant none
// @run-at document-idle
// @downloadURL https://gist.github.com/CHFR-wide/c47d240e46112333145510cc73607429/raw
// @updateURL https://gist.github.com/CHFR-wide/c47d240e46112333145510cc73607429/raw
// ==/UserScript==
(function() {
'use strict';
let videoElem = null;
let commentInputElem = null;
let cache = 0.0;
let cache_prev = null;
let undoStack = [];
let redoStack = [];
let isProgrammaticSeek = false;
const observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
// logDebug(mutation);
if (mutation.type === "attributes" && mutation.attributeName === "src") {
logDebug('Changed video, clearing hisstory');
undoStack = [];
redoStack = [];
cache = 0.0;
cache_prev = null;
}
});
});
waitForElm('#movie_player video').then((elm) => {
videoElem = elm;
logDebug('found video element', elm);
videoElem.addEventListener(
"seeking",
function (ev) {
if(isProgrammaticSeek) {
isProgrammaticSeek = false;
return;
}
cache_prev = cache;
cache = videoElem.currentTime;
if (cache_prev !== null) {
undoStack.push(cache_prev);
}
redoStack = [];
debugInfos();
});
videoElem.addEventListener("timeupdate", () => {
cache_prev = cache; //# save last known value
cache = videoElem.currentTime; //# before updating to new currentTime
});
document.addEventListener('keydown', handleKey);
observer.observe(elm, {
attributes: true //configure it to listen to attribute changes
});
})
waitForElm('#contenteditable-root').then((elm) => {
logDebug("Comments have been loaded");
commentInputElem = elm;
});
function undo() {
if (undoStack.length === 0) {
logDebug('cannot undo');
return
};
logDebug('undoing');
redoStack.push(videoElem.currentTime);
isProgrammaticSeek = true;
videoElem.currentTime = undoStack.pop();
cache_prev = cache;
cache = videoElem.currentTime;
debugInfos();
}
function redo() {
if (redoStack.length === 0) {
logDebug('cannot redo');
return;
};
logDebug('redoing');
undoStack.push(videoElem.currentTime);
isProgrammaticSeek = true;
videoElem.currentTime = redoStack.pop();
cache_prev = cache;
cache = videoElem.currentTime;
debugInfos();
}
function handleKey(e) {
if (document.activeElement.contains(commentInputElem)) {
logDebug("Comment element is focused, ignoring undo/redo inputs");
return;
}
if (e.key === 'z' && e.ctrlKey && !e.shiftKey) {
undo();
}
else if (e.key.toLowerCase() === 'z' && e.ctrlKey && e.shiftKey) {
redo();
}
}
function logDebug(...message) {
console.log("CH-DBG: ", ...message);
return;
}
function debugInfos() {
logDebug(JSON.stringify({
undoStack,
redoStack,
cache, cache_prev
}, null, 2));
}
// Thank you https://stackoverflow.com/questions/5525071/how-to-wait-until-an-element-exists
function waitForElm(selector) {
return new Promise(resolve => {
if (document.querySelector(selector)) {
return resolve(document.querySelector(selector));
}
const observer = new MutationObserver(mutations => {
if (document.querySelector(selector)) {
observer.disconnect();
resolve(document.querySelector(selector));
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
});
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment