FullTube

By chocolateboy Last update Oct 17, 2012 — Installed 3,353 times.

There are 9 previous versions of this script.

// ==UserScript==
// @name        FullTube
// @namespace   https://github.com/chocolateboy/userscripts
// @description Adds a full-screen button to embedded YouTube videos
// @version     0.70
// @author      chocolateboy
// @license     GPL: http://www.gnu.org/copyleft/gpl.html
// @include     *
// @exclude     http://youtube.com/*
// @exclude     http://*.youtube.com/*
// @exclude     https://youtube.com/*
// @exclude     https://*.youtube.com/*
// @require     https://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js
// @require     https://sprintf.googlecode.com/files/sprintf-0.7-beta1.js
// @grant       GM_registerMenuCommand
// ==/UserScript==

/*
 * @requires:
 *
 * jQuery 1.8.2
 *
 *     https://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.js
 *
 * sprintf() for JavaScript
 *
 *     http://www.diveintojavascript.com/projects/javascript-sprintf
 */

/*
 * 1) Iterate over all YouTube embed elements and make sure:
 *    a) their @src attribute contains "&fs=1" (or append it if it doesn't).
 *    b) the @allowFullScreen attribute is set to "true"
 *
 * This handles minimal, <embed>...</embed>-only embeds and does no harm to <object>...</object>
 * or <param>...</param> embeds.
 *
 * 2) Iterate over all object and/or param elements that contain at least one @movie/@src param
 *    with a YouTube link @value. These are required to have consistent attributes and/or
 *    child elements. Check/repair them.
 *
 * 3) Iterate over all YouTube iframe elements and make sure their @src attribute contains "&fs=1"
 * (or append it if it doesn't).
 *
 * The awkward squad (all tested and verified):
 *
 * 1) SMF 2.0 RC3 - uses SWFObject: params without name="movie" or name="src"
 *    (only <object data="http://www.youtube.com/v...">)
 *    http://code.google.com/p/swfobject/
 *
 * 2) http://www.avclub.com/articles/our-favorite-film-scenes-of-the-00s,35888/
 *    embeds without object parents (all &fs=1, though, and they work with or without FullTube)
 *
 *    XXX Update (2012-10-17): no longer working: e.g.:
 *
 *        http://www.avclub.com/articles/the-mirror-has-two-faces-15-great-movie-scenes-whe,86576/
 *
 *    - fixed in 0.70
 *
 * 3) Buzzfeed uses bare <embed>...</embed>s e.g. http://www.buzzfeed.com/mjs538/mom-ruins-hockey-fight e.g.
 *
 * <embed
 *     src="http://www.youtube.com/v/pzhkPfZwk20"
 *     type="application/x-shockwave-flash"
 *     allowscriptaccess="always"
 *     allowfullscreen="true"
 *     bgcolor="#000"
 *     wmode="transparent"
 *     height="376"
 *     width="625">
 *
 * 4) Bad iframe embed URL (prevents full screen working) e.g. http://selfstarter.us/
 * the iframe URL is fixed to conform to:
 * https://developers.google.com/youtube/player_parameters#Embedding_a_Player
 *
 */

/*
 *
 * Unsupported:
 *
 * 1) HTML5 iframes don't support the fs parameter e.g. (with HTML video enabled on YouTube):
 *    http://thedailywh.at/2012/05/18/morning-fluff-202/
 *    See https://developers.google.com/youtube/player_parameters#fs
 */

const EMBEDS = '//embed[(contains(@src, "youtube.com/v/") or contains(@src, "youtube-nocookie.com/v/"))]';

const OBJECTS = '//object[(contains(@data, "youtube.com/v/") or contains(@data, "youtube-nocookie.com/v/")) '
               + 'or ./param[(@name="movie" or @name="src") '
               + 'and (contains(@value, "youtube.com/v/") '
               + 'or contains(@value, "youtube-nocookie.com/v/"))]]';

const IFRAMES = '//iframe[(contains(@src, "youtube.com/embed/") or contains(@src, "youtube-nocookie.com/embed/") '
               + 'or contains(@src, "youtube.com/v/") or contains(@src, "youtube-nocookie.com/v/"))]';

function xpath(xpath, context) {
    var node_list = document.evaluate(xpath, context || document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
    var length;

    if (length = node_list.snapshotLength) {
        node_list.for_each = function(f) {
            for (var i = 0; i < length; ++i) {
                f(node_list.snapshotItem(i));
            }
        };
        return node_list;
    } else {
        return null;
    }
}

function fs(uri) {
    uri = unescape(uri);
    uri = uri.replace(/[?& ]+$/, ''); // remove trailing question marks, ampersands or spaces
    uri = uri.replace(/\bfs=(\w)*/g, 'fs=1');

    if (!uri.match(/[&?]fs=1\b/)) {
        uri += uri.match(/\?/) ? '&fs=1' : '?fs=1';
    }

    return uri;
}

// only set an element's src attribute (or equivalent) if the
// supplied URI is different to the attribute's current value.
// this reduces unnecessary reloading
function setSrc(element, src, _name) {
    var name = _name ? _name : 'src';

    if (!element.getAttribute(name) || (element.getAttribute(name) != src)) {
        element.setAttribute(name, src);
    }
}

function full_tube() {
    var embeds, objects, iframes;

    if (embeds = xpath(EMBEDS)) {
        embeds.for_each(function(embed) {
            var src = fs(embed.getAttribute('src'));
            setSrc(embed, src);
            embed.setAttribute('allowFullScreen', 'true');
            // bare embeds (i.e. not wrapped by objects) no longer support
            // full screen (in FF 16/Chromium 22), so wrap them; any remaining
            // object requirements will be added below
            var $embed = $(embed);
            if (!$embed.parent('object').length) {
                $embed.wrap(
                    sprintf(
                        '<object data="%s"%s%s />',
                        src,
                        ($embed.attr('width') ? sprintf(' width="%s"', $embed.attr('width')) : ''),
                        ($embed.attr('height') ? sprintf(' height="%s"', $embed.attr('height')) : '')
                    )
                );
            }
        });
    }

    if (objects = xpath(OBJECTS)) {
        objects.for_each(function(object) {
            var params;
            if (params = xpath('.//param[@name="allowFullScreen"]', object)) {
                params.for_each(function(param) { param.setAttribute('value', 'true') });
            } else {
                var param = document.createElement('param');
                param.setAttribute('name', 'allowFullScreen');
                param.setAttribute('value', 'true');
                object.appendChild(param);
            }

            var uri;
            if (params = xpath('.//param[@name="movie" or @name="src"]', object)) {
                params.for_each(function(param) {
                    // record the URI for later use
                    uri = fs(param.getAttribute('value'));
                    setSrc(param, uri, 'value');
                });
            } else {
                uri = fs(object.getAttribute('data'));
                /*
                 * embeds clearly work without this,
                 * but while we're at it we may as well
                 * make this conform to YouTube's own
                 * embed code
                 */
                var param = document.createElement('param');
                param.setAttribute('name', 'movie');
                setSrc(param, uri, 'value');
                object.appendChild(param);
            }

            var data;
            if (data = object.getAttribute('data')) {
                setSrc(object, fs(data), 'data');
            } else {
                setSrc(object, uri, 'data');
            }

            var embeds;
            if (embeds = xpath('.//embed[@src]', object)) {
                embeds.for_each(function(embed) {
                    embed.setAttribute('allowfullscreen', 'true');
                    setSrc(embed, uri);
                });
            } else {
                var embed = document.createElement('embed');
                embed.setAttribute('allowfullscreen', 'true');
                setSrc(embed, uri);
                object.appendChild(embed);
            }
        });
    }

    if (iframes = xpath(IFRAMES)) {
        iframes.for_each(function(iframe) {
            var src = fs(iframe.getAttribute('src'));

            // replace youtube.com/v/xyz?foo=bar with youtube.com/embed/xyz?foo=bar
            if (src.match(/\/v\//)) {
                src = src.replace('/v/', '/embed/');
            }

            setSrc(iframe, src);
        });
    }
}

GM_registerMenuCommand('FullTube', full_tube);
window.addEventListener('load', full_tube, false);