Greasy Fork is available in English.

Title Youtube Locations

Puts the video title in the location bar of all YouTube video pages. Now with extra features add scrollbars, animate thumbnails, reduce font sizes and un-float the header.

// ==UserScript==
// @name           Title Youtube Locations
// @namespace      TYTLs
// @description    Puts the video title in the location bar of all YouTube video pages.  Now with extra features add scrollbars, animate thumbnails, reduce font sizes and un-float the header.
// @version        1.3.0
// @downstreamURL  http://userscripts.org/scripts/source/87416.user.js
// @include        http://*.youtube.*/*
// @include        http://youtube.*/*
// @include        https://*.youtube.*/*
// @include        https://youtube.*/*
//// YouTube thumbnails almost certainly appear if you do a video search:
// @include        https://www.google.co.uk/search?*&tbm=vid&*
//// YouTube thumbnails occasionally appear in normal Google search results:
// @include        https://www.google.co.uk/search*
// @grant          GM_addStyle
// @grant          GM_log
// ==/UserScript==

// TODO: In case for some reason YouTube or another script redirects us to
// another URL which has lost the title param we added, we will re-run forever.
// To avoid this we should have a fallback.  E.g. if the title we want to set
// is the same as the last one we did set (via GM_set/getValue) then do not try
// again.
// This has never happened so far.  :)

// If you are interested in YouTube images:
// Thumbnails url looks like http://img.youtube.com/vi/8aYQ_wjmriQ/2.jpg
// Youtube generates 4 thumbnails: 0.jpg at 320x240, and 1.jpg, 2.jpg, 3.jpg
// Also large: hqdefault.jpg (480x360) and sometimes but not always hq1.jpg, hq2.jpg, hq3.jpg



var addTitleToLocation = false; // Oh dear, the primary feature of this plugin has started causing annoying reloads.  TODO Perhaps we can do it with pushState instead...
var reduceFontSizes    = true;
var addScrollbars      = true;
var scrollDownToVideo  = false; // YouTube's header ("masthead") is now floating.  Setting this true will un-float it, then scroll down to hide it.  But scrolling containers should then be enlarged.
var animateThumbnails  = true;
var unfloatTheHeader   = true;



if (addTitleToLocation) {

	// NOTE: This code is deprecated, as I now use the userscripts URLs_Need_Titles

	setTimeout(function(){
		if (document.location.pathname == "/watch") {

			var title = document.title.replace(/ - YouTube$/,'')
				|| null;

			if (title)
				title = title.replace(/ /g,'_').replace(/^[\r\n_]*/,'').replace(/[\r\n_]*$/,''); // "_"s paste better into IRC, since " "s become "%20"s which are hard to read.  The second and third parts trim "_"s and newlines from the start and end of the string.

			if (title) {
				if (!document.location.hash) {
					document.location.replace(document.location.href + '#' + title); // Does not alter browser history
					// document.location.hash = title; // Crashes Chrome less often
				}
			}
		}
	},5000); // This is what really stops the crashing!

}



if (reduceFontSizes) {
	// == Reduce font size of thumbnail titles ==
	GM_addStyle(".yt-tile-default.video-list-item a .title, #watch-sidebar .video-list-item .title { font-size: 11px; line-height: 10px; }");
	// Defaults are font-size: 13px; and line-height: 15px; which show only two lines in my browser.
}



if (addScrollbars) {

	// == Scrollbars on comments and related vids, to keep the video in view. ==
	setTimeout(function(){
		// We could alternatively act on watch-panel but that includes the video navigation buttons!
		// BUG: Interferes with YouTube's lazy-loading of thumbnails for related video.
		if (document.location.href.indexOf("/all_comments?") >= 0) {
			return;   // Leave the full comments page alone.
		}

		// The top of watch7-content has become very large, so put a scrollbar on the whole lot.
		var watchDiscussion = /* document.getElementById("watch-discussion") || */ document.getElementById("watch7-content");
		if (watchDiscussion) {
			var toSubtract = 464;     // Small video screen
			//var toSubtract = 544;   // Medium size video screen
			var roomForComments = window.innerHeight - toSubtract;
			if (roomForComments < 200) {
				roomForComments = 200;
			}
			watchDiscussion.style.overflow = "auto";
			watchDiscussion.style.maxHeight = roomForComments+"px"; /* For a video height 360p */
			GM_addStyle(" #watch7-content { border: 1px solid; border-color: #c8c8c8 #dddddd #dddddd #c8c8c8; margin-top: 5px; } #watch-header { margin-top: 0px; margin-bottom: 0px; } ");
		}

		var watchSidebar = document.getElementById("watch-sidebar") || document.getElementById("watch7-sidebar");
		if (watchSidebar) {
			watchSidebar.style.overflow = "auto";
			watchSidebar.style.maxHeight = (window.innerHeight - 69)+"px";
			// May 2012 - fixes below no longer needed.
			// Now the text wraps because of the scrollbar, so we widen the element:
			// watchSidebar.style.width = (320+24)+"px";
			// watchSidebar.style.width = '300px';
			// And we must widen its container also:
			// TODO BUG: Why does this work in the console, but not from the userscript?
			// document.getElementById("watch-main").style.width = (960+24)+"px";
			GM_addStyle(" #watch-sidebar, #watch7-sidebar { border: 1px solid; border-color: #c8c8c8 #dddddd #dddddd #c8c8c8; } ");
			// Without this, the Google notifications dropdown will appear behind our sidebar.
			// But with it, the sidebar is not clickable!  :-(
			//GM_addStyle(" #watch-sidebar, #watch7-sidebar { z-index: -20; } ");
		}

		if (scrollDownToVideo) {
			// Un-float the header:
			GM_addStyle(" #masthead-positioner { position: initial; } #masthead-positioner-height-offset { height: 0px; } ");
			//// Title text
			// document.getElementById("eow-title").scrollIntoView();
			//// Uploader info and videolist popdown.
			// document.getElementById("watch-headline-user-info").scrollIntoView();
			//// The author's video list (was supposed to be a small gap above the video when collapsed, but it's not)
			// document.getElementById("watch-more-from-user").scrollIntoView();
			//// The video
			// document.getElementsByTagName("embed")[0].scrollIntoView();
			//// The video
			var watchVideo = document.getElementById("movie_player") || document.getElementById("player-api");
			if (watchVideo) {
				watchVideo.scrollIntoView();
			}
			//// Slight gap above the video (I prefer that)
			// var watchContainer = document.getElementById("watch7-container");
			// if (watchContainer) {
			// 	watchContainer.scrollIntoView();
			// }
		}

		// ~ October 2014
		// This feels like a bug in Chrome 38.  The element is given position:absolute but it does not move up when its parent does!  We can fix it anyway:
		GM_addStyle(" #watch8-secondary-actions { position: initial; } ");
	},1000);

}



if (animateThumbnails) {

	// == Thumbnail animation ==
	// TODO: This is working fine on "related videos" thumbnails, but not on queue
	// thumbnails, even if I have the queue open when I load the page.
	// Perhaps we are responding to a mouseout event from a child element, because
	// we are not checking the event target like we should do.
	function initThumbnailAnimator() {
		// function createThumbnailAnimatorEvent(thumbImage) {
		var filenameRE = /\/([^/]*)\.(jpg|webp)(\?.*|$)/;
		var thumbImage    = null;
		var originalHref  = null;
		var timer  = null;
		//var frames = ["1.jpg","2.jpg","3.jpg"];   // "default.jpg",
		var frames = ["1","2","3"];
		var frameI = 0;
		function changeFrame() {
			frameI = (frameI + 1) % frames.length;
			var match = originalHref.match(filenameRE);
			var extension = match[2];
			var filename = frames[frameI] + '.' + extension;
			thumbImage.src = originalHref.replace(filenameRE, '/' + filename);
		}
		function startAnimation() {
			// Because there was a bug that the running animation would not stop!
			if (timer) {
				clearInterval(timer);
			}
			originalHref = thumbImage.src;
			if (originalHref.match(/^data:/)) {
				return;
			}
			// We make this check quite late, due to lazy loading
			if (originalHref.match(filenameRE)) {
				// logElem("Starting animation",thumbImage);
				timer = setInterval(changeFrame,600);
			}
		}
		function stopAnimation() {
			if (timer) {
				// logElem("Stopping animation",thumbImage);
				clearInterval(timer);
				timer = null;
				// This isn't really neccessary, except to ensure the check for default\.jpg above works next time!
				//thumbImage.src = thumbImage.src.replace(/\/[^/]*$/,'') + '/' + "default.jpg";
				thumbImage.src = originalHref;
			}
		}
		function logElem(name,elem) {
			report = "<"+elem.tagName+" id="+elem.id+" class="+elem.className+" src="+elem.src+" />";
			GM_log(name+" = "+report);
		}
		function check(fn) {
			return function(evt) {
				// logElem("["+evt.type+"] evt.target",evt.target);
				var elemToCheck = evt.target || evt.srcElement;
				if (elemToCheck.tagName == "IMG") {
					thumbImage = elemToCheck;
					return fn();
				} else if (elemToCheck.className=='screen') {
					var seekImg = elemToCheck.parentNode.getElementsByTagName("img")[0];
					if (seekImg) {
						thumbImage = seekImg;
						return fn();
					}
				// } else {
					// var imgCount = elemToCheck.getElementsByTagName("img").length;
					// if (imgCount == 1) {
						// thumbImage = elemToCheck.getElementsByTagName("img")[0];
						// // logElem("["+evt.type+"] checking sub-image",thumbImage);
						// logElem("Whilst checking",elemToCheck);
						// logElem("  Animating elem",thumbImage);
						// logElem("  with parent",thumbImage.parentNode);
						// logElem("  whilst currentTarget",evt.currentTarget);
						// logElem("  and srcElement",evt.srcElement);
						// return fn();
					// }
				}
			};
		}
		//// Unfortunately these do not fire on any HTMLImageElements when browsing the queue.
		document.body.addEventListener("mouseover",check(startAnimation),false);
		document.body.addEventListener("mouseout",check(stopAnimation),false);
		// var videoList = document.getElementById("watch-sidebar"); // or watch-module or watch-module-body or watch-related or watch-more-related
		// var videoList = document.getElementsByClassName("video-list")[0]; // can be 4 of these!
		// var thumbs = document.getElementsByTagName("img");
		// for (var i=0;i<thumbs.length;i++) {
			// createThumbnailAnimatorEvent(thumbs[i]);
		// }
	}
	setTimeout(initThumbnailAnimator,1000);

}



if (unfloatTheHeader) {
	GM_addStyle("#masthead-positioner { position: static; }");
	var gapElement = document.getElementById("masthead-positioner-height-offset");
	if (gapElement) {
		gapElement.parentNode.removeChild(gapElement);
	}
}