/*
Resize the YouTube player to 480px size.
Copyright (C) 2023 Runio
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
// ==UserScript==
// @name YouTube Sizer
// @author Runio
// @namespace namespace_runio
// @version 1.42
// @description Make YouTube Player 480px Size
// @match https://www.youtube.com/*
// @match https://www.youtu.be/*
// @exclude https://www.youtube.com/tv*
// @exclude https://www.youtube.com/embed/*
// @exclude https://www.youtube.com/live_chat*
// @exclude https://www.youtube.com/shorts/*
// @run-at document-end
// @grant GM_setValue
// @grant GM_getValue
// @icon https://i.imgur.com/KJeLd60.png
// @license GPL-3.0+
// @noframes
// ==/UserScript==
"use strict";
//==================================================================
//Local Storage Functions
if (window.frameElement) throw new Error("Stopped JavaScript.");
function setPref(preference, new_value) {
GM_setValue(preference, new_value);
}
function getPref(preference) {
return GM_getValue(preference);
}
function initPref(preference, new_value) {
let value = getPref(preference);
if (value === null) {
setPref(preference, new_value);
value = new_value;
}
return value;
}
//==================================================================
initPref("yt-resize", false);
//==================================================================
// Global Booleans
var scrubBool = false;
var buttonDone = false;
var prefDone = false;
var resizeDone = false;
//==================================================================
// Global Variables
var maxWidth = 480; // Max Width of Video
var aspectRatio = 16 / 9; // Aspect Ratio
var shortcutKey = "r"; // Shortcut Key
var smallerCss = `#primary.ytd-watch-flexy:not([theater]):not([fullscreen]) {max-width: calc(${maxWidth}px * ${aspectRatio}) !important;`;
var ytresizeCss = ".ytp-big-mode .ytp-chrome-controls .ytp-resize-button {display: none !important;} .ytp-chrome-bottom {max-width: calc(100% - 24px);width: calc(100% - 24px);}";
//==================================================================
window.addEventListener("yt-navigate-start", () => {
startScript();
});
window.addEventListener("yt-page-data-updated", () => {
startScript();
});
//==================================================================
// Start Script
function startScript() {
'use strict';
const isDocumentReady = ["complete", "loaded", "interactive"].includes(document.readyState);
if (isDocumentReady) {
startMethods();
} else {
document.addEventListener("DOMContentLoaded", startMethods);
}
}
//==================================================================
function startMethods() {
// Global Video Player Variables
/* global ytd_video, movie_player, outer, video, ytd_flexy */
window.ytd_video = document.getElementById("ytd-player");
window.movie_player = document.getElementById("movie_player");
window.outer = document.getElementById("player-container-outer");
window.video = document.getElementsByTagName("video")[0];
window.ytd_flexy = document.getElementsByTagName("ytd-watch-flexy")[0];
if (!prefDone) {
if (getPref("yt-resize")) {
addCss(smallerCss, "small-player");
}
autoResizeVideo();
addCss(ytresizeCss, "yt-css");
prefDone = true;
}
}
//==================================================================
//Scrubber Event Listener
function getComputedTranslateXY(obj) {
const transArr = [];
if (!window.getComputedStyle) return;
const style = getComputedStyle(obj),
transform = style.transform || style.webkitTransform || style.mozTransform;
let mat = transform.match(/^matrix3d\((.+)\)$/);
if (mat) return parseFloat(mat[1].split(', ')[13]);
mat = transform.match(/^matrix\((.+)\)$/);
mat ? transArr.push(parseFloat(mat[1].split(', ')[4])) : 0;
mat ? transArr.push(parseFloat(mat[1].split(', ')[5])) : 0;
return transArr;
}
function scrubListener() {
let elem = document.querySelector(".ytp-progress-bar-container > .ytp-progress-bar");
elem.addEventListener("timeupdate", function(elem) {
let event = new CustomEvent('scrubber');
elem.dispatchEvent(event);
});
elem.addEventListener("scrubber", function() {
let scrub = document.querySelector(".ytp-progress-bar > .ytp-scrubber-container");
let progress = document.querySelector(".ytp-progress-list > .ytp-hover-progress");
let numericValue = window.getComputedStyle(progress, null).getPropertyValue('left').match(/\d+/);
if (scrub) {
if (Math.floor(getComputedTranslateXY(scrub)[0]) == parseInt(numericValue[0])) {
scrubBool = true;
}
return (scrubBool);
} else {
return false;
}
});
}
//==================================================================
function isInViewport(video, element) {
let rect = element.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom >= video.clientHeight &&
rect.right >= video.clientWidth
);
}
function isCentered(element1, element2) {
let rect = element1.getBoundingClientRect();
let rect2 = element2.getBoundingClientRect();
let centered = {
outer: rect.left + rect.width / 2,
inner: rect2.left + rect2.width / 2,
};
return (
Math.floor(centered.outer) == Math.floor(centered.inner)
);
}
function addCss(cssString, id) {
const css = document.createElement('style');
css.type = "text/css";
css.id = id;
css.innerHTML = cssString;
document.head.appendChild(css);
}
function injectJs(link) {
const scr = document.createElement('script');
scr.type = "text/javascript";
scr.innerHTML = link;
document.head.appendChild(scr);
}
function showResizeButtonTooltip(btn, show = true) {
let tooltipTopOffset = 62; // Height above the button for the tooltip
const buttonRect = btn.getBoundingClientRect(); // Get button position
const tooltipHorizontalCenter = buttonRect.left + buttonRect.width / 2; // Tooltip horizontal center
const tooltipTop = buttonRect.top + buttonRect.height / 2 - tooltipTopOffset; // Tooltip top
const tooltip = document.getElementById("ytd-resize-tt") || createTooltip();
const tooltipText = tooltip.querySelector('#ytd-resize-tt-text');
if (show) { // Show
tooltip.style.top = `${tooltipTop}px`;
tooltipText.textContent = btn.getAttribute("aria-label");
tooltip.style.removeProperty("display");
const tooltipWidth = tooltip.getBoundingClientRect().width;
tooltip.style.left = `${tooltipHorizontalCenter - tooltipWidth / 2}px`;
btn.removeAttribute("title");
} else { // Hide
tooltip.style.setProperty("display", "none");
tooltipText.textContent = "";
btn.setAttribute("title", btn.getAttribute("aria-label"));
}
function createTooltip() {
const htmlPlayer = document.querySelector(".html5-video-player");
const tooltip = document.createElement("div");
const tooltipTextWrapper = document.createElement("div");
const tooltipText = document.createElement("span");
tooltip.setAttribute("class", "ytp-tooltip ytp-bottom");
tooltip.setAttribute("id", "ytd-resize-tt");
tooltip.style.setProperty("position", "fixed");
tooltipTextWrapper.setAttribute("class", "ytp-tooltip-text-wrapper");
tooltipText.setAttribute("class", "ytp-tooltip-text");
tooltipText.setAttribute("id", "ytd-resize-tt-text");
tooltip.appendChild(tooltipTextWrapper);
tooltipTextWrapper.appendChild(tooltipText);
htmlPlayer.appendChild(tooltip);
return tooltip;
}
}
function createResize() {
let ytd = {
height: movie_player.clientHeight,
width: movie_player.clientWidth,
};
ytd_video.player_.setInternalSize(ytd.width, ytd.height);
return;
}
function buttonScript() {
let keyScript = `
function keyPress() {
document.addEventListener("keydown", function(e) {
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) return;
if (/^(?:input|textarea|select|button)$/i.test(e.target.tagName)) return;
if (/(?:contenteditable-root)/i.test(e.target.id)) return;
if (e.key == "` + shortcutKey.toLowerCase() + `" || e.key == "` + shortcutKey.toUpperCase() + `") {
e.preventDefault();
e.stopPropagation();
document.getElementById("ytd-player").focus();
resizeScript();
}
return;
});
document.querySelector(".ytp-right-controls > .ytp-resize-button.ytp-button").onclick = function foo() {
resizeScript();
};
}
function resizeScript() {
let splayer = document.getElementById("small-player");
let ytvideo = document.getElementById("ytd-player");
let ytplayer = document.getElementById("movie_player");
if (document.head.contains(splayer)) {
splayer.parentNode.removeChild(splayer);
ytvideo.player_.setInternalSize(ytplayer.clientWidth, ytplayer.clientHeight);
} else {
var newCss = document.createElement("style");
newCss.type = "text/css";
newCss.setAttribute("id", "small-player");
newCss.innerHTML = "#primary.ytd-watch-flexy:not([theater]):not([fullscreen]) {max-width: calc(` + maxWidth + `px * ` + aspectRatio + `) !important;}";
document.head.appendChild(newCss);
ytvideo.player_.setInternalSize(ytplayer.clientWidth, ytplayer.clientHeight);
}
}
if (!keyDone) {
keyPress();
keyDone = true;
}`;
injectJs(keyScript);
}
/*Create Resize Button*/
function controlResize() {
if (!buttonDone) {
const abtn = document.querySelector("#movie_player > div.ytp-chrome-bottom > div.ytp-chrome-controls > div.ytp-right-controls");
const btn = document.createElement("button");
const btnContent = `<svg height="100%" version="1.1" viewBox="0 0 36 36" width="100%">
<use class="ytp-svg-shadow"></use>
<path d="M25,17 L25,17 L25,17 Z M29,25 L29,10.98 C29,9.88 28.1,9 27,9 L9,9 C7.9,9 7,9.88 7,10.98 L7,25 C7,26.1 7.9,27 9,27 L27,27 C28.1,27 29,26.1 29,25 L29,25 Z M27,25.02 L9,25.02 L9,10.97 L27,10.97 L27,25.02 L27,25.02 Z" fill="#fff" fill-rule="evenodd"></path>
</svg>`;
btn.innerHTML = btnContent;
btn.classList.add("ytp-resize-button", "ytp-button");
btn.setAttribute("id", "ytp-resize-button");
btn.setAttribute("data-tooltip-target-id", "ytp-resize-button");
btn.setAttribute("aria-label", `Resize (${shortcutKey})`);
btn.setAttribute("title", `Resize (${shortcutKey})`);
abtn.insertBefore(btn, abtn.lastChild.previousSibling);
buttonScript(); // Inject Button Script
/*Tooltip Event Handlers*/
const showTooltip = (event) => {
const isMouseOver = ["mouseover", "focus"].includes(event.type);
showResizeButtonTooltip(btn, isMouseOver);
};
btn.addEventListener("mouseover", showTooltip);
btn.addEventListener("mouseout", showTooltip);
btn.addEventListener("focus", showTooltip);
btn.addEventListener("blur", showTooltip);
buttonDone = true;
}
}
/*Viewport Observer*/
function viewObserver() {
let resizeObserver = new ResizeObserver((entries) => {
window.requestAnimationFrame(() => {
if (!Array.isArray(entries) || !entries.length) { // Check Animation Frame
return;
}
entries.forEach(entry => {
const isInViewportYTD = isInViewport(video, ytd_flexy);
const isCenteredMoviePlayer = isCentered(video, movie_player);
const isInViewportMoviePlayer = isInViewport(video, movie_player);
if (isInViewportYTD && !isCenteredMoviePlayer && (!isInViewportMoviePlayer || !scrubBool)) {
const {
height
} = entry.contentRect;
if (height !== maxWidth) {
createResize();
}
}
});
});
entries.forEach(entry => console.log(`Player Size: ${entry.contentRect.height} x ${entry.contentRect.width}`));
});
// Observe The Given Element For Changes
resizeObserver.observe(video);
}
/*Saves Size Setting*/
function sizeObserver() {
const targetNode = document.head;
const config = {
attributes: true,
childList: true,
subtree: true
};
const callback = function(mutationsList, observer) {
for (let mutation of mutationsList) {
if (mutation.removedNodes.length >= 1 && mutation.removedNodes[0].id == "small-player") {
setPref("yt-resize", false); // Set Resize To False
} else if (mutation.addedNodes.length >= 1 && mutation.addedNodes[0].id == "small-player") {
setPref("yt-resize", true); // Set Rresize To True
}
}
};
const observer = new MutationObserver(callback);
observer.observe(targetNode, config);
}
function autoResizeVideo() {
if (outer !== "null") {
injectJs("let keyDone = false;"); // Global Variable
scrubListener(); // Add Scrubber Listener
sizeObserver(); // Size Observer
viewObserver(); // Resize Observer
window.addEventListener("yt-action", () => { // Adds Resize Button
if (window.location.href.indexOf("youtube.com/watch") !== -1) {
if (video !== "null" && !resizeDone) {
controlResize();
resizeDone = true;
}
}
});
video.addEventListener("canplay", () => { // Resize on video load
setTimeout(function() {
createResize();
}, 500);
});
window.addEventListener("fullscreenchange", () => {
if (!document.fullscreenElement) { // Check if leaving fullscreen
createResize();
}
});
} else {
alert("player-container-outer not found");
}
}