YouTube HTML5 Video Pan And Zoom

Add controls to pan and zoom HTML5 video.

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

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