YouTube Embedder

Convert links and optionally URL text which points to a YouTube video, into embedded YouTube frame. For URL text, it can be converted to a link only. The main function can be configured for on-demand-only for slow computers. If enabled, the main function can be executed via bookmarklet using this URL: javascript:yte_ujs()

  1. // ==UserScript==
  2. // @name YouTube Embedder
  3. // @namespace https://greasyfork.org/en/users/85671-jcunews
  4. // @description Convert links and optionally URL text which points to a YouTube video, into embedded YouTube frame. For URL text, it can be converted to a link only. The main function can be configured for on-demand-only for slow computers. If enabled, the main function can be executed via bookmarklet using this URL: javascript:yte_ujs()
  5. // @author jcunews
  6. // @version 1.0.6
  7. // @license GNU AGPLv3
  8. // @match *://*/*
  9. // @exclude *://www.youtube.com/embed/*
  10. // @grant none
  11. // ==/UserScript==
  12.  
  13. (function(xe, fe, ce, rx, trx, trxg, hrx, lo, qu) {
  14.  
  15. //===== CONFIGURATION BEGIN =====
  16.  
  17. //The minimum width & height of the container element where the link or URL text is found, in order to apply the conversion.
  18. var minimumWidth = 256;
  19. var minimumHeight = 144;
  20.  
  21. //Embedded YouTube frame width in pixels (should be multiplication of 16).
  22. //If zero, the width is the container element width of the link or URL text, and the height is calculated using 16:9 ratio (widescreen video dimension).
  23. //If the final width is smaller than the minimum width setting, and if the source is an URL text, it will be converted to a link instead.
  24. var frameWidth = 0;
  25.  
  26. //Enable URL text to YouTube frame converter (aside from links).
  27. var convertUrlText = true;
  28.  
  29. //Convert to link only, when on these domains.
  30. var convertToLinkOnlyDomains = /somesite\.info|subnet\d+\.other\.net/i;
  31.  
  32. //Delay between processing each node scanning queue. Lower value means faster scanning, but more CPU intensive.
  33. //Higher value means lesser CPU usage, but also means slower scanning.
  34. var queueDelay = 50; //in milliseconds. 1000ms = 1 second.
  35.  
  36. //Number of nodes to process at a time. Adjust this and queueDelay settings to find the best performance without triggering the web browser's busy dialog.
  37. //Lower value means lesser CPU usage, but also means slower scanning.
  38. var nodesPerProcessing = 5;
  39.  
  40. //The onDemandOnlyDomains makes the main function available on-demand-only for specific or all sites.
  41. //If enabled, it's accessible via bookmarklet using this URL: javascript:yte_ujs()
  42. //This setting is for slow computers, since the main function is CPU intensive.
  43. var onDemandOnlyDomains = /somesite\.info|subnet\d+\.other\.net/i;
  44.  
  45. //===== CONFIGURATION END =====
  46.  
  47. function linkToFrame(node, m, pn, w, h, a) {
  48. if (node.ytebd_nocovert || !node.offsetWidth || !node.offsetHeight) return;
  49. if (!lo && (m = node.href.match(rx))) {
  50. m = "https://www.youtube.com/embed/" + m[1];
  51. pn = node.parentNode;
  52. while (pn) {
  53. if (pn.scrollHeight > pn.offsetHeight) break;
  54. pn = pn.parentNode;
  55. }
  56. h = ((pn || node.parentNode).offsetHeight * 0.9) >> 0;
  57. w = frameWidth > 0 ? frameWidth : node.parentNode.offsetWidth;
  58. if ((pn = (w / 16 * 9) >> 0) > h) {
  59. w = (h / 9 * 16) >> 0;
  60. } else h = pn;
  61. if ((w >= minimumWidth) && (h >= minimumHeight)) {
  62. c = document.createElement("IFRAME");
  63. c.src = m;
  64. c.allowFullscreen = true;
  65. c.referrerPolicy = "no-referrer";
  66. c.style.border = "none";
  67. c.width = w;
  68. c.height = h;
  69. c.ytebd_nocovert = 1;
  70. node.replaceWith(c);
  71. } else if (w && h) node.ytebd_nocovert = 1;
  72. } else node.ytebd_nocovert = 1;
  73. }
  74.  
  75. function processNode(node, c, m, w, h, pn) {
  76. switch (node.nodeType) {
  77. case Node.ELEMENT_NODE:
  78. case Node.DOCUMENT_NODE:
  79. if (node.ytebd_nocovert) break;
  80. if (node.nodeName === "A") {
  81. linkToFrame(node);
  82. node = null;
  83. }
  84. if (node && (xe.indexOf(node.nodeName) < 0)) {
  85. if (fe.indexOf(node.nodeName) >= 0) {
  86. m = (m = node.src.match(/:\/\/(.*?)\//)) && (m[1] === location.hostname);
  87. } else m = true;
  88. if (m && ((node.nodeName !== "A") || convertUrlText)) {
  89. node.childNodes.forEach(queue);
  90. }
  91. }
  92. break;
  93. case Node.TEXT_NODE:
  94. if (convertUrlText && (m = node.nodeValue.match(trx))) {
  95. w = node.nodeValue;
  96. h = -1;
  97. a = [];
  98. while (c = trxg.exec(w)) {
  99. if (c.index > 0) a.push(w.substring(h, c.index));
  100. a.push(c);
  101. h = c.index + c[0].length;
  102. }
  103. if ((h > 0) && (h < w.length)) a.push(w.substr(h));
  104. for (c = a.length - 1; c >= 0; c--) {
  105. if (!Array.isArray(a[c]) && !a[c].replace(/^\s+|\s+$/g, "")) a.splice(c, 1);
  106. }
  107. if (node.parentNode.nodeName === "A") {
  108. if (a.length === 1) {
  109. node.href = "https://www.youtube.com/embed/" + a[0][1];
  110. node.rel = "nofollow noopener noreferrer";
  111. node.target = "_blank";
  112. }
  113. a = null;
  114. }
  115. if (a) {
  116. pn = node.parentNode;
  117. c = node.nextSibling;
  118. a.forEach(function(v, i, n) {
  119. if (!Array.isArray(v)) {
  120. n = document.createTextNode(v);
  121. } else {
  122. n = document.createElement("A");
  123. n.textContent = v[0];
  124. n.title = v[1];
  125. n.href = "https://www.youtube.com/embed/" + v[1];
  126. n.rel = "nofollow noopener noreferrer";
  127. n.target = "_blank";
  128. }
  129. if (i > 0) {
  130. pn.insertBefore(n, c);
  131. } else {
  132. node.replaceWith(n);
  133. }
  134. linkToFrame(n);
  135. });
  136. }
  137. }
  138. }
  139. }
  140.  
  141. function processQueue() {
  142. setTimeout(function(nodes) {
  143. nodes.forEach(processNode);
  144. if (qu.length) setTimeout(processQueue, queueDelay);
  145. }, queueDelay, qu.splice(0, nodesPerProcessing));
  146. }
  147.  
  148. function queue(node) {
  149. qu.push(node);
  150. if (qu.length === 1) setTimeout(processQueue, queueDelay);
  151. }
  152.  
  153. xe = ["BUTTON", "INPUT", "SCRIPT", "SELECT", "STYLE"];
  154. fe = ["FRAME", "IFRAME"];
  155. rx = /^(?:https?:\/\/)?(?:(?:(?:www\.)?youtube\.com\/)(?:embed\/|watch\?.*v=)|youtu\.be\/)([0-9a-z_\-]{11})/i;
  156. trx = /(?:https?:\/\/)?(?:(?:(?:www\.)?youtube\.com\/)(?:embed\/|watch\?.*v=)|youtu\.be\/)([0-9a-z_\-]{11})[^\s,'")\]}>]*/i;
  157. trxg = /(?:https?:\/\/)?(?:(?:(?:www\.)?youtube\.com\/)(?:embed\/|watch\?.*v=)|youtu\.be\/)([0-9a-z_\-]{11})[^\s,'")\]}>]*/gi;
  158. hrx = /^(?:(?:www\.)?youtube\.com|youtu\.be)$/i;
  159. lo = convertToLinkOnlyDomains.test(location.hostname);
  160. qu = [];
  161.  
  162. if (nodesPerProcessing <= 0) nodesPerProcessing = 1;
  163. if (!document.body) return;
  164.  
  165. if (onDemandOnlyDomains.test(location.hostname)) {
  166. window.yte_ujs = function() {
  167. queue(document.body);
  168. return undefined;
  169. };
  170. } else {
  171. if (document.body) queue(document.body);
  172. (new MutationObserver(function(records) {
  173. records.forEach(function(record) {
  174. if (record.type === "childList") {
  175. record.addedNodes.forEach(queue);
  176. } else queue(record.target);
  177. });
  178. })).observe(document.body, {attributes: true, attributeFilter: ["class", "style"], childList: true, subtree: true});
  179. }
  180.  
  181. })();