NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==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();
}
}