YouTube Player Controls

Adds features to YouTube player page: fill window with video, stretch, float video, set HD 4K 8K resolution, hide annotations and ads, click skip ads, click show more, loop, replay a video segment, pause at start/end, zoom and pan video.

Fra 21.12.2024. Se den seneste versjonen.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name        YouTube Player Controls
// @namespace   https://greasyfork.org/en/users/670188-hacker09?sort=daily_installs
// @version     1
// @description Adds features to YouTube player page: fill window with video, stretch, float video, set HD 4K 8K resolution, hide annotations and ads, click skip ads, click show more, loop, replay a video segment, pause at start/end, zoom and pan video.
// @author      hacker09
// @match       http://www.youtube.com/*
// @match       https://www.youtube.com/*
// @grant       GM_setValue
// @grant       GM_getValue
// @grant       GM_registerMenuCommand
// @grant 		  GM_unregisterMenuCommand
// @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;
}

var option_menu_command_ID = null;
var button_menu_command_ID = null;

function build_menu_commands() {
  if (option_menu_command_ID || button_menu_command_ID) return;
  if (doc.getElementById("ytcp_fromcontrolslink")) return;

  option_menu_command_ID = GM_registerMenuCommand("Options", function() {ytplayer_options(true);});

  if (get_pref("ytButton"))
    show_button_menu_command();
  else
    hide_button_menu_command();
}

function hide_button_menu_command() {
  if (get_pref("ytButton")) set_pref("ytButton", false);
  if (button_menu_command_ID) GM_unregisterMenuCommand(button_menu_command_ID);
  button_menu_command_ID = GM_registerMenuCommand("Show Button",show_button_menu_command);
  build_yt_control();
}

function show_button_menu_command() {
  if (!get_pref("ytButton")) set_pref("ytButton", true);
  if (button_menu_command_ID) GM_unregisterMenuCommand(button_menu_command_ID);
  button_menu_command_ID = GM_registerMenuCommand("Hide Button",hide_button_menu_command);
  build_yt_control();
}

function delete_menu_commands() {
  if (button_menu_command_ID) GM_unregisterMenuCommand(button_menu_command_ID);
  if (option_menu_command_ID) GM_unregisterMenuCommand(option_menu_command_ID);
  option_menu_command_ID = null;
  button_menu_command_ID = null;
}


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

var style_annotations = "\
.html5-video-player .annotation,\
.html5-video-player .video-annotations,\
.html5-video-player .ytp-cards-button,\
.html5-video-player .ytp-cards-teaser,\
.html5-video-player .iv-drawer,\
.html5-video-player .ima-container,\
.html5-video-player .ytp-paid-content-overlay,\
.html5-video-player .ytp-ad-overlay-slot,\
.html5-video-player .ytp-suggested-action {display:none !important;}\
";

var style_extra_ads = "\
#player-ads,\
#offer-module,\
ytd-compact-promoted-video-renderer,\
ytd-promoted-sparkles-web-renderer,\
*[target-id='engagement-panel-ads'] {display:none !important;}\
";

var style_basic = "\
/* messages */\
.ytpc_message {position:fixed; top:0px; left:0px; font:12px/15px arial,sans-serif; color:white !important; background-color:black !important; margin:0px; padding:10px; border-radius:3px; z-index:2147483647;}\
.ytpc_message[hide] {display:none;}\
/* options */\
#ytpc_options_popup {direction:ltr; width:240px; box-shadow:0px 0px 6px 2px gray; font-size:11px; color:black; background:white; padding:5px; border-radius:5px; user-select:none; -moz-user-select:none;}\
#ytpc_options_popup.ytpc_popup_top {position:fixed; top:0px; right:10px; z-index:1000000;}\
#ytpc_options_popup.ytpc_popup_normal {position:absolute; top:5px; z-index:10;}\
body[dir='ltr'] #ytpc_options_popup.ytpc_popup_normal {right:5px;}\
body[dir='rtl'] #ytpc_options_popup.ytpc_popup_normal {left:5px;}\
html[dark] #ytpc_options_popup {color:#e0e0e0; background:#1b1b1b;}\
#ytpc_options_popup input {margin:3px 2px -2px 5px !important;}\
.ytpc_options_group.space {margin-bottom:3px; border:1px solid lightgray; border-radius:3px;}\
html[dark] .ytpc_options_group.space {border:1px solid #606060 !important;}\
.ytpc_options_group.space *[hide] {color:steelblue; margin-left:7px;}\
.ytpc_options_group[hide] *[hide] {opacity:0.5;}\
.ytpc_options_group.column > span:first-child {display:inline-block; min-width:120px;}\
.ytpc_options_text {font-weight:500; font-size:12px; margin-left:5px; margin-top:7px;}\
.ytpc_options_close {font-size:14px; color:#ff8888; position:absolute; top:0px; right:4px; cursor:pointer;}\
.ytpc_options_close:hover {font-weight:bold; color:red;}\
.ytpc_options_title {font-weight:500; font-size:14px; padding:3px 5px !important;}\
.ytpc_float_button {margin-left: 5px; padding:0px 2px; background-color:gray; color:white !important; border-radius:2px; opacity:0.7;}\
.ytpc_float_button:hover {opacity:1;}\
/* cog button */\
#ytpc_ytcontrol_container {position:relative; float:right; z-index:700;}\
body[dir='rtl'] #ytpc_ytcontrol_container {float:left !important;}\
#ytpc_cog_container {position:absolute; top:5px;}\
body[dir='ltr'] #ytpc_cog_container {right:5px;}\
body[dir='rtl'] #ytpc_cog_container {left:5px;}\
#ytpc_ytcontrol_button {overflow:hidden; position:relative; float:right; margin:-3px -2px 0px 0px; width:24px; height:23px; cursor:pointer; opacity:0.7; background-size:100%; user-select:none; -moz-user-select:none; background-image:url('data:image/png;base64,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'); }\
#ytpc_ytcontrol_button:hover {opacity:1;}\
/* masthead */\
#content[ytpc_cinema]:not([float]) #masthead-container[ytpc_hide] {display:none !important;}\
#content[ytpc_cinema][ytpc_top]:not([float]) #page-manager {margin-top:0px !important;}\
";


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

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

  var node = doc.createElement(kind);

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

var message_timeout_ID;

function message(str, time) {
  var node = doc.getElementById("ytpc_message_node");
  if (!node)
    node = newNode("div", "ytpc_message_node", "ytpc_message", doc.body);
  node.removeAttribute("hide");
  node.textContent = str + "\n";

  if (time) {
    clearTimeout(message_timeout_ID);
    message_timeout_ID = setTimeout(function () {
      var n = doc.getElementById("ytpc_message_node");
      if (n)
        n.setAttribute("hide","true");
    },
                                    time);
  }
}


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 deleteStyle(id) {
  var styleNode = doc.getElementById(id);
  if (styleNode) styleNode.parentElement.removeChild(styleNode);
}


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 simulClick(el) {
  var clickEvent = doc.createEvent('MouseEvents');
  clickEvent.initEvent('click', true, true);
  clickEvent.artificialevent = true;
  el.dispatchEvent(clickEvent);
}


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



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

function ytplayer_script() {
  injectScript("	var time1=0;\
var time2=0;\
\
function f() {\
var a = document.getElementById('c4-player') || document.getElementById('movie_player');\
var b = document.getElementById('ytpc_ytplayer_state');\
if (a == null || b == null) return;\
\
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 {\
if (b.getAttribute('pause_end') == 'true' && b.getAttribute('pause_end_mark') != 'true') {\
var d = a.getDuration();\
if ((d - a.getCurrentTime() <= 1) && d > 0) {\
a.pauseVideo();\
b.setAttribute('pause_end_mark', 'true');\
}\
}\
}\
\
if (b.getAttribute('replay') == 'true')\
if(a.getCurrentTime() >= time2 && time2 > time1)\
a.seekTo(time1,true);\
}\
\
document.addEventListener('ReplayMark', function (e) {\
var a = document.getElementById('c4-player') || document.getElementById('movie_player');\
var b = document.getElementById('ytpc_ytplayer_state');\
switch (e.detail.pos){\
case '0': time1 = 0; time2 = 0; b.setAttribute('replay','false'); break;\
case '1': time1 = a.getCurrentTime(); break;\
case '2': time2 = a.getCurrentTime();\
if (time2 <= time1) b.setAttribute('replay','false');\
break;\
}},\
false);\
\
f();\
setInterval(f, 1000);\
");
}


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

  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 reset_pause_end_mark() { ytplayer_state('pause_end_mark', 'false'); }
function success_pause() { return ytplayer_state('pause_status') == 'success'; }
function success_quality() { return ytplayer_state('quality_status') == 'success'; }


function adjust_pause_end() {
  var islive = docsearch("//yt-view-count-renderer/*[contains(.,'watching now')]").snapshotLength > 0;
  //message("Is live: " + islive);
  //alert("Is live: " + islive);
  get_pref("ytPauseEnd") && !islive ? set_pause_end() : set_nopause_end();
  reset_pause_end_mark();
}

function adjust_loop() {
  get_pref("ytLoop") ? set_loop() : set_noloop();
}


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) {
  if (def == "off") return;

  ytplayer_state('quality_status', 'fail');

  injectScript("var a = document.getElementById('c4-player') || document.getElementById('movie_player');\
if (a != null)\
if (a.setPlaybackQualityRange != null){\
var def = '" + def + "';\
var l = a.getAvailableQualityLevels();\
if (l){\
/*alert(l);*/\
if (!l.includes(def) && l.length > 0) {\
def = l[0];\
/*alert(def);*/\
}\
}\
a.setPlaybackQualityRange(def);\
var n = document.getElementById('ytpc_ytplayer_state');\
if (n) n.setAttribute('quality_status', 'success');\
}\
");
}

var pl_mouseover = false;
var pl_mouse_events_started = false;

function pl_reset_mouseevents() {
  pl_mouseover = false;
  pl_mouse_events_started = false;
}

function pl_enable_mouseevents() {
  var node = doc.getElementById("ytd-player");
  if (!node) {
    pl_reset_mouseevents();
    return;
  }

  if (!pl_mouse_events_started) {
    node.addEventListener("mouseover", function (e) {pl_mouseover = true;});
    node.addEventListener("mouseenter", function (e) {pl_mouseover = true;});
    node.addEventListener("mousemove", function (e) {pl_mouseover = true;});
    node.addEventListener("mouseleave", function (e) {pl_mouseover = false;});
    node.addEventListener("mouseout", function (e) {pl_mouseover = false;});

    pl_mouse_events_started = true;
  }
}

doc.addEventListener('keydown', keyDownMark, false);

var key_message_timeout = 2000;

function keyDownMark(event) {
  if (!pl_mouseover || !get_pref("ytKeys")) return;

  var name = event.key;
  var code = event.code;
  //message(name);
  //message(code);
  if (code == "KeyE" || name == "Escape") {
    ytplayer_state('replay', 'false');
    doc.dispatchEvent(new CustomEvent("ReplayMark",{detail:{pos:"1"}}));
    if (code == "KeyE") message("Replay Start Time", key_message_timeout);
  }
  else if (code == "KeyR"){
    ytplayer_state('replay', 'true');
    doc.dispatchEvent(new CustomEvent("ReplayMark",{detail:{pos:"2"}}));
    message("Replay End Time", key_message_timeout);
  }
}

function reset_keyDownMark() {
  doc.dispatchEvent(new CustomEvent("ReplayMark",{detail:{pos:"0"}}));
  ytplayer_state('replay', 'false');
}


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

init_pref("ytPause", false);
init_pref("ytPauseEnd", false);
init_pref("ytDef", "off");
init_pref("ytLoop", false);
init_pref("ytCine", false);
init_pref("ytStretch", true);
init_pref("ytHide", true);
init_pref("ytAnnot", false);
init_pref("ytAds", false);
init_pref("ytAdsExtra", false);
init_pref("ytFloat", true);
init_pref("ytFSmall", true);
init_pref("ytFloatPos",1);
init_pref("ytLoad", false);
init_pref("ytKeys", false);
init_pref("ytZoom", false);
init_pref("ytButton", true);

function close_ytplayer_options(e) {
  var popup = doc.getElementById("ytpc_options_popup");
  if (!popup) return;

  if (!e) {
    popup.parentNode.removeChild(popup);
    return;
  }

  if (e.artificialevent) return;

  var p = e.target;
  for (var i = 0; i < 5; i++) {
    if (p) {
      if (p.id)
        if (p.id.search(/ytpc/) == 0) {
          e.stopPropagation();
          return;
        }
      if (p.className)
        if (p.className.search(/ytpc/) == 0) {
          e.stopPropagation();
          return;
        }
    }
    p = p.parentNode;
  }

  popup.parentNode.removeChild(popup);
}


function new_checkbox(prefname, str, node_kind, title, parent, value, func, hide1, hide2) {
  var div = newNode(node_kind, null, "ytpc_generic", parent);
  if (title) div.title = title;
  var input = newNode("input", null, "ytpc_generic", div);
  input.type = "checkbox";
  if (!value) {
    input.checked = get_pref(prefname);
    if (hide1 && !input.checked) parent.setAttribute("hide", "true");
    input.onclick = function (e) {
      var val = get_pref(prefname);
      if (hide2 && parent.getAttribute("hide")) val = !val; //no change if hidden
      set_pref(prefname, !val);
      e.target.checked = !val;
      if (hide1)
        if (!val)
          parent.removeAttribute("hide");
        else
          parent.setAttribute("hide", "true");
      if (func) func();
    };
  }
  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 != e.target)) other.checked = false;
      if (func) func();
    };
  }
  var span = newNode("span", null, "ytpc_generic", div);
  span.textContent = str;
  if (hide2) div.setAttribute("hide", "true");
}

function new_float_pos_button(title, parent){
  var span = newNode("span", null, "ytpc_float_button", parent);
  if (title) span.title = title;
  span.textContent = "pos "+ get_pref("ytFloatPos");

  span.onclick = function(e) {
    var val = get_pref("ytFloatPos")+1;
    if (val == 5) val=1;
    set_pref("ytFloatPos",val);
    this.textContent = "pos "+ get_pref("ytFloatPos");
  }
}


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

  if (top)
    popup = newNode("div", "ytpc_options_popup", "ytpc_popup_top", doc.body);
  else {
    var parent = doc.getElementById("ytpc_ytcontrol_container");
    if (!parent) return;
    popup = newNode("span", "ytpc_options_popup", "ytpc_popup_normal", parent);
  }

  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 = "\u2716";
  closemark.title = "close";
  closemark.onclick = function (e) { e.stopPropagation(); close_ytplayer_options(); }

  var groupCine = newNode("div", null, "ytpc_options_group space", popup);
  new_checkbox("ytCine", "Fill Window", "span", "fits video horizontally in window", groupCine, null, function () { resetTheaterMode(); cinema(0); }, true, false);
  new_checkbox("ytStretch", "Stretch", "span", "fills the whole viewing area", groupCine, null, function () { cinema(0); }, false, true);
  new_checkbox("ytHide", "Hide Search", "span", "auto hides the search bar on top", groupCine, null, function () { cinema(1); }, false, true);

  var groupFloat = newNode("div", null, "ytpc_options_group column", popup);
  new_checkbox("ytFloat", "Float Video", "span", "allows video to float while scrolling", groupFloat, null, function () { float(0); }, true, false);
  new_checkbox("ytFSmall", "Small Float", "span", "shows a small size video while floating", groupFloat, null, null, false, true);
  new_float_pos_button("float position\nclick to change", groupFloat);

  var groupAnnot = newNode("div", null, "ytpc_options_group column", popup);
  new_checkbox("ytAnnot", "Hide Annotations", "span", "hide the video annotations", groupAnnot, null, annotation);
  new_checkbox("ytAdsExtra", "Hide Related Ads", "span", "hide ads in the related videos section", groupAnnot, null, extra_ads);

  var groupClick = newNode("div", null, "ytpc_options_group column", popup);
  new_checkbox("ytLoad", "Click Show More", "span", "auto click \'more\' in video description", groupClick, null, function() { auto_load(10); });
  new_checkbox("ytAds", "Click Skip Ads", "span", "auto click the \'skip ads\' button", groupClick);

  var groupKeys = newNode("div", null, "ytpc_options_group column", popup);
  new_checkbox("ytKeys", "Replay Keys", "span", "Replay a video segment:\nE marks start time\nR marks end time\npress ESC or E to cancel replay", groupKeys, null, function() {reset_keyDownMark();});
  new_checkbox("ytZoom", "Pan and Zoom", "span", "Pan and Zoom in fill window mode:\nZ zoom-in, X zoom-out\nto pan use the mouse or keys\nG left, H right, Y up, B down\npress ESC or CTRL+SHIFT to reset", groupKeys, null, function() {cinema(0);});

  var groupPause = newNode("div", null, "ytpc_options_group", popup);
  new_checkbox("ytLoop", "Loop Video", "span", "replay the whole video from beginning", groupPause, null, adjust_loop);
  new_checkbox("ytPause", "Pause Start", "span", "pause the video when it starts", groupPause);
  new_checkbox("ytPauseEnd", "Pause End", "span", "pause the video when it ends", groupPause, null, adjust_pause_end);

  var div = newNode("div", null, "ytpc_options_text", popup);
  div.textContent = "Resolution";
  div.title = "set highest preferred video resolution";
  var groupDef1 = newNode("div", null, "ytpc_options_group", popup);
  var groupDef2 = newNode("div", null, "ytpc_options_group", popup);
  var groupDef3 = newNode("div", null, "ytpc_options_group", popup);
  new_checkbox("ytDef", "OFF", "span", "inactive", groupDef1, "off");
  new_checkbox("ytDef", "Auto", "span", null, groupDef1, "auto", function() {ytplayer_quality("auto");});
  new_checkbox("ytDef", "144p", "span", null, groupDef1, "tiny", function() {ytplayer_quality("tiny");});
  new_checkbox("ytDef", "240p", "span", null, groupDef1, "small", function() {ytplayer_quality("small");});
  new_checkbox("ytDef", "360p", "span", null, groupDef1, "medium", function() {ytplayer_quality("medium");});
  new_checkbox("ytDef", "480p", "span", null, groupDef2, "large", function() {ytplayer_quality("large");});
  new_checkbox("ytDef", "720p", "span", null, groupDef2, "hd720", function() {ytplayer_quality("hd720");});
  new_checkbox("ytDef", "HD 1080p", "span", null, groupDef2, "hd1080", function() {ytplayer_quality("hd1080");});
  new_checkbox("ytDef", "HD 1440p", "span", null, groupDef2, "hd1440", function() {ytplayer_quality("hd1440");});
  new_checkbox("ytDef", "4K 2160p", "span", null, groupDef3, "hd2160", function() {ytplayer_quality("hd2160");});
  new_checkbox("ytDef", "5K 2880p", "span", null, groupDef3, "hd2880", function() {ytplayer_quality("hd2880");});
  new_checkbox("ytDef", "8K 4320p", "span", null, groupDef3, "highres", function() {ytplayer_quality("highres");});
}

function build_yt_control() {
  if (get_pref("ytButton")) {
    var parent = doc.getElementById("ytpc_ytcontrol_container");
    if (parent) return;

    //button container
    //var pp = null;
    var pp = doc.getElementById("below");

    if (!pp) pp = doc.getElementById("primary-inner");

    if (!pp) return;

    parent = newNode("span", "ytpc_ytcontrol_container", null, pp, 'first');
    var node = newNode("span", "ytpc_cog_container", null, parent);
    var control = newNode("span", "ytpc_ytcontrol_button", null, node);

    control.title = "YouTube Player Controls";
    control.onclick = function() {ytplayer_options(false);};
  }
  else {
    node = doc.getElementById("ytpc_ytcontrol_container");
    if (node) node.parentElement.removeChild(node);
  }
}


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

function setTheaterMode() {
  if (docsearch("//ytd-page-manager/ytd-watch-flexy[@theater]").snapshotLength > 0) return; //already in theater mode
  var thnode = docsearch("//ytd-page-manager/ytd-watch-flexy//*[@class='ytp-chrome-controls']//*[contains(@class,'ytp-size-button')]").snapshotItem(0);
  if (thnode) simulClick(thnode);
}

function resetTheaterMode() {
  if (get_pref("ytCine")) return;

  if (docsearch("//ytd-page-manager/ytd-watch-flexy[@theater]").snapshotLength == 0) return; //already in default view
  var thnode = docsearch("//ytd-page-manager/ytd-watch-flexy//*[@class='ytp-chrome-controls']//*[contains(@class,'ytp-size-button')]").snapshotItem(0);
  if (thnode) simulClick(thnode);

  deleteStyle("ytpc_style_cinemode");
  showmast(false);
}

function showmast(movetop) {
  var mastoffset = doc.getElementById("masthead-container");
  if (mastoffset) {
    mastoffset.removeAttribute("ytpc_hide");
    if (!movetop)
      mastoffset.parentNode.removeAttribute("ytpc_top");
  }
}

function hidemast(movetop) {
  var mastoffset = doc.getElementById("masthead-container");
  if (mastoffset) {
    mastoffset.setAttribute("ytpc_hide", "");
    if (movetop)
      mastoffset.parentNode.setAttribute("ytpc_top", "");
  }
}

var search_height = 56;
var key_left_offset = 0;
var key_up_offset = 0;
var key_zoom = 1;

function reset_key_offset_zoom() {
  key_left_offset = 0;
  key_up_offset = 0;
  key_zoom = 1;
}

function cinema(start_count) {
  if (start_count == 0) {
    reset_key_offset_zoom();
  }

  //not video page
  if (win.location.href.indexOf("watch?") == -1) {
    showmast(false);
    insertStyle("", "ytpc_style_cinemode");
    return;
  }

  //video page
  if (!get_pref("ytCine")) return;

  var page = docsearch("//ytd-page-manager/ytd-watch-flexy").snapshotItem(0);
  if (!page) return;

  setTheaterMode();

  var intheater = page.getAttribute("theater") != null;
  var fullscreen = page.getAttribute("fullscreen") != null;

  if (intheater || fullscreen)
    page.parentNode.parentNode.setAttribute("ytpc_cinema", "");
  else
    page.parentNode.parentNode.removeAttribute("ytpc_cinema");

  //check at most 5 times
  if (start_count >= 5) return;

  var hide = get_pref("ytHide") || fullscreen;

  if (hide && !fullscreen) { //hide or show search bar
    hidemast(true);
    if (win.pageYOffset > 0)
      showmast(true);
  }
  else
    showmast(false);

  var H = doc.documentElement.clientHeight || doc.body.clientHeight;
  var W = doc.documentElement.clientWidth || doc.body.clientWidth;
  var view_height = H - (hide ? 0 : search_height); //visible height, adjust for search bar
  var view_width = W;
  var view_ratio = view_width / view_height;

  var pl = docsearch("//ytd-watch-flexy//*[contains(@class,'html5-main-video')]").snapshotItem(0);
  if (!pl) return;
  registerMouse(pl);

  var pwidth = Number(pl.style.width.replace(/[^\d\.\-]/g, '')); //video width
  var pheight = Number(pl.style.height.replace(/[^\d\.\-]/g, '')); //video height
  var pratio = pwidth / pheight; //video aspect ratio

  var height = 1; // actual video height in view area
  var width = 1;
  var left = 0;
  var top = 0;

  var stretch = get_pref("ytStretch"); //horizontal stretch of videos

  if (stretch) {
    if (pratio < view_ratio) {
      width = view_width;
      height = width / pratio;
      top = -(height - view_height) / 2;
    }
    else {
      height = view_height;
      width = height * pratio;
      left = -(width - view_width) / 2;
    }
  }
  else{
    if (pratio < view_ratio) {
      height = view_height;
      width = height * pratio;
      left = -(width - view_width) / 2;
    }
    else{
      width = view_width;
      height = width / pratio;
      //top = -(height - view_height) / 2;
      view_height = height;
      if (hide && (!fullscreen) && (view_height <= H - search_height))
        showmast(false);
    }
  }

  var zheight = height * key_zoom;
  var zwidth = width * key_zoom;
  var ztop = -(zheight - height) / 2;
  var zleft = -(zwidth - width) / 2;
  height = zheight;
  width = zwidth;
  top = top + ztop + key_up_offset*key_zoom;
  left = left + zleft + key_left_offset*key_zoom;

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

  insertStyle("\
ytd-watch-flexy[theater]:not([float]) #full-bleed-container {height:" + view_height + "px !important; min-height:" + view_height + "px !important; max-height:" + view_height + "px !important;}\
ytd-watch-flexy[theater]:not([float]) .html5-main-video {width:" + width + "px !important; min-width:" + width + "px !important; max-width:" + width + "px !important; height:" + height + "px !important; min-height:" + height + "px !important; max-height:" + height + "px !important; left:" + left + "px !important; top:" + top + "px !important;}\
", "ytpc_style_cinemode");
}

var key_shift_offset_value = 50;
var key_zoom_value = 0.1;

doc.addEventListener('keydown', keyDownShift, false);

function keyDownShift(event) {
  if (!pl_mouseover || !get_pref("ytCine") || !get_pref("ytZoom")) return;

  var name = event.key;
  var code = event.code;
  //message(name);
  //message(code);
  switch (code) {
    case "KeyG" : shiftPlayer(key_shift_offset_value,0); break; //shift left
    case "KeyH" : shiftPlayer(-key_shift_offset_value,0); break;//shift right
    case "KeyY" : shiftPlayer(0,key_shift_offset_value); break; //shift up
    case "KeyB" : shiftPlayer(0,-key_shift_offset_value); break; //shift down
    case "KeyZ" : ZoomIn(); break;
    case "KeyX" : ZoomOut(); break;
  }

  if (name == "Escape") shiftReset(true);

  if (event.ctrlKey) {
    //message(code);
    switch (code) {
      case "ShiftLeft" :
      case "ShiftRight" : shiftReset(); break;
    }
  }

  //event.stopImmediatePropagation();
}

function shiftPlayer(x,y) {
  key_left_offset += x;
  key_up_offset += y;
  message("Pan X=" + key_left_offset + " Y=" + key_up_offset, key_message_timeout);
  cinema(1);
}

function ZoomIn() {
  key_zoom += key_zoom_value;
  message("Zoom In " + (Math.round(key_zoom * 10) / 10).toFixed(1), key_message_timeout);
  cinema(1);
}

function ZoomOut() {
  key_zoom -= key_zoom_value;
  if (key_zoom < 1) key_zoom = 1;
  message("Zoom Out " + (Math.round(key_zoom * 10) / 10).toFixed(1), key_message_timeout);
  cinema(1);
}

function shiftReset(NOmsg) {
  reset_key_offset_zoom();
  if (!NOmsg) message("Reset Pan & Zoom X=" + key_left_offset + " Y=" + key_up_offset + " Zoom=" + key_zoom, key_message_timeout);
  cinema(1);
}


var mouseLastX = 0;
var mouseLastY = 0;
var mouseIsDown = false;
var mouseMoved = false;
var mouseRegistered = false;

function registerMouse(node) {
  if (!get_pref("ytCine") || !get_pref("ytZoom")) return;

  if (mouseRegistered) return;
  mouseRegistered = true;

  node.addEventListener("mousemove", function (e) {
    if(mouseIsDown) {
      if (get_pref("ytCine") && get_pref("ytZoom")) {
        var diffX = e.pageX - mouseLastX;
        var diffY = e.pageY - mouseLastY;
        shiftPlayer(diffX, diffY);
        if (diffX != 0 || diffY !=0 )
          mouseMoved = true;
      }
      mouseLastX = e.pageX;
      mouseLastY = e.pageY;
    }
  }, false);

  node.addEventListener("mousedown", function (e) {
    mouseIsDown = true;
    mouseMoved = false;
    mouseLastX = e.pageX;
    mouseLastY = e.pageY;
  }, false);

  node.addEventListener("mouseup", function (e) {
    mouseIsDown = false;
  }, false);

  node.addEventListener("mouseout", function (e) {
    mouseIsDown = false;
    mouseMoved = false;
  }, false);

  node.addEventListener("click", function (e) {
    if (mouseMoved) {
      //e.preventDefault();
      //e.stopPropagation();
      e.stopImmediatePropagation();
      mouseMoved = false;
    }
  }, false);
}


//==================================================================
// Float

var floatheight = 0;
var floatbot = 0;

function reset_float() {
  var page = docsearch("//ytd-page-manager/ytd-watch-flexy").snapshotItem(0);
  if (!page) return;

  if (page.getAttribute("float") != null) setTimeout(function () { win.dispatchEvent(new Event('resize')) }, 100);
  page.removeAttribute("float");
  page.parentNode.parentNode.removeAttribute("float");
  insertStyle("", "ytpc_style_float");
  floatheight = 0;
  floatbot = 0;
}


function float(start_count) {
  if (start_count == 0) reset_float();

  if (!get_pref("ytFloat")) return;
  var small = get_pref("ytFSmall");
  var flpos = get_pref("ytFloatPos");
  var cine = get_pref("ytCine");

  var page = docsearch("//ytd-page-manager/ytd-watch-flexy").snapshotItem(0);
  if (!page) return;

  var intheater = page.getAttribute("theater") != null;
  var fullscreen = page.getAttribute("fullscreen") != null;
  if (start_count == 20 && !fullscreen) return;

  var vid = intheater || fullscreen ? docsearch("//*[@id='full-bleed-container']").snapshotItem(0)
  : docsearch("//*[@id='primary-inner']/*[@id='player']").snapshotItem(0);
  if (!vid) return;

  var val = vid.getBoundingClientRect();
  var vwidth = val.right - val.left;
  var vheight = val.bottom - val.top;
  var vleft = val.left;
  var vright = val.right;

  //player dimensions for fill window float
  var W = doc.body.clientWidth || doc.documentElement.clientWidth;
  var H = doc.body.clientHeight || doc.documentElement.clientHeight;
  var height = 240;
  var width = 427;
  var left = Math.round((W - width) / 2);

  var infloat = page.getAttribute("float") != null;
  var inpltop = (docsearch("//*[@ytpc_top]").snapshotLength > 0);

  //store initial values
  if (!infloat) {
    floatheight = vheight;
    floatbot = inpltop ? vheight - search_height : vheight;
  }

  var thres = -1;

  if (intheater || fullscreen) {
    if (floatheight > 0)
      thres = inpltop || fullscreen ? floatheight - 296 : floatheight - 240;
  }
  else
    if (small) {
      if (floatheight > 0)
        thres = floatheight - 220;
    }
  else {
    if (vheight > 0)
      thres = 1;
  }

  var scrollY = win.pageYOffset;
  if (fullscreen) { //in fullscreen the regular scroll value is not correct
    var ref = doc.getElementById("content");
    if (ref) scrollY = - ref.getBoundingClientRect().top;
  }

  if (scrollY >= thres && thres > 0) {
    page.setAttribute("float", "");
    page.parentNode.parentNode.setAttribute("float", "");

    if (intheater || fullscreen)
      insertStyle("\
ytd-watch-flexy[float] #full-bleed-container {position: fixed !important; top:56px !important; z-index:1000 !important;\
height: " + height + "px !important; max-height:" + height + "px !important; min-height:" + height + "px !important;}\
ytd-watch-flexy[float] .html5-main-video {width: " + width + "px !important; height: " + height + "px !important; left: " + left + "px !important; top:0px !important; margin-left:0px !important;}\
ytd-watch-flexy[float] #columns {margin-top: " + floatbot + "px !important;}\
", "ytpc_style_float");
    else {
      var rtl = (doc.body.getAttribute('dir') == 'rtl');
      var lroff = "";
      if (small) {
        var hoff = (flpos == 1 || flpos == 4) ? W - width : 0;
        var voff = (flpos == 1 || flpos == 2) ? H - height : 56;
        lroff = rtl ? "right:" + hoff + "px !important;" : "left:" + hoff + "px !important;";

        insertStyle("\
ytd-watch-flexy[float] #player-container {position: fixed !important; top: " + voff + "px !important; " + lroff + " width: " + width + "px !important; height: " + height + "px !important; z-index:1000 !important;}\
ytd-watch-flexy[float] .html5-main-video {width: " + width + "px !important; height: " + height + "px !important;}\
", "ytpc_style_float");
      }
      else {
        lroff = rtl ? "right: " + (W - vright) + "px !important;" : "left: " + vleft + "px !important;";
        insertStyle("\
ytd-watch-flexy[float] #player-container {position: fixed !important; top:80px !important; " + lroff + " width: " + vwidth + "px !important; height: " + vheight + "px !important; z-index:1000 !important;}\
", "ytpc_style_float");
      }
    }
  }
  else
    reset_float();
}


//=================================================================
//ads & annotations

var adds_on = false;
var fs_on = false;

function skip_ads(start_count) {

  //adjust cinema after adds by forcing resize event
  if (get_pref("ytCine")) {
    if (start_count == 0) adds_on = false;
    var adds = doc.getElementsByClassName("video-ads ytp-ad-module");
    if (adds.length > 0) {
      if (adds[0].style.display != "none") {
        adds_on = true;
      }
    }
    else {
      if (adds_on) { //adds turned off
        //win.dispatchEvent(new Event('resize'));
        setTimeout(function () { win.dispatchEvent(new Event('resize')) }, 100);
      }
      adds_on = false;
    }
  }

  //fullscreen also forces resize event
  var fs = (docsearch("//ytd-page-manager/ytd-watch-flexy[@fullscreen]").snapshotLength > 0);
  if (fs != fs_on) {
    //win.dispatchEvent(new Event('resize'));
    setTimeout(function () { win.dispatchEvent(new Event('resize')) }, 100);
    //alert("fullscreen");
    float(0);
  }
  fs_on = fs;

  //main skip ads
  if (!get_pref("ytAds")) return;

  var button = doc.getElementsByClassName("ytp-ad-skip-button ytp-button");
  if (button.length == 0)
    button = doc.getElementsByClassName("ytp-ad-skip-button-modern ytp-button");
  if (button.length > 0)
    if (button[0].parentNode)
      if (button[0].parentNode.style.display != "none") {
        //message("will click");
        simulClick(button[0]);
      }
}

function extra_ads() {
  insertStyle(get_pref("ytAdsExtra") ? style_extra_ads : "", "ytpc_style_extra_ads");
}

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


//==================================================================
//Load More

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

  var button = docsearch("//div[(@id='meta') or (@id='above-the-fold')]//tp-yt-paper-button[(@id='more') or (@id='expand')]").snapshotItem(0);
  if (!button) return;

  if (start_count < 2) {
    button.removeAttribute("buttonclicked");
    return;
  }

  if (!get_pref("ytLoad")) return;
  if (button.getAttribute("buttonclicked") == "true") return;

  button.setAttribute("buttonclicked","true");
  simulClick(button);
}



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

var old_addr = win.location.href;
var nochanges_count = -1;
var start_count = -1;
//for yt_start
var pause_count = 0;
var def_count = 0;
var has_focus = false;

ytplayer_script();
insertStyle(style_basic, "ytpc_style_basic");

win.addEventListener("focus", function () { reset_nochanges(); cinema(1); float(1); }, false);
win.addEventListener("blur", function () { reset_nochanges(); cinema(1); float(1); }, false);
win.addEventListener("resize", function () { reset_nochanges(); cinema(1); float(1); }, false);
win.addEventListener("scroll", function () { reset_nochanges(); cinema(1); float(1); }, false);
win.addEventListener("click", function (e) { reset_nochanges(); cinema(1); float(1); close_ytplayer_options(e); }, false);

function reset_nochanges() {
  nochanges_count = -1;
}

function yt_start() {
  if (start_count == 0) {
    pause_count = 0;
    def_count = 0;
  }

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

  if (def_count <= 2) {
    var ytdef = get_pref('ytDef');
    if (ytdef != "off") {
      ytplayer_quality(ytdef);
      if (success_quality()) def_count++;
    }
  }
}

//main routine
function check_changes() {
  if (old_addr == win.location.href) {
    if (nochanges_count < 20) nochanges_count++;
    if (start_count < 20) start_count++;
  }
  else {
    old_addr = win.location.href;
    nochanges_count = 0;
    start_count = 0;
  }

  if (!has_focus) {
    has_focus = doc.hasFocus();
    if (has_focus) {
      nochanges_count = 0;
      start_count = 0;
    }
  }

  //no video page
  if (win.location.href.indexOf("watch?") == -1) {
    if (start_count < 20) {
      set_noloop();
      set_nopause_end();
      pl_reset_mouseevents();
      reset_keyDownMark();
      cinema(20); //for showmast
      delete_menu_commands();
    }
    return;
  }

  //video page
  skip_ads(start_count);
  float(start_count);

  if (start_count < 20) {
    if (start_count == 0) {
      close_ytplayer_options();
      adjust_loop();
      adjust_pause_end();
      reset_keyDownMark();
    }
    build_yt_control();
    yt_start();
    annotation();
    extra_ads();
    auto_load(start_count);
    pl_enable_mouseevents();
    if (start_count > 0) build_menu_commands();
  }

  if (nochanges_count < 20) {
    cinema(start_count);
  }
}

setInterval(check_changes, 1000);
check_changes();