// ==UserScript==
// @name Lazy Embedded Video
// @namespace [email protected]
// @description Lazy load embedded videos from Youtube/Dailymotion/Vimeo/Rutube/Twitch/Ustream/Coub/Vine/Facebook
// @version 2.7
// @include *
// @resource playIcon https://i.imgur.com/1aybyWN.png
// @grant GM_getResourceURL
// @run-at document-start
// ==/UserScript==
(function() {
try {
if(/^([^.]+\.)?(youtube|dailymotion|vimeo|rutube|twitch|ustream|coub|vine|facebook)\.[^.]+$/.test(top.location.hostname))
return;
} catch(e) {}
var wnd = typeof(unsafeWindow) != "undefined" ? unsafeWindow : window;
var getResourceURL = typeof(GM_getResourceURL) != "undefined" ? GM_getResourceURL :
function(name) {
switch(name) {
case "playIcon" : return "//i.imgur.com/1aybyWN.png";
}
};
var CSP = -1;
var testCSP = function() {
if(CSP == -1) {
var script = document.createElement("SCRIPT");
script.innerHTML = "CSP_AllowInlineScript = true;";
document.head.appendChild(script);
document.head.removeChild(script);
CSP = wnd.CSP_AllowInlineScript ? 1 : 0;
}
return CSP;
};
var html, a;
var createHtml = function(url, iframe, api, background_img) {
/(.*\/\/(?:[^.\/]+\.)?([^.\/]+)\.[^.\/]+)\//i.test(url);
var provider_url = RegExp.$1, provider_name = RegExp.$2, data_convert = "", extra_script = "", button_hsb = [];
if(api.includes("yahooapis"))
data_convert += "data = data.query.results.json;";
switch(provider_name) {
case "youtube" :
button_hsb.push( 0, 100, 100);
data_convert += "delete data.thumbnail_url;";
extra_script += "var img = new Image();"+
"img.onload = function() {"+
"if(this.naturalWidth > 120) {"+
"document.body.style.backgroundImage = 'url('+this.src+')';"+
"if(this.src.includes('hqdefault_live'))"+
"this.src = this.src.replace('hqdefault', 'maxresdefault');"+
"} else if(this.src.includes('maxresdefault')) {"+
"this.src = this.src.replace('maxresdefault', 'sddefault');"+
"} else if(this.src.includes('hqdefault_live')) {"+
"this.src = this.src.replace('hqdefault_live', 'maxresdefault');"+
"}"+
"};"+
"img.src = '"+background_img.match(/\/(.+)\//)[0]+"hqdefault_live.jpg';";
break;
case "dailymotion" :
button_hsb.push( 60, 30, 300);
data_convert += "var img = new Image();"+
"img.onload = function() { document.body.style.backgroundImage = 'url('+this.src+')'; };"+
"img.src = removeProtocol(data.thumbnail_url.replace(/\\/x240[^.]+/i, ''));"+
"delete data.thumbnail_url;";
break;
case "vimeo" :
button_hsb.push(220, 50, 220);
data_convert += "data.thumbnail_url = data.thumbnail_url.replace(/_\\d+/i, '');";
break;
case "rutube" :
button_hsb.push( 0, 0, 100);
data_convert += "data.thumbnail_url = data.thumbnail_url.replace(/\\?.+/i, '');";
break;
case "twitch" :
button_hsb.push(270, 50, 100);
if(background_img) { // channel live
data_convert += "data.title = data.status || 'Untitled Broadcast';"+
"data.author_url = '"+provider_url+"/'+data.name+'/profile';"+
"data.author_name = data.display_name;"+
"data.duration = data.game && 'playing <a target=_blank href=\""+provider_url+
"/directory/game/'+data.game+'\">'+data.game+'</b>';"+
"offline_image = data.video_banner;";
extra_script += "function jsonpCallback2(data) {"+
"if(data.streams.length != 0) return;"+
"document.body.style.backgroundImage = 'url('+removeProtocol(offline_image)+')';"+
"document.getElementById('duration').textContent = 'offline';"+
"}"+
"</script>"+
"<script defer src='https://api.twitch.tv/kraken/streams?channel="+url.match(/[^\/]+$/)[0]+"&callback=jsonpCallback2'>";
} else { // video recorded
data_convert += "data.thumbnail_url = data.preview.replace(/\\d+x\\d+/i, '0x0');"+
"data.author_url = '"+provider_url+"/'+data.channel.name+'/profile';"+
"data.author_name = data.channel.display_name;"+
"data.duration = data.length;";
}
break;
case "ustream" :
button_hsb.push( 40, 50, 230);
if(background_img) // channel live
data_convert += "delete data.thumbnail_url;";
break;
case "coub" :
button_hsb.push(240, 150, 100);
data_convert += "data.author_url = data.channel_url;";
break;
case "vine" :
button_hsb.push(170, 150, 150);
break;
case "facebook" :
button_hsb.push( 0, 0, 300);
data_convert += "if(/<a.*?>(.+)<\\/a><p>/i.test(data.html))"+
"data.title = RegExp.$1;";
break;
}
if(!html) html = [
"<!doctype html>"+
"<html>"+
"<head>"+
"<title>Lazy Embedded Video</title>"+
"<script defer src='", api, "&callback=jsonpCallback'></script>"+
"<script>"+
"function removeProtocol(url) { return url.replace(/^[a-z]+:/i, ''); };"+
"function jsonpCallback(data) {",
data_convert,
"if(data.thumbnail_url) document.body.style.backgroundImage = 'url('+removeProtocol(data.thumbnail_url)+')';"+
"if(data.url) document.getElementById('title').href = removeProtocol(data.url);"+
"if(data.title) document.getElementById('title').textContent = "+
"document.getElementById('title').title = data.title;"+
"if(data.author_url) document.getElementById('author').href = removeProtocol(data.author_url);"+
"if(data.author_name) document.getElementById('author').textContent = data.author_name;"+
"if(data.duration) {"+
"if(Number(data.duration))"+
"document.getElementById('duration').textContent = new Date(data.duration*1000).toISOString().substr(11,8);"+
"else document.getElementById('duration').innerHTML = data.duration;"+
"}"+
"}",
extra_script,
"</script>"+
"<style>"+
"html { height:100%; }"+
"body { margin:0; height:100%; color:white; font:14px sans-serif;"+
"background:black ", background_img, " center/100% no-repeat; }"+
"a { color:inherit; font-weight:bold; text-decoration:none; }"+
"a:hover { text-decoration:underline; }"+
"ul { margin:0; padding:0; list-style:none; }"+
"#infobar { position:absolute; top:0px; width:100%; padding:8px 16px;"+
"box-sizing:border-box; background:rgba(0,0,0,0.5); word-wrap:break-word; }"+
"#infobar_right { float:right; margin-left:16px; text-align:right; text-transform:capitalize; }"+
"#button { height:100%; cursor:pointer; background-position:0px 50%; }"+
"#button:hover { background-position:-70px 50%;"+
(navigator.userAgent.includes("Firefox") ? "" : "-webkit-")+"filter:"+
"hue-rotate(", button_hsb[0], "deg) saturate(", button_hsb[1], "%) brightness(", button_hsb[2], "%);}"+
"#button > div { width:70px; height:100%; margin:auto; background-repeat:no-repeat; background-position:inherit; "+
"background-image:url("+getResourceURL("playIcon")+"); }"+
"#titleBlock { overflow:hidden; max-height:34px; }"+
"</style>"+
"</head>"+
"<body>"+
"<div id=button onclick='location.replace(\"", iframe, "\");'><div></div></div>"+
"<div id=infobar>"+
"<ul id=infobar_right>"+
"<li><a id=author target=_blank></a></li>"+
"<li><a id=provider target=_blank href='", provider_url, "'>", provider_name, "</a></li>"+
"</ul>"+
"<ul>"+
"<li id=titleBlock><a id=title target=_blank href='", url, "'>", url, "</a></li>"+
"<li id=duration></li>"+
"</ul>"+
"</div>"+
"</body>"+
"</html>"
];
html[ 1] = api;
html[ 3] = data_convert;
html[ 5] = extra_script;
html[ 7] = background_img;
html[ 9] = button_hsb[0];
html[11] = button_hsb[1];
html[13] = button_hsb[2];
html[15] = iframe;
html[17] = provider_url;
html[19] = provider_name;
html[21] = url;
html[23] = url;
};
var createOembed = function(api, url) { return api+encodeURIComponent(url); };
var createNOembed = function(api, url) { return createOembed("//noembed.com/embed?url=", url); };
var createYOembed = function(api, url) { return createOembed("//query.yahooapis.com/v1/public/yql?format=json&q=",
'SELECT * FROM json WHERE url="'+createOembed(location.protocol+api,url)+'"'); };
var createLazyVideo = function(elem) {
if(elem.tagName == "IFRAME" && elem.srcdoc) return true;
var id, args, url = elem.src || elem.data || elem.dataset.src;
if(!url) return true;
if(!a) a = document.createElement("A");
a.href = url;
/([^.]+)\.[^.]+$/i.test(a.hostname);
switch(RegExp.$1) {
case "youtube" :
if(/\/(?:v|embed)\/([^&]*)/i.test(a.pathname))
id = RegExp.$1 || (/[?&]v=([^&]+)/i.test(a.search) && RegExp.$1);
if(!id || !testCSP()) return !id;
args = "?autoplay=1";
if(/[?&](list=[^&]+)/i.test(a.search)) args += "&"+RegExp.$1;
if(/[?&](start=[^&]+)/i.test(a.search)) args += "&"+RegExp.$1;
createHtml(url =
"//www.youtube.com/watch"+args+"&v="+id,
"//www.youtube.com/embed/"+id+args,
createNOembed("//www.youtube.com/oembed?format=json&url=", location.protocol+url),
"url(//i.ytimg.com/vi/"+id+"/hqdefault.jpg)"
);
break;
case "dailymotion" :
if(/\/(?:swf|embed)\/video\/([^&_]+)/i.test(a.pathname)) id = RegExp.$1;
if(!id || !testCSP()) return !id;
args = "?autoplay=1";
if(/[?&](mute=[^&]+)/i.test(a.search)) args += "&"+RegExp.$1;
if(/[?&](start=[^&]+)/i.test(a.search)) args += "&"+RegExp.$1;
createHtml(url =
"//www.dailymotion.com/video/"+id+args,
"//www.dailymotion.com/embed/video/"+id+args,
createOembed("//www.dailymotion.com/services/oembed?format=json&url=", location.protocol+url),
"url(//www.dailymotion.com/thumbnail/video/"+id+")"
);
break;
case "vimeo" :
if(/\/(?:moogaloop\.swf|video\/)([^&]*)/i.test(a.pathname))
id = RegExp.$1 || (/[?&]clip_id=([^&]+)/i.test(a.search) && RegExp.$1);
if(!id || !testCSP()) return !id;
args = "?autoplay=1";
if(/[?&](loop=[^&]+)/i.test(a.search)) args += "&"+RegExp.$1;
if(/(\#t=[\dhms]+)/i.test(a.hash)) args += RegExp.$1;
createHtml(url =
"//vimeo.com/"+id+args,
"//player.vimeo.com/video/"+id+args,
createOembed("//vimeo.com/api/oembed.json?url=", url)
);
break;
case "rutube" :
if(/\/play\/embed\/([^&.\/]+)/i.test(a.pathname)) id = RegExp.$1;
if(!id || !testCSP()) return !id;
args = "?autoStart=1";
if(/[?&](bmstart=[^&]+)/i.test(a.search)) args += "&"+RegExp.$1;
createHtml(url =
"//rutube.ru/"+(isNaN(id) ? "video/"+id+"/" : "tracks/"+id+".html/")+args,
"//rutube.ru/play/embed/"+id+args,
createOembed("//rutube.ru/api/oembed/?format=jsonp&url=", url)
);
break;
case "twitch" :
if(/[?&](channel|video)=([^&]+)/i.test(a.search)) {args = RegExp.$1; id = RegExp.$2;}
else if(/\/(.+)\/embed/i.test(a.pathname)) {args = "channel"; id = RegExp.$1;}
if(!id || !testCSP()) return !id;
createHtml(url =
"//www.twitch.tv/"+(args=="video" ? id.replace("v","c/v/") : id),
"//player.twitch.tv/?autoplay=true&"+args+"="+id,
"https://api.twitch.tv/kraken/"+args+"s/"+id+"?",
args=="channel" ? "url(//static-cdn.jtvnw.net/previews-ttv/live_user_"+id+"-0x0.jpg)" : null
);
break;
case "ustream" :
if(/(?:\/embed)?\/(channel\/|recorded\/)?([^&]+)/i.test(a.pathname)) {args = RegExp.$1 || "channel/"; id = RegExp.$2;}
if(!id || !testCSP()) return !id;
createHtml(url =
"//www.ustream.tv/"+args+id,
"//www.ustream.tv/embed/"+(args=="channel/" ? "" : args)+id+"?html5ui=1&autoplay=1",
createYOembed("//www.ustream.tv/oembed?format=json&url=", url),
args=="channel/" && !isNaN(id) ? "url(//static-cdn1.ustream.tv/i/channel/live/1_"+id+",640x360,b:0.jpg)" : null
);
break;
case "coub" :
if(/\/embed\/([^&]+)/i.test(a.pathname)) id = RegExp.$1;
if(!id || !testCSP()) return !id;
createHtml(url =
"//coub.com/view/"+id,
"//coub.com/embed/"+id+"?startWithHD=true&autostart=true",
createYOembed("//coub.com/api/oembed.json?url=", url)
);
break;
case "vine" :
if(/\/v\/([^&]+)\/embed\/([^&]+)/i.test(a.pathname)) {args = RegExp.$2; id = RegExp.$1;}
if(!id || !testCSP()) return !id;
createHtml(url =
"//vine.co/v/"+id,
"//vine.co/v/"+id+"/embed/"+args+"?audio=1",
createOembed("//vine.co/oembed.json?url=", url)
);
break;
case "facebook" :
if(a.pathname.endsWith("/plugins/video.php") && /[?&]href=([^&]+)/i.test(a.search)) {
url = decodeURIComponent(RegExp.$1).replace(/^[a-z]+:/i, '');
if(/\/videos.*?\/(\d+)/i.test(url)) id = RegExp.$1;
}
if(!id || !testCSP()) return !id;
createHtml(
url,
"//www.facebook.com/plugins/video.php?autoplay=1&href="+encodeURIComponent(location.protocol+url),
createOembed("//www.facebook.com/plugins/video/oembed.json/?url=", location.protocol+url),
"url(//graph.facebook.com/"+id+"/picture)"
);
break;
default :
return true;
}
if(elem.tagName != "IFRAME") {
var iframe = document.createElement("IFRAME");
iframe.id = elem.id;
iframe.className = elem.className;
iframe.style.cssText = elem.style.cssText;
if(!iframe.style.width && elem.width ) iframe.style.width = elem.width+"px";
if(!iframe.style.height && elem.height) iframe.style.height = elem.height+"px";
if(elem.parentNode.tagName == "OBJECT") {
elem = elem.parentNode;
iframe.style.cssText = elem.style.cssText + iframe.style.cssText;
if(!iframe.style.width && elem.width ) iframe.style.width = elem.width+"px";
if(!iframe.style.height && elem.height) iframe.style.height = elem.height+"px";
}
if(!iframe.style.borderWidth) iframe.style.borderWidth = (elem.border||0)+"px";
switch(elem.align) {
case "left" : case "right" :
if(!iframe.style.float) iframe.style.float = elem.align; break;
case "top" : case "middle" : case "bottom" :
if(!iframe.style.verticalAlign) iframe.style.verticalAlign = elem.align; break;
}
elem.parentNode.replaceChild(iframe, elem);
elem = iframe;
}
elem.allowFullscreen = true;
elem.srcdoc = html.join("");
return true;
};
var update = function() {
// convert NodeList to Array because for some reason sometimes I wasn't able to read src when iterating directly through NodeList
var nodes = ["IFRAME", "EMBED", "OBJECT"].reduce(function(sum, value) {
return sum.concat([].slice.call(document.getElementsByTagName(value)));
}, frameElement ? [frameElement] : []);
for(var i = 0; i < nodes.length; i++)
if(!createLazyVideo(nodes[i])) return;
if(document.readyState == "loading") requestAnimationFrame(update);
};
update();
})();