// ==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.0.1 // @author wOxxOm // @namespace wOxxOm.scripts // @license MIT License // @grant GM_addStyle // @grant GM_xmlhttpRequest // @run-at document-start // @downloadURL none // ==/UserScript== var resizeToFit = true; GM_addStyle('\ .instant-youtube-container:not(small) {position:relative; overflow:hidden; cursor:pointer; background-color:black}\ .instant-youtube-container:not(small) .instant-youtube-thumbnail {transition:opacity 0.1s ease-out; opacity: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; 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%;\ color: white;\ display: block;\ left: 0;\ right: 0;\ margin: 40px auto;\ width: 10em;\ height: 1.7em;\ text-align: center;\ text-shadow: 1px 1px 3px black;\ text-decoration: none;\ }\ .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}\ '); processNodes(null, document.querySelectorAll('iframe[src*="youtube.com/embed"]')); setMutationHandler(document, 'iframe[src*="youtube.com/embed"]', processNodes); function processNodes(observer, nodes) { for (var i=0, nl=nodes.length, n; i= 0) continue; var id = n.src.match(/embed\/(.+?)(?:[&?\/].*|$)/); 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'; 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\n(sometimes buggy: playback starts only after the entire video is loaded)'; 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\ '); n.parentNode.insertBefore(div, n); n.remove(); div.addEventListener('click', function clickHandler(e) { if (e.target.localName == 'a') return; for (var div = e.target; !div.srcEmbed; div = div.parentNode) {} var iframeHTML = ''; div.removeEventListener('click', clickHandler); div.querySelector('.instant-youtube-play-button').outerHTML = ''; if (!e.shiftKey) div.insertAdjacentHTML('beforeend', iframeHTML); else { GM_xmlhttpRequest({ method: 'GET', url: div.srcEmbed.replace('/embed/', '/watch?v='), onload: function(response) { var video; video = div.parentNode.insertBefore(document.createElement('video'), div); video.controls = true; video.autoplay = true; video.width = div.scrollWidth; video.height = div.scrollHeight; video.className = 'instant-youtube-container'; if (window.chrome) video.addEventListener('click', function(e) { if (e.target.paused) {e.target.play()} else {e.target.pause()} }); var m; if (m = response.responseText.match(/ytplayer\.config\s*=\s*(\{.+?\});/)) { 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) div.remove(); else { video.remove(); div.insertAdjacentHTML('beforeend', iframeHTML); } } }); } }); } return true; } function setMutationHandler(baseNode, selector, cb) { var ob = new MutationObserver(function(mutations){ for (var i=0, ml=mutations.length, m; (i