// ==UserScript==
// @name On-demand 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.
// @version 1.1.2
// @author wOxxOm
// @namespace wOxxOm.scripts
// @license MIT License
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_addStyle
// @grant GM_xmlhttpRequest
// @run-at document-start
// @require https://greasyfork.org/scripts/12228/code/setMutationHandler.js
// ==/UserScript==
var resizeToFit = GM_getValue('resize', true);
var embedSelector = 'iframe[src*="youtube.com/embed"], embed[src*="youtube.com/v"]';
processNodes(document.querySelectorAll(embedSelector));
document.addEventListener('DOMContentLoaded', function() {
processNodes(document.querySelectorAll(embedSelector));
setMutationHandler(document, embedSelector, processNodes);
GM_addStyle('\
.instant-youtube-container:not(small) {position:relative; overflow:hidden; cursor:pointer; background-color:black; padding:0; margin:0}\
.instant-youtube-container:not(small) .instant-youtube-thumbnail {transition:opacity 0.1s ease-out; opacity:0; padding:0; margin:0}\
.instant-youtube-container:not(small) .instant-youtube-play-button {position:absolute; left:0; right:0; top:0; bottom:0; margin:auto; width:85px; height:60px}\
.instant-youtube-container:not(small) .instant-youtube-loading-button {position:absolute; left:0; right:0; top:0; bottom:0; padding:0; margin:auto; display:block; width:20px; height:20px; background: url("data:image/gif;base64,R0lGODlhFAAUAJEDAMzMzLOzs39/f////yH/C05FVFNDQVBFMi4wAwEAAAAh+QQFCgADACwAAAAAFAAUAAACPJyPqcuNItyCUJoQBo0ANIxpXOctYHaQpYkiHfM2cUrCNT0nqr4uudsz/IC5na/2Mh4Hu+HR6YBaplRDAQAh+QQFCgADACwEAAIADAAGAAACFpwdcYupC8BwSogR46xWZHl0l8ZYQwEAIfkEBQoAAwAsCAACAAoACgAAAhccMKl2uHxGCCvO+eTNmishcCCYjWEZFgAh+QQFCgADACwMAAQABgAMAAACFxwweaebhl4K4VE6r61DiOd5SfiN5VAAACH5BAUKAAMALAgACAAKAAoAAAIYnD8AeKqcHIwwhGntEWLkO3CcB4biNEIFACH5BAUKAAMALAQADAAMAAYAAAIWnDSpAHa4GHgohCHbGdbipnBdSHphAQAh+QQFCgADACwCAAgACgAKAAACF5w0qXa4fF6KUoVQ75UaA7Bs3yeNYAkWACH5BAUKAAMALAIABAAGAAwAAAIXnCU2iMfaRghqTmMp1moAoHyfIYIkWAAAOw==")}\
.instant-youtube-container:hover:not(small) .ytp-large-play-button-svg {fill:#CC181E}\
.instant-youtube-container:not(small) .instant-youtube-link {\
position:absolute; top:50%; left:0; right:0; width:20em; height:1.7em; margin:60px auto; padding:0;\
display:block; text-align:center; text-decoration:none; color:white; text-shadow:1px 1px 3px black;\
font:14px/1.0 bold Arial,Helvetica,Verdana,Sans-serif;\
}\
.instant-youtube-container:not(small) .instant-youtube-link:hover { color:white; text-decoration:underline; background:transparent}\
.instant-youtube-container:not(small) .instant-youtube-embed {position:absolute; left:0; top:0; padding:0; margin:0}\
.instant-youtube-container:not(small) .instant-youtube-link2 {\
display:none; z-index:9; background-color:rgba(0,0,0,0.5); color: white;\
width:100%; height: 1.7em; top:0; left:0; right:0; position:absolute; z-index: 9;\
color:white; text-shadow:1px 1px 2px black; text-align:center; text-decoration:none;\
margin:0; padding:0.5em 0.5em 0.2em; font:14px/1.0 normal Arial,Helvetica,Verdana,Sans-serif;\
}\
.instant-youtube-container:not(small):hover .instant-youtube-link2 {display:block; margin:0}\
.instant-youtube-container:not(small) .instant-youtube-link2:hover {text-decoration:underline}\
.instant-youtube-container:not(small) .instant-youtube-options {position:absolute; right:0; bottom:0; color:white; text-shadow:1px 1px 2px black; padding:0; margin:0 }\
.instant-youtube-container:not(small) .instant-youtube-options * {font-size:13px; vertical-align:middle; padding:0; margin:0}\
');
});
function processNodes(nodes) {
for (var i=0, nl=nodes.length, n; i<nl && (n=nodes[i]); i++) {
if (!n.parentNode || n.className.indexOf('instant-youtube-') >= 0 || n.src.indexOf('autoplay=1') > 0)
continue;
var id = n.src.match(/(?:embed\/|v[=\/])(.+?)(?:[&?\/].*|$)/);
if (!id)
continue;
id = id[1];
for (var np=n.parentNode, npw; np && !(npw=np.clientWidth); np=np.parentNode) {}
var containerWidth = resizeToFit ? npw : n.clientWidth;
var containerHeight = resizeToFit ? npw / 16 * 9 : n.clientHeight;
var div = document.createElement('div');
div.className = 'instant-youtube-container';
div.srcEmbed = n.src;
div.style.maxWidth = containerWidth + 'px';
div.style.height = containerHeight + 'px';
div.originalWidth = n.width;
div.originalHeight = n.height;
var img = div.appendChild(document.createElement('img'));
img.className = 'instant-youtube-thumbnail';
img.src = 'https://i.ytimg.com/vi' + (window.chrome?'_webp':'') + '/' + id + '/maxresdefault.' + (window.chrome?'webp':'jpg');
if (n.clientHeight) {
img.style.maxWidth = 'auto';
img.style.width = (containerHeight / 9 * 16) + 'px';
img.style.marginLeft = Math.round((containerWidth - containerHeight / 9 * 16) / 2) + 'px';
}
img.title = 'Shift-click to play directly as HTML5 video';
img.onload = function(e) {
var img = e.target;
if (img.naturalWidth <= 120)
img.onerror(e);
else {
if (img.src.indexOf('maxresdefault') < 0)
img.style.marginTop = ((img.parentNode.clientHeight - img.clientHeight) / 2).toFixed(1) + 'px';
img.style.setProperty('opacity', '1');
}
}
img.onerror = function(e) {
var img = e.target;
if (img.src.indexOf('maxresdefault') > 0)
img.src = img.src.replace('maxresdefault','hqdefault');
else if (img.src.indexOf('hqdefault.webp') > 0)
img.src = img.src.replace('_webp','').replace('.webp','.jpg');
};
div.insertAdjacentHTML('beforeend', '\
<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>\
<a class="instant-youtube-link" href="https://www.youtube.com/watch?v=' + id + '" target="_blank"><b>Watch on Youtube</b></a>\
<span class="instant-youtube-link" style="margin-top:6em">Watch as HTML5</span>\
<div class="instant-youtube-options">\
<label for="instant-youtube-options-resize">Resize to fit</label>\
<input type="checkbox" id="instant-youtube-options-resize"' + (resizeToFit ? ' checked' : '') + '>\
</div>\
');
n.parentNode.insertBefore(div, n);
n.remove();
div.addEventListener('click', function clickHandler(e) {
if (e.target.href)
return;
if (e.target.parentNode.className == 'instant-youtube-options') {
if (e.target.id == 'instant-youtube-options-resize') {
resizeToFit = e.target.checked;
GM_setValue('resize', resizeToFit);
[].forEach.call(document.querySelectorAll('div.instant-youtube-container'), function(n) {
var w = n.originalWidth, h = n.originalHeight, img = n.querySelector('img');
if (resizeToFit) {
for (var np=n.parentNode, npw; np && !(npw=np.clientWidth); np=np.parentNode) {}
w = npw;
h = npw / 16 * 9;
}
n.style.maxWidth = w + 'px';
n.style.height = h + 'px';
img.style.width = (h / 9 * 16) + 'px';
img.style.marginLeft = Math.round((w - h / 9 * 16) / 2) + 'px';
img.style.marginTop = ((h - img.clientHeight) / 2).toFixed(1) + 'px';
n.querySelector('input').checked = resizeToFit;
var video = n.querySelector('video');
if (video) {
video.width = w;
video.height = h;
}
});
}
return;
}
for (var div = e.target; !div.srcEmbed; div = div.parentNode) {}
var iframeHTML = '<iframe class="instant-youtube-embed" src="' + div.srcEmbed + (div.srcEmbed.indexOf('?') > 0 ? '' : '?') +
'&autoplay=1' +
'&autohide=2' +
'&border=0' +
'&controls=1' +
'&fs=1' +
'&showinfo=1' +
'&ssl=1' +
'&theme=dark' +
'" frameborder="0" allowfullscreen="allowfullscreen" width="' + div.clientWidth + '" height="' + div.clientHeight + '"></iframe>';
div.removeEventListener('click', clickHandler);
div.querySelector('svg').outerHTML = '<span class="instant-youtube-loading-button"></span>';
if (!e.shiftKey && e.target.className != 'instant-youtube-link')
div.insertAdjacentHTML('beforeend', iframeHTML);
else {
div.querySelector('span.instant-youtube-link').style.display = 'none';
GM_xmlhttpRequest({
method: 'GET',
url: div.srcEmbed.replace(/\/(embed\/|v[=\/])/, '/watch?v='),
onload: function(response) {
var video;
video = div.appendChild(document.createElement('video'));
video.controls = true;
video.autoplay = true;
video.width = div.clientWidth;
video.height = div.clientHeight;
video.className = 'instant-youtube-embed';
video.volume = GM_getValue('volume', 0.5);
var m;
if (m = response.responseText.match(/ytplayer\.config\s*=\s*(\{.+?\});\s*ytplayer\.load/)) {
var cfg = JSON.parse(m[1]), streams = {};
cfg.args.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;
});
cfg.args.fmt_list.split(',').forEach(function(fmt){
var itag = fmt.split('/')[0], dimensions = fmt.split('/')[1], stream = streams[itag];
if (stream) {
var source = video.appendChild(document.createElement('source'));
source.src = stream.url;
source.title = stream.quality + ', ' + dimensions + ', ' + stream.type;
}
});
} else {
var rx = /url=([^=]+?mime%3Dvideo%252F(?:mp4|webm)[^=]+?)(?:,quality|,itag|.u0026)/g;
var text = response.responseText.split('url_encoded_fmt_stream_map')[1];
while (m = rx.exec(text)) {
video.appendChild(document.createElement('source')).src = decodeURIComponent(decodeURIComponent(m[1]));
}
}
if (video.children.length) {
if (window.chrome) {
video.addEventListener('click', function(e) { if (e.target.paused) {e.target.play()} else {e.target.pause()} });
}
video.interval = (function() {
return setInterval(function() {
if (video.paused)
clearInterval(video.interval);
else
GM_setValue('volume', video.volume);
}, 1000);
})();
var title = response.responseText.match(/<title>(.+?)(?:\s*-\s*YouTube)?<\/title>/);
if (title) {
var a = div.querySelector('.instant-youtube-link');
a.innerHTML = '<b>' + title[1] + '</b> - watch on Youtube';
a.className = 'instant-youtube-link2';
}
div.querySelector('img').style.display = 'none';
} else {
video.remove();
div.insertAdjacentHTML('beforeend', iframeHTML);
}
}
});
}
});
}
return true;
}