1. // ==UserScript==
  2. // @name FYTE /Fast YouTube Embedded/ Player
  3. // @description Hugely improves load speed of pages with lots of embedded Youtube videos by instantly showing clickable and immediately accessible placeholders, then the thumbnails are loaded in background. HTML5 direct playback (720p max) is used when selected and available.
  4. // @description:ru На порядок ускоряет время загрузки страниц с большим количеством вставленных Youtube-видео. С первого момента загрузки страницы появляются заглушки для видео, которые можно щелкнуть для загрузки плеера, и почти сразу же появляются кавер-картинки с названием видео. В опциях можно включить режим использования упрощенного браузерного плеера (макс. 720p).
  5. // @version 2.4.9
  6. // @include *
  7. // @exclude https://www.youtube.com/*
  8. // @author wOxxOm
  9. // @namespace wOxxOm.scripts
  10. // @license MIT License
  11. // @grant GM_getValue
  12. // @grant GM_setValue
  13. // @grant GM_addStyle
  14. // @grant GM_xmlhttpRequest
  15. // @connect www.youtube.com
  16. // @connect youtube.com
  17. // @run-at document-start
  18. // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACABAMAAAAxEHz4AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAwUExURUxpcQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJuxkb8AAAAPdFJOUwDvH0biMvjOZFW6pxJ6kh7r3iMAAAPDSURBVGje7ZlNaBNBFMeXhNDWpkKgFg9iYlBB6SGwiPQkftaCYATjTaRKiwi1xVaKXpqTpHhosR66p4pQhb209CQGbSweK/TiRYwfFy+NkWBM2pR2nHm73abJzuceRMj/kEzSvl92Z9689+atpjXUUEMN1WgpoRupbH41nTbNUaxzlkIhe0C+M810Ov8zmzL0RGeNeeDThUEkof72N/Fqe/8LJK07sR173yJS0EbEATxFSurZtm9DilqxAV9VAZuWfbPbLBOFqtSBP9f/WxIAV2Bc6H5owiKPG7p+IpFIRG11LsPbEfyVrhvTqeyX1dfmaBiM9gFgjgwrTzJSfncMFq7s3EExJuu5/rHte3hPBvfkff84sbuEBxPkUiLygCC5hDV7CvpUtt81axICZBN9UwHsxYalOMxhIaIC8IVhFlvJtlALIWQl57Um/LquBpjBpkOwin1qADKLB7RD9moqiPz2TcAMqQGa4OI9Av5op/DrMzXAHmz6mw4IxEQA67AW825/bhngAVoBMEHzZD+aFQCsQUCkAAor/M2wCYAVdwCqxJmANgD8cmJjPQDt5wK22AD0nAVoBsAiE1BMcgAbAJikAqoTYP1CA4BEtBgdgC6yARUuAC3QI7sDiLMAxUk2YAwiIwNAn4YAhGU+YKcOqAUMCgJQziugHGMALmNAhANAWxkaoEgABS4ADdMyiyiglPMIcJ0GKQAayDAAGQEAuu8VUB/gJAH1AS4IgLAwAA24AAoygAeuAPFbqHPHoNwc1HuCJCDncRl7NG8At7Ak48qugVEGsOBxO7snB58T0ngASlwWjomFpMegOusxrFOLBCexsFMbvUzxCyVXRqEkBpjlpXdOgcEqFlsEKpRynFviMIus0md+kcUEDAuUeaxCcysjUGgySt1yTKTUZRTbOaFim17unxUr92doBw4f9zTKObGInZl+//NTW592VP3g+Q4Onh6Ovjfgt5vsPoSCJuDuPRz/58CFmhEtKPIEvY8kZAd3VxRxRJJSyIXcUu0/VOz3okITJRC2ex9kGdB5ecBVZLtgCyt70fUB2nGTTjOu/HFZohsXXLoOrbQKfDps1ePtTj9wSter2oGWoBnYRZqB+bQ5OnLaShpnrNAz6N6R7OW1I1HJjnmPVFuit7eDV1jNvuAkpJNqgJ0DQPCHiv3dqmULfJe3P7hrB/oej3T0S/Tme7tf1Xp/MArPB/Ayp82X5OlAaJfI8wHsJ2/zWXg6EGV4XXB5CbuN3mUYxnQKNI6HU9i3op0y3tpQQw39b/oLfDt0HcsiqWsAAAAASUVORK5CYII=
  19. // @compatible chrome
  20. // @compatible firefox
  21. // @compatible opera
  22. // ==/UserScript==
  23.  
  24. /* jshint lastsemic:true, multistr:true, laxbreak:true, -W030, -W041, -W084 */
  25.  
  26. var resizeMode = GM_getValue('resize', 'Fit to width');
  27. if (typeof resizeMode != 'string')
  28. resizeMode = resizeMode ? 'Fit to width' : 'Original';
  29.  
  30. var resizeWidth = GM_getValue('width', 1280) |0;
  31. var resizeHeight = GM_getValue('height', 720) |0;
  32. updateCustomSize();
  33.  
  34. var playDirectly = !!GM_getValue('playHTML5', false);
  35. var skipCustom = !!GM_getValue('skipCustom', true);
  36. var showStoryboard = !!GM_getValue('showStoryboard', true);
  37.  
  38. var _ = initTL();
  39.  
  40. var imageLoader = document.createElement('img');
  41. var imageLoader2 = document.createElement('img');
  42.  
  43. var iframes = document.getElementsByTagName('iframe');
  44. var oembeds = document.getElementsByTagName('embed');
  45. var persite = (function() {
  46. var rules = [
  47. {host: /(^|\.)google\.\w{2,3}(\.\w{2,3})?$/, class:'g-blk', query: 'a[href*="youtube.com/watch"][data-ved]', eatparent: 1},
  48. {host: /(^|\.)pikabu\.ru$/, class:'b-video', match: '[data-url*="youtube.com/embed"]', attr: 'data-url'},
  49. {host: /(^|\.)androidauthority\.com$/, tag:'iframe', match: '[src*="youtube.com/embed"]', eatparent: '.video-container'},
  50. {host: /(^|\.)reddit\.com$/, tag:'iframe',
  51. match: '[data-url*="youtube.com/"] [src*="/mediaembed"], [data-url*="youtu.be/"] [src*="/mediaembed"]',
  52. src: function(e) { return e.closest('[data-url*="youtube.com/"], [data-url*="youtu.be/"]').dataset.url }},
  53. ];
  54. for (var i=0, rule; (i<rules.length) && (rule=rules[i]); i++) {
  55. if (rule.host && location.hostname.match(rule.host) && (rule.class || rule.tag))
  56. return {
  57. nodes: rule.class ? document.getElementsByClassName(rule.class) : document.getElementsByTagName(rule.tag),
  58. match: rule.match ? function(e) { return e.matches(rule.match) ? e : null }
  59. : function(e) { return e.querySelector(rule.query) },
  60. attr: rule.attr,
  61. src: rule.src,
  62. eatparent: rule.eatparent,
  63. };
  64. }
  65. })();
  66.  
  67. findEmbeds();
  68. injectStylesIfNeeded();
  69. new MutationObserver(findEmbeds).observe(document, {subtree:true, childList:true});
  70.  
  71. document.addEventListener('DOMContentLoaded', function(e) {
  72. injectStylesIfNeeded();
  73. adjustNodesIfNeeded(e);
  74. });
  75. window.addEventListener('resize', adjustNodesIfNeeded, true);
  76. window.addEventListener('message', function(e) {
  77. if (e.data == 'iframe-allowfs') {
  78. $$('iframe:not([allowfullscreen])').some(function(iframe) {
  79. if (iframe.contentWindow == e.source) {
  80. iframe.allowFullscreen = true;
  81. return true;
  82. }
  83. });
  84. if (window != window.top)
  85. window.parent.postMessage('iframe-allowfs', '*');
  86. }
  87. });
  88.  
  89. function findEmbeds(mutations) {
  90. var i, len, e;
  91. if (mutations && mutations.length == 1 && !mutations[0].addedNodes.length)
  92. return;
  93. if (persite)
  94. for (i=0, len=persite.nodes.length; (i<len) && (e=persite.nodes[i]); i++)
  95. if (e = persite.match(e))
  96. processEmbed(e, persite.src && persite.src(e) || e.getAttribute(persite.attr));
  97. for (i=0, len=iframes.length; (i<len) && (e=iframes[i]); i++)
  98. if (/youtube\.com(\/|%2F)(embed|v)(\/|%2F)/i.test(e.src))
  99. processEmbed(e);
  100. for (i=0, len=oembeds.length; (i<len) && (e=oembeds[i]); i++)
  101. if (/youtube\.com(\/|%2F)(embed|v)\//i.test(e.src))
  102. processEmbed(e);
  103. }
  104.  
  105. function processEmbed(node, src) {
  106. function decodeEmbedUrl(url) {
  107. return url.indexOf('youtube.com%2Fembed') > 0
  108. ? decodeURIComponent(url.replace(/^.*?(http[^&?=]+?youtube.com%2Fembed[^&]+).*$/i, '$1'))
  109. : url;
  110. }
  111. src = src || node.src || node.href || '';
  112. var n = node;
  113. var np = n.parentNode, npw;
  114. var srcFixed = decodeEmbedUrl(src).replace(/\/(watch\?v=|v\/)/, '/embed/');
  115. if (src.indexOf('cdn.embedly.com/') > 0 ||
  116. resizeMode != 'Original' && np && np.children.length == 1 && !np.className && !np.id)
  117. {
  118. n = location.hostname == 'disqus.com' ? np.parentNode : np;
  119. np = n.parentElement;
  120. }
  121. if (!np ||
  122. !np.parentNode ||
  123. skipCustom && srcFixed.indexOf('enablejsapi=1') > 0 ||
  124. node.matches('.instant-youtube-embed, .YTLT-embed') ||
  125. node.onload // skip some retarded loaders
  126. )
  127. return;
  128.  
  129. var id = srcFixed.match(/(?:embed\/|v[=\/]|youtu\.be\/)([^\s,.()\[\]?]+?)(?:[&?\/].*|$)/);
  130. if (!id)
  131. return;
  132. id = id[1];
  133.  
  134. var autoplay = srcFixed.indexOf('autoplay=1') > 0;
  135.  
  136. if (np.localName == 'object')
  137. n = np, np = n.parentElement;
  138.  
  139. var eatparent = persite && persite.eatparent || 0;
  140. if (typeof eatparent == 'string')
  141. n = np.closest(eatparent) || n, np = n.parentElement;
  142. else
  143. while (eatparent--)
  144. n = np, np = n.parentElement;
  145.  
  146. var div = document.createElement('div');
  147. div.className = 'instant-youtube-container';
  148. div.FYTE = {
  149. state: 'querying',
  150. srcEmbed: srcFixed.replace(/&$/, ''),
  151. videoID: id,
  152. originalWidth: /%/.test(node.width) ? 320 : node.width|0 || n.clientWidth|0,
  153. originalHeight: /%/.test(node.height) ? 200 : node.height|0 || n.clientHeight|0,
  154. };
  155. div.FYTE.srcEmbedFixed = div.FYTE.srcEmbed.replace(/^http:/, 'https:').replace(/&?wmode=\w+/, '').replace(/[?&]feature=oembed/, '');
  156. div.FYTE.srcWatchFixed = div.FYTE.srcEmbedFixed.replace(/\/embed\//, '/watch?v=');
  157.  
  158. var divSize = calcContainerSize(div, n);
  159. var origStyle = getComputedStyle(n);
  160. div.style.cssText = important('background-color:transparent; transition:background-color 2s;' +
  161. (origStyle.hasOwnProperty('position') ? Object.keys(origStyle) : Object.keys(origStyle.__proto__) /*FF*/)
  162. .filter(function(k) { return !!k.match(/^(position|left|right|top|bottom)$/) })
  163. .map(function(k) { return k + ':' + origStyle[k] })
  164. .join(';')
  165. .replace(/\b[^;:]+:\s*(auto|static|block)(!\s*important)?;/g, '') +
  166. (origStyle.display == 'inline' ? ';display:inline-block;width:100%' : '') +
  167. ';min-width:' + Math.min(divSize.w, div.FYTE.originalWidth) + 'px' +
  168. ';min-height:' + Math.min(divSize.h, div.FYTE.originalHeight) + 'px' +
  169. (resizeMode == 'Fit to width' ? ';width:100%' : '') +
  170. ';max-width:' + divSize.w + 'px; height:' + divSize.h + 'px;');
  171. if (!autoplay) {
  172. setTimeout(function() { div.style.backgroundColor = '' }, 0);
  173. setTimeout(function() { div.style.transition = '' }, 2000);
  174. }
  175.  
  176. // consume parents of retardedly positioned videos
  177. if (div.style.position.match('absolute|relative')) {
  178. if (np.children.length == 1 && floatPadding(np, getComputedStyle(np, ':after'), 'Top') >= div.FYTE.originalHeight)
  179. n = np, np = n.parentElement;
  180. div.style.cssText = div.style.cssText.replace(/\b(position|left|top|right|bottom):[^;]+/g, '');
  181. }
  182.  
  183. var wrapper = div.appendChild(document.createElement('div'));
  184. wrapper.className = 'instant-youtube-wrapper';
  185.  
  186. var img = wrapper.appendChild(document.createElement('img'));
  187. if (!autoplay) {
  188. img.className = 'instant-youtube-thumbnail';
  189. img.src = 'https://i.ytimg.com/vi/' + id + '/maxresdefault.jpg';
  190. img.style.cssText = important('transition:opacity 0.1s ease-out; opacity:0; padding:0; margin:auto; position:absolute; left:0; right:0; top:0; bottom:0; max-width:none; max-height:none;');
  191.  
  192. img.title = _('Shift-click to use alternative player');
  193. img.onload = function(e) {
  194. if (img.naturalWidth <= 120)
  195. return img.onerror(e);
  196. var fitToWidth = true;
  197. if (img.naturalHeight) {
  198. var ratio = img.naturalWidth / img.naturalHeight;
  199. if (ratio > 4.1/3 && ratio < divSize.w/divSize.h) {
  200. img.style.cssText += important('width:auto; height:100%;');
  201. fitToWidth = false;
  202. }
  203. }
  204. if (fitToWidth) {
  205. img.style.cssText += important('width:100%; height:auto;');
  206. }
  207. if (img.videoWidth)
  208. fixThumbnailAR(div);
  209. img.style.opacity = 1;
  210. };
  211. img.onerror = function(e) {
  212. if (img.src.indexOf('maxresdefault') > 0)
  213. img.src = img.src.replace('maxresdefault','hqdefault');
  214. };
  215. }
  216.  
  217. GM_xmlhttpRequest({
  218. method: 'GET',
  219. url: 'https://www.youtube.com/get_video_info?video_id=' + div.FYTE.videoID + '&el=detailpage',
  220. headers: {'Accept-Encoding': 'gzip'},
  221. context: div,
  222. onload: parseVideoInfo
  223. });
  224.  
  225. translateHTML(wrapper, 'beforeend', '\
  226. <a class="instant-youtube-title" target="_blank" href="' + div.FYTE.srcWatchFixed + '">&nbsp;</a>\
  227. <svg class="instant-youtube-play-button"><path fill-rule="evenodd" clip-rule="evenodd" fill="#1F1F1F" class="ytp-large-play-button-svg" d="M84.15,26.4v6.35c0,2.833-0.15,5.967-0.45,9.4c-0.133,1.7-0.267,3.117-0.4,4.25l-0.15,0.95c-0.167,0.767-0.367,1.517-0.6,2.25c-0.667,2.367-1.533,4.083-2.6,5.15c-1.367,1.4-2.967,2.383-4.8,2.95c-0.633,0.2-1.316,0.333-2.05,0.4c-0.767,0.1-1.3,0.167-1.6,0.2c-4.9,0.367-11.283,0.617-19.15,0.75c-2.434,0.034-4.883,0.067-7.35,0.1h-2.95C38.417,59.117,34.5,59.067,30.3,59c-8.433-0.167-14.05-0.383-16.85-0.65c-0.067-0.033-0.667-0.117-1.8-0.25c-0.9-0.133-1.683-0.283-2.35-0.45c-2.066-0.533-3.783-1.5-5.15-2.9c-1.033-1.067-1.9-2.783-2.6-5.15C1.317,48.867,1.133,48.117,1,47.35L0.8,46.4c-0.133-1.133-0.267-2.55-0.4-4.25C0.133,38.717,0,35.583,0,32.75V26.4c0-2.833,0.133-5.95,0.4-9.35l0.4-4.25c0.167-0.966,0.417-2.05,0.75-3.25c0.7-2.333,1.567-4.033,2.6-5.1c1.367-1.434,2.967-2.434,4.8-3c0.633-0.167,1.333-0.3,2.1-0.4c0.4-0.066,0.917-0.133,1.55-0.2c4.9-0.333,11.283-0.567,19.15-0.7C35.65,0.05,39.083,0,42.05,0L45,0.05c2.467,0,4.933,0.034,7.4,0.1c7.833,0.133,14.2,0.367,19.1,0.7c0.3,0.033,0.833,0.1,1.6,0.2c0.733,0.1,1.417,0.233,2.05,0.4c1.833,0.566,3.434,1.566,4.8,3c1.066,1.066,1.933,2.767,2.6,5.1c0.367,1.2,0.617,2.284,0.75,3.25l0.4,4.25C84,20.45,84.15,23.567,84.15,26.4z M33.3,41.4L56,29.6L33.3,17.75V41.4z"></path><polygon fill-rule="evenodd" clip-rule="evenodd" fill="#FFFFFF" points="33.3,41.4 33.3,17.75 56,29.6"></polygon></svg>\
  228. <span tl class="instant-youtube-link">' + (playDirectly ? 'Play with Youtube player' : 'Play directly (up to 720p)') + '</span>\
  229. <div class="instant-youtube-storyboard" disabled><div>&nbsp;</div></div>\
  230. <div tl class="instant-youtube-options-button">Options</div>\
  231. ');
  232.  
  233. np.insertBefore(div, n);
  234. n.remove();
  235.  
  236. if (autoplay)
  237. return startPlaying(div);
  238.  
  239. div.addEventListener('click', clickHandler);
  240. if (showStoryboard)
  241. updateHoverHandler(div);
  242. }
  243.  
  244. function updateHoverHandler(div) {
  245. var sb = $(div, '.instant-youtube-storyboard');
  246. if (!showStoryboard) {
  247. if (!sb.getAttribute('disabled'))
  248. sb.setAttribute('disabled', '');
  249. return;
  250. }
  251. if (sb.hasAttribute('disabled'))
  252. sb.removeAttribute('disabled');
  253.  
  254. sb.addEventListener('click', storyboardClickHandler);
  255.  
  256. var oldIndex = null;
  257. var style = sb.firstElementChild.style;
  258. sb.addEventListener('mousemove', storyboardHoverHandler);
  259. sb.addEventListener('mouseout', storyboardHoverHandler);
  260.  
  261. div.addEventListener('mouseover', storyboardPreloader);
  262. div.addEventListener('mouseout', storyboardPreloader);
  263.  
  264. var spinner = document.createElement('span');
  265. spinner.className = 'instant-youtube-loading-button';
  266.  
  267. function storyboardClickHandler(e) {
  268. sb.removeEventListener('click', storyboardClickHandler);
  269. var offsetX = e.offsetX || e.clientX - this.getBoundingClientRect().left;
  270. div.FYTE.startAt = offsetX / this.clientWidth * div.FYTE.duration |0;
  271. div.FYTE.srcEmbedFixed = setUrlParams(div.FYTE.srcEmbedFixed, {start: div.FYTE.startAt});
  272. startPlaying(div, e.shiftKey);
  273. }
  274.  
  275. function storyboardPreloader(e) {
  276. if (e.type == 'mouseout') {
  277. imageLoader.onload = null; imageLoader.src = '';
  278. spinner.remove();
  279. return;
  280. }
  281. if (!div.FYTE.storyboard || div.FYTE.storyboard.preloaded)
  282. return;
  283. var lastpart = (div.FYTE.storyboard.len-1)/(div.FYTE.storyboard.rows * div.FYTE.storyboard.cols) |0;
  284. if (lastpart <= 0)
  285. return;
  286. var part = 0;
  287. imageLoader.src = setStoryboardUrl(part++);
  288. imageLoader.onload = function() {
  289. if (part > lastpart) {
  290. div.FYTE.storyboard.preloaded = true;
  291. div.removeEventListener('mouseover', storyboardPreloader);
  292. div.removeEventListener('mouseout', storyboardPreloader);
  293. imageLoader.onload = null;
  294. imageLoader.src = '';
  295. spinner.remove();
  296. return;
  297. }
  298. imageLoader.src = setStoryboardUrl(part++);
  299. };
  300. }
  301.  
  302. function setStoryboardUrl(part) {
  303. return div.FYTE.storyboard.url.replace(/M\d+\.jpg\?/, 'M' + part + '.jpg?');
  304. }
  305.  
  306. function storyboardHoverHandler(e) {
  307. if (!showStoryboard || !div.FYTE.storyboard)
  308. return;
  309. if (e.type == 'mouseout')
  310. return imageLoader2.onload && imageLoader2.onload();
  311. var w = div.FYTE.storyboard.w;
  312. var h = div.FYTE.storyboard.h;
  313. var cols = div.FYTE.storyboard.cols;
  314. var rows = div.FYTE.storyboard.rows;
  315. var len = div.FYTE.storyboard.len;
  316. var partlen = rows * cols;
  317.  
  318. var offsetX = e.offsetX || e.clientX - this.getBoundingClientRect().left;
  319. var left = Math.min(this.clientWidth - w, Math.max(0, offsetX - w/2)) |0;
  320. if (!style.left || parseInt(style.left) != left) {
  321. style.left = left + 'px';
  322. if (spinner.parentElement)
  323. spinner.style.cssText = important('left:' + (left + w/2 - 10) + 'px; right:auto;');
  324. }
  325.  
  326. var index = Math.min(offsetX / this.clientWidth * (len+1) |0, len - 1);
  327. if (index == oldIndex)
  328. return;
  329.  
  330. var indexBG = index/partlen|0;
  331. if (!oldIndex || indexBG != (oldIndex/partlen|0)) {
  332. style.cssText = style.cssText.replace(/$|background-image[^;]+;/,
  333. 'background-image: url(' + setStoryboardUrl(indexBG) + ')!important;');
  334. if (!div.FYTE.storyboard.preloaded) {
  335. if (spinner.timer)
  336. clearTimeout(spinner.timer);
  337. spinner.timer = setTimeout(function() {
  338. spinner.timer = 0;
  339. if (!imageLoader2.src)
  340. return;
  341. this.appendChild(spinner);
  342. spinner.style.cssText = important('left:' + (left + w/2 - 10) + 'px; right:auto;');
  343. }.bind(this), 50);
  344. imageLoader2.onload = function() {
  345. clearTimeout(spinner.timer);
  346. spinner.remove();
  347. spinner.timer = 0;
  348. imageLoader2.onload = null;
  349. imageLoader2.src = '';
  350. };
  351. imageLoader2.src = setStoryboardUrl(indexBG);
  352. }
  353. }
  354.  
  355. oldIndex = index;
  356. index = index % partlen;
  357. style.backgroundPosition = '-' + (index % cols) * w + 'px -' + (index / cols |0) * h + 'px';
  358. }
  359. }
  360.  
  361. function adjustNodesIfNeeded(e) {
  362. if (adjustNodesIfNeeded.scheduled)
  363. clearTimeout(adjustNodesIfNeeded.scheduled);
  364. adjustNodesIfNeeded.scheduled = setTimeout(function() {
  365. adjustNodes(e);
  366. adjustNodesIfNeeded.scheduled = 0;
  367. }, 16);
  368. }
  369.  
  370. function adjustNodes(e, clickedContainer) {
  371. var force = !!clickedContainer;
  372. var nearest = force ? clickedContainer : null;
  373.  
  374. var vids = $$('div.instant-youtube-container');
  375.  
  376. if (!nearest && e.type != 'DOMContentLoaded') {
  377. var minDistance = window.innerHeight*3/4 |0;
  378. var nearTargetY = window.innerHeight/2;
  379. vids.forEach(function(n) {
  380. var bounds = n.getBoundingClientRect();
  381. var distance = Math.abs((bounds.bottom + bounds.top)/2 - nearTargetY);
  382. if (distance < minDistance) {
  383. minDistance = distance;
  384. nearest = n;
  385. }
  386. });
  387. }
  388.  
  389. if (nearest) {
  390. var bounds = nearest.getBoundingClientRect();
  391. var nearestCenterYpct = (bounds.top + bounds.bottom)/2 / window.innerHeight;
  392. }
  393.  
  394. var resized = false;
  395.  
  396. vids.forEach(function(n) {
  397. var size = calcContainerSize(n);
  398. var w = size.w, h = size.h;
  399.  
  400. // prevent parent clipping
  401. for (var e=n.parentElement, style; e && (style=getComputedStyle(e)); e=e.parentElement)
  402. if (e.style.overflow != 'visible')
  403. if ((style.overflow+style.overflowX+style.overflowY).match(/hidden|scroll/))
  404. if (n.offsetTop < e.clientHeight / 2 && n.offsetTop + n.clientHeight > e.clientHeight)
  405. e.style.cssText = e.style.cssText.replace(/\boverflow(-[xy])?:[^;]+/g, '') +
  406. important('overflow:visible;overflow-x:visible;overflow-y:visible;');
  407.  
  408. if (force && Math.abs(w - parseFloat(n.style.maxWidth)) <= 2)
  409. return;
  410.  
  411. if (n.style.maxWidth != w + 'px') n.style.maxWidth = w + 'px';
  412. if (n.style.height != h + 'px') n.style.height = h + 'px';
  413. if (parseFloat(n.style.minWidth) > w) n.style.minWidth = n.style.maxWidth;
  414. if (parseFloat(n.style.minHeight) > h) n.style.minHeight = n.style.height;
  415.  
  416. fixThumbnailAR(n);
  417. resized = true;
  418. });
  419.  
  420. if (resized && nearest)
  421. setTimeout(function() {
  422. var bounds = nearest.getBoundingClientRect();
  423. var h = bounds.bottom - bounds.top;
  424. var projectedCenterY = nearestCenterYpct * window.innerHeight;
  425. var projectedTop = projectedCenterY - h/2;
  426. var safeTop = Math.min(Math.max(0, projectedTop), window.innerHeight - h);
  427. window.scrollBy(0, bounds.top - safeTop);
  428. }, 16);
  429. }
  430.  
  431. function calcContainerSize(div, origNode) {
  432. var w, h;
  433. origNode = origNode || div;
  434. switch (resizeMode) {
  435. case 'Original':
  436. w = div.FYTE.originalWidth;
  437. h = div.FYTE.originalHeight;
  438. break;
  439. case 'Custom':
  440. w = resizeWidth;
  441. h = resizeHeight;
  442. break;
  443. case '1080p':
  444. case '720p':
  445. case '480p':
  446. case '360p':
  447. h = parseInt(resizeMode);
  448. w = h / 9 * 16;
  449. break;
  450. default: // fit-to-width mode
  451. var n = origNode;
  452. do {
  453. n = n.parentElement;
  454. // find parent node with nonzero width (i.e. independent of our video element)
  455. } while (n && !(w = n.clientWidth));
  456. if (w)
  457. h = w / 16 * 9;
  458. else {
  459. w = origNode.clientWidth;
  460. h = origNode.clientHeight;
  461. }
  462. }
  463. var np = origNode.parentElement;
  464. var style = getComputedStyle(np);
  465. var parentWidth = parseFloat(style.width) - floatPadding(np, style, 'Left') - floatPadding(np, style, 'Right');
  466. if (parentWidth > 0 && parentWidth < w) {
  467. h = parentWidth / w * h;
  468. w = parentWidth;
  469. }
  470. if (resizeMode == 'Fit to width' && h < div.FYTE.originalHeight*0.9)
  471. h = Math.min(div.FYTE.originalHeight, w / div.FYTE.originalWidth * div.FYTE.originalHeight);
  472.  
  473. return {w: window.chrome ? w : Math.round(w), h:h};
  474. }
  475.  
  476. function parseVideoInfo(response) {
  477. var div = response.context;
  478. var txt = response.responseText;
  479. var info = tryCatch(function() { return JSON.parse(txt.replace(/(\w+)=?(.*?)(&|$)/g, '"$1":"$2",').replace(/^(.+?),?$/, '{$1}')) });
  480. var videoSources = [];
  481.  
  482. // parse width & height to adjust the thumbnail
  483. var m = decodeURIComponent(txt).match(/\b(\d+)x(\d+)\b/);
  484. if (m)
  485. fixThumbnailAR(div, m[1]|0, m[2]|0);
  486.  
  487. // parse video sources
  488. if (info.url_encoded_fmt_stream_map && info.fmt_list) {
  489. var streams = {};
  490. decodeURIComponent(info.url_encoded_fmt_stream_map).split(',').forEach(function(stream) {
  491. var params = {};
  492. stream.split('&').forEach(function(kv) {
  493. params[kv.split('=')[0]] = decodeURIComponent(kv.split('=')[1]);
  494. });
  495. streams[params.itag] = params;
  496. });
  497. decodeURIComponent(info.fmt_list).split(',').forEach(function(fmt) {
  498. var itag = fmt.split('/')[0];
  499. var dimensions = fmt.split('/')[1];
  500. var stream = streams[itag];
  501. if (stream) {
  502. videoSources.push({
  503. src: stream.url,
  504. title: stream.quality + ', ' + dimensions + ', ' + stream.type
  505. });
  506. }
  507. });
  508. } else {
  509. var rx = /url=([^=]+?mime%3Dvideo%252F(?:mp4|webm)[^=]+?)(?:,quality|,itag|.u0026)/g;
  510. var text = decodeURIComponent(txt).split('url_encoded_fmt_stream_map')[1];
  511. while (m = rx.exec(text)) {
  512. videoSources.push({
  513. src: decodeURIComponent(decodeURIComponent(m[1]))
  514. });
  515. }
  516. }
  517.  
  518. var duration = div.FYTE.duration = info.length_seconds|0 || '';
  519. if (duration) {
  520. var d = new Date(null);
  521. d.setSeconds(duration);
  522. duration = d.toISOString().replace(/^.+?T[0:]{0,4}(.+?)\..+$/, '<span>$1</span>');
  523. }
  524. var title = decodeURIComponent(info.title || info.reason || '').replace(/\+/g, ' ');
  525. if (title || duration) {
  526. $(div, '.instant-youtube-title').innerHTML = (title ? '<strong>' + title + '</strong>' : '') + duration;
  527. }
  528. if (info.reason)
  529. div.setAttribute('disabled', '');
  530.  
  531. if (videoSources.length)
  532. div.FYTE.videoSources = videoSources;
  533.  
  534. if (info.storyboard_spec) {
  535. m = decodeURIComponent(decodeURIComponent(info.storyboard_spec)).split('|');
  536. div.FYTE.storyboard = JSON.parse(m[m.length-1].replace(/^(\d+)#(\d+)#(\d+)#(\d+)#(\d+)#.+$/, '{"w":$1, "h":$2, "len":$3, "rows":$4, "cols":$5}'));
  537. div.FYTE.storyboard.url = m[0].replace('$L/$N.jpg', (m.length-2) + '/M0.jpg?sigh=' + m[m.length-1].replace(/^.+?#([^#]+)$/, '$1'));
  538. $(div, '.instant-youtube-storyboard div').style.cssText = important(
  539. 'width: ' + (div.FYTE.storyboard.w-1) + 'px; height: ' + div.FYTE.storyboard.h + 'px;');
  540. }
  541.  
  542. injectStylesIfNeeded();
  543.  
  544. if (div.FYTE.state == 'scheduled play')
  545. setTimeout(function() { startPlayingDirectly(div) }, 0);
  546.  
  547. div.FYTE.state = '';
  548. }
  549.  
  550. function fixThumbnailAR(div, w, h) {
  551. var img = $(div, 'img');
  552. var thw = img.naturalWidth, thh = img.naturalHeight;
  553. if (w && h) { // means thumbnail is still loading
  554. img.videoWidth = w;
  555. img.videoHeight = h;
  556. return;
  557. }
  558. w = img.videoWidth;
  559. h = img.videoHeight;
  560. var divw = div.clientWidth, divh = div.clientHeight;
  561. // if both video and thumbnail are 4:3, fit the image to height
  562. //console.log(divw, divh, thw, thh, w, h, h/w*divw / divh - 1, thh/thw*divw / divh - 1);
  563. if (Math.abs(h/w*divw / divh - 1) > 0.05 && Math.abs(thh/thw*divw / divh - 1) > 0.05) {
  564. img.style.maxHeight = img.clientHeight + 'px';
  565. if (!img.videoWidth) // skip animation if thumbnail is already loaded
  566. img.style.transition = 'height 1s ease, margin-top 1s ease';
  567. setTimeout(function() {
  568. img.style.maxHeight = 'none';
  569. img.style.cssText += important(h/w >= divh/divw ? 'width:auto; height:100%;' : 'width:100%; height:auto;');
  570. setTimeout(function() {
  571. img.style.transition = '';
  572. }, 1000);
  573. }, 0);
  574. }
  575. }
  576.  
  577. function clickHandler(e) {
  578. if (e.target.href)
  579. return;
  580. if (e.target.matches('.instant-youtube-options-button')) {
  581. showOptions(e);
  582. e.preventDefault();
  583. e.stopPropagation();
  584. return;
  585. }
  586. if (e.target.matches('.instant-youtube-options, .instant-youtube-options *'))
  587. return;
  588.  
  589. e.preventDefault();
  590. e.stopPropagation();
  591.  
  592. var alternateMode = e.shiftKey || e.target.className == 'instant-youtube-link';
  593. startPlaying(e.target.closest('.instant-youtube-container'), alternateMode);
  594. }
  595.  
  596. function startPlaying(div, alternateMode) {
  597. div.removeEventListener('click', clickHandler);
  598.  
  599. $$(div, '.instant-youtube-wrapper > *:not(img):not(a)').forEach(function(e) { e.style.cssText = 'display:none!important' });
  600. $(div, 'svg').outerHTML = '<span class="instant-youtube-loading-button"></span>';
  601.  
  602. if (window != window.top)
  603. window.parent.postMessage('iframe-allowfs', '*');
  604.  
  605. if ((!!playDirectly + !!alternateMode == 1) && (div.FYTE.videoSources || div.FYTE.state == 'querying')) {
  606. if (div.FYTE.videoSources)
  607. startPlayingDirectly(div);
  608. else {
  609. // playback will start in parseVideoInfo
  610. div.FYTE.state = 'scheduled play';
  611. // fallback to iframe in 5s
  612. setTimeout(function() {
  613. if (div.FYTE.state) {
  614. div.FYTE.state = '';
  615. switchToIFrame.call(div);
  616. }
  617. }, 5000);
  618. }
  619. }
  620. else
  621. switchToIFrame.call(div);
  622. }
  623.  
  624. function startPlayingDirectly(div) {
  625. var video = document.createElement('video');
  626. video.controls = true;
  627. video.autoplay = true;
  628. video.style.cssText = important(
  629. 'position:absolute; left:0; top:0; right:0; padding:0; margin:auto; opacity:0; transition:opacity 2s;' +
  630. 'width:100%; height:100%;');
  631. video.className = 'instant-youtube-embed';
  632. video.volume = GM_getValue('volume', 0.5);
  633.  
  634. div.FYTE.videoSources.forEach(function(src) {
  635. var srcdom = video.appendChild(document.createElement('source'));
  636. Object.keys(src).forEach(function(k) { srcdom[k] = src[k] });
  637. srcdom.onerror = switchToIFrame.bind(div);
  638. });
  639.  
  640.  
  641. if (window.chrome) {
  642. video.addEventListener('click', function(e) {
  643. this.paused ? this.play() : this.pause();
  644. });
  645. }
  646. video.interval = (function() {
  647. return setInterval(function() {
  648. if (video.volume != GM_getValue('volume', 0.5))
  649. GM_setValue('volume', video.volume);
  650. }, 1000);
  651. })();
  652. var title = $(div, '.instant-youtube-title');
  653. if (title) {
  654. video.onpause = function() { title.removeAttribute('hidden') };
  655. video.onplay = function() { title.setAttribute('hidden', true) };
  656. }
  657. video.onloadedmetadata = div.FYTE.startAt && function(e) {
  658. video.currentTime = div.FYTE.startAt;
  659. };
  660. video.onloadeddata = function(e) {
  661. pauseOtherVideos(this);
  662. div.style.cssText += 'contain:none!important'; // allow fullscreen
  663. div.firstElementChild.appendChild(this);
  664. this.style.opacity = 1;
  665. var img = $(div, 'img');
  666. img.style.transition = 'opacity 1s';
  667. img.style.opacity = 0;
  668. };
  669. }
  670.  
  671. function switchToIFrame(e) {
  672. var div = this;
  673. var wrapper = div.firstElementChild;
  674. if (e) {
  675. console.log('[FYTE] Direct linking canceled on %s, switching to IFRAME player', div.FYTE.srcEmbed);
  676. var video = e.target ? e.target.closest('video') : e.path && e.path[e.path.length-1];
  677. while (video.lastElementChild)
  678. video.lastElementChild.remove();
  679. }
  680.  
  681. wrapper.insertAdjacentHTML('beforeend',
  682. '<iframe class="instant-youtube-embed" allowtransparency="true" src="' +
  683. setUrlParams(div.FYTE.srcEmbedFixed, {
  684. html5: 1,
  685. autoplay: 1,
  686. autohide: 2,
  687. border: 0,
  688. controls: 1,
  689. fs: 1,
  690. showinfo: 1,
  691. ssl: 1,
  692. theme: 'dark',
  693. enablejsapi: 1,
  694. }) + '" frameborder="0" allowfullscreen width="100%" height="100%"></iframe>');
  695.  
  696. wrapper.lastElementChild.onload = function() {
  697. pauseOtherVideos(this);
  698. this.style.cssText = important(
  699. 'position:absolute; left:0; top:0; right:0; padding:0; margin:auto; opacity:1; transition:opacity 2s;');
  700.  
  701. div.setAttribute('iframe', '');
  702. div.style.cssText += 'contain:none!important'; // allow fullscreen
  703. setTimeout(function() {
  704. $(div, 'img').style.display = 'none';
  705. var title = $(div, '.instant-youtube-title');
  706. if (title)
  707. title.remove();
  708. }, 2000);
  709. };
  710. }
  711.  
  712. function setUrlParams(url, params) {
  713. var names = Object.keys(params);
  714. url = url.replace(new RegExp('[?&](' + names.join('|') + ')(=[^?&]*)?', 'gi'), '');
  715. return url +
  716. (url.indexOf('?') > 0 ? '&' : '?') +
  717. names.map(function(n) { return n + '=' + params[n] }).join('&');
  718. }
  719.  
  720. function pauseOtherVideos(activePlayer) {
  721. $$(activePlayer.ownerDocument, '.instant-youtube-embed').forEach(function(v) {
  722. if (v == activePlayer)
  723. return;
  724. switch (v.localName) {
  725. case 'video':
  726. if (!v.paused)
  727. v.pause();
  728. break;
  729. case 'iframe':
  730. try { v.contentWindow.postMessage('{"event":"command", "func":"pauseVideo", "args":""}', '*') } catch(e) {}
  731. break;
  732. }
  733. });
  734. }
  735.  
  736. function showOptions(e) {
  737. var optionsButton = e.target;
  738. translateHTML(optionsButton, 'afterend', '\
  739. <div class="instant-youtube-options">\
  740. <label tl style="width: 100% !important;">Size:<br>\
  741. <select data-action="size-mode">\
  742. <option tl value="Original">Original\
  743. <option tl value="Fit to width">Fit to width\
  744. <option>360p\
  745. <option>480p\
  746. <option>720p\
  747. <option>1080p\
  748. <option tl value="Custom">Custom...\
  749. </select>\
  750. </label>\
  751. <label data-action="size-custom" ' + (resizeMode != 'Custom' ? 'disabled' : '') + '>\
  752. <input type="number" min="320" max="9999" tl-placeholder="width" data-action="width" step="1" value="' + (resizeWidth||'') + '">\
  753. x\
  754. <input type="number" min="240" max="9999" tl-placeholder="height" data-action="height" step="1" value="' + (resizeHeight||'') + '">\
  755. </label>\
  756. <label tl="content,title" title="Show storyboard preview on mouse hover at the bottom">\
  757. <input data-action="storyboard" type="checkbox" ' + (showStoryboard ? 'checked' : '') + '>\
  758. Storyboard thumbs\
  759. </label>\
  760. <label tl="content,title" title="Tip: shift-clicking thumbnails will use alternative player">\
  761. <input data-action="direct" type="checkbox" ' + (playDirectly ? 'checked' : '') + '>\
  762. Play directly\
  763. </label>\
  764. <label tl="content,title" title="Do not process customized videos with enablejsapi=1 parameter (requires page reload)">\
  765. <input data-action="safe" type="checkbox" ' + (skipCustom ? 'checked' : '') + '>\
  766. Safe\
  767. </label>\
  768. <span data-action="buttons">\
  769. <button tl data-action="ok">OK</button>\
  770. <button tl data-action="cancel">Cancel</button>\
  771. </span>\
  772. </div>\
  773. ');
  774. var options = optionsButton.nextElementSibling;
  775.  
  776. options.addEventListener('keydown', function(e) {
  777. if (e.target.localName == 'input' &&
  778. !e.shiftKey && !e.altKey && !e.metaKey && !e.ctrlKey && e.key.match(/[.,]/))
  779. return false;
  780. });
  781.  
  782. $(options, '[data-action="size-mode"]').value = resizeMode;
  783. $(options, '[data-action="size-mode"]').addEventListener('change', function() {
  784. var v = this.value != 'Custom';
  785. var e = $(options, '[data-action="size-custom"]');
  786. e.children[0].disabled = e.children[1].disabled = v;
  787. v ? e.setAttribute('disabled', '') : e.removeAttribute('disabled');
  788. });
  789.  
  790. $(options, '[data-action="buttons"]').addEventListener('click', function(e) {
  791. if (e.target.dataset.action != 'ok') {
  792. options.remove();
  793. return;
  794. }
  795. var v, shouldAdjust;
  796. if (resizeMode != (v = $(options, '[data-action="size-mode"]').value)) {
  797. GM_setValue('resize', resizeMode = v);
  798. shouldAdjust = true;
  799. }
  800. if (resizeMode == 'Custom') {
  801. var w = $(options, '[data-action="width"]').value |0;
  802. var h = $(options, '[data-action="height"]').value |0;
  803. if (resizeWidth != w || resizeHeight != h) {
  804. updateCustomSize(w, h);
  805. GM_setValue('width', resizeWidth);
  806. GM_setValue('height', resizeHeight);
  807. shouldAdjust = true;
  808. }
  809. }
  810. if (showStoryboard != (v = $(options, '[data-action="storyboard"]').checked)) {
  811. GM_setValue('showStoryboard', showStoryboard = v);
  812. $$('.instant-youtube-container').forEach(updateHoverHandler);
  813. }
  814. if (playDirectly != (v = $(options, '[data-action="direct"]').checked)) {
  815. GM_setValue('playHTML5', playDirectly = v);
  816. $$('.instant-youtube-container .instant-youtube-link').forEach(function(e) {
  817. e.textContent = playDirectly ? 'Play with Youtube player' : 'Play directly (up to 720p)';
  818. });
  819. }
  820. if (skipCustom != (v = $(options, '[data-action="safe"]').checked)) {
  821. GM_setValue('skipCustom', skipCustom = v);
  822. }
  823.  
  824. options.remove();
  825.  
  826. if (shouldAdjust)
  827. adjustNodes(e, e.target.closest('.instant-youtube-container'));
  828. });
  829. }
  830.  
  831. function updateCustomSize(w, h) {
  832. resizeWidth = Math.min(9999, Math.max(320, w|0 || resizeWidth|0));
  833. resizeHeight = Math.min(9999, Math.max(240, h|0 || resizeHeight|0));
  834. }
  835.  
  836. function important(cssText) {
  837. return cssText.replace(/;/g, '!important;');
  838. }
  839.  
  840. function tryCatch(func) {
  841. try {
  842. return func();
  843. } catch(e) {
  844. console.log(e);
  845. }
  846. }
  847.  
  848. function getFunctionComment(fn) {
  849. return fn.toString().match(/\/\*([\s\S]*?)\*\/\s*\}$/)[1];
  850. }
  851.  
  852. function $(selORnode, sel) {
  853. return sel ? selORnode.querySelector(sel)
  854. : document.querySelector(selORnode);
  855. }
  856.  
  857. function $$(selORnode, sel) {
  858. return Array.prototype.slice.call(
  859. sel ? selORnode.querySelectorAll(sel)
  860. : document.querySelectorAll(selORnode));
  861. }
  862.  
  863. // fix dumb Firefox bug
  864. function floatPadding(node, style, dir) {
  865. var padding = style['padding' + dir];
  866. if (padding.indexOf('%') < 0)
  867. return parseFloat(padding);
  868. return parseFloat(padding) * (parseFloat(style.width) || node.clientWidth) / 100;
  869. }
  870.  
  871. function translateHTML(baseElement, place, html) {
  872. var tmp = document.createElement('div');
  873. tmp.innerHTML = html;
  874. $$(tmp, '[tl]').forEach(function(node) {
  875. (node.getAttribute('tl') || 'content').split(',').forEach(function(what) {
  876. var child, src, tl;
  877. if (what == 'content') {
  878. for (var i = node.childNodes.length-1, n; (i>=0) && (n=node.childNodes[i]); i--) {
  879. if (n.nodeType == Node.TEXT_NODE && n.textContent.trim()) {
  880. child = n;
  881. break;
  882. }
  883. }
  884. } else
  885. child = node.getAttributeNode(what);
  886. if (!child)
  887. return;
  888. src = child.textContent;
  889. srcTrimmed = src.trim();
  890. tl = src.replace(srcTrimmed, _(srcTrimmed));
  891. if (src != tl)
  892. child.textContent = tl;
  893. });
  894. });
  895. baseElement.insertAdjacentHTML(place, tmp.innerHTML);
  896. }
  897.  
  898. function initTL(src) {
  899. var tlSource = {
  900. 'watch on Youtube': {
  901. 'ru': 'открыть на Youtube',
  902. },
  903. 'Play with Youtube player': {
  904. 'ru': 'Включить плеер Youtube',
  905. },
  906. 'Play directly (up to 720p)': {
  907. 'ru': 'Включить напрямую (макс. 720p)',
  908. },
  909. 'Shift-click to use alternative player': {
  910. 'ru': 'Shift-клик для смены типа плеера',
  911. },
  912. 'Options': {
  913. 'ru': 'Опции',
  914. },
  915. 'Size:': {
  916. 'ru': 'Размер:',
  917. },
  918. 'Original': {
  919. 'ru': 'Исходный',
  920. },
  921. 'Fit to width': {
  922. 'ru': 'На всю ширину',
  923. },
  924. 'Custom...': {
  925. 'ru': 'Настроить...',
  926. },
  927. 'width': {
  928. 'ru': 'ширина',
  929. },
  930. 'height': {
  931. 'ru': 'высота',
  932. },
  933. 'Storyboard thumbs': {
  934. 'ru': 'Раскадровка',
  935. },
  936. 'Show storyboard preview on mouse hover at the bottom': {
  937. 'ru': 'Показывать миникадры при наведении мыши на низ кавер-картинки',
  938. },
  939. 'Play directly': {
  940. 'ru': 'Плеер браузера',
  941. },
  942. 'Tip: shift-clicking thumbnails will use alternative player': {
  943. 'ru': 'Удерживайте клавишу Shift при щелчке на картинке для альтернативного плеера',
  944. },
  945. 'Safe': {
  946. 'ru': 'Консервативный режим',
  947. },
  948. 'Do not process customized videos with enablejsapi=1 parameter (requires page reload)': {
  949. 'ru': 'Не обрабатывать нестандартные видео с параметром enablejsapi=1 (подействует после обновления страницы)',
  950. },
  951. 'OK': {
  952. 'ru': 'ОК',
  953. },
  954. 'Cancel': {
  955. 'ru': 'Оменить',
  956. },
  957. };
  958. var browserLang = navigator.language || navigator.languages && navigator.languages[0] || '';
  959. var browserLangMajor = browserLang.replace(/-.+/, '');
  960. var tl = {};
  961. Object.keys(tlSource).forEach(function(k) {
  962. var langs = tlSource[k];
  963. var text = langs[browserLang] || langs[browserLangMajor];
  964. if (text)
  965. tl[k] = text;
  966. });
  967. return function(src) { return tl[src] || src };
  968. }
  969.  
  970. function injectStylesIfNeeded() {
  971. var styledom = $('style#instant-youtube-styles');
  972. if (styledom) {
  973. // move our rules to the end of HEAD to increase CSS specificity
  974. if (styledom.nextElementSibling && document.head)
  975. document.head.insertBefore(styledom, null);
  976. return;
  977. }
  978. styledom = (document.head || document.documentElement).appendChild(document.createElement('style'));
  979. styledom.id = 'instant-youtube-styles';
  980. styledom.textContent = important(getFunctionComment(function() { /*
  981. .instant-youtube-container {
  982. contain: strict;
  983. position: relative;
  984. overflow: hidden;
  985. cursor: pointer;
  986. padding: 0;
  987. margin: 0;
  988. font: normal 14px/1.0 sans-serif, Arial, Helvetica, Verdana;
  989. text-align: center;
  990. background: black;
  991. }
  992. .instant-youtube-container[disabled] {
  993. background: #888;
  994. }
  995. .instant-youtube-container[disabled] .instant-youtube-storyboard {
  996. display: none;
  997. }
  998. .instant-youtube-container .instant-youtube-wrapper {
  999. width: 100%;
  1000. height: 100%;
  1001. }
  1002. .instant-youtube-container .instant-youtube-play-button {
  1003. display: block;
  1004. position: absolute;
  1005. width: 85px;
  1006. height: 60px;
  1007. left: 0;
  1008. right: 0;
  1009. top: 0;
  1010. bottom: 0;
  1011. margin: auto;
  1012. }
  1013. .instant-youtube-container .instant-youtube-loading-button {
  1014. display: block;
  1015. position: absolute;
  1016. width: 20px;
  1017. height: 20px;
  1018. left: 0;
  1019. right: 0;
  1020. top: 0;
  1021. bottom: 0;
  1022. padding: 0;
  1023. margin: auto;
  1024. pointer-events: none;
  1025. background: url("data:image/gif;base64,R0lGODlhFAAUAJEDAMzMzLOzs39/f////yH/C05FVFNDQVBFMi4wAwEAAAAh+QQFCgADACwAAAAAFAAUAAACPJyPqcuNItyCUJoQBo0ANIxpXOctYHaQpYkiHfM2cUrCNT0nqr4uudsz/IC5na/2Mh4Hu+HR6YBaplRDAQAh+QQFCgADACwEAAIADAAGAAACFpwdcYupC8BwSogR46xWZHl0l8ZYQwEAIfkEBQoAAwAsCAACAAoACgAAAhccMKl2uHxGCCvO+eTNmishcCCYjWEZFgAh+QQFCgADACwMAAQABgAMAAACFxwweaebhl4K4VE6r61DiOd5SfiN5VAAACH5BAUKAAMALAgACAAKAAoAAAIYnD8AeKqcHIwwhGntEWLkO3CcB4biNEIFACH5BAUKAAMALAQADAAMAAYAAAIWnDSpAHa4GHgohCHbGdbipnBdSHphAQAh+QQFCgADACwCAAgACgAKAAACF5w0qXa4fF6KUoVQ75UaA7Bs3yeNYAkWACH5BAUKAAMALAIABAAGAAwAAAIXnCU2iMfaRghqTmMp1moAoHyfIYIkWAAAOw==");
  1026. }
  1027. .instant-youtube-container:hover .ytp-large-play-button-svg {
  1028. fill: #CC181E;
  1029. }
  1030. .instant-youtube-container .instant-youtube-link {
  1031. display: block;
  1032. position: absolute;
  1033. width: 20em;
  1034. height: 20px;
  1035. top: 50%;
  1036. left: 0;
  1037. right: 0;
  1038. margin: 60px auto;
  1039. padding: 0;
  1040. border: none;
  1041. text-align: center;
  1042. text-decoration: none;
  1043. text-shadow: 1px 1px 3px black;
  1044. font-weight: bold;
  1045. color: white;
  1046. }
  1047. .instant-youtube-container span.instant-youtube-link {
  1048. z-index: 8;
  1049. font-weight: normal;
  1050. font-size: 12px;
  1051. }
  1052. .instant-youtube-container .instant-youtube-link:hover {
  1053. text-decoration: underline;
  1054. color: white;
  1055. background: transparent;
  1056. }
  1057. .instant-youtube-container iframe {
  1058. z-index: 10;
  1059. }
  1060. .instant-youtube-container .instant-youtube-title {
  1061. z-index: 9;
  1062. display: block;
  1063. position: absolute;
  1064. width: auto;
  1065. top: 0;
  1066. left: 0;
  1067. right: 0;
  1068. margin: 0;
  1069. padding: 7px;
  1070. border: none;
  1071. text-shadow: 1px 1px 2px black;
  1072. text-align: center;
  1073. text-decoration: none;
  1074. color: white;
  1075. background-color: rgba(0, 0, 0, 0.5);
  1076. }
  1077. .instant-youtube-container .instant-youtube-title strong {
  1078. font: bold 14px/1.0 sans-serif, Arial, Helvetica, Verdana;
  1079. }
  1080. .instant-youtube-container .instant-youtube-title strong:after {
  1081. content: " - $tl:'watch on Youtube'";
  1082. font-weight: normal;
  1083. margin-right: 1ex;
  1084. }
  1085. .instant-youtube-container .instant-youtube-title span {
  1086. color: inherit;
  1087. }
  1088. .instant-youtube-container .instant-youtube-title span:before {
  1089. content: "(";
  1090. }
  1091. .instant-youtube-container .instant-youtube-title span:after {
  1092. content: ")";
  1093. }
  1094. @-webkit-keyframes instant-youtube-fadein {
  1095. from { opacity: 0 }
  1096. to { opacity: 1 }
  1097. }
  1098. @-moz-keyframes instant-youtube-fadein {
  1099. from { opacity: 0 }
  1100. to { opacity: 1 }
  1101. }
  1102. @keyframes instant-youtube-fadein {
  1103. from { opacity: 0 }
  1104. to { opacity: 1 }
  1105. }
  1106. .instant-youtube-container:not(:hover) .instant-youtube-title[hidden] {
  1107. display: none;
  1108. margin: 0;
  1109. }
  1110. .instant-youtube-container .instant-youtube-title:hover {
  1111. text-decoration: underline;
  1112. }
  1113. .instant-youtube-container .instant-youtube-title strong {
  1114. color: white;
  1115. }
  1116. .instant-youtube-container .instant-youtube-options-button {
  1117. opacity: 0.6;
  1118. position: absolute;
  1119. right: 0;
  1120. bottom: 0;
  1121. margin: 0;
  1122. padding: 1.5ex 2ex;
  1123. font-size: 11px;
  1124. text-shadow: 1px 1px 2px black;
  1125. color: white;
  1126. }
  1127. .instant-youtube-container .instant-youtube-options-button:hover {
  1128. opacity: 1;
  1129. background: rgba(0, 0, 0, 0.5);
  1130. }
  1131. .instant-youtube-container .instant-youtube-options {
  1132. display: flex;
  1133. position: absolute;
  1134. right: 0;
  1135. bottom: 0;
  1136. margin: 0;
  1137. padding: 2ex 1ex 2ex 2ex;
  1138. flex-direction: column;
  1139. align-items: flex-start;
  1140. line-height: 1.5;
  1141. text-align: left;
  1142. opacity: 1;
  1143. color: white;
  1144. background: black;
  1145. }
  1146. .instant-youtube-container .instant-youtube-options * {
  1147. width: auto;
  1148. height: auto;
  1149. margin: 0;
  1150. padding: 0;
  1151. font: inherit;
  1152. font-size: 13px;
  1153. vertical-align: middle;
  1154. text-transform: none;
  1155. text-align: left;
  1156. border-radius: 0;
  1157. text-decoration: none;
  1158. color: white;
  1159. background: black;
  1160. }
  1161. .instant-youtube-container .instant-youtube-options > label {
  1162. margin-top: 1ex;
  1163. }
  1164. .instant-youtube-container .instant-youtube-options > label > * {
  1165. display: inline;
  1166. }
  1167. .instant-youtube-container .instant-youtube-options select {
  1168. width: 20ex;
  1169. padding: .5ex .25ex;
  1170. border: 1px solid #444;
  1171. -webkit-appearance: menulist;
  1172. }
  1173. .instant-youtube-container .instant-youtube-options [data-action="size-custom"] input {
  1174. width: 9ex;
  1175. padding: .5ex .5ex .4ex;
  1176. border: 1px solid #666;
  1177. }
  1178. .instant-youtube-container .instant-youtube-options [data-action="buttons"] {
  1179. margin-top: 1em;
  1180. }
  1181. .instant-youtube-container .instant-youtube-options button {
  1182. margin: 0 1ex 0 0;
  1183. padding: .5ex 2ex;
  1184. border: 2px solid gray;
  1185. font-weight: bold;
  1186. }
  1187. .instant-youtube-container .instant-youtube-options button:hover {
  1188. border-color: white;
  1189. }
  1190. .instant-youtube-container .instant-youtube-options > [disabled] {
  1191. opacity: 0.25;
  1192. }
  1193. .instant-youtube-container .instant-youtube-storyboard {
  1194. height: 33%;
  1195. max-height: 90px;
  1196. display: block;
  1197. position: absolute;
  1198. left: 0;
  1199. right: 0;
  1200. bottom: 0;
  1201. overflow: visible;
  1202. overflow-x: visible;
  1203. overflow-y: visible;
  1204. }
  1205. .instant-youtube-container .instant-youtube-storyboard:hover {
  1206. background-color: rgba(0,0,0,0.3);
  1207. }
  1208. .instant-youtube-container .instant-youtube-storyboard[disabled] {
  1209. display:none;
  1210. }
  1211. .instant-youtube-container .instant-youtube-storyboard div {
  1212. display: block;
  1213. position: absolute;
  1214. bottom: 0px;
  1215. pointer-events: none;
  1216. border: 3px solid #888;
  1217. box-shadow: 2px 2px 10px black;
  1218. transition: opacity .25s ease;
  1219. background-color: transparent;
  1220. background-origin: content-box;
  1221. opacity: 0;
  1222. }
  1223. .instant-youtube-container .instant-youtube-storyboard:hover div {
  1224. opacity: 1;
  1225. }
  1226. */}).replace(/\$tl:'(.+?)'/g, function(m, m1) { return _(m1) })
  1227. );
  1228. }