YouTube HTML5 Video Pan And Zoom

Add controls to pan and zoom HTML5 video.

当前为 2017-02-18 提交的版本,查看 最新版本

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