YouTube Preview

A userscript to play youtube videos by hovering over their thumbnails.

  1. // ==UserScript==
  2. // @name YouTube Preview
  3. // @author sooqua
  4. // @namespace https://github.com/sooqua/
  5. // @version 0.9
  6. // @description A userscript to play youtube videos by hovering over their thumbnails.
  7. // @match *://*.youtube.com/*
  8. // @run-at document-end
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. var APIready = new Promise(function(resolve) {
  13. window.onYouTubeIframeAPIReady = resolve;
  14. });
  15.  
  16. (function() {
  17. 'use strict';
  18.  
  19. function init() {
  20. // requesting api
  21. var scriptTag = document.createElement('script');
  22. scriptTag.src = "https://www.youtube.com/iframe_api";
  23. var firstScriptTag = document.getElementsByTagName('script')[0];
  24. firstScriptTag.parentNode.insertBefore(scriptTag, firstScriptTag);
  25.  
  26. addGlobalStyle(
  27. ".watch-sidebar-section { " +
  28. "z-index: auto !important; " +
  29. "} " +
  30.  
  31. ".yt-lockup-thumbnail:not(.yt-pl-thumb),.thumb-wrapper { " +
  32. "-webkit-transition: all 200ms ease-in !important; " +
  33. "-webkit-transform: scale(1) !important; " +
  34. "-moz-transition: all 200ms ease-in !important; " +
  35. "-moz-transform: scale(1) !important; " +
  36. "transition: all 200ms ease-in !important; " +
  37. "transform: scale(1) !important; " +
  38. "} " +
  39.  
  40. ".yt-lockup-thumbnail:not(.yt-pl-thumb):hover,.thumb-wrapper:hover { " +
  41. "z-index: 9999999999 !important; " +
  42. "box-shadow: 0px 0px 100px #000000 !important; " +
  43. "-webkit-transition: all 200ms ease-in !important; " +
  44. "-webkit-transform: scale(2.0) !important; " +
  45. "-moz-transition: all 200ms ease-in !important; " +
  46. "-moz-transform: scale(2.0) !important; " +
  47. "transition: all 200ms ease-in !important; " +
  48. "transform: scale(2.0) !important; " +
  49. "} " +
  50.  
  51. ".yt-thumb.video-thumb, .yt-uix-simple-thumb-wrap.yt-uix-simple-thumb-related { " +
  52. "background-color: black !important; " +
  53. "} " +
  54.  
  55. ".xspinner { " +
  56. "pointer-events: none; " +
  57. "position: absolute; " +
  58. "top: 0; " +
  59. "right: 0; " +
  60. "bottom: 0; " +
  61. "left: 0; " +
  62. "background: rgba(255,255,255,0.5); " +
  63. "font-size: 14px; " +
  64. "text-align: center; " +
  65. "line-height: 2; " +
  66. "color: rgb(0,0,0); " +
  67. "font-weight: bold; " +
  68. "} ");
  69.  
  70. initOn(document);
  71. var mo = new MutationObserver(function(muts) {
  72. muts.forEach(function(mut) {
  73. [].forEach.call(mut.addedNodes, function(node) {
  74. if (node instanceof HTMLElement) {
  75. initOn(node);
  76. }
  77. });
  78. });
  79. });
  80. mo.observe(document.body, {childList: true, subtree: true});
  81. }
  82.  
  83. function initOn(base) {
  84. [].forEach.call(base.querySelectorAll('.yt-lockup-thumbnail:not(.yt-pl-thumb) a[href^="/watch"], .thumb-wrapper a[href^="/watch"]'), function(thumbnail) {
  85.  
  86. thumbnail.parentNode.addEventListener('mouseover', function() {
  87. if(thumbnail.overlocker) return;
  88. thumbnail.overlocker = new Promise(function (unlock) {
  89. var rect = thumbnail.parentElement.getBoundingClientRect();
  90. var farRight = (rect.right + thumbnail.parentElement.clientWidth/2.0) > window.innerWidth;
  91. var farDown = (rect.bottom + thumbnail.parentElement.clientHeight/2.0) > window.innerHeight;
  92. var farLeft = (rect.left - thumbnail.parentElement.clientWidth/2.0) < 0;
  93. var farUp = (rect.top - thumbnail.parentElement.clientHeight/2.0) < 0;
  94. if(farRight || farDown || farLeft || farUp) {
  95. var transformOrig = (farRight ? 'right ':'') + (farDown ? 'bottom ':'') + (farLeft ? 'left ':'') + (farUp ? 'top ':'');
  96. thumbnail.parentElement.style.webkitTransformOrigin = transformOrig;
  97. thumbnail.parentElement.style.mozTransformOrigin = transformOrig;
  98. thumbnail.parentElement.style.transformOrigin = transformOrig;
  99. }
  100.  
  101. var spinner = document.createElement('div');
  102. spinner.className = 'xspinner';
  103. spinner.textContent = 'Loading...';
  104.  
  105. var childThumb = thumbnail.querySelector('.yt-thumb.video-thumb, .yt-uix-simple-thumb-wrap.yt-uix-simple-thumb-related');
  106. childThumb.appendChild(spinner);
  107.  
  108. thumbnail.watchedContainer = [];
  109. for (var el = thumbnail; el; el = el.parentElement) {
  110. if (el.classList.contains('watched')) {
  111. thumbnail.watchedContainer.push(el);
  112. el.classList.remove('watched');
  113. }
  114. }
  115. thumbnail.watchedBadgeContainer = [];
  116. [].forEach.call(thumbnail.parentNode.querySelectorAll('.watched-badge'), function (watchedBadge) {
  117. thumbnail.watchedBadgeContainer.push(watchedBadge);
  118. watchedBadge.style.display = 'none';
  119. });
  120. thumbnail.imageContainer = [];
  121. [].forEach.call(thumbnail.getElementsByTagName('img'), function(img) {
  122. thumbnail.imageContainer.push(img);
  123. img.style.opacity = 0;
  124. });
  125.  
  126. var vidId = thumbnail.href.split('v=')[1];
  127. thumbnail.PPlayer = new Promise(function (resolve) {
  128. var playerTag = document.createElement('div');
  129. playerTag.id = vidId;
  130. playerTag.style.pointerEvents = 'none';
  131. playerTag.style.position = 'absolute';
  132. childThumb.insertBefore(playerTag, childThumb.firstChild);
  133. APIready.then(function () {
  134. var pplayer = new YT.Player(playerTag.id, {
  135. width: childThumb.offsetWidth,
  136. height: childThumb.offsetHeight,
  137. videoId: vidId,
  138. playerVars: {
  139. 'enablejsapi': 1, 'autoplay': 1, 'showinfo': 0, 'controls': 0,
  140. 'modestbranding': 1, 'ps': 'docs', 'iv_load_policy': 3, 'rel': 0,
  141. 'vq': 'medium'
  142. },
  143. events: {
  144. 'onReady': function() { resolve(pplayer); }
  145. }
  146. });
  147. });
  148. });
  149. thumbnail.PPlayer.then(function () {
  150. childThumb.removeChild(spinner);
  151. unlock();
  152. });
  153. });
  154. });
  155.  
  156. thumbnail.parentNode.addEventListener('mousemove', function(evt) {
  157. if (thumbnail.spinner) return;
  158.  
  159. if(thumbnail.lastX !== evt.screenX) {
  160. var offsetX = evt.offsetX || evt.layerX - thumbnail.offsetLeft;
  161.  
  162. if(thumbnail.PPlayer) {
  163. thumbnail.PPlayer.then(function (PPlayer) {
  164. try {
  165. PPlayer.seekTo(PPlayer.getDuration() * offsetX / thumbnail.parentElement.offsetWidth, true);
  166. } catch (e){}
  167. });
  168. }
  169. }
  170. thumbnail.lastX = evt.screenX;
  171. });
  172.  
  173. thumbnail.parentNode.addEventListener('mouseout', function(evt) {
  174. if(!thumbnail.overlocker) return;
  175. thumbnail.overlocker.then(function () {
  176. if(evt.relatedTarget) {
  177. if (thumbnail.parentElement.contains(evt.relatedTarget)) return;
  178. if (evt.relatedTarget.className.indexOf('yt-uix-tooltip-tip') !== -1) return;
  179. }
  180.  
  181. if(thumbnail.watchedContainer) {
  182. for (var i = 0; i < thumbnail.watchedContainer.length; i++)
  183. thumbnail.watchedContainer[i].classList.add('watched');
  184. thumbnail.watchedContainer = null;
  185. }
  186. if(thumbnail.watchedBadgeContainer) {
  187. for (var i = 0; i < thumbnail.watchedBadgeContainer.length; i++)
  188. thumbnail.watchedBadgeContainer[i].style.display = null;
  189. thumbnail.watchedBadgeContainer = null;
  190. }
  191. if(thumbnail.imageContainer) {
  192. for (var i = 0; i < thumbnail.imageContainer.length; i++)
  193. thumbnail.imageContainer[i].style.opacity = null;
  194. thumbnail.imageContainer = null;
  195. }
  196.  
  197. if(thumbnail.PPlayer) {
  198. thumbnail.PPlayer.then(function(PPlayer) {
  199. if(PPlayer.a.parentNode)
  200. PPlayer.a.parentNode.removeChild(PPlayer.a);
  201. thumbnail.PPlayer = null;
  202. thumbnail.overlocker = null;
  203. });
  204. }
  205. else {
  206. thumbnail.overlocker = null;
  207. }
  208. });
  209. });
  210.  
  211. });
  212. }
  213.  
  214. function addGlobalStyle(css) {
  215. var head = document.getElementsByTagName('head')[0];
  216. if (!head) { return; }
  217. var style = document.createElement('style');
  218. style.type = 'text/css';
  219. style.innerHTML = css;
  220. head.appendChild(style);
  221. }
  222.  
  223. init();
  224. })();