// ==UserScript==
// @name           FYTE /Fast YouTube Embedded/ Player
// @description    Hugely improves load speed of pages with lots of embedded Youtube videos by instantly showing clickable and immediately accessible placeholders, then the thumbnails are loaded in background. HTML5 direct playback (720p max) is used when selected and available.
// @description:ru На порядок ускоряет время загрузки страниц с большим количеством вставленных Youtube-видео. С первого момента загрузки страницы появляются заглушки для видео, которые можно щелкнуть для загрузки плеера, и почти сразу же появляются кавер-картинки с названием видео. В опциях можно включить режим использования упрощенного браузерного плеера (макс. 720p).
// @version        2.4.2
// @include        *
// @exclude        https://www.youtube.com/*
// @author         wOxxOm
// @namespace      wOxxOm.scripts
// @license        MIT License
// @grant          GM_getValue
// @grant          GM_setValue
// @grant          GM_addStyle
// @grant          GM_xmlhttpRequest
// @connect        www.youtube.com
// @connect        youtube.com
// @run-at         document-start
// @icon           data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACABAMAAAAxEHz4AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAwUExURUxpcQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJuxkb8AAAAPdFJOUwDvH0biMvjOZFW6pxJ6kh7r3iMAAAPDSURBVGje7ZlNaBNBFMeXhNDWpkKgFg9iYlBB6SGwiPQkftaCYATjTaRKiwi1xVaKXpqTpHhosR66p4pQhb209CQGbSweK/TiRYwfFy+NkWBM2pR2nHm73abJzuceRMj/kEzSvl92Z9689+atpjXUUEMN1WgpoRupbH41nTbNUaxzlkIhe0C+M810Ov8zmzL0RGeNeeDThUEkof72N/Fqe/8LJK07sR173yJS0EbEATxFSurZtm9DilqxAV9VAZuWfbPbLBOFqtSBP9f/WxIAV2Bc6H5owiKPG7p+IpFIRG11LsPbEfyVrhvTqeyX1dfmaBiM9gFgjgwrTzJSfncMFq7s3EExJuu5/rHte3hPBvfkff84sbuEBxPkUiLygCC5hDV7CvpUtt81axICZBN9UwHsxYalOMxhIaIC8IVhFlvJtlALIWQl57Um/LquBpjBpkOwin1qADKLB7RD9moqiPz2TcAMqQGa4OI9Av5op/DrMzXAHmz6mw4IxEQA67AW825/bhngAVoBMEHzZD+aFQCsQUCkAAor/M2wCYAVdwCqxJmANgD8cmJjPQDt5wK22AD0nAVoBsAiE1BMcgAbAJikAqoTYP1CA4BEtBgdgC6yARUuAC3QI7sDiLMAxUk2YAwiIwNAn4YAhGU+YKcOqAUMCgJQziugHGMALmNAhANAWxkaoEgABS4ADdMyiyiglPMIcJ0GKQAayDAAGQEAuu8VUB/gJAH1AS4IgLAwAA24AAoygAeuAPFbqHPHoNwc1HuCJCDncRl7NG8At7Ak48qugVEGsOBxO7snB58T0ngASlwWjomFpMegOusxrFOLBCexsFMbvUzxCyVXRqEkBpjlpXdOgcEqFlsEKpRynFviMIus0md+kcUEDAuUeaxCcysjUGgySt1yTKTUZRTbOaFim17unxUr92doBw4f9zTKObGInZl+//NTW592VP3g+Q4Onh6Ovjfgt5vsPoSCJuDuPRz/58CFmhEtKPIEvY8kZAd3VxRxRJJSyIXcUu0/VOz3okITJRC2ex9kGdB5ecBVZLtgCyt70fUB2nGTTjOu/HFZohsXXLoOrbQKfDps1ePtTj9wSter2oGWoBnYRZqB+bQ5OnLaShpnrNAz6N6R7OW1I1HJjnmPVFuit7eDV1jNvuAkpJNqgJ0DQPCHiv3dqmULfJe3P7hrB/oej3T0S/Tme7tf1Xp/MArPB/Ayp82X5OlAaJfI8wHsJ2/zWXg6EGV4XXB5CbuN3mUYxnQKNI6HU9i3op0y3tpQQw39b/oLfDt0HcsiqWsAAAAASUVORK5CYII=
// @compatible     chrome
// @compatible     firefox
// @compatible     opera
// ==/UserScript==

/* jshint lastsemic:true, multistr:true, laxbreak:true, -W030, -W041, -W084 */

var resizeMode = GM_getValue('resize', 'Fit to width');
if (typeof resizeMode != 'string')
	resizeMode = resizeMode ? 'Fit to width' : 'Original';

var resizeWidth = GM_getValue('width', 1280) |0;
var resizeHeight = GM_getValue('height', 720) |0;
updateCustomSize();

var playDirectly = !!GM_getValue('playHTML5', false);
var skipCustom = !!GM_getValue('skipCustom', true);
var showStoryboard = !!GM_getValue('showStoryboard', true);

var _ = initTL();
var iframes = document.getElementsByTagName('iframe');
var oembeds = document.getElementsByTagName('embed');
var persite = (function() {
	var rules = [
		{host: /(^|\.)google\.\w{2,3}(\.\w{2,3})?$/, class:'g-blk', query: 'a[href*="youtube.com/watch"][data-ved]', eatparent: 1},
		{host: /(^|\.)pikabu\.ru$/, class:'b-video', match: '[data-url*="youtube.com/embed"]', attr: 'data-url'},
		{host: /(^|\.)androidauthority\.com$/, tag:'iframe', match: '[src*="youtube.com/embed"]', eatparent: 2},
	];
	for (var i=0, rule; (i<rules.length) && (rule=rules[i]); i++) {
		if (rule.host && location.hostname.match(rule.host) && (rule.class || rule.tag))
			return {
				nodes: rule.class ? document.getElementsByClassName(rule.class) : document.getElementsByTagName(rule.tag),
				match: rule.match ? function(e) { return e.matches(rule.match) ? e : null }
				                  : function(e) { return e.querySelector(rule.query) },
				attr: rule.attr,
				eatparent: rule.eatparent,
			};
	}
})();

findEmbeds();
injectStylesIfNeeded();
new MutationObserver(findEmbeds).observe(document, {subtree:true, childList:true});

document.addEventListener('DOMContentLoaded', function(e) {
	injectStylesIfNeeded();
	adjustNodesIfNeeded(e);
});
window.addEventListener('resize', adjustNodesIfNeeded, true);
window.addEventListener('message', function(e) {
	if (e.data == 'iframe-allowfs') {
		$$('iframe:not([allowfullscreen])').some(function(iframe) {
			if (iframe.contentWindow == e.source) {
				iframe.allowFullscreen = true;
				return true;
			}
		});
		if (window != window.top)
			window.parent.postMessage('iframe-allowfs', '*');
	}
});

function findEmbeds(mutations) {
	var i, len, e;
	if (mutations && mutations.length == 1 && !mutations[0].addedNodes.length)
		return;
	if (persite)
		for (i=0, len=persite.nodes.length; (i<len) && (e=persite.nodes[i]); i++)
			if (e = persite.match(e))
				processEmbed(e, e.getAttribute(persite.attr));
	for (i=0, len=iframes.length; (i<len) && (e=iframes[i]); i++)
		if (/youtube\.com(\/|%2F)(embed|v)\//i.test(e.src))
			processEmbed(e);
	for (i=0, len=oembeds.length; (i<len) && (e=oembeds[i]); i++)
		if (/youtube\.com(\/|%2F)(embed|v)\//i.test(e.src))
			processEmbed(e);
}

function processEmbed(node, src) {
	function decodeEmbedUrl(url) {
		return url.indexOf('youtube.com%2Fembed') > 0
			 ? decodeURIComponent(url.replace(/^.*?(http[^&?=]+?youtube.com%2Fembed[^&]+).*$/i, '$1'))
		     : url;
	}
	src = src || node.src || node.href || '';
	var n = node;
	var np = n.parentNode, npw;
	var srcFixed = decodeEmbedUrl(src).replace(/\/(watch\?v=|v\/)/, '/embed/');
	if (src.indexOf('cdn.embedly.com/') > 0 ||
		resizeMode != 'Original' && np && np.children.length == 1 && !np.className && !np.id)
	{
		n = location.hostname == 'disqus.com' ? np.parentNode : np;
		np = n.parentElement;
	}
	if (!np ||
		!np.parentNode ||
		skipCustom && srcFixed.indexOf('enablejsapi=1') > 0 ||
		n.className.indexOf('instant-youtube-') >= 0 ||
		n.onload // skip some retarded loaders
	)
		return;

	var id = srcFixed.match(/(?:embed\/|v[=\/])([^\s,.()\[\]?]+?)(?:[&?\/].*|$)/);
	if (!id)
		return;
	id = id[1];

	var eatparent = persite && persite.eatparent || 0;
	while (eatparent--)
		n = np, np = n.parentElement;

	var div = document.createElement('div');
	div.className = 'instant-youtube-container';
	div.FYTE = {
		state: 'querying',
		srcEmbed: srcFixed.replace(/&$/, ''),
		videoID: id,
		originalWidth: node.width|0 || n.clientWidth|0,
		originalHeight: node.height|0 || n.clientHeight|0,
	};
	div.FYTE.srcEmbedFixed = div.FYTE.srcEmbed.replace(/^http:/, 'https:').replace(/&?wmode=\w+/, '').replace(/[?&]feature=oembed/, '');
	div.FYTE.srcWatchFixed = div.FYTE.srcEmbedFixed.replace(/\/embed\//, '/watch?v=');

	var divSize = calcContainerSize(div, n);
	var origStyle = getComputedStyle(n);
	div.style.cssText = important('background-color:transparent; transition:background-color 2s;' +
		(origStyle.hasOwnProperty('position') ? Object.keys(origStyle) : Object.values(origStyle))
			.filter(function(k) { return !!k.match(/^(position|left|right|top|bottom|margin.*?)$/) })
			.map(function(k) { return k + ':' + origStyle[k] })
			.join(';').replace(/\b[^;:]+:(auto|static)(!\s*important)?;/g, '') +
		';max-width:' + divSize.w + 'px; height:' + divSize.h + 'px;');
	setTimeout(function() { div.style.backgroundColor = '' }, 0);
	setTimeout(function() { div.style.transition = '' }, 2000);

	// consume parents of retardedly positioned videos
	if (div.style.position.match('absolute|relative')) {
		if (np.children.length == 1 && floatPadding(np, getComputedStyle(np, ':after'), 'Top') >= div.FYTE.originalHeight)
			n = np, np = n.parentElement;
		div.style.cssText = div.style.cssText.replace(/\b(position|left|top|right|bottom):[^;]+/g, '');
	}

	var wrapper = div.appendChild(document.createElement('div'));
	wrapper.className = 'instant-youtube-wrapper';

	var img = wrapper.appendChild(document.createElement('img'));
	img.className = 'instant-youtube-thumbnail';
	img.src = 'https://i.ytimg.com/vi/' + id + '/maxresdefault.jpg';
	img.style.cssText = important('transition:opacity 0.1s ease-out; opacity:0; padding:0; margin:auto; position:absolute; left:0; right:0; top:0; bottom:0; max-width:none; max-height:none;');

	img.title = _('Shift-click to use alternative player');
	img.onload = function(e) {
		if (img.naturalWidth <= 120)
			return img.onerror(e);
		var fitToWidth = true;
		if (img.naturalHeight) {
			var ratio = img.naturalWidth / img.naturalHeight;
			if (ratio > 4.1/3 && ratio < divSize.w/divSize.h) {
				img.style.cssText += important('width:auto; height:100%;');
				fitToWidth = false;
			}
		}
		if (fitToWidth) {
			img.style.cssText += important('width:100%; height:auto;');
		}
		if (img.videoWidth)
			fixThumbnailAR(div);
		img.style.opacity = 1;
	};
	img.onerror = function(e) {
		if (img.src.indexOf('maxresdefault') > 0)
			img.src = img.src.replace('maxresdefault','hqdefault');
	};

	GM_xmlhttpRequest({
		method: 'GET',
		url: 'https://www.youtube.com/get_video_info?video_id=' + div.FYTE.videoID + '&el=detailpage',
		headers: {'Accept-Encoding': 'gzip'},
		context: div,
		onload: parseVideoInfo
	});

	translateHTML(wrapper, 'beforeend', '\
		<a class="instant-youtube-title" target="_blank" href="' + div.FYTE.srcWatchFixed + '">&nbsp;</a>\
		<svg class="instant-youtube-play-button"><path fill-rule="evenodd" clip-rule="evenodd" fill="#1F1F1F" class="ytp-large-play-button-svg" d="M84.15,26.4v6.35c0,2.833-0.15,5.967-0.45,9.4c-0.133,1.7-0.267,3.117-0.4,4.25l-0.15,0.95c-0.167,0.767-0.367,1.517-0.6,2.25c-0.667,2.367-1.533,4.083-2.6,5.15c-1.367,1.4-2.967,2.383-4.8,2.95c-0.633,0.2-1.316,0.333-2.05,0.4c-0.767,0.1-1.3,0.167-1.6,0.2c-4.9,0.367-11.283,0.617-19.15,0.75c-2.434,0.034-4.883,0.067-7.35,0.1h-2.95C38.417,59.117,34.5,59.067,30.3,59c-8.433-0.167-14.05-0.383-16.85-0.65c-0.067-0.033-0.667-0.117-1.8-0.25c-0.9-0.133-1.683-0.283-2.35-0.45c-2.066-0.533-3.783-1.5-5.15-2.9c-1.033-1.067-1.9-2.783-2.6-5.15C1.317,48.867,1.133,48.117,1,47.35L0.8,46.4c-0.133-1.133-0.267-2.55-0.4-4.25C0.133,38.717,0,35.583,0,32.75V26.4c0-2.833,0.133-5.95,0.4-9.35l0.4-4.25c0.167-0.966,0.417-2.05,0.75-3.25c0.7-2.333,1.567-4.033,2.6-5.1c1.367-1.434,2.967-2.434,4.8-3c0.633-0.167,1.333-0.3,2.1-0.4c0.4-0.066,0.917-0.133,1.55-0.2c4.9-0.333,11.283-0.567,19.15-0.7C35.65,0.05,39.083,0,42.05,0L45,0.05c2.467,0,4.933,0.034,7.4,0.1c7.833,0.133,14.2,0.367,19.1,0.7c0.3,0.033,0.833,0.1,1.6,0.2c0.733,0.1,1.417,0.233,2.05,0.4c1.833,0.566,3.434,1.566,4.8,3c1.066,1.066,1.933,2.767,2.6,5.1c0.367,1.2,0.617,2.284,0.75,3.25l0.4,4.25C84,20.45,84.15,23.567,84.15,26.4z M33.3,41.4L56,29.6L33.3,17.75V41.4z"></path><polygon fill-rule="evenodd" clip-rule="evenodd" fill="#FFFFFF" points="33.3,41.4 33.3,17.75 56,29.6"></polygon></svg>\
		<span tl class="instant-youtube-link">' + (playDirectly ? 'Play with Youtube player' : 'Play directly (up to 720p)') + '</span>\
		<div class="instant-youtube-storyboard"></div>\
		<div tl class="instant-youtube-options-button">Options</div>\
	');

	if (np.localName == 'object')
		n = np, np = n.parentElement;
	np.insertBefore(div, n);
	n.remove();

	if (srcFixed.indexOf('autoplay=1') > 0)
		return startPlaying(div);

	div.addEventListener('click', clickHandler);
	if (showStoryboard)
		updateHoverHandler(div);
}

function updateHoverHandler(div) {
	if (!showStoryboard) {
		var storyboard = $(div, '.instant-youtube-storyboard');
		if (storyboard.innerHTML)
			storyboard.innerHTML = '';
		return;
	}
	div.addEventListener('mouseover', storyboardHoverHandler);

	function storyboardHoverHandler(e) {
		if (!showStoryboard)
			return;
		div.removeEventListener('mouseover', storyboardHoverHandler);
		$(div, '.instant-youtube-storyboard').innerHTML = '123'
			.replace(/./g, '<div><img src="https://i.ytimg.com/vi/' + div.FYTE.videoID + '/$&.jpg"></div>');
	}
}

function adjustNodesIfNeeded(e) {
	if (resizeMode != 'Original' && !adjustNodesIfNeeded.scheduled)
		adjustNodesIfNeeded.scheduled = setTimeout(function() {
			adjustNodes(e);
			adjustNodesIfNeeded.scheduled = 0;
		}, 16);
}

function adjustNodes(e, clickedContainer) {
	var force = !!clickedContainer;
	var nearest = force ? clickedContainer : null;

	var vids = $$('div.instant-youtube-container');

	if (!nearest && e.type != 'DOMContentLoaded') {
		var minDistance = window.innerHeight*3/4 |0;
		var nearTargetY = window.innerHeight/2;
		vids.forEach(function(n) {
			var bounds = n.getBoundingClientRect();
			var distance = Math.abs((bounds.bottom + bounds.top)/2 - nearTargetY);
			if (distance < minDistance) {
				minDistance = distance;
				nearest = n;
			}
		});
	}

	if (nearest) {
		var bounds = nearest.getBoundingClientRect();
		var nearestCenterYpct = (bounds.top + bounds.bottom)/2 / window.innerHeight;
	}

	var resized = false;

	vids.forEach(function(n) {
		var size = calcContainerSize(n);
		var w = size.w, h = size.h;

		// prevent parent clipping
		for (var e=n.parentElement, style; e && (style=getComputedStyle(e)); e=e.parentElement)
			if ((style.overflow+style.overflowX+style.overflowY).match(/hidden|scroll/))
				if (n.offsetTop < e.clientHeight / 2 && n.offsetTop + n.clientHeight > e.clientHeight)
					e.style.cssText = e.style.cssText.replace(/\boverflow(-[xy])?:[^;]+/g, '') +
						important('overflow:visible;overflow-x:visible;overflow-y:visible;');

		if (force && Math.abs(w - parseFloat(n.style.maxWidth)) <= 2)
			return;

		if (n.style.maxWidth != w + 'px') n.style.maxWidth = w + 'px';
		if (n.style.height != h + 'px')   n.style.height = h + 'px';

		var video = $(n, 'video');
		if (video) {
			video.width = w;
			video.height = h;
		}

		fixThumbnailAR(n);
		resized = true;
	});

	if (resized && nearest)
		setTimeout(function() {
			var bounds = nearest.getBoundingClientRect();
			var h = bounds.bottom - bounds.top;
			var projectedCenterY = nearestCenterYpct * window.innerHeight;
			var projectedTop = projectedCenterY - h/2;
			var safeTop = Math.min(Math.max(0, projectedTop), window.innerHeight - h);
			window.scrollBy(0, bounds.top - safeTop);
		}, 16);
}

function calcContainerSize(div, origNode) {
	var w, h;
	origNode = origNode || div;
	switch (resizeMode) {
		case 'Original':
			w = div.FYTE.originalWidth;
			h = div.FYTE.originalHeight;
			break;
		case 'Custom':
			w = resizeWidth;
			h = resizeHeight;
			break;
		case '1080p':
		case '720p':
		case '480p':
		case '360p':
			h = parseInt(resizeMode);
			w = h / 9 * 16;
			break;
		default: // fit-to-width mode
			var n = origNode;
			do {
				n = n.parentElement;
				// find parent node with nonzero width (i.e. independent of our video element)
			} while (n && !(w = n.clientWidth));
			if (w)
				h = w / 16 * 9;
			else {
				w = origNode.clientWidth;
				h = origNode.clientHeight;
			}
	}
	var np = origNode.parentElement;
	var style = getComputedStyle(np);
	var parentWidth = parseFloat(style.width) - floatPadding(np, style, 'Left') - floatPadding(np, style, 'Right');
	if (parentWidth > 0 && parentWidth < w) {
		h = parentWidth / w * h;
		w = parentWidth;
	}
	if (resizeMode == 'Fit to width' && h < div.FYTE.originalHeight*0.9)
		h = Math.min(div.FYTE.originalHeight, w / div.FYTE.originalWidth * div.FYTE.originalHeight);

	return {w:w, h:h};
}

function parseVideoInfo(response) {
	var div = response.context;
	var txt = response.responseText;
	var info = tryCatch(function() { return JSON.parse(txt.replace(/(\w+)=?(.*?)(&|$)/g, '"$1":"$2",').replace(/^(.+?),?$/, '{$1}')) });
	var videoSources = [];

	// parse width & height to adjust the thumbnail
	var m = decodeURIComponent(txt).match(/\b(\d+)x(\d+)\b/);
	if (m)
		fixThumbnailAR(div, m[1]|0, m[2]|0);

	// parse video sources
	if (info.url_encoded_fmt_stream_map && info.fmt_list) {
		var streams = {};
		decodeURIComponent(info.url_encoded_fmt_stream_map).split(',').forEach(function(stream) {
			var params = {};
			stream.split('&').forEach(function(kv) {
				params[kv.split('=')[0]] = decodeURIComponent(kv.split('=')[1]);
			});
			streams[params.itag] = params;
		});
		decodeURIComponent(info.fmt_list).split(',').forEach(function(fmt) {
			var itag = fmt.split('/')[0];
			var dimensions = fmt.split('/')[1];
			var stream = streams[itag];
			if (stream) {
				videoSources.push({
					src: stream.url,
					title: stream.quality + ', ' + dimensions + ', ' + stream.type
				});
			}
		});
	} else {
		var rx = /url=([^=]+?mime%3Dvideo%252F(?:mp4|webm)[^=]+?)(?:,quality|,itag|.u0026)/g;
		var text = decodeURIComponent(txt).split('url_encoded_fmt_stream_map')[1];
		while (m = rx.exec(text)) {
			videoSources.push({
				src: decodeURIComponent(decodeURIComponent(m[1]))
			});
		}
	}

	var duration = info.length_seconds|0 || '';
	if (duration) {
		var d = new Date(null);
		d.setSeconds(duration);
		duration = d.toISOString().replace(/^.+?T[0:]{0,4}(.+?)\..+$/, '<span>$1</span>');
	}
	var title = decodeURIComponent(info.title || info.reason || '').replace(/\+/g, ' ');
	if (title || duration) {
		$(div, '.instant-youtube-title').innerHTML = (title ? '<strong>' + title + '</strong>' : '') + duration;
	}
	if (info.reason)
		div.setAttribute('disabled', '');

	if (videoSources.length)
		div.FYTE.videoSources = videoSources;

	injectStylesIfNeeded();

	if (div.FYTE.state == 'scheduled play')
		setTimeout(function() { startPlayingDirectly(div) }, 0);

	div.FYTE.state = '';
}

function fixThumbnailAR(div, w, h) {
	var img = $(div, 'img');
	var thw = img.naturalWidth, thh = img.naturalHeight;
	if (w && h) { // means thumbnail is still loading
		img.videoWidth = w;
		img.videoHeight = h;
		return;
	}
	w = img.videoWidth;
	h = img.videoHeight;
	var divw = div.clientWidth, divh = div.clientHeight;
	// if both video and thumbnail are 4:3, fit the image to height
	//console.log(divw, divh, thw, thh, w, h, h/w*divw / divh - 1, thh/thw*divw / divh - 1);
	if (Math.abs(h/w*divw / divh - 1) > 0.05 && Math.abs(thh/thw*divw / divh - 1) > 0.05) {
		img.style.maxHeight = img.clientHeight + 'px';
		if (!img.videoWidth) // skip animation if thumbnail is already loaded
			img.style.transition = 'height 1s ease, margin-top 1s ease';
		setTimeout(function() {
			img.style.maxHeight = 'none';
			img.style.cssText += important(h/w >= divh/divw ? 'width:auto; height:100%;' : 'width:100%; height:auto;');
			setTimeout(function() {
				img.style.transition = '';
			}, 1000);
		}, 0);
	}
}

function clickHandler(e) {
	if (e.target.href)
		return;
	if (e.target.matches('.instant-youtube-options-button')) {
		showOptions(e);
		e.preventDefault();
		e.stopPropagation();
		return;
	}
	if (e.target.matches('.instant-youtube-options, .instant-youtube-options *'))
		return;

	e.preventDefault();
	e.stopPropagation();

	var alternateMode = e.shiftKey || e.target.className == 'instant-youtube-link';
	startPlaying(e.target.closest('.instant-youtube-container'), alternateMode);
}

function startPlaying(div, alternateMode) {
	div.removeEventListener('click', clickHandler);

	$$(div, '.instant-youtube-wrapper > *:not(img):not(a)').forEach(function(e) { e.style.cssText = 'display:none!important' });
	$(div, 'svg').outerHTML = '<span class="instant-youtube-loading-button"></span>';

	if (window != window.top)
		window.parent.postMessage('iframe-allowfs', '*');

	if ((!!playDirectly + !!alternateMode == 1) && (div.FYTE.videoSources || div.FYTE.state == 'querying')) {
		if (div.FYTE.videoSources)
			startPlayingDirectly(div);
		else {
			// playback will start in parseVideoInfo
			div.FYTE.state = 'scheduled play';
			// fallback to iframe in 5s
			setTimeout(function() {
				if (div.FYTE.state) {
					div.FYTE.state = '';
					switchToIFrame.call(div);
				}
			}, 5000);
		}
	}
	else
		switchToIFrame.call(div);
}

function startPlayingDirectly(div) {
	var video = document.createElement('video');
	video.controls = true;
	video.autoplay = true;
	video.style.cssText = important(
		'position:absolute; left:0; top:0; right:0; padding:0; margin:auto; opacity:0; transition:opacity 2s;' +
		'width:100%; height:100%;');
	video.className = 'instant-youtube-embed';
	video.volume = GM_getValue('volume', 0.5);

	div.FYTE.videoSources.forEach(function(src) {
		var srcdom = video.appendChild(document.createElement('source'));
		Object.keys(src).forEach(function(k) { srcdom[k] = src[k] });
		srcdom.onerror = switchToIFrame.bind(div);
	});


	if (window.chrome) {
		video.addEventListener('click', function(e) {
			this.paused ? this.play() : this.pause();
		});
	}
	video.interval = (function() {
		return setInterval(function() {
			if (video.volume != GM_getValue('volume', 0.5))
				GM_setValue('volume', video.volume);
		}, 1000);
	})();
	var title = $(div, '.instant-youtube-title');
	if (title) {
		video.onpause = function() { title.removeAttribute('hidden') };
		video.onplay = function() { title.setAttribute('hidden', true) };
	}
	video.onloadeddata = function(e) {
		pauseOtherVideos(this);
		div.style.cssText += 'contain:none!important'; // allow fullscreen
		div.firstElementChild.appendChild(this);
		this.style.opacity = 1;
		var img = $(div, 'img');
		img.style.transition = 'opacity 1s';
		img.style.opacity = 0;
	};
}

function switchToIFrame(e) {
	var div = this;
	var wrapper = div.firstElementChild;
	if (e) {
		console.log('[FYTE] Direct linking canceled on %s, switching to IFRAME player', div.FYTE.srcEmbed);
		var video = e.target ? e.target.closest('video') : e.path && e.path[e.path.length-1];
		while (video.lastElementChild)
			video.lastElementChild.remove();
	}

	wrapper.insertAdjacentHTML('beforeend',
		'<iframe class="instant-youtube-embed" allowtransparency="true" src="' + div.FYTE.srcEmbedFixed +
		(div.FYTE.srcEmbedFixed.indexOf('?') > 0 ? '&' : '?') +
		'html5=1' +
		'&autoplay=1' +
		'&autohide=2' +
		'&border=0' +
		'&controls=1' +
		'&fs=1' +
		'&showinfo=1' +
		'&ssl=1' +
		'&theme=dark' +
		'&enablejsapi=1' +
		'" frameborder="0" allowfullscreen width="100%" height="100%"></iframe>');

	wrapper.lastElementChild.onload = function() {
		pauseOtherVideos(this);
		this.style.cssText = important(
			'position:absolute; left:0; top:0; right:0; padding:0; margin:auto; opacity:1; transition:opacity 2s;');

		div.setAttribute('iframe', '');
		div.style.cssText += 'contain:none!important'; // allow fullscreen
		setTimeout(function() {
			$(div, 'img').style.display = 'none';
			var title = $(div, '.instant-youtube-title');
			if (title)
				title.remove();
		}, 2000);
	};
}

function pauseOtherVideos(activePlayer) {
	$$(activePlayer.ownerDocument, '.instant-youtube-embed').forEach(function(v) {
		if (v == activePlayer)
			return;
		switch (v.localName) {
			case 'video':
				if (!v.paused)
					v.pause();
				break;
			case 'iframe':
				try { v.contentWindow.postMessage('{"event":"command", "func":"pauseVideo", "args":""}', '*') } catch(e) {}
				break;
		}
	});
}

function showOptions(e) {
	var optionsButton = e.target;
	translateHTML(optionsButton, 'afterend', '\
		<div class="instant-youtube-options">\
			<label tl style="width: 100% !important;">Size:<br>\
				<select data-action="size-mode">\
					<option tl value="Original">Original\
					<option tl value="Fit to width">Fit to width\
					<option>360p\
					<option>480p\
					<option>720p\
					<option>1080p\
					<option tl value="Custom">Custom...\
				</select>\
			</label>\
			<label data-action="size-custom" ' + (resizeMode != 'Custom' ? 'disabled' : '') + '>\
				<input type="number" min="320" max="9999" tl-placeholder="width" data-action="width" step="1" value="' + (resizeWidth||'') + '">\
				x\
				<input type="number" min="240" max="9999" tl-placeholder="height" data-action="height" step="1" value="' + (resizeHeight||'') + '">\
			</label>\
			<label tl="content,title" title="Show 3 thumbnails on mouse hover: at 25%, 50%, 75% of video duration">\
				<input data-action="storyboard" type="checkbox" ' + (showStoryboard ? 'checked' : '') + '>\
				Storyboard thumbs\
			</label>\
			<label tl="content,title" title="Tip: shift-clicking thumbnails will use alternative player">\
				<input data-action="direct" type="checkbox" ' + (playDirectly ? 'checked' : '') + '>\
				Play directly\
			</label>\
			<label tl="content,title" title="Do not process customized videos with enablejsapi=1 parameter (requires page reload)">\
				<input data-action="safe" type="checkbox" ' + (skipCustom ? 'checked' : '') + '>\
				Safe\
			</label>\
			<span data-action="buttons">\
				<button tl data-action="ok">OK</button>\
				<button tl data-action="cancel">Cancel</button>\
			</span>\
		</div>\
	');
	var options = optionsButton.nextElementSibling;

	options.addEventListener('keydown', function(e) {
		if (e.target.localName == 'input' &&
			!e.shiftKey && !e.altKey && !e.metaKey && !e.ctrlKey && e.key.match(/[.,]/))
			return false;
	});

	$(options, '[data-action="size-mode"]').value = resizeMode;
	$(options, '[data-action="size-mode"]').addEventListener('change', function() {
		var v = this.value != 'Custom';
		var e = $(options, '[data-action="size-custom"]');
		e.children[0].disabled = e.children[1].disabled = v;
		v ? e.setAttribute('disabled', '') : e.removeAttribute('disabled');
	});

	$(options, '[data-action="buttons"]').addEventListener('click', function(e) {
		if (e.target.dataset.action != 'ok') {
			options.remove();
			return;
		}
		var v, shouldAdjust;
		if (resizeMode != (v = $(options, '[data-action="size-mode"]').value)) {
			GM_setValue('resize', resizeMode = v);
			shouldAdjust = true;
		}
		if (resizeMode == 'Custom') {
			var w = $(options, '[data-action="width"]').value |0;
			var h = $(options, '[data-action="height"]').value |0;
			if (resizeWidth != w || resizeHeight != h) {
				updateCustomSize(w, h);
				GM_setValue('width', resizeWidth);
				GM_setValue('height', resizeHeight);
				shouldAdjust = true;
			}
		}
		if (showStoryboard != (v = $(options, '[data-action="storyboard"]').checked)) {
			GM_setValue('showStoryboard', showStoryboard = v);
			$$('.instant-youtube-container').forEach(updateHoverHandler);
		}
		if (playDirectly != (v = $(options, '[data-action="direct"]').checked)) {
			GM_setValue('playHTML5', playDirectly = v);
			$$('.instant-youtube-container .instant-youtube-link').forEach(function(e) {
				e.textContent = playDirectly ? 'Play with Youtube player' : 'Play directly (up to 720p)';
			});
		}
		if (skipCustom != (v = $(options, '[data-action="safe"]').checked)) {
			GM_setValue('skipCustom', skipCustom = v);
		}

		options.remove();

		if (shouldAdjust)
			adjustNodes(e, e.target.closest('.instant-youtube-container'));
	});
}

function updateCustomSize(w, h) {
	resizeWidth = Math.min(9999, Math.max(320, w|0 || resizeWidth|0));
	resizeHeight = Math.min(9999, Math.max(240, h|0 || resizeHeight|0));
}

function important(cssText) {
	return cssText.replace(/;/g, '!important;');
}

function tryCatch(func) {
	try {
		return func();
	} catch(e) {
		console.log(e);
	}
}

function getFunctionComment(fn) {
	return fn.toString().match(/\/\*([\s\S]*?)\*\/\s*\}$/)[1];
}

function $(selORnode, sel) {
	return sel ? selORnode.querySelector(sel)
	           : document.querySelector(selORnode);
}

function $$(selORnode, sel) {
	return Array.prototype.slice.call(
		sel ? selORnode.querySelectorAll(sel)
		    : document.querySelectorAll(selORnode));
}

// fix dumb Firefox bug
function floatPadding(node, style, dir) {
	var padding = style['padding' + dir];
	if (padding.indexOf('%') < 0)
		return parseFloat(padding);
	return parseFloat(padding) * (parseFloat(style.width) || node.clientWidth) / 100;
}

function translateHTML(baseElement, place, html) {
	var tmp = document.createElement('div');
	tmp.innerHTML = html;
	$$(tmp, '[tl]').forEach(function(node) {
		(node.getAttribute('tl') || 'content').split(',').forEach(function(what) {
			var child, src, tl;
			if (what == 'content') {
				for (var i = node.childNodes.length-1, n; (i>=0) && (n=node.childNodes[i]); i--) {
					if (n.nodeType == Node.TEXT_NODE && n.textContent.trim()) {
						child = n;
						break;
					}
				}
			} else
				child = node.getAttributeNode(what);
			if (!child)
				return;
			src = child.textContent;
			srcTrimmed = src.trim();
			tl = src.replace(srcTrimmed, _(srcTrimmed));
			if (src != tl)
				child.textContent = tl;
		});
	});
	baseElement.insertAdjacentHTML(place, tmp.innerHTML);
}

function initTL(src) {
	var tlSource = {
		'watch on Youtube': {
			'ru': 'открыть на Youtube',
		},
		'Play with Youtube player': {
			'ru': 'Включить плеер Youtube',
		},
		'Play directly (up to 720p)': {
			'ru': 'Включить напрямую (макс. 720p)',
		},
		'Shift-click to use alternative player': {
			'ru': 'Shift-клик для смены типа плеера',
		},
		'Options': {
			'ru': 'Опции',
		},
		'Size:': {
			'ru': 'Размер:',
		},
		'Original': {
			'ru': 'Исходный',
		},
		'Fit to width': {
			'ru': 'На всю ширину',
		},
		'Custom...': {
			'ru': 'Настроить...',
		},
		'width': {
			'ru': 'ширина',
		},
		'height': {
			'ru': 'высота',
		},
		'Storyboard thumbs': {
			'ru': 'Раскадровка',
		},
		'Show 3 thumbnails on mouse hover: at 25%, 50%, 75% of video duration': {
			'ru': 'Показывать 3 миникадра при наведении мыши: моменты на 25%, 50%, 75% времени видео',
		},
		'Play directly': {
			'ru': 'Плеер браузера',
		},
		'Tip: shift-clicking thumbnails will use alternative player': {
			'ru': 'Удерживайте клавишу Shift при щелчке на картинке для альтернативного плеера',
		},
		'Safe': {
			'ru': 'Консервативный режим',
		},
		'Do not process customized videos with enablejsapi=1 parameter (requires page reload)': {
			'ru': 'Не обрабатывать нестандартные видео с параметром enablejsapi=1 (подействует после обновления страницы)',
		},
		'OK': {
			'ru': 'ОК',
		},
		'Cancel': {
			'ru': 'Оменить',
		},
	};
	var browserLang = navigator.language || navigator.languages && navigator.languages[0] || '';
	var browserLangMajor = browserLang.replace(/-.+/, '');
	var tl = {};
	Object.keys(tlSource).forEach(function(k) {
		var langs = tlSource[k];
		var text = langs[browserLang] || langs[browserLangMajor];
		if (text)
			tl[k] = text;
	});
	return function(src) { return tl[src] || src };
}

function injectStylesIfNeeded() {
	var styledom = $('style#instant-youtube-styles');
	if (styledom) {
		// move our rules to the end of HEAD to increase CSS specificity
		if (styledom.nextElementSibling && document.head)
			document.head.insertBefore(styledom, null);
		return;
	}
	styledom = (document.head || document.documentElement).appendChild(document.createElement('style'));
	styledom.id = 'instant-youtube-styles';
	styledom.textContent = important(getFunctionComment(function() { /*
		.instant-youtube-container {
		    contain: strict;
		    position: relative;
		    overflow: hidden;
		    cursor: pointer;
		    padding: 0;
		    margin: 0;
		    font: normal 14px/1.0 sans-serif, Arial, Helvetica, Verdana;
		    text-align: center;
		    background: black;
		}
		.instant-youtube-container[disabled] {
		    background: #888;
		}
		.instant-youtube-container[disabled] .instant-youtube-storyboard {
		    display: none;
		}
		.instant-youtube-container .instant-youtube-wrapper {
		    width: 100%;
		    height: 100%;
		}
		.instant-youtube-container .instant-youtube-play-button {
		    display: block;
		    position: absolute;
		    width: 85px;
		    height: 60px;
		    left: 0;
		    right: 0;
		    top: 0;
		    bottom: 0;
		    margin: auto;
		}
		.instant-youtube-container .instant-youtube-loading-button {
		    display: block;
		    position: absolute;
		    width: 20px;
		    height: 20px;
		    left: 0;
		    right: 0;
		    top: 0;
		    bottom: 0;
		    padding: 0;
		    margin: auto;
		    background: url("data:image/gif;base64,R0lGODlhFAAUAJEDAMzMzLOzs39/f////yH/C05FVFNDQVBFMi4wAwEAAAAh+QQFCgADACwAAAAAFAAUAAACPJyPqcuNItyCUJoQBo0ANIxpXOctYHaQpYkiHfM2cUrCNT0nqr4uudsz/IC5na/2Mh4Hu+HR6YBaplRDAQAh+QQFCgADACwEAAIADAAGAAACFpwdcYupC8BwSogR46xWZHl0l8ZYQwEAIfkEBQoAAwAsCAACAAoACgAAAhccMKl2uHxGCCvO+eTNmishcCCYjWEZFgAh+QQFCgADACwMAAQABgAMAAACFxwweaebhl4K4VE6r61DiOd5SfiN5VAAACH5BAUKAAMALAgACAAKAAoAAAIYnD8AeKqcHIwwhGntEWLkO3CcB4biNEIFACH5BAUKAAMALAQADAAMAAYAAAIWnDSpAHa4GHgohCHbGdbipnBdSHphAQAh+QQFCgADACwCAAgACgAKAAACF5w0qXa4fF6KUoVQ75UaA7Bs3yeNYAkWACH5BAUKAAMALAIABAAGAAwAAAIXnCU2iMfaRghqTmMp1moAoHyfIYIkWAAAOw==");
		}
		.instant-youtube-container:hover .ytp-large-play-button-svg {
		    fill: #CC181E;
		}
		.instant-youtube-container .instant-youtube-link {
		    display: block;
		    position: absolute;
		    width: 20em;
		    height: 20px;
		    top: 50%;
		    left: 0;
		    right: 0;
		    margin: 60px auto;
		    padding: 0;
		    border: none;
		    text-align: center;
		    text-decoration: none;
		    text-shadow: 1px 1px 3px black;
		    font-weight: bold;
		    color: white;
		}
		.instant-youtube-container span.instant-youtube-link {
		    z-index: 8;
		    font-weight: normal;
		    font-size: 12px;
		}
		.instant-youtube-container .instant-youtube-link:hover {
		    text-decoration: underline;
		    color: white;
		    background: transparent;
		}
		.instant-youtube-container iframe {
		    z-index: 10;
		}
		.instant-youtube-container .instant-youtube-title {
		    z-index: 9;
		    display: block;
		    position: absolute;
		    width: 100%;
		    top: 0;
		    left: 0;
		    right: 0;
		    margin: 0;
		    padding: 7px;
		    border: none;
		    text-shadow: 1px 1px 2px black;
		    text-align: center;
		    text-decoration: none;
		    color: white;
		    background-color: rgba(0, 0, 0, 0.5);
		}
		.instant-youtube-container .instant-youtube-title strong {
		    font: bold 14px/1.0 sans-serif, Arial, Helvetica, Verdana;
		}
		.instant-youtube-container .instant-youtube-title strong:after {
		    content: " - $tl:'watch on Youtube'";
		    font-weight: normal;
		    margin-right: 1ex;
		}
		.instant-youtube-container .instant-youtube-title span {
		    color: inherit;
		}
		.instant-youtube-container .instant-youtube-title span:before {
		    content: "(";
		}
		.instant-youtube-container .instant-youtube-title span:after {
		    content: ")";
		}
		@-webkit-keyframes instant-youtube-fadein {
		    from { opacity: 0 }
		    to { opacity: 1 }
		}
		@-moz-keyframes instant-youtube-fadein {
		    from { opacity: 0 }
		    to { opacity: 1 }
		}
		@keyframes instant-youtube-fadein {
		    from { opacity: 0 }
		    to { opacity: 1 }
		}
		.instant-youtube-container:not(:hover) .instant-youtube-title[hidden] {
		    display: none;
		    margin: 0;
		}
		.instant-youtube-container .instant-youtube-title:hover {
		    text-decoration: underline;
		}
		.instant-youtube-container .instant-youtube-title strong {
		    color: white;
		}
		.instant-youtube-container .instant-youtube-options-button {
		    opacity: 0.6;
		    position: absolute;
		    right: 0;
		    bottom: 0;
		    margin: 0;
		    padding: 1.5ex 2ex;
		    font-size: 11px;
		    text-shadow: 1px 1px 2px black;
		    color: white;
		}
		.instant-youtube-container .instant-youtube-options-button:hover {
		    opacity: 1;
		    background: rgba(0, 0, 0, 0.5);
		}
		.instant-youtube-container .instant-youtube-options {
		    display: flex;
		    position: absolute;
		    right: 0;
		    bottom: 0;
		    margin: 0;
		    padding: 2ex 1ex 2ex 2ex;
		    flex-direction: column;
		    align-items: flex-start;
		    line-height: 1.5;
		    text-align: left;
		    opacity: 1;
		    color: white;
		    background: black;
		}
		.instant-youtube-container .instant-youtube-options * {
		    width: auto;
		    height: auto;
		    margin: 0;
		    padding: 0;
		    font: inherit;
		    font-size: 13px;
		    vertical-align: middle;
		    text-transform: none;
		    text-align: left;
		    border-radius: 0;
		    text-decoration: none;
		    color: white;
		    background: black;
		}
		.instant-youtube-container .instant-youtube-options > label {
		    margin-top: 1ex;
		}
		.instant-youtube-container .instant-youtube-options > label > * {
		    display: inline;
		}
		.instant-youtube-container .instant-youtube-options select {
		    width: 20ex;
		    padding: .5ex .25ex;
		    border: 1px solid #444;
		    -webkit-appearance: menulist;
		}
		.instant-youtube-container .instant-youtube-options [data-action="size-custom"] input {
		    width: 9ex;
		    padding: .5ex .5ex .4ex;
		    border: 1px solid #666;
		}
		.instant-youtube-container .instant-youtube-options [data-action="buttons"] {
		    margin-top: 1em;
		}
		.instant-youtube-container .instant-youtube-options button {
		    margin: 0 1ex 0 0;
		    padding: .5ex 2ex;
		    border: 2px solid gray;
		    font-weight: bold;
		}
		.instant-youtube-container .instant-youtube-options button:hover {
		    border-color: white;
		}
		.instant-youtube-container .instant-youtube-options > [disabled] {
		    opacity: 0.25;
		}
		.instant-youtube-container .instant-youtube-storyboard {
		    opacity: 0;
		    display: flex;
		    flex-direction: row;
		    position: absolute;
		    max-height: 33%;
		    left: 0;
		    right: 0;
		    bottom: 10px;
		    transition: opacity 1s ease;
		}
		.instant-youtube-container:hover .instant-youtube-storyboard {
		    opacity: 1;
		}
		.instant-youtube-container .instant-youtube-storyboard div {
		    flex: auto;
		}
		.instant-youtube-container .instant-youtube-storyboard img {
		    width: auto;
		    max-height: 100%;
		    border: 3px solid rgba(255, 255, 255, .5);
		    box-shadow: 2px 2px 10px black;
		}
	*/}).replace(/\$tl:'(.+?)'/g, function(m, m1) { return _(m1) })
	);
}