YouTube Player Controls

Fit to window, set HD definition, hide annotations, replay button, pause at start or end.

Pada tanggal 20 Januari 2016. Lihat %(latest_version_link).

// ==UserScript==
// @name         YouTube Player Controls
// @namespace    YouTubePlayerControls
// @version      1.0.1
// @description  Fit to window, set HD definition, hide annotations, replay button, pause at start or end.
// @author       Costas
// @match        http://www.youtube.com/*
// @match        https://www.youtube.com/*
// @grant 		 GM_setValue
// @grant 		 GM_getValue
// @noframes
// ==/UserScript==

//==================================================================
//Userscript specific functions

var doc = document;
var win = window;

if (win.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;
}

//==================================================================
//==================================================================


//==================================================================
// Styles

var annot_style = "\
#player-api .annotation {display:none !important;}\
#player-api .video-annotations {display:none !important;}\
#player-api .ytp-cards-button {display:none !important;}\
#player-api .ima-container {display:none !important;}\
";

var style_basic = "\
/* messages */\
.ytpc_message {font:12px/15px arial,sans-serif; text-align:left; white-space:pre; float:left; clear:both; color:black; background:beige; margin:10px 0px 0px 300px; z-index:2147483647;}\
/* player options */\
#ytpc_options_popup {position:absolute; top:5px; right:0px; box-shadow:0px 0px 5px 5px lightgray; font:11px/11px arial,sans-serif; color:black; background:linear-gradient(#ffffff,#f8f8f8); padding:5px; border-radius:5px; /*z-index:2147483647;*/ z-index:2147483646;}\
#ytpc_options_popup > div  {padding:2px;}\
.ytpc_options_group > span {padding:2px;}\
.ytpc_options_text {font-weight:bold; margin-left:5px; margin-top:7px; color:black;}\
.ytpc_options_close {font:14px/14px arial,sans-serif; color:#aaaaaa; position:absolute; top:3px; right:5px; cursor:pointer;}\
.ytpc_options_close:hover {font-weight:bold; color:gray;}\
.ytpc_options_title {font:bold 13px/13px arial,sans-serif; padding:5px !important; color:black;}\
/* yt player options */\
#gridtube_title_container {position:absolute; top:5px; right:0px;}\
#ytpc_ytcontrol_container {position:relative; float:right;}\
#ytpc_ytcontrol_button {font:bold 12px/12px arial,sans-serif; position:relative; float:right; padding:2px 5px; margin-left:10px; border-radius:3px; border:gray 1px solid; color:black; background:white; background:linear-gradient(#FFFFFF,#D3D3D3); cursor:pointer; opacity:0.8;}\
#ytpc_ytcontrol_button:hover {background:linear-gradient(#EFEFEF,#C3C3C3); opacity:1;}\
#ytpc_ytcontrol_loop {position:relative; float:right; height:18px; margin-left:10px; border-radius:5px; cursor:pointer; opacity:0.8;}\
#ytpc_ytcontrol_loop[loop] {background:lightblue;}\
#ytpc_ytcontrol_loop:hover {box-shadow:0px 0px 1px 1px gray; opacity:1;}\
#ytpc_ytcontrol_loop img {height:22px; margin-top:-2px; padding:0px 3px;}\
";

//loop icon
var loopsrc = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQFJREFUeNrsluENgjAQhYEFcAPcoG6AG3QEHM0NGAG\
               coLqBG+AGeCZtcrmI3J2UGsNL3g8S6Pt6bY9m2aZNy6v0TqYa7LgQRSSIA7jnQBQRKxEgzKeXckY5T+C9IHjnw4Me4CP4Jt1ILXhcyMNcJai6BcODG254EzGcdTIc+fj1bIXHEJfeonDHGYDSV4o+QNe9RBMTA2ga0\
               VT4KgAV2fFOOh5d/yCjWA7VhHB4icIHX95VAN6FjwqASgOAwy0KlwLYiSPNasOxG1KSbthJ1s6Q0n/rdqoN5zMQvf+9Bl3975WrO/gMvmgvFYZspDpLINxKkwBgiGQAP3Hd3vSfegowAOVS0NA3eDs0AAAAAElFTkSuQmCC";


//==============================================================
//basic

function newNode(kind, id, classname, refnode, position) {

    switch (kind) {
        case "div":
            node = doc.createElement("div");
            break;

        case "span":
            node = doc.createElement("span");
            break;

        case "a":
            node = doc.createElement("a");
            break;

        case "img":
            node = doc.createElement("img");
            break;

        case "style":
            node = doc.createElement("style");
            break;

        case "option":
            node = doc.createElement("option");
            break;

        case "iframe":
            node = doc.createElement("iframe");
            break;

        case "input":
            node = doc.createElement("input");
            break;

        case "embed":
            node = doc.createElement("embed");
            break;

        case "select":
            node = doc.createElement("select");
            break;

        case "ul":
            node = doc.createElement("ul");
            break;

        case "li":
            node = doc.createElement("li");
            break;
    }

    if (node == null) return null;

    if (id != null) node.id = id;

    if (classname != null) node.className = classname;

    if (refnode != null) {
        switch (position) {
            //insert after refnode
            case 'after':
                if (refnode.nextSibling != null)
                    refnode.parentNode.insertBefore(node, refnode.nextSibling);
                else
                    refnode.parentNode.appendChild(node);
                break;

                //insert before refnode
            case 'before':
                refnode.parentNode.insertBefore(node, refnode);
                break;

                //insert as first child of refnode                  
            case 'first':
                var child = refnode.childNodes[0];
                if (child != null)
                    refnode.insertBefore(node, child);
                else
                    refnode.appendChild(node);
                break;

                //insert as last child of refnode
            case 'last':
            default:
                refnode.appendChild(node);
                break;
        }
    }

    return node;
}


function message(str) {
    var node = newNode("div", null, "ytpc_message", doc.body);
    node.textContent = str + "\n";
}


function insertStyle(str, id) {
    var styleNode = null;

    if (id != null) {
        styleNode = doc.getElementById(id);
    }

    if (styleNode == null) {
        styleNode = newNode("style", id, null, doc.head);
        styleNode.setAttribute("type", "text/css");
    }

    if (styleNode.textContent != str)
        styleNode.textContent = str;
}


function injectScript(str, src) {
    var script = doc.createElement("script");
    if (str) script.textContent = str;
    if (src) script.src = src;
    doc.body.appendChild(script);
    if (!src) doc.body.removeChild(script);
}


function xpath(outer_dom, inner_dom, query) {
    //XPathResult.ORDERED_NODE_SNAPSHOT_TYPE = 7
    return outer_dom.evaluate(query, inner_dom, null, 7, null);
}


function docsearch(query) {
    return xpath(doc, doc, query);
}


function innersearch(inner, query) {
    return xpath(doc, inner, query);
}


function filter(str, w, delim) {
    if (str == null) return null;

    var r = null;
    var m = str.match(RegExp("[" + delim + "]" + w + "[^" + delim + "]*"));
    if (m != null) {
        r = m[0];
        r = r.replace(RegExp("[" + delim + "]" + w), "");
    }
    return r;
}


//==================================================================
//YT Player

function ytplayer_script() {
    injectScript("function f() {\
                    var a = document.getElementById('c4-player') || document.getElementById('movie_player');\
                    var b = document.getElementById('ytpc_ytplayer_state');\
                    if (a != null && b != null)\
                      if (b.getAttribute('loop') == 'true') {\
                        if (window.location.href.indexOf('list=') == -1) {\
                          if (a.getPlayerState() == 0) {\
                            a.playVideo();\
                          }\
                        }\
                        else {\
                          var d = a.getDuration();\
                          if ((d - a.getCurrentTime() <= 1) && d > 0) {\
                            a.playVideoAt(a.getPlaylistIndex());\
                          }\
                        }\
                      }\
                      else {\
                         var c = document.getElementById('ytpc_ytcontrol_container');\
                         if (!c) return;\
                         if (b.getAttribute('pause_end') == 'true' && c.getAttribute('pause_end_mark') != 'true') {\
                           var d = a.getDuration();\
                           if ((d - a.getCurrentTime() <= 1) && d > 0) {\
                             a.pauseVideo();\
                             c.setAttribute('pause_end_mark', 'true');\
                           }\
                         }\
                      }\
                  }\
                  window.setInterval(f, 1000);\
                  ");
}

//initialize script
ytplayer_script();


function ytplayer_state(attr, value) {
    var node = doc.getElementById('ytpc_ytplayer_state');
    if (!node) {
        node = newNode("div", 'ytpc_ytplayer_state', null, doc.body);
        node.style.display = "none";
    }

    if (!node) return;

    if (attr && value) node.setAttribute(attr, value);
    else if (attr) return node.getAttribute(attr);
}

function set_loop() { ytplayer_state('loop', 'true'); }
function set_noloop() { ytplayer_state('loop', 'false'); }
function set_pause_end() { ytplayer_state('pause_end', 'true'); }
function set_nopause_end() { ytplayer_state('pause_end', 'false'); }
function success_pause() { return ytplayer_state('pause_status') == 'success'; }
function success_quality() { return ytplayer_state('quality_status') == 'success'; }


function flip_loop_state() {
    var inloop = !get_pref("ytLoop");

    set_pref("ytLoop", inloop);
    inloop ? set_loop() : set_noloop();

    var node = doc.getElementById('ytpc_ytcontrol_loop');
    if (!node) return;

    if (inloop)
        node.setAttribute("loop", "true");
    else
        if (node.getAttribute("loop"))
            node.removeAttribute("loop");
}


function ytplayer_pause() {
    ytplayer_state('pause_status', 'fail');

    injectScript("var a = document.getElementById('c4-player') || document.getElementById('movie_player');\
                   if (a != null)\
                      if (a.pauseVideo != null){\
                          a.pauseVideo();\
                          var n = document.getElementById('ytpc_ytplayer_state');\
                          if (n) n.setAttribute('pause_status', 'success');\
                      }\
                  ");
}


function ytplayer_quality(def) {
    ytplayer_state('quality_status', 'fail');

    injectScript("var a = document.getElementById('c4-player') || document.getElementById('movie_player');\
                   if (a != null)\
                      if (a.setPlaybackQuality != null){\
                          a.setPlaybackQuality('" + def + "');\
                          var n = document.getElementById('ytpc_ytplayer_state');\
                          if (n) n.setAttribute('quality_status', 'success');\
                      }\
                  ");
}

//==============================================================
//preferences

//yt player preferences
init_pref("ytPause", false);
init_pref("ytPauseEnd", false);
init_pref("ytDef", "default");
init_pref("ytLoop", false);
init_pref("ytCine", false);
init_pref("ytAnnot", false);


function close_ytplayer_options() {
    var popup = doc.getElementById("ytpc_options_popup");
    if (popup) popup.parentNode.removeChild(popup);
}


function new_checkbox(prefname, str, node_kind, parent, value) {
    var div = newNode(node_kind, null, "ytpc_generic", parent);
    var input = newNode("input", null, "ytpc_generic", div);
    input.type = "checkbox";
    if (!value) {
        input.checked = get_pref(prefname);
        input.onclick = function (e) { var val = get_pref(prefname); set_pref(prefname, !val); e.target.checked = !val; };
    }
    else {
        input.value = value;
        input.checked = (get_pref(prefname) == input.value);
        input.onclick = function (e) {
            var val = get_pref(prefname); set_pref(prefname, e.target.value); e.target.checked = true;
            var other = innersearch(parent.parentNode, ".//input[@value='" + val + "']").snapshotItem(0);
            if (other)
                other.checked = false;
        };
    }
    var span = newNode("span", null, "ytpc_generic", div);
    span.textContent = str;
}


function ytplayer_options() {
    var popup = doc.getElementById("ytpc_options_popup");
    if (popup) return;

    var parent = doc.getElementById("ytpc_ytcontrol_container");
    if (!parent) return;

    var ppp = parent.parentNode.parentNode;
    popup = newNode("span", "ytpc_options_popup", null, ppp);
    ppp.style.overflow = "visible";

    var title_node = newNode("div", null, "ytpc_options_title", popup);
    title_node.textContent = "YouTube Player Controls";

    var closemark = newNode("span", null, "ytpc_options_close", popup);
    closemark.textContent = "\u274C";
    closemark.title = "close";
    closemark.onclick = close_ytplayer_options;

    new_checkbox("ytCine", "Fit to Window", "div", popup);
    new_checkbox("ytAnnot", "Hide Annotations", "div", popup);
    new_checkbox("ytPause", "Pause at Start", "div", popup);
    new_checkbox("ytPauseEnd", "Pause at End (if no Replay)", "div", popup);
    var div = newNode("div", null, "ytpc_options_text", popup);
    //default, small, medium, large, hd720, hd1080, hd1440, highres;
    div.textContent = "Definition";
    var group1 = newNode("div", null, "ytpc_options_group", popup);
    var group2 = newNode("div", null, "ytpc_options_group", popup);
    new_checkbox("ytDef", "Default", "span", group1, "default");
    new_checkbox("ytDef", "LQ 240", "span", group1, "small");
    new_checkbox("ytDef", "MQ 360", "span", group1, "medium");
    new_checkbox("ytDef", "HQ 480", "span", group1, "large");
    new_checkbox("ytDef", "HD 720", "span", group2, "hd720");
    new_checkbox("ytDef", "HD 1080", "span", group2, "hd1080");
    new_checkbox("ytDef", "HD 1440", "span", group2, "hd1440");
    new_checkbox("ytDef", "MAX", "span", group2, "highres");
}

function build_yt_control() {
    if (doc.getElementById("ytpc_ytcontrol_container")) return;

    var parent = doc.getElementById("gridtube_title_container");
    if (!parent) {
        var pp = doc.getElementById("watch7-user-header");
        if (!pp) { set_noloop(); set_nopause_end(); return; }
        parent = newNode("span", "gridtube_title_container", null, pp);
    }
    if (!parent) { set_noloop(); set_nopause_end(); return; }

    var node = newNode("span", "ytpc_ytcontrol_container", null, parent);
    if (!node) { set_noloop(); set_nopause_end(); return; }

    get_pref("ytPauseEnd") ? set_pause_end() : set_nopause_end();

    //control button
    var control = newNode("span", "ytpc_ytcontrol_button", null, node);
    control.textContent = "Ctrl";
    control.title = "YouTube Player Controls";
    control.onclick = ytplayer_options;

    //loop button
    var loop = newNode("span", "ytpc_ytcontrol_loop", null, node);
    loop.title = "Replay Video";
    loop.onclick = flip_loop_state;
    var img = newNode("img", null, null, loop);
    img.src = loopsrc;

    if (get_pref("ytLoop")) {
        loop.setAttribute("loop", "true");
        set_loop();
    }
    else
        set_noloop();
}


//==================================================================
//Theater mode

function cinema() {

    if (win.location.href.indexOf("watch?") == -1) return;

    var page = doc.getElementById("page");
    if (!page) return;

    var mast = doc.getElementById("masthead-positioner");

    if (!get_pref("ytCine")) {
        if (page.getAttribute("ytpc_cinemode")) {

            page.removeAttribute("ytpc_cinemode");

            insertStyle("", "ytpc_style_cinemode");

            if (mast) mast.style.visibility = "visible";

            var mode_button = docsearch("//*[contains(@class,'ytp-size-button') and contains(@class,'ytp-button')]").snapshotItem(0);
            if (mode_button)
                if (mode_button.title == "Theater mode")
                    if (page.classList.contains("watch-stage-mode")) {
                        page.classList.remove("watch-stage-mode");
                        page.classList.remove("watch-wide");
                        page.classList.add("watch-non-stage-mode");
                    }
        }
        return;
    }

    page.setAttribute("ytpc_cinemode", "true");

    if (page.classList.contains("watch-non-stage-mode")) {
        page.classList.remove("watch-non-stage-mode");
        page.classList.add("watch-wide");
        page.classList.add("watch-stage-mode");
    }

    var height1 = 0;

    if (mast) {
        height1 = mast.offsetHeight;
        if (doc.body.scrollTop || doc.documentElement.scrollTop)
            mast.style.visibility = "visible";
        else
            mast.style.visibility = "hidden";
    }

    //var height = win.innerHeight - height1 - height2 - 30;
    var H = doc.body.clientHeight;
    var W = doc.body.clientWidth;
    //var height = H - height1;
    var height = H;
    var width = (height * 16) / 9;
    if (width > W) {
        width = W;
        var height = (width * 9) / 16;
        if (H >= height + height1 && height1 > 0) {
            if (mast) mast.style.visibility = "visible";
            height1 = 0;
        }
    }
    var left = (doc.body.clientWidth - width) / 2;

    height = Math.round(height);
    width = Math.round(width);
    left = Math.round(left);

    var h2 = 0;
    var pl = doc.getElementById("placeholder-playlist");
    if (pl)
        if (pl.style.display != "none" && !pl.classList.contains("hid"))
            h2 = (height1 > 0 ? height1 + 10 : 0);

    var l = 0;
    var ybot = docsearch("//*[contains(@class,'ytp-chrome-bottom')]").snapshotItem(0);
    if (ybot)
        l = Math.round((width - ybot.offsetWidth) / 2);

    insertStyle(".watch-stage-mode #placeholder-player {overflow:hidden !important;}\
                 .watch-stage-mode #theater-background {z-index:1 !important;}\
                 .watch-stage-mode .player-height {height: " + height + "px !important; margin-top:-" + height1 + "px !important;}\
                 .watch-stage-mode .player-width {width: " + width + "px !important; left: " + left + "px !important; margin-left:0px !important;}\
                 .watch-stage-mode .html5-video-content {height: " + height + "px !important; width: " + width + "px !important;}\
                 .watch-stage-mode .html5-main-video {height: " + height + "px !important; width: " + width + "px !important; left:0px !important; top:0px !important;}\
                 .watch-stage-mode .ytp-chrome-bottom {left:" + l + "px !important;}\
                 .watch-stage-mode .ytp-tooltip.ytp-bottom {margin-left:" + (l - 12).toString() + "px !important;}\
                 .watch-stage-mode .ytp-storyboard {margin-left:" + (l - 12).toString() + "px !important;}\
                 .watch-stage-mode .ytp-iv-video-content {height: " + height + "px !important; width: " + width + "px !important;}\
                 .watch-stage-mode .video-ads .ad-container {height: " + height + "px !important; width: " + width + "px !important;}\
                 .watch-stage-mode .webgl canvas {height: " + height + "px !important; width: " + width + "px !important;}\
                 .watch-stage-mode #player-playlist .watch-playlist {top: " + (height + 10).toString() + "px !important; transform:translateY(0px) !important; height:auto !important;}\
                 .watch-stage-mode #watch7-sidebar-contents {margin-top: " + h2 + "px !important;}",
                 "ytpc_style_cinemode");
}

function annotation() {
    insertStyle(get_pref("ytAnnot") ? annot_style : "", "ytpc_style_annotations");
}


//==================================================================
// Main

insertStyle(style_basic, "ytpc_style_basic");

//things to do at start of page
var pause_count = 0;
var def_count = 0;

function yt_start(count) {
    if (count >= 20) return;
    if (win.location.href.indexOf("watch?") == -1) { set_noloop(); set_nopause_end(); return; }

    build_yt_control();

    if (count == 0) {
        pause_count = 0;
        def_count = 0;
        //message("count 0 - " + win.location.href);
    }

    if (get_pref('ytPause') && pause_count <= 1) {
        ytplayer_pause();
        if (success_pause()) pause_count++;
    }

    var def = get_pref('ytDef');
    if (def != 'default' && def_count <= 1) {
        ytplayer_quality(def);
        if (success_quality()) def_count++;
    }
}


var old_addr = win.location.href;
var nochanges_count = -1;
var start_count = -1;

win.addEventListener("focus", function () { nochanges_count = -1; }, false);
win.addEventListener("blur", function () { nochanges_count = -1; }, false);
win.addEventListener("resize", function () { nochanges_count = -1; cinema(); }, false);
win.addEventListener("scroll", function () { nochanges_count = -1; cinema(); }, false);
win.addEventListener("click", function () { nochanges_count = -1; }, false);

//main routine
function check_changes() {
    if (old_addr == win.location.href) {
        nochanges_count++;
        start_count++;
    }
    else {
        nochanges_count = 0;
        start_count = 0;
        old_addr = win.location.href;
        //message("new addr");
    }

    if (nochanges_count >= 20) return;

    yt_start(start_count);
    cinema();
    annotation();
}

win.setInterval(check_changes, 1000);
check_changes();