YouTube Me Again!

ytma! automatically converts YouTube(TM), Vimeo, Vine, Soundcloud, WebM, and MP4 links into real embedded videos.

当前为 2015-05-14 提交的版本,查看 最新版本

  1. /*jslint indent: 4, maxerr: 50, browser: true, devel: true, sub: false, fragment: false, nomen: true, plusplus: true, bitwise: true, regexp: true, newcap: true */
  2. // ==UserScript==
  3. // Do not modify and re-release this script!
  4. // If you would like to add support for other sites, please tell me and I'll put it in the includes.
  5.  
  6. // @id youtube-me-again
  7. // @name YouTube Me Again!
  8. // @namespace hateradio)))
  9. // @author hateradio
  10. // @version 6.0.1
  11. // @description ytma! automatically converts YouTube(TM), Vimeo, Vine, Soundcloud, WebM, and MP4 links into real embedded videos.
  12. // @homepage https://greasyfork.org/en/scripts/1023-youtube-me-again
  13. // @icon https://dl.dropboxusercontent.com/u/14626536/userscripts/i/ytma/ytma32.png
  14. // @icon64 https://dl.dropboxusercontent.com/u/14626536/userscripts/i/ytma/ytma64.png
  15. // @screenshot https://dl.dropboxusercontent.com/u/14626536/userscripts/i/ytma/ytmascreen5.png
  16.  
  17. // @include https://vine.co/v/*/embed/simple
  18. // @match https://vine.co/v/*/embed/simple
  19.  
  20. // @include http*://*youtube-nocookie.com/embed/*
  21. // @match *://*.youtube-nocookie.com/embed/*
  22.  
  23. // @include http*://*youtube.com/embed/*
  24. // @match *://*.youtube.com/embed/*
  25.  
  26. // @include https://gfycat.com/iframe/*
  27. // @match https://gfycat.com/iframe/*
  28.  
  29. // @include http://*.neogaf.com/forum/showthread.php*
  30. // @include http://*.neogaf.com/forum/showpost.php?p*
  31. // @include http://*.neogaf.com/forum/newreply.php*
  32. // @include http://*.neogaf.com/forum/editpost.php*
  33. // @include http://*.neogaf.com/forum/private.php*
  34.  
  35. // @match http://*.neogaf.com/forum/showthread.php*
  36. // @match http://*.neogaf.com/forum/showpost.php?p*
  37. // @match http://*.neogaf.com/forum/newreply.php*
  38. // @match http://*.neogaf.com/forum/editpost.php*
  39. // @match http://*.neogaf.com/forum/private.php*
  40.  
  41. // @include http*://*what.cd/forums.php?*viewthread*
  42. // @include http*://*what.cd/torrents.php?*
  43. // @include http*://*what.cd/user.php?*
  44.  
  45. // @match *://*.what.cd/forums.php?*viewthread*
  46. // @match *://*.what.cd/torrents.php?*
  47. // @match *://*.what.cd/user.php?*
  48.  
  49. // @updated 13 May 2015
  50.  
  51. // @grant GM_xmlhttpRequest
  52. // @grant unsafeWindow
  53.  
  54. // @run-at document-end
  55. // ==/UserScript==
  56.  
  57. /*
  58.  
  59. Changes:
  60.  
  61. ### 6.0
  62.  
  63. * New: Imgur GIFV (WEBM/MP4) support
  64. * New: Button to remove cache (descriptions/thumbnail links/etc)
  65. * New: SoundCloud playlist support
  66. * Default video quality is now 720p/HD
  67. * Soundcloud now uses HTML5 player
  68. * Players that open on scroll will no longer trigger the opening of players higher on the page
  69. * Adds HTML5, Gfycat, Imgur icons on links
  70. * Improved Soundcloud and GfyCat URL matchers
  71. * Restructured code base to simplify creation of media controls
  72. * Restructured CSS
  73. * Patched back Gfycat iFrame setting for Safari (it is incompatible with new settings)
  74. * Updates YouTube data API
  75.  
  76.  
  77. Removes:
  78.  
  79. * Object tag for YouTube for Flash (Deprecated)
  80. * "Batch" loading of descriptions (Only manual and scroll methods are supported)
  81.  
  82. Whitelist these sites on NoScript/NotScript/etc.
  83. ------------------------------------------------
  84. * neogaf.com
  85. * youtube.com
  86. * youtube-nocookie.com
  87. * googlevideo.com (HTML5 player sends videos from this domain)
  88. * googleapis.com (YT video data)
  89. * vimeo.com
  90. * vimeocdn.com
  91. * soundcloud.com
  92. * sndcdn.com
  93. * vineco.com
  94. * vine.com
  95. * vine.co
  96. * gfycat.com
  97. * dropboxusercontent.com
  98.  
  99.  
  100. Whitelist these on Ghostery
  101. ---------------------------
  102.  
  103. * SoundCloud (Widgets, Audio / Music Player)
  104.  
  105. */
  106.  
  107. (function () {
  108. 'use strict';
  109.  
  110. var $$, strg, update;
  111.  
  112. if (!Function.prototype.bind) {
  113. Function.prototype.bind = function (self) {
  114. var args = [].slice.call(arguments, 1), fn = this;
  115. return function () {
  116. return fn.apply(self, args.concat([].slice.call(arguments)));
  117. };
  118. };
  119. }
  120.  
  121. function isNumber(n) {
  122. return !isNaN(parseFloat(n)) && isFinite(n);
  123. }
  124.  
  125. function inject(func) {
  126. var script = document.createElement('script');
  127. script.type = 'text/javascript';
  128. script.textContent = '(' + func + ')();';
  129. document.body.appendChild(script);
  130. document.body.removeChild(script);
  131. }
  132.  
  133. // D O M Handle
  134. $$ = {
  135. s: function (selector, cb) { var s = document.querySelectorAll(selector), i = -1; while (++i < s.length) { if (cb(s[i], i, s) === false) { break; } } },
  136. o: function (object, cb) { var i; for (i in object) { if (object.hasOwnProperty(i)) { if (cb(i, object[i], object) === false) { break; } } } },
  137. a: function (e) { var i = 1, j = arguments.length, f = document.createDocumentFragment(); for (i; i < j; i++) { if (arguments[i]) { f.appendChild(arguments[i]); } } e.appendChild(f); return e; },
  138. e: function (t, o, e, p) {
  139. var c = document.createElement(t);
  140. $$.o(o, function (k, v) {
  141. var b = k.charAt(0);
  142. switch (b) {
  143. case '_':
  144. c.dataset[k.substring(1)] = v;
  145. break;
  146. case '$':
  147. c.setAttribute(k.substring(1), v);
  148. break;
  149. default:
  150. c[k] = v;
  151. }
  152. });
  153.  
  154. if (e && p) {
  155. c.appendChild(e);
  156. } else if (e) {
  157. e.appendChild(c);
  158. }
  159. return c;
  160. },
  161. x: function (selector) { return this.ary(document.querySelectorAll(selector)); },
  162. ary: function (ary) { return Array.from ? Array.from(ary) : Array.prototype.slice.call(ary); }
  163. };
  164.  
  165. // S T O R A G E HANDLE
  166. strg = {
  167. MAX: 6010,
  168. on: false,
  169. test: function () { try { var a, b = localStorage, c = Math.random().toString(16).substr(2, 8); b.setItem(c, c); a = b.getItem(c); return a === c ? !b.removeItem(c) : false; } catch (e) { return false; } },
  170. read: function (key) {
  171. try {
  172. return JSON.parse(localStorage.getItem(key));
  173. } catch (e) {
  174. console.error(e.lineNumber + ':' + e.message);
  175. return undefined;
  176. }
  177. },
  178. save: function (key, val) { return this.on ? !localStorage.setItem(key, JSON.stringify(val)) : false; },
  179. wipe: function (key) { return this.on ? !localStorage.removeItem(key) : false; },
  180. zero: function (o) { var k; for (k in o) { if (o.hasOwnProperty(k)) { return false; } } return true; },
  181. grab: function (key, def) { var s = strg.read(key); return strg.zero(s) ? def : s; },
  182. size: function () {
  183. var length = 0, key;
  184. try {
  185. for (key in window.localStorage) {
  186. if (window.localStorage.hasOwnProperty(key)) {
  187. length += window.localStorage[key].length;
  188. }
  189. }
  190. } catch (e) {}
  191. return 3 + ((length * 16) / (8 * 1024));
  192. },
  193. full: function () {
  194. try {
  195. var date = +(new Date());
  196. localStorage.setItem(date, date);
  197. localStorage.removeItem(date);
  198. return false;
  199. } catch (e) {
  200. if (e.name === 'QuotaExceededError' ||
  201. e.name === 'NS_ERROR_DOM_QUOTA_REACHED') {
  202. return true;
  203. }
  204. }
  205. },
  206. init: function () { this.on = this.test(); }
  207. };
  208. strg.init();
  209.  
  210. // U P D A T E HANDLE
  211. update = {
  212. name: 'ytma!',
  213. version: 5100,
  214. key: 'ujs_YTMA_UPDT_HR',
  215. callback: 'ytmaupdater',
  216. page: 'https://greasyfork.org/scripts/1023-youtube-me-again',
  217. uric: 'https://dl.dropboxusercontent.com/u/14626536/userscripts/updt/ytma/ytma.js', // If you get "Failed to load source for:" in Firebug, allow dropboxusercontent.com to run scripts.
  218. interval: 5,
  219. day: (new Date()).getTime(),
  220. time: function () { return new Date(this.day + (1000 * 60 * 60 * 24 * this.interval)).getTime(); },
  221. top: document.head || document.body,
  222. css: function (t) {
  223. if (!this.style) {
  224. this.style = document.createElement('style');
  225. this.style.type = 'text/css';
  226. this.top.appendChild(this.style);
  227. }
  228. this.style.appendChild(document.createTextNode(t + '\n'));
  229. },
  230. js: function (t) {
  231. var j = document.createElement('script');
  232. j.type = 'text/javascript';
  233. j[/^https?\:\/\//i.test(t) ? 'src' : 'textContent'] = t;
  234. this.top.appendChild(j);
  235. },
  236. notification: function (j) {
  237. if (j) {
  238. if (this.version < j.version) {
  239. window.localStorage.setItem(this.key,
  240. JSON.stringify({date: this.time(), version: j.version, page: j.page }));
  241. } else {
  242. return true;
  243. }
  244. }
  245. var a = document.createElement('a'), b = JSON.parse(window.localStorage.getItem(this.key));
  246. a.href = b.page || '#';
  247. a.target = '_blank';
  248. a.id = 'userscriptupdater';
  249. a.title = 'Update now.';
  250. a.textContent = 'An update for ' + this.name + ' is available.';
  251. document.body.appendChild(a);
  252. return true;
  253. },
  254. check: function (opt) {
  255. if (!strg.on) { return; } // typeof (GM_updatingEnabled) === 'boolean' ||
  256. var stored = strg.read(this.key), j, page;
  257. this.csstxt();
  258. if (opt || !stored || stored.date < this.day) {
  259. page = stored && stored.page ? stored.page : '#';
  260. strg.save(this.key, {date: this.time(), version: this.version, page: page});
  261. j = this.notification.toString()
  262. .replace('function', 'function ' + this.callback)
  263. .replace('this.version', this.version)
  264. .replace(/(?:this\.key)/g, "'" + this.key + "'")
  265. .replace('this.time()', this.time())
  266. .replace('this.name', "'" + this.name + "'");
  267. this.js(j);
  268. this.js(this.uric);
  269. } else if (this.version < stored.version) { this.notification(); }
  270. },
  271. csstxt: function () {
  272. if (!this.pop) { this.pop = true; this.css('#userscriptupdater,#userscriptupdater:visited{-moz-box-shadow:0 0 6px #787878;-webkit-box-shadow:0 0 6px #787878;box-shadow:0 0 6px #787878;border:1px solid #777;-moz-border-radius:4px;border-radius:4px;cursor:pointer;color:#555;font-family:Arial, Verdana, sans-serif;font-size:11px;font-weight:700;text-align:justify;min-height:45px;position:fixed;z-index:999999;right:10px;top:10px;width:170px;background:#ebebeb url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACsAAACLCAYAAAD4QWAuAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2RpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo1NUIzQjc3MTI4N0RFMDExOUM4QzlBNkE2NUU3NDJFNCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpGN0Q1OEQyNjdEQzUxMUUwQThCNEE3MTU1NDU1NzY2OSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpGN0Q1OEQyNTdEQzUxMUUwQThCNEE3MTU1NDU1NzY2OSIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo1NUIzQjc3MTI4N0RFMDExOUM4QzlBNkE2NUU3NDJFNCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo1NUIzQjc3MTI4N0RFMDExOUM4QzlBNkE2NUU3NDJFNCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Po6YcvQAAAQFSURBVHja7JzBSxRRHMdnp+gkiLdOgtshKGSljQVF8CK0biEErYfwFmT+BQpdA0MIBEFtTx2qSxESaAt5ioUQFDp5sjl06rbnumzfp7+VbZx5M+/Nb9wZ+f3g56wzO28//ua93/u9J/stdDodx2/P3o85llaFT8JvwlvwTfhf00a2Hv8IPO86PHYHvg//An8OfwRfg/9RfzvTZ7DBvoZXQq6p6D7MCuwT+N2I92zAB/sNO0yPO8quwxf7DasABmK+d0XTVVKHnYIvG96z1i9Ymw8ep/R2obAqNdkm41e2sFct71v1/f4BiXyOJpRpHKZ918s9527B5+FvLwJWDaoR3zmvZ/bZw2HPNyMeBOTeb/BfaXaDEuVMvx2G3QDQMkW21wZsUpkp7GbIeU9zz3TI+WXTVGYCW6XRbApb1lxbTwt2VVMltS1hVWRnuWFVqhoNudbW9NchHIpc+ToO7GDE49JFtRij/ZG4gy0O7CIVIjZWNuhiw0lhK1SA6GzI8ppxKouCjTNaOWC7qWzKFrYaNw/SQOKwNVtYk4KjyAQ7RpnHCHaeCg7ugZQon7sBj3RYM62mHdmTVAaGxbiRNVmqRM3/bUvgDQCX/CcLvZsceEOF1v82dgPTrkdVVp2iXU8Q4e9ob0IHu59gUecxdwdlMwBunusGAJ1NuPr0KLoFdYQ3GGBXAiMLWC9gBRDX2gTa9g3Wp7Rbk8TqaPfjWWRp9I0kaLARVCbiXMO/xLGwdfCd7Oa4eDGQdD0fYYcJ7z/bzXHpxbWEDRaddO1FF3aSobE6pazAawztX0H7465mXWVqB2hwqWdwFeFfGaM+Wlh4V/rkMO2fpmy3VWTf5AD0NzLLkYsfn53T7fUs21k2UPmw5jBs9qZgx/AH4Ns+VxvQwJg0rGXTMPUfnhYgj0MLmayb6+TIBFZgBVZgBVZgBVZgBVZgBVZgBVZgBVZgBVZgBVZgBVZgBVZgBVZgBVZgBVZgBVZgBVZgBVZgBVZgBTZzVrg3U+Nsz1iTo7m7c+GRFU2ONGBFkyMNWNHkSANWNDl0xqbJAZ+j1/nR5HBOv6zm/8JaPjQ5KKqiyRFVpORfk8PRf3NZq8lRrd3PhiaHc6pvcLk0ORDdfGlyAFg0OdKAPUlliG76mhyGUNaDLXOaHIjuJdXkoKVKXzU5wlJZZjU5AFyKKhErFkuVbjcoUo3Apcmhnu6Ebkcmc5oczd2dZlA3YNHkUAFwUtLkcJlWnm1a1ng94AvkbKnM1SxVTKwRMphYNDkAPNiFFU0OZuPV5NDMYiyaHOgKvJoc8CVftFk1ORRsi/FxvYR3yH9qZjYba+VGkwOTw5GCzZcmByzTmhyI6ra/kNkiz4wmByD/0+T4J8AAyDkZArebBxMAAAAASUVORK5CYII=) no-repeat 13px 15px;padding:12px 20px 10px 65px}#userscriptupdater:hover,#userscriptupdater:visited:hover{color:#55698c!important;background-position:13px -85px;border-color:#8f8d96}'); }
  273. }
  274. };
  275.  
  276. /** Y T M A CLASS
  277. * Bare YTMA class, filled through _new() or _reactivate()
  278. */
  279. function YTMA() {}
  280.  
  281. YTMA.events = {
  282. clicks: function (e) { // YTMA global click dispatcher
  283. var t = e.target;
  284.  
  285. if (t) {
  286. // console.log('YTMA.clicks');
  287. if (t.tagName === 'VAR' && t.hasAttribute('data-ytmuid')) { // trigger the ui
  288. console.log('show', t.dataset.ytmuid);
  289. YTMA.UI.createFromTrigger(t).showPlayer();
  290. } else if (t.hasAttribute('data-ytmdescription')) {
  291. console.log('load', t.dataset.ytmid);
  292. YTMA.external.events.manualLoad(e);
  293. }
  294. }
  295. },
  296. thumb: {
  297. start: function (e) {
  298. var el = e.target;
  299. el.dataset.thumb = el.dataset.thumb > 0 ? (el.dataset.thumb % 3) + 1 : 2;
  300. el.style.backgroundImage = ['url(https://i3.ytimg.com/vi/', el.dataset.ytmid, '/', el.dataset.thumb, '.jpg)'].join('');
  301. el.dataset.timeout = window.setTimeout(YTMA.events.thumb.start.bind(this, e), 800);
  302. },
  303. stop: function (e) {
  304. window.clearTimeout(e.target.dataset.timeout);
  305. }
  306. }
  307. };
  308.  
  309. YTMA.num = 0;
  310.  
  311. YTMA.create = function (link) {
  312. var data = YTMA.grabIdAndSite(link), id, y;
  313. if (data.valid === true) {
  314. y = new YTMA()._new(data.id, data.site, link);
  315. id = y.data.uid;
  316. YTMA.set[id] = y;
  317. YTMA.set[id].setup();
  318. return y;
  319. }
  320. return {};
  321. };
  322.  
  323. YTMA.grabIdAndSite = function (link) {
  324. var uri = link.href, id, site, match;
  325. try {
  326. site = YTMA.reg.siteByTest[YTMA.reg.site.test(uri) ? RegExp.lastMatch : ''];
  327. // console.log(site);
  328.  
  329. if (site === 'html5') { // || site === 'html5-audio'
  330. id = uri.slice(-15);
  331. } else if (site === 'soundcloud') {
  332. if (!YTMA.reg.extra.soundcloud.playlist.test(uri)) {
  333. link.href = uri = YTMA.reg.fix.soundcloud(uri);
  334. }
  335. match = YTMA.reg.matchers.soundcloud.exec(uri);
  336. if (match) {
  337. id = YTMA.escapeId(match[1]).slice(-50);
  338. }
  339. } else {
  340. id = uri.match(YTMA.reg.matchers[site])[1];
  341. }
  342.  
  343. if (id && YTMA.DB.sites[site]) {
  344. return {id: id, site: site, valid: true};
  345. }
  346. throw TypeError('Invalid ID/Site: ' + id + ' @ ' + site);
  347. } catch (e) {
  348. console.error(uri, e);
  349. return {valid: false};
  350. }
  351. };
  352.  
  353. YTMA.escapeId = function (id) {
  354. return (id += '').replace(/(?:\W)/g, '_');
  355. };
  356.  
  357. YTMA.route = {
  358. host: document.location.host.replace('www.', ''),
  359. control: {
  360. $: {
  361. patchSafari: function () {
  362. YTMA.DB.videoTag.gfycat = false;
  363. YTMA.DB.sources.gfycat = YTMA.DB.sources.$gfycatFrame;
  364. },
  365. checkStorage: function () {
  366. if (strg.full() === true) {
  367. console.log('storage is full!');
  368. try {
  369. localStorage.removeItem(YTMA.external.version);
  370. strg.on = strg.test();
  371. } catch (e) {
  372. console.error(e);
  373. }
  374. }
  375. },
  376. runOnce: function () {
  377. if (!document.body.hasAttribute('ytma-enabled')) {
  378. document.body.setAttribute('ytma-enabled', true);
  379.  
  380. this.checkStorage();
  381.  
  382. if (!YTMA.DB.extension) { update.check(); }
  383.  
  384. YTMA.css();
  385. YTMA.user.init();
  386. YTMA.DB.postInit();
  387.  
  388. document.body.addEventListener('click', YTMA.events.clicks, false);
  389. }
  390. }
  391. },
  392. go: function (host) {
  393. if (/(?:googlevideo|youtube-nocookie\.com|youtube\.com\.?)/i.test(host)) {
  394. this.sites.youtube();
  395. } else if (this.sites[host]) {
  396. this.sites[host]();
  397. } else {
  398. this.sites.$generic();
  399. }
  400. },
  401. sites: {
  402. $generic: function () {
  403. YTMA.route.control.$.runOnce();
  404.  
  405. if (YTMA.DB.browser.safari) { // safari patch
  406. YTMA.route.control.$.patchSafari();
  407. }
  408.  
  409. if (YTMA.selector.processor() > 0) {
  410. YTMA.user.fn.loadPreferences();
  411. }
  412. },
  413. 'gfycat.com': function () {
  414. var v = document.querySelector('video');
  415. v.controls = true;
  416. update.css('body,html {overflow:hidden;height:100%;width:100%} video {display:table;height:100%;margin:0 auto;}');
  417. document.body.appendChild(v);
  418. },
  419. 'vine.co': function () {
  420. // console.log('vine.co');
  421.  
  422. window.addEventListener('resize', function () {
  423. $$.s('[style]', function (e) {
  424. e.removeAttribute('style');
  425. });
  426. });
  427. },
  428. youtube: function () { // lets force some quality parity
  429. console.log('now inside youtube . . .', document.location);
  430.  
  431. if (/(?:vq=(\w+))/.test(document.location.search)) {
  432. document.body.setAttribute('gm-player-quality', RegExp.lastParen);
  433. }
  434.  
  435. if (/(?:volume=(\d+))/.test(document.location.search)) {
  436. document.body.setAttribute('gm-player-volume', RegExp.lastParen);
  437. }
  438.  
  439. inject(function () {
  440. var max = 10, count = 1, intr = window.setInterval(function () {
  441. console.log('inside says: ', count, !!window.yt);
  442. if (window.yt && window.player) {
  443. var p = window.yt.player.getPlayerByElement(window.player);
  444.  
  445. if (document.body.hasAttribute('gm-player-quality')) {
  446. console.log('inside says: setting quality to ', document.body.getAttribute('gm-player-quality'));
  447. p.setPlaybackQuality(document.body.getAttribute('gm-player-quality'));
  448. }
  449.  
  450. if (document.body.hasAttribute('gm-player-volume')) {
  451. console.log('inside says: setting volume to ', document.body.getAttribute('gm-player-volume'));
  452. p.setVolume(document.body.getAttribute('gm-player-volume'));
  453. }
  454.  
  455. window.clearInterval(intr);
  456. } else {
  457. console.log(count);
  458. count += 1;
  459. if (count > max) {
  460. window.clearInterval(intr);
  461. }
  462. }
  463. }, 500);
  464. });
  465. }
  466. }
  467. },
  468. load: function () {
  469. this.control.go(this.host);
  470. }
  471. };
  472.  
  473. YTMA.main = function () {
  474. YTMA.route.load();
  475. };
  476.  
  477. YTMA.set = {};
  478.  
  479. YTMA.collect = function (id) {
  480. var i, a = [];
  481. for (i in YTMA.set) {
  482. if (YTMA.set.hasOwnProperty(i) && YTMA.set[i].data.id === id) {
  483. a.push(YTMA.set[i]);
  484. }
  485. }
  486. return a;
  487. };
  488.  
  489. YTMA.reg = {
  490. site : /\b(youtu)|(vimeo)|(vine)|(soundcloud)|(gfycat)|(imgur)|(\.webm$)|(\.mp4$)|(\.gifv$)/,
  491. time : /(?:t\=(?:(\d+)m)?(\d+))/,
  492. ios : /(?:\b(?:ipod|iphone|ipad))\b/i,
  493. matchers: {
  494. youtube: /(?:(?:(?:v\=|#p\/u\/\d*?\/)|(?:v\=|#p\/c\/[a-zA-Z0-9]+\/\d*?\/)|(?:embed\/)|(?:v\/)|(?:\.be\/))([A-Za-z0-9-_]{11}))/i,
  495. vimeo: /(?:vimeo\.com\/(\d+))/i,
  496. vine: /(?:vine\.co\/v\/([A-Za-z0-9-_]{11}))/i,
  497. soundcloud: /(?:\/\/(?:\bwww|m\.\b)?soundcloud\.com\/(.+?\/.+))/i,
  498. gfycat: /(?:gfycat\.com\/(?:(\b(?:[A-Z][a-z]*){3,}\b)))/,
  499. imgur: /(?:imgur\.com\/(\w+)\.(?:gifv|mp4|webm))/i
  500. },
  501. extra: {
  502. soundcloud: {
  503. playlist: /(?:soundcloud\.com\/.+\/sets\/)/
  504. }
  505. },
  506. siteByTest: {
  507. youtu: 'youtube',
  508. vimeo: 'vimeo',
  509. vine: 'vine',
  510. gfycat: 'gfycat',
  511. imgur: 'imgur',
  512. '.webm': 'html5',
  513. '.mp4': 'html5',
  514. // '.mp3': 'html5-audio',
  515. '.gifv': 'html5',
  516. soundcloud: 'soundcloud'
  517. },
  518. fix: {
  519. $removeSearch: function (uri, keepHash) {
  520. var s = uri.indexOf('?'), h = uri.indexOf('#'), hash = '';
  521. if (s > -1) {
  522. if (keepHash && h > -1) {
  523. hash = uri.substr(h);
  524. }
  525. uri = uri.substr(0, s) + hash;
  526. }
  527. return uri;
  528. },
  529. soundcloud: function (uri) {
  530. var match = YTMA.reg.matchers.soundcloud.exec(uri), id;
  531. if (match) {
  532. id = match[1].split('/', 2).join('/');
  533. uri = this.$removeSearch('https://soundcloud.com/' + id, true);
  534. }
  535.  
  536. return uri;
  537. }
  538. }
  539. };
  540.  
  541. YTMA.img = {
  542. fav: {
  543. soundcloud : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAXZJREFUeNp0UjFOw0AQnD3bCYaEoIgiUIQKikiAKKGgSAkFL0BQ0FEgUfECShpewB+QEE+gokDiBxGRiCCEBBzHd8ucLYGQ4Kw7+9azc7OzJ6oKnFYUxgD+W/DHYNA54GIooifTitYqg1okaPFff6BcTLF5fECYb5IJNJ3kMfUP2dxHPycwcQ0SliARocSGsA46YkKWQW0GS6BDAGlu5Myu84AgSmHKM/DY0NegwwR2+AJrSjBre5BaA1H7GFKpI729QHp9jqiaeTBn5mB7fWirjWB9B9JYBrwEMnop5d0zfD53YO9vYMY8SQcpZGkTZvsIstiivDfoaxfIxt8exYeXCLYOSOwlJZQ6twSpL0DfX4EoZtkCqc7/Njalbxnfdt+oSwhurkBnCWI/1FlgqgKNZ3JnZDSA3FFSpYdQAoF56tKNDmhOYTzJ3OSHXZkflJhYEyawcnH02HpmxZ+DrRJlgmacvrsHRmHln2tRnIiAy5WTLwEGAK4QoBQmtGHkAAAAAElFTkSuQmCC',
  544. youtube : 'https://www.youtube.com/favicon.ico',
  545. vimeo : 'https://vimeo.com/favicon.ico',
  546. vine : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABcklEQVQ4jX2SvyvFYRTGP+9NdzAYbpIkysA/ICnKoEwvA4vlUkoyGQzvoOiK4R0YbDLhDhbi9maWIjEqcScWkyRhMNzXcM/3On39OMt5nvOe85wfvSbGyH9mgs8Cq8AUkAMegU1gJVpXqfu3umpbwITiLUBB8HJGuryb4KMJ/izVvUMVnwNjwJPwGYCMkLL4zlT3XoUL0boD4FBNUhO4Fd9ogm9SRVmFn8Una79qgWuV2K3wvcLJdG3iL7TaiUocAI4Fn0vnHDBrgt+L1g2q3NoEl8CH4KHkMVr3CSwI7QeOTPA9WsAk/8AEvw+MSrwrWleuJQVfABZJWbTOZBTfVXg6lbgEHKjQTcL1BFngAWimeuH2aN2LvHUDV1JcjNbl0zdI9l0T2pDsboKvB7Yl/ga4X2+gku+AVqAC9AFzwLik5KN1xT8FRGQEOBL6yfdn2onWTZKyTDoQrSsB60KT4lNSh/1TQETmgQ1ZowQMy41+2BeLRXeRaKuHSAAAAABJRU5ErkJggg==',
  547. html5 : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA0AAAANCAYAAABy6+R8AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAdlJREFUeNpcks9rE0EUx7+b3TRktxvbhUhrNm6SrUWtxxbxJ9iLl0JFKWg8WLD05EWhIuRieogXwYPX4tGb/gPSq/oP6KH+uG2MFaRJJXaTbp7fWbsl+uDDzLx533lv3ozmeyUM2VlSJxeISULygTwhr5Og1JDgOXlPrpLRMAxTIpLlfJa8Ipv/i56Re4lT07SYKIqQSh2eO0/eqYnujI35HF8O19jr9XBneRmt1je0d3ag63qy5ZIAvNObE+WKTJXKktENoVPsrCntdlsWFxbiNXNJyS3KdMUXxv8w6LzY7XZhmiYer9fBYOTzR5HL5XCzWsWxYhFnTs/gxcYGgiCAZVmOyhR5BVcm8nlZvbsi29+3ZdgGg4Gs1+viua4UJiZVpkiJeqo0VaIqxbGPSKfTORRduXQ59peLx5PyItWaj9yDQtmt21XYto1GoxGvbywtxaNhGHE3ab+V6L6aKYczPo7ZuTk8WnuIWq2G64vX4JU8zJw8BXXv5D21gx+xydrn7VEb/f0+tj5twa/4+Pz1C6Y4Oo6DZrOJdDrdYuxkIlKP+ZbZzoV7e8iyk8kD/9rdRXpkBJlMpsmDC/98I97pPF//AQUBl/2/jRvsm5b1kxmeJgJlfwQYAKZQxgzeI6/EAAAAAElFTkSuQmCC',
  548. gyfcat : 'https://gfycat.com/favicon.ico',
  549. imgur : 'https://imgur.com/favicon.ico'
  550. },
  551. css: {
  552. load : 'data:image/gif;base64,R0lGODlhDgAKAJEAAP///+BKV////wAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFCgACACwAAAAADgAKAAACHFSOeQYI71p6MtAJz41162yBH+do5Ih1kKG0QgEAIfkEBQoAAgAsAAABAA0ACAAAAhSUYGEoerkgdIzKGlu2ET/9ceJmFAAh+QQFCgACACwAAAEADQAIAAACFJRhcbmiglx78SXKYK6za+NxHyYVACH5BAUKAAIALAAAAQANAAgAAAIWVCSAl+hqEGRTLhtbdvTqnlUf9nhTAQAh+QQFCgACACwAAAEADQAIAAACFZRiYCh6uaCRzNXYsKVT+5eBW3gJBQAh+QQJCgACACwAAAAADgAKAAACGpSPaWGwfZhwQtIK8VTUvuxpm9Yp4XlmpiIUADs='
  553. }
  554. };
  555.  
  556. YTMA.selector = { // to build the selector
  557. all: 'a[href*="youtube."], a[href*="youtu.be/"], a[href*="vimeo.com/"], a[href*="vine.co/"], a[href*="gfycat.com/"], a[href*=".webm"], a[href*=".mp4"], a[href*=".mp3"], a[href*=".gifv"], a[href*="soundcloud.com/"]',
  558. parentBlacklist: ['.smallfont', '.colhead_dark', '.spoiler', 'pre'],
  559. chrome37Blacklist: 'a[href*="pomf.se/"]',
  560. ignore: function () {
  561. var i, j, ignore = [], all = this.all.split(','), blacklist = this.parentBlacklist;
  562. for (i = 0; i < blacklist.length; i++) {
  563. for (j = 0; j < all.length; j++) {
  564. ignore.push(blacklist[i] + ' ' + all[j]);
  565. }
  566. }
  567. //console.log(ignore);
  568. return ignore.join(',');
  569. },
  570. links: function () {
  571. var links;
  572.  
  573. $$.x(YTMA.selector.ignore()).map(function (el) { el.setAttribute('ytmaignore', true); });
  574.  
  575. links = $$.x(YTMA.selector.all).filter(function (el) {
  576. var r = !el.hasAttribute('ytmaprocessed') && !el.hasAttribute('ytmaignore');
  577. el.setAttribute('ytmaprocessed', true);
  578. return r;
  579. });
  580.  
  581. return links;
  582. },
  583. processor: function () {
  584. var links = this.links();
  585.  
  586. if (links.length > 0) {
  587. if (window.chrome && (/(?:Chrome\/(\d+))/.exec(window.navigator.appVersion) && RegExp.lastParen < 38)) {
  588. $$.s(YTMA.selector.chrome37Blacklist, function (a) {
  589. if (/(?:\.webm)/i.test(a.href)) {
  590. a.dataset.ytmscroll = false;
  591. }
  592. });
  593. }
  594.  
  595. links.forEach(YTMA.create);
  596. }
  597.  
  598. return links.length;
  599. }
  600. };
  601.  
  602. /**
  603. * User Preferences
  604. * size: Small (240p), Medium (360p), Large (480p), XL (720p)
  605. * ratio: 1 4:3, 2 16:9
  606. * quality: 240, 360, 480, 720, 1080
  607. * focus: 0/1; Will attempt to set the window's focus near the video
  608. * autoShow: 0/1; Will automatically display HTML5 videos, which currently lack descriptions and thumbnails
  609. * desc: (Descriptions) 0 None; 1 Yes on scroll; 2 Yes all at once
  610. * yt_nocookie: 0/1; Will disable/enable youtube-nocookie.com
  611. * yt_volume: positive number; youtube volume
  612. * yt_annotation: 0/1; youtube annotations
  613. */
  614. YTMA.user = {
  615. KEY: 'ytmasetts',
  616. $form: null,
  617. init: function () {
  618. this.load();
  619.  
  620. if (strg.on) {
  621. this.fn.makeForm();
  622. this.mark();
  623. }
  624. },
  625. valid: {
  626. focus: [0, 1],
  627. desc: [0, 1, 2],
  628. ratio: [1, 2],
  629. size: [240, 360, 480, 720],
  630. quality: [240, 360, 480, 720, 1080],
  631. autoShow: [0, 1],
  632. yt_nocookie: [0, 1],
  633. yt_annotation: [0, 1], // hide | show
  634. yt_volume: 100 // todo? function () { a = []; for (i = 0; i < 100; i++) { a[i] = i; } }
  635. },
  636. mapping: { // map values to some other values used by an external API, for example
  637. yt_annotation: [3, 1] // 3 = hide | 1 = show
  638. },
  639. validate: function (property, n) {
  640. n = +n;
  641. if (property === 'yt_volume') {
  642. return n >= 0 && n <= 100 ? (+n) : YTMA.user.defaults()[property];
  643. }
  644. return YTMA.user.valid[property].indexOf(n) > -1 ? n : YTMA.user.defaults()[property];
  645. },
  646. defaults: function () {
  647. return {
  648. focus : 0,
  649. desc : 1,
  650. ratio : 2,
  651. size : 360,
  652. quality : 720,
  653. autoShow : 1,
  654. yt_nocookie : 0,
  655. yt_annotation : 1,
  656. yt_volume : 100
  657. };
  658. },
  659. load: function () {
  660. var s = strg.grab(YTMA.user.KEY, {});
  661.  
  662. YTMA.user.preferences = {
  663. size : YTMA.user.validate('size', s.size),
  664. ratio : YTMA.user.validate('ratio', s.ratio),
  665. desc : YTMA.user.validate('desc', s.desc),
  666. focus : YTMA.user.validate('focus', s.focus),
  667. quality : YTMA.user.validate('quality', s.quality),
  668. autoShow : YTMA.user.validate('autoShow', s.autoShow),
  669. yt_nocookie : YTMA.user.validate('yt_nocookie', s.yt_nocookie),
  670. yt_annotation : YTMA.user.validate('yt_annotation', s.yt_annotation),
  671. yt_volume : YTMA.user.validate('yt_volume', s.yt_volume)
  672. };
  673.  
  674. $$.o(YTMA.user.mapping, function (key, val) {
  675. if (!val.hasOwnProperty('indexOf')) {
  676. YTMA.user.preferences[key] = val[YTMA.user.valid[key].indexOf(YTMA.user.preferences[key])];
  677. }
  678. });
  679.  
  680. console.log('loaded: ', YTMA.user.preferences);
  681. },
  682. mark: function () {
  683. var a = {};
  684. a.ytma__focus = !!YTMA.user.preferences.focus;
  685. a.ytma__autoShow = !!YTMA.user.preferences.autoShow;
  686. a.ytma__yt_nocookie = !!YTMA.user.preferences.yt_nocookie;
  687. a.ytma__yt_annotation = !!YTMA.user.preferences.yt_annotation;
  688. a.ytma__yt_volume = YTMA.user.preferences.yt_volume;
  689. a['ytma__ratio' + YTMA.user.preferences.ratio] = true;
  690. a['ytma__size' + YTMA.user.preferences.size] = true;
  691. a['ytma__desc' + YTMA.user.preferences.desc] = true;
  692. a['ytma__quality' + YTMA.user.preferences.quality] = !!YTMA.user.preferences.quality;
  693.  
  694. // console.log('marking', a);
  695. $$.o(a, function (id, val) {
  696. try {
  697. var el = document.getElementById(id);
  698. el.checked = val;
  699. el.value = val;
  700. } catch (e) {
  701. // console.log(id, e);
  702. }
  703. });
  704. },
  705. events: {
  706. save: function (e) {
  707. var o = {};
  708.  
  709. if (e && /(?:INPUT|LABEL)/i.test(e.target.nodeName)) {
  710. // console.log(YTMA.user.$form.querySelectorAll('[data-key]'));
  711. // [data-key]:checked
  712. $$.ary(YTMA.user.$form.querySelectorAll('[data-key]')).forEach(function (e) {
  713. var key;
  714. key = e.dataset.key;
  715.  
  716. if (e.type === 'checkbox') {
  717. o[key] = +e.checked;
  718. } else if (e.type === 'radio') {
  719. if (e.checked) {
  720. if (e.hasAttribute('data-num')) {
  721. o[key] = +e.dataset.num;
  722. }
  723. }
  724. } else {
  725. o[key] = +e.value;
  726. }
  727. });
  728.  
  729. if (strg.save(YTMA.user.KEY, o)) {
  730. YTMA.user.load();
  731. } else {
  732. YTMA.user.error.classList.remove('ytm_none');
  733. }
  734. }
  735.  
  736. },
  737. reset: function () {
  738. YTMA.user.preferences = YTMA.user.defaults();
  739. YTMA.user.mark();
  740. strg.wipe(YTMA.user.KEY);
  741. YTMA.user.error.classList.add('ytm_none');
  742. },
  743. clear: function () {
  744. try {
  745. localStorage.removeItem(YTMA.external.version);
  746. YTMA.user.events.reset();
  747. console.log('removed all YTMA cache');
  748. } catch (e) {
  749. console.error(e);
  750. }
  751. },
  752. formToggle: function (e) {
  753. if (!e || (e && e.target && !/(?:INPUT|LABEL)/i.test(e.target.nodeName))) {
  754. YTMA.user.$form.classList.toggle('ytm_none');
  755. }
  756. },
  757. formToggleKeyboard: function (e) {
  758. // press CTRL+SHIFT+Y (META+SHIFT+Y) to display settings form
  759. if ((e.ctrlKey || e.metaKey) && e.shiftKey && String.fromCharCode(e.which).toLowerCase() === 'y') {
  760. e.preventDefault();
  761. YTMA.user.events.formToggle();
  762. }
  763. }
  764. },
  765. fn: {
  766. $scroller: null,
  767. $once: false,
  768. loadPreferences: function () {
  769. if (YTMA.user.preferences.desc === 1) {
  770. YTMA.user.fn.onScrollViewDescriptions();
  771. }
  772.  
  773. this.loadPreferencesOnce();
  774. },
  775. loadPreferencesOnce: function () {
  776. if (this.$once) { return; }
  777.  
  778. this.$once = true;
  779.  
  780. if (YTMA.user.preferences.autoShow === 1) {
  781. YTMA.user.fn.onScrollViewMedia();
  782. }
  783. },
  784. showMedia: function () {
  785. console.log('showMedia');
  786. return new YTMA.Scroll('a.ytm_scroll:not([data-ytmscroll="false"])', function (link) {
  787. if (YTMA.Scroll.visibleAll(link, 50)) {
  788. $$.s('var[data-ytmsid="' + link.dataset.ytmsid + '"]:not([data-ytmscroll="false"])', function (trigger) {
  789. var ui = YTMA.UI.createFromTrigger(trigger);
  790. ui.showOnScroll(link);
  791. });
  792. }
  793. });
  794. },
  795. toggleMedia: function () {
  796. return new YTMA.Scroll('div.ytm_panel_switcher', function (div) {
  797. var v = div.querySelector('video'),
  798. paused = v && (v.paused || v.ended),
  799. ui = YTMA.set[div.dataset.ytmuid].getUI();
  800.  
  801. if (paused && !YTMA.Scroll.visibleAll(div, 0)) {
  802. return ui.play.switchStandby();
  803. }
  804.  
  805. if (ui.play.isStandby() && YTMA.Scroll.visibleAll(div, 200)) {
  806. return ui.play.switchOn();
  807. }
  808.  
  809. // todo ascertain embedded player properties
  810. // f = div.querySelector('iframe, object');
  811. // if (f && !YTMA.Scroll.visibleAll(div, 200)) {
  812. // y.hidePlayer();
  813. // }
  814. });
  815. },
  816. onScrollViewMedia: function () {
  817. this.showMedia();
  818. this.toggleMedia();
  819. },
  820. onScrollViewDescriptions: function () {
  821. if (YTMA.user.fn.$scroller) { YTMA.user.fn.$scroller.stop(); }
  822.  
  823. YTMA.user.fn.$scroller = new YTMA.Scroll('span.ytm_manual > a.ytm_title:not(.ytm_error)', function (a) {
  824. if (YTMA.Scroll.visibleAll(a, 200)) {
  825. YTMA.ajax.loadFromDataset(a.dataset);
  826. // console.log('doc', document.querySelectorAll(YTMA.user.fn.$scroller.selector).length, a.dataset.id);
  827. }
  828.  
  829. if (document.querySelectorAll(YTMA.user.fn.$scroller.selector).length === 0) {
  830. YTMA.user.fn.$scroller.stop();
  831. }
  832. });
  833. },
  834. makeForm: function () {
  835. var e, f = [
  836. '<div id="ytm_settings" class="ytm_sans ytm_block ytm_normalize"><form action="" title="Double click to close"><div id="ytm_settingst">ytma! Site Settings</div><div class="ytm_field_container">',
  837. '<fieldset><legend title="Load descriptions from the content sever.">Load Descriptions</legend><p><span><input id="ytma__desc0" type="radio" data-num="0" name="ytma__desc" data-key="desc"><label for="ytma__desc0" title="Load descriptions on demand">Manually</label></span><span><input id="ytma__desc1" type="radio" data-num="1" name="ytma__desc" data-key="desc"><label for="ytma__desc1" title="Load descriptions as they become visible on the screen.">Automatically, on scrolling</label></span></p></fieldset>',
  838. '<fieldset><legend>HTML5 Players</legend><p><input name="ytma__autoShow" data-key="autoShow" id="ytma__autoShow" type="checkbox"><label for="ytma__autoShow">Automatically show WebM, MP4 and Soundcloud players</label></p></fieldset>',
  839. '<fieldset><legend>Player Size</legend><p><span><input type="radio" name="ytma__size" data-key="size" data-num="240" id="ytma__size240" /><label for="ytma__size240">S <small>240p</small></label></span><span><input name="ytma__size" data-key="size" type="radio" id="ytma__size360" data-num="360" /><label for="ytma__size360">M <small>360p</small></label></span><span><input type="radio" name="ytma__size" data-key="size" data-num="480" id="ytma__size480" /><label for="ytma__size480">L <small>480p</small></label></span><span><input type="radio" name="ytma__size" data-key="size" data-num="720" id="ytma__size720" /><label for="ytma__size720">X <small>720p</small></label></span></p></fieldset>',
  840. '<fieldset><legend>Quality</legend><p><span><input name="ytma__quality" data-key="quality" data-num="240" id="ytma__quality240" type="radio"><label for="ytma__quality240">240p</label></span><span><input name="ytma__quality" data-key="quality" id="ytma__quality360" data-num="360" type="radio"><label for="ytma__quality360">360p</label></span><span><input name="ytma__quality" data-key="quality" data-num="480" id="ytma__quality480" type="radio"><label for="ytma__quality480">480p</label></span><span><input name="ytma__quality" data-key="quality" data-num="720" id="ytma__quality720" type="radio"><label for="ytma__quality720">720p</label></span><span><input name="ytma__quality" data-key="quality" data-num="1080" id="ytma__quality1080" type="radio"><label for="ytma__quality1080">1080p</label></span></p></fieldset>',
  841. '<fieldset><legend>Aspect Ratio</legend><p><span><input name="ytma__ratio" data-key="ratio" type="radio" id="ytma__ratio2" data-num="2" /><label for="ytma__ratio2">16:9</label></span><span><input type="radio" name="ytma__ratio" data-key="ratio" data-num="1" id="ytma__ratio1" /><label for="ytma__ratio1">4:3</label></span></p></fieldset>',
  842. '<fieldset><legend>YouTube</legend>',
  843. '<p><input name="ytma__yt_annotation" data-key="yt_annotation" type="checkbox" id="ytma__yt_annotation" /><label for="ytma__yt_annotation">Enable video annotations</label></p>',
  844. '<p><input name="ytma__yt_nocookie" data-key="yt_nocookie" type="checkbox" id="ytma__yt_nocookie" /><label for="ytma__yt_nocookie">Use https://youtube-nocookie.com to load videos</label></p>',
  845. '<p><input name="ytma__yt_volume" data-key="yt_volume" type="number" style="width:4em" min=0 max=100 id="ytma__yt_volume" /><label for="ytma__yt_volume">Video volume (Experimental)</label></p>',
  846. '</fieldset>',
  847. '<fieldset><legend>Window Focus</legend><p><input name="ytma__focus" data-key="focus" type="checkbox" id="ytma__focus" value="focus" /><label for="ytma__focus">After clicking the thumbnail, set the video at the top of the window.</label></p></fieldset>',
  848. '</div><p><small id="ytm_settings_error" class="ytm_error ytm_none ytm_title">Error! Your settings could not be saved.</small></p><p id="ytm_opts"><button type="button" id="ytmaclose">Close</button> <button type="button" id="ytmareset">Reset</button> <button type="button" id="ytmaclear" title="Remove all video descriptions that have been cached">Reset & Remove Cache</button></p></form></div>'
  849. ].join('');
  850.  
  851. YTMA.user.$form = $$.e('div', {className: 'ytm_fix_center ytm_none ytm_box', innerHTML: f}, document.body);
  852. YTMA.user.error = document.getElementById('ytm_settings_error');
  853.  
  854. e = YTMA.Scroll.debounce(YTMA.user.events.save, 500);
  855. YTMA.user.$form.addEventListener('submit', function (evt) { evt.preventDefault(); }, false);
  856. YTMA.user.$form.addEventListener('keyup', e, false);
  857. YTMA.user.$form.addEventListener('click', e, false);
  858.  
  859. YTMA.user.$form.addEventListener('dblclick', YTMA.user.events.formToggle, false);
  860. document.getElementById('ytmaclose').addEventListener('click', YTMA.user.events.formToggle, false);
  861. document.getElementById('ytmareset').addEventListener('click', YTMA.user.events.reset, false);
  862. document.getElementById('ytmaclear').addEventListener('click', YTMA.user.events.clear, false);
  863. document.body.addEventListener('keydown', YTMA.user.events.formToggleKeyboard, false);
  864. }
  865. }
  866. };
  867.  
  868. YTMA.css = function () {
  869. var playerCss = YTMA.Player.css.generator();
  870. // console.log(playerCss);
  871. update.css(playerCss);
  872.  
  873. // images
  874. update.css([
  875. '.ytm_loading{background:url(', YTMA.img.css.load, ') 0 3px no-repeat;}',
  876. '.ytm_link{background:url(', YTMA.img.fav.youtube, ') 0 center no-repeat !important;margin-left:4px;padding-left:20px!important;}',
  877. '.ytm_link.ytm_link_vimeo{background-image:url(', YTMA.img.fav.vimeo, ') !important;background-size:12px 12px !important;padding-left:18px!important}',
  878. '.ytm_link.ytm_link_vine{background-image:url(', YTMA.img.fav.vine, ') !important;background-size:10px 10px!important;padding-left:16px!important}',
  879. '.ytm_link.ytm_link_soundcloud{background-image:url(', YTMA.img.fav.soundcloud, ')!important;padding-left:17px!important}',
  880. '.ytm_link.ytm_link_html5{background-image:url(', YTMA.img.fav.html5, ') !important;padding-left:16px!important}',
  881. '.ytm_link.ytm_link_gfycat{background-image:url(', YTMA.img.fav.gyfcat, ') !important;background-size:12px 12px !important;padding-left:16px!important;}',
  882. '.ytm_link.ytm_link_imgur{background-image:url(', YTMA.img.fav.imgur, ') !important;background-size:12px 12px !important;padding-left:16px!important}'
  883. ].join(''));
  884.  
  885. // todo
  886. // if (window.NO_YTMA_CSS) { return; }
  887.  
  888. update.css('.ytm_none,.ytm_link br{display:none!important}.ytm_box{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.ytm_block{display:block;position:relative;clear:both;text-align:left;border:0;margin:0;padding:0;overflow:hidden}.ytm_normalize{font-weight:400!important;font-style:normal!important;line-height:1.2!important}.ytm_sans{font-family:Arial,Helvetica,sans-serif!important}.ytm_spacer{overflow:auto;margin:0 0 6px;padding:4px}.ytm_spacer.ytm_site_slim{display:inline}.ytm_clear:after{content:"";display:table;clear:both}.ytm_center{text-align:center}.ytm_link b,.ytm_link strong{font-weight:400!important}.ytm_link u{text-decoration:none!important}.ytm_link i,.ytm_link em{font-style:normal!important}.ytm_trigger{width:118px;height:66px;background-color:#262626!important;cursor:pointer;background-position:-1px -12px;float:left;box-shadow:2px 2px rgba(0,0,0,.3);background-size:auto 90px!important;color:#fff;text-shadow:#333 0 0 2px;font-size:13px}.ytm_trigger:hover{box-shadow:2px 2px #9eae9e;opacity:.95}.ytm_trigger var{z-index:2;height:100%;width:100%;position:absolute;left:0;top:0;text-align:right}.ytm_label{display:block;padding:3px 6px;line-height:1.2;font-style:normal}.ytm_init{height:22px;background:rgba(11,11,11,.62);padding:4px 25px 6px 6px}.ytm_site_vine .ytm_trigger{background-color:#90ee90!important;background-size:120px auto!important}.ytm_site_slim .ytm_trigger{background:#e34c26!important;height:auto;box-shadow:0 0 2px #ffdb9d inset,2px 2px rgba(0,0,0,.3);margin:0 3px 0 0;width:auto;transition:all .3s ease-in-out 0s}.ytm_site_slim .ytm_trigger:hover{opacity:.8}.ytm_site_slim .ytm_label{text-shadow:0 0 1px #f06529}.ytm_site_slim .ytm_init{background:transparent}.ytm_bd{float:left;max-width:500px;margin:2px 10px;font-size:90%}.ytm_title{font-weight:700}.ytm_error{color:#cc2f24;font-style:italic}.ytm_loading{font-style:italic;padding:1px 1.5em}.ytm_descr{word-wrap:break-word;max-height:48px;overflow:auto;padding-right:20px}.ytm_descr[data-full]{cursor:pointer}.ytm_descr_open{resize:both;white-space:pre-line}.ytm_descr_open[style]{max-height:none}.ytm_projector{margin-bottom:4px}ul.ytm_control{overflow:hidden;margin:0!important;padding:3px 0 1px;list-style-position:outside!important}.ytm_control li{display:inline;margin:0!important;padding:0!important}.ytm_control li>ul{display:inline-block;margin:0;padding:0 1px 0 0}.ytm_control li ul li{-webkit-user-select:none;-moz-user-select:none;-o-user-select:none;user-select:none;list-style-type:none;cursor:pointer;float:left;color:#858585;border:1px solid #1d1d1d;border-bottom:1px solid #000;border-top:1px solid #292929;box-shadow:0 0 1px #555;height:14px;font-size:12px!important;line-height:12px!important;background:#222;background:linear-gradient(#2d2c2c,#222);margin:0!important;padding:5px 9px 3px!important}.ytm_control li ul li:first-child{border-radius:2px 0 0 2px}.ytm_control li ul li:last-child{border-left:0!important;border-radius:0 2px 2px 0;margin:0 2px 0 0!important}.ytm_control li ul li:first-child:last-child,.ytm_li_setting{border-radius:2px}.ytm_control li ul li:hover{color:#ccc;text-shadow:1px 1px 0 #333;background:#181818}.ytm_control li ul li[id]{color:#ddd;text-shadow:0 0 2px #444}.ytm_panel_size{background:#000}.ytm_panel_switcher[data-standby="true"]{background:#111}.ytm_panel_switcher[data-standby="true"]:after{cursor:cell;color:#0e0e0e;content:"ytma!";display:block;font-size:85px;font-style:italic;font-weight:700;left:50%;position:absolute;text-shadow:2px 1px #181818,-1px -1px #0a0a0a;top:50%;transform:translate(-50%,-50%)}.ytm_site_soundcloud .ytm_panel_size.ytm_soundcloud-playlist{height:334px!important}.ytm_fix_center{background:rgba(51,51,51,.41);height:100%;left:0;position:fixed;top:0;width:100%;z-index:99998}#ytm_settings{z-index:99999;max-width:500px;max-height:85%;overflow:auto;background:#fbfbfb;border:1px solid #bbb;color:#444;box-shadow:0 0 5px rgba(0,0,0,.2),0 0 3px rgba(239,239,239,.1) inset;margin:4% auto;padding:4px 8px 0}#ytm_settings p{margin:5px 0;padding:0}#ytm_settings fieldset{vertical-align:top;border-radius:3px;border:1px solid #ccc;margin:0 0 5px}#ytm_settings fieldset span{display:inline-block;min-width:5em}#ytm_settings input{vertical-align:baseline!important;margin:3px 5px!important}#ytm_settingst{font-size:110%;border-bottom:1px solid #d00;margin:3px 0 9px;padding:0 3px 3px}#ytm_settings label{cursor:pointer}#ytm_settings small{font-size:90%}#ytm_opts button{cursor:pointer;margin:10px 5px 8px 2px;padding:3px;border:1px solid #adadad;border-radius:2px;background:#eee;font-size:90%}#ytm_opts button:hover{background:#ddd}');
  889. };
  890.  
  891. YTMA.ajax = {
  892. load: function (site, id, uri) {
  893. var cache = YTMA.external.dataFromStorage(site, id);
  894. console.log('YTMA.ajax.load:', site, id, uri);
  895. console.log('@cache:', cache);
  896.  
  897. if (cache) { return YTMA.external.populate(cache); }
  898.  
  899. if (YTMA.DB.ajaxExtension[site]) { return this.gmxhr(uri, site, id); }
  900.  
  901. console.log('ajax.site?', YTMA.DB.ajax[site].replace('%key', id).replace('%uri', uri));
  902. if (YTMA.DB.ajax[site]) {
  903. console.log('preping uri');
  904. uri = YTMA.DB.ajax[site].replace('%key', id).replace('%uri', uri);
  905. return this.xhr(uri, site, id);
  906. }
  907.  
  908. return null;
  909. },
  910. loadFromDataset: function (dataset) {
  911. return this.load(dataset.ytmsite, dataset.ytmid, dataset.ytmuri);
  912. },
  913. gmxhr: function (uri, site, id) {
  914. try {
  915. // alert('gmxhr starting!');
  916. // console.log('gmxhr starting!');
  917. GM_xmlhttpRequest({
  918. method: 'GET',
  919. url: uri,
  920. onload: function (response) {
  921. console.log(response);
  922. YTMA.external.parse(response.responseText, site, id);
  923. },
  924. onerror: function () {
  925. console.log('GM Cannot XHR');
  926. YTMA.ajax.failure.call({id: id});
  927. }
  928. });
  929. YTMA.ajax.preProcess(id);
  930.  
  931. } catch (e) {
  932. if (YTMA.DB.extension) {
  933. console.log('attempting cs xhr');
  934. this.xhr(uri, site, id);
  935. } else {
  936. console.log('No applicable CORS request available.');
  937. this.failure.call({id: id});
  938. }
  939. }
  940. },
  941. xhr: function (uri, site, id) {
  942. var x = new XMLHttpRequest();
  943. console.log('xhr', uri, id, site);
  944.  
  945. YTMA.ajax.preProcess(id);
  946.  
  947. x.onreadystatechange = function () {
  948. if (this.readyState === this.DONE) {
  949. // console.log(this.readyState, this.status);
  950. if (this.status === 200) {
  951. YTMA.external.parse(this.responseText, site, id);
  952. } else if (this.status === 403) {
  953. YTMA.external.populate({site: site, id: id, title: 'Error 403', desc: ''});
  954. YTMA.external.save({site: site, id: id, title: 'Error 403', desc: ''});
  955. } else { // if (this.status >= 400 || this.status === 0) {
  956. YTMA.ajax.failure.call({id: id});
  957. }
  958. }
  959. };
  960.  
  961. try {
  962. console.log('sending');
  963. x.open('get', uri, true);
  964. x.send();
  965. } catch (e) {
  966. console.error('Cannot send xhr', uri);
  967. YTMA.ajax.failure.call({id: id});
  968. console.error(e);
  969. }
  970. },
  971. failure: function () {
  972. $$.s('.ytm_bd._' + YTMA.escapeId(this.id), function (el) {
  973. var a = el.querySelector('a');
  974. a.dataset.tries = a.dataset.tries ? parseFloat(a.dataset.tries) + 1 : 1;
  975. a.textContent = 'Error, unable to load data. [Retry ' + (a.dataset.tries > 0 ? a.dataset.tries : '') + ']';
  976. a.className = 'ytm_error ytm_title';
  977. });
  978. },
  979. preProcess: function (id) {
  980. $$.s('.ytm_manual._' + YTMA.escapeId(id) + ' a', function (el) {
  981. el.classList.add('ytm_loading');
  982. el.textContent = 'Loading data . . .';
  983. el.title = 'Retry loading data.';
  984. });
  985. }
  986. };
  987.  
  988. /** E X T E R N A L Apparatus
  989. * Data from external sites
  990. */
  991. YTMA.external = {
  992. version: 'ytma.4.1.dat',
  993. parse: function (response, site, id) {
  994. if (this.parsers[site]) {
  995. response = YTMA.DB.ajax[site] ? JSON.parse(response) : response;
  996. this.populate(this.helper.cutDescription(this.parsers[site](response, id)));
  997. }
  998. },
  999. parsers: {
  1000. soundcloud: function (j, id) {
  1001. return {
  1002. site: 'soundcloud',
  1003. id: id, //unescape(j.html).match(/tracks\/(\d+)/)[1],
  1004. title: j.title,
  1005. desc: j.description,
  1006. th: YTMA.reg.fix.$removeSearch(j.thumbnail_url)
  1007. };
  1008. },
  1009. vimeo: function (j) {
  1010. j = j[0];
  1011. return {
  1012. site: 'vimeo',
  1013. id: j.id,
  1014. title: j.title + ' ' + YTMA.external.helper.time(j.duration),
  1015. desc: j.description.replace(/<br.?.?>/g, ''),
  1016. th: decodeURI(j.thumbnail_medium)
  1017. };
  1018. },
  1019. youtube: function (j, id) {
  1020. if (j.pageInfo.totalResults < 1) {
  1021. return { id: id, error: true };
  1022. }
  1023.  
  1024. j = j.items[0];
  1025. var o = {
  1026. site: 'youtube',
  1027. id: id,
  1028. title: j.snippet.title + ' ' + YTMA.external.helper.time(j.contentDetails.duration),
  1029. desc: j.snippet.description
  1030. // aspectRatio: j.contentDetails.aspectRatio
  1031. };
  1032.  
  1033. return o;
  1034. },
  1035. vine: function (html, id) {
  1036. var o, doc = document.implementation.createHTMLDocument('');
  1037. doc.documentElement.innerHTML = html;
  1038.  
  1039. o = {
  1040. site: 'vine',
  1041. id: id, //doc.querySelector('meta[property="twitter:app:url:googleplay"]').content.split('/').slice(-1),
  1042. title: doc.querySelector('meta[property="twitter:title"]').content,
  1043. desc: doc.querySelector('meta[property="twitter:description"]').content,
  1044. th: doc.querySelector('meta[property="twitter:image"]').content.split('?')[0]
  1045. };
  1046.  
  1047. doc = null;
  1048.  
  1049. return o;
  1050. },
  1051. gfycat: function (j, id) {
  1052. j = j.gfyItem;
  1053. if (j) {
  1054. return {
  1055. site: 'gfycat',
  1056. id: id || j.gfyName,
  1057. title: j.title || j.gfyName
  1058. };
  1059. }
  1060. }
  1061. },
  1062. set: function (data) {
  1063. if (!this.db[data.site]) {
  1064. this.db[data.site] = {};
  1065. }
  1066. this.db[data.site][data.id] = data;
  1067. return this.save();
  1068. },
  1069. unset: function (data) {
  1070. // console.log('unset', data.id);
  1071. if (data.site) {
  1072. delete this.db[data.site][data.id];
  1073. return this.save();
  1074. }
  1075. },
  1076. limitDB: function (max, db) {
  1077. // limits an object's items by half of the max
  1078. // removes the older items at the start of the object
  1079. var keys = Object.keys(db),
  1080. half = Math.floor(max / 2),
  1081. start,
  1082. ndb,
  1083. i;
  1084.  
  1085. if (keys.length > max) {
  1086. ndb = {};
  1087. start = keys.length - half;
  1088.  
  1089. for (i = start; i < keys.length; i++) {
  1090. ndb[keys[i]] = db[keys[i]];
  1091. }
  1092. }
  1093.  
  1094. return ndb || db;
  1095. },
  1096. save: function () {
  1097. this.db = this.limitDB(1000, this.db);
  1098. return strg.save(this.version, this.db);
  1099. },
  1100. helper: {
  1101. cutDescription: function (data) {
  1102. if (data.desc && data.desc.length > 140) {
  1103. data.full = data.desc;
  1104. data.desc = data.desc.substr(0, 130) + ' . . .';
  1105. }
  1106. return data;
  1107. },
  1108. time: function (iso8601) {
  1109. var p = '', a, i;
  1110. // P#DT#H#M#S || PT#H#M#S
  1111. try {
  1112. a = iso8601.replace(/(?:PT?)/, '').split(/(?:D|H|M|S)/g).slice(0, -1);
  1113.  
  1114. for (i = (a.length - 1); i > 0; i--) {
  1115. if ((a[i - 1] >= 0) && a[i] > 0) {
  1116. a[i] = ('00' + a[i]).slice(-2);
  1117. }
  1118. }
  1119.  
  1120. p = '(' + a.join(':') + ')';
  1121. } catch (e) {
  1122. }
  1123. return p;
  1124. },
  1125. thumbnail: function (data) {
  1126. $$.s('[data-ytmid="%id"].ytm_trigger'.replace('%id', data.id), function (el) {
  1127. el.setAttribute('style', 'background: url(' + data.th + ')');
  1128. });
  1129. },
  1130. titleToggle: function () {
  1131. this.classList.toggle('ytm_descr_open');
  1132. this.textContent = this.textContent.length < 140 ? this.dataset.full : this.dataset.full.substr(0, 130) + ' . . .';
  1133. this.removeAttribute('style');
  1134. }
  1135. },
  1136. validate: function (data) {
  1137. if (!data || !data.id || data.error) {
  1138. return YTMA.ajax.failure.call(data);
  1139. }
  1140.  
  1141. if (data.id && !data.title && !data.desc) {
  1142. this.unset(data.id);
  1143. return YTMA.ajax.failure.call(data);
  1144. }
  1145.  
  1146. return true;
  1147. },
  1148. populate: function (data, ignoreValidation) {
  1149. if (!ignoreValidation && !this.validate(data)) { return; }
  1150.  
  1151. this.set(data);
  1152.  
  1153. if (data.th) { this.helper.thumbnail(data); }
  1154.  
  1155. $$.s('.ytm_bd._' + YTMA.escapeId(data.id), function (el) {
  1156. var q;
  1157. el.innerHTML = '<span class="ytm_title">' + data.title + '</span>';
  1158. if (data.desc) {
  1159. q = $$.e('q', { className: 'ytm_descr ytm_block', textContent: data.desc }, el);
  1160. if (data.full) {
  1161. q.dataset.full = data.full;
  1162. q.title = 'Click to toggle the length of the description.';
  1163. q.addEventListener('dblclick', YTMA.external.helper.titleToggle, false);
  1164. }
  1165. }
  1166. });
  1167. },
  1168. dataFromStorage: function (site, id) {
  1169. if (this.db && this.db[site]) {
  1170. return this.db[site][id];
  1171. }
  1172. },
  1173. events: {
  1174. manualLoad: function (e) {
  1175. // console.log(this);
  1176. e.preventDefault();
  1177. YTMA.ajax.loadFromDataset(e.target.dataset);
  1178. }
  1179. }
  1180. };
  1181. YTMA.external.db = strg.grab(YTMA.external.version, {});
  1182.  
  1183. /** Database */
  1184. YTMA.DB = {
  1185. postInit: function () {
  1186. if (YTMA.user.preferences.yt_nocookie) {
  1187. YTMA.DB.sites.youtube = 'https://www.youtube-nocookie.com/';
  1188. } else {
  1189. YTMA.DB.sites.youtube = 'https://www.youtube.com/';
  1190. }
  1191. },
  1192. fn: {
  1193. hasAjax: function (site) {
  1194. return YTMA.DB.ajax[site] || YTMA.DB.ajaxExtension[site];
  1195. }
  1196. },
  1197. extension: window.chrome && window.chrome.extension,
  1198. browser: {
  1199. pod: YTMA.reg.ios.test(navigator.userAgent),
  1200. ie: !!document.documentMode, // IE, basically | window.navigator.cpuClass
  1201. safari: Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0
  1202. },
  1203. sites: {
  1204. youtube: 'https://www.youtube.com/',
  1205. vimeo: 'http://vimeo.com/',
  1206. vine: 'https://vine.co/',
  1207. gfycat: 'https://gfycat.com/',
  1208. html5: true,
  1209. 'html5-audio': true,
  1210. soundcloud: 'https://soundcloud.com/',
  1211. imgur: 'https://i.imgur.com/'
  1212. },
  1213. customControls: {
  1214. $get: function (site) {
  1215. var customBar = this[site] || {};
  1216. return {
  1217. ratio: customBar.ratio === undefined ? true : customBar.ratio,
  1218. size: customBar.size === undefined ? true : customBar.size
  1219. };
  1220. },
  1221. vine: {
  1222. ratio: false,
  1223. size: true
  1224. },
  1225. soundcloud: {
  1226. ratio: false,
  1227. size: false
  1228. }
  1229. },
  1230. slim: {
  1231. html5: true
  1232. },
  1233. scroll: {
  1234. html5: true,
  1235. soundcloud: true,
  1236. gfycat: true,
  1237. imgur: true
  1238. },
  1239. playerSize: {
  1240. ratios: {
  1241. 1: 'sd',
  1242. 2: 'hd',
  1243. 3: 'pr'
  1244. },
  1245. sizes: {
  1246. 0 : 'h',
  1247. 240 : 's',
  1248. 360 : 'm',
  1249. 480 : 'l',
  1250. 720 : 'xl'
  1251. },
  1252. aspects: {
  1253. 1: 4 / 3,
  1254. 2: 16 / 9,
  1255. 3: 16 / 9
  1256. },
  1257. $get: function (ratio, size) {
  1258. return 'ytm_panel ytm_block ytm_panel-' + this.ratios[ratio] + ' ytm_panel-' + this.sizes[size];
  1259. }
  1260. },
  1261. qualities: {
  1262. 240 : 'small',
  1263. 360 : 'medium',
  1264. 480 : 'large',
  1265. 720 : 'hd720',
  1266. 1080 : 'hd1080',
  1267. 1081 : 'highres',
  1268. $get: function (quality) {
  1269. return this[quality] || this[360];
  1270. }
  1271. },
  1272. ajax: {
  1273. youtube: 'https://www.googleapis.com/youtube/v3/videos?part=snippet,contentDetails&id=%key' + window.atob('JmtleT1BSXphU3lEVG5INkxzRERyVElYaFZTZWRQQjlyRHo1czBSczQzZnM='),
  1274. vimeo: 'https://vimeo.com/api/v2/video/%key.json',
  1275. soundcloud: 'https://soundcloud.com/oembed?format=json&url=%uri',
  1276. gfycat: 'http://gfycat.com/cajax/get/%key'
  1277. },
  1278. ajaxExtension: { // cross-site request with extension/GM support
  1279. vine: 'https://vine.co/v/%key'
  1280. },
  1281. videoTag: { // sites / types that use video tag
  1282. html5: true,
  1283. imgur: true,
  1284. gfycat: true
  1285. },
  1286. // videoTypes: (function () {
  1287. // var v = document.createElement('video');
  1288.  
  1289. // return {
  1290. // ogg: !!v.canPlayType('video/ogg; codecs="theora, vorbis"'),
  1291. // webm: !!v.canPlayType('video/webm'),
  1292. // mp4: !!v.canPlayType('video/mp4')
  1293. // };
  1294. // }()),
  1295. sources: {
  1296. 'html5-audio': function (data) {
  1297. return [
  1298. {type: 'audio/mp3', src: data.uri}
  1299. ];
  1300. },
  1301. html5: function (data) {
  1302. // attaching the type as either mp4 or webm
  1303. return [
  1304. {type: 'video/mp4', src: data.uri},
  1305. {type: 'video/webm', src: data.uri}
  1306. ];
  1307. },
  1308. imgur: function (data) {
  1309. return [
  1310. {type: 'video/webm', src: 'https://i.imgur.com/' + data.id + '.webm'},
  1311. {type: 'video/mp4', src: 'https://i.imgur.com/' + data.id + '.mp4'}
  1312. ];
  1313. },
  1314. $gfycatFrame: function (data) {
  1315. return [{type: 'text/html', src: 'https://gfycat.com/iframe/' + data.id }, false];
  1316. },
  1317. gfycat: function (data) {
  1318. return [
  1319. {type: 'video/mp4', src: 'http://zippy.gfycat.com/' + data.id + '.mp4'},
  1320. {type: 'video/mp4', src: 'http://fat.gfycat.com/' + data.id + '.mp4'},
  1321. {type: 'video/mp4', src: 'http://giant.gfycat.com/' + data.id + '.mp4'},
  1322. {type: 'video/webm', src: 'http://zippy.gfycat.com/' + data.id + '.webm'},
  1323. {type: 'video/webm', src: 'http://fat.gfycat.com/' + data.id + '.webm'},
  1324. {type: 'video/webm', src: 'http://giant.gfycat.com/' + data.id + '.webm'}
  1325. ];
  1326. },
  1327. youtube: function (data, attrs) {
  1328. var params = '?html5=1&version=3&modestbranding=1&theme=dark&color=white&showinfo=1&vq=' + attrs.quality
  1329. + '&iv_load_policy=' + YTMA.user.preferences.yt_annotation
  1330. + '&start=' + attrs.start
  1331. + '&volume=' + YTMA.user.preferences.yt_volume;
  1332. return [
  1333. {type: 'text/html', src: YTMA.DB.sites[data.site] + 'embed/' + data.id + params}
  1334. ];
  1335. },
  1336. vimeo: function (data) {
  1337. return [{type: 'text/html', src: 'http://player.vimeo.com/video/' + data.id + '?badge=0'}, null];
  1338. },
  1339. vine: function (data) {
  1340. return [{type: 'text/html', src: 'https://vine.co/v/' + data.id + '/embed/simple?audio=1'}, null]; // card
  1341. },
  1342. soundcloud: function (data) {
  1343. return [
  1344. {type: 'text/html', src: 'https://w.soundcloud.com/player/?show_comments=false&url=' + data.uri},
  1345. null
  1346. ];
  1347. }
  1348. }
  1349. };
  1350.  
  1351. /** U I CLASS
  1352. * Class for the player controls
  1353. */
  1354. YTMA.UI = function (ytma) {
  1355. this.ytmx = ytma;
  1356.  
  1357. this.play = new YTMA.Player(this.ytmx);
  1358. this.open = false;
  1359. this.selected = { size: null, ratio: null };
  1360.  
  1361. this.trigger = ytma.spn;
  1362. this.projector = $$.e('div', {className: 'ytm_projector ytm_none ytm_block ytm_normalize ytm_sans'});
  1363. this.control = $$.e('ul', {className: 'ytm_control ytm_sans'});
  1364. this.customBar = YTMA.DB.customControls.$get(this.ytmx.data.site);
  1365. this.controlBar();
  1366. };
  1367.  
  1368. YTMA.UI.ratios = {
  1369. SD: 1,
  1370. HD: 2,
  1371. PORTRAIT: 3
  1372. };
  1373.  
  1374. YTMA.UI.sizes = {
  1375. HIDDEN: 0,
  1376. S: 240,
  1377. M: 360,
  1378. L: 480,
  1379. X: 720
  1380. };
  1381.  
  1382. /** Trigger is the VAR element */
  1383. YTMA.UI.createFromTrigger = function (t) {
  1384. console.log('createFromTrigger');
  1385. if (t.hasAttribute('data-ytmuid') && !YTMA.set[t.dataset.ytmuid]) {
  1386. console.log('createFromTrigger-new');
  1387. YTMA.set[t.dataset.ytmuid] = new YTMA()._reactivate(t);
  1388. }
  1389. console.log('createFromTrigger-ui');
  1390. return YTMA.set[t.dataset.ytmuid].getUI();
  1391. };
  1392.  
  1393. YTMA.UI.events = {
  1394. $fire: {
  1395. settings: function () {
  1396. YTMA.user.events.formToggle();
  1397. },
  1398. close: function () {
  1399. if (YTMA.DB.scroll[this.ytmx.data.site]) {
  1400. console.log('events.close-1');
  1401. this.hideAllPlayers();
  1402. } else {
  1403. console.log('events.close-2');
  1404. this.ytmx.disableOpenOnScroll();
  1405. this.hidePlayer();
  1406. }
  1407. },
  1408. ratio: function (li) {
  1409. var n = parseInt(li.dataset.value, 10);
  1410. this.play.dimmensions(n);
  1411. this.markSelected(li, 'ratio');
  1412. },
  1413. size: function (li) {
  1414. var n = parseInt(li.dataset.value, 10);
  1415. this.play.dimmensions(null, n);
  1416. this.markSelected(li, 'size');
  1417. }
  1418. },
  1419. videoBar: function (e) {
  1420. var el = e.target, t;
  1421.  
  1422. if (el.tagName.toLowerCase() === 'li' && el.dataset && el.dataset.type) {
  1423. t = el.dataset.type;
  1424. if (YTMA.UI.events.$fire[t]) {
  1425. YTMA.UI.events.$fire[t].call(this, el);
  1426. }
  1427. }
  1428. }
  1429. };
  1430.  
  1431. YTMA.UI.prototype = {
  1432. constructor: YTMA.UI,
  1433. resetViewSize: function () {
  1434. this.play.dimmensions();
  1435. this.setControlBarSize(this.play.attrs.size);
  1436. },
  1437. showOnScroll: function (el) {
  1438. if (!this.open && this.ytmx.canScroll() && this.ytmx.isBelow(el)) {
  1439. this.showPlayer();
  1440. }
  1441. },
  1442. showPlayer: function () {
  1443. this.open = true;
  1444.  
  1445. this.trigger.classList.add('ytm_none');
  1446. this.projector.classList.remove('ytm_none');
  1447.  
  1448. this.attachPlayPanel();
  1449. this.play.switchOn();
  1450.  
  1451. if (YTMA.user.preferences.focus) {
  1452. document.location.hash = '#' + this.ytmx.container.id;
  1453. }
  1454. },
  1455. hidePlayer: function () {
  1456. this.open = false;
  1457.  
  1458. this.play.switchOff();
  1459. this.trigger.classList.remove('ytm_none');
  1460. this.projector.classList.add('ytm_none');
  1461. },
  1462. attachPlayPanel: function () {
  1463. if (!this.play.panel.parentNode) {
  1464. // console.log('attaching display panel');
  1465. this.projector.appendChild(this.play.panel);
  1466. }
  1467. },
  1468. hideAllPlayers: function () {
  1469. var group = YTMA.collect(this.ytmx.data.id);
  1470. console.log('closing all', this.ytmx.data.id, group.length);
  1471. group.forEach(function (y) {
  1472. y.disableOpenOnScroll();
  1473. y.getUI().hidePlayer();
  1474. });
  1475. },
  1476. setControlBarSize: function (size) {
  1477. this.markSelected(this.control.querySelector('li[data-value="' + size + '"]'), 'size');
  1478. },
  1479. controlBar: function () {
  1480. var f = document.createDocumentFragment();
  1481.  
  1482. $$.a(f,
  1483. this.customBar.ratio ? this.buildList('ytm_ratios', [
  1484. {type: 'ratio', text: '4:3', value: YTMA.UI.ratios.SD, title: 'SD'},
  1485. {type: 'ratio', text: '16:9', value: YTMA.UI.ratios.HD, title: 'Landscape'},
  1486. {type: 'ratio', text: '9:16', value: YTMA.UI.ratios.PORTRAIT, title: 'Portrait'}]) : null,
  1487. this.customBar.size ? this.buildList('ytm_sizes', [
  1488. {type: 'size', text: '\u00D8', value: YTMA.UI.sizes.HIDDEN, title: 'Hide the video.'},
  1489. {type: 'size', text: 'S', value: YTMA.UI.sizes.S, title: '240p'},
  1490. {type: 'size', text: 'M', value: YTMA.UI.sizes.M, title: '360p'},
  1491. {type: 'size', text: 'L', value: YTMA.UI.sizes.L, title: '480p'},
  1492. {type: 'size', text: 'X', value: YTMA.UI.sizes.X, title: '720p'}]) : null,
  1493. this.buildList('ytm_options', [
  1494. strg.on ? {type: 'settings', text: '!', title: 'YTMA Settings'} : null,
  1495. {type: 'close', text: '\u00D7', title: 'Close the video.'}])
  1496. );
  1497.  
  1498. this.control.appendChild(f);
  1499. this.control.addEventListener('click', YTMA.UI.events.videoBar.bind(this), false);
  1500. this.projector.appendChild(this.control);
  1501. this.ytmx.container.insertBefore(this.projector, this.trigger.nextSibling);
  1502. },
  1503. markSelected: function (el, type) {
  1504. el.id = type + this.ytmx.data.uid;
  1505. try {
  1506. this.selected[type].removeAttribute('id');
  1507. } catch (e) {}
  1508. this.selected[type] = el;
  1509. },
  1510. buildList: function (className, elements) {
  1511. var li = $$.e('li'),
  1512. ul = $$.e('ul', {className: className}, li),
  1513. f = document.createDocumentFragment(),
  1514. i,
  1515. e;
  1516.  
  1517. for (i = 0; i < elements.length; i++) {
  1518. e = elements[i];
  1519. if (e) {
  1520. f.appendChild(this.li(e.type, e.text, e.value, e.title));
  1521. }
  1522. }
  1523. ul.appendChild(f);
  1524. return li;
  1525. },
  1526. li: function (type, txt, value, title) {
  1527. var l = $$.e('li', {_type: type, textContent: txt, _value: value, title: title});
  1528. if ((type === 'size' && this.play.attrs.size === value) || (type === 'ratio' && this.play.attrs.ratio === value)) {
  1529. this.markSelected(l, type);
  1530. }
  1531. return l;
  1532. }
  1533. };
  1534.  
  1535. /** P L A Y E R CLASS
  1536. * @param parent YTMA instance
  1537. */
  1538. YTMA.Player = function (parent) {
  1539. this.parent = parent;
  1540.  
  1541. this.mode = 'off';
  1542.  
  1543. this.attrs = {
  1544. sources: null,
  1545. quality: YTMA.DB.qualities.$get(YTMA.user.preferences.quality),
  1546. size: null,
  1547. ratio: null,
  1548. start: this.time(),
  1549. type: null
  1550. };
  1551.  
  1552. this.attrs.sources = YTMA.DB.sources[parent.data.site](parent.data, this.attrs);
  1553. this.attrs.type = this.findType();
  1554. this.media = YTMA.Player.makeMedia[this.attrs.type](this);
  1555.  
  1556. this.channel = $$.e('div', {className: 'ytm_panel_channel ytm_block'}, this.media, true);
  1557. this.switcher = $$.e('div', {className: 'ytm_panel_switcher ytm_panel_size ytm_block ytm_' + this.attrs.type, _ytmuid: this.parent.data.uid, _standby: true});
  1558. this.panel = $$.e('div', {className: 'ytm_panel ytm_block'}, this.switcher, true);
  1559.  
  1560. if (parent.data.site === 'soundcloud' && YTMA.reg.extra.soundcloud.playlist.test(parent.anchor.href)) {
  1561. this.media.classList.add('ytm_soundcloud-playlist');
  1562. this.switcher.classList.add('ytm_soundcloud-playlist');
  1563. }
  1564.  
  1565. this.dimmensions(YTMA.user.preferences.ratio, YTMA.user.preferences.size);
  1566. };
  1567.  
  1568. YTMA.Player.css = {
  1569. item: function (key, value) {
  1570. if (isNumber(value)) {
  1571. value += 'px';
  1572. }
  1573.  
  1574. return '\t' + key + ': ' + value + ';\n';
  1575. },
  1576. iter: function (css, cssEntries) {
  1577. $$.o(cssEntries, function (key, value) {
  1578. css.push(YTMA.Player.css.item(key, value));
  1579. });
  1580. css.push('}');
  1581. },
  1582. generator: function () {
  1583. var css = [];
  1584.  
  1585. $$.o(this.sizes, function (size, sizes) {
  1586. $$.o(sizes, function (dimm, keys) {
  1587. css.push('\n.ytm_panel-' + size + '.ytm_panel-' + dimm + ' .ytm_panel_size {\n');
  1588. YTMA.Player.css.iter(css, keys);
  1589. });
  1590. });
  1591.  
  1592. // add site overrides
  1593. $$.o(this.sites, function (site, data) {
  1594. $$.o(data, function (setting, keys) {
  1595. if (setting === 'all') {
  1596. css.push('\n.ytm_site_' + site + ' .ytm_panel_size {\n');
  1597. } else {
  1598. css.push('\n.ytm_site_' + site + ' .ytm_panel-' + setting + ' .ytm_panel_size {\n');
  1599. }
  1600. YTMA.Player.css.iter(css, keys);
  1601. });
  1602. });
  1603.  
  1604. return css.join('');
  1605. },
  1606. sizes: (function () {
  1607. var merge = {};
  1608. $$.o(YTMA.DB.playerSize.sizes, function (num, size) {
  1609. if (num >= 0) {
  1610. merge[size] = {};
  1611.  
  1612. $$.o(YTMA.DB.playerSize.ratios, function (k, ratio) {
  1613. if (ratio === 'pr') {
  1614. var w = Math.floor(num * 0.95); // smaller than the normal sizes
  1615. merge[size][ratio] = {
  1616. width: w,
  1617. height: Math.floor(Math.floor(w * YTMA.DB.playerSize.aspects[k]))
  1618. };
  1619. } else {
  1620. merge[size][ratio] = {
  1621. width: Math.floor(num * YTMA.DB.playerSize.aspects[k]),
  1622. height: num
  1623. };
  1624. }
  1625. });
  1626. }
  1627. });
  1628.  
  1629. return merge;
  1630. }()),
  1631. sites: { // custom sizes per site
  1632. soundcloud: {
  1633. all: {
  1634. height: '118px !important'
  1635. }
  1636. },
  1637. vine: {
  1638. s: {
  1639. width: 240,
  1640. height: 240
  1641. },
  1642. m: {
  1643. width: 360,
  1644. height: 360
  1645. },
  1646. l: {
  1647. width: 480,
  1648. height: 480
  1649. },
  1650. xl: {
  1651. width: 720,
  1652. height: 720
  1653. }
  1654. }
  1655. }
  1656. };
  1657.  
  1658. YTMA.Player.makeMedia = {
  1659. $css: function (type) {
  1660. return 'ytm_panel_media ytm_panel_size ytm_block ytm_' + type;
  1661. },
  1662. video: function (player) {
  1663. var video = $$.e('video', {
  1664. controls: true,
  1665. autoplay: false,
  1666. loop: true,
  1667. className: this.$css('video'),
  1668. $allowscriptaccess: true,
  1669. preload: 'metadata'
  1670. });
  1671.  
  1672. player.attrs.sources.forEach(function (source) {
  1673. $$.e('source', {src: source.src, $type: source.type}, video);
  1674. });
  1675.  
  1676. return video;
  1677. },
  1678. iframe: function (player) {
  1679. return $$.e('iframe', {
  1680. $allowfullscreen: true,
  1681. // $sandbox: 'allow-same-origin allow-scripts allow-popups',
  1682. $type: player.attrs.sources[0].type,
  1683. src: player.attrs.sources[0].src,
  1684. className: this.$css('iframe')
  1685. });
  1686. },
  1687. audio: function (player) {
  1688. return $$.e('audio', {
  1689. src: player.attrs.sources[0].src,
  1690. $type: player.attrs.sources[0].type
  1691. });
  1692. }
  1693. };
  1694.  
  1695. YTMA.Player.prototype = {
  1696. constructor: YTMA.Player,
  1697. dimmensions: function (ratio, size) {
  1698. this.attrs.ratio = isNumber(ratio) ? ratio : this.attrs.ratio;
  1699. this.attrs.size = isNumber(size) ? size : this.attrs.size;
  1700. this.panel.className = YTMA.DB.playerSize.$get(this.attrs.ratio, this.attrs.size);
  1701. },
  1702. time: function () {
  1703. try {
  1704. var m = this.parent.data.uri.match(YTMA.reg.time).slice(1, 3);
  1705. return ((+m[0] || 0) * 60) + (+m[1] || 0);
  1706. } catch (e) { return 0; }
  1707. },
  1708. findType: function () {
  1709. if (this.parent.data.site === 'html5-audio') { return 'audio'; }
  1710. if (YTMA.DB.videoTag[this.parent.data.site]) { return 'video'; }
  1711. return 'iframe';
  1712. },
  1713. switchOff: function () {
  1714. // console.log('removed media');
  1715.  
  1716. if (this.media.pause) {
  1717. console.log('pausing');
  1718. this.media.pause();
  1719. }
  1720.  
  1721. try {
  1722. this.switcher.removeChild(this.channel);
  1723. } catch (e) {
  1724. // console.error(e);
  1725. }
  1726. this.mode = 'off';
  1727. },
  1728. switchOn: function () {
  1729. if (this.attrs.size === 0) {
  1730. this.attrs.size = YTMA.user.preferences.size;
  1731. this.parent.ui.resetViewSize();
  1732. }
  1733. // console.log('switch to media');
  1734. this.switcher.appendChild(this.channel);
  1735. this.switcher.dataset.standby = false;
  1736. this.mode = 'on';
  1737. },
  1738. switchStandby: function () {
  1739. // console.log('switch to standby');
  1740. this.switchOff();
  1741. this.switcher.dataset.standby = true;
  1742. this.mode = 'standby';
  1743. },
  1744. isStandby: function () {
  1745. return this.mode === 'standby';
  1746. }
  1747. };
  1748.  
  1749. YTMA.prototype = {
  1750. constructor: YTMA,
  1751. getUI: function () {
  1752. if (!this.ui) {
  1753. this.ui = new YTMA.UI(this);
  1754. }
  1755.  
  1756. return this.ui;
  1757. },
  1758. setup: function () {
  1759. try {
  1760. this.dom.mod[this.data.site].call(this);
  1761. } catch (e) {}
  1762. this.dom.link.call(this);
  1763. this.dom.span.call(this);
  1764. },
  1765. disableOpenOnScroll: function () {
  1766. this.anchor.dataset.ytmscroll = false;
  1767. },
  1768. canScroll: function () {
  1769. return this.anchor.dataset.ytmscroll === 'true';
  1770. },
  1771. isBelow: function (link) {
  1772. return YTMA.Scroll.compare(this.anchor, link) < 1;
  1773. },
  1774. dom: {
  1775. mod: { // modifies YTMA interface according to site
  1776. youtube: function () {
  1777. this.spn.title = 'ytma!';
  1778. this.spn.addEventListener('mouseenter', YTMA.events.thumb.start, false);
  1779. this.spn.addEventListener('mouseleave', YTMA.events.thumb.stop, false);
  1780. this.spn.style.backgroundImage = ['url(https://i3.ytimg.com/vi/', this.data.id, '/1.jpg)'].join('');
  1781. this.achor.href = this.achor.href.replace('http:', 'https:').replace('youtu.be/', 'youtube.com/watch?v=');
  1782. },
  1783. vimeo: function () {
  1784. this.spn.title = 'vimeo too!';
  1785. },
  1786. vine: function () {
  1787. this.spn.title = 'vine me!';
  1788. this.achor.href = this.achor.href.replace('http:', 'https:');
  1789. },
  1790. soundcloud: function () {
  1791. this.spn.title = 'sound off!';
  1792. this.achor.href = this.achor.href.replace('http:', 'https:');
  1793. },
  1794. html5: function () {
  1795. this.spn.title = 'html5 go!';
  1796. },
  1797. gfycat: function () {
  1798. this.spn.style.backgroundImage = ['url(https://thumbs.gfycat.com/', this.data.id, '-poster.jpg)'].join('');
  1799. this.spn.title = 'gfycat meow!';
  1800. this.achor.href = this.achor.href.replace('http:', 'https:');
  1801. },
  1802. imgur: function () {
  1803. this.spn.style.backgroundImage = ['url(https://i.imgur.com/', this.data.id, 'h.jpg)'].join('');
  1804. this.spn.title = 'imgur it!';
  1805. this.achor.href = this.achor.href.replace('http:', 'https:');
  1806. }
  1807. },
  1808. link: function () {
  1809. if (this.anchor.getElementsByTagName('img').length === 0) {
  1810. this.anchor.className += ' ytm_link ytm_link_' + this.data.site + ' ';
  1811. }
  1812. this.anchor.dataset.ytmid = this.data.id;
  1813. this.anchor.dataset.ytmuid = this.data.uid;
  1814. this.anchor.dataset.ytmsid = this.data.sid;
  1815. this.anchor.title = 'Visit the video page.';
  1816. this.anchor.parentNode.insertBefore(this.container, this.anchor.nextSibling);
  1817. },
  1818. span: function () {
  1819. var f = document.createDocumentFragment();
  1820.  
  1821. $$.e('span', {className: 'ytm_init ytm_label ytm_sans ytm_box', textContent: this.spn.title}, this.spn);
  1822. $$.e('var', {className: 'ytm_label ytm_box', _ytmid: this.data.id, _ytmuid: this.data.uid, _ytmsid: this.data.sid, _ytmsite: this.data.site, textContent: '\u25B6'}, this.spn);
  1823.  
  1824. this.spn.title = 'Watch now!';
  1825. f.appendChild(this.spn);
  1826.  
  1827. if (YTMA.DB.fn.hasAjax(this.data.site)) { f.appendChild(this.dom.dataLoadLink.call(this)); }
  1828. if (YTMA.DB.slim[this.data.site]) { this.container.classList.add('ytm_site_slim'); }
  1829. if (YTMA.DB.scroll[this.data.site]) { this.anchor.classList.add('ytm_scroll'); }
  1830.  
  1831. this.container.appendChild(f);
  1832. },
  1833. dataLoadLink: function () {
  1834. var a, s;
  1835. s = $$.e('span', {className: 'ytm_bd ytm_normalize ytm_manual _' + this.data.sid});
  1836. a = $$.e('a', {
  1837. className: 'ytm_title',
  1838. textContent: 'Load description.',
  1839. href: '#',
  1840. title: 'Load this video\'s description.',
  1841. _ytmid: this.data.id,
  1842. _ytmsite: this.data.site,
  1843. _ytmuri: this.data.uri,
  1844. _ytmdescription: 'true'
  1845. });
  1846. return $$.a(s, a);
  1847. }
  1848. }
  1849. };
  1850.  
  1851. /**
  1852. * Creates a new YTMA from the given attributes
  1853. * @String|Number id Unique ID
  1854. * @String site Website eg: youtube, vimeo
  1855. * @HTMLAnchorElement a Anchor element
  1856. */
  1857. YTMA.prototype._new = function (id, site, a) {
  1858. var uid = YTMA.escapeId(id + '_' + (YTMA.num += 1));
  1859.  
  1860. this.data = {
  1861. id: id,
  1862. uid: YTMA.escapeId(uid), // unique id
  1863. sid: YTMA.escapeId(id), // shared id
  1864. site: site,
  1865. uri: a.href
  1866. };
  1867.  
  1868. this.ui = null;
  1869.  
  1870. if (!a.hasAttribute('data-ytmscroll')) { a.dataset.ytmscroll = true; }
  1871.  
  1872. this.anchor = a;
  1873.  
  1874. this.spn = $$.e('span', {className: 'ytm_trigger ytm_block ytm_normalize ytm_sans', _ytmid: this.data.id, _ytmsite: this.data.site});
  1875. this.container = $$.e('div', {id: 'w' + this.data.uid, className: 'ytm_spacer ytm_block ytm_site_' + this.data.site});
  1876.  
  1877. return this;
  1878. };
  1879.  
  1880. /**
  1881. * Recreates a YTMA object from a trigger element
  1882. * @HTMLElement
  1883. */
  1884. YTMA.prototype._reactivate = function (trigger) {
  1885. var id = trigger.dataset.ytmid,
  1886. a = document.querySelector('a[data-ytmuid="' + trigger.dataset.ytmuid + '"]');
  1887.  
  1888. this.data = {
  1889. id: id,
  1890. uid: trigger.dataset.ytmuid,
  1891. sid: trigger.dataset.ytmsid,
  1892. site: trigger.dataset.ytmsite,
  1893. uri: a.href
  1894. };
  1895.  
  1896. this.ui = null;
  1897. this.anchor = a;
  1898. this.spn = trigger.parentElement;
  1899. this.container = this.spn.parentElement;
  1900.  
  1901. return this;
  1902. };
  1903.  
  1904. /** S C R O L L CLASS
  1905. * Window-Scroll Event Helper
  1906. */
  1907. YTMA.Scroll = (function () {
  1908.  
  1909. function Scroll(selector, cb, delay) {
  1910. this.selector = selector;
  1911. this.cb = cb;
  1912.  
  1913. // console.log('YTMA.Scroll Monitor: ', selector);
  1914. this.bound = Scroll.debounce(this.monitor.bind(this), delay || 500);
  1915.  
  1916. this.bound();
  1917. window.addEventListener('scroll', this.bound, false);
  1918. }
  1919.  
  1920. Scroll.debounce = function (fn, delay) {
  1921. var timeout;
  1922. delay = delay || 250;
  1923.  
  1924. return function () {
  1925. var self = this, args = arguments, timed;
  1926.  
  1927. timed = function () {
  1928. timeout = null;
  1929. fn.apply(self, args);
  1930. };
  1931.  
  1932. window.clearTimeout(timeout);
  1933. timeout = window.setTimeout(timed, delay);
  1934. };
  1935. };
  1936.  
  1937. Scroll.visible = function (el) {
  1938. var bound = el.getBoundingClientRect();
  1939. return (bound.top >= 0 && bound.top <= document.documentElement.clientHeight);
  1940. };
  1941.  
  1942. Scroll.visibleAll = function (el, offset) {
  1943. var bound = el.getBoundingClientRect(),
  1944. height = document.documentElement.clientHeight;
  1945. offset = isNumber(offset) ? +offset : 0;
  1946. return ((bound.bottom + offset >= 0)
  1947. && (bound.top <= height + offset || bound.bottom <= height - offset));
  1948. };
  1949.  
  1950. /** Returns 1, 0, -1 when el1 is above, exactly the same, or below el2 */
  1951. Scroll.compare = function (el1, el2) {
  1952. var a = el1.getBoundingClientRect().y,
  1953. b = el2.getBoundingClientRect().y;
  1954.  
  1955. if (a < b) { return 1; }
  1956. if (a === b) { return 0; }
  1957. return -1;
  1958. };
  1959.  
  1960. Scroll.prototype = {
  1961. stop: function () {
  1962. // console.log('clear scroll: ', this.selector);
  1963. window.removeEventListener('scroll', this.bound);
  1964. },
  1965. monitor: function () {
  1966. $$.s(this.selector, this.cb);
  1967. }
  1968. };
  1969.  
  1970. return Scroll;
  1971.  
  1972. }());
  1973.  
  1974. YTMA.main();
  1975.  
  1976. }());