/* Copyright (C) 2010 - 2017 Sebastian Luncan Copyright (C) 2017 Arun Isaac 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 . 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."); } })();