YouTube HTML5 Video Pan And Zoom

Add controls to pan and zoom HTML5 video.

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