464 lines
20 KiB
JavaScript
464 lines
20 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: </label>
|
|
<input type="text" id="rangeStartID"/>
|
|
<label>Loop Range End: </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, loopingInterval;
|
|
|
|
let poppedContainer, videoSlug, volumeLbl, part;
|
|
let modalHasBeenClosed = false;
|
|
// Default to false b/c if tag checked it sets this in setupProcess and elsewhere.
|
|
let isEndlessWatch = 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 };
|
|
|
|
|
|
// Start init
|
|
let existCondition = setInterval(function() {
|
|
if (document.getElementById("masthead-container")) {
|
|
clearInterval(existCondition);
|
|
setupProcess();
|
|
count = 0;
|
|
} else {
|
|
if (count == 12) { // ~ 6 sec through half sec loops
|
|
count = 0;
|
|
clearInterval(existCondition);
|
|
setupProcess();
|
|
} else {
|
|
count++;
|
|
}
|
|
}
|
|
}, 500); // check every 500ms
|
|
|
|
const setupProcess = () => {
|
|
loadUI();
|
|
setupVariables();
|
|
fillUIAndSetupEvents();
|
|
setupObservers();
|
|
}
|
|
|
|
const loadUI = () => {
|
|
// 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 );
|
|
controlsAreLoaded = false;
|
|
} else {
|
|
controlsAreLoaded = true;
|
|
}
|
|
|
|
// Remove download button if system doesn't support youtube-dl functionality
|
|
ytEnhancerMenu = document.getElementById("enhancerMenuID");
|
|
if (!OSName.includes("MacOS") && !OSName.includes("UNIX") && !OSName.includes("Linux")) {
|
|
console.log("System does not support downloader...");
|
|
ytEnhancerMenu.removeChild(ytDownloadBttn);
|
|
}
|
|
}
|
|
|
|
const setupVariables = () => {
|
|
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]; // Actual video object (I think...)
|
|
|
|
// Container of actual player (Used for floating window)
|
|
containerOfPlyrWndow = document.getElementById("player-container");
|
|
part = "https://img.youtube.com/vi/";
|
|
}
|
|
|
|
const fillUIAndSetupEvents = () => {
|
|
slugInputTag.value = video.baseURI.slice(32, 32+11);
|
|
ytRangeStart.value = "0:00";
|
|
|
|
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 (!controlsAreLoaded) {
|
|
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", setVideoLoopState);
|
|
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);
|
|
}
|
|
}
|
|
|
|
const setupObservers = () => {
|
|
// ---- 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) {
|
|
this.isEndlessWatch = 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') {
|
|
controlsShowHideToggle(mutation.target);
|
|
} else if (mutation.type === 'childList') {
|
|
if (this.isEndlessWatch) {
|
|
let elm = document.querySelector(dialogElementQueryRef).parentElement;
|
|
elmDisplayState = elm.style.display;
|
|
if (elmDisplayState !== 'none' && modalHasBeenClosed == false) {
|
|
modalHasBeenClosed = true;
|
|
clickDialog();
|
|
setTimeout(function () {
|
|
modalHasBeenClosed = false;
|
|
}, 5000);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
const controlsShowHideToggle = (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 = () => {
|
|
if (this.isEndlessWatch) {
|
|
endlessPlayTag.setAttribute("checked", false);
|
|
this.isEndlessWatch = false;
|
|
console.log("Stopping endless play...");
|
|
observer2.disconnect();
|
|
} else {
|
|
endlessPlayTag.setAttribute("checked", true);
|
|
this.isEndlessWatch = true;
|
|
console.log("Starting endless play...");
|
|
observer2.observe(confirmDialogElement, observerConfig2);
|
|
}
|
|
}
|
|
|
|
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 setVideoLoopState = (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&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 = () => {
|
|
console.log("Downloading: " + video.baseURI);
|
|
browser.runtime.sendMessage( { "url": video.baseURI } );
|
|
}
|
|
|
|
const setbuttonImage = (elm, path) => {
|
|
elm.style.backgroundImage = "url('" + browser.extension.getURL(path); + "')";
|
|
}
|
|
}());
|