YouTube Sizer

Make YouTube Player 480px Size

目前為 2022-08-30 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

/*
    Resize the 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.34
// @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 = "#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 = "#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.previousSibling);

        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");
    }
}