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.2
  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. var iframes = document.getElementsByTagName('iframe');
  40. var oembeds = document.getElementsByTagName('embed');
  41. var persite = (function() {
  42. var rules = [
  43. {host: /(^|\.)google\.\w{2,3}(\.\w{2,3})?$/, class:'g-blk', query: 'a[href*="youtube.com/watch"][data-ved]', eatparent: 1},
  44. {host: /(^|\.)pikabu\.ru$/, class:'b-video', match: '[data-url*="youtube.com/embed"]', attr: 'data-url'},
  45. {host: /(^|\.)androidauthority\.com$/, tag:'iframe', match: '[src*="youtube.com/embed"]', eatparent: '.video-container'},
  46. {host: /(^|\.)lanoire\.wikia\.com$/, tag:'iframe', match: '[src*="youtube.com/embed"]', eatparent: '.inline-video'},
  47. ];
  48. for (var i=0, rule; (i<rules.length) && (rule=rules[i]); i++) {
  49. if (rule.host && location.hostname.match(rule.host) && (rule.class || rule.tag))
  50. return {
  51. nodes: rule.class ? document.getElementsByClassName(rule.class) : document.getElementsByTagName(rule.tag),
  52. match: rule.match ? function(e) { return e.matches(rule.match) ? e : null }
  53. : function(e) { return e.querySelector(rule.query) },
  54. attr: rule.attr,
  55. eatparent: rule.eatparent,
  56. };
  57. }
  58. })();
  59.  
  60. findEmbeds();
  61. injectStylesIfNeeded();
  62. new MutationObserver(findEmbeds).observe(document, {subtree:true, childList:true});
  63.  
  64. document.addEventListener('DOMContentLoaded', function(e) {
  65. injectStylesIfNeeded();
  66. adjustNodesIfNeeded(e);
  67. });
  68. window.addEventListener('resize', adjustNodesIfNeeded, true);
  69. window.addEventListener('message', function(e) {
  70. if (e.data == 'iframe-allowfs') {
  71. $$('iframe:not([allowfullscreen])').some(function(iframe) {
  72. if (iframe.contentWindow == e.source) {
  73. iframe.allowFullscreen = true;
  74. return true;
  75. }
  76. });
  77. if (window != window.top)
  78. window.parent.postMessage('iframe-allowfs', '*');
  79. }
  80. });
  81.  
  82. function findEmbeds(mutations) {
  83. var i, len, e;
  84. if (mutations && mutations.length == 1 && !mutations[0].addedNodes.length)
  85. return;
  86. if (persite)
  87. for (i=0, len=persite.nodes.length; (i<len) && (e=persite.nodes[i]); i++)
  88. if (e = persite.match(e))
  89. processEmbed(e, e.getAttribute(persite.attr));
  90. for (i=0, len=iframes.length; (i<len) && (e=iframes[i]); i++)
  91. if (/youtube\.com(\/|%2F)(embed|v)(\/|%2F)/i.test(e.src))
  92. processEmbed(e);
  93. for (i=0, len=oembeds.length; (i<len) && (e=oembeds[i]); i++)
  94. if (/youtube\.com(\/|%2F)(embed|v)\//i.test(e.src))
  95. processEmbed(e);
  96. }
  97.  
  98. function processEmbed(node, src) {
  99. function decodeEmbedUrl(url) {
  100. return url.indexOf('youtube.com%2Fembed') > 0
  101. ? decodeURIComponent(url.replace(/^.*?(http[^&?=]+?youtube.com%2Fembed[^&]+).*$/i, '$1'))
  102. : url;
  103. }
  104. src = src || node.src || node.href || '';
  105. var n = node;
  106. var np = n.parentNode, npw;
  107. var srcFixed = decodeEmbedUrl(src).replace(/\/(watch\?v=|v\/)/, '/embed/');
  108. if (src.indexOf('cdn.embedly.com/') > 0 ||
  109. resizeMode != 'Original' && np && np.children.length == 1 && !np.className && !np.id)
  110. {
  111. n = location.hostname == 'disqus.com' ? np.parentNode : np;
  112. np = n.parentElement;
  113. }
  114. if (!np ||
  115. !np.parentNode ||
  116. skipCustom && srcFixed.indexOf('enablejsapi=1') > 0 ||
  117. n.className.indexOf('instant-youtube-') >= 0 ||
  118. n.onload // skip some retarded loaders
  119. )
  120. return;
  121.  
  122. var id = srcFixed.match(/(?:embed\/|v[=\/])([^\s,.()\[\]?]+?)(?:[&?\/].*|$)/);
  123. if (!id)
  124. return;
  125. id = id[1];
  126.  
  127. var eatparent = persite && persite.eatparent || 0;
  128. if (typeof eatparent == 'string')
  129. n = np.closest(eatparent) || n, np = n.parentElement;
  130. else
  131. while (eatparent--)
  132. n = np, np = n.parentElement;
  133.  
  134. var div = document.createElement('div');
  135. div.className = 'instant-youtube-container';
  136. div.FYTE = {
  137. state: 'querying',
  138. srcEmbed: srcFixed.replace(/&$/, ''),
  139. videoID: id,
  140. originalWidth: /%/.test(node.width) ? 320 : node.width|0 || n.clientWidth|0,
  141. originalHeight: /%/.test(node.height) ? 200 : node.height|0 || n.clientHeight|0,
  142. };
  143. div.FYTE.srcEmbedFixed = div.FYTE.srcEmbed.replace(/^http:/, 'https:').replace(/&?wmode=\w+/, '').replace(/[?&]feature=oembed/, '');
  144. div.FYTE.srcWatchFixed = div.FYTE.srcEmbedFixed.replace(/\/embed\//, '/watch?v=');
  145.  
  146. var divSize = calcContainerSize(div, n);
  147. var origStyle = getComputedStyle(n);
  148. div.style.cssText = important('background-color:transparent; transition:background-color 2s;' +
  149. (origStyle.hasOwnProperty('position') ? Object.keys(origStyle) : Object.values(origStyle))
  150. .filter(function(k) { return !!k.match(/^(position|left|right|top|bottom)$/) })
  151. .map(function(k) { return k + ':' + origStyle[k] })
  152. .join(';').replace(/\b[^;:]+:(auto|static)(!\s*important)?;/g, '') +
  153. ';min-width:' + Math.min(divSize.w, div.FYTE.originalWidth) + 'px; min-height:' + Math.min(divSize.h, div.FYTE.originalHeight) + 'px' +
  154. ';max-width:' + divSize.w + 'px; height:' + divSize.h + 'px;');
  155. setTimeout(function() { div.style.backgroundColor = '' }, 0);
  156. setTimeout(function() { div.style.transition = '' }, 2000);
  157.  
  158. // consume parents of retardedly positioned videos
  159. if (div.style.position.match('absolute|relative')) {
  160. if (np.children.length == 1 && floatPadding(np, getComputedStyle(np, ':after'), 'Top') >= div.FYTE.originalHeight)
  161. n = np, np = n.parentElement;
  162. div.style.cssText = div.style.cssText.replace(/\b(position|left|top|right|bottom):[^;]+/g, '');
  163. }
  164.  
  165. var wrapper = div.appendChild(document.createElement('div'));
  166. wrapper.className = 'instant-youtube-wrapper';
  167.  
  168. var img = wrapper.appendChild(document.createElement('img'));
  169. img.className = 'instant-youtube-thumbnail';
  170. img.src = 'https://i.ytimg.com/vi/' + id + '/maxresdefault.jpg';
  171. 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;');
  172.  
  173. img.title = _('Shift-click to use alternative player');
  174. img.onload = function(e) {
  175. if (img.naturalWidth <= 120)
  176. return img.onerror(e);
  177. var fitToWidth = true;
  178. if (img.naturalHeight) {
  179. var ratio = img.naturalWidth / img.naturalHeight;
  180. if (ratio > 4.1/3 && ratio < divSize.w/divSize.h) {
  181. img.style.cssText += important('width:auto; height:100%;');
  182. fitToWidth = false;
  183. }
  184. }
  185. if (fitToWidth) {
  186. img.style.cssText += important('width:100%; height:auto;');
  187. }
  188. if (img.videoWidth)
  189. fixThumbnailAR(div);
  190. img.style.opacity = 1;
  191. };
  192. img.onerror = function(e) {
  193. if (img.src.indexOf('maxresdefault') > 0)
  194. img.src = img.src.replace('maxresdefault','hqdefault');
  195. };
  196.  
  197. GM_xmlhttpRequest({
  198. method: 'GET',
  199. url: 'https://www.youtube.com/get_video_info?video_id=' + div.FYTE.videoID + '&el=detailpage',
  200. headers: {'Accept-Encoding': 'gzip'},
  201. context: div,
  202. onload: parseVideoInfo
  203. });
  204.  
  205. translateHTML(wrapper, 'beforeend', '\
  206. <a class="instant-youtube-title" target="_blank" href="' + div.FYTE.srcWatchFixed + '">&nbsp;</a>\
  207. <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>\
  208. <span tl class="instant-youtube-link">' + (playDirectly ? 'Play with Youtube player' : 'Play directly (up to 720p)') + '</span>\
  209. <div class="instant-youtube-storyboard"></div>\
  210. <div tl class="instant-youtube-options-button">Options</div>\
  211. ');
  212.  
  213. if (np.localName == 'object')
  214. n = np, np = n.parentElement;
  215. np.insertBefore(div, n);
  216. n.remove();
  217.  
  218. if (srcFixed.indexOf('autoplay=1') > 0)
  219. return startPlaying(div);
  220.  
  221. div.addEventListener('click', clickHandler);
  222. if (showStoryboard)
  223. updateHoverHandler(div);
  224. }
  225.  
  226. function updateHoverHandler(div) {
  227. if (!showStoryboard) {
  228. var storyboard = $(div, '.instant-youtube-storyboard');
  229. if (storyboard.innerHTML)
  230. storyboard.innerHTML = '';
  231. return;
  232. }
  233. div.addEventListener('mouseover', storyboardHoverHandler);
  234.  
  235. function storyboardHoverHandler(e) {
  236. if (!showStoryboard)
  237. return;
  238. div.removeEventListener('mouseover', storyboardHoverHandler);
  239. $(div, '.instant-youtube-storyboard').innerHTML = '123'
  240. .replace(/./g, '<div><img src="https://i.ytimg.com/vi/' + div.FYTE.videoID + '/$&.jpg"></div>');
  241. }
  242. }
  243.  
  244. function adjustNodesIfNeeded(e) {
  245. if (resizeMode != 'Original' && !adjustNodesIfNeeded.scheduled)
  246. adjustNodesIfNeeded.scheduled = setTimeout(function() {
  247. adjustNodes(e);
  248. adjustNodesIfNeeded.scheduled = 0;
  249. }, 16);
  250. }
  251.  
  252. function adjustNodes(e, clickedContainer) {
  253. var force = !!clickedContainer;
  254. var nearest = force ? clickedContainer : null;
  255.  
  256. var vids = $$('div.instant-youtube-container');
  257.  
  258. if (!nearest && e.type != 'DOMContentLoaded') {
  259. var minDistance = window.innerHeight*3/4 |0;
  260. var nearTargetY = window.innerHeight/2;
  261. vids.forEach(function(n) {
  262. var bounds = n.getBoundingClientRect();
  263. var distance = Math.abs((bounds.bottom + bounds.top)/2 - nearTargetY);
  264. if (distance < minDistance) {
  265. minDistance = distance;
  266. nearest = n;
  267. }
  268. });
  269. }
  270.  
  271. if (nearest) {
  272. var bounds = nearest.getBoundingClientRect();
  273. var nearestCenterYpct = (bounds.top + bounds.bottom)/2 / window.innerHeight;
  274. }
  275.  
  276. var resized = false;
  277.  
  278. vids.forEach(function(n) {
  279. var size = calcContainerSize(n);
  280. var w = size.w, h = size.h;
  281.  
  282. // prevent parent clipping
  283. for (var e=n.parentElement, style; e && (style=getComputedStyle(e)); e=e.parentElement)
  284. if ((style.overflow+style.overflowX+style.overflowY).match(/hidden|scroll/))
  285. if (n.offsetTop < e.clientHeight / 2 && n.offsetTop + n.clientHeight > e.clientHeight)
  286. e.style.cssText = e.style.cssText.replace(/\boverflow(-[xy])?:[^;]+/g, '') +
  287. important('overflow:visible;overflow-x:visible;overflow-y:visible;');
  288.  
  289. if (force && Math.abs(w - parseFloat(n.style.maxWidth)) <= 2)
  290. return;
  291.  
  292. if (n.style.maxWidth != w + 'px') n.style.maxWidth = w + 'px';
  293. if (n.style.height != h + 'px') n.style.height = h + 'px';
  294. if (parseFloat(n.style.minWidth) > w) n.style.minWidth = n.style.maxWidth;
  295. if (parseFloat(n.style.minHeight) > h) n.style.minHeight = n.style.height;
  296.  
  297. var video = $(n, 'video');
  298. if (video) {
  299. video.width = w;
  300. video.height = h;
  301. }
  302.  
  303. fixThumbnailAR(n);
  304. resized = true;
  305. });
  306.  
  307. if (resized && nearest)
  308. setTimeout(function() {
  309. var bounds = nearest.getBoundingClientRect();
  310. var h = bounds.bottom - bounds.top;
  311. var projectedCenterY = nearestCenterYpct * window.innerHeight;
  312. var projectedTop = projectedCenterY - h/2;
  313. var safeTop = Math.min(Math.max(0, projectedTop), window.innerHeight - h);
  314. window.scrollBy(0, bounds.top - safeTop);
  315. }, 16);
  316. }
  317.  
  318. function calcContainerSize(div, origNode) {
  319. var w, h;
  320. origNode = origNode || div;
  321. switch (resizeMode) {
  322. case 'Original':
  323. w = div.FYTE.originalWidth;
  324. h = div.FYTE.originalHeight;
  325. break;
  326. case 'Custom':
  327. w = resizeWidth;
  328. h = resizeHeight;
  329. break;
  330. case '1080p':
  331. case '720p':
  332. case '480p':
  333. case '360p':
  334. h = parseInt(resizeMode);
  335. w = h / 9 * 16;
  336. break;
  337. default: // fit-to-width mode
  338. var n = origNode;
  339. do {
  340. n = n.parentElement;
  341. // find parent node with nonzero width (i.e. independent of our video element)
  342. } while (n && !(w = n.clientWidth));
  343. if (w)
  344. h = w / 16 * 9;
  345. else {
  346. w = origNode.clientWidth;
  347. h = origNode.clientHeight;
  348. }
  349. }
  350. var np = origNode.parentElement;
  351. var style = getComputedStyle(np);
  352. var parentWidth = parseFloat(style.width) - floatPadding(np, style, 'Left') - floatPadding(np, style, 'Right');
  353. if (parentWidth > 0 && parentWidth < w) {
  354. h = parentWidth / w * h;
  355. w = parentWidth;
  356. }
  357. if (resizeMode == 'Fit to width' && h < div.FYTE.originalHeight*0.9)
  358. h = Math.min(div.FYTE.originalHeight, w / div.FYTE.originalWidth * div.FYTE.originalHeight);
  359.  
  360. return {w:Math.round(w), h:Math.round(h)};
  361. }
  362.  
  363. function parseVideoInfo(response) {
  364. var div = response.context;
  365. var txt = response.responseText;
  366. var info = tryCatch(function() { return JSON.parse(txt.replace(/(\w+)=?(.*?)(&|$)/g, '"$1":"$2",').replace(/^(.+?),?$/, '{$1}')) });
  367. var videoSources = [];
  368.  
  369. // parse width & height to adjust the thumbnail
  370. var m = decodeURIComponent(txt).match(/\b(\d+)x(\d+)\b/);
  371. if (m)
  372. fixThumbnailAR(div, m[1]|0, m[2]|0);
  373.  
  374. // parse video sources
  375. if (info.url_encoded_fmt_stream_map && info.fmt_list) {
  376. var streams = {};
  377. decodeURIComponent(info.url_encoded_fmt_stream_map).split(',').forEach(function(stream) {
  378. var params = {};
  379. stream.split('&').forEach(function(kv) {
  380. params[kv.split('=')[0]] = decodeURIComponent(kv.split('=')[1]);
  381. });
  382. streams[params.itag] = params;
  383. });
  384. decodeURIComponent(info.fmt_list).split(',').forEach(function(fmt) {
  385. var itag = fmt.split('/')[0];
  386. var dimensions = fmt.split('/')[1];
  387. var stream = streams[itag];
  388. if (stream) {
  389. videoSources.push({
  390. src: stream.url,
  391. title: stream.quality + ', ' + dimensions + ', ' + stream.type
  392. });
  393. }
  394. });
  395. } else {
  396. var rx = /url=([^=]+?mime%3Dvideo%252F(?:mp4|webm)[^=]+?)(?:,quality|,itag|.u0026)/g;
  397. var text = decodeURIComponent(txt).split('url_encoded_fmt_stream_map')[1];
  398. while (m = rx.exec(text)) {
  399. videoSources.push({
  400. src: decodeURIComponent(decodeURIComponent(m[1]))
  401. });
  402. }
  403. }
  404.  
  405. var duration = info.length_seconds|0 || '';
  406. if (duration) {
  407. var d = new Date(null);
  408. d.setSeconds(duration);
  409. duration = d.toISOString().replace(/^.+?T[0:]{0,4}(.+?)\..+$/, '<span>$1</span>');
  410. }
  411. var title = decodeURIComponent(info.title || info.reason || '').replace(/\+/g, ' ');
  412. if (title || duration) {
  413. $(div, '.instant-youtube-title').innerHTML = (title ? '<strong>' + title + '</strong>' : '') + duration;
  414. }
  415. if (info.reason)
  416. div.setAttribute('disabled', '');
  417.  
  418. if (videoSources.length)
  419. div.FYTE.videoSources = videoSources;
  420.  
  421. injectStylesIfNeeded();
  422.  
  423. if (div.FYTE.state == 'scheduled play')
  424. setTimeout(function() { startPlayingDirectly(div) }, 0);
  425.  
  426. div.FYTE.state = '';
  427. }
  428.  
  429. function fixThumbnailAR(div, w, h) {
  430. var img = $(div, 'img');
  431. var thw = img.naturalWidth, thh = img.naturalHeight;
  432. if (w && h) { // means thumbnail is still loading
  433. img.videoWidth = w;
  434. img.videoHeight = h;
  435. return;
  436. }
  437. w = img.videoWidth;
  438. h = img.videoHeight;
  439. var divw = div.clientWidth, divh = div.clientHeight;
  440. // if both video and thumbnail are 4:3, fit the image to height
  441. //console.log(divw, divh, thw, thh, w, h, h/w*divw / divh - 1, thh/thw*divw / divh - 1);
  442. if (Math.abs(h/w*divw / divh - 1) > 0.05 && Math.abs(thh/thw*divw / divh - 1) > 0.05) {
  443. img.style.maxHeight = img.clientHeight + 'px';
  444. if (!img.videoWidth) // skip animation if thumbnail is already loaded
  445. img.style.transition = 'height 1s ease, margin-top 1s ease';
  446. setTimeout(function() {
  447. img.style.maxHeight = 'none';
  448. img.style.cssText += important(h/w >= divh/divw ? 'width:auto; height:100%;' : 'width:100%; height:auto;');
  449. setTimeout(function() {
  450. img.style.transition = '';
  451. }, 1000);
  452. }, 0);
  453. }
  454. }
  455.  
  456. function clickHandler(e) {
  457. if (e.target.href)
  458. return;
  459. if (e.target.matches('.instant-youtube-options-button')) {
  460. showOptions(e);
  461. e.preventDefault();
  462. e.stopPropagation();
  463. return;
  464. }
  465. if (e.target.matches('.instant-youtube-options, .instant-youtube-options *'))
  466. return;
  467.  
  468. e.preventDefault();
  469. e.stopPropagation();
  470.  
  471. var alternateMode = e.shiftKey || e.target.className == 'instant-youtube-link';
  472. startPlaying(e.target.closest('.instant-youtube-container'), alternateMode);
  473. }
  474.  
  475. function startPlaying(div, alternateMode) {
  476. div.removeEventListener('click', clickHandler);
  477.  
  478. $$(div, '.instant-youtube-wrapper > *:not(img):not(a)').forEach(function(e) { e.style.cssText = 'display:none!important' });
  479. $(div, 'svg').outerHTML = '<span class="instant-youtube-loading-button"></span>';
  480.  
  481. if (window != window.top)
  482. window.parent.postMessage('iframe-allowfs', '*');
  483.  
  484. if ((!!playDirectly + !!alternateMode == 1) && (div.FYTE.videoSources || div.FYTE.state == 'querying')) {
  485. if (div.FYTE.videoSources)
  486. startPlayingDirectly(div);
  487. else {
  488. // playback will start in parseVideoInfo
  489. div.FYTE.state = 'scheduled play';
  490. // fallback to iframe in 5s
  491. setTimeout(function() {
  492. if (div.FYTE.state) {
  493. div.FYTE.state = '';
  494. switchToIFrame.call(div);
  495. }
  496. }, 5000);
  497. }
  498. }
  499. else
  500. switchToIFrame.call(div);
  501. }
  502.  
  503. function startPlayingDirectly(div) {
  504. var video = document.createElement('video');
  505. video.controls = true;
  506. video.autoplay = true;
  507. video.style.cssText = important(
  508. 'position:absolute; left:0; top:0; right:0; padding:0; margin:auto; opacity:0; transition:opacity 2s;' +
  509. 'width:100%; height:100%;');
  510. video.className = 'instant-youtube-embed';
  511. video.volume = GM_getValue('volume', 0.5);
  512.  
  513. div.FYTE.videoSources.forEach(function(src) {
  514. var srcdom = video.appendChild(document.createElement('source'));
  515. Object.keys(src).forEach(function(k) { srcdom[k] = src[k] });
  516. srcdom.onerror = switchToIFrame.bind(div);
  517. });
  518.  
  519.  
  520. if (window.chrome) {
  521. video.addEventListener('click', function(e) {
  522. this.paused ? this.play() : this.pause();
  523. });
  524. }
  525. video.interval = (function() {
  526. return setInterval(function() {
  527. if (video.volume != GM_getValue('volume', 0.5))
  528. GM_setValue('volume', video.volume);
  529. }, 1000);
  530. })();
  531. var title = $(div, '.instant-youtube-title');
  532. if (title) {
  533. video.onpause = function() { title.removeAttribute('hidden') };
  534. video.onplay = function() { title.setAttribute('hidden', true) };
  535. }
  536. video.onloadeddata = function(e) {
  537. pauseOtherVideos(this);
  538. div.style.cssText += 'contain:none!important'; // allow fullscreen
  539. div.firstElementChild.appendChild(this);
  540. this.style.opacity = 1;
  541. var img = $(div, 'img');
  542. img.style.transition = 'opacity 1s';
  543. img.style.opacity = 0;
  544. };
  545. }
  546.  
  547. function switchToIFrame(e) {
  548. var div = this;
  549. var wrapper = div.firstElementChild;
  550. if (e) {
  551. console.log('[FYTE] Direct linking canceled on %s, switching to IFRAME player', div.FYTE.srcEmbed);
  552. var video = e.target ? e.target.closest('video') : e.path && e.path[e.path.length-1];
  553. while (video.lastElementChild)
  554. video.lastElementChild.remove();
  555. }
  556.  
  557. wrapper.insertAdjacentHTML('beforeend',
  558. '<iframe class="instant-youtube-embed" allowtransparency="true" src="' + div.FYTE.srcEmbedFixed +
  559. (div.FYTE.srcEmbedFixed.indexOf('?') > 0 ? '&' : '?') +
  560. 'html5=1' +
  561. '&autoplay=1' +
  562. '&autohide=2' +
  563. '&border=0' +
  564. '&controls=1' +
  565. '&fs=1' +
  566. '&showinfo=1' +
  567. '&ssl=1' +
  568. '&theme=dark' +
  569. '&enablejsapi=1' +
  570. '" frameborder="0" allowfullscreen width="100%" height="100%"></iframe>');
  571.  
  572. wrapper.lastElementChild.onload = function() {
  573. pauseOtherVideos(this);
  574. this.style.cssText = important(
  575. 'position:absolute; left:0; top:0; right:0; padding:0; margin:auto; opacity:1; transition:opacity 2s;');
  576.  
  577. div.setAttribute('iframe', '');
  578. div.style.cssText += 'contain:none!important'; // allow fullscreen
  579. setTimeout(function() {
  580. $(div, 'img').style.display = 'none';
  581. var title = $(div, '.instant-youtube-title');
  582. if (title)
  583. title.remove();
  584. }, 2000);
  585. };
  586. }
  587.  
  588. function pauseOtherVideos(activePlayer) {
  589. $$(activePlayer.ownerDocument, '.instant-youtube-embed').forEach(function(v) {
  590. if (v == activePlayer)
  591. return;
  592. switch (v.localName) {
  593. case 'video':
  594. if (!v.paused)
  595. v.pause();
  596. break;
  597. case 'iframe':
  598. try { v.contentWindow.postMessage('{"event":"command", "func":"pauseVideo", "args":""}', '*') } catch(e) {}
  599. break;
  600. }
  601. });
  602. }
  603.  
  604. function showOptions(e) {
  605. var optionsButton = e.target;
  606. translateHTML(optionsButton, 'afterend', '\
  607. <div class="instant-youtube-options">\
  608. <label tl style="width: 100% !important;">Size:<br>\
  609. <select data-action="size-mode">\
  610. <option tl value="Original">Original\
  611. <option tl value="Fit to width">Fit to width\
  612. <option>360p\
  613. <option>480p\
  614. <option>720p\
  615. <option>1080p\
  616. <option tl value="Custom">Custom...\
  617. </select>\
  618. </label>\
  619. <label data-action="size-custom" ' + (resizeMode != 'Custom' ? 'disabled' : '') + '>\
  620. <input type="number" min="320" max="9999" tl-placeholder="width" data-action="width" step="1" value="' + (resizeWidth||'') + '">\
  621. x\
  622. <input type="number" min="240" max="9999" tl-placeholder="height" data-action="height" step="1" value="' + (resizeHeight||'') + '">\
  623. </label>\
  624. <label tl="content,title" title="Show 3 thumbnails on mouse hover: at 25%, 50%, 75% of video duration">\
  625. <input data-action="storyboard" type="checkbox" ' + (showStoryboard ? 'checked' : '') + '>\
  626. Storyboard thumbs\
  627. </label>\
  628. <label tl="content,title" title="Tip: shift-clicking thumbnails will use alternative player">\
  629. <input data-action="direct" type="checkbox" ' + (playDirectly ? 'checked' : '') + '>\
  630. Play directly\
  631. </label>\
  632. <label tl="content,title" title="Do not process customized videos with enablejsapi=1 parameter (requires page reload)">\
  633. <input data-action="safe" type="checkbox" ' + (skipCustom ? 'checked' : '') + '>\
  634. Safe\
  635. </label>\
  636. <span data-action="buttons">\
  637. <button tl data-action="ok">OK</button>\
  638. <button tl data-action="cancel">Cancel</button>\
  639. </span>\
  640. </div>\
  641. ');
  642. var options = optionsButton.nextElementSibling;
  643.  
  644. options.addEventListener('keydown', function(e) {
  645. if (e.target.localName == 'input' &&
  646. !e.shiftKey && !e.altKey && !e.metaKey && !e.ctrlKey && e.key.match(/[.,]/))
  647. return false;
  648. });
  649.  
  650. $(options, '[data-action="size-mode"]').value = resizeMode;
  651. $(options, '[data-action="size-mode"]').addEventListener('change', function() {
  652. var v = this.value != 'Custom';
  653. var e = $(options, '[data-action="size-custom"]');
  654. e.children[0].disabled = e.children[1].disabled = v;
  655. v ? e.setAttribute('disabled', '') : e.removeAttribute('disabled');
  656. });
  657.  
  658. $(options, '[data-action="buttons"]').addEventListener('click', function(e) {
  659. if (e.target.dataset.action != 'ok') {
  660. options.remove();
  661. return;
  662. }
  663. var v, shouldAdjust;
  664. if (resizeMode != (v = $(options, '[data-action="size-mode"]').value)) {
  665. GM_setValue('resize', resizeMode = v);
  666. shouldAdjust = true;
  667. }
  668. if (resizeMode == 'Custom') {
  669. var w = $(options, '[data-action="width"]').value |0;
  670. var h = $(options, '[data-action="height"]').value |0;
  671. if (resizeWidth != w || resizeHeight != h) {
  672. updateCustomSize(w, h);
  673. GM_setValue('width', resizeWidth);
  674. GM_setValue('height', resizeHeight);
  675. shouldAdjust = true;
  676. }
  677. }
  678. if (showStoryboard != (v = $(options, '[data-action="storyboard"]').checked)) {
  679. GM_setValue('showStoryboard', showStoryboard = v);
  680. $$('.instant-youtube-container').forEach(updateHoverHandler);
  681. }
  682. if (playDirectly != (v = $(options, '[data-action="direct"]').checked)) {
  683. GM_setValue('playHTML5', playDirectly = v);
  684. $$('.instant-youtube-container .instant-youtube-link').forEach(function(e) {
  685. e.textContent = playDirectly ? 'Play with Youtube player' : 'Play directly (up to 720p)';
  686. });
  687. }
  688. if (skipCustom != (v = $(options, '[data-action="safe"]').checked)) {
  689. GM_setValue('skipCustom', skipCustom = v);
  690. }
  691.  
  692. options.remove();
  693.  
  694. if (shouldAdjust)
  695. adjustNodes(e, e.target.closest('.instant-youtube-container'));
  696. });
  697. }
  698.  
  699. function updateCustomSize(w, h) {
  700. resizeWidth = Math.min(9999, Math.max(320, w|0 || resizeWidth|0));
  701. resizeHeight = Math.min(9999, Math.max(240, h|0 || resizeHeight|0));
  702. }
  703.  
  704. function important(cssText) {
  705. return cssText.replace(/;/g, '!important;');
  706. }
  707.  
  708. function tryCatch(func) {
  709. try {
  710. return func();
  711. } catch(e) {
  712. console.log(e);
  713. }
  714. }
  715.  
  716. function getFunctionComment(fn) {
  717. return fn.toString().match(/\/\*([\s\S]*?)\*\/\s*\}$/)[1];
  718. }
  719.  
  720. function $(selORnode, sel) {
  721. return sel ? selORnode.querySelector(sel)
  722. : document.querySelector(selORnode);
  723. }
  724.  
  725. function $$(selORnode, sel) {
  726. return Array.prototype.slice.call(
  727. sel ? selORnode.querySelectorAll(sel)
  728. : document.querySelectorAll(selORnode));
  729. }
  730.  
  731. // fix dumb Firefox bug
  732. function floatPadding(node, style, dir) {
  733. var padding = style['padding' + dir];
  734. if (padding.indexOf('%') < 0)
  735. return parseFloat(padding);
  736. return parseFloat(padding) * (parseFloat(style.width) || node.clientWidth) / 100;
  737. }
  738.  
  739. function translateHTML(baseElement, place, html) {
  740. var tmp = document.createElement('div');
  741. tmp.innerHTML = html;
  742. $$(tmp, '[tl]').forEach(function(node) {
  743. (node.getAttribute('tl') || 'content').split(',').forEach(function(what) {
  744. var child, src, tl;
  745. if (what == 'content') {
  746. for (var i = node.childNodes.length-1, n; (i>=0) && (n=node.childNodes[i]); i--) {
  747. if (n.nodeType == Node.TEXT_NODE && n.textContent.trim()) {
  748. child = n;
  749. break;
  750. }
  751. }
  752. } else
  753. child = node.getAttributeNode(what);
  754. if (!child)
  755. return;
  756. src = child.textContent;
  757. srcTrimmed = src.trim();
  758. tl = src.replace(srcTrimmed, _(srcTrimmed));
  759. if (src != tl)
  760. child.textContent = tl;
  761. });
  762. });
  763. baseElement.insertAdjacentHTML(place, tmp.innerHTML);
  764. }
  765.  
  766. function initTL(src) {
  767. var tlSource = {
  768. 'watch on Youtube': {
  769. 'ru': 'открыть на Youtube',
  770. },
  771. 'Play with Youtube player': {
  772. 'ru': 'Включить плеер Youtube',
  773. },
  774. 'Play directly (up to 720p)': {
  775. 'ru': 'Включить напрямую (макс. 720p)',
  776. },
  777. 'Shift-click to use alternative player': {
  778. 'ru': 'Shift-клик для смены типа плеера',
  779. },
  780. 'Options': {
  781. 'ru': 'Опции',
  782. },
  783. 'Size:': {
  784. 'ru': 'Размер:',
  785. },
  786. 'Original': {
  787. 'ru': 'Исходный',
  788. },
  789. 'Fit to width': {
  790. 'ru': 'На всю ширину',
  791. },
  792. 'Custom...': {
  793. 'ru': 'Настроить...',
  794. },
  795. 'width': {
  796. 'ru': 'ширина',
  797. },
  798. 'height': {
  799. 'ru': 'высота',
  800. },
  801. 'Storyboard thumbs': {
  802. 'ru': 'Раскадровка',
  803. },
  804. 'Show 3 thumbnails on mouse hover: at 25%, 50%, 75% of video duration': {
  805. 'ru': 'Показывать 3 миникадра при наведении мыши: моменты на 25%, 50%, 75% времени видео',
  806. },
  807. 'Play directly': {
  808. 'ru': 'Плеер браузера',
  809. },
  810. 'Tip: shift-clicking thumbnails will use alternative player': {
  811. 'ru': 'Удерживайте клавишу Shift при щелчке на картинке для альтернативного плеера',
  812. },
  813. 'Safe': {
  814. 'ru': 'Консервативный режим',
  815. },
  816. 'Do not process customized videos with enablejsapi=1 parameter (requires page reload)': {
  817. 'ru': 'Не обрабатывать нестандартные видео с параметром enablejsapi=1 (подействует после обновления страницы)',
  818. },
  819. 'OK': {
  820. 'ru': 'ОК',
  821. },
  822. 'Cancel': {
  823. 'ru': 'Оменить',
  824. },
  825. };
  826. var browserLang = navigator.language || navigator.languages && navigator.languages[0] || '';
  827. var browserLangMajor = browserLang.replace(/-.+/, '');
  828. var tl = {};
  829. Object.keys(tlSource).forEach(function(k) {
  830. var langs = tlSource[k];
  831. var text = langs[browserLang] || langs[browserLangMajor];
  832. if (text)
  833. tl[k] = text;
  834. });
  835. return function(src) { return tl[src] || src };
  836. }
  837.  
  838. function injectStylesIfNeeded() {
  839. var styledom = $('style#instant-youtube-styles');
  840. if (styledom) {
  841. // move our rules to the end of HEAD to increase CSS specificity
  842. if (styledom.nextElementSibling && document.head)
  843. document.head.insertBefore(styledom, null);
  844. return;
  845. }
  846. styledom = (document.head || document.documentElement).appendChild(document.createElement('style'));
  847. styledom.id = 'instant-youtube-styles';
  848. styledom.textContent = important(getFunctionComment(function() { /*
  849. .instant-youtube-container {
  850. contain: strict;
  851. position: relative;
  852. overflow: hidden;
  853. cursor: pointer;
  854. padding: 0;
  855. margin: 0;
  856. font: normal 14px/1.0 sans-serif, Arial, Helvetica, Verdana;
  857. text-align: center;
  858. background: black;
  859. }
  860. .instant-youtube-container[disabled] {
  861. background: #888;
  862. }
  863. .instant-youtube-container[disabled] .instant-youtube-storyboard {
  864. display: none;
  865. }
  866. .instant-youtube-container .instant-youtube-wrapper {
  867. width: 100%;
  868. height: 100%;
  869. }
  870. .instant-youtube-container .instant-youtube-play-button {
  871. display: block;
  872. position: absolute;
  873. width: 85px;
  874. height: 60px;
  875. left: 0;
  876. right: 0;
  877. top: 0;
  878. bottom: 0;
  879. margin: auto;
  880. }
  881. .instant-youtube-container .instant-youtube-loading-button {
  882. display: block;
  883. position: absolute;
  884. width: 20px;
  885. height: 20px;
  886. left: 0;
  887. right: 0;
  888. top: 0;
  889. bottom: 0;
  890. padding: 0;
  891. margin: auto;
  892. background: url("data:image/gif;base64,R0lGODlhFAAUAJEDAMzMzLOzs39/f////yH/C05FVFNDQVBFMi4wAwEAAAAh+QQFCgADACwAAAAAFAAUAAACPJyPqcuNItyCUJoQBo0ANIxpXOctYHaQpYkiHfM2cUrCNT0nqr4uudsz/IC5na/2Mh4Hu+HR6YBaplRDAQAh+QQFCgADACwEAAIADAAGAAACFpwdcYupC8BwSogR46xWZHl0l8ZYQwEAIfkEBQoAAwAsCAACAAoACgAAAhccMKl2uHxGCCvO+eTNmishcCCYjWEZFgAh+QQFCgADACwMAAQABgAMAAACFxwweaebhl4K4VE6r61DiOd5SfiN5VAAACH5BAUKAAMALAgACAAKAAoAAAIYnD8AeKqcHIwwhGntEWLkO3CcB4biNEIFACH5BAUKAAMALAQADAAMAAYAAAIWnDSpAHa4GHgohCHbGdbipnBdSHphAQAh+QQFCgADACwCAAgACgAKAAACF5w0qXa4fF6KUoVQ75UaA7Bs3yeNYAkWACH5BAUKAAMALAIABAAGAAwAAAIXnCU2iMfaRghqTmMp1moAoHyfIYIkWAAAOw==");
  893. }
  894. .instant-youtube-container:hover .ytp-large-play-button-svg {
  895. fill: #CC181E;
  896. }
  897. .instant-youtube-container .instant-youtube-link {
  898. display: block;
  899. position: absolute;
  900. width: 20em;
  901. height: 20px;
  902. top: 50%;
  903. left: 0;
  904. right: 0;
  905. margin: 60px auto;
  906. padding: 0;
  907. border: none;
  908. text-align: center;
  909. text-decoration: none;
  910. text-shadow: 1px 1px 3px black;
  911. font-weight: bold;
  912. color: white;
  913. }
  914. .instant-youtube-container span.instant-youtube-link {
  915. z-index: 8;
  916. font-weight: normal;
  917. font-size: 12px;
  918. }
  919. .instant-youtube-container .instant-youtube-link:hover {
  920. text-decoration: underline;
  921. color: white;
  922. background: transparent;
  923. }
  924. .instant-youtube-container iframe {
  925. z-index: 10;
  926. }
  927. .instant-youtube-container .instant-youtube-title {
  928. z-index: 9;
  929. display: block;
  930. position: absolute;
  931. width: 100%;
  932. top: 0;
  933. left: 0;
  934. right: 0;
  935. margin: 0;
  936. padding: 7px;
  937. border: none;
  938. text-shadow: 1px 1px 2px black;
  939. text-align: center;
  940. text-decoration: none;
  941. color: white;
  942. background-color: rgba(0, 0, 0, 0.5);
  943. }
  944. .instant-youtube-container .instant-youtube-title strong {
  945. font: bold 14px/1.0 sans-serif, Arial, Helvetica, Verdana;
  946. }
  947. .instant-youtube-container .instant-youtube-title strong:after {
  948. content: " - $tl:'watch on Youtube'";
  949. font-weight: normal;
  950. margin-right: 1ex;
  951. }
  952. .instant-youtube-container .instant-youtube-title span {
  953. color: inherit;
  954. }
  955. .instant-youtube-container .instant-youtube-title span:before {
  956. content: "(";
  957. }
  958. .instant-youtube-container .instant-youtube-title span:after {
  959. content: ")";
  960. }
  961. @-webkit-keyframes instant-youtube-fadein {
  962. from { opacity: 0 }
  963. to { opacity: 1 }
  964. }
  965. @-moz-keyframes instant-youtube-fadein {
  966. from { opacity: 0 }
  967. to { opacity: 1 }
  968. }
  969. @keyframes instant-youtube-fadein {
  970. from { opacity: 0 }
  971. to { opacity: 1 }
  972. }
  973. .instant-youtube-container:not(:hover) .instant-youtube-title[hidden] {
  974. display: none;
  975. margin: 0;
  976. }
  977. .instant-youtube-container .instant-youtube-title:hover {
  978. text-decoration: underline;
  979. }
  980. .instant-youtube-container .instant-youtube-title strong {
  981. color: white;
  982. }
  983. .instant-youtube-container .instant-youtube-options-button {
  984. opacity: 0.6;
  985. position: absolute;
  986. right: 0;
  987. bottom: 0;
  988. margin: 0;
  989. padding: 1.5ex 2ex;
  990. font-size: 11px;
  991. text-shadow: 1px 1px 2px black;
  992. color: white;
  993. }
  994. .instant-youtube-container .instant-youtube-options-button:hover {
  995. opacity: 1;
  996. background: rgba(0, 0, 0, 0.5);
  997. }
  998. .instant-youtube-container .instant-youtube-options {
  999. display: flex;
  1000. position: absolute;
  1001. right: 0;
  1002. bottom: 0;
  1003. margin: 0;
  1004. padding: 2ex 1ex 2ex 2ex;
  1005. flex-direction: column;
  1006. align-items: flex-start;
  1007. line-height: 1.5;
  1008. text-align: left;
  1009. opacity: 1;
  1010. color: white;
  1011. background: black;
  1012. }
  1013. .instant-youtube-container .instant-youtube-options * {
  1014. width: auto;
  1015. height: auto;
  1016. margin: 0;
  1017. padding: 0;
  1018. font: inherit;
  1019. font-size: 13px;
  1020. vertical-align: middle;
  1021. text-transform: none;
  1022. text-align: left;
  1023. border-radius: 0;
  1024. text-decoration: none;
  1025. color: white;
  1026. background: black;
  1027. }
  1028. .instant-youtube-container .instant-youtube-options > label {
  1029. margin-top: 1ex;
  1030. }
  1031. .instant-youtube-container .instant-youtube-options > label > * {
  1032. display: inline;
  1033. }
  1034. .instant-youtube-container .instant-youtube-options select {
  1035. width: 20ex;
  1036. padding: .5ex .25ex;
  1037. border: 1px solid #444;
  1038. -webkit-appearance: menulist;
  1039. }
  1040. .instant-youtube-container .instant-youtube-options [data-action="size-custom"] input {
  1041. width: 9ex;
  1042. padding: .5ex .5ex .4ex;
  1043. border: 1px solid #666;
  1044. }
  1045. .instant-youtube-container .instant-youtube-options [data-action="buttons"] {
  1046. margin-top: 1em;
  1047. }
  1048. .instant-youtube-container .instant-youtube-options button {
  1049. margin: 0 1ex 0 0;
  1050. padding: .5ex 2ex;
  1051. border: 2px solid gray;
  1052. font-weight: bold;
  1053. }
  1054. .instant-youtube-container .instant-youtube-options button:hover {
  1055. border-color: white;
  1056. }
  1057. .instant-youtube-container .instant-youtube-options > [disabled] {
  1058. opacity: 0.25;
  1059. }
  1060. .instant-youtube-container .instant-youtube-storyboard {
  1061. opacity: 0;
  1062. display: flex;
  1063. flex-direction: row;
  1064. position: absolute;
  1065. max-height: 33%;
  1066. left: 0;
  1067. right: 0;
  1068. bottom: 10px;
  1069. transition: opacity 1s ease;
  1070. }
  1071. .instant-youtube-container:hover .instant-youtube-storyboard {
  1072. opacity: 1;
  1073. }
  1074. .instant-youtube-container .instant-youtube-storyboard div {
  1075. flex: auto;
  1076. }
  1077. .instant-youtube-container .instant-youtube-storyboard img {
  1078. width: auto;
  1079. max-height: 100%;
  1080. border: 3px solid rgba(255, 255, 255, .5);
  1081. box-shadow: 2px 2px 10px black;
  1082. }
  1083. */}).replace(/\$tl:'(.+?)'/g, function(m, m1) { return _(m1) })
  1084. );
  1085. }