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(); } }