YouTube Animated Thumbnails & Preview Videos

On hover, each thumbnail cycles though its 3 thumbnails. Additional feature to view those 3 thumbnails all at once (non-animated)

  1. // ==UserScript==
  2. // @name YouTube Animated Thumbnails & Preview Videos
  3. // @namespace http://userscripts.org/users/23652
  4. // @description On hover, each thumbnail cycles though its 3 thumbnails. Additional feature to view those 3 thumbnails all at once (non-animated)
  5. // @include http://*.youtube.com/*
  6. // @include http://youtube.com/*
  7. // @include https://*.youtube.com/*
  8. // @include https://youtube.com/*
  9. // @exclude http://*youtube.com/my_videos_edit*
  10. // @exclude http://*youtube.com/my_subscribers*
  11. // @exclude https://*youtube.com/my_videos_edit*
  12. // @exclude https://*youtube.com/my_subscribers*
  13. // @copyright JoeSimmons
  14. // @version 1.1.0
  15. // @license GPL version 3 or any later version; http://www.gnu.org/copyleft/gpl.html
  16. // @require https://greasyfork.org/scripts/1884-gm-config/code/GM_config.js?version=4836
  17. // @require https://greasyfork.org/scripts/1885-joesimmons-library/code/JoeSimmons'%20Library.js?version=4838
  18. // @require https://greasyfork.org/scripts/2817-jsl-ajax-plugin/code/JSL%20-%20AJAX%20plugin.js?version=7911
  19. // @grant GM_getValue
  20. // @grant GM_registerMenuCommand
  21. // @grant GM_setValue
  22. // @grant GM_xmlhttpRequest
  23. // ==/UserScript==
  24.  
  25.  
  26. /* CHANGELOG -------------------------------------------------------------------------------------------
  27.  
  28. 1.1.0 (4/8/2014)
  29. - fixed thumbnail previews sometimes not displaying the right video's thumbnail
  30.  
  31. 1.0.94 (2/25/2014)
  32. - fixed problem with thumbnails appearing under the masthead
  33.  
  34. 1.0.93 (11/18/2013)
  35. - removed fading, too inconsistent & laggy
  36.  
  37. 1.0.92 (10/8/2013)
  38. - re-write of the entire script for readability and efficiency
  39. - added some minor fade effects
  40. - instead of making the video black when hovering over a thumbnail,
  41. the script will now keep showing the video
  42. - previews open in the opposite corner of where your mouse is
  43. - more reliable HQ image getting
  44. - only the hovered over thumbnail is animated, not all the images now
  45.  
  46. 1.0.91
  47. - Added a new option that allows animated thumbnails only on hovered images
  48.  
  49. 1.0.90
  50. - Moved the options button to inside the YouTube dropdown menu
  51.  
  52. 1.0.89
  53. - Added compatibility for Opera & Chrome
  54.  
  55. ------------------------------------------------------------------------------------------------------ */
  56.  
  57.  
  58. (function () {
  59. 'use strict';
  60.  
  61. var rVi = /[a-z0-9-]+\.ytimg\.com\/vi\/([^\/]+)\//i,
  62. rYtimgUrl = /[a-z0-9-]+\.ytimg\.com\/vi\//i,
  63. rWhichImage = /(vi\/[a-z0-9-_]+\/)(1|3|mqdefault)(\.jpg)/i,
  64. x = 0, y = 0,
  65. isHoverEnabled, thumbSize, isAnimationEnabled, animationSpeed,
  66. menulist, __preview_timeout, __animation_interval, stillOnThumb;
  67.  
  68. var preview = {
  69. // clears the timeout and hides the hover preview
  70. hide : function () {
  71. window.clearInterval(__preview_timeout);
  72. JSL('#hover_img').hide().find('img').attribute('src', '');
  73. },
  74. show : function () {
  75. // sets the timeout to show the hover preview
  76. __preview_timeout = window.setTimeout(function () {
  77. JSL('#hover_img').show('block');
  78. }, 400);
  79. }
  80. };
  81.  
  82. function getNextImage(currentImage) {
  83. var imagesInOrder = ['1', 'mqdefault', '3'],
  84. indexOfNextImage = imagesInOrder.indexOf(currentImage) + 1;
  85.  
  86. if ( indexOfNextImage > (imagesInOrder.length - 1) ) {
  87. // if it's on the last one, go back to the first
  88. return imagesInOrder[0];
  89. } else {
  90. // if it's not on the last one, show the next one
  91. return imagesInOrder[indexOfNextImage];
  92. }
  93. }
  94.  
  95. function handleHoverLogic(urlIdPrefix) {
  96. var box = JSL('#hover_img'),
  97. hori, vert;
  98.  
  99. preview.hide();
  100.  
  101. // set the previews for this thumbnail
  102. JSL('#hover_img_1').attribute('src', urlIdPrefix + '1.jpg');
  103. JSL('#hover_img_2').attribute('src', urlIdPrefix + 'mqdefault.jpg');
  104. JSL('#hover_img_3').attribute('src', urlIdPrefix + '3.jpg');
  105.  
  106. // check for a higher quality preview while we
  107. // wait for the image to display
  108. JSL.ajax([
  109. urlIdPrefix + 'maxresdefault.jpg',
  110. urlIdPrefix + 'hqdefault.jpg'
  111. ], {
  112. method : 'HEAD',
  113. onload : function (resp) {
  114. var img2 = JSL('#hover_img_2');
  115.  
  116. // check for a 200 (OK) status
  117. // & that we're showing the correct thumbnail
  118. if ( resp.status === 200 && resp.url.getMatch(rVi, 1) === img2.attribute('src').getMatch(rVi, 1) ) {
  119. img2.attribute('src', resp.url);
  120. JSL.ajaxClear();
  121. }
  122. }
  123. });
  124.  
  125. vert = !(y < (window.innerHeight / 2) ) ? 'top' : 'bottom'; // should the preview be on the top or bottom
  126. hori = !(x < ( (window.innerWidth - 15) / 2) ) ? 'left' : 'right'; // should the preview be on the left or right
  127.  
  128. // set the vertical align style property of each of the 3 preview images
  129. JSL('#hover_img img').css('vertical-align', vert);
  130.  
  131. // reset the position of the hover box
  132. box.css('top', 'auto').css('right', 'auto').css('bottom', 'auto').css('left', 'auto');
  133.  
  134. // set the corner it will appear in
  135. box.css(vert, '0').css(hori, '0');
  136.  
  137. // set a delay for the previews to show
  138. preview.show();
  139. }
  140.  
  141. function handleAnimationLogic(elem) {
  142. __animation_interval = JSL.setInterval(function () {
  143. var currentImage = elem.attribute('src').getMatch(rWhichImage, 2),
  144. nextImage = getNextImage(currentImage);
  145.  
  146. elem.attribute( 'src', elem.attribute('src').replace(rWhichImage, '$1' + nextImage + '$3') );
  147. }, animationSpeed);
  148. }
  149.  
  150. function show(event) {
  151. var elem = JSL(event.target),
  152. id = elem.attribute('src').getMatch(rVi, 1),
  153. urlIdPrefix = 'http://' + elem.attribute('src').getMatch(rYtimgUrl) + id + '/';
  154.  
  155. // filter out non-thumbnails
  156. if ( elem.is('img') && elem.isnt('#hover_img, img[id^="hover_img_"]') && urlIdPrefix.getMatch(rVi) ) {
  157. if (isHoverEnabled) {
  158. handleHoverLogic(urlIdPrefix);
  159. }
  160. if (isAnimationEnabled) {
  161. handleAnimationLogic(elem);
  162. }
  163. }
  164. }
  165.  
  166. function hide(event) {
  167. var elem = JSL(event.target),
  168. id = elem.attribute('src').getMatch(rVi, 1),
  169. urlIdPrefix = 'http://' + elem.attribute('src').getMatch(rYtimgUrl) + id + '/';
  170.  
  171. // clear the last HQ thumbnail request
  172. JSL.ajaxClear();
  173.  
  174. // don't hide the preview if the mouse goes over it
  175. if ( elem.isnt('#hover_img, img[id^="hover_img_"]') ) {
  176. preview.hide();
  177. }
  178.  
  179. // stop animating the current thumbnail
  180. if ( elem.is('img') && elem.isnt('#hover_img, img[id^="hover_img_"]') && elem.attribute('src').getMatch(rWhichImage) ) {
  181. JSL.clearInterval(__animation_interval); // stop the animation
  182. elem.attribute( 'src', elem.attribute('src').replace(rWhichImage, '$1mqdefault$3') ); // reset the thumbnail
  183. }
  184. }
  185.  
  186. function trackMouse(event) {
  187. x = event.pageX - window.pageXOffset;
  188. y = event.pageY - window.pageYOffset;
  189. }
  190.  
  191. function GM_config_open() {
  192. GM_config.open();
  193. }
  194.  
  195. // Make sure the page is not in a frame
  196. if (window.self !== window.top) { return; }
  197.  
  198. // String.prototype.getMatch by JoeSimmons
  199. // e.g., 'foobar'.getMatch(/foo(bar)/, 1) ==> 'bar'
  200. Object.defineProperty(String.prototype, 'getMatch', {
  201. value : function (regex, index) {
  202. var match = this.match(regex) || ['', '', '', '', '', '', '', '', '', ''];
  203.  
  204. if (typeof index === 'number' && index > -1) {
  205. return match[index];
  206. }
  207.  
  208. return match[0];
  209. }
  210. });
  211.  
  212. GM_config.init('YouTube Animated Thumbnails Options', {
  213. hoverimages : {
  214. section : ['Hover Options'],
  215. label : 'Enable Hover Images?',
  216. type : 'checkbox',
  217. 'default' : true,
  218. title : 'Hovering over a thumbnail shows 3 preview images.'
  219. },
  220. thumbSize : {
  221. label : 'Hover Thumbnail Size',
  222. type : 'select',
  223. options : {
  224. '90' : 'Small',
  225. '216' : 'Medium',
  226. '432' : 'Large'
  227. },
  228. 'default' : '432',
  229. 'title' : 'Choose the size of the hovering thumbnails'
  230. },
  231. animatedthumbnails : {
  232. section : ['Animated Thumbs Options'],
  233. label : 'Enable Animated Thumbnails?',
  234. type : 'checkbox',
  235. 'default' : true,
  236. title : 'Thumbnails cycle through the 3 images while hovering.'
  237. },
  238. animationspeed : {
  239. label : 'Animation (Cycle) Speed',
  240. type : 'select',
  241. options : {
  242. '1200' : 'Super Slow',
  243. '800' : 'Slow',
  244. '600' : 'Medium',
  245. '400' : 'Fast',
  246. '200' : 'Super Fast'
  247. },
  248. 'default' : '600',
  249. title : 'Set the speed of the cycled images'
  250. }
  251. });
  252.  
  253. JSL.runAt('interactive', function () {
  254. isHoverEnabled = GM_config.get('hoverimages') === true;
  255. thumbSize = GM_config.get('thumbSize');
  256. isAnimationEnabled = GM_config.get('animatedthumbnails') === true;
  257. animationSpeed = parseInt(GM_config.get('animationspeed'), 10);
  258.  
  259. JSL.addStyle('' +
  260. '#hover_img { ' +
  261. 'position: fixed; ' +
  262. 'z-index: 999999999999; ' +
  263. 'background: #000000; ' +
  264. 'border: 1px solid #000000; ' +
  265. 'outline: 1px solid #CCCCCC; ' +
  266. '}' +
  267. '#hover_img img { ' +
  268. 'max-height: ' + thumbSize + 'px; ' +
  269. '}' +
  270.  
  271. // fix for images showing under the youtube player
  272. '#hover_img img { ' +
  273. 'background-color: #000000; ' +
  274. '}' +
  275. '#yt_at_ops { ' +
  276. 'display: inline-block; ' +
  277. 'padding: 6px; ' +
  278. '}' +
  279. '#GM_config { ' +
  280. 'z-index: 999999999999 !important; ' + // 999,999,999,999
  281. '}' +
  282. '');
  283.  
  284. JSL(document.body).append('' +
  285. '<div id="hover_img" style="display: none;">' +
  286. '<img src="" alt="" id="hover_img_1" />' +
  287. '<img src="" alt="" id="hover_img_2" />' +
  288. '<img src="" alt="" id="hover_img_3" />' +
  289. '</div>' +
  290. '');
  291. // Add a user script command for the options menu
  292. if (typeof GM_registerMenuCommand === 'function') {
  293. GM_registerMenuCommand('YouTube Animated Thumbnails Options', GM_config.open);
  294. }
  295.  
  296. // Add an options button to the page
  297. JSL('#masthead-expanded-menu-list, #footer-main').append('' +
  298. '<li id="yt_at_ops" class="masthead-expanded-menu-item">' +
  299. '<a href="javascript: void(0);" class="yt-uix-sessionlink">Animated Thumbnails Options</a>' +
  300. '</li>' +
  301. '');
  302. JSL('#yt_at_ops a').addEvent('click', GM_config.open);
  303.  
  304. if (isHoverEnabled) {
  305. JSL.addEvent(window, 'mousemove', trackMouse);
  306. JSL.addEvent(window, 'click', preview.hide);
  307. }
  308. JSL.addEvent(window, 'mouseover', show);
  309. JSL.addEvent(window, 'mouseout', hide);
  310. });
  311.  
  312. })();