There are 28 previous versions of this script.
Add Syntax Highlighting (this will take a few seconds, probably freezing your browser while it works)
// ==UserScript==
// @name Yays! (Yet Another Youtube Script)
// @namespace youtube
// @description A lightweight and non-intrusive userscript that control autoplaying and set the preferred player size and playback quality on YouTube.
// @version 1.6.2
// @author Eugene Nouvellieu <eugenox_gmail_com>
// @license MIT License
// @include http*://*.youtube.com/*
// @include http*://youtube.com/*
// @run-at document-end
// @noframes
// @grant GM_deleteValue
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_xmlhttpRequest
// @homepageURL http://eugenox.appspot.com/script/yays
// @updateURL https://eugenox.appspot.com/blob/yays/yays.meta.js
// @downloadURL https://eugenox.appspot.com/blob/yays/yays.user.js
// ==/UserScript==
// Copyright (c) 2012-2013 Eugene Nouvellieu <eugenox_gmail_com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
function YAYS(unsafeWindow) {
/*
* Meta.
*/
var Meta = {
title: 'Yays! (Yet Another Youtube Script)',
version: '1.6.2',
releasedate: 'Mar 29, 2013',
site: 'http://eugenox.appspot.com/script/yays',
ns: 'yays'
};
/*
* Script context.
*/
unsafeWindow[Meta.ns] = {};
/*
* Utility functions.
*/
function each(iterable, callback, scope) {
if (iterable.length) {
for (var i = 0, len = iterable.length; i < len; ++i)
callback.call(scope, i, iterable[i]);
}
else {
for (var key in iterable)
if (iterable.hasOwnProperty(key))
callback.call(scope, key, iterable[key]);
}
}
function map() {
var
args = Array.prototype.constructor.apply([], arguments),
callback = args.shift() || bind(Array.prototype.constructor, []),
buffer = [];
if (args.length > 1) {
var i = 0, len = Math.max.apply(Math, map(function(arg) { return arg.length; }, args));
function getter(arg) { return arg[i]; }
for (; i < len; ++i)
buffer.push(callback.apply(null, map(getter, args)));
}
else {
for (var i = 0, arg = args[0], len = arg.length; i < len; ++i)
buffer.push(callback(arg[i]));
}
return buffer;
}
function combine(keys, values) {
var object = {};
map(function(key, value) { object[key] = value; }, keys, values);
return object;
}
function bind(func, scope, args) {
return function() {
return func.apply(scope, typeof args == 'undefined' ? arguments : args);
};
}
function merge(target, source, override) {
override = override === undefined || override;
for (var key in source) {
if (override || ! target.hasOwnProperty(key))
target[key] = source[key];
}
return target;
}
function extend(base, proto) {
function T() {}
T.prototype = base.prototype;
return merge(new T(), proto);
}
function asyncCall(func, scope, args) {
setTimeout(bind(func, scope, args), 0);
}
function asyncProxy(func) {
return function() {
asyncCall(func, this, arguments);
};
}
function extendFn(func, extension) {
if (! func)
return extension;
return function() {
asyncCall(func, this, arguments);
extension.apply(this, arguments);
};
}
function emptyFn() { return; };
function buildURL(path, parameters) {
var query = [];
each(parameters, function(key, value) { query.push(key.concat('=', encodeURIComponent(value))); });
return path.concat('?', query.join('&'));
}
function parseJSON(data) {
if (typeof JSON != 'undefined')
return JSON.parse(data);
return eval('('.concat(data, ')'));
}
function debug() {
unsafeWindow.console.debug.apply(unsafeWindow.console, Array.prototype.concat.apply(['['.concat(Meta.ns, ']')], arguments));
}
/*
* i18n
*/
var _ = (function() {
var
vocabulary = ["Auto play", "ON", "OFF", "AUTO", "Toggle video autoplay", "Quality", "AUTO", "ORIGINAL", "Set default video quality", "Size", "AUTO", "WIDE", "FIT", "Set default player size", "Player settings", "Help"],
dictionary = combine(vocabulary, (function() {
switch ((document.documentElement.lang || 'en').substr(0, 2)) {
// hungarian - eugenox
case 'hu':
return ["Automatikus lej\u00e1tsz\u00e1s", "BE", "KI", "AUTO", "Automatikus lej\u00e1tsz\u00e1s ki-be kapcsol\u00e1sa", "Min\u0151s\u00e9g", "AUTO", "EREDETI", "Vide\u00f3k alap\u00e9rtelmezett felbont\u00e1sa", "M\u00e9ret", "AUTO", "SZ\u00c9LES", "ILLESZTETT", "Lej\u00e1tsz\u00f3 alap\u00e9rtelmezett m\u00e9rete", "Lej\u00e1tsz\u00f3 be\u00e1ll\u00edt\u00e1sai", "S\u00fag\u00f3"];
// dutch - Mike-RaWare
case 'nl':
return ["Auto spelen", "AAN", "UIT", "AUTOMATISCH", "Stel automatisch afspelen in", "Kwaliteit", "AUTOMATISCH", null, "Stel standaard videokwaliteit in", null, null, null, null, null, null, null];
// spanish - yonane
case 'es':
return ["Reproducci\u00f3n Autom\u00e1tica", null, null, "AUTO", "Modificar Reproducci\u00f3n Autom\u00e1tica", "Calidad", "AUTO", null, "Usar calidad por defecto", null, null, null, null, null, null, null];
// german - xemino
case 'de':
return ["Automatische Wiedergabe", "AN", "AUS", "AUTO", "Automatische Wiedergabe umschalten", "Qualit\u00e4t", "AUTO", null, "Standard Video Qualit\u00e4t setzen", null, null, null, null, null, null, null];
// portuguese - Pitukinha
case 'pt':
return ["Reprodu\u00e7\u00e3o Autom\u00e1tica", "LIGADO", "DESLIGADO", "AUTOM\u00c1TICO", "Modificar Reprodu\u00e7\u00e3o Autom\u00e1tica", "Qualidade", "AUTOM\u00c1TICO", null, "Defini\u00e7\u00e3o padr\u00e3o de v\u00eddeo", null, null, null, null, null, "Configura\u00e7\u00e3o do usu\u00e1rio", null];
// greek - TastyTeo
case 'el':
return ["\u0391\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03b1\u03bd\u03b1\u03c0\u03b1\u03c1\u03b1\u03b3\u03c9\u03b3\u03ae", "\u0395\u039d\u0395\u03a1\u0393\u039f", "\u0391\u039d\u0395\u039d\u0395\u03a1\u0393\u039f", "\u0391\u03a5\u03a4\u039f\u039c\u0391\u03a4\u039f", "\u0395\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03ae \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7\u03c2 \u03b1\u03bd\u03b1\u03c0\u03b1\u03c1\u03b1\u03b3\u03c9\u03b3\u03ae\u03c2", "\u03a0\u03bf\u03b9\u03cc\u03c4\u03b7\u03c4\u03b1", "\u0391\u03a5\u03a4\u039f\u039c\u0391\u03a4\u039f", "\u03a0\u03a1\u039f\u0395\u03a0\u0399\u039b\u039f\u0393\u0397", "\u039f\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2 \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03b7\u03c2 \u03c0\u03bf\u03b9\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b2\u03af\u03bd\u03c4\u03b5\u03bf", "\u039c\u03ad\u03b3\u03b5\u03b8\u03bf\u03c2", "\u0391\u03a5\u03a4\u039f\u039c\u0391\u03a4\u039f", "\u03a0\u039b\u0391\u03a4\u03a5", "\u03a0\u03a1\u039f\u03a3\u0391\u03a1\u039c\u039f\u0393\u0397", "\u039f\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2 \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03b7\u03c2 \u03b1\u03bd\u03ac\u03bb\u03c5\u03c3\u03b7\u03c2 \u03b1\u03bd\u03b1\u03c0\u03b1\u03c1\u03b1\u03b3\u03c9\u03b3\u03ad\u03b1", "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03b1\u03bd\u03b1\u03c0\u03b1\u03c1\u03b1\u03b3\u03c9\u03b3\u03ad\u03b1", "\u0392\u03bf\u03ae\u03b8\u03b5\u03b9\u03b1"];
// french - eXa
case 'fr':
return ["Lecture Auto", "ON", "OFF", "AUTO", "Lecture auto ON/OFF", "Qualit\u00e9", "AUTO", "ORIGINAL", "Qualit\u00e9 par d\u00e9faut", "Taille", "AUTO", "LARGE", "ADAPT\u00c9", "Taille par d\u00e9faut du lecteur", "Options du lecteur", "Aide"];
// slovenian - Paranoia.Com
case 'sl':
return ["Samodejno predvajanje", "Vklju\u010di", "Izklju\u010di", "Samodejno", "Vklop/izklop samodejnega predvajanja", "Kakovost", "Samodejno", null, "Nastavi privzeto kakovost videa", null, null, null, null, null, "Nastavitve predvajalnika", "Pomo\u010d"];
// russian - an1k3y
case 'ru':
return ["\u0410\u0432\u0442\u043e \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435", "\u0412\u041a\u041b", "\u0412\u042b\u041a\u041b", "\u0410\u0412\u0422\u041e", "\u0410\u0432\u0442\u043e\u0437\u0430\u043f\u0443\u0441\u043a \u0432\u0438\u0434\u0435\u043e", "\u041a\u0430\u0447\u0435\u0441\u0442\u0432\u043e", "\u0410\u0412\u0422\u041e", null, "\u041a\u0430\u0447\u0435\u0441\u0442\u0432\u043e \u0432\u0438\u0434\u0435\u043e \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e", "\u0420\u0410\u0417\u041c\u0415\u0420", null, "\u0420\u0410\u0417\u0412\u0415\u0420\u041d\u0423\u0422\u042c", "\u0420\u0410\u0421\u0422\u042f\u041d\u0423\u0422\u042c", "\u0420\u0430\u0437\u043c\u0435\u0440 \u0432\u0438\u0434\u0435\u043e \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e", "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043f\u043b\u0435\u0435\u0440\u0430", "\u041f\u043e\u043c\u043e\u0449\u044c"];
// hebrew - baryoni
case 'iw':
return ["\u05d4\u05e4\u05e2\u05dc\u05d4 \u05d0\u05d5\u05d8\u05d5\u05de\u05d8\u05d9\u05ea", "\u05e4\u05e2\u05d9\u05dc", "\u05db\u05d1\u05d5\u05d9", "\u05d0\u05d5\u05d8\u05d5\u05de\u05d8\u05d9", "\u05e9\u05e0\u05d4 \u05de\u05e6\u05d1 \u05d4\u05e4\u05e2\u05dc\u05d4 \u05d0\u05d5\u05d8\u05d5\u05de\u05d8\u05d9\u05ea \u05e9\u05dc \u05d4\u05d5\u05d9\u05d3\u05d0\u05d5", "\u05d0\u05d9\u05db\u05d5\u05ea", "\u05d0\u05d5\u05d8\u05d5\u05de\u05d8\u05d9\u05ea", null, "\u05d4\u05d2\u05d3\u05e8 \u05d0\u05ea \u05d0\u05d9\u05db\u05d5\u05ea \u05d1\u05e8\u05d9\u05e8\u05ea \u05d4\u05de\u05d7\u05d3\u05dc \u05e9\u05dc \u05d4\u05d5\u05d9\u05d3\u05d0\u05d5", "\u05d2\u05d5\u05d3\u05dc", null, "\u05e8\u05d7\u05d1", "\u05de\u05dc\u05d0", "\u05d4\u05d2\u05d3\u05e8 \u05d0\u05ea \u05d2\u05d5\u05d3\u05dc \u05d1\u05e8\u05d9\u05e8\u05ea \u05d4\u05de\u05d7\u05d3\u05dc \u05e9\u05dc \u05d4\u05e0\u05d2\u05df", "\u05d4\u05d2\u05d3\u05e8\u05d5\u05ea \u05e0\u05d2\u05df", "\u05e2\u05d6\u05e8\u05d4"];
// chinese - blankhang
case 'zh':
return ["\u81ea\u52a8\u64ad\u653e", "\u5f00", "\u5173", "\u81ea\u52a8", "\u5207\u6362\u89c6\u9891\u81ea\u52a8\u64ad\u653e", "\u89c6\u9891\u8d28\u91cf", "\u81ea\u52a8", "\u539f\u753b", "\u8bbe\u7f6e\u9ed8\u8ba4\u89c6\u9891\u8d28\u91cf", "\u64ad\u653e\u5668\u5927\u5c0f", "\u81ea\u52a8", "\u5bbd\u5c4f", "\u81ea\u9002\u5e94", "\u8bbe\u7f6e\u64ad\u653e\u5668\u9ed8\u8ba4\u5927\u5c0f", "\u64ad\u653e\u5668\u8bbe\u7f6e", "\u5e2e\u52a9"];
// polish - mkvs
case 'pl':
return ["Automatyczne odtwarzanie", "W\u0141\u0104CZONE", "WY\u0141ACZNONE", "AUTOMATYCZNE", "Ustaw automatyczne odtwarzanie film\u00f3w", "Jako\u015b\u0107", "AUTOMATYCZNA", "ORYGINALNA", "Ustaw domy\u015bln\u0105 jako\u015b\u0107 film\u00f3w", "Rozmiar", "AUTOMATYCZNY", "SZEROKI", "DOPASOWANY", "Ustaw domy\u015blny rozmiar odtwarzacza", "Ustawienia odtwarzacza", "Pomoc"];
// swedish - eson
case 'sv':
return ["Automatisk uppspelning", "P\u00c5", "AV", "AUTO", "V\u00e4xla uppspelningsl\u00e4ge", "Kvalitet", "AUTO", "ORIGINAL", "Ange standardkvalitet", "Storlek", "AUTO", "BRED", "ANPASSAD", "Ange standardstorlek", "Inst\u00e4llningar", "Hj\u00e4lp"];
}
return [];
})());
return function(text) { return dictionary[text] || text; };
})();
/*
* DOM Helper singleton.
*/
var DH = {
ELEMENT_NODE: 1,
build: function(def) {
switch (Object.prototype.toString.call(def)) {
case '[object Object]':
def = merge({tag: 'div', style: null, attributes: null, listeners: null, children: null}, def);
var node = this.createElement(def.tag);
if (def.style !== null)
this.style(node, def.style);
if (def.attributes !== null)
this.attributes(node, def.attributes);
if (def.listeners !== null)
this.listeners(node, def.listeners);
if (def.children !== null)
this.append(node, def.children);
return node;
case '[object String]':
return this.createTextNode(def);
default:
return def;
}
},
id: bind(unsafeWindow.document.getElementById, unsafeWindow.document),
createElement: bind(unsafeWindow.document.createElement, unsafeWindow.document),
createTextNode: bind(unsafeWindow.document.createTextNode, unsafeWindow.document),
style: function(node, style) {
each(style, node.style.setProperty, node.style);
},
append: function(node, children) {
each([].concat(children), function(i, child) { node.appendChild(this.build(child)); }, this);
node.normalize();
},
insertAfter: function(node, children) {
var parent = node.parentNode, sibling = node.nextSibling;
if (sibling) {
each([].concat(children), function(i, child) { parent.insertBefore(this.build(child), sibling); }, this);
parent.normalize();
}
else
this.append(parent, children);
},
prepend: function(node, children) {
if (node.hasChildNodes())
each([].concat(children), function(i, child) { node.insertBefore(this.build(child), node.firstChild); }, this);
else
this.append(node, children);
},
attributes: function(node, attributes) {
each(attributes, node.setAttribute, node);
},
hasClass: function(node, clss) {
return node.hasAttribute('class') &&
(clss instanceof RegExp ? clss.test(node.getAttribute('class')) : node.getAttribute('class').indexOf(clss) != -1);
},
addClass: function(node, clss) {
if (clss.indexOf(' ') == -1) {
if (! this.hasClass(node, clss))
node.setAttribute('class', (node.getAttribute('class') || '').concat(' ', clss).trim());
}
else
each(clss.split(/ +/), function(i, clss) { this.addClass(node, clss); }, this);
},
delClass: function(node, clss) {
clss = clss.trim().replace(/ +/g, '|');
if (this.hasClass(node, new RegExp(clss)))
node.setAttribute('class', node.getAttribute('class').replace(new RegExp('\\s*'.concat(clss, '\\s*'), 'g'), ' ').trim());
},
listeners: function(node, listeners) {
each(listeners, function(type, listener) { this.on(node, type, listener); }, this);
},
on: function(node, type, listener) {
node.addEventListener(type, listener, false);
},
un: function(node, type, listener) {
node.removeEventListener(type, listener, false);
},
unwrap: function(element) {
if (typeof XPCNativeWrapper != 'undefined' && typeof XPCNativeWrapper.unwrap == 'function')
return XPCNativeWrapper.unwrap(element);
return element;
},
walk: function(node, path) {
var steps = path.split('/'), step = null;
while (node && (step = steps.shift())) {
if (step == '..') {
node = node.parentNode;
continue;
}
var
selector = /^(\w*)(?:\[(\d+)\])?$/.exec(step),
name = selector[1],
index = Number(selector[2]) || 0;
for (var i = 0, j = 0, nodes = node.childNodes; node = nodes.item(i); ++i)
if (node.nodeType == this.ELEMENT_NODE && (! name || node.tagName.toLowerCase() == name) && j++ == index)
break;
}
return node;
}
};
/*
* Configuration handler singleton.
*/
var Config = (function(namespace) {
// Greasemonkey compatible
if (typeof GM_getValue == 'function') {
return {
get: GM_getValue,
set: GM_setValue,
del: GM_deleteValue
};
}
var prefix = namespace + '.';
// HTML5
if (typeof unsafeWindow.localStorage != 'undefined') {
return {
get: function(key) {
return unsafeWindow.localStorage.getItem(prefix + key);
},
set: function(key, value) {
unsafeWindow.localStorage.setItem(prefix + key, value);
},
del: function(key) {
unsafeWindow.localStorage.removeItem(prefix + key);
}
};
}
// Cookie
return {
get: function(key) {
return (document.cookie.match(new RegExp('(?:^|; *)'.concat(prefix, key, '=(\\w+)(?:$|;)'))) || [, null])[1];
},
set: function(key, value) {
document.cookie = prefix.concat(key, '=', value, '; path=/; expires=', new Date(new Date().valueOf() + 365 * 24 * 36e5).toUTCString());
},
del: function(key) {
document.cookie = prefix.concat(key, '=deleted; path=/; max-age=0');
}
};
})(Meta.ns);
/*
* Create XHR or JSONP requests.
*/
var JSONRequest = (function(namespace) {
var Request = null;
// XHR
if (typeof GM_xmlhttpRequest == 'function') {
Request = function(url, parameters, callback) {
this._callback = callback;
GM_xmlhttpRequest({
method: 'GET',
url: buildURL(url, parameters),
onload: bind(this._onLoad, this)
});
};
Request.prototype = {
_onLoad: function(response) {
this._callback(parseJSON(response.responseText));
}
};
}
// JSONP
else {
Request = (function() {
var requests = [], requestsNs = 'jsonp';
function Request(url, parameters, callback) {
this._callback = callback;
this._id = requests.push(bind(this._onLoad, this)) - 1;
parameters.callback = namespace.concat('.', requestsNs, '[', this._id, ']');
this._scriptNode = document.body.appendChild(DH.build({
tag: 'script',
attributes: {
'type': 'text/javascript',
'src': buildURL(url, parameters)
}
}));
}
Request.prototype = {
_callback: null,
_id: null,
_scriptNode: null,
_onLoad: function(response) {
this._callback(response);
document.body.removeChild(this._scriptNode);
delete requests[this._id];
}
};
unsafeWindow[namespace][requestsNs] = requests;
return Request;
})();
}
return Request;
})(Meta.ns);
/*
* Update checker.
*/
(function() {
if (new Date().valueOf() - Number(Config.get('update_checked_at')) < 24 * 36e5) // 1 day
return;
var popup = null;
new JSONRequest(Meta.site + '/changelog', {version: Meta.version}, function(changelog) {
Config.set('update_checked_at', new Date().valueOf().toFixed());
if (changelog && changelog.length)
popup = renderPopup(changelog);
});
function renderPopup(changelog) {
return document.body.appendChild(DH.build({
style: {
'position': 'fixed',
'top': '15px',
'right': '15px',
'z-index': '1000',
'padding': '10px 15px 10px',
'background-color': '#f8f8f8',
'border': '1px solid #cccccc'
},
children: [{
tag: 'h3',
style: {
'text-align': 'center',
},
children: Meta.title
}, {
style: {
'font-size': '11px',
'color': '#808080',
'text-align': 'center'
},
children: 'User Script update notification.'
}, {
tag: 'p',
style: {
'margin': '10px 0'
},
children: [
'You are using version ',
{
tag: 'strong',
children: Meta.version
},
', released on ',
{
tag: 'em',
children: Meta.releasedate
},
'.',
{
tag: 'br'
},
'Please consider updating to the latest release.'
]
}, {
children: map(function(entry) {
return {
style: {
'margin-bottom': '5px'
},
children: [{
tag: 'strong',
style: {
'font-size': '11px'
},
children: entry.version
}, {
tag: 'em',
style: {
'margin-left': '5px'
},
children: entry.date
}, {
style: {
'padding': '0 0 2px 10px',
'white-space': 'pre'
},
children: [].concat(entry.note).join('\n')
}]
};
}, [].concat(changelog))
}, {
style: {
'text-align': 'center',
'padding': '10px 0'
},
children: map(function(text, handler) {
return DH.build({
tag: 'span',
attributes: {
'class': 'yt-uix-button yt-uix-button-default'
},
style: {
'margin': '0 5px',
'padding': '5px 10px'
},
children: text,
listeners: {
'click': handler
}
});
}, ['Update', 'Dismiss'], [openDownloadSite, removePopup])
}]
}));
}
function removePopup() {
document.body.removeChild(popup);
}
function openDownloadSite() {
removePopup();
unsafeWindow.open(buildURL(Meta.site + '/download', {version: Meta.version}));
}
})();
/*
* Player singleton.
*/
var Player = (function() {
function Player(element) {
this._element = element;
this._boot();
}
Player.prototype = {
_element: null,
_muted: 0,
_onReady: emptyFn,
_boot: function() {
if (typeof this._element.getApiInterface == 'function') {
this._exportApiInterface();
this._onApiReady();
}
else
setTimeout(bind(this._boot, this), 10);
},
_exportApiInterface: function() {
each(this._element.getApiInterface(), function(i, method) {
if (! Player.prototype.hasOwnProperty(method))
this[method] = bind(this._element[method], this._element);
}, this);
},
_onApiReady: function() {
this._muted = Number(this.isMuted());
// The player sometimes reports inconsistent state.
if (this.isAutoPlaying())
this.resetState();
this._onReady(this);
this._onReady = null;
},
onReady: function(callback) {
if (this._onReady)
this._onReady = callback;
else
callback(this);
},
getArgument: function(name) {
// Flash
if (this._element.hasAttribute('flashvars')) {
var match = this._element.getAttribute('flashvars').match(new RegExp('(?:^|&)'.concat(name, '=(.+?)(?:&|$)')));
if (match)
return decodeURIComponent(match[1]);
}
// HTML5
else {
try {
return unsafeWindow.ytplayer.config.args[name];
}
catch (e) {}
}
return;
},
isAutoPlaying: function() {
return (this.getArgument('autoplay') || '1') == 1;
},
// Suppressing random exception.
seekTo: function() {
try {
this._element.seekTo.apply(this._element, arguments);
}
catch (e) {}
},
// Seek to the beginning of the video considering deep-links.
seekToStart: function(ahead) {
var
code = (location.hash + location.search).match(/\bt=(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?/) || new Array(4),
seconds = (Number(code[1]) || 0) * 3600 + (Number(code[2]) || 0) * 60 + (Number(code[3]) || 0);
this.seekTo(seconds, ahead);
},
// This hack resets some aspects of the player.
resetState: function() {
this.seekTo(this.getCurrentTime(), true);
},
mute: function() {
if (! this._muted++)
this._element.mute();
},
unMute: function() {
if (! --this._muted)
this._element.unMute();
}
};
var instance = {
_element: null
};
return {
UNSTARTED: -1,
ENDED: 0,
PLAYING: 1,
PAUSED: 2,
BUFFERING: 3,
instance: function() {
return instance;
},
initialize: function(element) {
if (instance._element === element)
throw 'Player already initialized';
return instance = new Player(element);
}
};
})();
/*
* Button class.
*/
function Button(labelText, tooltipText, callbacks) {
var
node = DH.build(this._def.node),
label = DH.build(this._def.label),
indicator = DH.build(this._def.indicator);
DH.attributes(node, {title: tooltipText});
DH.append(label, labelText);
DH.append(node, [label, indicator]);
DH.on(node, 'click', bind(this._onClick, this));
this._node = node;
this._indicator = indicator.firstChild;
merge(this, callbacks);
}
Button.prototype = {
_indicator: null,
_node: null,
_def: {
node: {
tag: 'button',
style: {
'margin': '2px'
},
attributes: {
'type': 'button',
'class': 'yt-uix-button yt-uix-button-default yt-uix-tooltip'
}
},
label: {
tag: 'span',
attributes: {
'class': 'yt-uix-button-content'
}
},
indicator: {
tag: 'span',
style: {
'font-size': '14px',
'margin-left': '5px'
},
attributes: {
'class': 'yt-uix-button-content'
},
children: '-'
}
},
_onClick: function() {
this.handler();
this.refresh();
},
refresh: function() {
this._indicator.data = this.display();
},
render: function() {
this.refresh();
return this._node;
},
handler: emptyFn,
display: emptyFn
};
/*
* PlayerOption class.
*/
function PlayerOption(key, overrides) {
merge(this, overrides);
this._key = key;
}
PlayerOption.prototype = {
_player: null,
_key: null,
label: null,
tooltip: null,
states: [],
_step: function() {
this.set((this.get() + 1) % this.states.length);
},
_indicator: function() {
return _(this.states[this.get()]);
},
get: function() {
return Number(Config.get(this._key) || 0);
},
set: function(value) {
Config.set(this._key, Number(value));
},
button: function(type) {
return new type(_(this.label), _(this.tooltip), {
handler: bind(this._step, this),
display: bind(this._indicator, this)
});
},
init: function(player) {
this._player = player;
this.configure();
},
configure: emptyFn,
apply: emptyFn
};
/*
* Prevent autoplaying.
*/
var AutoPlay = new PlayerOption('auto_play', {
_applied: false,
_focused: false,
_muted: false,
_player: null,
_timer: null,
label: 'Auto play',
tooltip: 'Toggle video autoplay',
states: ['ON', 'OFF', 'AUTO'],
_onFocus: function() {
if (this._applied && ! this._focused) {
this._timer = setTimeout(bind(function() {
this._player.resetState();
this._player.playVideo();
debug('Playback autostarted');
this._focused = true;
this._timer = null;
}, this), 500);
}
},
_onBlur: function() {
if (this._timer !== null) {
clearTimeout(this._timer);
this._timer = null;
}
},
// @see http://www.w3.org/TR/page-visibility/
_isVisible: function() {
var doc = unsafeWindow.document;
return doc.hidden === false || doc.mozHidden === false || doc.webkitHidden === false;
},
configure: function() {
if (this._player.isAutoPlaying()) {
switch (this.get()) {
case 0: // ON
this._applied = true;
break;
case 1: // OFF
this._applied = false;
break;
case 2: // AUTO
// Video is visible or opened in the same window.
if (this._focused || this._isVisible() || unsafeWindow.history.length > 1) {
this._applied = true;
}
// Video is opened in a background tab.
else {
DH.on(unsafeWindow, 'focus', bind(this._onFocus, this));
DH.on(unsafeWindow, 'blur', bind(this._onBlur, this));
this._applied = false;
}
break;
}
}
else
this._applied = true;
},
apply: function() {
if (! this._applied) {
if (! this._muted) {
this._player.mute();
this._muted = true;
}
if (this._player.getPlayerState() == Player.PLAYING) {
this._applied = true;
this._player.seekToStart(true);
this._player.pauseVideo();
debug('Playback paused');
this._player.unMute();
this._muted = false;
}
}
else {
if (this._player.getPlayerState() == Player.PLAYING)
this._focused = true;
}
}
});
/*
* Set video quality.
*/
var VideoQuality = new PlayerOption('video_quality', {
_applied: false,
_muted: false,
_buffered: false,
_player: null,
_qualities: [, 'small', 'medium', 'large', 'hd720', 'hd1080', 'highres'],
label: 'Quality',
tooltip: 'Set default video quality',
states: ['AUTO', '240p', '360p', '480p', '720p', '1080p', 'ORIGINAL'],
configure: function() {
this._applied = ! this.get();
},
apply: function() {
if (! this._applied) {
if (! this._muted) {
this._player.mute();
this._muted = true;
}
if (this._player.getPlayerState() > Player.UNSTARTED) {
if (this._player.getAvailableQualityLevels().length) {
this._applied = true;
var quality = this._qualities[this.get()];
if (quality && quality != this._player.getPlaybackQuality()) {
this._buffered = false;
this._player.seekToStart(true);
this._player.setPlaybackQuality(quality);
debug('Quality changed to', quality);
// Sometimes buffering event doesn't occur after the quality has changed.
this.apply();
return;
}
}
}
else
return;
this._player.unMute();
this._muted = false;
}
else {
switch (this._player.getPlayerState()) {
case Player.BUFFERING:
this._buffered = true;
break;
case Player.PLAYING:
this._buffered = true;
// no break;
default:
if (this._buffered && this._muted) {
this._player.unMute();
this._muted = false;
}
}
}
}
});
/*
* Set player size.
*/
var PlayerSize = new PlayerOption('player_size', {
label: 'Size',
tooltip: 'Set default player size',
states: ['AUTO', 'WIDE', 'FIT'],
apply: function() {
var mode = this.get();
switch (mode) {
case 2: // FIT
DH.append(document.body, {
tag: 'style',
attributes: {
'type': 'text/css'
},
children: [
'.watch-medium .watch7-playlist-bar {',
'width: 945px;',
'}',
'.watch-medium #watch7-playlist-tray-container {',
'height: 533px;',
'}',
'.watch-medium.watch-playlist-collapsed #watch7-playlist-tray-container {',
'height: 0;',
'}',
'.watch-medium #player-api {',
'width: 945px;',
'height: 560px;',
'}'
]
});
// no break;
case 1: // WIDE
DH.addClass(DH.id('watch7-container'), 'watch-wide watch-medium watch-playlist-collapsed');
break;
default:
return;
}
debug('Size set to', ['wide', 'fit'][mode - 1]);
}
});
/*
* Abstract UI class.
*/
function UI(buttons) {
this.buttons = buttons;
this.button = DH.build(this._def.button(bind(this.toggle, this)));
this.panel = DH.build(this._def.panel(buttons));
}
UI.setVisible = function(node, visible) {
DH[visible ? 'delClass' : 'addClass'](node, 'hid');
DH.style(node, {
'display': visible ? 'block' : 'none'
});
};
UI.prototype = {
_def: {
icon: {
tag: 'img',
attributes: {
'src': 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAA4ElEQVQoz32RMU4CQRhG38xqQ0e7CbHCnnxHEM/AEUiIthZegFAYErIhegTuwAWIGYiWWGKypY0bkgUZCxZ2JIuvmnkz8//fzECA2ppqqnbozJ8NOZfA2tVKZwE0lFcGbADwoExeo6KCujxTzb1LLBBxDgsRpK/xmtuK5Uf3BEZvNKgXakEHmNAq5t+sjHxw5tp9gJosT27xHxe8By0m2rc4kPFpAPTAoDJkHyJQj2Fl9Zv4K51Z4OdsgB1YcC8kQO4MOQSjsUvKb9pn2crLa1ua4zOnAMRzrlhxly4PBn4BWEpBljV5iJUAAAAASUVORK5CYII='}
},
button: function(click) {
return {
tag: 'span',
children: {
tag: 'button',
attributes: {
'type': 'button',
'role': 'button',
'class': 'action-panel-trigger yt-uix-button yt-uix-button-text yt-uix-button-empty yt-uix-tooltip',
'data-button-toggle': 'true',
'data-trigger-for': 'action-panel-yays',
'data-tooltip-text': _('Player settings')
},
children: {
tag: 'span',
attributes: {
'class': 'yt-uix-button-icon-wrapper'
},
children: [this.icon, {
tag: 'span',
attributes: {
'class': 'yt-uix-button-valign'
}
}]
},
listeners: {
'click': click
}
}
};
},
panel: function(buttons) {
return [{
style: {
'margin-bottom': '10px'
},
children: [{
tag: 'strong',
children: _('Player settings')
}, {
tag: 'a',
attributes: {
'href': Meta.site,
'target': '_blank'
},
style: {
'margin-left': '4px',
'vertical-align': 'super',
'font-size': '10px'
},
children: _('Help')
}]
}, {
style: {
'text-align': 'center',
},
children: map(bind(Button.prototype.render.call, Button.prototype.render), buttons)
}];
}
},
buttons: null,
button: null,
panel: null,
refresh: function() {
each(this.buttons, function(i, button) { button.refresh(); });
},
toggle: emptyFn
};
/*
* WatchUI class.
*/
function WatchUI() {
UI.call(this, [
VideoQuality.button(Button),
PlayerSize.button(Button),
AutoPlay.button(Button)
]);
this.panel = DH.build({
attributes: {
'id': 'action-panel-yays',
'class': 'action-panel-content hid',
'data-panel-loaded': 'true'
},
style: {
'display': 'none',
'color': '#333'
},
children: this.panel
});
DH.append(DH.id('watch7-secondary-actions'), this.button);
DH.prepend(DH.id('watch7-action-panels'), this.panel);
PlayerSize.apply();
}
WatchUI.prototype = extend(UI, {
toggle: function() {
this.refresh();
}
});
/*
* ChannelUI class.
*/
function ChannelUI() {
UI.call(this, [
VideoQuality.button(Button),
AutoPlay.button(Button)
]);
this.panel = DH.build({
attributes: {
'class': 'hid'
},
style: {
'display': 'none',
'margin-top': '7px'
},
children: this.panel
});
DH.append(DH.walk(DH.id('flag-video-panel'), '../h3/div'), [' ', this.button]);
DH.insertAfter(DH.id('flag-video-panel'), this.panel);
}
ChannelUI.prototype = extend(UI, {
_def: merge({
button: function(click) {
return {
tag: 'button',
style: {
'padding': '0 4px'
},
attributes: {
'type': 'button',
'role': 'button',
'class': 'yt-uix-button yt-uix-button-default yt-uix-tooltip yt-uix-tooltip-reverse yt-uix-button-empty',
'title': _('Player settings')
},
children: this.icon,
listeners: {
'click': click
}
};
}
}, UI.prototype._def, false),
toggle: function() {
if (DH.hasClass(this.panel, 'hid')) {
each(this.panel.parentNode.childNodes, function(i, node) {
if (node.nodeType == DH.ELEMENT_NODE && node.tagName.toLowerCase() == 'div')
UI.setVisible(node, false);
});
this.refresh();
UI.setVisible(this.panel, true);
}
else
UI.setVisible(this.panel, false);
}
});
/*
* Player state change callback.
*/
unsafeWindow[Meta.ns].onPlayerStateChange = asyncProxy(function(state) {
debug('State changed to', ['unstarted', 'ended', 'playing', 'paused', 'buffering'][state + 1]);
AutoPlay.apply();
// Pausing playback doesn't have any effect if we rebuffer the video in the new quality level immediately.
asyncCall(VideoQuality.apply, VideoQuality);
});
/*
* Player ready callback.
*/
var onPlayerReady = asyncProxy(function() {
var element = DH.id('movie_player') || DH.id('movie_player-flash') || DH.id('movie_player-html5');
if (element) {
try {
Player.initialize(DH.unwrap(element)).onReady(function(player) {
debug('Player ready');
each([AutoPlay, VideoQuality, PlayerSize], function(i, option) {
option.init(player);
});
AutoPlay.apply();
VideoQuality.apply();
player.addEventListener('onStateChange', Meta.ns + '.onPlayerStateChange');
});
}
catch (e) {
debug(e);
}
}
});
each(['onYouTubePlayerReady', 'ytPlayerOnYouTubePlayerReady'], function(i, callback) {
unsafeWindow[callback] = extendFn(unsafeWindow[callback], onPlayerReady);
});
onPlayerReady();
var page = DH.id('page');
if (page) {
if (DH.hasClass(page, 'watch'))
new WatchUI();
else if (DH.hasClass(page, 'channel'))
new ChannelUI();
}
} // YAYS
if (window.top === window.self) {
if (this['unsafeWindow']) { // Greasemonkey.
YAYS(unsafeWindow);
}
else {
var node = document.createElement('script');
node.setAttribute('type', 'text/javascript');
node.text = '('.concat(YAYS.toString(), ')(window);');
document.body.appendChild(node);
document.body.removeChild(node);
}
}