ita84aa / YouTube Annotation Markers

// ==UserScript==
// @name        YouTube Annotation Markers
// @namespace   http://www.example.com
// @description Marks where annotations are on the progress bar of the HTML5 YouTube player
// @include     https://www.youtube.com/watch*
// @version     0.1
// @grant       none
// ==/UserScript==

// TODO: disable markers if annotations are disabled

// Skip frames
if (window.top == window.self) {

	// Converts HH:MM:SS or MM:SS string to seconds
	function getSecs(timeStr) {
		if (timeStr != null) {
			var time = timeStr.split(':');
			if (time.length == 3) {
				return (+time[0]) * 60 * 60 + (+time[1]) * 60 + (+time[2]);
			}
			else if (time.length == 2) {
				return (+time[0]) * 60 + (+time[1]);
			}
		}
		return null;
	}

	// Get video ID
	var videoId = null;
	if (window.location.href.indexOf("v=") > 0) {
		var args = window.location.search.substr(1).split("&");
		for (var i = 0; i < args.length; i++) {
			var arg = args[i].split("=");
			if (arg[0] == "v") {
				videoId = arg[1];
				break;
			}
		}
	}

	if (videoId != null) {
		// constants
		var lengthClass = "ytp-time-duration";
		var progressBarClass = "ytp-progress-list";
		var annotationXmlTag = "annotation";
		var annotationRectTag = "rectRegion";
		var annotationStartTimeTag = "t";
		var playProgressClass = "ytp-play-progress"; // annotation marker style is based on the YouTube play progress bar
		var annotationMarkerWidth = "0.005";
		var annotationMarkerColor = "#FFE600";

		// Get video length
		var duration = document.getElementsByClassName(lengthClass)[0].innerHTML;

		// Download and parse annotations XML
		var annotationsXmlRequest = new XMLHttpRequest();
		var parseXml;
		if (window.DOMParser) {
			parseXml = function(xmlStr) {
				return ( new window.DOMParser() ).parseFromString(xmlStr, "text/xml");
			};
		}
		else if (typeof window.ActiveXObject != "undefined" && new window.ActiveXObject("Microsoft.XMLDOM")) {
			parseXml = function(xmlStr) {
				var xmlDoc = new window.ActiveXObject("Microsoft.XMLDOM");
				xmlDoc.async = "false";
				xmlDoc.loadXML(xmlStr);
				return xmlDoc;
			};
		}
		else {
			parseXml = function() {
				return null;
			};
		}

		annotationsXmlRequest.onreadystatechange = function() {
			if (annotationsXmlRequest.readyState == 4 && annotationsXmlRequest.status == 200 && annotationsXmlRequest.responseText) {
				annotationsXml = parseXml(annotationsXmlRequest.responseText);
				if (annotationsXml) {
					// Get annotation times
					var annotationsArray = [];
					annotationNodelist = annotationsXml.getElementsByTagName(annotationXmlTag);
					for (var i = 0; i < annotationNodelist.length; i++) {
						var annotationAttrs = annotationNodelist[i].getElementsByTagName(annotationRectTag)[0].attributes;
						annotationsArray[annotationsArray.length] = annotationAttrs.getNamedItem(annotationStartTimeTag).value;
					}
					var durationSecs = getSecs(duration);
					if (durationSecs != null) {
						var progressBar = document.getElementsByClassName(progressBarClass)[0];
						if (progressBar) {
							for (var j = 0; j < annotationsArray.length; j++) {
								var annotationSecs = getSecs(annotationsArray[j]);
								if (annotationSecs != null) {
									// Add marker to progress bar
									var annotationMarker = document.createElement("div");
									annotationMarker.classList.add(playProgressClass);
									annotationMarker.style = "left: " + (annotationSecs / durationSecs) * 100 + "%; transform: scaleX(" + annotationMarkerWidth + "); background: none repeat scroll 0% 0% " + annotationMarkerColor + ";";
									progressBar.appendChild(annotationMarker);
								}
							}
						}
					}
				}
			}
		}

		annotationsXmlRequest.open("GET", "annotations_invideo?video_id=" + videoId, true);
		annotationsXmlRequest.send();
	}
}