Youtube HD

Select a youtube resolution and resize the player.

目前为 2017-03-23 提交的版本。查看 最新版本

// ==UserScript==
// @name          Youtube HD
// @author        adisib
// @namespace     namespace_adisib
// @description   Select a youtube resolution and resize the player.
// @version       2017.03.23
// @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 only resize when in theater mode.
// By default only runs youtube website, not players embeded on other websites.
// YouTube players embeded on other websites should work if enabled, but this feature is not supported.

// To enable for embedded players outside of YouTube website, do the following steps:
//   add  " @include * "  to the script metadata
//   remove  " @noframes "  from the script metadata

// 2017.03.23
// - Fixed resolution not being set when changing videos while in fullscreen
// - Probably a video loading speed improvement when buffer is flushed
// - As a side effect, should now work for embedded youtube players (but will always be disabled by default)
// - various minor improvements

(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 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;


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


    function debugLog(message)
    {
        if (!DEBUG)
        {
            return;
        }

        console.log(message);
    }


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


    // 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] || "YTHD | 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("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);

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

            debugLog("YTHD | Available Resolutions: " + ytResolutions.join(", "));

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

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

                debugLog(id);
            }
            ytPlayer.setPlaybackQuality(resolutionList[nextBestIndex]);

            debugLog("YTHD | 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.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)
    {
        debugLog("YTHD | Setting video player size");

        let heightStr, widthStr, leftStr, playlistTop;
        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";

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

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

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


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

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

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

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

        window.addEventListener("loadstart", function() {
            ytPlayer = ytPlayer || doc.getElementById("movie_player") || doc.getElementsByClassName("html5-video-player")[0];
            if (ytPlayer)
            {
                setResOnReady(ytPlayer, resolutions);
            }
        }, true );
    }

})();