Greasy Fork

Greasy Fork is available in English.

Youtube HD

Select a youtube resolution and resize the player.

当前为 2017-11-18 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴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       2017.11.18
// @include       http://youtube.com/*
// @include       https://youtube.com/*
// @include       http://www.youtube.com/*
// @include       https://www.youtube.com/*
// @include       http://gaming.youtube.com/*
// @include       https://gaming.youtube.com/*
// @noframes
// @grant         none
// ==/UserScript==

// Only the html5 player is supported.
// The video will only resize when in theater mode on the main youtube website.
// By default only runs on youtube website, not players embeded on other websites, but there is experimental support for embeds.
// To enable experimental support for embedded players outside of YouTube website, do the following steps:
//   add  " @include * "  to the script metadata
//   remove  " @noframes "  from the script metadata

// 2017.11.18
// - Fixed Greasemonkey 4 WebExtension breaking things in Firefox 57
// - Added option to allow the script to set cookies to speed up theater mode

// 2017.08.05
// - Fix tiny mistake that broke playlist positioning when resizing

(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 = "hd1080";
    // 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 autoTheater is true, each video page opened will default to theater mode.
    // This means the video will always be resized immediately if you are changing the size.
    // NOTE: YouTube will not always allow theater mode immediately, the page must be fully loaded first.
    const autoTheater = false;

    // If flushBuffer is false, then the first second or so of the video may not always be the desired resolution.
    //   If true, then the entire video will be guaranteed to be the target resolution, but there may be
    //   a very small additional delay before the video starts if the buffer needs to be flushed.
    const flushBuffer = true;

    // Setting cookies can allow some operations to perform faster or without a delay (e.g. theater mode)
    // Some people don't like setting cookies, so this is false by default (which is the same as old behavior)
    const allowCookies = false;

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




    // --- 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, win = window;


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


    function debugLog(message)
    {
        if (DEBUG)
        {
            console.log("YTHD | " + message);
        }
    }


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


    function unwrapElement(el)
    {
        if (el && el.wrappedJSObject)
        {
            return el.wrappedJSObject;
        }
        return el;
    }


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


    // Get video ID from the currently loaded video (which might be different than currently loaded page)
    function getVideoIDFromURL(ytPlayer)
    {
        const idMatch = /(?:v=)([\w\-]+)/;
        let videoURL = ytPlayer.getVideoUrl();
        let id = idMatch.exec(videoURL)[1] || "ERROR: idMatch failed; youtube changed something";

        return id;
    }


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


    // Attempt to set the video resolution to desired quality or the next best quality
    function setResolution(ytPlayer, resolutionList)
    {
        debugLog("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);
            debugLog("Resolution Set To: " + targetRes);
            return;
        }

        const end = resolutionList.length - 1;
        let nextBestIndex = Math.max(resolutionList.indexOf(targetRes), 0);
        let ytResolutions = ytPlayer.getAvailableQualityLevels();
        debugLog("Available Resolutions: " + ytResolutions.join(", "));

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

        if (flushBuffer && ytPlayer.getPlaybackQuality() !== resolutionList[nextBestIndex])
        {
            let id = getVideoIDFromURL(ytPlayer);
            if (id.indexOf("ERROR: ") === -1)
            {
                let pos = ytPlayer.getCurrentTime();
                ytPlayer.loadVideoById(id, pos, resolutionList[nextBestIndex]);
            }

            debugLog("ID: " + id);
        }
        ytPlayer.setPlaybackQuality(resolutionList[nextBestIndex]);

        debugLog("Resolution Set To: " + resolutionList[nextBestIndex]);
    }


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


    // Set resolution, but only when API is ready (it should normally already be ready)
    function setResOnReady(ytPlayer, resolutionList)
    {
        if (ytPlayer.getPlaybackQuality === undefined)
        {
            win.setTimeout(setResOnReady, 100, ytPlayer, resolutionList);
        }
        else
        {
            setResolution(ytPlayer, resolutionList);

            let storedQuality = localStorage.getItem("yt-player-quality");
            if (!storedQuality || storedQuality.indexOf(targetRes) === -1)
            {
                let tc = Date.now(), te = tc + 2592000000;
                localStorage.setItem("yt-player-quality","{\"data\":\"" + targetRes + "\",\"expiration\":" + te + ",\"creation\":" + tc + "}");
            }
        }
    }


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


    function setTheaterMode(ytPlayer)
    {
        debugLog("Setting Theater Mode");

        if (win.location.href.indexOf("/watch") !== -1)
        {
            let page = unwrapElement(doc.getElementById("page"));
            let pageManager = unwrapElement(doc.getElementsByTagName("ytd-watch")[0]);

            if (ytPlayer && page)
            {
                // Wait until youtube has already set the page class, so it doesn't overwrite the theater mode change
                let isLoaded = doc.body.classList.contains("page-loaded");
                if (page.className.indexOf(getVideoIDFromURL(ytPlayer)) === -1 || !isLoaded)
                {
                    win.setTimeout(setTheaterMode, 250, ytPlayer);
                }
                if (isLoaded)
                {
                    page.classList.remove("watch-non-stage-mode");
                    page.classList.add("watch-stage-mode", "watch-wide");
                    win.dispatchEvent(new Event("resize"));
                }
            }
            else if (pageManager)
            {
                pageManager.setAttribute("theater", "true");
                pageManager.setAttribute("theater-requested_", "true");
                win.dispatchEvent(new Event("resize"));
            }
        }
    }


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


    // resize the player
    function resizePlayer(width, height)
    {
        debugLog("Setting video player size");

        let left, playlistTop, playlistHeight;
        left = (-width / 2);
        playlistTop = (height - 360);
        playlistHeight = (height - 100);

        let styleContent = " \
        #page.watch-stage-mode .player-height, ytd-watch[theater] #player.style-scope { min-height: " + height + "px !important; } \
        #page.watch-stage-mode .player-width, ytd-watch[theater] #player.style-scope { min-width: " + width + "px !important; } \
        #page.watch-stage-mode .player-width { left: " + left + "px !important; } \
        #page.watch-stage-mode #watch-appbar-playlist { top: " + playlistTop + "px !important; } \
        #page.watch-stage-mode #playlist-autoscroll-list { max-height: " + playlistHeight + "px !important; } \
        ";

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

        win.dispatchEvent(new Event("resize"));
    }


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


    function main()
    {
        let ytPlayer = doc.getElementById("movie_player") || doc.getElementsByClassName("html5-video-player")[0];
        let ytPlayerUnwrapped = unwrapElement(ytPlayer);

        if (autoTheater && ytPlayerUnwrapped)
        {
            if (allowCookies && doc.cookie.indexOf("wide=1") === -1)
            {
                doc.cookie = "wide=1; domain=.youtube.com";
            }

            setTheaterMode(ytPlayerUnwrapped);
        }

        if (changePlayerSize && win.location.host.indexOf("youtube.com") !== -1 && win.location.host.indexOf("gaming.") === -1)
        {
            let width, height;
            if (useCustomSize)
            {
                height = customHeight;
                width = customWidth;
            }
            else
            {
                // don't include youtube search bar as part of the space the video can try to fit in
                let heightOffsetEl = doc.getElementById("masthead-positioner-height-offset") || doc.getElementById("masthead");
                let mastheadContainerEl = doc.getElementById("yt-masthead-container") || doc.getElementById("masthead-container");
                let mastheadHeight = 50, mastheadPadding = 16;
                if (heightOffsetEl && mastheadContainerEl)
                {
                    mastheadHeight = parseInt(win.getComputedStyle(heightOffsetEl).height, 10);
                    mastheadPadding = parseInt(win.getComputedStyle(mastheadContainerEl).paddingBottom, 10) * 2;
                }

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

            resizePlayer(width, height);
        }

        if (changeResolution && ytPlayerUnwrapped)
        {
            setResOnReady(ytPlayerUnwrapped, resolutions);
        }

        if (changeResolution || autoTheater)
        {
            win.addEventListener("loadstart", function(e) {
                if (!(e.target instanceof win.HTMLMediaElement))
                {
                    return;
                }

                ytPlayer = doc.getElementById("movie_player") || doc.getElementsByClassName("html5-video-player")[0];
                ytPlayerUnwrapped = unwrapElement(ytPlayer);
                if (ytPlayerUnwrapped)
                {
                    debugLog("Loaded new video");
                    if (changeResolution)
                    {
                        setResOnReady(ytPlayerUnwrapped, resolutions);
                    }
                    if (autoTheater)
                    {
                        setTheaterMode(ytPlayerUnwrapped);
                    }
                }
            }, true );
        }

        win.removeEventListener("yt-navigate-finish", main, true );
    }

    main();
    // Youtube doesn't load the page immediately in new version so you can watch before waiting for page load
    // But we can only set resolution until the page finishes loading
    win.addEventListener("yt-navigate-finish", main, true );

})();