- // ==UserScript==
- // @name YouTube HTML5 Video Pan And Zoom
- // @namespace YouTubeHTML5VideoPanAndZoom
- // @version 1.1.9
- // @license AGPLv3
- // @author jcunews
- // @description Add controls to pan and zoom HTML5 video.
- // @include https://www.youtube.com/watch*
- // @grant none
- // ==/UserScript==
-
- (() => {
- var to = {createHTML: s => s, createScript: s => s}, tp = window.trustedTypes?.createPolicy ? trustedTypes.createPolicy("", to) : to;
- var html = s => tp.createHTML(s), script = s => tp.createScript(s);
- var ele = document.createElement("SCRIPT");
- ele.text = script("(" + (function() {
- var resizeUpdateDelay = 300;
- var eleVideo, baseWidth, baseHeight, posX, posY, deltaX, deltaY, scaleX, scaleY;
- var eleContainer, containerWidth, containerHeight, configs;
- var changing = false, timerIdChange = 0, timerIdUpdateAll = 0;
- var to = {createHTML: s => s, createScript: s => s}, tp = window.trustedTypes?.createPolicy ? trustedTypes.createPolicy("", to) : to;
- var html = s => tp.createHTML(s), script = s => tp.createScript(s);
-
- function doneChange() {
- changing = false;
- clearTimeout(timerIdChange);
- timerIdChange = 0;
- }
-
- function doChange() {
- changing = true;
- if (timerIdChange) clearTimeout(timerIdChange);
- timerIdChange = setTimeout(doneChange, 100);
- }
-
- function setPos(dx, dy) {
- var rw = 1, rh = 1;
- if (document.fullscreen) {
- rw = screen.width / eleContainer.offsetWidth;
- rh = screen.height / eleContainer.offsetHeight;
- }
- if (dx !== undefined) {
- deltaX += dx;
- deltaY += dy;
- } else {
- posX = 0;
- posY = 0;
- deltaX = 0;
- deltaY = 0;
- }
- doChange();
- eleVideo.style.left = ((posX + deltaX) * rw) + "px";
- eleVideo.style.top = ((posY + deltaY) * rh) + "px";
- eleVideo.style.width = "100%";
- }
-
- function setSize(dx, dy) {
- var rw = 1, rh = 1;
- if (document.fullscreen) {
- rw = screen.width / eleContainer.offsetWidth;
- rh = screen.height / eleContainer.offsetHeight;
- }
- if (dx !== undefined) {
- scaleX += dx;
- scaleY += dy;
- } else {
- scaleX = 1;
- scaleY = 1;
- }
- doChange();
- eleVideo.style.MozTransform = eleVideo.style.WebkitTransform =
- "scaleX(" + (scaleX*rw).toFixed(2) + ") scaleY(" + (scaleY*rh).toFixed(2) + ")";
- }
-
- function updateAll() {
- var rw = 1, rh = 1, px = posX + deltaX, py = posY + deltaY;
- if (document.fullscreen) {
- rw = screen.width / eleContainer.offsetWidth;
- rh = screen.height / eleContainer.offsetHeight;
- }
- doChange();
- eleVideo.style.left = (px * rw).toFixed(0) + "px";
- eleVideo.style.top = (py * rh).toFixed(0) + "px";
- eleVideo.style.width = "100%";
- eleVideo.style.MozTransform = eleVideo.style.WebkitTransform =
- "scaleX(" + (scaleX*rw).toFixed(2) + ") scaleY(" + (scaleY*rh).toFixed(2) + ")";
- vpzConfigs.style.top = document.fullscreen ? "-3px" : "";
- clearTimeout(timerIdUpdateAll);
- timerIdUpdateAll = 0;
- }
-
- function delayedUpdateAll() {
- if (timerIdUpdateAll) clearTimeout(timerIdUpdateAll);
- timerIdUpdateAll = setTimeout(updateAll, 100);
- }
-
- function setup() {
- var vpzPanel = window.vpzPanel;
- if (!vpzPanel) {
- vpzPanel = document.createElement("DIV");
- vpzPanel.id = "vpzPanel";
- vpzPanel.innerHTML = html(`<style>
- #vpzPanel{position:relative;float:left;margin:10px 0 0 20px;white-space:nowrap}
- #vpzPanel button{vertical-align:top;border:none;border-radius:3px;padding:0;width:18px;height:18px;line-height:0;font-size:15px;font-weight:bold;cursor:pointer}
- #vpzPanel button:hover{background:#bdb}
- #vpzMoveLeft{margin-left:0}
- #vpzMoveL,#vpzShrink,#vpzShrinkH,#vpzShrinkV,#vpzConfig{margin-left:10px!important}
- #vpzCfgContainer{display:none;position:absolute;z-index:99;right:0;bottom:55px;padding:5px;line-height:normal;background:#555}
- #vpzCfgContainer button{height:21px;padding:0 5px}
- #vpzConfigs{position:relative}
- #vpzConfigs~button{width:auto}
- </style>
- <button id="vpzReset" class="yt-uix-button-default" title="Reset">0</button>
- <button id="vpzMoveL" class="yt-uix-button-default" title="Move Left">←</button>
- <button id="vpzMoveU" class="yt-uix-button-default" title="Move Up">↑</button>
- <button id="vpzMoveD" class="yt-uix-button-default" title="Move Down">↓</button>
- <button id="vpzMoveR" class="yt-uix-button-default" title="Move Right">→</button>
- <button id="vpzShrink" class="yt-uix-button-default" title="Shrink">↙</button>
- <button id="vpzExpand" class="yt-uix-button-default" title="Expand">↗</button>
- <button id="vpzShrinkH" class="yt-uix-button-default" title="Shrink Horizontal">⇇</button>
- <button id="vpzExpandH" class="yt-uix-button-default" title="Expand Horizontal">⇉</button>
- <button id="vpzShrinkV" class="yt-uix-button-default" title="Shrink Vertical">⇊</button>
- <button id="vpzExpandV" class="yt-uix-button-default" title="Expand Vertical">⇈</button>
- <button id="vpzConfig" class="yt-uix-button-default" title="Show/Hide Profiles Panel">P</button>
- <div id="vpzCfgContainer">
- Configs: <select id="vpzConfigs"></select>
- <button id="vpzSaveCfg" class="yt-uix-button-default">Save</button>
- <button id="vpzLoadCfg" class="yt-uix-button-default">Load</button>
- <button id="vpzDelCfg" class="yt-uix-button-default">Delete</button>
- </div>`);
- var a = window["movie_player"].querySelector(".ytp-chrome-controls .ytp-right-controls");
- a.parentNode.insertBefore(vpzPanel, a);
-
- vpzReset.onclick = function() {
- setPos();
- setSize();
- };
-
- vpzMoveL.onclick = function() {
- setPos(-8, 0);
- };
- vpzMoveU.onclick = function() {
- setPos(0, -8);
- };
- vpzMoveD.onclick = function() {
- setPos(0, 8);
- };
- vpzMoveR.onclick = function() {
- setPos(8, 0);
- };
-
- vpzShrink.onclick = function() {
- setSize(-0.01, -0.01);
- };
- vpzExpand.onclick = function() {
- setSize(0.01, 0.01);
- };
-
- vpzShrinkH.onclick = function() {
- setSize(-0.01, 0);
- };
- vpzExpandH.onclick = function() {
- setSize(0.01, 0);
- };
-
- vpzShrinkV.onclick = function() {
- setSize(0, -0.01);
- };
- vpzExpandV.onclick = function() {
- setSize(0, 0.01);
- };
-
- vpzConfig.onclick = function() {
- vpzCfgContainer.style.display = vpzCfgContainer.style.display ? "" : "block";
- };
-
- var i, opt;
- for (i = 0; i < configs.length; i++) {
- opt = document.createElement("OPTION");
- opt.value = i;
- opt.textContent = configs[i].name;
- vpzConfigs.appendChild(opt);
- }
-
- function configIndex(cfgName) {
- for (var i = configs.length-1; i >= 0; i--) {
- if (configs[i].name === cfgName) {
- return i;
- break;
- }
- }
- return -1;
- }
- function optionIndex(idx) {
- for (var i = configs.length-1; i >= 0; i--) {
- if (vpz.options[i].value == idx) {
- return i;
- break;
- }
- }
- return -1;
- }
-
- vpzSaveCfg.onclick = function() {
- var cfgName, idx, i, opt;
- if (vpzConfigs.selectedIndex >= 0) {
- cfgName = vpzConfigs.selectedOptions[0].textContent;
- } else {
- cfgName = "";
- }
- cfgName = prompt("Enter configuration name.", cfgName);
- if (cfgName === null) return;
- cfgName = cfgName.trim();
- if (!cfgName) return;
- idx = configIndex(cfgName);
- if (idx >= 0) {
- if (!confirm("Replace existing configuration?")) return;
- vpzConfigs.options[optionIndex(idx)].textContent = cfgName;
- } else {
- idx = configs.length;
- opt = document.createElement("OPTION");
- opt.value = idx;
- opt.textContent = cfgName;
- vpzConfigs.appendChild(opt);
- vpzConfigs.selectedIndex = idx;
- }
- configs.splice(idx, 1, {
- name: cfgName,
- data: [deltaX, deltaY, scaleX, scaleY]
- });
- localStorage.vpzConfigs = JSON.stringify(configs);
- };
- vpzLoadCfg.onclick = function() {
- var idx;
- if (vpzConfigs.selectedIndex < 0) return;
- idx = parseInt(vpzConfigs.selectedOptions[0].value);
- setPos();
- setPos(configs[idx].data[0], configs[idx].data[1]);
- scaleX = 0; scaleY = 0;
- setSize(configs[idx].data[2], configs[idx].data[3]);
- };
- vpzDelCfg.onclick = function() {
- if ((vpzConfigs.selectedIndex < 0) || !confirm("Delete selected configuration?")) return;
- configs.splice(vpzConfigs.selectedOptions[0].value, 1);
- localStorage.vpzConfigs = JSON.stringify(configs);
- vpzConfigs.removeChild(vpzConfigs.selectedOptions[0]);
- };
-
- }
- }
-
- function init() {
- eleVideo = document.querySelector(".html5-main-video");
- if (eleVideo) {
- baseWidth = eleVideo.offsetWidth;
- baseHeight = eleVideo.offsetHeight;
- posX = eleVideo.offsetLeft;
- posY = eleVideo.offsetTop;
- deltaX = 0;
- deltaY = 0;
- scaleX = 1;
- scaleY = 1;
- eleContainer = eleVideo.parentNode.parentNode;
- containerWidth = eleContainer.offsetWidth;
- containerHeight = eleContainer.offsetHeight;
- configs = JSON.parse(localStorage.vpzConfigs || "[]");
- if (eleVideo.videoHeight) {
- eleVideo.style.left = "0px";
- eleVideo.style.top = "0px";
- eleVideo.style.width = "100%";
- baseWidth = eleVideo.offsetWidth;
- baseHeight = eleVideo.offsetHeight;
- setup();
- } else {
- setTimeout(init, 100);
- }
- (new MutationObserver(function(records, obj) {
- if (!changing) delayedUpdateAll();
- })).observe(eleVideo, {
- attributes: true,
- attributeFilter: ["style"]
- });
- }
- }
- init();
- addEventListener("spfdone", init, false);
-
- addEventListener("resize", function() {
- if (!eleVideo || !window.vpzConfigs) return;
- setTimeout(updateAll, resizeUpdateDelay);
- }, false);
- }).toString() + ")()");
- document.head.appendChild(ele);
- })()