diff --git a/plugins/precise-volume/back.js b/plugins/precise-volume/back.js index 93ebea9f..f891ce32 100644 --- a/plugins/precise-volume/back.js +++ b/plugins/precise-volume/back.js @@ -1,23 +1,9 @@ -const { isEnabled } = require("../../config/plugins"); - /* This is used to determine if plugin is actually active (not if its only enabled in options) */ let enabled = false; -module.exports = (win) => { - enabled = true; +module.exports = () => enabled = true; - // youtube-music register some of the target listeners after DOMContentLoaded - // did-finish-load is called after all elements finished loading, including said listeners - // Thats the reason the timing is controlled from main - win.webContents.once("did-finish-load", () => { - win.webContents.send("restoreAddEventListener"); - win.webContents.send("setupVideoPlayerVolumeMousewheel", !isEnabled("hide-video-player")); - }); -}; - -module.exports.enabled = () => { - return enabled; -}; +module.exports.enabled = () => enabled; diff --git a/plugins/precise-volume/front.js b/plugins/precise-volume/front.js index f44aa643..84241fb8 100644 --- a/plugins/precise-volume/front.js +++ b/plugins/precise-volume/front.js @@ -3,25 +3,73 @@ const { ipcRenderer, remote } = require("electron"); const { setOptions } = require("../../config/plugins"); function $(selector) { return document.querySelector(selector); } +let api; module.exports = (options) => { + document.addEventListener('apiLoaded', e => { + api = e.detail; + firstRun(options); + }) +}; + +/** Restore saved volume and setup tooltip */ +function firstRun(options) { + if (typeof options.savedVolume === "number") { + // Set saved volume as tooltip + setTooltip(options.savedVolume); + + if (api.getVolume() !== options.savedVolume) { + api.setVolume(options.savedVolume); + } + } setupPlaybar(options); - setupSliderObserver(options); - setupLocalArrowShortcuts(options); setupGlobalShortcuts(options); - firstRun(options); + const noVid = $("#main-panel")?.computedStyleMap().get("display").value === "none"; + injectVolumeHud(noVid); + if (!noVid) { + setupVideoPlayerOnwheel(options); + } +} - // This way the ipc listener gets cleared either way - ipcRenderer.once("setupVideoPlayerVolumeMousewheel", (_event, toEnable) => { - if (toEnable) - setupVideoPlayerOnwheel(options); - }); -}; +function injectVolumeHud(noVid) { + if (noVid) { + const position = "top: 18px; right: 60px; z-index: 999; position: absolute;"; + const mainStyle = "font-size: xx-large; padding: 10px; transition: opacity 1s"; + + $(".center-content.ytmusic-nav-bar").insertAdjacentHTML("beforeend", + ``) + } else { + const position = `top: 10px; left: 10px; z-index: 999; position: absolute;`; + const mainStyle = "font-size: xxx-large; padding: 10px; transition: opacity 0.6s; webkit-text-stroke: 1px black; font-weight: 600;"; + + $("#song-video").insertAdjacentHTML('afterend', + ``) + } +} + +let hudFadeTimeout; + +function showVolumeHud(volume) { + let volumeHud = $("#volumeHud"); + if (!volumeHud) return; + + volumeHud.textContent = volume + '%'; + volumeHud.style.opacity = 1; + + if (hudFadeTimeout) { + clearTimeout(hudFadeTimeout); + } + + hudFadeTimeout = setTimeout(() => { + volumeHud.style.opacity = 0; + hudFadeTimeout = null; + }, 2000); +} /** Add onwheel event to video player */ function setupVideoPlayerOnwheel(options) { @@ -32,35 +80,20 @@ function setupVideoPlayerOnwheel(options) { }); } -function toPercent(volume) { - return Math.round(Number.parseFloat(volume) * 100); -} - function saveVolume(volume, options) { options.savedVolume = volume; - setOptions("precise-volume", options); + writeOptions(options); } -/** Restore saved volume and setup tooltip */ -function firstRun(options) { - const videoStream = $(".video-stream"); - const slider = $("#volume-slider"); - // Those elements load abit after DOMContentLoaded - if (videoStream && slider) { - // Set saved volume IF it pass checks - if (options.savedVolume - && options.savedVolume >= 0 && options.savedVolume <= 100 - && Math.abs(slider.value - options.savedVolume) < 5 - // If plugin was disabled and volume changed then diff>4 - ) { - videoStream.volume = options.savedVolume / 100; - slider.value = options.savedVolume; - } - // Set current volume as tooltip - setTooltip(toPercent(videoStream.volume)); - } else { - setTimeout(firstRun, 500, options); // Try again in 500 milliseconds - } +//without this function it would rewrite config 20 time when volume change by 20 +let writeTimeout; +function writeOptions(options) { + if (writeTimeout) clearTimeout(writeTimeout); + + writeTimeout = setTimeout(() => { + setOptions("precise-volume", options); + writeTimeout = null; + }, 1500) } /** Add onwheel event to play bar and also track if play bar is hovered*/ @@ -81,32 +114,63 @@ function setupPlaybar(options) { playerbar.addEventListener("mouseleave", () => { playerbar.classList.remove("on-hover"); }); + + setupSliderObserver(options); +} + +/** Save volume + Update the volume tooltip when volume-slider is manually changed */ +function setupSliderObserver(options) { + const sliderObserver = new MutationObserver(mutations => { + for (const mutation of mutations) { + // This checks that volume-slider was manually set + if (mutation.oldValue !== mutation.target.value && + (typeof options.savedVolume !== "number" || Math.abs(options.savedVolume - mutation.target.value) > 4)) { + // Diff>4 means it was manually set + setTooltip(mutation.target.value); + saveVolume(mutation.target.value, options); + } + } + }); + + // Observing only changes in 'value' of volume-slider + sliderObserver.observe($("#volume-slider"), { + attributeFilter: ["value"], + attributeOldValue: true + }); } /** if (toIncrease = false) then volume decrease */ function changeVolume(toIncrease, options) { - // Need to change both the actual volume and the slider - const videoStream = $(".video-stream"); - const slider = $("#volume-slider"); // Apply volume change if valid - const steps = (options.steps || 1) / 100; - videoStream.volume = toIncrease ? - Math.min(videoStream.volume + steps, 1) : - Math.max(videoStream.volume - steps, 0); + const steps = (options.steps || 1); + api.setVolume(toIncrease ? + Math.min(api.getVolume() + steps, 100) : + Math.max(api.getVolume() - steps, 0)); // Save the new volume - saveVolume(toPercent(videoStream.volume), options); - // Slider value automatically rounds to multiples of 5 - slider.value = options.savedVolume; + saveVolume(api.getVolume(), options); + + // change slider position (important) + updateVolumeSlider(options); + // Change tooltips to new value setTooltip(options.savedVolume); - // Show volume slider on volume change - showVolumeSlider(slider); + // Show volume slider + showVolumeSlider(); + // Show volume HUD + showVolumeHud(options.savedVolume); +} + +function updateVolumeSlider(options) { + // Slider value automatically rounds to multiples of 5 + $("#volume-slider").value = options.savedVolume > 0 && options.savedVolume < 5 ? + 5 : options.savedVolume; } let volumeHoverTimeoutID; -function showVolumeSlider(slider) { +function showVolumeSlider() { + const slider = $("#volume-slider"); // This class display the volume slider if not in minimized mode slider.classList.add("on-hover"); // Reset timeout if previous one hasn't completed @@ -122,27 +186,6 @@ function showVolumeSlider(slider) { }, 3000); } -/** Save volume + Update the volume tooltip when volume-slider is manually changed */ -function setupSliderObserver(options) { - const sliderObserver = new MutationObserver(mutations => { - for (const mutation of mutations) { - // This checks that volume-slider was manually set - if (mutation.oldValue !== mutation.target.value && - (!options.savedVolume || Math.abs(options.savedVolume - mutation.target.value) > 4)) { - // Diff>4 means it was manually set - setTooltip(mutation.target.value); - saveVolume(mutation.target.value, options); - } - } - }); - - // Observing only changes in 'value' of volume-slider - sliderObserver.observe($("#volume-slider"), { - attributeFilter: ["value"], - attributeOldValue: true - }); -} - // Set new volume as tooltip for volume slider and icon + expanding slider (appears when window size is small) const tooltipTargets = [ "#volume-slider", diff --git a/plugins/precise-volume/preload.js b/plugins/precise-volume/preload.js index edc5f20c..6a0fd482 100644 --- a/plugins/precise-volume/preload.js +++ b/plugins/precise-volume/preload.js @@ -24,10 +24,10 @@ function overrideAddEventListener() { module.exports = () => { overrideAddEventListener(); - // Restore original function after did-finish-load to avoid keeping Element.prototype altered - ipcRenderer.once("restoreAddEventListener", () => { // Called from main to make sure page is completly loaded + // Restore original function after finished loading to avoid keeping Element.prototype altered + window.addEventListener('load', () => { Element.prototype.addEventListener = Element.prototype._addEventListener; Element.prototype._addEventListener = undefined; ignored = undefined; - }); + }, { once: true }); }; diff --git a/preload.js b/preload.js index 5a09c845..67ae8f7c 100644 --- a/preload.js +++ b/preload.js @@ -10,6 +10,8 @@ const setupSongInfo = require("./providers/song-info-front"); const plugins = config.plugins.getEnabled(); +let api; + plugins.forEach(([plugin, options]) => { const preloadPath = path.join(__dirname, "plugins", plugin, "preload.js"); fileExists(preloadPath, () => { @@ -38,6 +40,9 @@ document.addEventListener("DOMContentLoaded", () => { }); }); + // wait for complete load of youtube api + listenForApiLoad(); + // inject song-info provider setupSongInfo(); @@ -51,3 +56,25 @@ document.addEventListener("DOMContentLoaded", () => { global.reload = () => remote.getCurrentWindow().webContents.loadURL(config.get("url")); }); + +function listenForApiLoad() { + api = document.querySelector('#movie_player'); + if (api) { + onApiLoaded(); + return; + } + + const observer = new MutationObserver(() => { + api = document.querySelector('#movie_player'); + if (api) { + observer.disconnect(); + onApiLoaded(); + } + }) + + observer.observe(document.documentElement, { childList: true, subtree: true }); +} + +function onApiLoaded() { + document.dispatchEvent(new CustomEvent('apiLoaded', { detail: api })); +}