/*
Copyright (C) 2010 - 2017 Sebastian Luncan
Copyright (C) 2017 Arun Isaac <arunisaac@systemreboot.net>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Website: http://isebaro.com/viewtube
Contact: http://isebaro.com/contact
*/
// ==========Variables========== //
(function() {
// Userscript
var userscript = 'ViewTube';
// Page
var page = {dom: document.documentElement.outerHTML, win: window, url: window.location.href};
// Player
var player = {};
var mimetypes = {
'MPEG': 'video/mpeg',
'MP4': 'video/mp4',
'WebM': 'video/webm',
'FLV': 'video/x-flv',
'MOV': 'video/quicktime',
'M4V': 'video/x-m4v',
'AVI': 'video/x-msvideo',
'3GP': 'video/3gpp',
};
// Links
var website = 'https://git.systemreboot.net/youtube-noscript-shim/about';
var contact = 'mailto:arunisaac@systemreboot.net';
// ==========Functions========== //
function createElement (type, attributes, parent) {
var element = document.createElement(type);
for (var key in attributes)
element.setAttribute(key, attributes[key]);
if (parent) parent.appendChild(element);
return element;
}
function createVideoElement (attributes, parent, ...children) {
return createElement.apply(null, ["video", Object.assign({controls: "controls", autoplay: "autoplay", volume: 0.8}, attributes), parent].concat(children));
}
function playDASHwithHTML5() {
function playAudio (play) {
if (play) player["contentAudio"].play();
else player["contentAudio"].pause();
}
if (player['videoPlay'].indexOf('MP4') != -1) {
player["contentVideo"] = createVideoElement({src: player["videoList"][player["videoPlay"].replace(/MP4/, "Video MP4")]});
if (player['videoList']['High Bitrate Audio Opus'])
player["contentAudio"] = createVideoElement({src: player["videoList"]["High Bitrate Audio Opus"]});
else if (player['videoList']['Medium Bitrate Audio Opus'])
player['contentAudio'] = createVideoElement({src: player['videoList']['Medium Bitrate Audio Opus']});
else player['contentAudio'] = createVideoElement({src: player['videoList']['Medium Bitrate Audio MP4']});
}
else {
player["contentVideo"] = createVideoElement({src: player["videoList"][player["videoPlay"].replace(/WebM/, "Video WebM")]});
if (player['videoList']['High Bitrate Audio Opus'])
player["contentAudio"] = createVideoElement({src: player["videoList"]["High Bitrate Audio Opus"]});
else if (player['videoList']['Medium Bitrate Audio Opus'])
player["contentAudio"] = createVideoElement({src: player["videoList"]["Medium Bitrate Audio Opus"]});
else player["contentAudio"] = createVideoElement({src: player["videoList"]["Medium Bitrate Audio WebM"]});
}
player['contentAudio'].pause();
player['contentVideo'].addEventListener('play', playAudio.bind(null, true), false);
player['contentVideo'].addEventListener('pause', playAudio.bind(null, false), false);
player['contentVideo'].addEventListener('ended', function() {
player['contentVideo'].pause();
player['contentAudio'].pause();
}, false);
player['contentVideo'].addEventListener('timeupdate', function() {
if (player['contentAudio'].paused && !player['contentVideo'].paused)
player['contentAudio'].play();
if (Math.abs(player['contentVideo'].currentTime - player['contentAudio'].currentTime) >= 0.30)
player['contentAudio'].currentTime = player['contentVideo'].currentTime;
}, false);
player["contentAudio"].classList.add("hide");
player['contentVideo'].appendChild(player['contentAudio']);
}
function playMyVideo() {
if (player['videoList'][player['videoPlay']] == 'DASH')
playDASHwithHTML5();
else player["contentVideo"] = createVideoElement({src: player["videoList"][player["videoPlay"]],
poster: player["videoThumb"]});
player['playerWindow'].appendChild(player['contentVideo']);
}
function cleanMyContent(content, unesc) {
var myNewContent = content;
if (unesc) myNewContent = unescape(myNewContent);
return myNewContent.replace(/\\u0025/g,'%').replace(/\\u0026/g,'&').replace(/\\/g,'').replace(/\n/g,'');
}
function getMyContent(url, pattern, clean) {
var myPageContent, myVideosParse;
// Get content
if (url == page.url) myPageContent = page.dom;
else {
var xmlHTTP = new XMLHttpRequest();
xmlHTTP.open('GET', url, false);
xmlHTTP.send();
myPageContent = xmlHTTP.responseText;
}
// Match pattern
if (pattern == "TEXT") return myPageContent;
else {
if (clean) myPageContent = cleanMyContent(myPageContent, true);
myVideosParse = myPageContent.match(pattern);
return myVideosParse ? myVideosParse[1] : null;
}
}
// =====YouTube===== //
// Add stylesheet
createElement("link", {rel: "stylesheet",
type: "text/css",
href: browser.extension.getURL("viewtube.css")},
document.head);
/* Video Availability */
var ytVideoUnavailable = document.querySelector("#player-unavailable");
if (ytVideoUnavailable && (ytVideoUnavailable.className.indexOf('hid') == -1)) {
var ytAgeGateContent = document.querySelector("#watch7-player-age-gate-content");
if ((!ytAgeGateContent) || (ytAgeGateContent.indexOf('feature=private_video') != -1)) return;
}
/* Get Player Window */
var ytPlayerWindow = document.querySelector("#player");
if (!ytPlayerWindow) console.log("Couldn't get the player element.");
else {
/* Get Video Thumbnail */
var ytVideoThumb = getMyContent(page.url, 'link\\s+itemprop="thumbnailUrl"\\s+href="(.*?)"', false)
|| getMyContent(page.url, 'meta\\s+property="og:image"\\s+content="(.*?)"', false)
|| ('https://img.youtube.com/vi/' + page.url.match(/(\?|&)v=(.*?)(&|$)/)[2] + '/0.jpg');
/* Get Videos Content */
var ytVideosEncodedFmts, ytVideosAdaptiveFmts, ytVideosContent, ytHLSVideos, ytHLSContent, ytVideoID, ytVideosInfo;
ytVideosAdaptiveFmts = getMyContent(page.url, '"adaptive_fmts":\\s*"(.*?)"', false)
|| getMyContent(page.url, '\\\\"adaptive_fmts\\\\":\\s*\\\\"(.*?)\\\\"', false);
if (ytVideosEncodedFmts = getMyContent(page.url, '"url_encoded_fmt_stream_map":\\s*"(.*?)"', false)
|| getMyContent(page.url, '\\\\"url_encoded_fmt_stream_map\\\\":\\s*\\\\"(.*?)\\\\"', false))
ytVideosContent = ytVideosEncodedFmts;
else if (ytHLSVideos = getMyContent(page.url, '"hlsvp":\\s*"(.*?)"', false)
|| getMyContent(page.url, '\\\\"hlsvp\\\\":\\s*\\\\"(.*?)\\\\"', false)) {
ytHLSVideos = cleanMyContent(ytHLSVideos, false);
if (ytHLSVideos.indexOf('keepalive/yes/') != -1)
ytHLSVideos = ytHLSVideos.replace('keepalive/yes/', '');
}
else if (ytVideoID = page.url.match(/(\?|&)v=(.*?)(&|$)/)[2]) {
var ytVideoSts = getMyContent(page.url.replace(/watch.*?v=/, 'embed/').replace(/&.*$/, ''), '"sts"\\s*:\\s*(\\d+)', false);
var ytVideosInfoURL = page.win.location.protocol + '//' + page.win.location.hostname + '/get_video_info?video_id=' + ytVideoID + '&eurl=https://youtube.googleapis.com/v/' + ytVideoID + '&sts=' + ytVideoSts;
if ((ytVideosInfo = getMyContent(ytVideosInfoURL, 'TEXT', false)) &&
(ytVideosEncodedFmts = ytVideosInfo.match(/url_encoded_fmt_stream_map=(.*?)&/)[1]))
ytVideosContent = cleanMyContent(ytVideosEncodedFmts, true);
if (!ytVideosAdaptiveFmts && (ytVideosAdaptiveFmts = ytVideosInfo.match(/adaptive_fmts=(.*?)&/)[1]))
ytVideosAdaptiveFmts = cleanMyContent(ytVideosAdaptiveFmts, true);
}
if (ytVideosAdaptiveFmts && !ytHLSVideos) {
if (ytVideosContent) ytVideosContent += ',' + ytVideosAdaptiveFmts;
else ytVideosContent = ytVideosAdaptiveFmts;
}
/* Playlist */
var ytPlaylist, ytPlaceholderPlaylist;
if ((ytPlaylist = document.querySelector("#player-playlist"))
&& (ytPlaceholderPlaylist = document.querySelector("#placeholder-playlist")))
ytPlaceholderPlaylist.appendChild(ytPlaylist);
/* Create Player */
var ytDefaultVideo = 'Low Definition MP4';
function ytPlayer() {
player = {
'playerSocket': ytPlayerWindow,
'playerWindow': document.querySelector("#player-api"),
'videoList': ytVideoList,
'videoPlay': ytDefaultVideo,
'videoThumb': ytVideoThumb
};
playMyVideo();
}
/* Parse Videos */
function ytVideos() {
var ytVideoFormats = {
'5': 'Very Low Definition FLV',
'17': 'Very Low Definition 3GP',
'18': 'Low Definition MP4',
'22': 'High Definition MP4',
'34': 'Low Definition FLV',
'35': 'Standard Definition FLV',
'36': 'Low Definition 3GP',
'37': 'Full High Definition MP4',
'38': 'Ultra High Definition MP4',
'43': 'Low Definition WebM',
'44': 'Standard Definition WebM',
'45': 'High Definition WebM',
'46': 'Full High Definition WebM',
'82': 'Low Definition 3D MP4',
'83': 'Standard Definition 3D MP4',
'84': 'High Definition 3D MP4',
'85': 'Full High Definition 3D MP4',
'100': 'Low Definition 3D WebM',
'101': 'Standard Definition 3D WebM',
'102': 'High Definition 3D WebM',
'135': 'Standard Definition Video MP4',
'136': 'High Definition Video MP4',
'137': 'Full High Definition Video MP4',
'138': 'Ultra High Definition Video MP4',
'139': 'Low Bitrate Audio MP4',
'140': 'Medium Bitrate Audio MP4',
'141': 'High Bitrate Audio MP4',
'171': 'Medium Bitrate Audio WebM',
'172': 'High Bitrate Audio WebM',
'244': 'Standard Definition Video WebM',
'247': 'High Definition Video WebM',
'248': 'Full High Definition Video WebM',
'249': 'Low Bitrate Audio Opus',
'250': 'Medium Bitrate Audio Opus',
'251': 'High Bitrate Audio Opus',
'266': 'Ultra High Definition Video MP4',
'272': 'Ultra High Definition Video WebM',
'298': 'High Definition Video MP4',
'299': 'Full High Definition Video MP4',
'302': 'High Definition Video WebM',
'303': 'Full High Definition Video WebM',
'313': 'Ultra High Definition Video WebM'
};
var ytVideoParse, ytVideoCode, myVideoCode;
for (var ytVideo of ytVideosContent.split(',')) {
if ((!ytVideo.match(/^url/)) && (ytVideoParse = ytVideo.match(/(.*)(url=.*$)/)))
ytVideo = ytVideoParse[2] + '&' + ytVideoParse[1];
if ((ytVideoCode = (ytVideo.match(/itag=(\d{1,3})/) || [])[1])
&& (myVideoCode = ytVideoFormats[ytVideoCode])) {
ytVideo = cleanMyContent(ytVideo, true);
ytVideo = ytVideo.replace(/url=/, "");
if ((ytVideo.match(/itag=/g) || []).length > 1)
ytVideo = ytVideo.replace(/itag=\d{1,3}/, "");
if ((ytVideo.match(/clen=/g) || []).length > 1)
ytVideo = ytVideo.replace(/clen=\d+/, "");
if ((ytVideo.match(/lmt=/g) || []).length > 1)
ytVideo = ytVideo.replace(/lmt=\d+/, "");
ytVideo = ytVideo.replace(/type=(video|audio).*?/, "").replace(/xtags=[^%=]*?/, "");
ytVideo = ytVideo.replace(/&&/g, "&").replace(/^&/, "").replace(/&$/, "");
if (ytVideo.match(/&sig=/)) ytVideo = ytVideo.replace(/&sig=/, '&signature=');
else if (ytVideo.match(/&s=/)) {
console.log("Decrypting cipher signatures not supported.");
ytVideo = "";
}
ytVideo = cleanMyContent(ytVideo, true);
if (ytVideo.indexOf('ratebypass') == -1) ytVideo += '&ratebypass=yes';
if (ytVideo && ytVideo.indexOf('http') == 0)
ytVideoList[myVideoCode] = ytVideo;
}
}
if (Object.keys(ytVideoList).length > 0) {
for (var container of ["Standard", "High", "Full High", "Ultra High"])
for (var definition of ["MP4", "WebM"])
if (!ytVideoList[definition + " Definition " + container] &&
ytVideoList[definition + " Definition Video " + container])
ytVideoList[definition + " Definition " + container] = "DASH";
ytPlayer();
}
else if (ytVideosContent.indexOf("conn=rtmp") != -1)
console.log("This video uses the RTMP protocol and is not supported.");
else console.log("Couldn't get any video.");
}
/* Parse HLS */
function ytHLS() {
var ytHLSFormats = {
'92': 'Very Low Definition MP4',
'93': 'Low Definition MP4',
'94': 'Standard Definition MP4',
'95': 'High Definition MP4',
'96': 'Full High Definition MP4'
};
ytVideoList["Any Definition MP4"] = ytHLSVideos;
if (ytHLSContent) {
var ytHLSVideo, ytVideoCodeParse, myVideoCode;
if (ytHLSVideos = ytHLSContent.match(/(http.*?m3u8)/g))
for (var ytHLSVideo in ytHLSVideos)
if (ytVideoCode = (ytHLSVideo.match(/\/itag\/(\d{1,3})\//) || [])[1]) {
myVideoCode = ytHLSFormats[ytVideoCode];
if (myVideoCode && ytHLSVideo)
ytVideoList[myVideoCode] = ytHLSVideo;
}
}
ytDefaultVideo = 'Any Definition MP4';
ytPlayer();
}
/* Get Videos */
var ytVideoList = {}, ytScriptURL;
if (ytVideosContent) {
if (ytVideosContent.match(/&s=/) || ytVideosContent.match(/,s=/) || ytVideosContent.match(/u0026s=/)) {
if (ytScriptURL = getMyContent(page.url, '"js":\\s*"(.*?)"', true)
|| getMyContent(page.url.replace(/watch.*?v=/, 'embed/').replace(/&.*$/, ''), '"js":\\s*"(.*?)"', true)) {
ytScriptURL = page.win.location.protocol + ytScriptURL;
try {
if (ytScriptSrc = getMyContent(ytScriptURL, 'TEXT', false)) ytDecryptFunction();
ytVideos();
}
catch(e) {
try {
var oReq = new XMLHttpRequest();
oReq.open("GET", ytScriptURL);
oReq.onload = function(response) {
if (response.readyState === 4 && response.status === 200 && response.responseText) {
ytScriptSrc = response.responseText;
ytDecryptFunction();
ytVideos();
}
else console.log("Couldn't get the signature content.");
};
oReq.onerror = console.log.bind(null, "Couldn't make the request. Make sure your browser user scripts extension supports cross-domain requests.");
oReq.send();
}
catch(e) {
console.log("Couldn't make the request. Make sure your browser user scripts extension supports cross-domain requests.");
}
}
}
else console.log("Couldn't get the signature link.");
}
else ytVideos();
}
else if (ytHLSVideos) {
try {
ytHLSContent = getMyContent(ytHLSVideos, 'TEXT', false);
ytHLS();
}
catch(e) {
try {
var oReq = new XMLHttpRequest();
oReq.open("GET", ytHLSVideos);
oReq.onload = function(response) {
if (response.readyState === 4 && response.status === 200 && response.responseText)
ytHLSContent = response.responseText;
ytHLS();
};
oReq.onerror = ytHLS;
oReq.send();
}
catch(e) {
ytHLS();
}
}
}
else console.log("Couldn't get the videos content.");
}
})();