YouTube Perfect - Previews, mp4/flash, HQ/HD, download

By Víctor Pimentel Last update Aug 17, 2009 — Installed 41,117 times. Daily Installs: 127, 140, 100, 102, 118, 113, 96, 116, 103, 90, 131, 131, 99, 104, 104, 140, 126, 125, 128, 111, 132, 115, 113, 128, 138, 148, 186, 138, 188, 132, 140, 141

There are 20 previous versions of this script.

Add Syntax Highlighting (this will take a few seconds, probably freezing your browser while it works)

// ==UserScript==
// @name           YouTube Perfect - Previews, mp4/flash, HQ/HD, download
// @namespace      http://victorpimentel.com/
// @description    No autoplay; buttons to play HD/HQ with MP4 or Flash plugin; download.
// @include        http://*.youtube.com/watch*
// @include        http://youtube.com/watch*
// @author         Victor Pimentel <victor.pimentel@gmail.com>
// @copyright      2009 by Victor Pimentel
// @license        Public Domain
// @version        1.5
// @lastupdated    2009-08-17
// ==/UserScript==

// Wrap everything in an anonymous function because this script doesn't run
// in a wrapper when used in GreaseKit or other contexts other than Firefox
(function(){

// PREVIEWS:  true: avoid autoplay with previews.  false: allow autoplay.
var previews = true;

// EMBED:   true: use MP4 plug-in.  false: use the youtube flash player.
var embed = true;

// HD SUPPORT:  true: force HD when available.  false: never force HD.
var hdSupport = true;

// AUTO EXPAND:  true: always expand videos.  false: don't expand videos but HD.
var autoExpand = true;

// CORRECT 4:3:  true: expand 4:3 videos to fill all width.  false: don't expand.
var correct43 = true;

// AUTO LIGHTS OUT:  true: auto-trigger lights out.  false: don't.
var autoLightsOut = true;

// HIDE COMMENTS:  true: hide youtube comments.  false: show them.
var hideComments = true;

// HIDE ANNOTATIONS:  true: hide youtube annotations.  false: show them.
var hideAnnotations = true;

// BUTTON STYLE:  true: blue buttons.  false: yellow buttons.
var buttonStyle = true;

// AUTO CHECK UPDATES:  true: auto check updates.  false: ignore updates.
var autoCheckUpdates = true;

// DEBUG MODE:  true: generate logs.  false: ignore logs.
var debugMode = false;

/////////////////////////////////////////////////////////

// LOCALIZATION (Send me your translations if you want)
var lang = document.getElementsByTagName('html')[0].getAttribute('lang');

// Spanish
if (lang == 'es') {

  var playMP4 = 'Reproducir MP4';
  var playFlash = 'Reproducir Flash';
  var stop = 'Detener';
  var expand = 'Expandir';
  var contract = 'Contraer';
  var hdOn = 'HD activado';
  var hdOff = 'HD desactivado';
  var download = 'Descargar';
  var optionsMenu = 'Opciones';
  var optionsTooltip = 'Haz click para cambiar esta opción';
  var update = 'Actualizar Script';
  var previewsOption = 'Desactivar reproducción automática';
  var embedOption = 'Cambiar Flash por MP4';
  var hdSupportOption = 'HD por defecto';
  var autoExpandOption = 'Expandir todos los vídeos';
  var correct43Option = 'Corregir vídeos en 4:3';
  var autoLightsOutOption = 'Activar modo "luces fuera"';
  var hideCommentsOption = 'Ocultar comentarios';
  var hideAnnotationsOption = 'Ocultar anotaciones';
  var buttonStyleOption = 'Usar botones azules';
  var autoCheckUpdatesOption = 'Comprobar actualizaciones';
  var debugModeOption = 'Activar modo desarrollador';

// German (Thanks to Lucas Bares http://luke-b.com )
} else if (lang == 'de') {

  var playMP4 = 'MP4 abspielen';
  var playFlash = 'Flash abspielen';
  var stop = 'Stop';
  var expand = 'Vergrößern';
  var contract = 'Verkleinern';
  var hdOn = 'HD an';
  var hdOff = 'HD aus';
  var download = 'Herunterladen';
  var optionsMenu = 'Optionen';
  var optionsTooltip = 'Klicken Sie auf, um diese Option';
  var update = 'Script Aktualisieren';
  var previewsOption = 'Autoplay deaktivieren';
  var embedOption = 'Veränderung Flash für MP4';
  var hdSupportOption = 'HD Videos spielen';
  var autoExpandOption = 'Alle Videos vergrößern';
  var correct43Option = '4:3 Videos korrigieren';
  var autoLightsOutOption = 'Weniger Licht';
  var hideCommentsOption = 'Kommentare ausblenden';
  var hideAnnotationsOption = 'Anmerkungen ausblenden';
  var buttonStyleOption = 'Blaue Buttons verwenden';
  var autoCheckUpdatesOption = 'Aktualisierungen suchen';
  var debugModeOption = 'Debug Modus aktivieren';

// French (Thanks to Ceolien)
} else if (lang == 'fr') {

  var playMP4 = 'Lire en MP4';
  var playFlash = 'Lire en Flash';
  var stop = 'Arrêter';
  var expand = 'Plein écran';
  var contract = 'Réduire';
  var hdOn = 'HD activée';
  var hdOff = 'HD désactivée';
  var download = 'Télécharger';
  var optionsMenu = 'Options';
  var optionsTooltip = 'Cliquer pour activer cette option';
  var update = 'Mettre à jour le script';
  var previewsOption = 'Désactiver la lecture automatique';
  var embedOption = 'Remplacer Flash par MP4';
  var hdSupportOption = 'Activer la HD';
  var autoExpandOption = 'Toutes les vidéos en plein écran';
  var correct43Option = 'Correction des vidéos 4:3';
  var autoLightsOutOption = 'Activer le mode « lumières éteintes »';
  var hideCommentsOption = 'Masquer les commentaires';
  var hideAnnotationsOption = 'Masquer les annotations';
  var buttonStyleOption = 'Utiliser des boutons bleus';
  var autoCheckUpdatesOption = 'Rechercher des mises à jour';
  var debugModeOption = 'Activer le mode de débogage';

// English
} else {

  var playMP4 = 'Play MP4';
  var playFlash = 'Play Flash';
  var stop = 'Stop';
  var expand = 'Expand';
  var contract = 'Contract';
  var hdOn = 'HD on';
  var hdOff = 'HD off';
  var download = 'Download';
  var optionsMenu = 'Options';
  var optionsTooltip = 'Click to toggle this option';
  var update = 'Update Script';
  var previewsOption = 'Disable autoplay';
  var embedOption = 'Change Flash with MP4';
  var hdSupportOption = 'Enable HD';
  var autoExpandOption = 'Expand all videos';
  var correct43Option = 'Correct 4:3 videos';
  var autoLightsOutOption = 'Enable lights out';
  var hideCommentsOption = 'Hide comments';
  var hideAnnotationsOption = 'Hide annotations';
  var buttonStyleOption = 'Use blue buttons';
  var autoCheckUpdatesOption = 'Check for updates';
  var debugModeOption = 'Enable debug mode';

}

// Actual Window
var myWindow;

if (typeof(unsafeWindow) != "undefined") {
  myWindow = unsafeWindow;
} else {
  myWindow = window;
}

// Is the video widescreen?
var wideView = false;

if (typeof(isWidescreen) != "undefined" && isWidescreen) {
  wideView = true;
} else if (myWindow.isWidescreen) {
  wideView = true;
}

// Is the video in HD?
var hdAvailable = false;

if (typeof(isHDAvailable) != "undefined" && isHDAvailable) {
  hdAvailable = true;
} else if (myWindow.isHDAvailable) {
  hdAvailable = true;
}

/////////////////////////////////////////////////////////

// UTILITIES:  to ensure Cross-browser compatibility

// Shortcut for getElementById
function $(id) {
  return typeof id == 'string' ? document.getElementById(id) : id;
}

// Shortcut for createElement
function $E(name, attributes, content) {
  if (typeof attributes == 'string') {
    content = attributes;
    attributes = null;
  }
  var node = document.createElement(name);
  if (attributes) for (var attr in attributes) node.setAttribute(attr, attributes[attr]);
  if (content) node.innerHTML = content;
  return node;
}

// Add a style, no matter if we are in GreaseMonkey or GreaseKit
function addStyle(css) {
  if (typeof GM_addStyle == 'function') {
    GM_addStyle(css);
  } else {
    if (!styleElement) {
      var head = document.getElementsByTagName('head')[0];
      var styleElement = $E('style', { type: 'text/css' });
      head.appendChild(styleElement);
    }
    styleElement.appendChild(document.createTextNode(css));
  }
}

// Set a variable for the Youtube Player Copyright: Joe Simmons
String.prototype.setVar = function(q, v) {
  var regex = new RegExp('([\&\?])?'+q+'=[^\&\#]*', 'g');
  return regex.test(this) ? this.replace(regex, '$1'+q+'='+v) : this+'&'+q+'='+v;
}

// Add a log, no matter if we are in GreaseMonkey or GreaseKit
function log(message) {
  if (debugMode) {
    for (var i = 1; i < arguments.length; i++)
      message = message.replace('%s', arguments[i]);

    if (typeof GM_log == 'function') GM_log(message);
    else if (window.console) console.log(message);
    else if (window.opera) window.opera.postError(message);
    else alert(message);
  }
}

// Greasemonkey functions that can be implemented
if (typeof GM_getValue == 'function') {
  var getValue = GM_getValue;
  var setValue = GM_setValue;
  var xhr = GM_xmlhttpRequest;
  var firefox = true;
} else {
  var setValue = function (name, value) {
    document.cookie = [
      name, '=', escape(value), ';expires=',
      (new Date(new Date().getTime() + 365 * 1000 * 60 * 60 * 24)).toGMTString()
      ].join('');
  };
  var getValue = function (name, defaultValue) {
    var r = new RegExp(name + '=([^;]*)'), m;
    if (m = document.cookie.match(r)) {
      var dirty = unescape(m[1]);
      if (dirty == "true") {
        return true;
      } else if (dirty == "false") {
        return false
      } else {
        return dirty;
      }
    }
    return defaultValue;
  }
  var xhr = function(params) { return null }
  var firefox = false;
  autoCheckUpdates = false;
}

/////////////////////////////////////////////////////////

// SPECIFIC FUNCTIONS:  they only make sense in this context

// Generate an HTML button
function buildButton(text, id, target) {

  // Create the button (it's a normal link)
  var l = $E('a',
    { 'id': id,
      'href': target != null ? target : 'javascript:void(0);',
      'class': 'yt-button yt-button-' +
            (id == 'update' ? 'urgent' : (buttonStyle ? 'primary' : 'urgent'))
    },
    '<span>' + text + '</span>');

  // Create a container TD
  var n = $E('td', { 'id': id + '_td' });

  // Append the link to the TD
  n.appendChild(l);

  // Return the TD node
  return n;

}

// Generate the lights-out button
function buildLightsOutButton() {

  // Create the button (it's a normal link)
  var l = $E('a',
    { 'id': 'lightsout',
      'class': 'lights-off',
      'href': 'javascript:void(0);'
    });

  // Create a container TD
  var n = $E('td', { 'id': 'lightsout_td' });

  // Append the link to the TD
  n.appendChild(l);

  // Return the TD node
  return n;

}

// Generate the player
function buildPlayer(player) {

  setTimeout(
    (function() {
      // Change the dimensions, if necessary
      if (height != '385')
        player = player.replace(/(height=")385/g, "$1" + height);

      // Set the player and its height
      $('watch-player-div').innerHTML = player;
      $('watch-player-div').style.height = height + 'px';

      // Get some elements
      var flash = $('movie_player');
      var mp4 = $('mp4-player');
      var mode = $('hdplay').innerHTML;

      // If the player is the Flash version, force HQ if desired
      if (flash) {
        if (hdState || (hdSupport && !hdAvailable)) {
          changeQuality(2);
          setTimeout('toggleWidePlayer(true)', 1);
          log('Changing Flash version to HQ.');
        }
        if (mode.match(contract)) {
          // Trick to force wideview
          setTimeout('toggleWidePlayer(true)', 1);
        }
        if (correct43 && mode.match(expand)) {
          flash.style.height = height + 'px';
          flash.height = height + 'px';
        }
      // If not, it is the MP4 version, so only set its height if is HD
      } else if (hdState) {
        setTimeout('toggleWidePlayer(true)', 1);
        mp4.height = '575px';
        mp4.width = '960px';
        log('Changing MP4 version to HD.');
      // Fix it if HD is available but the user wants it disabled
      } else if (!hdState && hdAvailable) {
        mp4.src = mp4.src.replace(/fmt=22/g, 'fmt=18');
        if (mode.match(contract)) {
          // Trick to force wideview mode
          setTimeout('toggleWidePlayer(true)', 1);
          mp4.height = '575px';
          mp4.width = '960px';
        }
      } else if (mode.match(contract)) {
        // Trick to force wideview mode
        setTimeout('toggleWidePlayer(true)', 1);
        mp4.height = '575px';
        mp4.width = '960px';
      }

      // Set the start time if needed
      var starTimeRE = window.location.href.match(/#t=(.*)/);
      if (starTimeRE) {
        setTimeout((function() { myWindow.seekTo(extractSeconds(starTimeRE[1])); }), 5000);
      }

      // Enable lights out if desired
      if (autoLightsOut) lightsOut();

    }),
  10);
}

// Stop the player, show the previews
function stopPlayer() {
  // Little trick to revert the size of the video if we were in HD mode
  setTimeout('toggleWidePlayer(false)', 1);
  setTimeout(
    (function() {
      video_player.innerHTML = '';
      video_player.appendChild(play_video_link);
      $('watch-player-div').style.height = '385px';
    }),
  10);
}

// Stop the player, show the previews (very tricky)
function toggleHD() {

  // Player (flash or mp4) and play mode
  var flash = $('movie_player');
  var mp4 = $('mp4-player');
  var mode = $('hdplay').innerHTML;

  // First, we face the case when we were in HD
  if (hdState) {
    // Toggle HD state
    hdState = !hdState;

    // Change the text in the button
    $('hdplay').innerHTML = $('hdplay').innerHTML.
      replace('>' + hdOn + '<', '>' + hdOff + '<');

    // Change the quality in the flash or mp4 player
    if (flash) {
      changeQuality(1);
      setTimeout('toggleWidePlayer(false)', 1);
    } else if (mp4) {
      setTimeout('toggleWidePlayer(false)', 1);
      mp4.src = mp4.src.replace(/fmt=22/g, 'fmt=18');
      mp4.height = height + 'px';
      mp4.width = '640px';
      // Trick to force repainting in Safari
      if (!firefox) buildPlayer(mp4.parentNode.innerHTML);
    }

  // The case when we want to expand the video (only in non-HD videos)
  } else if (mode.match(expand)) {
    // Change the text in the button
    $('hdplay').innerHTML = $('hdplay').innerHTML.
      replace('>' + expand + '<', '>' + contract + '<');

    // Change the size of the flash or mp4 player
    if (flash) {
      setTimeout('toggleWidePlayer(true)', 1);
    } else if (mp4) {
      setTimeout('toggleWidePlayer(true)', 1);
      mp4.height = '575px';
      mp4.width = '960px';
    }

  // The case when we want to contract a video (only in non-HD videos)
  } else if (mode.match(contract)) {
    // Change the text in the button
    $('hdplay').innerHTML = $('hdplay').innerHTML.
      replace('>' + contract + '<', '>' + expand + '<');

    // Change the size of the flash or mp4 player
    if (flash) {
      setTimeout('toggleWidePlayer(false)', 1);
      flash.style.height = height + 'px';
      flash.height = height + 'px';
    } else if (mp4) {
      setTimeout('toggleWidePlayer(false)', 1);
      mp4.height = height + 'px';
      mp4.width = '640px';
    }

  // And finally, the case when we want HD
  } else {
    // Toggle HD state
    hdState = !hdState;

    // Change the text in the button
    $('hdplay').innerHTML = $('hdplay').innerHTML.
      replace('>' + hdOff + '<', '>' + hdOn + '<');

    // Change the quality in the flash or mp4 player
    if (flash) {
      changeQuality(2);
      setTimeout('toggleWidePlayer(true)', 1);
    } else if (mp4) {
      setTimeout('toggleWidePlayer(true)', 1);
      mp4.src = mp4.src.replace(/fmt=18/g, 'fmt=22');
      mp4.height = '575px';
      mp4.width = '960px';
      // Trick to force repainting in Safari
      if (!firefox) buildPlayer(mp4.parentNode.innerHTML);
    }
  }

}

// Change the video quality in the Flash movie
function changeQuality(quality) {

  // Flash player
  var flash = $('movie_player');

  // Change the quality in the movie
  flash.setAttribute("flashvars", flash.getAttribute("flashvars").setVar('vq', quality));

  // Reload the video
  flash.parentNode.replaceChild(flash.cloneNode(true), flash);

}

// Expand the player for 4:3 videos
function expandPlayer() {

  // Height of the player
  height = '520';

  // Change the height of MP4 and/or Flash versions
  if ($('mp4-player')) {
    $('mp4-player').height = height + 'px';
    $('watch-player-div').style.height = height + 'px';
  }
  if ($('movie_player')) {
    $('movie_player').style.height = height + 'px';
    $('movie_player').height = height + 'px';
    $('watch-player-div').style.height = height + 'px';
  }

}

// Toggle the lights
function lightsOut() {

  // Get the image link that triggers this method
  var lights = $('lightsout');

  // Get the image that cover the page (if created previously)
  var lightsImg = $('img-lights-out');

  // Image that will put a dark gray overlay
  var mediumImg = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyAQMAAAAk8RryAAAAA1BMVEUAAACnej3aAAAAAXRSTlPNpTNmawAAAA5JREFUGJVjYBgFgwkAAAGQAAHY85U/AAAAAElFTkSuQmCC';

  // Image that will put a 100% black overlay
  var outImg = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyAQMAAAAk8RryAAAAA1BMVEUCAgJ4xuoaAAAADklEQVQYlWNgGAWDCQAAAZAAAdjzlT8AAAAASUVORK5CYII=';

  // Change the link image to show lights-out mode is on
  lights.className = 'lights-on';

  // Create an special image
  var imgLightsOut = $E('img',
    { 'id': 'img-lights-out',
      'src': outImg,
      'style': 'position:absolute;top:0;left:0;width:100%;height:' +
               document.height + 'px;z-index:100;' });

  // Change the color of the MP4 player's background
  if ($('mp4-player'))
      myWindow.document.getElementById('mp4-player').SetBgColor('#040404');
//      myWindow.document.getElementById('mp4-player').SetBgColor('#323232');
  
  // When the user clicks on the overlay, we want to darken it the first time
  // and erase it the second time
  imgLightsOut.addEventListener('click', function (e) {
      if ($('img-lights-out').src != outImg) {
        $('img-lights-out').src = outImg;
        var mp4 = myWindow.document.getElementById('mp4-player');
        if (mp4)
          mp4.SetBgColor('#040404');
      } else {
        $('lightsout').className = 'lights-off';
        document.body.removeChild($('img-lights-out'));
        var mp4 = myWindow.document.getElementById('mp4-player');
        if (mp4)
          mp4.SetBgColor('#FFFFFF');
      }
    }, false);

  // Put the image on the page
  document.body.appendChild(imgLightsOut);

  // Navigate nicely to the title of the page
  location.replace(location.href.replace(location.hash, '') + '#watch-vid-title');

}

// Insert the Options menu
function insertOptionsMenu() {

  // Searching for the menubar
  var menubar = $('masthead-utility');

  // Get the first element
  var firstMenuElement = document.getElementsByClassName('utility-item')[0];

  // Create and insert the link in the menubar
  var optionsLink = $E('span', { 'id' : 'options-link', 'class': 'utility-item' });
  menubar.insertBefore(optionsLink, firstMenuElement);

  // Custom images for the options menu
  var checkedImg = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAIAAABv85FHAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAN9JREFUeNpifHTxobvhs+sMCKCpyXAdyA8WZfr68ofLYtV79/Wv39a9ft/g+nlZY0H25m5uhmt/mYAKxQQ4OHhY+AXY+HmYjlQ+fhOnEG3PwXCdASTHwPr35dk3T/4w/Ln9MHWHQH8wD8P3/0BhoBwj27fv8zweHn386/D09/3HFPgZGP6CLWZhYPjPwMcXO5fTzOyyVZrUClVmuKNAZv76wyjjpxjDwJCYK8kCFmVnZQTpY2H4t2zpI9nXHLp1Al9OPd0K1vDl0nsGK0HGRw/fX7ry/TcDGmCS1hECCDAASMhNiGDcZRIAAAAASUVORK5CYII=';
  var uncheckedImg = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAIAAABv85FHAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAHxJREFUeNpifHTxobvhs+sMGCBYlOXryx8ui1W32nD9/PMPIsjCwvz+yj2zsr8sQI6YAAcHDwsHkh5mPhaG6wxMYPY/NPP+gkkmBtyAoBwrC5ooOysj2FEM/5YtfSz7mu33L4SGL5feM1gJMj56+P7Sle+/McyT1hECCDAASe4kkicK9k8AAAAASUVORK5CYII=';

  // To select the image that apply for that option
  var checkboxStatus = { 'true' : '<img src="' + checkedImg + '" /> ',
                         'false' : '<img src="' + uncheckedImg + '" /> ' };

  // Generate an extra element we need
  var optionsLinkSpan = $E('span', { 'id' : 'options-dropdown', 'class': 'yt-menulink yt-menulink-primary',
                                     'onmouseenter' : 'this.className += " yt-menulink-primary-hover";',
                                     'onmouseleave' : 'this.className = this.className.replace(" yt-menulink-primary-hover", "");' });
  optionsLink.appendChild(optionsLinkSpan);

  // The link that will be showing always
  optionsLinkSpan.appendChild($E('a', { 'class': 'yt-menulink-btn yt-button yt-button-primary'},
                                        '<img class="master-sprite" src="http://s.ytimg.com/yt/img/pixel-vfl73.gif">' +
                                        optionsMenu));

  // These are the options links: note that a reload() is needed to apply an option
  var optionsDropdown = $E('ul', { 'id' : 'options-dropdown', 'class': 'yt-menulink-menu' });
  optionsLinkSpan.appendChild(optionsDropdown);
  optionsDropdown.appendChild($E('li', '<a id="previews-option" href="javascript:location.reload()" title="' +
                                optionsTooltip + '">' + checkboxStatus[previews] + previewsOption + '</a>'));
  optionsDropdown.appendChild($E('li', '<a id="embed-option" href="javascript:location.reload()" title="' +
                                optionsTooltip + '">' + checkboxStatus[embed] + embedOption + '</a>'));
  optionsDropdown.appendChild($E('li', '<a id="hd-support-option" href="javascript:location.reload()" title="' +
                                optionsTooltip + '">' + checkboxStatus[hdSupport] + hdSupportOption + '</a>'));
  optionsDropdown.appendChild($E('li', '<a id="auto-expand-option" href="javascript:location.reload()" title="' +
                                optionsTooltip + '">' + checkboxStatus[autoExpand] + autoExpandOption + '</a>'));
  optionsDropdown.appendChild($E('li', '<a id="correct-43-option" href="javascript:location.reload()" title="' +
                                optionsTooltip + '">' + checkboxStatus[correct43] + correct43Option + '</a>'));
  optionsDropdown.appendChild($E('li', '<a id="auto-lights-out-option" href="javascript:location.reload()" title="' +
                                optionsTooltip + '">' + checkboxStatus[autoLightsOut] + autoLightsOutOption + '</a>'));
  optionsDropdown.appendChild($E('li', '<a id="hide-comments-option" href="javascript:location.reload()" title="' +
                                optionsTooltip + '">' + checkboxStatus[hideComments] + hideCommentsOption + '</a>'));
  optionsDropdown.appendChild($E('li', '<a id="hide-annotations-option" href="javascript:location.reload()" title="' +
                                optionsTooltip + '">' + checkboxStatus[hideAnnotations] + hideAnnotationsOption + '</a>'));
  optionsDropdown.appendChild($E('li', '<a id="button-style-option" href="javascript:location.reload()" title="' +
                                optionsTooltip + '">' + checkboxStatus[buttonStyle] + buttonStyleOption + '</a>'));
  if (firefox)
  optionsDropdown.appendChild($E('li', '<a id="auto-check-updates-option" href="javascript:location.reload()" title="' +
                                optionsTooltip + '">' + checkboxStatus[autoCheckUpdates] + autoCheckUpdatesOption + '</a>'));
  optionsDropdown.appendChild($E('li', '<a id="debug-mode-option" href="javascript:location.reload()" title="' +
                                optionsTooltip + '">' + checkboxStatus[debugMode] + debugModeOption + '</a>'));

  // The actual code for the links, it is only a bunch of setValues
  $('previews-option').addEventListener('click', function(e){setValue('previews', !previews);}, false);
  $('embed-option').addEventListener('click', function(e){setValue('embed', !embed);}, false);
  $('hd-support-option').addEventListener('click', function(e){setValue('hdSupport', !hdSupport);}, false);
  $('auto-expand-option').addEventListener('click', function(e){setValue('autoExpand', !autoExpand);}, false);
  $('correct-43-option').addEventListener('click', function(e){setValue('correct43', !correct43);}, false);
  $('auto-lights-out-option').addEventListener('click', function(e){setValue('autoLightsOut', !autoLightsOut);}, false);
  $('hide-comments-option').addEventListener('click', function(e){setValue('hideComments', !hideComments);}, false);
  $('hide-annotations-option').addEventListener('click', function(e){setValue('hideAnnotations', !hideAnnotations);}, false);
  $('button-style-option').addEventListener('click', function(e){setValue('buttonStyle', !buttonStyle);}, false);
  if (firefox)
  $('auto-check-updates-option').addEventListener('click', function(e){setValue('autoCheckUpdates', !autoCheckUpdates);}, false);
  $('debug-mode-option').addEventListener('click', function(e){setValue('debugMode', !debugMode);}, false);

}

// Window's title
var lastTitle = document.title;

// Show the playback time on the Window's title
function showPlayback() {

  var mp4 = myWindow.document.getElementById('mp4-player');
  if (mp4) {
    var mp4Status = mp4.GetPluginStatus();
    if (mp4Status == 'Complete' || mp4Status == 'Playable') {
      var duration = formatTimeString(mp4.GetDuration() / mp4.GetTimeScale());
      var time = formatTimeString(mp4.GetTime() / mp4.GetTimeScale());
      document.title = time + ' / ' + duration;
    }
  } else if (document.title != lastTitle) {
    document.title = lastTitle;
  }

  // Repeat after one second...
  setTimeout(showPlayback, 1000);

}

// Simple function to format a seconds count
function formatTimeString(sec) {

  // Initialize string
  var timeString = "";

  // Calculate hour and minutes
  var min = Math.floor(sec / 60);
  var hour = Math.floor(min / 60);

  // Get the rest
  sec = Math.floor(sec - (min * 60));
  min = Math.floor(min - (hour * 60));

  // Add padding if needed
  if(min<10) min = '0' + min;
  if(sec<10) sec = '0' + sec;

  // Add hour if needed
  if(hour > 0) timeString = hour + ':';

  // Add the rest (minutes and seconds)
  timeString += min + ':' + sec;

  return timeString;

}

// Simple function to format a seconds count
function extractSeconds(timeString) {

  // Format is 00h00m00s
  // Extract the three variables
  var hour = timeString.match(/(\d*)h/);
  var min = timeString.match(/(\d*)m/);
  var sec = timeString.match(/(\d*)s/);

  // Clean everything
  if (hour) hour = parseInt(hour[1]);
  else      hour = 0;
  if (min)  min = parseInt(min[1]);
  else      min = 0;
  if (sec)  sec = parseInt(sec[1]);
  else      sec = 0;

  // Calculate seconds
  sec = hour * 60 * 60 + min * 60 + sec;

  return sec;

}

// Hijack seekTo function to also function with the MP4 Player
var oldSeekTo = myWindow.seekTo;
myWindow.seekTo = function(time) {
  setTimeout((function() {
    var mp4 = myWindow.document.getElementById('mp4-player');
    var flash = myWindow.document.getElementById('movie_player');
    if (mp4) {
      var timeScale = mp4.GetTimeScale();
      time = time * timeScale;
      mp4.SetTime(time);
      myWindow.smoothScrollIntoView(mp4,50);
    } else if (flash) {
      oldSeekTo(time);
    }
  }), 100);
};

/////////////////////////////////////////////////////////

// MAIN:  actual code to execute

// Continue only if there's a Video Player
var video_player = $('watch-player-div');

if (video_player) {

  // Styles come first so things aren't ugly while loading
  addStyle("\
    #play_btn { position: absolute; left: 0; margin-left: 5px;\
    padding: 130px 170px; opacity: 0.6; z-index: 200; }\
    #play_btn:hover { opacity: 1; cursor: pointer; }\
    #no_play_btn { display: none; }\
    #still0 { height: 370px; margin-left: 6px; }\
    div:hover > #play_btn { opacity: 1; cursor: pointer; }\
    .stilli { display: block; padding: 0 0 5px 5px; }\
    #play_video_link { display: block; margin: 0 0 0 -6px; }\
    #watch-player-div { height: 385px; width: 640px; }\
    #control_table a { margin: 15px 5px 0; }\
    #control_table td { vertical-align: top; text-align: center; }\
    #lightsout { vertical-align: middle; white-space: nowrap;\
    text-decoration: none; display: inline-block; height: 24px; width: 32px; }\
    .lights-on { background: url(/img/lights_on_32x24.gif) no-repeat 0 0; }\
    .lights-off { background: url(/img/lights_off_32x24.gif) no-repeat 0 0; }\
    #mp4-player, #movie_player { z-index: 200 !important; position: absolute; }\
    #watch-longform-buttons { display: none; }\
    .watch-wide-mode #watch-this-vid #watch-player-div {\
    padding-left: 0px !important; height: 575px !important;}\
    .watch-wide-mode #watch-this-vid #watch-player-div #movie_player {\
    width: 960px !important; height: 575px !important; }\
    #options-link { z-index: 100; }\
    #options-link a { font-weight: bold; text-decoration: none }\
    #options-link img { height: 18px; width: 18px;\ margin: 10px 5px 0 0;\
    background-position: 0 -268px }\
    #options-link a:hover img { background-position: -18px -268px }\
    #options-link li img { height: 9px; width: 9px;\ margin: 0 0 1px;\ }\
    #update_td { width: 100% }\
    #update_td * { float: right; }".
      replace(/}/g,"}\n"));

  // Get the user preferences for Greasemonkey
  // For other browsers these options have to be changed manually from above
  previews = getValue('previews', previews);
  embed = getValue('embed', embed);
  hdSupport = getValue('hdSupport', hdSupport);
  autoExpand = getValue('autoExpand', autoExpand);
  correct43 = getValue('correct43', correct43);
  autoLightsOut = getValue('autoLightsOut', autoLightsOut);
  hideComments = getValue('hideComments', hideComments);
  hideAnnotations = getValue('hideAnnotations', hideAnnotations);
  buttonStyle = getValue('buttonStyle', buttonStyle);
  autoCheckUpdates = getValue('autoCheckUpdates', autoCheckUpdates);
  debugMode = getValue('debugMode', debugMode);

  // Set the height of the player
  var height = '385';

  // Calculate the HD State
  var hdState = hdSupport && hdAvailable;

  // Remove the Channel Ad the hard way
  var ad = $('watch-channel-brand-div');
  if (ad) ad.parentNode.removeChild(ad);

  // Remove annotations
  if (hideAnnotations)
    video_player.innerHTML = video_player.innerHTML.
                               replace(/&amp;iv_module=[^&]*&/, '&').
                               replace(/&amp;iv_storage_server=[^&]*&/, '&');

  // Hide comments
  if (hideComments) {
    var com = $('watch-comment-panel');
    if (com) com.className = com.className.replace(/(^| )expanded\b/g, '');
    var res = $('watch-video-responses-children');
    if (res) res.parentNode.className = res.parentNode.className.replace(/(^| )expanded\b/g, '');
  }

  // Retrieve the video source in HQ
  var video_id = location.search.replace(/.*v=/,'').replace(/&.*/,'');
  var video_t = $('movie_player').getAttribute('flashvars').
                replace(/.*&t=/,'').replace(/&.*/,'');
  var video_src = location.protocol + '//' + location.host
                + '/get_video?video_id=' + video_id + '&t=' + video_t + '&fmt=18';

  // Set the HD source if possible
  if (hdState)
    video_src = video_src.replace(/fmt=18/,'fmt=22');

  // Generate the players
  var old_player = video_player.innerHTML;
  var new_player = '<embed id="mp4-player" starttime="0" type="video/mp4" src="'
        + video_src.replace(/&/g, "&amp;") + '" '
        + 'height="385" width="640" scale="aspect"></embed>';
// HTML5 Test
//  var new_player = '<video style="border:solid 1px black" id="mp4-player" src="'
//          + video_src.replace(/&/g, "&amp;") + '" '
//          + 'height="385" width="640" autoplay="true"></video>';

  // Remove the video_player
  video_player.innerHTML = '';

  // Container div for the previews and button to play
  var stills = $E('div');
  var play_btn = $E('img',
        { 'id': 'play_btn',
          'src': 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJYAAABkCAYAAABkW8nwAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABR9JREFUeNrsm21olWUYx69z5o57OxtJUjRaTIzJYrDQtl4oyZVNhKCoL4YQBEEfRlEkQYEkfgikPknRIIIgKcSiSBwI0UASRdictjRfivVOsTE5eM42z9Z1PZ2HDoezdbZzn5c9z+8Hf89kTnguft73dd/PZUSOTMkyaNE8rtmp2ajZLBBUkppxzXHNkGZ4OT8cKVCsVs2bml2aemoeSv7SvKc5oEkUK5atUK9pXkQoyBJsr2ZQk16JWLbVHct8AuRiW+OTmsl834wu8kP9mhGkgiXYqjml6ShULGvOv9I0UTv4H2zh+TbfApQrVo/msKaGmkGBrNN8kenH84plf+ATTYxawTLpzLiTV6z9mnZqBCvE+vLnck+FZtwYWyA4uIrYoEn4K9arSAUOWO+vWrZiWdP1u3ABCm64qNkUzVwvIBW4wu61uqKZpgvAJTtNrPuoAzimx8Rqow7gejuMchqEEtAapQZQApoQC0pBDWJBSUAsQCxALEAsAMQqms/ubZSNHIYRyzVP3FYrY31x2d9ZJ01rIhiAWO6or4nI65vq5NL2uDx7B5PYiOWYW+ui8uHmBjn1cFx6buLNFmI5xqQyuT6+p0Fa6ykJYjlm1+0xufBo3NsmbbsExHKGNfTW2JtgT7XWUhDEcktbQ1QO9zbKNw81SXcL/RdiOWbrzWtkpC8u79/dIOtibI+I5Zjn22Ny9bFmefnOtRKjaojlkpbaiLzdVS9jfc3Sfwv9F2I5piMelWMPNHrpbKb/QizH2Ko1si3urWK2mgFiOcP6Leu7rP96YcNaCoJYbrET47vd9d4J0k6SiAVOsTsvu/uy8Ry7C0MscIqN59jtfVjHcxCrhGSP5+xuiyEWuMXGcz7aEq7xHMQqI/54jklmsiEWOMW2Rdsegzyeg1gVwh/P+e6RuNfoIxY4pb0x6l1NBG08B7GqBLtUPbMt7l2yBmE8B7GqCGu37LWQP56zmtsvxKpC/hvPia/a8RzEqmJi0dW7ZPG2tAqZnluQfRdScvDKjMzOIxY44IOfZmXP+aRMzi6s6udArCph+O8b8tLZpIxOpwPxPIhVYSauz3sr1Ke/zAXquRCrQiRuLMhbP8zIO5dmJJleCNzzIVYFOPSz9VEp+TU5H9hnRKwycnoqLQOj173PoINYZeCP1Ly8MZ7yTnxhAbFKiPVO1kNZL2U9VZhArBLx+W9z3mnvcmI+lM+PWI45d836qKR3LxVmEMsRdlNufdTgjzOSXqAeiFUkJpG909v7fcp7xweIVTRDf87JK+dSMn4tTTEQq3isIR84m/TEAsQqmiCMsyBWlRGUcRbEqhKCNs6CWBUmqOMsiFUhgj7OglgVIAzjLIhVRsI0zoJYZSCM4yyIVULCPM6CWCUi7OMs5SIiR6b4JwvO4b/YA2IBYgFiASAWIBaEmFnEglKQMLF4nwGumTCxLlMHcMy4iTVMHcAxJ0ysr6kDOOaoiXVUM0ktwBEn/R4rqTlIPcARB+wXm26wzybNVc166gJFrlb32xf+PVZCs4+6QJHs8b/IviAd1JymNrBCrJ064f/G3wp92jRn2BJhmRzX7NCk861YxoRmO6dEWAYXNU9nS5VPLGNU0yvcyENhK5U169O531jsJbRJtUUzRO1giZ5qx2K721LTDdOZH3yG1QuysAPeg5qB3O2vULF8DmnuyvxFCBZeTmZ6qd7s099i5J4KC6Fb06/p0nTIv5erHdQ9UIxmVqPRTL7MHOwK5h8BBgAHUHTXJ6KlKQAAAABJRU5ErkJggg==' });

  stills.appendChild(play_btn);

  // First preview
  var still0 = $E('img',
        { 'id': 'still0',
          'src': 'http://i3.ytimg.com/vi/' + video_id + '/0.jpg', });
  still0.style.cssFloat = 'left';
  stills.appendChild(still0);

  // Others previews
  for (var i = 1; i <= 3; i++) {
    var stilli = $E('img',
        { 'class': 'stilli',
          'src': still0.src.replace(/0.jpg/, i + '.jpg') });
    stills.appendChild(stilli);
  }

  // Link to play the video
  var play_video_link = $E('a',
      { 'id': 'play_video_link',
        'href': 'javascript:void(0);' });
  play_video_link.appendChild(stills);
  play_video_link.addEventListener('click', function(e){buildPlayer(embed ? new_player : old_player)}, false);

  // Create the buttons
  var control_table = $E('table', { 'id': 'control_table' });
  var control_row = $E('tr');
  control_row.appendChild(buildButton(playMP4, 'mp4play'));
  control_row.appendChild(buildButton(playFlash, 'flashplay'));
  control_row.appendChild(buildButton(stop, 'stop'));
  if (hdState) {
    control_row.appendChild(buildButton(hdOn, 'hdplay'));
  } else {
    control_row.appendChild(buildButton(expand, 'hdplay'));
  }
  control_row.appendChild(buildButton(download, 'download', video_src));
  control_row.appendChild(buildLightsOutButton());
  control_row.appendChild($E('div', {id: 'playback-display'}));
  control_table.appendChild(control_row);

  // Insert the buttons
  video_player.parentNode.insertBefore(control_table, video_player.nextSibling);

  // Append eventListeners (actual links)
  $('mp4play').addEventListener('click', function(e){buildPlayer(new_player)}, false);
  $('flashplay').addEventListener('click', function(e){buildPlayer(old_player)}, false);
  $('stop').addEventListener('click', function(e){stopPlayer()}, false);
  $('hdplay').addEventListener('click', function(e){toggleHD()}, false);
  $('lightsout').addEventListener('click', function(e){lightsOut()}, false);

  // Insert the options menu
  insertOptionsMenu();

  // Autoexpand if the video is not widescreen
  if (!wideView && !hdState && correct43) expandPlayer();

  // Autoexpand if the user wants
  if (autoExpand && !hdState) toggleHD();

  // Autoplay if the user wants
  if (previews) {
    // Append the link to the video_player
    video_player.appendChild(play_video_link);
  } else {
    buildPlayer(embed ? new_player : old_player);
  }

  // Calculate time every second if MP4 is being played
  showPlayback();

}

/////////////////////////////////////////////////////////

// UPDATE:  code needed to autocheck for updates
if (autoCheckUpdates) {

  // Some general variables about the update process
  var scriptURL = 'http://userscripts.org/scripts/show/38074';
  var sourceURL = scriptURL.replace(/show\/(\d+)$/, 'source/$1.user.js');

  // Size of this script in bytes, hardcoded
  var scriptLength = 37841;

  // Update frequency of the scripts in days. Default is 2 days.
  var updateFrequency = getValue('updateFrequency', 2);

  // If a previous check says that an update is available
  var updateAvailable = getValue('updateAvailable', false);
  var time = Math.floor(new Date().getTime() / 1000);

  // Last time the script was checked
  var lastUpdate = getValue('updateTimestamp', time);

  // If the last update was long time ago, it should check again
  var performCheck = time > lastUpdate + updateFrequency*86400;

  // Function to check the length of the local and remote scripts
  function validateScriptLength(length) {
    if (updateAvailable = scriptLength != length)
      setValue('updateAvailable', length);
    else
      setValue('updateAvailable', false);
  }

  // Function that shows the button if there's any update
  function showUpdates() {
    if (getValue('updateAvailable')) {
      if (getValue('updateAvailable') != scriptLength) {
        control_row.appendChild(buildButton(update, 'update', scriptURL));
      } else {
        setValue('updateAvailable', false);
      }
    }
  }

  // If we have to check...
  if (performCheck) {
    xhr({
      method: 'HEAD',
      url: sourceURL,
      headers: { 'Accept-Encoding': '' },
      onload: function(r) {
        var m = r.responseHeaders.match(/Content-Length: (\d+)/);
        validateScriptLength(Number(m[1]));
        if (getValue('updateAvailable'))
          showUpdates();
        setValue('updateTimestamp', time);
        log('Performed check for script updates. Local: %s Server: %s', scriptLength, Number(m[1]));
      }
    })
  } else {
    showUpdates();
  }

}

})()