/*
Resize YouTube player to 480px size.
Copyright (C) 2021 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.30
// @description Make Youtube Player 480px Size
// @match https://www.youtube.com/*
// @match https://youtu.be/*
// @exclude-match https://www.youtube.com/
// @exclude-match https://www.youtube.com/tv*
// @exclude-match https://www.youtube.com/embed/*
// @exclude-match https://www.youtube.com/live_chat*
// @run-at document-end
// @grant GM_setValue
// @grant GM_getValue
// @compatible Firefox version >= 69
// @compatible Chrome version >= 64
// @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 set_pref(preference, new_value) {
GM_setValue(preference, new_value);
}
function get_pref(preference) {
return GM_getValue(preference);
}
function init_pref(preference, new_value) {
var value = get_pref(preference);
if (value == null) {
set_pref(preference, new_value);
value = new_value;
}
return value;
}
//==================================================================
init_pref("yt-resize", false);
//==================================================================
// Global Booleans
var scrub_bool = false;
var button_done = false;
var pref_done = false;
var resize_done = false;
//==================================================================
// Global Variables
var max_width = 480; // Max Width of Video
var aspect_ratio = 16/9; // Aspect Ratio
var shortcut_key = "r"; // Shortcut Key
var smaller_string = "ytd-watch-flexy[flexy_] #primary.ytd-watch-flexy:not([theater]):not([fullscreen]) {max-width: calc("+max_width+"px * "+aspect_ratio+") !important;";
var css_ytresize = ".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';
if (document.readyState == "complete" || document.readyState == "loaded" || document.readyState == "interactive") {
startMethods();
} else {
document.addEventListener("DOMContentLoaded", function() {
startMethods();
});
}
}
//==================================================================
function startMethods() {
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 (pref_done == false) {
if (get_pref("yt-resize")) {
addSmaller(smaller_string);
}
autoResizeVideo();
addCss(css_ytresize);
pref_done = 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() {
var elem = document.querySelector(".ytp-progress-bar-container > .ytp-progress-bar");
elem.addEventListener('timeupdate', function(elem) {
var event = new CustomEvent('scrubber');
elem.dispatchEvent(event);
});
elem.addEventListener('scrubber', function() {
var scrub = document.querySelector(".ytp-progress-bar > .ytp-scrubber-container");
var progress = document.querySelector(".ytp-progress-list > .ytp-hover-progress");
var numericValue = window.getComputedStyle(progress, null).getPropertyValue('left').match(/\d+/);
if (scrub) {
if (Math.floor(getComputedTranslateXY(scrub)[0]) == parseInt(numericValue[0])) {
scrub_bool = true;
}
return (scrub_bool);
} else {
return false;
}
});
}
//==================================================================
function isInViewport(video, element) {
var rect = element.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom >= video.clientHeight &&
rect.right >= video.clientWidth
);
}
function isCentered(element1, element2) {
var rect = element1.getBoundingClientRect();
var rect2 = element2.getBoundingClientRect();
var 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) {
var head = document.getElementsByTagName('head')[0];
var newCss = document.createElement('style');
newCss.type = "text/css";
newCss.setAttribute("id", "yt-css");
newCss.innerHTML = cssString;
head.appendChild(newCss);
}
function addSmaller(cssString) {
var head = document.getElementsByTagName('head')[0];
var newCss = document.createElement('style');
newCss.type = "text/css";
newCss.setAttribute("id", "small-player");
newCss.innerHTML = cssString;
head.appendChild(newCss);
}
function injectJs(link) {
var scr = document.createElement('script');
scr.type = "text/javascript";
scr.innerHTML = link;
document.getElementsByTagName('head')[0].appendChild(scr);
}
function show_resize_button_tooltip(btn, show = true) {
var bbcr = btn.getBoundingClientRect(); // Get button position
var tooltip_horiz_cen = bbcr.left + bbcr.width / 2; // Tooltip horizontal center
var tooltip_top_offset = 57; // Height above the button for the tooltip
var tooltip_top = bbcr.top + bbcr.height / 2 - tooltip_top_offset; // Tooltip top
var tooltip = document.getElementById("ytd-resize-tt");
var html_player = document.getElementsByClassName("html5-video-player")[0];
var tooltip_text_wrapper = document.createElement("div");
var tooltip_text, tooltip_width;
if (document.fullscreenElement) { // Check if Fullscreen
tooltip_top_offset = 75;
}
if (!tooltip) { // Create Tooltip if it doesn't exist.
tooltip = document.createElement("div");
tooltip_text = document.createElement("span");
tooltip.setAttribute("class", "ytp-tooltip ytp-bottom");
tooltip.setAttribute("id", "ytd-resize-tt");
tooltip.style.setProperty("position", "fixed");
tooltip_text_wrapper.setAttribute("class", "ytp-tooltip-text-wrapper");
tooltip_text.setAttribute("class", "ytp-tooltip-text");
tooltip_text.setAttribute("id", "ytd-resize-tt-text");
tooltip.appendChild(tooltip_text_wrapper);
tooltip_text_wrapper.appendChild(tooltip_text);
html_player.appendChild(tooltip);
} else { // Get Tooltip text if exists
tooltip_text = document.getElementById("ytd-resize-tt-text");
}
if (show) { // Show
tooltip.style.setProperty("top", tooltip_top + "px");
tooltip_text.innerHTML = btn.getAttribute("aria-label");
tooltip.style.removeProperty("display"); // Show the Tooltip
tooltip_width = tooltip.getBoundingClientRect().width;
tooltip.style.setProperty("left", tooltip_horiz_cen - tooltip_width / 2 + "px");
btn.removeAttribute("title");
} else { // Hide
tooltip.style.setProperty("display", "none");
tooltip_text.innerHTML = "";
btn.setAttribute("title", btn.getAttribute("aria-label"));
}
}
function createResize() {
var ytd = {
height: movie_player.clientHeight,
width: movie_player.clientWidth,
};
ytd_video.player_.setInternalSize(ytd.width, ytd.height);
return;
}
function buttonScript() {
var key_script = `
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 == "` + shortcut_key.toLowerCase() + `" || e.key == "` + shortcut_key.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 head = document.getElementsByTagName("head")[0];
var newCss = document.createElement("style");
newCss.type = "text/css";
newCss.setAttribute("id", "small-player");
newCss.innerHTML = "ytd-watch-flexy[flexy_] #primary.ytd-watch-flexy:not([theater]):not([fullscreen]) {max-width: calc(`+max_width+`px * `+aspect_ratio+`) !important;}";
head.appendChild(newCss);
ytvideo.player_.setInternalSize(ytplayer.clientWidth, ytplayer.clientHeight);
}
}
if (key_done == false) {
keyPress();
key_done = true;
}`;
injectJs(key_script);
}
/*Create Resize Button*/
function controlResize() {
if (!button_done) {
var abtn = document.querySelector("#movie_player > div.ytp-chrome-bottom > div.ytp-chrome-controls > div.ytp-right-controls");
var btn = document.createElement("button");
btn.innerHTML = '<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></button>';
btn.className += "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 (" + shortcut_key + ")");
btn.setAttribute("title", "Resize (" + shortcut_key + ")");
abtn.insertBefore(btn, abtn.lastChild);
buttonScript(); // Inject button script
/*Tooltip Event Handlers*/
btn.addEventListener("mouseover", function() {
show_resize_button_tooltip(btn, true);
});
btn.addEventListener("mouseout", function() {
show_resize_button_tooltip(btn, false);
});
btn.addEventListener("focus", function() {
show_resize_button_tooltip(btn, true);
});
btn.addEventListener("blur", function() {
show_resize_button_tooltip(btn, false);
});
button_done = true
return;
}
}
/*Viewport Observer*/
function viewObserver() {
let resizeObserver = new ResizeObserver((entries) => {
window.requestAnimationFrame(() => {
if (!Array.isArray(entries) || !entries.length) { // Check Animation Frame
return;
}
for (let entry of entries) {
if (isInViewport(video, ytd_flexy) && !isCentered(video, movie_player)) { // Check Top Viewport and Centered
if (!isInViewport(video, movie_player) || !scrub_bool) { // Check Video Player Size and Scrubber
if (entry.contentRect.height != max_width) { // Check Max Width
createResize();
}
}
}
}
});
for (let entry of entries) {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() {
// Select the node that will be observed for mutations
const targetNode = document.head;
// Options for the observer (which mutations to observe)
const config = {
attributes: true,
childList: true,
subtree: true
};
// Callback function to execute when mutations are observed
const callback = function(mutationsList, observer) {
// Use traditional 'for loops' for IE 11
for (let mutation of mutationsList) {
if (mutation.removedNodes.length >= 1) {
if (mutation.removedNodes[0].id == "small-player") {
set_pref("yt-resize", false); // Set resize to false
}
} else if (mutation.addedNodes.length >= 1) {
if (mutation.addedNodes[0].id == "small-player") {
set_pref("yt-resize", true); // Set resize to true
}
}
}
};
// Create an observer instance linked to the callback function
const observer = new MutationObserver(callback);
// Start observing the target node for configured mutations
observer.observe(targetNode, config);
}
function autoResizeVideo() {
if (outer !== "null") {
injectJs('let key_done = 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" && !resize_done) {
controlResize();
resize_done = 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");
}
}