Greasy Fork

Greasy Fork is available in English.

Youtube HD

Select a youtube resolution and resize the player.

当前为 2016-10-02 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name          Youtube HD
// @author        adisib
// @namespace     namespace_adisib
// @description   Select a youtube resolution and resize the player.
// @version       2016.10.02
// @include       http://youtube.com*
// @include       http://www.youtube.com*
// @include       https://youtube.com*
// @include       https://www.youtube.com*
// @noframes
// @grant         none
// ==/UserScript==

// Only the html5 player is supported.
// The video will not resize when not in theater mode.
// Only supports youtube website, not players embeded on other websites.

(function() {

    "use strict";

    // --- SETTINGS -------

    // Target Resolution to always set to. If not available, the next best resolution will be used.
    const changeResolution = true;
    const targetRes = "hd720";
    // Choices for targetRes are currently:
    //   "highres" | ( 8K / 4320p / QUHD  )
    //   "hd2880"  | ( 5K / 2880p /  UHD+ )
    //   "hd2160"  | ( 4K / 2160p /  UHD  )
    //   "hd1440"  | (      1440p /  QHD  )
    //   "hd1080"  | (      1080p /  FHD  )
    //   "hd720"   | (       720p /   HD  )
    //   "large"   | (       480p         )
    //   "medium"  | (       360p         )
    //   "small"   | (       240p         )
    //   "tiny"    | (       144p         )

    // If changePlayerSize is true, then the video's size will be changed on the page
    //   instead of using youtube's default (if theater mode is enabled).
    // If useCustomSize is false, then the player will be resized to try to match the target resolution.
    //   If true, then it will use the customHeight and customWidth variables.
    const changePlayerSize = false;
    const useCustomSize = false;
    const customHeight = 600, customWidth = 1280;

    // If flushBuffer is false, then the very beginning of the video may not be the desired resolution
    //   If true, then the entire video will be guaranteed to be target resolution, but there may be
    //   a small additional delay before the video starts
    const flushBuffer = true;

    // --------------------




    // --- GLOBALS --------


    const DEBUG = false;

    // Possible resolution choices (in decreasing order, i.e. highres is the best):
    const resolutions = ['highres', 'hd2880', 'hd2160', 'hd1440', 'hd1080', 'hd720', 'large', 'medium', 'small', 'tiny'];
    // youtube is always 16:9 right now, but has to be at least 480x270 for the player UI
    const heights = [4320, 2880, 2160, 1440, 1080, 720, 480, 360, 270, 270];
    const widths = [7680, 5120, 3840, 2560, 1920, 1280, 854, 640, 480, 480];

    let doc = document; let win = window;


    // --------------------


    // Attempt to set the video resolution to desired quality or the next best quality
    function setResolution(ytPlayer, resolutionList)
    {
        if (DEBUG)
        {
            console.log("YTHD | Setting Resolution...");
        }

        // Youtube doesn't return "auto" for auto, so set to make sure that auto is not set by setting
        //   even when already at target res or above, but do so without removing the buffer for this quality
        if (resolutionList.indexOf(targetRes) >= resolutionList.indexOf(ytPlayer.getPlaybackQuality()))
        {
            ytPlayer.setPlaybackQuality(targetRes);

            if (DEBUG)
            {
                console.log("YTHD | Resolution Set To: " + targetRes);
            }
        }
        else
        {
            let ytResolutions = ytPlayer.getAvailableQualityLevels();
            let nextBestIndex = resolutionList.indexOf(targetRes) || 0;
            let len = resolutionList.length;

            if (DEBUG)
            {
                console.log("YTHD | Available Resolutions: " + ytResolutions.join(", "));
            }

            while ( (ytResolutions.indexOf(resolutionList[nextBestIndex]) === -1) && nextBestIndex < (len-1) )
            {
                ++nextBestIndex;
            }

            if (flushBuffer && ytPlayer.getPlaybackQuality() !== resolutionList[nextBestIndex])
            {
                let id = getVideoIDFromPage();
                let pos = ytPlayer.getCurrentTime();
                ytPlayer.loadVideoById(id, pos, resolutionList[nextBestIndex]);
            }
            ytPlayer.setPlaybackQuality(resolutionList[nextBestIndex]);

            if (DEBUG)
            {
                console.log("YTHD | Resolution Set To: " + resolutionList[nextBestIndex]);
            }
        }

    }


    // --------------------


    // Get video ID from page div class
    function getVideoIDFromPage()
    {
        let pageClass = document.getElementById("page").className;
        let idMatch = /(?:video-)([\S]+)/;
        let id = idMatch.exec(pageClass)[1] || "ERROR: idMatch needs modification";
        
        return id;
    }


    // --------------------


    // Set resolution, but only when API is ready
    function setResOnReady(ytPlayer, resolutionList)
    {
        if (   (ytPlayer.getPlayerState === undefined)
            // || (ytPlayer.getPlayerState() === -1) // This prevents a youtube bug where the video buffer gets stuck
           )
        {
            win.setTimeout(setResOnReady, 100, ytPlayer, resolutionList);
        }
        else
        {
            setResolution(ytPlayer, resolutionList);
        }
    }


    // --------------------


    // resize the player
    function resizePlayer(ytPlayer, width, height)
    {
        if (DEBUG)
        {
            console.log("YTHD | Setting video player size");
        }

        let ythdStyle = doc.getElementById("ythdStyleSheet");
        if (!ythdStyle)
        {
            ythdStyle = doc.createElement("style");
            ythdStyle.type = "text/css";
            ythdStyle.id = "ythdStyleSheet";
            doc.head.appendChild(ythdStyle);
        }

        let heightStr, widthStr, leftStr, playlistTop;
        if (doc.getElementById("page").className.indexOf("watch-stage-mode") !== -1)
        {
            heightStr = height.toString() + "px !important";
            widthStr = width.toString() + "px !important";
            leftStr = (-width/2).toString() + "px !important";

            // TODO: Do more research on this
            playlistTop = (height - 360).toString() + "px !important";
        }
        else
        {
            heightStr = widthStr = leftStr = playlistTop = "0px";
        }

        let styleContent = ".player-height { min-height: " + heightStr + "; } \
                            .player-width { left: " + leftStr + "; min-width: " + widthStr + "; } \
                            #watch-appbar-playlist {top: " + playlistTop + "; } \
                           ";

        if (styleContent !== ythdStyle.innerHTML)
        {
            ythdStyle.innerHTML = styleContent;

            // Youtube's video player wont resize itself until interacted with so remind it to resize on video page
            if (ytPlayer)
            {
                ytPlayer.setSize(width, height);
            }
        }
    }


    // --------------------


    // Set HD and Resize if appropriate
    function runActions(width, height)
    {
        let ytPlayer = doc.getElementById("movie_player");

        if (changePlayerSize)
        {
            resizePlayer(ytPlayer, width, height);
        }

        if (!ytPlayer || win.location.href.indexOf("/watch") === -1)
        {
            return;
        }

        if (changeResolution)
        {
            setResOnReady(ytPlayer, resolutions);
        }
    }


    // --- MAIN -----------


    let width, height;
    if (useCustomSize)
    {
        height = customHeight;
        width = customWidth;
    }
    else
    {
        let mastheadHeight = parseInt(win.getComputedStyle(doc.getElementById("masthead-positioner-height-offset")).height, 10) || 50;
        let mastheadPadding = (parseInt(win.getComputedStyle(doc.getElementById("yt-masthead-container")).paddingBottom, 10) * 2) || 16;
        let heightOffset = (mastheadHeight + mastheadPadding);

        let i = resolutions.indexOf(targetRes) || 0;
        height = Math.min(heights[i], win.innerHeight - heightOffset);
        width = Math.min(widths[i], win.innerWidth);
    }

    runActions(width, height);

    // This requires a check because youtube doesn't actually load a new video page instead using ajax
    // The page div class holds the video id and stage mode state, so just check for changes on its class
    let pageDiv = doc.getElementById("page");
    if (pageDiv)
    {
        let prevClass = pageDiv.className;
        let ytVidMutationObserver = new MutationObserver( function(mutations) {
            // Fullscreen will rewrite className with same content
            if(prevClass !== pageDiv.className)
            {
                prevClass = pageDiv.className;
                runActions(width, height);
            }
        } );
        let MOInitOps = { attributes: true, attributeFilter: ["class"] };
        ytVidMutationObserver.observe(pageDiv, MOInitOps);
    }

})();