FYTE /Fast YouTube Embedded/ Player

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. Optionally a fast simple HTML5 direct playback (720p max) can be selected if available for the video.

当前为 2017-01-31 提交的版本,查看 最新版本

  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. Optionally a fast simple HTML5 direct playback (720p max) can be selected if available for the video.
  4. // @description:ru На порядок ускоряет время загрузки страниц с большим количеством вставленных Youtube-видео. С первого момента загрузки страницы появляются заглушки для видео, которые можно щелкнуть для загрузки плеера, и почти сразу же появляются кавер-картинки с названием видео. В опциях можно включить режим использования упрощенного браузерного плеера (макс. 720p).
  5. // @version 2.7.8
  6. // @include *
  7. // @exclude /^https:\/\/www\.youtube\.com\/(?!embed)/
  8. // @exclude https://plus.google.*/_/notifications/frame*
  9. // @exclude https://accounts.google.*/o/oauth2/postmessageRelay*
  10. // @exclude https://clients*.google.*/youtubei/*
  11. // @exclude https://clients*.google.*/static/proxy*
  12. // @author wOxxOm
  13. // @namespace wOxxOm.scripts
  14. // @license MIT License
  15. // @grant GM_getValue
  16. // @grant GM_setValue
  17. // @grant GM_addStyle
  18. // @grant GM_xmlhttpRequest
  19. // @connect www.youtube.com
  20. // @connect youtube.com
  21. // @run-at document-start
  22. // @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=
  23. // @compatible chrome
  24. // @compatible firefox
  25. // @compatible opera
  26. // ==/UserScript==
  27.  
  28. /* jshint lastsemic:true, multistr:true, laxbreak:true, -W030, -W041, -W084 */
  29.  
  30. if (location.href.indexOf('https://www.youtube.com/') == 0) {
  31. if (!window.chrome || window == window.parent
  32. || !location.href.match(/^https:\/\/www\.youtube\.com\/embed.*?FYTEfullscreen=1/))
  33. return;
  34. var fsbtn = document.getElementsByClassName('ytp-fullscreen-button');
  35. new MutationObserver(function() {
  36. if (fsbtn[0]) {
  37. this.disconnect();
  38. fsbtn[0].outerHTML = fsbtn[0].outerHTML;
  39. fsbtn[0].addEventListener('click', function(e) {
  40. window.parent.postMessage('FYTE-toggle-fullscreen', '*');
  41. });
  42. }
  43. }).observe(document, {subtree:true, childList:true});
  44. return;
  45. }
  46.  
  47. var resizeMode = GM_getValue('resize', 'Fit to width');
  48. if (typeof resizeMode != 'string')
  49. resizeMode = resizeMode ? 'Fit to width' : 'Original';
  50.  
  51. var resizeWidth = GM_getValue('width', 1280) |0;
  52. var resizeHeight = GM_getValue('height', 720) |0;
  53. updateCustomSize();
  54.  
  55. var playDirectly = !!GM_getValue('playHTML5', false);
  56. var playDirectlyShown = !!GM_getValue('playHTML5shown', false);
  57. var skipCustom = !!GM_getValue('skipCustom', true);
  58. var showStoryboard = !!GM_getValue('showStoryboard', true);
  59. var pinnable = GM_getValue('pinnable', 'on');
  60. if (!/^(on|hide|off)$/.test(pinnable))
  61. pinnable = !!pinnable ? 'on' : 'hide';
  62.  
  63. var _ = initTL();
  64.  
  65. var imageLoader = document.createElement('img');
  66. var imageLoader2 = document.createElement('img');
  67.  
  68. var fytedom = document.getElementsByClassName('instant-youtube-container');
  69. var iframes = document.getElementsByTagName('iframe');
  70. var objects = document.getElementsByTagName('object');
  71. var persite = (function() {
  72. var rules = [
  73. {host: /(^|\.)google\.\w{2,3}(\.\w{2,3})?$/, class:'g-blk', query: 'a[href*="youtube.com/watch"][data-ved]', eatparent: 1},
  74. {host: 'pikabu.ru', class:'b-video', match: '[data-url*="youtube.com/embed"]', attr: 'data-url'},
  75. {host: 'androidauthority.com', eatparent: '.video-container'},
  76. {host: 'reddit.com',
  77. match: '[data-url*="youtube.com/"] [src*="/mediaembed"], [data-url*="youtu.be/"] [src*="/mediaembed"]',
  78. src: function(e) { return e.closest('[data-url*="youtube.com/"], [data-url*="youtu.be/"]').dataset.url }},
  79. {host: /(www\.)?theverge\.com$/, eatparent: '.p-scalable-video'},
  80. {host: '9gag.com', eatparent: 0},
  81. ];
  82. for (var i=0, rule; (i<rules.length) && (rule=rules[i]); i++) {
  83. var rx = rule.host instanceof RegExp ? rule.host : new RegExp('(^|\\.)' + rule.host.replace(/\./g, '\\.') + '$', 'i');
  84. if (rx.test(location.hostname)) {
  85. if (!rule.tag && !rule.class)
  86. rule.tag = 'iframe';
  87. if (!rule.match && !rule.query)
  88. rule.match = '[src*="youtube.com/embed"]';
  89. return {
  90. nodes: rule.class ? document.getElementsByClassName(rule.class) : document.getElementsByTagName(rule.tag),
  91. match: rule.match ? function(e) { return e.matches(rule.match) ? e : null }
  92. : function(e) { return e.querySelector(rule.query) },
  93. attr: rule.attr,
  94. src: rule.src,
  95. eatparent: rule.eatparent,
  96. };
  97. }
  98. }
  99. })();
  100.  
  101. findEmbeds();
  102. injectStylesIfNeeded();
  103. new MutationObserver(findEmbeds).observe(document, {subtree:true, childList:true});
  104.  
  105. document.addEventListener('DOMContentLoaded', function(e) {
  106. injectStylesIfNeeded();
  107. adjustNodesIfNeeded(e);
  108. });
  109. window.addEventListener('resize', adjustNodesIfNeeded, true);
  110. window.addEventListener('message', function(e) {
  111. if (e.data == 'FYTE-toggle-fullscreen') {
  112. $$('iframe[allowfullscreen]').some(function(iframe) {
  113. if (iframe.contentWindow == e.source) {
  114. goFullscreen(iframe, !(document.webkitIsFullScreen || document.mozIsFullScreen || document.isFullScreen));
  115. return true;
  116. }
  117. });
  118. }
  119. else if (e.data == 'iframe-allowfs') {
  120. $$('iframe:not([allowfullscreen])').some(function(iframe) {
  121. if (iframe.contentWindow == e.source) {
  122. iframe.allowFullscreen = true;
  123. return true;
  124. }
  125. });
  126. if (window != window.top)
  127. window.parent.postMessage('iframe-allowfs', '*');
  128. }
  129. });
  130.  
  131. function findEmbeds(mutations) {
  132. var i, len, e, m;
  133. if (mutations && mutations.length == 1 && !mutations[0].addedNodes.length)
  134. return;
  135. if (persite)
  136. for (i=0, len=persite.nodes.length; (i<len) && (e=persite.nodes[i]); i++)
  137. if (e = persite.match(e))
  138. processEmbed(e, persite.src && persite.src(e) || e.getAttribute(persite.attr));
  139. for (i=0, len=iframes.length; (i<len) && (e=iframes[i]); i++)
  140. if (/youtube\.com|youtu\.be/i.test(e.src))
  141. processEmbed(e);
  142. for (i=0, len=objects.length; (i<len) && (e=objects[i]); i++)
  143. if (m = e.querySelector('embed, [value*="youtu.be"], [value*="youtube.com"]'))
  144. processEmbed(e, m.src || 'https://' + m.value.match(/youtu\.be.*|youtube\.com.*/)[0]);
  145. }
  146.  
  147. function processEmbed(node, src) {
  148. function decodeEmbedUrl(url) {
  149. return url.indexOf('youtube.com%2Fembed') > 0
  150. ? decodeURIComponent(url.replace(/^.*?(http[^&?=]+?youtube.com%2Fembed[^&]+).*$/i, '$1'))
  151. : url;
  152. }
  153. src = src || node.src || node.href || '';
  154. var n = node;
  155. var np = n.parentNode, npw;
  156. var srcFixed = decodeEmbedUrl(src).replace(/\/(watch\?v=|v\/)/, '/embed/');
  157. if (src.indexOf('cdn.embedly.com/') > 0 ||
  158. resizeMode != 'Original' && np && np.children.length == 1 && !np.className && !np.id)
  159. {
  160. n = location.hostname == 'disqus.com' ? np.parentNode : np;
  161. np = n.parentElement;
  162. }
  163. if (!np ||
  164. !np.parentNode ||
  165. skipCustom && srcFixed.indexOf('enablejsapi=1') > 0 ||
  166. node.matches('.instant-youtube-embed, .YTLT-embed') ||
  167. srcFixed.indexOf('/embed/videoseries') > 0 ||
  168. node.onload // skip some retarded loaders
  169. )
  170. return;
  171.  
  172. var id = srcFixed.match(/(?:^https?:\/\/)(?:www\.)?(?:youtube\.com\/(?:embed\/|\/.*?[&?\/]v[=\/])|youtu\.be\/)([^\s,.()\[\]?]+?)(?:[&?\/].*|$)/);
  173. if (!id)
  174. return;
  175. id = id[1];
  176.  
  177. var autoplay = srcFixed.indexOf('autoplay=1') > 0;
  178.  
  179. if (np.localName == 'object')
  180. n = np, np = n.parentElement;
  181.  
  182. var eatparent = persite && persite.eatparent || 0;
  183. if (typeof eatparent == 'string')
  184. n = np.closest(eatparent) || n, np = n.parentElement;
  185. else
  186. while (eatparent--)
  187. n = np, np = n.parentElement;
  188.  
  189. injectStylesIfNeeded('force');
  190.  
  191. var div = document.createElement('div');
  192. div.className = 'instant-youtube-container';
  193. div.FYTE = {
  194. state: 'querying',
  195. srcEmbed: srcFixed.replace(/&$/, ''),
  196. originalWidth: /%/.test(node.width) ? 320 : node.width|0 || n.clientWidth|0,
  197. originalHeight: /%/.test(node.height) ? 200 : node.height|0 || n.clientHeight|0,
  198. cache: JSON.parse(GM_getValue('cache-' + id, '0')) || {
  199. id: id,
  200. }
  201. };
  202. div.FYTE.srcEmbedFixed = div.FYTE.srcEmbed.replace(/^http:/, 'https:').replace(/&?wmode=\w+/, '').replace(/[?&]feature=oembed/, '');
  203. div.FYTE.srcWatchFixed = div.FYTE.srcEmbedFixed.replace(/\/embed\//, '/watch?v=');
  204.  
  205. var cache = div.FYTE.cache;
  206.  
  207. if (cache.reason)
  208. div.setAttribute('disabled', '');
  209.  
  210. var divSize = calcContainerSize(div, n);
  211. var origStyle = getComputedStyle(n);
  212. div.style.cssText = important(
  213. (autoplay ? '' : 'background-color:transparent; transition:background-color 2s;') +
  214. (origStyle.hasOwnProperty('position') ? Object.keys(origStyle) : Object.keys(origStyle.__proto__) /*FF*/)
  215. .filter(function(k) { return !!k.match(/^(position|left|right|top|bottom)$/) })
  216. .map(function(k) { return k + ':' + origStyle[k] })
  217. .join(';')
  218. .replace(/\b[^;:]+:\s*(auto|static|block)\s*(!\s*important)?;/g, '') +
  219. (origStyle.display == 'inline' ? ';display:inline-block;width:100%' : '') +
  220. ';min-width:' + Math.min(divSize.w, div.FYTE.originalWidth) + 'px' +
  221. ';min-height:' + Math.min(divSize.h, div.FYTE.originalHeight) + 'px' +
  222. (resizeMode == 'Fit to width' ? ';width:100%' : '') +
  223. ';max-width:' + divSize.w + 'px; height:' + (persite && persite.eatparent === 0 ? '100%' : divSize.h + 'px;'));
  224. if (!autoplay) {
  225. setTimeout(function() { div.style.backgroundColor = '' }, 0);
  226. setTimeout(function() { div.style.transition = '' }, 2000);
  227. }
  228.  
  229. // consume parents of retardedly positioned videos
  230. if (div.style.position.match('absolute|relative')) {
  231. if (np.children.length == 1 &&
  232. floatPadding(np, getComputedStyle(np), 'Top') >= div.FYTE.originalHeight-1 ||
  233. floatPadding(np, getComputedStyle(np, ':after'), 'Top') >= div.FYTE.originalHeight-1
  234. ) {
  235. n = np, np = n.parentElement;
  236. }
  237. div.style.cssText = div.style.cssText.replace(/\b(position|left|top|right|bottom):[^;]+/g, '');
  238. }
  239.  
  240. var wrapper = div.appendChild(document.createElement('div'));
  241. wrapper.className = 'instant-youtube-wrapper';
  242.  
  243. var img = wrapper.appendChild(document.createElement('img'));
  244. if (!autoplay)
  245. img.src = 'https://i.ytimg.com/vi/' + id + '/' + (cache.cover || 'maxresdefault.jpg');
  246. img.className = 'instant-youtube-thumbnail';
  247. img.style.cssText = important((cache.cover ? '' : 'transition:opacity 0.1s ease-out; opacity:0; ') +
  248. 'padding:0; margin:auto; position:absolute; left:0; right:0; top:0; bottom:0; max-width:none; max-height:none;');
  249.  
  250. img.title = _('Shift-click to use alternative player');
  251. img.onload = function(e) {
  252. if (img.naturalWidth <= 120 && !cache.coverHeight)
  253. return img.onerror(e);
  254. var fitToWidth = true;
  255. if (img.naturalHeight || cache.coverHeight) {
  256. if (!cache.coverHeight) {
  257. cache.coverWidth = img.naturalWidth;
  258. cache.coverHeight = img.naturalHeight;
  259. GM_setValue('cache-' + id, JSON.stringify(cache));
  260. }
  261. var ratio = cache.coverWidth / cache.coverHeight;
  262. if (ratio > 4.1/3 && ratio < divSize.w/divSize.h) {
  263. img.style.cssText += important('width:auto; height:100%;');
  264. fitToWidth = false;
  265. }
  266. }
  267. if (fitToWidth) {
  268. img.style.cssText += important('width:100%; height:auto;');
  269. }
  270. if (cache.videoWidth)
  271. fixThumbnailAR(div);
  272. if (!autoplay)
  273. img.style.opacity = 1;
  274. };
  275. img.onerror = function(e) {
  276. if (img.src.indexOf('maxresdefault') > 0)
  277. img.src = img.src.replace('maxresdefault','hqdefault');
  278. };
  279. if (cache.coverWidth)
  280. img.onload();
  281.  
  282. translateHTML(wrapper, 'beforeend', '\
  283. <a class="instant-youtube-title" target="_blank" href="' + div.FYTE.srcWatchFixed + '">' +
  284. (cache.title || cache.reason ? '<strong>' + (cache.title || cache.reason || '') + '</strong>' +
  285. '<span>' + (cache.duration || '') + '</span>'
  286. : '&nbsp;') + '</a>\
  287. <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>\
  288. <span tl class="instant-youtube-alternative">' + (playDirectly ? 'Play with Youtube player' : 'Play directly (up to 720p)') + '</span>\
  289. <div tl class="instant-youtube-options-button">Options</div>\
  290. ');
  291.  
  292. np.insertBefore(div, n);
  293. n.remove();
  294.  
  295. if (!cache.title && !cache.reason || autoplay && playDirectly)
  296. fetchInfo();
  297.  
  298. if (autoplay)
  299. return startPlaying(div);
  300.  
  301. div.addEventListener('click', clickHandler);
  302. div.addEventListener('mousedown', clickHandler);
  303. div.addEventListener('mouseenter', fetchInfo);
  304.  
  305. function fetchInfo(e) {
  306. div.removeEventListener('mouseenter', fetchInfo);
  307. GM_xmlhttpRequest({
  308. method: 'GET',
  309. url: 'https://www.youtube.com/get_video_info?video_id=' + id +
  310. '&hl=en_US&html5=1&el=embedded&eurl=' + encodeURIComponent(location.href),
  311. headers: {'Accept-Encoding': 'gzip'},
  312. context: div,
  313. onload: parseVideoInfo
  314. });
  315. }
  316. }
  317.  
  318. function adjustNodesIfNeeded(e) {
  319. if (!fytedom[0])
  320. return;
  321. if (adjustNodesIfNeeded.scheduled)
  322. clearTimeout(adjustNodesIfNeeded.scheduled);
  323. adjustNodesIfNeeded.scheduled = setTimeout(function() {
  324. adjustNodes(e);
  325. adjustNodesIfNeeded.scheduled = 0;
  326. }, 16);
  327. }
  328.  
  329. function adjustNodes(e, clickedContainer) {
  330. var force = !!clickedContainer;
  331. var nearest = force ? clickedContainer : null;
  332.  
  333. var vids = $$('.instant-youtube-container:not([pinned]):not([stub])');
  334.  
  335. if (!nearest && e.type != 'DOMContentLoaded') {
  336. var minDistance = window.innerHeight*3/4 |0;
  337. var nearTargetY = window.innerHeight/2;
  338. vids.forEach(function(n) {
  339. var bounds = n.getBoundingClientRect();
  340. var distance = Math.abs((bounds.bottom + bounds.top)/2 - nearTargetY);
  341. if (distance < minDistance) {
  342. minDistance = distance;
  343. nearest = n;
  344. }
  345. });
  346. }
  347.  
  348. if (nearest) {
  349. var bounds = nearest.getBoundingClientRect();
  350. var nearestCenterYpct = (bounds.top + bounds.bottom)/2 / window.innerHeight;
  351. }
  352.  
  353. var resized = false;
  354.  
  355. vids.forEach(function(n) {
  356. var size = calcContainerSize(n);
  357. var w = size.w, h = size.h;
  358.  
  359. // prevent parent clipping
  360. for (var e=n.parentElement, style; e; e=e.parentElement)
  361. if (e.style.overflow != 'visible' && (style=getComputedStyle(e)))
  362. if ((style.overflow+style.overflowX+style.overflowY).match(/hidden|scroll/))
  363. if (n.offsetTop < e.clientHeight / 2 && n.offsetTop + n.clientHeight > e.clientHeight)
  364. e.style.cssText = e.style.cssText.replace(/\boverflow(-[xy])?:[^;]+/g, '') +
  365. important('overflow:visible;overflow-x:visible;overflow-y:visible;');
  366.  
  367. if (force && Math.abs(w - parseFloat(n.style.maxWidth)) <= 2)
  368. return;
  369.  
  370. if (n.style.maxWidth != w + 'px') n.style.maxWidth = w + 'px';
  371. if (n.style.height != h + 'px') n.style.height = h + 'px';
  372. if (parseFloat(n.style.minWidth) > w) n.style.minWidth = n.style.maxWidth;
  373. if (parseFloat(n.style.minHeight) > h) n.style.minHeight = n.style.height;
  374.  
  375. fixThumbnailAR(n);
  376. resized = true;
  377. });
  378.  
  379. if (resized && nearest)
  380. setTimeout(function() {
  381. var bounds = nearest.getBoundingClientRect();
  382. var h = bounds.bottom - bounds.top;
  383. var projectedCenterY = nearestCenterYpct * window.innerHeight;
  384. var projectedTop = projectedCenterY - h/2;
  385. var safeTop = Math.min(Math.max(0, projectedTop), window.innerHeight - h);
  386. window.scrollBy(0, bounds.top - safeTop);
  387. }, 16);
  388. }
  389.  
  390. function calcContainerSize(div, origNode) {
  391. origNode = origNode || div;
  392. var w, h;
  393. var np = origNode.parentElement;
  394. var style = getComputedStyle(np);
  395. var parentWidth = parseFloat(style.width) - floatPadding(np, style, 'Left') - floatPadding(np, style, 'Right');
  396. if (+style.columnCount > 1)
  397. parentWidth = (parentWidth + parseFloat(style.columnGap)) / style.columnCount - parseFloat(style.columnGap);
  398. switch (resizeMode) {
  399. case 'Original':
  400. if (div.FYTE.originalWidth == 320 && div.FYTE.originalHeight == 200) {
  401. w = parentWidth;
  402. h = parentWidth / 16 * 9;
  403. } else {
  404. w = div.FYTE.originalWidth;
  405. h = div.FYTE.originalHeight;
  406. }
  407. break;
  408. case 'Custom':
  409. w = resizeWidth;
  410. h = resizeHeight;
  411. break;
  412. case '1080p':
  413. case '720p':
  414. case '480p':
  415. case '360p':
  416. h = parseInt(resizeMode);
  417. w = h / 9 * 16;
  418. break;
  419. default: // fit-to-width mode
  420. var n = origNode;
  421. do {
  422. n = n.parentElement;
  423. // find parent node with nonzero width (i.e. independent of our video element)
  424. } while (n && !(w = n.clientWidth));
  425. if (w)
  426. h = w / 16 * 9;
  427. else {
  428. w = origNode.clientWidth;
  429. h = origNode.clientHeight;
  430. }
  431. }
  432. if (parentWidth > 0 && parentWidth < w) {
  433. h = parentWidth / w * h;
  434. w = parentWidth;
  435. }
  436. if (resizeMode == 'Fit to width' && h < div.FYTE.originalHeight*0.9)
  437. h = Math.min(div.FYTE.originalHeight, w / div.FYTE.originalWidth * div.FYTE.originalHeight);
  438.  
  439. return {w: window.chrome ? w : Math.round(w), h:h};
  440. }
  441.  
  442. function parseVideoInfo(response) {
  443. var div = response.context;
  444. var txt = response.responseText;
  445. var info = tryCatch(function() { return JSON.parse(txt.replace(/(\w+)=?(.*?)(&|$)/g, '"$1":"$2",').replace(/^(.+?),?$/, '{$1}')) }) || {};
  446. var cache = div.FYTE.cache;
  447. var shouldUpdateCache = false;
  448. var videoSources = [];
  449.  
  450. // parse width & height to adjust the thumbnail
  451. var m = decodeURIComponent(info.adaptive_fmts || '').match(/size=(\d+)x(\d+)\b/) ||
  452. decodeURIComponent(txt).match(/\/(\d+)x(\d+)\//);
  453.  
  454. if (m && (cache.videoWidth != (m[1]|0) || cache.videoHeight != (m[2]|0))) {
  455. fixThumbnailAR(div, m[1]|0, m[2]|0);
  456. cache.videoWidth = m[1]|0;
  457. cache.videoHeight = m[2]|0;
  458. shouldUpdateCache = true;
  459. }
  460.  
  461. // parse video sources
  462. if (info.url_encoded_fmt_stream_map && info.fmt_list) {
  463. var streams = {};
  464. decodeURIComponent(info.url_encoded_fmt_stream_map).split(',').forEach(function(stream) {
  465. var params = {};
  466. stream.split('&').forEach(function(kv) {
  467. params[kv.split('=')[0]] = decodeURIComponent(kv.split('=')[1]);
  468. });
  469. streams[params.itag] = params;
  470. });
  471. decodeURIComponent(info.fmt_list).split(',').forEach(function(fmt) {
  472. var itag = fmt.split('/')[0];
  473. var dimensions = fmt.split('/')[1];
  474. var stream = streams[itag];
  475. if (stream) {
  476. videoSources.push({
  477. src: stream.url + (stream.s ? '&signature=' + stream.s : ''),
  478. title: stream.quality + ', ' + dimensions + ', ' + stream.type
  479. });
  480. }
  481. });
  482. } else {
  483. var rx = /url=([^=]+?mime%3Dvideo%252F(?:mp4|webm)[^=]+?)(?:,quality|,itag|.u0026)/g;
  484. var text = decodeURIComponent(txt).split('url_encoded_fmt_stream_map')[1];
  485. while (m = rx.exec(text)) {
  486. videoSources.push({
  487. src: decodeURIComponent(decodeURIComponent(m[1]))
  488. });
  489. }
  490. }
  491.  
  492. var duration = div.FYTE.duration = info.length_seconds|0 || '';
  493. if (duration) {
  494. var d = new Date(null);
  495. d.setSeconds(duration);
  496. duration = d.toISOString().replace(/^.+?T[0:]{0,4}(.+?)\..+$/, '$1');
  497. if (cache.duration != duration) {
  498. cache.duration = duration;
  499. shouldUpdateCache = true;
  500. }
  501. duration = '<span>' + duration + '</span>';
  502. }
  503. var title = decodeURIComponent(info.title || info.reason || '').replace(/\+/g, ' ');
  504. if (title) {
  505. $(div, '.instant-youtube-title').innerHTML = (title ? '<strong>' + title + '</strong>' : '') + duration;
  506. if (cache.title != title) {
  507. cache.title = title;
  508. shouldUpdateCache = true;
  509. }
  510. }
  511. if (pinnable != 'off' && info.title)
  512. makeDraggable(div);
  513.  
  514. if (info.reason) {
  515. div.setAttribute('disabled', '');
  516. if (cache.reason != info.reason) {
  517. cache.reason = info.reason;
  518. shouldUpdateCache = true;
  519. }
  520. }
  521.  
  522. if (videoSources.length)
  523. div.FYTE.videoSources = videoSources;
  524.  
  525. if (info.storyboard_spec && div.FYTE.state != 'scheduled play') {
  526. m = decodeURIComponent(decodeURIComponent(info.storyboard_spec)).split('|');
  527. 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}'));
  528. if (div.FYTE.storyboard.w * div.FYTE.storyboard.h > 2000) {
  529. div.FYTE.storyboard.url = m[0].replace('$L/$N.jpg', (m.length-2) + '/M0.jpg?sigh=' + m[m.length-1].replace(/^.+?#([^#]+)$/, '$1'));
  530. $(div, '.instant-youtube-options-button').insertAdjacentHTML('beforebegin',
  531. '<div class="instant-youtube-storyboard"' + (showStoryboard ? '' : ' disabled') + '>' +
  532. important('<div style="width:' + (div.FYTE.storyboard.w-1) + 'px; height:' + div.FYTE.storyboard.h + 'px;') +
  533. '">&nbsp;</div>' +
  534. '</div>');
  535. if (showStoryboard)
  536. updateHoverHandler(div);
  537. }
  538. }
  539.  
  540. injectStylesIfNeeded();
  541.  
  542. if (div.FYTE.state == 'scheduled play')
  543. setTimeout(function() { startPlayingDirectly(div) }, 0);
  544.  
  545. div.FYTE.state = '';
  546.  
  547. var cover = decodeURIComponent(info.iurlmaxres || info.iurlhq || info.iurl || '').match(/[^\/]+$/);
  548. if (cover && cache.cover != cover[0]) {
  549. cache.cover = cover[0];
  550. shouldUpdateCache = true;
  551. }
  552. if (shouldUpdateCache)
  553. GM_setValue('cache-' + info.video_id, JSON.stringify(cache));
  554. }
  555.  
  556. function fixThumbnailAR(div, w, h) {
  557. var img = $(div, 'img');
  558. if (!img)
  559. return;
  560. var thw = img.naturalWidth, thh = img.naturalHeight;
  561. if (w && h) { // means thumbnail is still loading
  562. div.FYTE.cache.videoWidth = w;
  563. div.FYTE.cache.videoHeight = h;
  564. } else {
  565. w = div.FYTE.cache.videoWidth;
  566. h = div.FYTE.cache.videoHeight;
  567. if (!w || !h)
  568. return;
  569. }
  570. var divw = div.clientWidth, divh = div.clientHeight;
  571. // if both video and thumbnail are 4:3, fit the image to height
  572. //console.log(div, divw, divh, thw, thh, w, h, h/w*divw / divh - 1, thh/thw*divw / divh - 1);
  573. if (Math.abs(h/w*divw / divh - 1) > 0.05 && Math.abs(thh/thw*divw / divh - 1) > 0.05) {
  574. img.style.maxHeight = img.clientHeight + 'px';
  575. if (!div.FYTE.cache.videoWidth) // skip animation if thumbnail is already loaded
  576. img.style.transition = 'height 1s ease, margin-top 1s ease';
  577. setTimeout(function() {
  578. img.style.maxHeight = 'none';
  579. img.style.cssText += important(h/w >= divh/divw ? 'width:auto; height:100%;' : 'width:100%; height:auto;');
  580. setTimeout(function() {
  581. img.style.transition = '';
  582. }, 1000);
  583. }, 0);
  584. }
  585. }
  586.  
  587. function updateHoverHandler(div) {
  588. var sb = $(div, '.instant-youtube-storyboard');
  589. if (!showStoryboard) {
  590. if (!sb.getAttribute('disabled'))
  591. sb.setAttribute('disabled', '');
  592. return;
  593. }
  594. if (sb.hasAttribute('disabled'))
  595. sb.removeAttribute('disabled');
  596.  
  597. sb.addEventListener('click', storyboardClickHandler);
  598.  
  599. var oldIndex = null;
  600. var style = sb.firstElementChild.style;
  601. sb.addEventListener('mousemove', storyboardHoverHandler);
  602. sb.addEventListener('mouseout', storyboardHoverHandler);
  603.  
  604. div.addEventListener('mouseover', storyboardPreloader);
  605. div.addEventListener('mouseout', storyboardPreloader);
  606.  
  607. var spinner = document.createElement('span');
  608. spinner.className = 'instant-youtube-loading-spinner';
  609.  
  610. function storyboardClickHandler(e) {
  611. sb.removeEventListener('click', storyboardClickHandler);
  612. var offsetX = e.offsetX || e.clientX - this.getBoundingClientRect().left;
  613. div.FYTE.startAt = offsetX / this.clientWidth * div.FYTE.duration |0;
  614. div.FYTE.srcEmbedFixed = setUrlParams(div.FYTE.srcEmbedFixed, {start: div.FYTE.startAt});
  615. startPlaying(div, {alternateMode: e.shiftKey});
  616. }
  617.  
  618. function storyboardPreloader(e) {
  619. if (e.type == 'mouseout') {
  620. imageLoader.onload = null; imageLoader.src = '';
  621. spinner.remove();
  622. return;
  623. }
  624. if (!div.FYTE.storyboard || div.FYTE.storyboard.preloaded)
  625. return;
  626. var lastpart = (div.FYTE.storyboard.len-1)/(div.FYTE.storyboard.rows * div.FYTE.storyboard.cols) |0;
  627. if (lastpart <= 0)
  628. return;
  629. var part = 0;
  630. imageLoader.src = setStoryboardUrl(part++);
  631. imageLoader.onload = function() {
  632. if (part > lastpart) {
  633. div.FYTE.storyboard.preloaded = true;
  634. div.removeEventListener('mouseover', storyboardPreloader);
  635. div.removeEventListener('mouseout', storyboardPreloader);
  636. imageLoader.onload = null;
  637. imageLoader.src = '';
  638. spinner.remove();
  639. return;
  640. }
  641. imageLoader.src = setStoryboardUrl(part++);
  642. };
  643. }
  644.  
  645. function setStoryboardUrl(part) {
  646. return div.FYTE.storyboard.url.replace(/M\d+\.jpg\?/, 'M' + part + '.jpg?');
  647. }
  648.  
  649. function storyboardHoverHandler(e) {
  650. if (!showStoryboard || !div.FYTE.storyboard)
  651. return;
  652. if (e.type == 'mouseout')
  653. return imageLoader2.onload && imageLoader2.onload();
  654. var w = div.FYTE.storyboard.w;
  655. var h = div.FYTE.storyboard.h;
  656. var cols = div.FYTE.storyboard.cols;
  657. var rows = div.FYTE.storyboard.rows;
  658. var len = div.FYTE.storyboard.len;
  659. var partlen = rows * cols;
  660.  
  661. var offsetX = e.offsetX || e.clientX - this.getBoundingClientRect().left;
  662. var left = Math.min(this.clientWidth - w, Math.max(0, offsetX - w)) |0;
  663. if (!style.left || parseInt(style.left) != left) {
  664. style.left = left + 'px';
  665. if (spinner.parentElement)
  666. spinner.style.cssText = important('left:' + (left + w/2 - 10) + 'px; right:auto;');
  667. }
  668.  
  669. var index = Math.min(offsetX / this.clientWidth * (len+1) |0, len - 1);
  670. if (index == oldIndex)
  671. return;
  672.  
  673. var part = index/partlen|0;
  674. if (!oldIndex || part != (oldIndex/partlen|0)) {
  675. style.cssText = style.cssText.replace(/$|background-image[^;]+;/,
  676. 'background-image: url(' + setStoryboardUrl(part) + ')!important;');
  677. if (!div.FYTE.storyboard.preloaded) {
  678. if (spinner.timer)
  679. clearTimeout(spinner.timer);
  680. spinner.timer = setTimeout(function() {
  681. spinner.timer = 0;
  682. if (!imageLoader2.src)
  683. return;
  684. this.appendChild(spinner);
  685. spinner.style.cssText = important('left:' + (left + w/2 - 10) + 'px; right:auto;');
  686. }.bind(this), 50);
  687. imageLoader2.onload = function() {
  688. clearTimeout(spinner.timer);
  689. spinner.remove();
  690. spinner.timer = 0;
  691. imageLoader2.onload = null;
  692. imageLoader2.src = '';
  693. };
  694. imageLoader2.src = setStoryboardUrl(part);
  695. }
  696. }
  697.  
  698. oldIndex = index;
  699. index = index % partlen;
  700. style.backgroundPosition = '-' + (index % cols) * w + 'px -' + (index / cols |0) * h + 'px';
  701. }
  702. }
  703.  
  704. function clickHandler(e) {
  705. if (e.target.closest('a')
  706. || e.type == 'mousedown' && e.button != 1
  707. || e.type == 'click' && e.target.matches('.instant-youtube-options, .instant-youtube-options *'))
  708. return;
  709. if (e.type == 'click' && e.target.matches('.instant-youtube-options-button')) {
  710. showOptions(e);
  711. e.preventDefault();
  712. e.stopPropagation();
  713. return;
  714. }
  715.  
  716. e.preventDefault();
  717. e.stopPropagation();
  718. e.stopImmediatePropagation();
  719.  
  720. startPlaying(e.target.closest('.instant-youtube-container'), {
  721. alternateMode: e.shiftKey || e.target.matches('.instant-youtube-alternative'),
  722. fullscreen: e.button == 1,
  723. });
  724. }
  725.  
  726. function startPlaying(div, params) {
  727. div.removeEventListener('click', clickHandler);
  728. div.removeEventListener('mousedown', clickHandler);
  729.  
  730. $$remove(div, '.instant-youtube-alternative, .instant-youtube-storyboard, .instant-youtube-options-button');
  731. $(div, 'svg').outerHTML = '<span class="instant-youtube-loading-spinner"></span>';
  732.  
  733. if (pinnable != 'off') {
  734. makePinnable(div);
  735. if (params && params.pin)
  736. $(div, '[pin="' + params.pin + '"]').click();
  737. }
  738.  
  739. if (window != window.top)
  740. window.parent.postMessage('iframe-allowfs', '*');
  741.  
  742. if ((!!playDirectly + !!(params && params.alternateMode) == 1)
  743. && (div.FYTE.videoSources || div.FYTE.state == 'querying')) {
  744. if (div.FYTE.videoSources)
  745. startPlayingDirectly(div, params);
  746. else {
  747. // playback will start in parseVideoInfo
  748. div.FYTE.state = 'scheduled play';
  749. // fallback to iframe in 5s
  750. setTimeout(function() {
  751. if (div.FYTE.state) {
  752. div.FYTE.state = '';
  753. switchToIFrame.call(div, params);
  754. }
  755. }, 5000);
  756. }
  757. }
  758. else
  759. switchToIFrame.call(div, params);
  760. }
  761.  
  762. function startPlayingDirectly(div, params) {
  763. var video = document.createElement('video');
  764. video.controls = true;
  765. video.autoplay = true;
  766. video.style.cssText = important('position:absolute; left:0; top:0; right:0; bottom:0; padding:0; margin:auto; opacity:0; width:100%; height:100%;');
  767. video.className = 'instant-youtube-embed';
  768. video.volume = GM_getValue('volume', 0.5);
  769.  
  770. div.FYTE.videoSources.forEach(function(src) {
  771. var srcdom = video.appendChild(document.createElement('source'));
  772. Object.keys(src).forEach(function(k) { srcdom[k] = src[k] });
  773. srcdom.onerror = switchToIFrame.bind(div, params);
  774. });
  775.  
  776. overrideCSS($(div, 'img'), {transition: 'opacity 1s', opacity: '0'});
  777.  
  778. if (params && params.fullscreen) {
  779. div.firstElementChild.appendChild(video);
  780. div.setAttribute('playing', '');
  781. video.style.opacity = 1;
  782. goFullscreen(video);
  783. }
  784.  
  785. if (window.chrome) {
  786. video.addEventListener('click', function(e) {
  787. this.paused ? this.play() : this.pause();
  788. });
  789. }
  790.  
  791. video.interval = (function() {
  792. return setInterval(function() {
  793. if (video.volume != GM_getValue('volume', 0.5))
  794. GM_setValue('volume', video.volume);
  795. }, 1000);
  796. })();
  797.  
  798. var title = $(div, '.instant-youtube-title');
  799. if (title) {
  800. video.onpause = function() { title.removeAttribute('hidden') };
  801. video.onplay = function() { title.setAttribute('hidden', true) };
  802. }
  803.  
  804. video.onloadedmetadata = div.FYTE.startAt && function(e) {
  805. video.currentTime = div.FYTE.startAt;
  806. };
  807. video.onloadeddata = function(e) {
  808. pauseOtherVideos(video);
  809. if (params && params.fullscreen)
  810. return;
  811. div.setAttribute('playing', '');
  812. div.firstElementChild.appendChild(video);
  813. video.style.opacity = 1;
  814. };
  815. }
  816.  
  817. function switchToIFrame(params, e) {
  818. var div = this;
  819. var wrapper = div.firstElementChild;
  820. var fullscreen = params && params.fullscreen && !e;
  821. if (e instanceof Event) {
  822. console.log('[FYTE] Direct linking canceled on %s, switching to IFRAME player', div.FYTE.srcEmbed);
  823. var video = e.target ? e.target.closest('video') : e.path && e.path[e.path.length-1];
  824. while (video.lastElementChild)
  825. video.lastElementChild.remove();
  826. goFullscreen(video, false);
  827. video.remove();
  828. }
  829.  
  830. ($(div, '[pin]') || wrapper).insertAdjacentHTML(pinnable != 'off' ? 'beforebegin' : 'beforeend',
  831. '<iframe class="instant-youtube-embed" allowtransparency="true" src="' +
  832. setUrlParams(div.FYTE.srcEmbedFixed, {
  833. html5: 1,
  834. autoplay: 1,
  835. autohide: 2,
  836. border: 0,
  837. controls: 1,
  838. fs: 1,
  839. showinfo: 1,
  840. ssl: 1,
  841. theme: 'dark',
  842. enablejsapi: 1,
  843. FYTEfullscreen: fullscreen|0,
  844. }) + '" style="' + important('position:absolute; left:0; top:0; right:0; padding:0; margin:auto; opacity:0;') +
  845. '" frameborder="0" allowfullscreen width="100%" height="100%"></iframe>');
  846.  
  847. div.setAttribute('iframe', '');
  848. div.setAttribute('playing', '');
  849.  
  850. var iframe = $(div, 'iframe');
  851. if (fullscreen) {
  852. goFullscreen(iframe);
  853. iframe.style.opacity = 1;
  854. }
  855.  
  856. iframe.onload = function(e) {
  857. window.addEventListener('message', YTlistener);
  858. iframe.contentWindow.postMessage('{"event":"listening"}', '*');
  859. };
  860. setTimeout(function() {
  861. iframe.style.opacity = 1;
  862. window.removeEventListener('message', YTlistener);
  863. }, 5000);
  864.  
  865. function YTlistener(e) {
  866. if (e.source != iframe.contentWindow || !e.data)
  867. return;
  868. var data = JSON.parse(e.data);
  869. if (!data.info || data.info.playerState != 1)
  870. return;
  871. window.removeEventListener('message', YTlistener);
  872. pauseOtherVideos(iframe);
  873. iframe.style.opacity = 1;
  874. $$remove(div, 'span, a');
  875. $(div, 'img').style.display = 'none';
  876. }
  877. }
  878.  
  879. function setUrlParams(url, params) {
  880. var names = Object.keys(params);
  881. url = url.replace(new RegExp('[?&](' + names.join('|') + ')(=[^?&]*)?', 'gi'), '');
  882. return url +
  883. (url.indexOf('?') > 0 ? '&' : '?') +
  884. names.map(function(n) { return n + '=' + params[n] }).join('&');
  885. }
  886.  
  887. function pauseOtherVideos(activePlayer) {
  888. $$(activePlayer.ownerDocument, '.instant-youtube-embed').forEach(function(v) {
  889. if (v == activePlayer)
  890. return;
  891. switch (v.localName) {
  892. case 'video':
  893. if (!v.paused)
  894. v.pause();
  895. break;
  896. case 'iframe':
  897. try { v.contentWindow.postMessage('{"event":"command", "func":"pauseVideo", "args":""}', '*') } catch(e) {}
  898. break;
  899. }
  900. });
  901. }
  902.  
  903. function goFullscreen(el, enable) {
  904. if (enable !== false)
  905. el.webkitRequestFullScreen && el.webkitRequestFullScreen()
  906. || el.mozRequestFullScreen && el.mozRequestFullScreen()
  907. || el.requestFullScreen && el.requestFullScreen();
  908. else
  909. document.webkitCancelFullScreen && document.webkitCancelFullScreen()
  910. || document.mozCancelFullScreen && document.mozCancelFullScreen()
  911. || document.cancelFullScreen && document.cancelFullScreen();
  912. }
  913.  
  914. function makePinnable(div) {
  915. div.firstElementChild.insertAdjacentHTML('beforeend',
  916. '<div pin="top-left"></div><div pin="top-right"></div><div pin="bottom-right"></div><div pin="bottom-left"></div>');
  917. $$(div, '[pin]').forEach(function(pin) {
  918. if (pinnable == 'hide')
  919. pin.setAttribute('transparent', '');
  920. pin.onclick = function(e) {
  921. var pinIt = !div.hasAttribute('pinned') || !pin.hasAttribute('active');
  922. var corner = pin.getAttribute('pin');
  923. var video = $(div, 'video');
  924. if (pinIt) {
  925. $$(div, '[pin][active]').forEach(function(p) { p.removeAttribute('active') });
  926. pin.setAttribute('active', '');
  927. if (!div.FYTE.unpinnedStyle) {
  928. div.FYTE.unpinnedStyle = div.style.cssText;
  929. var stub = div.cloneNode();
  930. var img = $(div, 'img').cloneNode();
  931. img.style.opacity = 1;
  932. img.style.display = 'block';
  933. img.title = '';
  934. stub.appendChild(img);
  935. stub.onclick = function(e) { $(div, '[pin][active]').onclick(e) };
  936. stub.style.cssText += 'opacity:0.3!important;';
  937. stub.setAttribute('stub', '');
  938. div.FYTE.stub = stub;
  939. div.parentNode.insertBefore(stub, div);
  940. }
  941. div.style.cssText = important(
  942. 'position: fixed;' +
  943. 'contain: inherit;' +
  944. 'width: 400px;' +
  945. 'z-index: 999999999;' +
  946. 'height:' + (400 / div.FYTE.cache.videoWidth * div.FYTE.cache.videoHeight) + 'px;' +
  947. 'top:' + (corner.indexOf('top') >= 0 ? '0' : 'auto') + ';' +
  948. 'bottom:' + (corner.indexOf('bottom') >= 0 ? '0' : 'auto') + ';' +
  949. 'left:' + (corner.indexOf('left') >= 0 ? '0' : 'auto') + ';' +
  950. 'right:' + (corner.indexOf('right') >= 0 ? '0' : 'auto') + ';'
  951. );
  952. adjustPinnedOffset(div, div, corner);
  953. div.setAttribute('pinned', '');
  954. if (video && document.body)
  955. document.body.appendChild(div);
  956. }
  957. else { // unpin
  958. pin.removeAttribute('active');
  959. div.removeAttribute('pinned');
  960. div.style.cssText = div.FYTE.unpinnedStyle;
  961. div.FYTE.unpinnedStyle = '';
  962. if (div.FYTE.stub) {
  963. if (video && document.body)
  964. div.FYTE.stub.parentNode.replaceChild(div, div.FYTE.stub);
  965. div.FYTE.stub.remove();
  966. div.FYTE.stub = null;
  967. }
  968. }
  969. if (video && video.paused)
  970. video.play();
  971. };
  972. });
  973. }
  974.  
  975. function makeDraggable(div) {
  976. div.draggable = true;
  977. div.addEventListener('dragstart', function(e) {
  978. var offsetY = e.offsetY || e.clientY - div.getBoundingClientRect().top;
  979. if (offsetY > div.clientHeight - 30)
  980. return e.preventDefault();
  981.  
  982. e.dataTransfer.setData('text/plain', '');
  983.  
  984. var dropZone = document.createElement('div');
  985. var dropZoneHeight = 400 / div.FYTE.cache.videoWidth * div.FYTE.cache.videoHeight;
  986. dropZone.className = 'instant-youtube-dragndrop-placeholder';
  987.  
  988. document.body.addEventListener('dragenter', dragHandler);
  989. document.body.addEventListener('dragover', dragHandler);
  990. document.body.addEventListener('dragend', dragHandler);
  991. document.body.addEventListener('drop', dragHandler);
  992. function dragHandler(e) {
  993. e.stopImmediatePropagation();
  994. e.stopPropagation();
  995. e.preventDefault();
  996. switch (e.type) {
  997. case 'dragover':
  998. var playing = div.hasAttribute('playing');
  999. var stub = e.target.closest('.instant-youtube-container[stub]') == div.FYTE.stub && div.FYTE.stub;
  1000. var gizmo = playing && !stub
  1001. ? {left:0, top:0, right:innerWidth, bottom:innerHeight}
  1002. : (stub || div).getBoundingClientRect();
  1003. var x = e.clientX, y = e.clientY;
  1004. var cx = (gizmo.left + gizmo.right) / 2;
  1005. var cy = (gizmo.top + gizmo.bottom) / 2;
  1006. var stay = !!stub || y >= cy-200 && y <= cy+200 && x >= cx-200 && x <= cx+200;
  1007. overrideCSS(dropZone, {
  1008. top: y < cy || stay ? '0' : 'auto',
  1009. bottom: y > cy || stay ? '0' : 'auto',
  1010. left: x < cx || stay ? '0' : 'auto',
  1011. right: x > cx || stay ? '0' : 'auto',
  1012. width: playing && stay && stub ? stub.clientWidth+'px' : '400px',
  1013. height: playing && stay && stub ? stub.clientHeight+'px' : dropZoneHeight + 'px',
  1014. margin: playing && stay ? 'auto' : '0',
  1015. position: !playing && stay || stub ? 'absolute' : 'fixed',
  1016. 'background-color': stub ? 'rgba(0,0,255,0.5)' : stay ? 'rgba(255,255,0,0.4)' : 'rgba(0,255,0,0.2)',
  1017. });
  1018. adjustPinnedOffset(dropZone, div);
  1019. (stay && !playing || stub ? (stub || div) : document.body).appendChild(dropZone);
  1020. break;
  1021. case 'dragend':
  1022. case 'drop':
  1023. var corner = calcPinnedCorner(dropZone);
  1024. dropZone.remove();
  1025. dropZone = null;
  1026. document.body.removeEventListener('dragenter', dragHandler);
  1027. document.body.removeEventListener('dragover', dragHandler);
  1028. document.body.removeEventListener('dragend', dragHandler);
  1029. document.body.removeEventListener('drop', dragHandler);
  1030. if (e.type == 'dragend')
  1031. break;
  1032. if (div.hasAttribute('playing'))
  1033. (corner ? $(div, '[pin="' + corner + '"]') : div.FYTE.stub).click();
  1034. else
  1035. startPlaying(div, {pin: corner});
  1036. }
  1037. }
  1038. });
  1039. }
  1040.  
  1041. function adjustPinnedOffset(el, self, corner) {
  1042. var offset = 0;
  1043. $$('.instant-youtube-container[pinned] [pin="' + (corner || calcPinnedCorner(el)) + '"][active]').forEach(function(pin) {
  1044. var container = pin.closest('[pinned]');
  1045. if (container != el && container != self) {
  1046. var bounds = container.getBoundingClientRect();
  1047. offset = Math.max(offset, el.style.top == '0px' ? bounds.bottom : innerHeight - bounds.top);
  1048. }
  1049. });
  1050. if (offset)
  1051. el.style[el.style.top == '0px' ? 'top' : 'bottom'] = offset + 'px';
  1052. }
  1053.  
  1054. function calcPinnedCorner(el) {
  1055. var t = el.style.top != 'auto';
  1056. var b = el.style.bottom != 'auto';
  1057. var l = el.style.left != 'auto';
  1058. var r = el.style.right != 'auto';
  1059. return t && b && l && r ? '' : (t ? 'top' : 'bottom') + '-' + (l ? 'left' : 'right');
  1060. }
  1061.  
  1062. function showOptions(e) {
  1063. var optionsButton = e.target;
  1064. translateHTML(optionsButton, 'afterend', '\
  1065. <div class="instant-youtube-options">\
  1066. <span>\
  1067. <label tl style="width: 100% !important;">Size:&nbsp;\
  1068. <select data-action="size-mode">\
  1069. <option tl value="Original">Original\
  1070. <option tl value="Fit to width">Fit to width\
  1071. <option>360p\
  1072. <option>480p\
  1073. <option>720p\
  1074. <option>1080p\
  1075. <option tl value="Custom">Custom...\
  1076. </select>\
  1077. </label>&nbsp;\
  1078. <label data-action="size-custom" ' + (resizeMode != 'Custom' ? 'disabled' : '') + '>\
  1079. <input type="number" min="320" max="9999" tl-placeholder="width" data-action="width" step="1" value="' + (resizeWidth||'') + '">\
  1080. x\
  1081. <input type="number" min="240" max="9999" tl-placeholder="height" data-action="height" step="1" value="' + (resizeHeight||'') + '">\
  1082. </label>\
  1083. </span>\
  1084. <label tl="content,title" title="msgStoryboardTip">\
  1085. <input data-action="storyboard" type="checkbox" ' + (showStoryboard ? 'checked' : '') + '>\
  1086. msgStoryboard\
  1087. </label>\
  1088. <span>\
  1089. <label tl="content,title" title="msgDirectTip">\
  1090. <input data-action="direct" type="checkbox" ' + (playDirectly ? 'checked' : '') + '>\
  1091. msgDirect\
  1092. </label>\
  1093. &nbsp;\
  1094. <label tl="content,title" title="msgDirectTip">\
  1095. <input data-action="direct-shown" type="checkbox" ' + (playDirectlyShown ? 'checked' : '') + '>\
  1096. msgDirectShown\
  1097. </label>\
  1098. </span>\
  1099. <label tl="content,title" title="msgSafeTip">\
  1100. <input data-action="safe" type="checkbox" ' + (skipCustom ? 'checked' : '') + '>\
  1101. msgSafe\
  1102. </label>\
  1103. <label tl="content,title" title="msgPinningTip">msgPinning\
  1104. <select data-action="pinnable">\
  1105. <option tl value="on">msgPinningOn\
  1106. <option tl value="hide">msgPinningHover\
  1107. <option tl value="off">msgPinningOff\
  1108. </select>\
  1109. </label>\
  1110. <span data-action="buttons">\
  1111. <button tl data-action="ok">OK</button>\
  1112. <button tl data-action="cancel">Cancel</button>\
  1113. </span>\
  1114. </div>\
  1115. ');
  1116. var options = optionsButton.nextElementSibling;
  1117.  
  1118. options.addEventListener('keydown', function(e) {
  1119. if (e.target.localName == 'input' &&
  1120. !e.shiftKey && !e.altKey && !e.metaKey && !e.ctrlKey && e.key.match(/[.,]/))
  1121. return false;
  1122. });
  1123.  
  1124. $(options, '[data-action="size-mode"]').value = resizeMode;
  1125. $(options, '[data-action="size-mode"]').addEventListener('change', function() {
  1126. var v = this.value != 'Custom';
  1127. var e = $(options, '[data-action="size-custom"]');
  1128. e.children[0].disabled = e.children[1].disabled = v;
  1129. v ? e.setAttribute('disabled', '') : e.removeAttribute('disabled');
  1130. });
  1131.  
  1132. $(options, '[data-action="pinnable"]').value = pinnable;
  1133.  
  1134. $(options, '[data-action="buttons"]').addEventListener('click', function(e) {
  1135. if (e.target.dataset.action != 'ok') {
  1136. options.remove();
  1137. return;
  1138. }
  1139. var v, shouldAdjust;
  1140. if (resizeMode != (v = $(options, '[data-action="size-mode"]').value)) {
  1141. GM_setValue('resize', resizeMode = v);
  1142. shouldAdjust = true;
  1143. }
  1144. if (resizeMode == 'Custom') {
  1145. var w = $(options, '[data-action="width"]').value |0;
  1146. var h = $(options, '[data-action="height"]').value |0;
  1147. if (resizeWidth != w || resizeHeight != h) {
  1148. updateCustomSize(w, h);
  1149. GM_setValue('width', resizeWidth);
  1150. GM_setValue('height', resizeHeight);
  1151. shouldAdjust = true;
  1152. }
  1153. }
  1154. if (showStoryboard != (v = $(options, '[data-action="storyboard"]').checked)) {
  1155. GM_setValue('showStoryboard', showStoryboard = v);
  1156. $$('.instant-youtube-container').forEach(updateHoverHandler);
  1157. }
  1158. if (playDirectly != (v = $(options, '[data-action="direct"]').checked)) {
  1159. GM_setValue('playHTML5', playDirectly = v);
  1160. if (playDirectlyShown) {
  1161. var newAltText = _(playDirectly ? 'Play with Youtube player' : 'Play directly (up to 720p)');
  1162. $$('.instant-youtube-alternative').forEach(function(e) {
  1163. e.textContent = newAltText;
  1164. });
  1165. }
  1166. }
  1167. if (playDirectlyShown != (v = $(options, '[data-action="direct-shown"]').checked)) {
  1168. GM_setValue('playHTML5', playDirectlyShown = v);
  1169. updateAltPlayerCSS();
  1170. }
  1171. if (skipCustom != (v = $(options, '[data-action="safe"]').checked)) {
  1172. GM_setValue('skipCustom', skipCustom = v);
  1173. }
  1174. if (pinnable != (v = $(options, '[data-action="pinnable"]').value)) {
  1175. GM_setValue('pinnable', pinnable = v);
  1176. }
  1177.  
  1178. options.remove();
  1179.  
  1180. if (shouldAdjust)
  1181. adjustNodes(e, e.target.closest('.instant-youtube-container'));
  1182. });
  1183. }
  1184.  
  1185. function updateCustomSize(w, h) {
  1186. resizeWidth = Math.min(9999, Math.max(320, w|0 || resizeWidth|0));
  1187. resizeHeight = Math.min(9999, Math.max(240, h|0 || resizeHeight|0));
  1188. }
  1189.  
  1190. function updateAltPlayerCSS() {
  1191. var s = '.instant-youtube-alternative { display:' + (playDirectlyShown ? 'block' : 'none') + '!important}';
  1192. $('style#instant-youtube-styles').textContent += s;
  1193. return s;
  1194. }
  1195.  
  1196. function important(cssText) {
  1197. return cssText.replace(/;/g, '!important;');
  1198. }
  1199.  
  1200. function tryCatch(func) {
  1201. try {
  1202. return func();
  1203. } catch(e) {
  1204. console.log(e);
  1205. }
  1206. }
  1207.  
  1208. function getFunctionComment(fn) {
  1209. return fn.toString().match(/\/\*([\s\S]*?)\*\/\s*\}$/)[1];
  1210. }
  1211.  
  1212. function $(selORnode, sel) {
  1213. return sel ? selORnode.querySelector(sel)
  1214. : document.querySelector(selORnode);
  1215. }
  1216.  
  1217. function $$(selORnode, sel) {
  1218. return Array.prototype.slice.call(
  1219. sel ? selORnode.querySelectorAll(sel)
  1220. : document.querySelectorAll(selORnode));
  1221. }
  1222.  
  1223. function $$remove(selORnode, sel) {
  1224. Array.prototype.forEach.call(
  1225. sel ? selORnode.querySelectorAll(sel)
  1226. : document.querySelectorAll(selORnode),
  1227. function(e) { e.remove() }
  1228. );
  1229. }
  1230.  
  1231. function overrideCSS(e, params) {
  1232. var names = Object.keys(params);
  1233. var style = e.style.cssText.replace(new RegExp('(^|\s|;)(' + names.join('|') + ')(:[^;]+)', 'gi'), '$1');
  1234. e.style.cssText = style.replace(/[^;]\s*$/, '$&;').replace(/^\s*;\s*/, '') +
  1235. names.map(function(n) { return n + ':' + params[n] + '!important' }).join(';') + ';';
  1236. }
  1237.  
  1238. // fix dumb Firefox bug
  1239. function floatPadding(node, style, dir) {
  1240. var padding = style['padding' + dir];
  1241. if (padding.indexOf('%') < 0)
  1242. return parseFloat(padding);
  1243. return parseFloat(padding) * (parseFloat(style.width) || node.clientWidth) / 100;
  1244. }
  1245.  
  1246. function translateHTML(baseElement, place, html) {
  1247. var tmp = document.createElement('div');
  1248. tmp.innerHTML = html;
  1249. $$(tmp, '[tl]').forEach(function(node) {
  1250. (node.getAttribute('tl') || 'content').split(',').forEach(function(what) {
  1251. var child, src, tl;
  1252. if (what == 'content') {
  1253. for (var i = node.childNodes.length-1, n; (i>=0) && (n=node.childNodes[i]); i--) {
  1254. if (n.nodeType == Node.TEXT_NODE && n.textContent.trim()) {
  1255. child = n;
  1256. break;
  1257. }
  1258. }
  1259. } else
  1260. child = node.getAttributeNode(what);
  1261. if (!child)
  1262. return;
  1263. src = child.textContent;
  1264. srcTrimmed = src.trim();
  1265. tl = src.replace(srcTrimmed, _(srcTrimmed));
  1266. if (src != tl)
  1267. child.textContent = tl;
  1268. });
  1269. });
  1270. baseElement.insertAdjacentHTML(place, tmp.innerHTML);
  1271. }
  1272.  
  1273. function initTL(src) {
  1274. var tlSource = {
  1275. 'watch on Youtube': {
  1276. 'ru': 'открыть на Youtube',
  1277. },
  1278. 'Play with Youtube player': {
  1279. 'ru': 'Включить плеер Youtube',
  1280. },
  1281. 'Play directly (up to 720p)': {
  1282. 'ru': 'Включить напрямую (макс. 720p)',
  1283. },
  1284. 'Shift-click to use alternative player': {
  1285. 'ru': 'Shift-клик для смены типа плеера',
  1286. },
  1287. 'Options': {
  1288. 'ru': 'Опции',
  1289. },
  1290. 'Size:': {
  1291. 'ru': 'Размер:',
  1292. },
  1293. 'Original': {
  1294. 'ru': 'Исходный',
  1295. },
  1296. 'Fit to width': {
  1297. 'ru': 'На всю ширину',
  1298. },
  1299. 'Custom...': {
  1300. 'ru': 'Настроить...',
  1301. },
  1302. 'width': {
  1303. 'ru': 'ширина',
  1304. },
  1305. 'height': {
  1306. 'ru': 'высота',
  1307. },
  1308. msgStoryboard: {
  1309. 'en': 'Storyboard thumbnails on hover',
  1310. 'ru': 'Раскадровка при наведении курсора',
  1311. },
  1312. msgStoryboardTip: {
  1313. 'en': 'Show storyboard preview on mouse hover at the bottom',
  1314. 'ru': 'Показывать миникадры при наведении мыши на низ кавер-картинки',
  1315. },
  1316. msgDirect: {
  1317. 'en': 'Play directly',
  1318. 'ru': 'Встроенный плеер браузера',
  1319. },
  1320. msgDirectTip: {
  1321. 'en': 'Note: Shift-click a thumbnail to use alternative player',
  1322. 'ru': 'Напоминание: удерживайте клавишу Shift при щелчке на картинке для альтернативного плеера',
  1323. },
  1324. msgDirectShown: {
  1325. 'en': 'Show under play button',
  1326. 'ru': 'Показывать под кнопкой ►',
  1327. },
  1328. msgSafe: {
  1329. 'en': 'Safe (skip videos with enablejsapi=1)',
  1330. 'ru': 'Консервативный режим',
  1331. },
  1332. msgSafeTip: {
  1333. 'en': 'Do not process customized videos with enablejsapi=1 parameter (requires page reload)',
  1334. 'ru': 'Не обрабатывать нестандартные видео с параметром enablejsapi=1 (подействует после обновления страницы)',
  1335. },
  1336. msgPinning: {
  1337. 'en': 'Corner pinning',
  1338. 'ru': 'Закрепление по углам',
  1339. },
  1340. msgPinningTip: {
  1341. 'en': 'Enable corner pinning controls when a video is playing. \nTo restore the video click the active corner pin or the original video placeholder.',
  1342. 'ru': 'Включить шпильки по углам для закрепления видео во время просмотра. \nДля отмены можно нажать еще раз на активированный уголЪ или на заглушку, где исходно было видео',
  1343. },
  1344. msgPinningOn: {
  1345. 'en': 'On',
  1346. 'ru': 'Да',
  1347. },
  1348. msgPinningHover: {
  1349. 'en': 'On, hover a corner to show',
  1350. 'ru': 'Да, при наведении курсора',
  1351. },
  1352. msgPinningOff: {
  1353. 'en': 'Off',
  1354. 'ru': 'Нет',
  1355. },
  1356. 'OK': {
  1357. 'ru': 'ОК',
  1358. },
  1359. 'Cancel': {
  1360. 'ru': 'Оменить',
  1361. },
  1362. };
  1363. var browserLang = navigator.language || navigator.languages && navigator.languages[0] || '';
  1364. var browserLangMajor = browserLang.replace(/-.+/, '');
  1365. var tl = {};
  1366. Object.keys(tlSource).forEach(function(k) {
  1367. var langs = tlSource[k];
  1368. var text = langs[browserLang] || langs[browserLangMajor];
  1369. if (text)
  1370. tl[k] = text;
  1371. });
  1372. return function(src) { return tl[src] || src };
  1373. }
  1374.  
  1375. function injectStylesIfNeeded(force) {
  1376. if (!fytedom[0] && !force)
  1377. return;
  1378. var styledom = $('style#instant-youtube-styles');
  1379. if (styledom) {
  1380. // move our rules to the end of HEAD to increase CSS specificity
  1381. if (styledom.nextElementSibling && document.head)
  1382. document.head.insertBefore(styledom, null);
  1383. return;
  1384. }
  1385. styledom = (document.head || document.documentElement).appendChild(document.createElement('style'));
  1386. styledom.id = 'instant-youtube-styles';
  1387. styledom.textContent = important(getFunctionComment(function() { /*
  1388. .instant-youtube-container {
  1389. contain: strict;
  1390. position: relative;
  1391. overflow: hidden;
  1392. cursor: pointer;
  1393. padding: 0;
  1394. margin: auto;
  1395. font: normal 14px/1.0 sans-serif, Arial, Helvetica, Verdana;
  1396. text-align: center;
  1397. background: black;
  1398. break-inside: avoid-column;
  1399. }
  1400. .instant-youtube-container[disabled] {
  1401. background: #888;
  1402. }
  1403. .instant-youtube-container[disabled] .instant-youtube-storyboard {
  1404. display: none;
  1405. }
  1406. .instant-youtube-container[pinned] {
  1407. box-shadow: 0 0 30px black;
  1408. }
  1409. .instant-youtube-container[playing] {
  1410. contain: inherit;
  1411. }
  1412. .instant-youtube-wrapper {
  1413. width: 100%;
  1414. height: 100%;
  1415. }
  1416. .instant-youtube-play-button {
  1417. display: block;
  1418. position: absolute;
  1419. width: 85px;
  1420. height: 60px;
  1421. left: 0;
  1422. right: 0;
  1423. top: 0;
  1424. bottom: 0;
  1425. margin: auto;
  1426. }
  1427. .instant-youtube-loading-spinner {
  1428. display: block;
  1429. position: absolute;
  1430. width: 20px;
  1431. height: 20px;
  1432. left: 0;
  1433. right: 0;
  1434. top: 0;
  1435. bottom: 0;
  1436. padding: 0;
  1437. margin: auto;
  1438. pointer-events: none;
  1439. background: url("data:image/gif;base64,R0lGODlhFAAUAJEDAMzMzLOzs39/f////yH/C05FVFNDQVBFMi4wAwEAAAAh+QQFCgADACwAAAAAFAAUAAACPJyPqcuNItyCUJoQBo0ANIxpXOctYHaQpYkiHfM2cUrCNT0nqr4uudsz/IC5na/2Mh4Hu+HR6YBaplRDAQAh+QQFCgADACwEAAIADAAGAAACFpwdcYupC8BwSogR46xWZHl0l8ZYQwEAIfkEBQoAAwAsCAACAAoACgAAAhccMKl2uHxGCCvO+eTNmishcCCYjWEZFgAh+QQFCgADACwMAAQABgAMAAACFxwweaebhl4K4VE6r61DiOd5SfiN5VAAACH5BAUKAAMALAgACAAKAAoAAAIYnD8AeKqcHIwwhGntEWLkO3CcB4biNEIFACH5BAUKAAMALAQADAAMAAYAAAIWnDSpAHa4GHgohCHbGdbipnBdSHphAQAh+QQFCgADACwCAAgACgAKAAACF5w0qXa4fF6KUoVQ75UaA7Bs3yeNYAkWACH5BAUKAAMALAIABAAGAAwAAAIXnCU2iMfaRghqTmMp1moAoHyfIYIkWAAAOw==");
  1440. }
  1441. .instant-youtube-container:hover .ytp-large-play-button-svg {
  1442. fill: #CC181E;
  1443. }
  1444. .instant-youtube-alternative {
  1445. display: block;
  1446. position: absolute;
  1447. width: 20em;
  1448. height: 20px;
  1449. top: 50%;
  1450. left: 0;
  1451. right: 0;
  1452. margin: 60px auto;
  1453. padding: 0;
  1454. border: none;
  1455. text-align: center;
  1456. text-decoration: none;
  1457. text-shadow: 1px 1px 3px black;
  1458. font-weight: bold;
  1459. color: white;
  1460. z-index: 8;
  1461. font-weight: normal;
  1462. font-size: 12px;
  1463. }
  1464. .instant-youtube-alternative:hover {
  1465. text-decoration: underline;
  1466. color: white;
  1467. background: transparent;
  1468. }
  1469. .instant-youtube-embed {
  1470. z-index: 10;
  1471. background: transparent;
  1472. }
  1473. .instant-youtube-title {
  1474. z-index: 20;
  1475. display: block;
  1476. position: absolute;
  1477. width: auto;
  1478. top: 0;
  1479. left: 0;
  1480. right: 0;
  1481. margin: 0;
  1482. padding: 7px;
  1483. border: none;
  1484. text-shadow: 1px 1px 2px black;
  1485. text-align: center;
  1486. text-decoration: none;
  1487. color: white;
  1488. background-color: rgba(0, 0, 0, 0.5);
  1489. }
  1490. .instant-youtube-title strong {
  1491. font: bold 14px/1.0 sans-serif, Arial, Helvetica, Verdana;
  1492. }
  1493. .instant-youtube-title strong:after {
  1494. content: " - $tl:'watch on Youtube'";
  1495. font-weight: normal;
  1496. margin-right: 1ex;
  1497. }
  1498. .instant-youtube-title span {
  1499. color: inherit;
  1500. }
  1501. .instant-youtube-title span:before {
  1502. content: "(";
  1503. }
  1504. .instant-youtube-title span:after {
  1505. content: ")";
  1506. }
  1507. @-webkit-keyframes instant-youtube-fadein {
  1508. from { opacity: 0 }
  1509. to { opacity: 1 }
  1510. }
  1511. @-moz-keyframes instant-youtube-fadein {
  1512. from { opacity: 0 }
  1513. to { opacity: 1 }
  1514. }
  1515. @keyframes instant-youtube-fadein {
  1516. from { opacity: 0 }
  1517. to { opacity: 1 }
  1518. }
  1519. .instant-youtube-container:not(:hover) .instant-youtube-title[hidden] {
  1520. display: none;
  1521. margin: 0;
  1522. }
  1523. .instant-youtube-title:hover {
  1524. text-decoration: underline;
  1525. }
  1526. .instant-youtube-title strong {
  1527. color: white;
  1528. }
  1529. .instant-youtube-options-button {
  1530. opacity: 0.6;
  1531. position: absolute;
  1532. right: 0;
  1533. bottom: 0;
  1534. margin: 0;
  1535. padding: 1.5ex 2ex;
  1536. font-size: 11px;
  1537. text-shadow: 1px 1px 2px black;
  1538. color: white;
  1539. }
  1540. .instant-youtube-options-button:hover {
  1541. opacity: 1;
  1542. background: rgba(0, 0, 0, 0.5);
  1543. }
  1544. .instant-youtube-options {
  1545. display: flex;
  1546. position: absolute;
  1547. right: 0;
  1548. bottom: 0;
  1549. margin: 0;
  1550. padding: 1ex 1ex 2ex 2ex;
  1551. flex-direction: column;
  1552. align-items: flex-start;
  1553. line-height: 1.5;
  1554. text-align: left;
  1555. opacity: 1;
  1556. color: white;
  1557. background: black;
  1558. z-index: 999;
  1559. }
  1560. .instant-youtube-options * {
  1561. width: auto;
  1562. height: auto;
  1563. margin: 0;
  1564. padding: 0;
  1565. font: inherit;
  1566. font-size: 13px;
  1567. vertical-align: middle;
  1568. text-transform: none;
  1569. text-align: left;
  1570. border-radius: 0;
  1571. text-decoration: none;
  1572. color: white;
  1573. background: black;
  1574. }
  1575. .instant-youtube-options > * {
  1576. margin-top: 1ex;
  1577. }
  1578. .instant-youtube-options label > * {
  1579. display: inline;
  1580. }
  1581. .instant-youtube-options select {
  1582. padding: .5ex .25ex;
  1583. border: 1px solid #444;
  1584. -webkit-appearance: menulist;
  1585. }
  1586. .instant-youtube-options [data-action="size-custom"] input {
  1587. width: 9ex;
  1588. padding: .5ex .5ex .4ex;
  1589. border: 1px solid #666;
  1590. }
  1591. .instant-youtube-options [data-action="buttons"] {
  1592. margin-top: 1em;
  1593. }
  1594. .instant-youtube-options button {
  1595. margin: 0 1ex 0 0;
  1596. padding: .5ex 2ex;
  1597. border: 2px solid gray;
  1598. font-weight: bold;
  1599. }
  1600. .instant-youtube-options button:hover {
  1601. border-color: white;
  1602. }
  1603. .instant-youtube-options label[disabled] {
  1604. opacity: 0.25;
  1605. }
  1606. .instant-youtube-storyboard {
  1607. height: 33%;
  1608. max-height: 90px;
  1609. display: block;
  1610. position: absolute;
  1611. left: 0;
  1612. right: 0;
  1613. bottom: 0;
  1614. overflow: visible;
  1615. overflow-x: visible;
  1616. overflow-y: visible;
  1617. }
  1618. .instant-youtube-storyboard:hover {
  1619. background-color: rgba(0,0,0,0.3);
  1620. }
  1621. .instant-youtube-storyboard[disabled] {
  1622. display:none;
  1623. }
  1624. .instant-youtube-storyboard div {
  1625. display: block;
  1626. position: absolute;
  1627. bottom: 0px;
  1628. pointer-events: none;
  1629. border: 3px solid #888;
  1630. box-shadow: 2px 2px 10px black;
  1631. transition: opacity .25s ease;
  1632. background-color: transparent;
  1633. background-origin: content-box;
  1634. opacity: 0;
  1635. }
  1636. .instant-youtube-storyboard:hover div {
  1637. opacity: 1;
  1638. }
  1639. .instant-youtube-container [pin] {
  1640. position: absolute;
  1641. width: 0;
  1642. height: 0;
  1643. margin: 0;
  1644. padding: 0;
  1645. top: auto; bottom: auto; left: auto; right: auto;
  1646. border-style: solid;
  1647. transition: opacity 2.5s ease-in, opacity 0.4s ease-out;
  1648. opacity: 0;
  1649. z-index: 100;
  1650. }
  1651. .instant-youtube-container[playing]:hover [pin]:not([transparent]) {
  1652. opacity: 1;
  1653. }
  1654. .instant-youtube-container[playing] [pin]:hover {
  1655. cursor: alias;
  1656. opacity: 1;
  1657. transition: opacity 0s;
  1658. }
  1659. .instant-youtube-container [pin=top-left][active] { border-top-color: green; }
  1660. .instant-youtube-container [pin=top-left]:hover { border-top-color: #fc0; }
  1661. .instant-youtube-container [pin=top-left] {
  1662. top: 0; left: 0;
  1663. border-width: 10px 10px 0 0;
  1664. border-color: red transparent transparent transparent;
  1665. }
  1666. .instant-youtube-container [pin=top-right][active] { border-right-color: green; }
  1667. .instant-youtube-container [pin=top-right]:hover { border-right-color: #fc0; }
  1668. .instant-youtube-container [pin=top-right] {
  1669. top: 0; right: 0;
  1670. border-width: 0 10px 10px 0;
  1671. border-color: transparent red transparent transparent;
  1672. }
  1673. .instant-youtube-container [pin=bottom-right][active] { border-bottom-color: green; }
  1674. .instant-youtube-container [pin=bottom-right]:hover { border-bottom-color: #fc0; }
  1675. .instant-youtube-container [pin=bottom-right] {
  1676. bottom: 0; right: 0;
  1677. border-width: 0 0 10px 10px;
  1678. border-color: transparent transparent red transparent;
  1679. }
  1680. .instant-youtube-container [pin=bottom-left][active] { border-left-color: green; }
  1681. .instant-youtube-container [pin=bottom-left]:hover { border-left-color: #fc0; }
  1682. .instant-youtube-container [pin=bottom-left] {
  1683. bottom: 0; left: 0;
  1684. border-width: 10px 0 0 10px;
  1685. border-color: transparent transparent transparent red;
  1686. }
  1687. .instant-youtube-dragndrop-placeholder {
  1688. z-index: 999999999;
  1689. margin: 0;
  1690. padding: 0;
  1691. background: rgba(0, 255, 0, 0.1);
  1692. border: 2px dotted green;
  1693. box-sizing: border-box;
  1694. pointer-events: none;
  1695. }
  1696. */}).replace(/\$tl:'(.+?)'/g, function(m, m1) { return _(m1) })
  1697. ) +
  1698. updateAltPlayerCSS();
  1699. }