Better-Youtube-Plus/src/scripts/betterYoutube.js

431 lines
19 KiB
JavaScript

(function() {
const menuTemplate = `
<div class="ytMenuStyle" id="enhancerMenuID" style="display: block;">
<div id="ytThumbnailBttn" class="imageStyle" title="Video Thumbnails..."></div>
<div id="ytLoopBttn" class="imageStyle" title="Start Loop..."/></div>
<div id="ytFloatBttn" class="imageStyle" title="Float Video Container"/></div>
<div id="ytDownloadBttn" class="imageStyle" title="Download Video..."/></div>
</div>
`
const slugTemplate = `
<div id="enhancerMenuID2">
<div id="volumeContainerID" style="">
Volume <label id="volumeValueLbl"></label>
</div>
<input id="slugCopyZone" type="text">
<label>Loop Range Start:&nbsp;&nbsp;</label>
<input type="text" id="rangeStartID"/>
<label>Loop Range End:&nbsp;&nbsp;</label>
<input id="rangeEndID" type="text"/>
<label for="endlessPlayID">Endless Play</label>
<input type="checkbox" checked id="endlessPlayID" name="endlessPlayID"/>
</div>
`
const thumbnailTemplate = `
<div id="ytThumbMenuID" class="ytThumbMenuStyle" style="display: none;">
<a id="ytAMaxDefaultImgID" target="_blank" href=""><img id="ytMaxDefaultImgID" class="thumbImageStyle" src="" title="Max Resolution Default"/></a>
<a id="ytAHqDefaultImgID" target="_blank" href=""><img id="ytHqDefaultImgID" class="thumbImageStyle" src="" title="High Quality Default"/></a>
<a id="ytAMedDefaultImgID" target="_blank" href=""><img id="ytMqDefaultImgID" class="thumbImageStyle" src="" title="Medium Quality Default"></a>
<a id="ytASdDefaultImgID" target="_blank" href=""><img id="ytSdDefaultImgID" class="thumbImageStyle" src="" title="Standard Quality Default"/></a>
</div>
`
const popedContainerTemplate = `
<div id="draggable" style="display:none; block; top: 114px; left: 408px;">
<iframe id="popIframe" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
autoplay="" allowfullscreen="true" width="650px" height="400px" frameborder="0" src="" />
</iframe>
</div>
`
// Declare other variables
let ytThumbImgMenu, ytEnhancerMenu, ytEnhancerMenu2; // Menu systems
let ytThumbnailBttn, ytLoopBttn, ytFloatBttn, ytDownloadBttn; // Menu Buttons
let ytMaxDefaultImg, ytHqDefaultImg, ytAMaxDefaultImg, ytAHqDefaultImg; // Thumbnail images
let mainContentArea, playerWindow, containerOfPlyrWndow, video; // Youtube Player container
let videoTimeLength, videoTimeCurent, ytRangeStart, ytRangeEnd,
slugInputTag, endlessPlayTag, ytVideoIntervalLoop;
let poppedContainer, videoSlug, volumeLbl, part;
let loopingInterval = false;
let shouldHideVol = true;
let OSName = "";
let count = 0;
// confirm dialog elm
const isYoutubeMusic = window.location.hostname === 'music.youtube.com';
let dialogElementQueryRef = isYoutubeMusic ? 'ytmusic-you-there-renderer' : 'yt-confirm-dialog-renderer';
// Options for the observer (which mutations to observe)
let observer, observer2;
const observerConfig = { attributes: true };
const observerConfig2 = { childList: true, subtree: true };
const preSetupProc = () => {
// Look to add saving image from video elm.
// path = "/html/body/ytd-app/div/ytd-page-manager/ytd-watch-flexy/div[4]/div[1]/div/div[1]/div/div/div/ytd-player/div/div/div[1]/video"
// elm = document.evaluate(path, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
if (navigator.appVersion.indexOf("Win")!=-1) OSName = "Windows";
if (navigator.appVersion.indexOf("Mac")!=-1) OSName = "MacOS";
if (navigator.appVersion.indexOf("X11")!=-1) OSName = "UNIX";
if (navigator.appVersion.indexOf("Linux")!=-1) OSName = "Linux";
// check if we've loaded elements already...
if (!document.getElementById("slugCopyZone")) {
document.body.insertAdjacentHTML( 'beforeend', slugTemplate );
document.body.insertAdjacentHTML( 'beforeend', menuTemplate );
document.body.insertAdjacentHTML( 'beforeend', thumbnailTemplate );
document.body.insertAdjacentHTML( 'beforeend', popedContainerTemplate );
controlsWereLoaded = false;
} else {
controlsWereLoaded = true;
}
ytEnhancerMenu = document.getElementById("enhancerMenuID");
ytEnhancerMenu2 = document.getElementById("enhancerMenuID2");
ytThumbnailBttn = document.getElementById("ytThumbnailBttn");
ytLoopBttn = document.getElementById("ytLoopBttn");
ytRangeEnd = document.getElementById("rangeEndID");
ytRangeStart = document.getElementById("rangeStartID");
ytFloatBttn = document.getElementById("ytFloatBttn");
ytDownloadBttn = document.getElementById("ytDownloadBttn");
volumeContainer = document.getElementById("volumeContainerID");
volumeLbl = document.getElementById("volumeValueLbl");
endlessPlayTag = document.getElementById("endlessPlayID");
ytThumbImgMenu = document.getElementById("ytThumbMenuID");
ytAMaxDefaultImg = document.getElementById("ytAMaxDefaultImgID");
ytAHqDefaultImg = document.getElementById("ytAHqDefaultImgID");
ytAMedDefaultImg = document.getElementById("ytAMedDefaultImgID");
ytASdDefaultImg = document.getElementById("ytASdDefaultImgID");
ytMaxDefaultImg = document.getElementById("ytMaxDefaultImgID");
ytHqDefaultImg = document.getElementById("ytHqDefaultImgID");
ytMqDefaultImg = document.getElementById("ytMqDefaultImgID");
ytSdDefaultImg = document.getElementById("ytSdDefaultImgID");
poppedContainer = document.getElementById("draggable");
ytIfrm = document.getElementById("popIframe");
slugInputTag = document.getElementById("slugCopyZone");
// Video Controler
video = document.getElementsByTagName("video")[0];
// Container of actual player (Used for floating window)
containerOfPlyrWndow = document.getElementById("player-container");
}
const setupProc = () => {
part = "https://img.youtube.com/vi/";
slugInputTag.value = video.baseURI.slice(32, 32+11);
ytRangeStart.value = "0:00";
// We need to wait for info to load before getting full duration
setTimeout(function () {
videoTimeLength = document.getElementsByClassName("ytp-time-duration")[0].innerText;
ytRangeEnd.value = videoTimeLength;
}, 2000);
// Only setting these up if we need to load controls' info
if (!controlsWereLoaded) {
setbuttonImage(ytThumbnailBttn, "/icons/thumbnailOff.png");
setbuttonImage(ytLoopBttn, "/icons/loopFalse.png");
setbuttonImage(ytFloatBttn, "/icons/floatPlayer.png");
setbuttonImage(ytDownloadBttn, "/icons/downloadVid.png");
// Set onclick actions
ytThumbnailBttn.addEventListener("click", showThumbImageVew);
ytLoopBttn.addEventListener("click", setLoop);
ytFloatBttn.addEventListener("click", toggleFloat);
endlessPlayTag.addEventListener("click", toggleEndlessPlay);
ytDownloadBttn.addEventListener("click", downloadVideo);
video.addEventListener("wheel", manageVolume);
// Dragable window for floating video event setup
dragVideo(poppedContainer);
// Remove download button if system doesn't support youtube-dl functionality
if (!OSName.includes("MacOS") && !OSName.includes("UNIX") && !OSName.includes("Linux")) {
console.log("System does not support downloader...");
ytEnhancerMenu.removeChild(ytDownloadBttn);
}
}
// ---- Hide controls if fullscreen using obsever ----
// Observer target for fullscreen action...
let targetNode = document.getElementsByTagName('ytd-app')[0];
// Create an observer instance linked to the callback function
observer = new MutationObserver(mutationCallback);
// Start observing the target node for configured mutations
observer.observe(targetNode, observerConfig);
// ---- Endless play obsever ----
// Create an observer instance linked to the callback function
observer2 = new MutationObserver(mutationCallback);
// Start observing the target node for configured mutations
if (endlessPlayTag.checked == true) {
endlessPlayTag.setAttribute("checked", true);
console.log("Endless play checked. Starting observer...");
observer2.observe(document, observerConfig2);
}
}
// Functions
// Callback function to execute when mutations are observed
const mutationCallback = (mutationsList, observer) => {
for(let mutation of mutationsList) {
if (mutation.type === 'attributes') {
controlManager(mutation.target);
} else if (mutation.type === 'childList') {
let elm = document.querySelector(dialogElementQueryRef).parentElement;
elmDisplayState = elm.style.display;
if (elmDisplayState !== 'none') {
clickDialog();
}
}
}
};
const controlManager = (elm) => {
attrib = elm.getAttribute('masthead-hidden_');
if (attrib == null) { // if out of fullscreen
ytEnhancerMenu.style.display = "block";
ytEnhancerMenu2.style.display = "block";
} else { // if fullscreen
ytEnhancerMenu.style.display = "none";
ytEnhancerMenu2.style.display = "none";
}
}
const clickDialog = () => {
console.log("Clicking dialog confirm to continue playing...");
document
.querySelector(dialogElementQueryRef)
.querySelector('yt-button-renderer[dialog-confirm]')
.click();
}
const toggleEndlessPlay = () => {
isChecked = endlessPlayTag.getAttribute("checked");
if (isChecked) {
endlessPlayTag.setAttribute("checked", false);
console.log("Stopping endless play...");
observer2.disconnect();
} else {
endlessPlayTag.setAttribute("checked", true);
console.log("Starting endless play...");
observer2.observe(confirmDialogElement, observerConfig2);
}
console.log(observer2);
}
const showThumbImageVew = (e) => {
videoSlug = video.baseURI.slice(32, 32+11); // Used for setting up thumbnails
if (ytThumbImgMenu.style.display == "block") {
ytThumbImgMenu.style.display = "none";
setbuttonImage(ytThumbnailBttn, "/icons/thumbnailOff.png");
} else {
ytAMaxDefaultImg.href = part + videoSlug + "/maxresdefault.jpg";
ytAHqDefaultImg.href = part + videoSlug + "/hqdefault.jpg";
ytAMedDefaultImg.href = part + videoSlug + "/mqdefault.jpg";
ytASdDefaultImg.href = part + videoSlug + "/sddefault.jpg";
ytMaxDefaultImg.src = part + videoSlug + "/maxresdefault.jpg";
ytHqDefaultImg.src = part + videoSlug + "/hqdefault.jpg";
ytMqDefaultImg.src = part + videoSlug + "/mqdefault.jpg";
ytSdDefaultImg.src = part + videoSlug + "/sddefault.jpg";
setbuttonImage(ytThumbnailBttn, "/icons/thumbnailOn.png");
ytThumbImgMenu.style.display = "block";
}
return;
}
const setLoop = (e) => {
let start = ytRangeStart.value.trim();
let end = ytRangeEnd.value.trim();
if (loopingInterval) {
console.log("Unsetting interval for loop check...");
clearInterval(ytVideoIntervalLoop);
loopingInterval = false;
ytLoopBttn.title = "Start Loop...";
let ipath = browser.extension.getURL("/icons/loopFalse.png");
setbuttonImage(ytLoopBttn, ipath);
return ;
}
if (start.includes("0:00") && end.includes(videoTimeLength)) {
if (video.loop == false) {
console.log("Setting default loop marker...");
video.loop = true;
ytLoopBttn.title = "Stop Loop...";
let ipath = browser.extension.getURL("/icons/loopTrue.png");
setbuttonImage(ytLoopBttn, ipath);
} else {
console.log("Unsetting default loop marker...");
video.loop = false;
ytLoopBttn.title = "Start Loop...";
let ipath = browser.extension.getURL("/icons/loopFalse.png");
setbuttonImage(ytLoopBttn, ipath);
}
return ;
} else {
const getTotalSize = (array) => {
let size = 0.0;
let hours = 0.0;
let minutes = 0.0;
let seconds = 0.0;
if (array.length === 2) {
minutes = parseFloat(array[0]) * 60.0;
seconds = parseFloat(array[1]);
size = minutes + seconds;
} else if (array.length === 3) {
hours = parseFloat(array[0]) * 3600.0;
minutes = parseFloat(array[1]) * 60.0;
seconds = parseFloat(array[2]);
size = hours + minutes + seconds;
}
return size;
}
const checkLoopStuff = () => {
videoTimeCurent = video.currentTime;
if (videoTimeCurent > totalEnd ||
videoTimeCurent < totalStart) {
video.currentTime = totalStart;
}
}
// Make sure start and end are proper
let totalStart = getTotalSize(start.split(":"));
let totalEnd = getTotalSize(end.split(":"));
if (totalStart < 0.0 || totalStart > video.duration) { return ; }
if (totalEnd < 0.0 || totalEnd > video.duration) { return ; }
// Setup interval check for 1 sec and compare value of current pos to end
ytLoopBttn.title = "Stop Loop...";
let ipath = browser.extension.getURL("/icons/loopTrue.png");
setbuttonImage(ytLoopBttn, ipath);
loopingInterval = true;
console.log("Setting interval for loop check...");
ytVideoIntervalLoop = setInterval(checkLoopStuff, 1000);
}
}
const toggleFloat = () => {
let mainPlayer = document.getElementById("player-container-outer");
var ifrmBody = (ytIfrm.contentWindow || ytIfrm.contentDocument);
if (ifrmBody.document) ifrmBody = ifrmBody.document;
if (poppedContainer.style.display == "none"){
video.pause();
mainPlayer.style.display = "none";
ytIfrm.src = "https://www.youtube.com/embed/" + video.baseURI.slice(32, 32+11) +
"?autoplay=1&amp;start=" + Math.floor(video.currentTime);
poppedContainer.style.display = "block";
} else {
video.currentTime = ifrmBody.getElementsByTagName("video")[0].currentTime;
poppedContainer.style.display = "none";
mainPlayer.style.display = "";
ytIfrm.src = "";
video.play();
}
}
const setVideoStyle = (w, h, pr) => {
let elm = document.getElementsByClassName("ytp-right-controls")[0];
video.style.width = w;
video.style.height = h;
elm.style.paddingRight = pr;
console.log("W: " + w + "\nH: " + h + "\npr: " + pr);
console.log(video.style);
}
const manageVolume = (e) => {
let delta;
e.preventDefault(); // Keep page from scrolling while in video area
shouldHideVol = false;
volumeContainer.style.display = "block";
// Detect scroll direction
let incramentType = "+";
if (e.wheelDelta) delta = e.wheelDelta; else delta = -1 * e.deltaY;
// Vol UP || Vol DOWN
let tmpVol = video.volume.toFixed(2);
let volume = Math.round((tmpVol * 100), 2).toFixed(0);
if (delta > 0) {
incramentType = "+";
while ((volume % 5) !== 0) { volume += 1; }
video.volume = (volume/100) + 0.05;
} else if (delta < 0) {
incramentType = "-";
while ((volume % 5) !== 0) { volume -= 1; }
video.volume = (volume/100) - 0.05;
}
volumeLbl.innerText = incramentType + Math.round((video.volume * 100), 1).toFixed(0);
setTimeout(function () {
if (shouldHideVol) {
volumeContainer.style.display = "none";
}
}, 1000);
shouldHideVol = true;
}
const dragVideo = (elmnt) => {
let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
elmnt.onmousedown = dragMouseDown;
function dragMouseDown(e) {
e = e || window.event;
pauseEvent(e);
// get the mouse cursor position at startup:
pos3 = e.clientX;
pos4 = e.clientY;
document.onmouseup = closeDragElement;
// call a function whenever the cursor moves:
document.onmousemove = elementDrag;
}
const elementDrag = (e) => {
e = e || window.event;
pauseEvent(e);
// calculate the new cursor position:
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
// set the element's new position:
elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
}
const closeDragElement = (e) => {
// stop moving when mouse button is released:
document.onmouseup = null;
document.onmousemove = null;
}
const pauseEvent = (e) => {
if(e.stopPropagation) e.stopPropagation();
if(e.preventDefault) e.preventDefault();
e.cancelBubble = true;
e.returnValue = false;
return false;
}
}
const downloadVideo = () => {
browser.runtime.sendMessage( { "url": video.baseURI } );
}
const setbuttonImage = (elm, path) => {
elm.style.backgroundImage = "url('" + browser.extension.getURL(path); + "')";
}
// Start init
preSetupProc();
setupProc();
}());