// ==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.2.1 // @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 // @downloadURL none // ==/UserScript== var resizeToFit = GM_getValue('resize', true); var playHTML5 = GM_getValue('playHTML5', false); 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('\ :root .instant-youtube-container {position:relative; overflow:hidden; cursor:pointer; background-color:black; padding:0; margin:0; font:normal 14px/1.0 sans-serif,Arial,Helvetica,Verdana;}\ :root .instant-youtube-container .instant-youtube-thumbnail {transition:opacity 0.1s ease-out; opacity:0; padding:0; margin:0}\ :root .instant-youtube-container .instant-youtube-play-button {position:absolute; left:0; right:0; top:0; bottom:0; margin:auto; width:85px; height:60px}\ :root .instant-youtube-container .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==")}\ :root .instant-youtube-container:hover .ytp-large-play-button-svg {fill:#CC181E}\ :root .instant-youtube-container .instant-youtube-link, .instant-youtube-container .instant-youtube-link:link, .instant-youtube-container .instant-youtube-link:visited, .instant-youtube-container .instant-youtube-link:active, .instant-youtube-container .instant-youtube-link:focus {\ position:absolute; top:50%; left:0; right:0; width:20em; height:1.7em; margin:60px auto; padding:0; border:none; \ display:block; text-align:center; text-decoration:none; color:white; text-shadow:1px 1px 3px black; font-weight:bold;\ }\ :root .instant-youtube-container span.instant-youtube-link {font-weight:normal; font-size:12px}\ :root .instant-youtube-container .instant-youtube-link:hover {color:white; text-decoration:underline; background:transparent}\ :root .instant-youtube-container .instant-youtube-embed {position:absolute; left:0; top:0; padding:0; margin:0; height:inherit!important;}\ :root .instant-youtube-container .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;\ }\ :root .instant-youtube-container:hover .instant-youtube-link2 {display:block; margin:0}\ :root .instant-youtube-container .instant-youtube-link2:hover {text-decoration:underline}\ :root .instant-youtube-container .instant-youtube-link2 b {color:white}\ :root .instant-youtube-container .instant-youtube-options {position:absolute; right:0; bottom:0; color:white; text-shadow:1px 1px 2px black; padding:0; margin:0 }\ :root .instant-youtube-container .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= 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', '\ \ Watch on Youtube\ ' + (playHTML5 ? 'Play with Youtube player' : 'Play directly (up to 720p)') + '\
\ \ \ \ \
\ '); 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; } }); } else if (e.target.id == 'instant-youtube-options-html5') { playHTML5 = e.target.checked; GM_setValue('playHTML5', playHTML5); [].forEach.call(document.querySelectorAll('div.instant-youtube-container span.instant-youtube-link'), function(n) { n.textContent = playHTML5 ? 'Play with Youtube player' : 'Play directly (up to 720p)'; }); } return; } for (var div = e.target; !div.srcEmbed; div = div.parentNode) {} var iframeHTML = ''; div.removeEventListener('click', clickHandler); div.querySelector('svg').outerHTML = ''; var alternateMode = e.target.className == 'instant-youtube-link' || e.shiftKey; if (playHTML5 && alternateMode || !playHTML5 && !alternateMode) 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(/(.+?)(?:\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; }