YouTube Auto Buffer & Auto HD

Buffers the video without autoplaying and puts it in HD if the option is on. For Firefox, Opera, & Chrome

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 Auto Buffer & Auto HD
// @namespace     http://userscripts.org/users/23652
// @description   Buffers the video without autoplaying and puts it in HD if the option is on. For Firefox, Opera, & Chrome
// @icon          https://raw.github.com/joesimmons/YouTube---Auto-Buffer---Auto-HD/master/media/logo-64x64.png
// @include       http://*.youtube.com/*
// @include       http://youtube.com/*
// @include       https://*.youtube.com/*
// @include       https://youtube.com/*
// @copyright     JoeSimmons
// @author        JoeSimmons
// @version       1.2.88
// @license       GPL version 3 or any later version; http://www.gnu.org/copyleft/gpl.html
// @require       https://greatest.deepsurf.us/scripts/1885-joesimmons-library/code/JoeSimmons'%20Library.js?version=7915
// @require       https://greatest.deepsurf.us/scripts/2104-youtube-button-container-require/code/YouTube%20-%20Button%20Container%20(@require).js?version=5493
// @require       https://greatest.deepsurf.us/scripts/1884-gm-config/code/GM_config.js?version=4836
// @grant         GM_getValue
// @grant         GM_setValue
// @grant         GM_registerMenuCommand
// ==/UserScript==

/* CHANGELOG

    1.2.88 (5/22/2015)
        - fixed HTML5 on Firefox
        - fixed script on Chrome
        - added a new option to disable autoplaying the next video on playlists
        - removed theme color option - only works on flash and that's gone now

    1.2.87 (1/22/2014)
        - fixed odd internal error that made the player not show
        - fixed problem with "Red Bar" not being disabled properly

    1.2.86 (12/21/2013)
        - fixed Large player button (as far as I can tell)
        - added Automatic quality option (the YouTube default)
        - switched the DASH option default to enabled
            this is really what the script was made for... auto-buffering

    1.2.85 (12/11/2013)
        - added a script icon
        - added a "Player Color Scheme" option
        - fixed bug with SPF not being de-activated properly in non-Firefox browsers
        - fixed volume bug
        - fixed adding time in url bug. it will now skip to the right portion of the video
        - changed internal name of the "Activation Mode" option. shouldn't affect the user
        - the script doesn't add any javascript to the page anymore
            it uses onYouTubePlayerReady to detect when the player is ready;
                it's much more performant than an interval

    1.2.84 (10/31/2013)
        - added primitive type checking when copying ytplayer.config.args into the flashvars.
            this fixes the issue with Flashgot and possibly other add-ons
        - fixed non-activation by moving the _spf_state check to the top of init.
            this disables SPF on every YouTube page now, and should make the script activate correctly
        - changed all RegExp test methods to match. match seems more consistent.
            I've had cases where test doesn't work, but match does

    1.2.83 (10/28/2013)
        - added auto HD, volume, and more activation modes for html5 (thanks to youtube updating its API)
        - changed the default quality to 1080p
        - changed the wording of some options
        - changed the "Disable Dash Playback" option to false for default
        - disabled SPF (aka Red Bar feature) completely until I get playlists working better
        - changed the setPref prototype function to a regular function

    1.2.82 (9/5/2013)
        - added support for older Firefox versions (tested on 3.6)
        - added a new option to disable 'dash' playback (videos loading in blocks/pieces)
        - re-added ad removal feature (experimental for now)

    1.2.81
        - fixed HTML5 support. YT changed tag names so the script got confused
        - made a few minor performance tweaks
        - fixed 'play symbol in title' bug in autobuffer mode (it would show playing, even though it's paused/buffering)

    1.2.80
        - switched to JSL.setInterval for consistency and drift accommodation
        - visual tweaks to:
            msg().
                the rest of the page now dims while the msg box is visible
                changed the spacing of most of the elements
                changed the font sizes and the font (Arial)
                added a close button instead of requiring a double click
                made it auto-open the options screen when the msg is closed
            GM_config.
                made the background color more mellow and moved the section title near the middle

    1.2.79
        - adjusted to the new play symbol in the youtube title feature
        - Changed margins on the settings button when in footer
        - Switched JSL/pushState checking order.
            Previously in 1.2.78, if JSL didn't exist or wasn't @required, the script
            would still loop every 500ms to re-set the pushState method, even though the
            script wasn't going to be running.
            I switched that so that JSL has to exist before the script does anything.

    1.2.78
        - Fixed bug where options button wasn't getting added to the footer with the new Red Bar YT feature

    1.2.77
        - Adapted to the new YouTube feature that uses HTML5's history.pushState to load videos
        - Small fixes here and there
        - Excluded (with RegExp) pages without videos on them
        - Fixed GM_config.log()
        - Declared all variables at the beginning of functions
        - Made finding the video player a little more reliable
        - Make 'autoplay on playlists' work with HTML5 videos

    1.2.76
        - Added new quality option ('1080p+' - for anything higher than 1080p)

    1.2.75
        - Added a new option (to move option button to page footer)
        - Added a new option (to autoplay on playlists regardless of auto[play/buffer] setting)
        - Added a first time user message box
        - Fixed bug with GM_config's [set/get]Value functions. Chrome/Opera were not using localStorage before this update

    1.2.74
        - Adapted to YouTube's new layout

    1.2.73
        - Added compatibility for user pages

    1.2.72
        - Made it fully working again in Opera & Chrome
        - Switched from setInterval to setTimeout due to instability
        - Added an anonymous function wrapper

    1.2.71
        - Added compatibility for HTML5

*/




// run the script in an IIFE, to hide its variables from the global scope
(function (undefined) {

    'use strict';

    var aBlank = ['', '', ''],
        URL = location.href,
        navID = 'watch7-user-header',
        rYoutubeUrl = /^https?:\/\/([^\.]+\.)?youtube\.com\//,
        // rYoutubeBlacklistedUrl = /^https?:\/\/([^\.]+\.)?youtube\.com\/(feed\/(?!subscriptions)|account|inbox|my_|tags|view_all|analytics)/i,
        rList = /[?&]list=/i,
        rPlaySymbol = /^\u25B6\s*/,
        script_name = 'YouTube - Auto-Buffer & Auto-HD',
        tTime = (URL.match(/[&#?]t=([sm0-9]+)/) || aBlank)[1],
        ads = [
            'supported_without_ads',
            'ad3_module',
            'adsense_video_doc_id',
            'allowed_ads',
            'baseUrl',
            'cafe_experiment_id',
            'afv_inslate_ad_tag',
            'advideo',
            'ad_device',
            'ad_channel_code_instream',
            'ad_channel_code_overlay',
            'ad_eurl',
            'ad_flags',
            'ad_host',
            'ad_host_tier',
            'ad_logging_flag',
            'ad_preroll',
            'ad_slots',
            'ad_tag',
            'ad_video_pub_id',
            'aftv',
            'afv',
            'afv_ad_tag',
            'afv_instream_max',
            'afv_ad_tag_restricted_to_instream',
            'afv_video_min_cpm',
            'prefetch_ad_live_stream'
        ],
        hasMainBeenRun, missing_require, nav, uw, wait_intv;

    function toNum(a) {
        return parseInt(a, 10);
    }

    // msg by JoeSimmons
    function msg(infoObject) {

        var box_id_name = 'script_msg',
            box = document.getElementById(box_id_name),
            rLinebreaks = /[\r\n]/g,
            title = typeof infoObject.title === 'string' && infoObject.title.length > 3 ? infoObject.title : 'Message Box by JoeSimmons.';

        // add BR tags to line breaks
        infoObject.text = infoObject.text.replace(rLinebreaks, '<br />\n');

        function msg_close(event) {
            event.preventDefault();

            document.getElementById(box_id_name).style.display = 'none';

            if (typeof infoObject.onclose === 'function') {
                infoObject.onclose();
            }
        }

        if (box == null) {
            JSL.addStyle('' +
                '@keyframes blink { ' +
                    '50% { color: #B95C00; } ' +
                '}\n\n' +
                '#' + box_id_name + ' .msg-header { ' +
                    'animation: blink 1s linear infinite normal; ' +
                '}' +
            '');
            document.body.appendChild(
                JSL.create('div', {id : box_id_name, style : 'position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 999999; background-color: rgba(0, 0, 0, 0.6);'}, [
                    // main box
                    JSL.create('div', {id : box_id_name + '_box', style : 'position: absolute; top: 25%; left: 25%; width: 50%; height: 50%; padding-top: 50px; background-color: #E9E9E9; border: 3px double #006195;'}, [
                        // header
                        JSL.create('div', {style : 'margin: 0 auto; padding-bottom: 40px; color: #F07800; font-size: 21pt; font-family: Arial, Verdana, "Myriad Pro"; font-weight: normal; text-shadow: 2px 2px 4px #C7C7C7; text-align: center;', 'class' : 'msg-header', textContent : title}),

                        // text (message)
                        JSL.create('div', {innerHTML : infoObject.text, style : 'text-align: center; margin: 0 auto; padding-top: 39px; border-top: 1px solid #B0B0B0; color: #000000; font-size: 11pt; font-family: Arial, Verdana, "Myriad Pro"; font-weight: normal; text-shadow: 0 0 8px #AEAEAE;'}),

                        // close button
                        JSL.create('div', {style : 'position: absolute; bottom: 20px; left: 0; width: 100%; text-align: center;'}, [
                            JSL.create('input', {id : box_id_name + '_close', type : 'button', value : 'Close Message', onclick : msg_close, style : 'margin: 0 auto; padding: 2px 20px; font-size: 11pt; font-family: Arial, Verdana, "Myriad Pro"; font-weight: normal;'})
                        ])
                    ])
                ])
            );
        } else {
            box.innerHTML += infoObject.text;
        }
        
    }

    // will return true if the value is a primitive value
    function isPrimitiveType(value) {
        switch (typeof value) {
            case 'string': case 'number': case 'boolean': case 'undefined': {
                return true;
            }
            case 'object': {
                return !value;
            }
        }

        return false;
    }

    function setPref(str, values) {
        var i, value, rQuery;

        for (i = 0; value = values[i]; i += 1) {
            // (several lines for readability)
            rQuery = new RegExp('[?&]?' + value[0] + '=[^&]*');
            str = str.replace(rQuery, '') + '&' + value[0] + '=' + value[1];
            str = str.replace(/^&+|&+$/g, '');
        }

        return str;
    }

    // unwraps the element so we can use its methods freely
    function unwrap(elem) {
        if (elem) {
            if ( typeof XPCNativeWrapper === 'function' && typeof XPCNativeWrapper.unwrap === 'function' ) {
                return XPCNativeWrapper.unwrap(elem);
            } else if (elem.wrappedJSObject) {
                return elem.wrappedJSObject;
            }
        }

        return elem;
    }

    function fixPlaySymbol() {
        document.title = document.title.replace(rPlaySymbol, '');
    }

    // grabs the un-wrapped player
    function getPlayer() {
        var doc = uw.document;
        return doc.getElementById('c4-player') || doc.getElementById('movie_player');
    }

    // adds the Options button below the video
    function addButton() {
        var footer = GM_config.get('footer') === true,
            footerHolder = document.getElementById('footer-main');

        addButtonToContainer('Auto-Buffer Options', function () { GM_config.open(); }, 'autobuffer-options');

        if (footer && footerHolder) {
            footerHolder.appendChild( document.getElementById('autobuffer-options') );
        }
    }

     // this function sets up the script
    function init() {
        hasMainBeenRun = false;

        // get the raw window object of the YouTube page
        uw = typeof unsafeWindow !== 'undefined' ? unsafeWindow : unwrap(window);

        // disable Red Bar aka SPF
        if (uw._spf_state && uw._spf_state.config) {
            uw._spf_state.config['navigate-limit'] = 0;
            uw._spf_state.config['navigate-part-received-callback'] = function (targetUrl) {
                location.href = targetUrl;
            };
        }
        if (uw.ytspf && uw.ytspf.config && typeof uw.ytspf.enabled !== 'undefined') {
            uw.ytspf.enabled = false;
            uw.ytspf.config['navigate-limit'] = 0;
        }

        uw.onYouTubePlayerReady = function onYouTubePlayerReady(player) {
            if (typeof player === 'object' && hasMainBeenRun === false) {
                window.postMessage('YTAB__ready', '*');
            }
        };

        JSL.waitFor({
            selector : '#c4-player, #movie_player',
            verifier : function (elem) {
                var elem = unwrap( elem[0] );
                return (elem.getPlayerState() == 1 && typeof elem.pauseVideo === 'function');
            },
            done : function () {
                if (hasMainBeenRun === false) {
                    window.setTimeout(main, 0); // run main() asynchronously (less freezing)
                }
            }
        });
    }

    // this is the main function. it does all the autobuffering, quality/volume changing, annotation hiding, etc
    function main() {
        var player = getPlayer(),
            parent = player.parentNode,
            alreadyBuffered = false,
            time = 0,
            args, arg, buffer_intv, fv, isHTML5, playerClone,
            playIfPlaylist, val, userOpts, wtpIntv;

        // don't let main() run again unless a new video is loaded
        hasMainBeenRun = true;

        // set up the user options object
        userOpts = {
            activationMode    : GM_config.get('activationMode'),
            disableAutoplay   : GM_config.get('disableAutoplay') === true,
            disableDash       : GM_config.get('disableDash') === true,
            hideAnnotations   : GM_config.get('hideAnnotations') === true,
            hideAds           : GM_config.get('hideAds') === true,
            quality           : GM_config.get('autoHD'),
            volume            : GM_config.get('volume')
        };

        // set up other variables
        isHTML5 = uw.ytplayer.config.html5 == true;
        playerClone = isHTML5 === false ? player.cloneNode(true) : null; // clone player if flash
        fv = isHTML5 === false ? player.getAttribute('flashvars') : ''; // get flashvars if flash
        playIfPlaylist = URL.match(rList) != null && GM_config.get('autoplayplaylists') === true;

        if (uw.ytplayer && uw.ytplayer.config && uw.ytplayer.config.args) {
            args = uw.ytplayer.config.args;
        }

        // get video starting time from the url or player itself
        if ( tTime.match(/\d+m/) ) {
            time += toNum( tTime.match(/(\d+)m/)[1] ) * 60;
        }
        if ( tTime.match(/\d+s/) ) {
            time += toNum( tTime.match(/(\d+)s/)[1] );
        }
        if ( tTime.match(/^\d+$/) ) {
            time += toNum(tTime);
        }
        if (time <= 3) {
            // if no time is in the url, check the player's time
            try {
                // sometimes causes a weird error.
                // it will say getCurrentTime isn't a function,
                // even though the typeof is "function",
                // and alerting its value says [native code]
                time = Math.floor( player.getCurrentTime() );
            } catch (e) {}
            if (time <= 3) {
                time = 0;
            }
        }

        // set the volume to the user's preference
        if (userOpts.volume != 1000) {
            player.setVolume(userOpts.volume);
        }

        if (isHTML5) {
            // set video quality
            if (userOpts.quality !== 'default' && player.getPlaybackQuality() !== userOpts.quality) {
                player.setPlaybackQuality(userOpts.quality);
            }

            // seek to the right time
            player.seekTo(time);

            // handle the activation mode (buffer/play)
            if (playIfPlaylist === false && userOpts.activationMode === 'buffer') {
                player.pauseVideo();

                // sometimes it doesn't like pausing when told,
                // so keep trying to pause until it does
                wtpIntv = JSL.setInterval(function () {
                    var player = getPlayer();

                    if (player.getPlayerState() == 1) {
                        player.pauseVideo();
                    } else {
                        JSL.clearInterval(wtpIntv);
                    }
                }, 50);
            }
        } else {
            // copy 'ytplayer.config.args' into the flash vars
            if (args) {
                for (arg in args) {
                    val = args[arg];
                    if ( args.hasOwnProperty(arg) && isPrimitiveType(val) ) {
                        fv = setPref(fv, [ [ arg, encodeURIComponent(val) ] ]);
                    }
                }
            }

            // ad removal
            if (userOpts.hideAds) {
                fv = fv.replace(new RegExp('(&amp;|[&?])?(' + ads.join('|') + ')=[^&]*', 'g'), '');
            }

            // disable DASH playback
            if (userOpts.disableDash) {
                fv = setPref(fv, [
                    ['dashmpd', ''],
                    ['dash', '0']
                ]);
            }

            // edit the flashvars
            fv = setPref(fv, [
                ['vq', userOpts.quality],                                                           // set the quality
                ['autoplay', (userOpts.activationMode !== 'none' || playIfPlaylist) ? '1' : '0' ],  // enable/disable autoplay
                ['iv_load_policy', userOpts.hideAnnotations ? '3' : '1' ],                          // enable/disable annotations

                // some "just-in-case" settings
                ['enablejsapi',           '1'],                                                     // enable JS API
                ['jsapicallback',         'onYouTubePlayerReady'],                                  // enable JS ready callback
                ['fs',                    '1'],                                                     // enable fullscreen button, just in-case
                ['modestbranding',        '1'],                                                     // hide YouTube logo in player
                ['disablekb',             '0']                                                      // enable keyboard controls in player
            ]);

            // set the player's time
            fv = setPref( fv, [ ['start', time] ] );

            // set the new player's flashvars
            playerClone.setAttribute('flashvars', fv);

            // replace the original player with the modified clone
            parent.replaceChild(playerClone, player);

            if (userOpts.activationMode === 'buffer' && playIfPlaylist === false) {
                // handle auto-buffering
                buffer_intv = JSL.setInterval(function () {
                    var player = getPlayer();

                    if (player && typeof player.getPlayerState === 'function') {
                        JSL.clearInterval(buffer_intv);

                        // pause the video so it can buffer
                        player.pauseVideo();

                        // seek back to beginning if time elapsed is not much
                        if (player.getCurrentTime() <= 3) {
                            player.seekTo(0);
                        }

                        // adjust to the 'play symbol in title' feature
                        window.setTimeout(fixPlaySymbol, 1000);
                    }
                }, 100);
            } else if (userOpts.activationMode === 'none') {
                // adjust to the 'play symbol in title' feature
                window.setTimeout(fixPlaySymbol, 1500);
            }
        }

        // disable autoplay on playlists if desired
        if (userOpts.disableAutoplay) {
            for (var key in uw._yt_www) {
                if ( ('' + uw._yt_www[key]).indexOf('window.spf.navigate') !== -1) {
                    uw._yt_www[key] = function () {};
                }
            }
        }

        // show the first time user message, then set it to never show again
        if (GM_config.getValue('yt-autobuffer-autohd-first', 'yes') === 'yes') {
            msg({
                text : 'Welcome to "' + script_name + '".\n\n\n\n' +
                    'There is an options button below the video.\n\n\n\n' +
                    'The options screen will automatically open when you close this message.',
                title : '"' + script_name + '" Message',
                onclose : function () { GM_config.open(); }
            });
            GM_config.setValue('yt-autobuffer-autohd-first', 'no');
        }
    }

    // make sure the page is not in a frame
    // & is on a YouTube page (the @include works most of the time, but this is 100%)
    // & isn't on a blacklisted YouTube page
    if ( window.frameElement || window !== window.top || !URL.match(rYoutubeUrl) /*|| URL.match(rYoutubeBlacklistedUrl)*/ ) { return; }

    // quit if one of the @requires is non-existent
    if (typeof JSL === 'undefined' || typeof GM_config === 'undefined' || typeof addButtonToContainer === 'undefined') {
        missing_require = typeof JSL === 'undefined' ? 'JSL' : typeof GM_config === 'undefined' ? 'GM_config' : typeof addButtonToContainer ? 'Button Container' : 'unknown';

        return alert('' +
            'A @require is missing (' + missing_require + ').\n\n' +
            'Either you\'re not using the correct plug-in, or @require isn\'t working.\n\n' +
            'Please review the script\'s main page to see which browser & add-on to use.' +
        '');
    }

    // add a user-script command
    if (typeof GM_registerMenuCommand === 'function') {
        GM_registerMenuCommand('"' + script_name + '" Options', GM_config.open);
    }

    // init GM_config
    GM_config.init('"' + script_name + '" Options', {
        activationMode : {
            section : ['Main Options'],
            label : 'Activation Mode',
            type : 'select',
            options : {
                'buffer' : 'Auto-Pause',
                'play' : 'Auto Play'
            },
            'default' : 'buffer'
        },
        autoHD : {
            label : 'Auto HD',
            type : 'select',
            options : {
                'default' : 'Automatic (default)',
                'tiny' : '144p',
                'small' : '240p',
                'medium' : '360p',
                'large' : '480p',
                'hd720' : '720p (HD)',
                'hd1080' : '1080p (HD)',
                'hd1440' : '1440p (HD)',
                'highres' : 'Original (highest)'
            },
            'default' : 'hd1080'
        },
        disableDash : {
            label : 'Disable DASH Playback',
            type : 'checkbox',
            'default' : true,
            title : '"DASH" loads the video in blocks/pieces; disrupts autobuffering -- Note: Qualities are limited when disabled'
        },
        hideAds : {
            label : 'Disable Ads',
            type : 'checkbox',
            'default' : true,
            title : 'Should disable advertisements. AdBlock Edge is better, though. Get that instead'
        },
        hideAnnotations : {
            label : 'Disable Annotations',
            type : 'checkbox',
            'default' : false,
            title : 'Not working for HTML5 - PM me for suggestions'
        },
        disableAutoplay : {
            label : 'Disable Autoplay-Next-Video In Playlist',
            type : 'checkbox',
            'default' : false
        },
        volume : {
            section : ['Other Options'],
            label : 'Set volume to: ',
            type : 'select',
            options : {
                '1000' : 'Don\'t Change',
                '0' : '0% (Off)',
                '5' : '5%',
                '10' : '10%',
                '20' : '20%',
                '25' : '25% (quarter)',
                '30' : '30%',
                '40' : '40%',
                '50' : '50% (half)',
                '60' : '60%',
                '70' : '70%',
                '75' : '75% (three quarters)',
                '80' : '80%',
                '90' : '90%',
                '100' : '100% (full)',
            },
            title : 'What to set the volume to',
            'default' : '1000'
        },
        autoplayplaylists : {
            label : 'Autoplay on Playlists (override)',
            type : 'checkbox',
            'default' : false,
            title : 'This will enable autoplay on playlists, regardless of the "Activation Mode" option'
        },
        footer : {
            label : 'Options Button In Footer',
            type : 'checkbox',
            'default' : false,
            title : 'This will make the options button show at the bottom of the page in the footer'
        }
    }, '' +
    'body { ' +
        'background-color: #DDDDDD !important; ' +
        'color: #434343 !important; ' +
        'font-family: Arial, Verdana, sans-serif !important; ' +
    '}' +
    '#config_header { ' +
        'font-size: 16pt !important; ' +
    '}' +
    '.config_var { ' +
        'margin-left: 20% !important; ' +
        'margin-top: 20px !important; ' +
    '}' +
    '#header { ' +
        'margin-bottom: 40px !important; ' +
        'margin-top: 20px !important; ' +
    '}' +
    '.indent40 { ' +
        'margin-left: 20% !important; ' +
    '}' + 
    '.config_var * { ' +
        'font-size: 10pt !important; ' +
    '}' +
    '.section_header_holder { ' +
        'border-bottom: 1px solid #BBBBBB !important; ' +
        'margin-top: 14px !important; ' +
    '}' +
    '.section_header { ' +
        'background-color: #BEDBFF !important; ' +
        'color: #434343 !important; ' +
        'margin-left: 20% !important; ' +
        'margin-top: 8px !important; ' +
        'padding: 2px 200px !important; ' +
        'text-decoration: none !important; ' +
    '}' +
    '.section_kids { ' +
        'margin-bottom: 14px !important; ' +
    '}' +
    '.saveclose_buttons { ' +
        'font-size: 14pt !important; ' +
    '}' +
    '#buttons_holder { ' +
        'padding-right: 50px; ' +
    '}' +
    '', {
        close : function () {
            JSL('#c4-player, #movie_player').css('visibility', 'visible');
            JSL('#lights_out').hide();
        },
        open : function () {
            JSL('#c4-player, #movie_player').css('visibility', 'hidden');
            JSL('#lights_out').show('block');
            JSL('#GM_config').css('height', '80%').css('width', '80%').css('zIndex', '999999999999');
        }
    });

    // this is for the "lights out" feature of GM_config
    JSL.runAt('interactive', function () {
        JSL(document.body).append('div', {
            id : 'lights_out',
            style : 'display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 999999999998; background: rgba(0, 0, 0, 0.72);'
        });

        // call the function that sets up everything
        init();
    });

    // add a message listener for when the unsafeWindow function fires a message
    window.addEventListener('message', function (msg) {
        if (msg.data === 'YTAB__ready') {
            main();
        }
    }, false);

    // wait for an element that can hold the options button to load,
    // then run our add button function
    JSL.waitFor({
        selector : '#watch7-headline, #gh-overviewtab div.c4-spotlight-module-component, #footer-main',
        done : addButton
    });

}());