YouTube Sizer

Make YouTube Player 480px Size

目前為 2023-04-06 提交的版本,檢視 最新版本

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

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址