Skip to content

Instantly share code, notes, and snippets.

@vogler
Last active June 12, 2023 13:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vogler/451aa48d0af7b659e391fdbeeea0d9d8 to your computer and use it in GitHub Desktop.
Save vogler/451aa48d0af7b659e391fdbeeea0d9d8 to your computer and use it in GitHub Desktop.
Tampermonkey: YouTube: show time left in title
// ==UserScript==
// @name YouTube: show time left in title
// @namespace https://gist.github.com/vogler
// @downloadURL https://gist.github.com/vogler/451aa48d0af7b659e391fdbeeea0d9d8/raw/youtube-time-left.tamper.js
// @version 0.5
// @description YouTube: show time left in title
// @author Ralf Vogler
// @ match https://www.youtube.com/watch?v=* // this will not work if you open youtube.com and then click on a video since it is a SPA
// @match https://www.youtube.com/*
// @grant window.onurlchange
// ==/UserScript==
// options
const opt = {
onLoad: true, // false: only update title after video starts playing
position: 'start', // add to 'start' | 'end' of document.title
withRate: true, // true: divide time left by playback speed, false: ignore playback speed
}
const formatDuration = seconds => new Date(1000 * seconds).toISOString().substr(11, 8).replace(/^[0:]+/, "");
const parseDuration = str => str.split(':').toReversed().reduce((a,x,i) => a + parseInt(x) * 60**i, 0); // to seconds
(async function() {
'use strict';
// console.log('title onload:', document.title);
let originalTitle = document.title;
// window.addEventListener('urlchange', e => { // update originalTitle after urlchange
// // console.log('urlchange', e.url, document.title);
// new MutationObserver((m, o) => {
// // console.log('title change:', document.title, m);
// originalTitle = document.title;
// o.disconnect(); // TODO seems like this does not work reliably
// }).observe(document.querySelector('title'), { childList: true });
// }, { passive: true });
// new MutationObserver((m, o) => console.log('title change:', document.title, m)).observe(document.querySelector('title'), { childList: true });
// window.addEventListener('focus', e => console.log('window.focus')); // to debug opening page in background tab
// const v = document.querySelector('#movie_player video');
// console.log('video:', v);
// console.log('duration:', v.duration, 'playbackRate:', v.playbackRate);
// duration is not set if page is loaded in background tab, playbackRate may still be 1 if Video Speed Controller extension runs afterwards
// update time left in title
// the surrounding MutationObserver is needed for when navigating from youtube.com to a video instead of opening it in a new tab
(new MutationObserver((mutations, observer) => {
if (document.location.pathname != '/watch') return; // not on a video page
// if (!document.querySelector('#ytd-player')) return; // not enough since setting the title here will revert it to just 'YouTube'
if (!mutations.some(m => m.target.className == 'ytp-large-play-button ytp-button')) return;
// console.log(mutations);
observer.disconnect();
const v = document.querySelector('#movie_player video');
// console.log('video:', v);
const originalDuration = parseDuration(document.querySelector('.ytp-time-duration').innerText);
console.log('youtube-time-left:', 'duration:', v.duration, 'playbackRate:', v.playbackRate, 'originalDuration:', originalDuration);
const update = e => {
if (document.location.pathname != '/watch') return;
let timeLeft = (v.duration || originalDuration) - v.currentTime;
if (opt.withRate) timeLeft /= v.playbackRate;
timeLeft = formatDuration(timeLeft);
const sep = timeLeft ? ' - ' : '';
if (opt.position == 'start') {
originalTitle = document.title.replace(/^(\d{1,2}:)*\d{1,2} - /, ''); // strip timeLeft: simpler and more reliable alternative to urlchange listener + MutationObserver on title
document.title = timeLeft + sep + originalTitle;
} else {
originalTitle = document.title.replace(/ - (\d{1,2}:)*\d{1,2}$/, ''); // same as above, but at the end
document.title = originalTitle + sep + timeLeft;
}
};
if (opt.onLoad) update();
v.addEventListener('timeupdate', update, { passive: true });
})).observe(document, { subtree: true, childList: true }); // .querySelector('#page-manager') was not reliable
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment