YouTube HTML5 Video Pan And Zoom

Add controls to pan and zoom HTML5 video.

目前为 2017-04-16 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name YouTube HTML5 Video Pan And Zoom
  3. // @namespace YouTubeHTML5VideoPanAndZoom
  4. // @description Add controls to pan and zoom HTML5 video.
  5. // @author jcunews
  6. // @include https://www.youtube.com/watch*
  7. // @version 1.0.4
  8. // @grant none
  9. // ==/UserScript==
  10.  
  11. var ele = document.createElement("SCRIPT");
  12. ele.innerHTML = "(" + (function() {
  13. var resizeUpdateDelay = 400;
  14. var eleVideo, baseWidth, baseHeight, posX, posY, deltaX, deltaY, scaleX, scaleY, newCSS;
  15. var eleContainer, containerWidth, containerHeight, configs;
  16. var changing = false, timerIdUpdateAll = 0;
  17.  
  18. function setPos(dx, dy) {
  19. var rw = 1, rh = 1;
  20. if (fullScreen) {
  21. rw = screen.width / eleContainer.offsetWidth;
  22. rh = screen.height / eleContainer.offsetHeight;
  23. }
  24. if (dx !== undefined) {
  25. deltaX += dx;
  26. deltaY += dy;
  27. } else {
  28. posX = 0;
  29. posY = 0;
  30. deltaX = 0;
  31. deltaY = 0;
  32. }
  33. changing = true;
  34. eleVideo.style.left = ((posX + deltaX) * rw) + "px";
  35. eleVideo.style.top = ((posY + deltaY) * rh) + "px";
  36. eleVideo.style.width = "100%";
  37. newCSS = eleVideo.style.cssText;
  38. changing = false;
  39. }
  40.  
  41. function setSize(dx, dy) {
  42. var rw = 1, rh = 1;
  43. if (fullScreen) {
  44. rw = screen.width / eleContainer.offsetWidth;
  45. rh = screen.height / eleContainer.offsetHeight;
  46. }
  47. if (dx !== undefined) {
  48. scaleX += dx;
  49. scaleY += dy;
  50. } else {
  51. scaleX = 1;
  52. scaleY = 1;
  53. }
  54. changing = true;
  55. eleVideo.style.MozTransform = eleVideo.style.WebkitTransform =
  56. "scaleX(" + (scaleX*rw).toFixed(2) + ") scaleY(" + (scaleY*rh).toFixed(2) + ")";
  57. newCSS = eleVideo.style.cssText;
  58. changing = false;
  59. }
  60.  
  61. function updateAll() {
  62. var rw = 1, rh = 1, px = posX + deltaX, py = posY + deltaY;
  63. if (fullScreen) {
  64. rw = screen.width / eleContainer.offsetWidth;
  65. rh = screen.height / eleContainer.offsetHeight;
  66. }
  67. changing = true;
  68. eleVideo.style.cssText = newCSS;
  69. changing = false;
  70. vpzConfigs.style.top = fullScreen ? "-3px" : "";
  71. clearTimeout(timerIdUpdateAll);
  72. timerIdUpdateAll = 0;
  73. }
  74.  
  75. function setupPanel() {
  76. var vpzPanel = window.vpzPanel;
  77. if (!vpzPanel) {
  78. vpzPanel = document.createElement("DIV");
  79. vpzPanel.id = "vpzPanel";
  80. vpzPanel.innerHTML=(function(){/*
  81. <style>
  82. #vpzPanel{position:relative;float:left;margin:10px 0 0 20px;white-space:nowrap}
  83. #vpzPanel button{vertical-align:top;border-radius:3px;width:18px;height:18px;line-height:1;font-size:15px;font-weight:bold;cursor:pointer}
  84. #vpzPanel button:hover{background:#bdb}
  85. #vpzMoveLeft{margin-left:0}
  86. #vpzMoveL,#vpzShrink,#vpzShrinkH,#vpzShrinkV,#vpzConfig{margin-left:10px!important}
  87. #vpzCfgContainer{display:none;position:absolute;z-index:99;right:0;bottom:55px;padding:5px;line-height:normal;background:#555}
  88. #vpzCfgContainer button{height:21px;padding:0 5px}
  89. #vpzConfigs{position:relative}
  90. #vpzConfigs~button{width:auto}
  91. </style>
  92. <button id="vpzReset" class="yt-uix-button-default" title="Reset">0</button>
  93. <button id="vpzMoveL" class="yt-uix-button-default" title="Move Left">&#x2190;</button>
  94. <button id="vpzMoveU" class="yt-uix-button-default" title="Move Up">&#x2191;</button>
  95. <button id="vpzMoveD" class="yt-uix-button-default" title="Move Down">&#x2193;</button>
  96. <button id="vpzMoveR" class="yt-uix-button-default" title="Move Right">&#x2192;</button>
  97. <button id="vpzShrink" class="yt-uix-button-default" title="Shrink">&#x2199;</button>
  98. <button id="vpzExpand" class="yt-uix-button-default" title="Expand">&#x2197;</button>
  99. <button id="vpzShrinkH" class="yt-uix-button-default" title="Shrink Horizontal">&#x21C7;</button>
  100. <button id="vpzExpandH" class="yt-uix-button-default" title="Expand Horizontal">&#x21C9;</button>
  101. <button id="vpzShrinkV" class="yt-uix-button-default" title="Shrink Vertical">&#x21CA;</button>
  102. <button id="vpzExpandV" class="yt-uix-button-default" title="Expand Vertical">&#x21C8;</button>
  103. <button id="vpzConfig" class="yt-uix-button-default" title="Show/Hide Profiles Panel">P</button>
  104. <div id="vpzCfgContainer">
  105. Configs: <select id="vpzConfigs"></select>
  106. <button id="vpzSaveCfg" class="yt-uix-button-default">Save</button>
  107. <button id="vpzLoadCfg" class="yt-uix-button-default">Load</button>
  108. <button id="vpzDelCfg" class="yt-uix-button-default">Delete</button>
  109. </div>
  110. */}).toString().match(/(?:\/\*)\s+((\S+|\s+)*)(?=\*\/)/)[1];
  111. var a = window["movie_player"].querySelector(".ytp-chrome-controls .ytp-right-controls");
  112. a.parentNode.insertBefore(vpzPanel, a);
  113.  
  114. vpzReset.onclick = function() {
  115. setPos();
  116. setSize();
  117. };
  118.  
  119. vpzMoveL.onclick = function() {
  120. setPos(-8, 0);
  121. };
  122. vpzMoveU.onclick = function() {
  123. setPos(0, -8);
  124. };
  125. vpzMoveD.onclick = function() {
  126. setPos(0, 8);
  127. };
  128. vpzMoveR.onclick = function() {
  129. setPos(8, 0);
  130. };
  131.  
  132. vpzShrink.onclick = function() {
  133. setSize(-0.01, -0.01);
  134. };
  135. vpzExpand.onclick = function() {
  136. setSize(0.01, 0.01);
  137. };
  138.  
  139. vpzShrinkH.onclick = function() {
  140. setSize(-0.01, 0);
  141. };
  142. vpzExpandH.onclick = function() {
  143. setSize(0.01, 0);
  144. };
  145.  
  146. vpzShrinkV.onclick = function() {
  147. setSize(0, -0.01);
  148. };
  149. vpzExpandV.onclick = function() {
  150. setSize(0, 0.01);
  151. };
  152.  
  153. vpzConfig.onclick = function() {
  154. vpzCfgContainer.style.display = vpzCfgContainer.style.display ? "" : "block";
  155. };
  156.  
  157. var i, opt;
  158. for (i = 0; i < configs.length; i++) {
  159. opt = document.createElement("OPTION");
  160. opt.value = i;
  161. opt.textContent = configs[i].name;
  162. vpzConfigs.appendChild(opt);
  163. }
  164. function configIndex(cfgName) {
  165. for (var i = configs.length-1; i >= 0; i--) {
  166. if (configs[i].name === cfgName) {
  167. return i;
  168. break;
  169. }
  170. }
  171. return -1;
  172. }
  173. function optionIndex(idx) {
  174. for (var i = configs.length-1; i >= 0; i--) {
  175. if (vpz.options[i].value == idx) {
  176. return i;
  177. break;
  178. }
  179. }
  180. return -1;
  181. }
  182.  
  183. vpzSaveCfg.onclick = function() {
  184. var cfgName, idx, i, opt;
  185. if (vpzConfigs.selectedIndex >= 0) {
  186. cfgName = vpzConfigs.selectedOptions[0].textContent;
  187. } else {
  188. cfgName = "";
  189. }
  190. cfgName = prompt("Enter configuration name.", cfgName);
  191. if (cfgName === null) return;
  192. cfgName = cfgName.trim();
  193. if (!cfgName) return;
  194. idx = configIndex(cfgName);
  195. if (idx >= 0) {
  196. if (!confirm("Replace existing configuration?")) return;
  197. vpzConfigs.options[optionIndex(idx)].textContent = cfgName;
  198. } else {
  199. idx = configs.length;
  200. opt = document.createElement("OPTION");
  201. opt.value = idx;
  202. opt.textContent = cfgName;
  203. vpzConfigs.appendChild(opt);
  204. vpzConfigs.selectedIndex = idx;
  205. }
  206. configs.splice(idx, 1, {
  207. name: cfgName,
  208. data: [deltaX, deltaY, scaleX, scaleY]
  209. });
  210. localStorage.vpzConfigs = JSON.stringify(configs);
  211. };
  212. vpzLoadCfg.onclick = function() {
  213. var idx;
  214. if (vpzConfigs.selectedIndex < 0) return;
  215. idx = parseInt(vpzConfigs.selectedOptions[0].value);
  216. setPos();
  217. setPos(configs[idx].data[0], configs[idx].data[1]);
  218. scaleX = 0; scaleY = 0;
  219. setSize(configs[idx].data[2], configs[idx].data[3]);
  220. };
  221. vpzDelCfg.onclick = function() {
  222. if ((vpzConfigs.selectedIndex < 0) || !confirm("Delete selected configuration?")) return;
  223. configs.splice(vpzConfigs.selectedOptions[0].value, 1);
  224. localStorage.vpzConfigs = JSON.stringify(configs);
  225. vpzConfigs.removeChild(vpzConfigs.selectedOptions[0]);
  226. };
  227.  
  228. }
  229. }
  230.  
  231. function init() {
  232. eleVideo = document.querySelector(".html5-main-video");
  233. if (eleVideo) {
  234. if (eleVideo.videoHeight) {
  235. baseWidth = eleVideo.offsetWidth;
  236. baseHeight = eleVideo.offsetHeight;
  237. posX = eleVideo.offsetLeft;
  238. posY = eleVideo.offsetTop;
  239. deltaX = 0;
  240. deltaY = 0;
  241. scaleX = 1;
  242. scaleY = 1;
  243. eleContainer = eleVideo.parentNode.parentNode;
  244. containerWidth = eleContainer.offsetWidth;
  245. containerHeight = eleContainer.offsetHeight;
  246. configs = JSON.parse(localStorage.vpzConfigs || "[]");
  247. eleVideo.style.left = "0px";
  248. eleVideo.style.top = "0px";
  249. eleVideo.style.width = "100%";
  250. newCSS = eleVideo.style.cssText;
  251. baseWidth = eleVideo.offsetWidth;
  252. baseHeight = eleVideo.offsetHeight;
  253. setupPanel();
  254. (new MutationObserver(function(records, obj) {
  255. if (!changing && (eleVideo.style.cssText !== newCSS)) {
  256. if (timerIdUpdateAll) clearTimeout(timerIdUpdateAll);
  257. timerIdUpdateAll = setTimeout(updateAll, 100);
  258. }
  259. })).observe(eleVideo, {
  260. attributes: true,
  261. attributeFilter: ["style"]
  262. });
  263. } else {
  264. setTimeout(init, 100);
  265. }
  266. }
  267. }
  268. init();
  269. addEventListener("spfdone", init, false);
  270.  
  271. addEventListener("resize", function() {
  272. if (!eleVideo || !window.vpzConfigs) return;
  273. setTimeout(updateAll, resizeUpdateDelay);
  274. }, false);
  275. }).toString() + ")()";
  276. document.head.appendChild(ele);